wip: app permissions changes
parent
0ea4a769de
commit
16c615fd47
|
|
@ -15,7 +15,7 @@ import {
|
|||
type McpStatus,
|
||||
type LspStatus,
|
||||
type VcsInfo,
|
||||
type Permission,
|
||||
type PermissionRequest,
|
||||
createOpencodeClient,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
|
|
@ -46,7 +46,7 @@ type State = {
|
|||
[sessionID: string]: Todo[]
|
||||
}
|
||||
permission: {
|
||||
[sessionID: string]: Permission[]
|
||||
[sessionID: string]: PermissionRequest[]
|
||||
}
|
||||
mcp: {
|
||||
[name: string]: McpStatus
|
||||
|
|
@ -168,7 +168,7 @@ function createGlobalSync() {
|
|||
vcs: () => sdk.vcs.get().then((x) => setStore("vcs", x.data)),
|
||||
permission: () =>
|
||||
sdk.permission.list().then((x) => {
|
||||
const grouped: Record<string, Permission[]> = {}
|
||||
const grouped: Record<string, PermissionRequest[]> = {}
|
||||
for (const perm of x.data ?? []) {
|
||||
if (!perm?.id || !perm.sessionID) continue
|
||||
const existing = grouped[perm.sessionID]
|
||||
|
|
@ -349,7 +349,7 @@ function createGlobalSync() {
|
|||
setStore("vcs", { branch: event.properties.branch })
|
||||
break
|
||||
}
|
||||
case "permission.updated": {
|
||||
case "permission.asked": {
|
||||
const sessionID = event.properties.sessionID
|
||||
const permissions = store.permission[sessionID]
|
||||
if (!permissions) {
|
||||
|
|
@ -375,7 +375,7 @@ function createGlobalSync() {
|
|||
case "permission.replied": {
|
||||
const permissions = store.permission[event.properties.sessionID]
|
||||
if (!permissions) break
|
||||
const result = Binary.search(permissions, event.properties.permissionID, (p) => p.id)
|
||||
const result = Binary.search(permissions, event.properties.requestID, (p) => p.id)
|
||||
if (!result.found) break
|
||||
setStore(
|
||||
"permission",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createMemo, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import type { Permission } from "@opencode-ai/sdk/v2/client"
|
||||
import type { PermissionRequest } from "@opencode-ai/sdk/v2/client"
|
||||
import { persisted } from "@/utils/persist"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
|
|
@ -14,10 +14,8 @@ type PermissionRespondFn = (input: {
|
|||
directory?: string
|
||||
}) => void
|
||||
|
||||
const AUTO_ACCEPT_TYPES = new Set(["edit", "write"])
|
||||
|
||||
function shouldAutoAccept(perm: Permission) {
|
||||
return AUTO_ACCEPT_TYPES.has(perm.type)
|
||||
function shouldAutoAccept(perm: PermissionRequest) {
|
||||
return perm.permission === "edit"
|
||||
}
|
||||
|
||||
export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
|
||||
|
|
@ -48,7 +46,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||
})
|
||||
}
|
||||
|
||||
function respondOnce(permission: Permission, directory?: string) {
|
||||
function respondOnce(permission: PermissionRequest, directory?: string) {
|
||||
if (responded.has(permission.id)) return
|
||||
responded.add(permission.id)
|
||||
respond({
|
||||
|
|
@ -65,7 +63,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||
|
||||
const unsubscribe = globalSDK.event.listen((e) => {
|
||||
const event = e.details
|
||||
if (event?.type !== "permission.updated") return
|
||||
if (event?.type !== "permission.asked") return
|
||||
|
||||
const perm = event.properties
|
||||
if (!isAutoAccepting(perm.sessionID)) return
|
||||
|
|
@ -98,7 +96,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||
return {
|
||||
ready,
|
||||
respond,
|
||||
autoResponds(permission: Permission) {
|
||||
autoResponds(permission: PermissionRequest) {
|
||||
return isAutoAccepting(permission.sessionID) && shouldAutoAccept(permission)
|
||||
},
|
||||
isAutoAccepting,
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export default function Layout(props: ParentProps) {
|
|||
const permissionAlertCooldownMs = 5000
|
||||
|
||||
const unsub = globalSDK.event.listen((e) => {
|
||||
if (e.details?.type !== "permission.updated") return
|
||||
if (e.details?.type !== "permission.asked") return
|
||||
const directory = e.name
|
||||
const perm = e.details.properties
|
||||
if (permission.autoResponds(perm)) return
|
||||
|
|
|
|||
|
|
@ -83,9 +83,9 @@ export namespace PermissionNext {
|
|||
})
|
||||
|
||||
export const Event = {
|
||||
Asked: BusEvent.define("permission.next.asked", Request),
|
||||
Asked: BusEvent.define("permission.asked", Request),
|
||||
Replied: BusEvent.define(
|
||||
"permission.next.replied",
|
||||
"permission.replied",
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
requestID: z.string(),
|
||||
|
|
|
|||
|
|
@ -1596,6 +1596,8 @@ export class Permission extends HeyApiClient {
|
|||
* Respond to permission
|
||||
*
|
||||
* Approve or deny a permission request from the AI assistant.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public respond<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
|
|
|
|||
|
|
@ -466,47 +466,17 @@ export type PermissionRequest = {
|
|||
}
|
||||
}
|
||||
|
||||
export type EventPermissionNextAsked = {
|
||||
type: "permission.next.asked"
|
||||
export type EventPermissionAsked = {
|
||||
type: "permission.asked"
|
||||
properties: PermissionRequest
|
||||
}
|
||||
|
||||
export type EventPermissionNextReplied = {
|
||||
type: "permission.next.replied"
|
||||
properties: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
reply: "once" | "always" | "reject"
|
||||
}
|
||||
}
|
||||
|
||||
export type Permission = {
|
||||
id: string
|
||||
type: string
|
||||
pattern?: string | Array<string>
|
||||
sessionID: string
|
||||
messageID: string
|
||||
callID?: string
|
||||
message: string
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPermissionUpdated = {
|
||||
type: "permission.updated"
|
||||
properties: Permission
|
||||
}
|
||||
|
||||
export type EventPermissionReplied = {
|
||||
type: "permission.replied"
|
||||
properties: {
|
||||
sessionID: string
|
||||
permissionID: string
|
||||
response: string
|
||||
requestID: string
|
||||
reply: "once" | "always" | "reject"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -796,9 +766,7 @@ export type Event =
|
|||
| EventMessageRemoved
|
||||
| EventMessagePartUpdated
|
||||
| EventMessagePartRemoved
|
||||
| EventPermissionNextAsked
|
||||
| EventPermissionNextReplied
|
||||
| EventPermissionUpdated
|
||||
| EventPermissionAsked
|
||||
| EventPermissionReplied
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
|
|
@ -1248,6 +1216,7 @@ export type PermissionConfig =
|
|||
webfetch?: PermissionActionConfig
|
||||
websearch?: PermissionActionConfig
|
||||
codesearch?: PermissionActionConfig
|
||||
lsp?: PermissionRuleConfig
|
||||
doom_loop?: PermissionActionConfig
|
||||
[key: string]: PermissionRuleConfig | PermissionActionConfig | undefined
|
||||
}
|
||||
|
|
@ -3457,7 +3426,7 @@ export type PermissionListResponses = {
|
|||
/**
|
||||
* List of pending permissions
|
||||
*/
|
||||
200: Array<Permission>
|
||||
200: Array<PermissionRequest>
|
||||
}
|
||||
|
||||
export type PermissionListResponse = PermissionListResponses[keyof PermissionListResponses]
|
||||
|
|
|
|||
|
|
@ -455,8 +455,8 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
|
|||
|
||||
const permission = createMemo(() => {
|
||||
const next = data.store.permission?.[props.message.sessionID]?.[0]
|
||||
if (!next) return undefined
|
||||
if (next.callID !== part.callID) return undefined
|
||||
if (!next || !next.tool) return undefined
|
||||
if (next.tool!.callID !== part.callID) return undefined
|
||||
return next
|
||||
})
|
||||
|
||||
|
|
@ -732,19 +732,20 @@ ToolRegistry.register({
|
|||
|
||||
const childToolPart = createMemo(() => {
|
||||
const perm = childPermission()
|
||||
if (!perm) return undefined
|
||||
if (!perm || !perm.tool) return undefined
|
||||
const sessionId = childSessionId()
|
||||
if (!sessionId) return undefined
|
||||
// Find the tool part that matches the permission's callID
|
||||
const messages = data.store.message[sessionId] ?? []
|
||||
for (const msg of messages) {
|
||||
const parts = data.store.part[msg.id] ?? []
|
||||
for (const part of parts) {
|
||||
if (part.type === "tool" && (part as ToolPart).callID === perm.callID) {
|
||||
return { part: part as ToolPart, message: msg }
|
||||
}
|
||||
const message = messages.findLast((m) => m.id === perm.tool!.messageID)
|
||||
if (!message) return undefined
|
||||
const parts = data.store.part[message.id] ?? []
|
||||
for (const part of parts) {
|
||||
if (part.type === "tool" && (part as ToolPart).callID === perm.tool!.callID) {
|
||||
return { part: part as ToolPart, message }
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
AssistantMessage,
|
||||
Message as MessageType,
|
||||
Part as PartType,
|
||||
type Permission,
|
||||
type PermissionRequest,
|
||||
TextPart,
|
||||
ToolPart,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
|
|
@ -132,7 +132,7 @@ export function SessionTurn(
|
|||
const emptyMessages: MessageType[] = []
|
||||
const emptyParts: PartType[] = []
|
||||
const emptyAssistant: AssistantMessage[] = []
|
||||
const emptyPermissions: Permission[] = []
|
||||
const emptyPermissions: PermissionRequest[] = []
|
||||
const emptyPermissionParts: { part: ToolPart; message: AssistantMessage }[] = []
|
||||
const idle = { type: "idle" as const }
|
||||
|
||||
|
|
@ -235,16 +235,18 @@ export function SessionTurn(
|
|||
if (props.stepsExpanded) return emptyPermissionParts
|
||||
|
||||
const next = nextPermission()
|
||||
if (!next) return emptyPermissionParts
|
||||
if (!next || !next.tool) return emptyPermissionParts
|
||||
|
||||
for (const message of assistantMessages()) {
|
||||
const parts = data.store.part[message.id] ?? emptyParts
|
||||
for (const part of parts) {
|
||||
if (part?.type !== "tool") continue
|
||||
const tool = part as ToolPart
|
||||
if (tool.callID === next.callID) return [{ part: tool, message }]
|
||||
}
|
||||
const message = assistantMessages().findLast((m) => m.id === next.tool!.messageID)
|
||||
if (!message) return emptyPermissionParts
|
||||
|
||||
const parts = data.store.part[message.id] ?? emptyParts
|
||||
for (const part of parts) {
|
||||
if (part?.type !== "tool") continue
|
||||
const tool = part as ToolPart
|
||||
if (tool.callID === next.tool?.callID) return [{ part: tool, message }]
|
||||
}
|
||||
|
||||
return emptyPermissionParts
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Message, Session, Part, FileDiff, SessionStatus, Permission } from "@opencode-ai/sdk/v2"
|
||||
import type { Message, Session, Part, FileDiff, SessionStatus, PermissionRequest } from "@opencode-ai/sdk/v2"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ type Data = {
|
|||
[sessionID: string]: PreloadMultiFileDiffResult<any>[]
|
||||
}
|
||||
permission?: {
|
||||
[sessionID: string]: Permission[]
|
||||
[sessionID: string]: PermissionRequest[]
|
||||
}
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
|
|
|
|||
Loading…
Reference in New Issue