Update UI permissions for SDK 1.0.166
Handle permission.asked events and requestID replies while keeping legacy compatibility.
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -7476,7 +7476,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git-diff-view/solid": "^0.0.8",
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "^1.0.138",
|
"@opencode-ai/sdk": "1.0.166",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
"@suid/icons-material": "^0.9.0",
|
"@suid/icons-material": "^0.9.0",
|
||||||
"@suid/material": "^0.19.0",
|
"@suid/material": "^0.19.0",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git-diff-view/solid": "^0.0.8",
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@opencode-ai/sdk": "^1.0.138",
|
"@opencode-ai/sdk": "1.0.166",
|
||||||
"@solidjs/router": "^0.13.0",
|
"@solidjs/router": "^0.13.0",
|
||||||
"@suid/icons-material": "^0.9.0",
|
"@suid/icons-material": "^0.9.0",
|
||||||
"@suid/material": "^0.19.0",
|
"@suid/material": "^0.19.0",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useGlobalCache } from "../lib/hooks/use-global-cache"
|
|||||||
import { useConfig } from "../stores/preferences"
|
import { useConfig } from "../stores/preferences"
|
||||||
import type { DiffViewMode } from "../stores/preferences"
|
import type { DiffViewMode } from "../stores/preferences"
|
||||||
import { sendPermissionResponse } from "../stores/instances"
|
import { sendPermissionResponse } from "../stores/instances"
|
||||||
|
import { getPermissionDisplayTitle, getPermissionKind, getPermissionSessionId } from "../types/permission"
|
||||||
import type { TextPart, RenderCache } from "../types/message"
|
import type { TextPart, RenderCache } from "../types/message"
|
||||||
import { resolveToolRenderer } from "./tool-call/renderers"
|
import { resolveToolRenderer } from "./tool-call/renderers"
|
||||||
import type {
|
import type {
|
||||||
@@ -837,7 +838,7 @@ export default function ToolCall(props: ToolCallProps) {
|
|||||||
setPermissionSubmitting(true)
|
setPermissionSubmitting(true)
|
||||||
setPermissionError(null)
|
setPermissionError(null)
|
||||||
try {
|
try {
|
||||||
const sessionId = permission.sessionID || props.sessionId
|
const sessionId = getPermissionSessionId(permission) || props.sessionId
|
||||||
await sendPermissionResponse(props.instanceId, sessionId, permission.id, response)
|
await sendPermissionResponse(props.instanceId, sessionId, permission.id, response)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to send permission response", error)
|
log.error("Failed to send permission response", error)
|
||||||
@@ -882,11 +883,11 @@ export default function ToolCall(props: ToolCallProps) {
|
|||||||
<div class={`tool-call-permission ${active ? "tool-call-permission-active" : "tool-call-permission-queued"}`}>
|
<div class={`tool-call-permission ${active ? "tool-call-permission-active" : "tool-call-permission-queued"}`}>
|
||||||
<div class="tool-call-permission-header">
|
<div class="tool-call-permission-header">
|
||||||
<span class="tool-call-permission-label">{active ? "Permission Required" : "Permission Queued"}</span>
|
<span class="tool-call-permission-label">{active ? "Permission Required" : "Permission Queued"}</span>
|
||||||
<span class="tool-call-permission-type">{permission.type}</span>
|
<span class="tool-call-permission-type">{getPermissionKind(permission)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-call-permission-body">
|
<div class="tool-call-permission-body">
|
||||||
<div class="tool-call-permission-title">
|
<div class="tool-call-permission-title">
|
||||||
<code>{permission.title}</code>
|
<code>{getPermissionDisplayTitle(permission)}</code>
|
||||||
</div>
|
</div>
|
||||||
<Show when={diffPayload}>
|
<Show when={diffPayload}>
|
||||||
{(payload) => (
|
{(payload) => (
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import {
|
|||||||
} from "../types/message"
|
} from "../types/message"
|
||||||
import type {
|
import type {
|
||||||
EventLspUpdated,
|
EventLspUpdated,
|
||||||
EventPermissionReplied,
|
|
||||||
EventPermissionUpdated,
|
|
||||||
EventSessionCompacted,
|
EventSessionCompacted,
|
||||||
EventSessionError,
|
EventSessionError,
|
||||||
EventSessionIdle,
|
EventSessionIdle,
|
||||||
@@ -62,8 +61,8 @@ type SSEEvent =
|
|||||||
| EventSessionCompacted
|
| EventSessionCompacted
|
||||||
| EventSessionError
|
| EventSessionError
|
||||||
| EventSessionIdle
|
| EventSessionIdle
|
||||||
| EventPermissionUpdated
|
| { type: "permission.updated" | "permission.asked"; properties?: any }
|
||||||
| EventPermissionReplied
|
| { type: "permission.replied"; properties?: any }
|
||||||
| EventLspUpdated
|
| EventLspUpdated
|
||||||
| TuiToastEvent
|
| TuiToastEvent
|
||||||
| BackgroundProcessUpdatedEvent
|
| BackgroundProcessUpdatedEvent
|
||||||
@@ -139,10 +138,11 @@ class SSEManager {
|
|||||||
this.onSessionStatus?.(instanceId, event as EventSessionStatus)
|
this.onSessionStatus?.(instanceId, event as EventSessionStatus)
|
||||||
break
|
break
|
||||||
case "permission.updated":
|
case "permission.updated":
|
||||||
this.onPermissionUpdated?.(instanceId, event as EventPermissionUpdated)
|
case "permission.asked":
|
||||||
|
this.onPermissionUpdated?.(instanceId, event as any)
|
||||||
break
|
break
|
||||||
case "permission.replied":
|
case "permission.replied":
|
||||||
this.onPermissionReplied?.(instanceId, event as EventPermissionReplied)
|
this.onPermissionReplied?.(instanceId, event as any)
|
||||||
break
|
break
|
||||||
case "lsp.updated":
|
case "lsp.updated":
|
||||||
this.onLspUpdated?.(instanceId, event as EventLspUpdated)
|
this.onLspUpdated?.(instanceId, event as EventLspUpdated)
|
||||||
@@ -176,8 +176,8 @@ class SSEManager {
|
|||||||
onTuiToast?: (instanceId: string, event: TuiToastEvent) => void
|
onTuiToast?: (instanceId: string, event: TuiToastEvent) => void
|
||||||
onSessionIdle?: (instanceId: string, event: EventSessionIdle) => void
|
onSessionIdle?: (instanceId: string, event: EventSessionIdle) => void
|
||||||
onSessionStatus?: (instanceId: string, event: EventSessionStatus) => void
|
onSessionStatus?: (instanceId: string, event: EventSessionStatus) => void
|
||||||
onPermissionUpdated?: (instanceId: string, event: EventPermissionUpdated) => void
|
onPermissionUpdated?: (instanceId: string, event: any) => void
|
||||||
onPermissionReplied?: (instanceId: string, event: EventPermissionReplied) => void
|
onPermissionReplied?: (instanceId: string, event: any) => void
|
||||||
onLspUpdated?: (instanceId: string, event: EventLspUpdated) => void
|
onLspUpdated?: (instanceId: string, event: EventLspUpdated) => void
|
||||||
onBackgroundProcessUpdated?: (instanceId: string, event: BackgroundProcessUpdatedEvent) => void
|
onBackgroundProcessUpdated?: (instanceId: string, event: BackgroundProcessUpdatedEvent) => void
|
||||||
onBackgroundProcessRemoved?: (instanceId: string, event: BackgroundProcessRemovedEvent) => void
|
onBackgroundProcessRemoved?: (instanceId: string, event: BackgroundProcessRemovedEvent) => void
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createSignal } from "solid-js"
|
import { createSignal } from "solid-js"
|
||||||
import type { Instance, LogEntry } from "../types/instance"
|
import type { Instance, LogEntry } from "../types/instance"
|
||||||
import type { LspStatus, Permission } from "@opencode-ai/sdk"
|
import type { LspStatus } from "@opencode-ai/sdk"
|
||||||
|
import type { PermissionReply, PermissionRequestLike } from "../types/permission"
|
||||||
|
import { getPermissionCreatedAt, getPermissionSessionId } from "../types/permission"
|
||||||
import { sdkManager } from "../lib/sdk-manager"
|
import { sdkManager } from "../lib/sdk-manager"
|
||||||
import { sseManager } from "../lib/sse-manager"
|
import { sseManager } from "../lib/sse-manager"
|
||||||
import { serverApi } from "../lib/api-client"
|
import { serverApi } from "../lib/api-client"
|
||||||
@@ -31,7 +33,7 @@ const [instanceLogs, setInstanceLogs] = createSignal<Map<string, LogEntry[]>>(ne
|
|||||||
const [logStreamingState, setLogStreamingState] = createSignal<Map<string, boolean>>(new Map())
|
const [logStreamingState, setLogStreamingState] = createSignal<Map<string, boolean>>(new Map())
|
||||||
|
|
||||||
// Permission queue management per instance
|
// Permission queue management per instance
|
||||||
const [permissionQueues, setPermissionQueues] = createSignal<Map<string, Permission[]>>(new Map())
|
const [permissionQueues, setPermissionQueues] = createSignal<Map<string, PermissionRequestLike[]>>(new Map())
|
||||||
const [activePermissionId, setActivePermissionId] = createSignal<Map<string, string | null>>(new Map())
|
const [activePermissionId, setActivePermissionId] = createSignal<Map<string, string | null>>(new Map())
|
||||||
const permissionSessionCounts = new Map<string, Map<string, number>>()
|
const permissionSessionCounts = new Map<string, Map<string, number>>()
|
||||||
|
|
||||||
@@ -382,7 +384,7 @@ function clearLogs(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Permission management functions
|
// Permission management functions
|
||||||
function getPermissionQueue(instanceId: string): Permission[] {
|
function getPermissionQueue(instanceId: string): PermissionRequestLike[] {
|
||||||
const queue = permissionQueues().get(instanceId)
|
const queue = permissionQueues().get(instanceId)
|
||||||
if (!queue) {
|
if (!queue) {
|
||||||
return []
|
return []
|
||||||
@@ -429,7 +431,7 @@ function clearSessionPendingCounts(instanceId: string): void {
|
|||||||
permissionSessionCounts.delete(instanceId)
|
permissionSessionCounts.delete(instanceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPermissionToQueue(instanceId: string, permission: Permission): void {
|
function addPermissionToQueue(instanceId: string, permission: PermissionRequestLike): void {
|
||||||
let inserted = false
|
let inserted = false
|
||||||
|
|
||||||
setPermissionQueues((prev) => {
|
setPermissionQueues((prev) => {
|
||||||
@@ -440,7 +442,7 @@ function addPermissionToQueue(instanceId: string, permission: Permission): void
|
|||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedQueue = [...queue, permission].sort((a, b) => a.time.created - b.time.created)
|
const updatedQueue = [...queue, permission].sort((a, b) => getPermissionCreatedAt(a) - getPermissionCreatedAt(b))
|
||||||
next.set(instanceId, updatedQueue)
|
next.set(instanceId, updatedQueue)
|
||||||
inserted = true
|
inserted = true
|
||||||
return next
|
return next
|
||||||
@@ -459,17 +461,19 @@ function addPermissionToQueue(instanceId: string, permission: Permission): void
|
|||||||
})
|
})
|
||||||
|
|
||||||
const sessionId = getPermissionSessionId(permission)
|
const sessionId = getPermissionSessionId(permission)
|
||||||
incrementSessionPendingCount(instanceId, sessionId)
|
if (sessionId) {
|
||||||
setSessionPendingPermission(instanceId, sessionId, true)
|
incrementSessionPendingCount(instanceId, sessionId)
|
||||||
|
setSessionPendingPermission(instanceId, sessionId, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePermissionFromQueue(instanceId: string, permissionId: string): void {
|
function removePermissionFromQueue(instanceId: string, permissionId: string): void {
|
||||||
let removedPermission: Permission | null = null
|
let removedPermission: PermissionRequestLike | null = null
|
||||||
|
|
||||||
setPermissionQueues((prev) => {
|
setPermissionQueues((prev) => {
|
||||||
const next = new Map(prev)
|
const next = new Map(prev)
|
||||||
const queue = next.get(instanceId) ?? []
|
const queue = next.get(instanceId) ?? []
|
||||||
const filtered: Permission[] = []
|
const filtered: PermissionRequestLike[] = []
|
||||||
|
|
||||||
for (const item of queue) {
|
for (const item of queue) {
|
||||||
if (item.id === permissionId) {
|
if (item.id === permissionId) {
|
||||||
@@ -493,7 +497,7 @@ function removePermissionFromQueue(instanceId: string, permissionId: string): vo
|
|||||||
const next = new Map(prev)
|
const next = new Map(prev)
|
||||||
const activeId = next.get(instanceId)
|
const activeId = next.get(instanceId)
|
||||||
if (activeId === permissionId) {
|
if (activeId === permissionId) {
|
||||||
const nextPermission = updatedQueue.length > 0 ? (updatedQueue[0] as Permission) : null
|
const nextPermission = updatedQueue.length > 0 ? (updatedQueue[0] as PermissionRequestLike) : null
|
||||||
next.set(instanceId, nextPermission?.id ?? null)
|
next.set(instanceId, nextPermission?.id ?? null)
|
||||||
}
|
}
|
||||||
return next
|
return next
|
||||||
@@ -502,8 +506,10 @@ function removePermissionFromQueue(instanceId: string, permissionId: string): vo
|
|||||||
const removed = removedPermission
|
const removed = removedPermission
|
||||||
if (removed) {
|
if (removed) {
|
||||||
const removedSessionId = getPermissionSessionId(removed)
|
const removedSessionId = getPermissionSessionId(removed)
|
||||||
const remaining = decrementSessionPendingCount(instanceId, removedSessionId)
|
if (removedSessionId) {
|
||||||
setSessionPendingPermission(instanceId, removedSessionId, remaining > 0)
|
const remaining = decrementSessionPendingCount(instanceId, removedSessionId)
|
||||||
|
setSessionPendingPermission(instanceId, removedSessionId, remaining > 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,29 +527,55 @@ function clearPermissionQueue(instanceId: string): void {
|
|||||||
clearSessionPendingCounts(instanceId)
|
clearSessionPendingCounts(instanceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPermissionSessionId(permission: Permission): string {
|
|
||||||
return (permission as any).sessionID
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendPermissionResponse(
|
async function sendPermissionResponse(
|
||||||
instanceId: string,
|
instanceId: string,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
permissionId: string,
|
requestId: string,
|
||||||
response: "once" | "always" | "reject"
|
reply: PermissionReply
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const instance = instances().get(instanceId)
|
const instance = instances().get(instanceId)
|
||||||
if (!instance?.client) {
|
if (!instance?.client) {
|
||||||
throw new Error("Instance not ready")
|
throw new Error("Instance not ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client: any = instance.client
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await instance.client.postSessionIdPermissionsPermissionId({
|
// New API (preferred): POST /permission/:requestID/reply
|
||||||
path: { id: sessionId, permissionID: permissionId },
|
if (typeof client.postPermissionRequestIdReply === "function") {
|
||||||
body: { response },
|
await client.postPermissionRequestIdReply({
|
||||||
})
|
path: { requestID: requestId },
|
||||||
|
body: { reply },
|
||||||
|
})
|
||||||
|
} else if (typeof client.postPermissionRequestIDReply === "function") {
|
||||||
|
await client.postPermissionRequestIDReply({
|
||||||
|
path: { requestID: requestId },
|
||||||
|
body: { reply },
|
||||||
|
})
|
||||||
|
} else if (typeof client.postPermissionRequestIdReply2 === "function") {
|
||||||
|
await client.postPermissionRequestIdReply2({
|
||||||
|
path: { requestID: requestId },
|
||||||
|
body: { reply },
|
||||||
|
})
|
||||||
|
} else if (client.permission && typeof client.permission.reply === "function") {
|
||||||
|
await client.permission.reply({
|
||||||
|
path: { requestID: requestId },
|
||||||
|
body: { reply },
|
||||||
|
})
|
||||||
|
} else if (typeof client.postSessionIdPermissionsPermissionId === "function") {
|
||||||
|
// Legacy API fallback: POST /session/:sessionID/permissions/:permissionID
|
||||||
|
await client.postSessionIdPermissionsPermissionId({
|
||||||
|
path: { id: sessionId, permissionID: requestId },
|
||||||
|
body: { response: reply },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error("Unsupported permissions API in client")
|
||||||
|
}
|
||||||
|
|
||||||
// Remove from queue after successful response
|
// Remove from queue after successful response
|
||||||
removePermissionFromQueue(instanceId, permissionId)
|
removePermissionFromQueue(instanceId, requestId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to send permission response", error)
|
log.error("Failed to send permission response", error)
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Permission } from "@opencode-ai/sdk"
|
import type { PermissionRequestLike } from "../../types/permission"
|
||||||
|
import { getPermissionCallId, getPermissionMessageId } from "../../types/permission"
|
||||||
import type { Message, MessageInfo, ClientPart } from "../../types/message"
|
import type { Message, MessageInfo, ClientPart } from "../../types/message"
|
||||||
import type { Session } from "../../types/session"
|
import type { Session } from "../../types/session"
|
||||||
import { messageStoreBus } from "./bus"
|
import { messageStoreBus } from "./bus"
|
||||||
@@ -107,11 +108,11 @@ export function replaceMessageIdV2(instanceId: string, oldId: string, newId: str
|
|||||||
store.replaceMessageId({ oldId, newId })
|
store.replaceMessageId({ oldId, newId })
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPermissionMessageId(permission: Permission): string | undefined {
|
function extractPermissionMessageId(permission: PermissionRequestLike): string | undefined {
|
||||||
return (permission as any).messageID || (permission as any).messageId
|
return getPermissionMessageId(permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPermissionPartId(permission: Permission): string | undefined {
|
function extractPermissionPartId(permission: PermissionRequestLike): string | undefined {
|
||||||
const metadata = (permission as any).metadata || {}
|
const metadata = (permission as any).metadata || {}
|
||||||
return (
|
return (
|
||||||
(permission as any).partID ||
|
(permission as any).partID ||
|
||||||
@@ -122,17 +123,8 @@ function extractPermissionPartId(permission: Permission): string | undefined {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPermissionCallId(permission: Permission): string | undefined {
|
function extractPermissionCallId(permission: PermissionRequestLike): string | undefined {
|
||||||
const metadata = (permission as any).metadata || {}
|
return getPermissionCallId(permission)
|
||||||
return (
|
|
||||||
(permission as any).callID ||
|
|
||||||
(permission as any).callId ||
|
|
||||||
(permission as any).toolCallID ||
|
|
||||||
(permission as any).toolCallId ||
|
|
||||||
metadata.callID ||
|
|
||||||
metadata.callId ||
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePartIdFromCallId(store: ReturnType<typeof messageStoreBus.getOrCreate>, messageId?: string, callId?: string): string | undefined {
|
function resolvePartIdFromCallId(store: ReturnType<typeof messageStoreBus.getOrCreate>, messageId?: string, callId?: string): string | undefined {
|
||||||
@@ -155,7 +147,7 @@ function resolvePartIdFromCallId(store: ReturnType<typeof messageStoreBus.getOrC
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function upsertPermissionV2(instanceId: string, permission: Permission): void {
|
export function upsertPermissionV2(instanceId: string, permission: PermissionRequestLike): void {
|
||||||
if (!permission) return
|
if (!permission) return
|
||||||
const store = messageStoreBus.getOrCreate(instanceId)
|
const store = messageStoreBus.getOrCreate(instanceId)
|
||||||
const messageId = extractPermissionMessageId(permission)
|
const messageId = extractPermissionMessageId(permission)
|
||||||
|
|||||||
@@ -521,6 +521,8 @@ export function createInstanceMessageStore(instanceId: string, hooks?: MessageSt
|
|||||||
for (const [key, entry] of Object.entries(draft)) {
|
for (const [key, entry] of Object.entries(draft)) {
|
||||||
if (!entry || entry.partId) continue
|
if (!entry || entry.partId) continue
|
||||||
const permissionCallId =
|
const permissionCallId =
|
||||||
|
(entry.permission as any).tool?.callID ??
|
||||||
|
(entry.permission as any).tool?.callId ??
|
||||||
(entry.permission as any).callID ??
|
(entry.permission as any).callID ??
|
||||||
(entry.permission as any).callId ??
|
(entry.permission as any).callId ??
|
||||||
(entry.permission as any).toolCallID ??
|
(entry.permission as any).toolCallID ??
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ClientPart } from "../../types/message"
|
import type { ClientPart } from "../../types/message"
|
||||||
import type { Permission } from "@opencode-ai/sdk"
|
import type { PermissionRequestLike } from "../../types/permission"
|
||||||
|
|
||||||
export type MessageStatus = "sending" | "sent" | "streaming" | "complete" | "error"
|
export type MessageStatus = "sending" | "sent" | "streaming" | "complete" | "error"
|
||||||
export type MessageRole = "user" | "assistant"
|
export type MessageRole = "user" | "assistant"
|
||||||
@@ -47,7 +47,7 @@ export interface PendingPartEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PermissionEntry {
|
export interface PermissionEntry {
|
||||||
permission: Permission
|
permission: PermissionRequestLike
|
||||||
messageId?: string
|
messageId?: string
|
||||||
partId?: string
|
partId?: string
|
||||||
enqueuedAt: number
|
enqueuedAt: number
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import type {
|
|||||||
MessageUpdateEvent,
|
MessageUpdateEvent,
|
||||||
} from "../types/message"
|
} from "../types/message"
|
||||||
import type {
|
import type {
|
||||||
EventPermissionReplied,
|
|
||||||
EventPermissionUpdated,
|
|
||||||
EventSessionCompacted,
|
EventSessionCompacted,
|
||||||
EventSessionError,
|
EventSessionError,
|
||||||
EventSessionIdle,
|
EventSessionIdle,
|
||||||
@@ -17,6 +15,8 @@ import type {
|
|||||||
import type { MessageStatus } from "./message-v2/types"
|
import type { MessageStatus } from "./message-v2/types"
|
||||||
|
|
||||||
import { getLogger } from "../lib/logger"
|
import { getLogger } from "../lib/logger"
|
||||||
|
import { getPermissionId, getPermissionKind, getRequestIdFromPermissionReply } from "../types/permission"
|
||||||
|
import type { PermissionReplyEventPropertiesLike, PermissionRequestLike } from "../types/permission"
|
||||||
import { showToastNotification, ToastVariant } from "../lib/notifications"
|
import { showToastNotification, ToastVariant } from "../lib/notifications"
|
||||||
import { instances, addPermissionToQueue, removePermissionFromQueue } from "./instances"
|
import { instances, addPermissionToQueue, removePermissionFromQueue } from "./instances"
|
||||||
import { showAlertDialog } from "./alerts"
|
import { showAlertDialog } from "./alerts"
|
||||||
@@ -442,22 +442,23 @@ function handleTuiToast(_instanceId: string, event: TuiToastEvent): void {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePermissionUpdated(instanceId: string, event: EventPermissionUpdated): void {
|
function handlePermissionUpdated(instanceId: string, event: { type: string; properties?: PermissionRequestLike } | any): void {
|
||||||
const permission = event.properties
|
const permission = event?.properties as PermissionRequestLike | undefined
|
||||||
if (!permission) return
|
if (!permission) return
|
||||||
|
|
||||||
log.info(`[SSE] Permission updated: ${permission.id} (${permission.type})`)
|
log.info(`[SSE] Permission request: ${getPermissionId(permission)} (${getPermissionKind(permission)})`)
|
||||||
addPermissionToQueue(instanceId, permission)
|
addPermissionToQueue(instanceId, permission)
|
||||||
upsertPermissionV2(instanceId, permission)
|
upsertPermissionV2(instanceId, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePermissionReplied(instanceId: string, event: EventPermissionReplied): void {
|
function handlePermissionReplied(instanceId: string, event: { type: string; properties?: PermissionReplyEventPropertiesLike } | any): void {
|
||||||
const { permissionID } = event.properties
|
const properties = event?.properties as PermissionReplyEventPropertiesLike | undefined
|
||||||
if (!permissionID) return
|
const requestId = getRequestIdFromPermissionReply(properties)
|
||||||
|
if (!requestId) return
|
||||||
|
|
||||||
log.info(`[SSE] Permission replied: ${permissionID}`)
|
log.info(`[SSE] Permission replied: ${requestId}`)
|
||||||
removePermissionFromQueue(instanceId, permissionID)
|
removePermissionFromQueue(instanceId, requestId)
|
||||||
removePermissionV2(instanceId, permissionID)
|
removePermissionV2(instanceId, requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import type {
|
|||||||
EventMessagePartRemoved as MessagePartRemovedEvent,
|
EventMessagePartRemoved as MessagePartRemovedEvent,
|
||||||
Part as SDKPart,
|
Part as SDKPart,
|
||||||
Message as SDKMessage,
|
Message as SDKMessage,
|
||||||
Permission,
|
|
||||||
} from "@opencode-ai/sdk"
|
} from "@opencode-ai/sdk"
|
||||||
|
|
||||||
|
import type { PermissionRequestLike } from "./permission"
|
||||||
|
|
||||||
// Re-export for other modules
|
// Re-export for other modules
|
||||||
export type {
|
export type {
|
||||||
MessageUpdateEvent,
|
MessageUpdateEvent,
|
||||||
@@ -27,7 +28,7 @@ export interface RenderCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingPermissionState {
|
export interface PendingPermissionState {
|
||||||
permission: Permission
|
permission: PermissionRequestLike
|
||||||
active: boolean
|
active: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
131
packages/ui/src/types/permission.ts
Normal file
131
packages/ui/src/types/permission.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
export type PermissionReply = "once" | "always" | "reject"
|
||||||
|
|
||||||
|
export interface PermissionToolRefLike {
|
||||||
|
messageID?: string
|
||||||
|
messageId?: string
|
||||||
|
callID?: string
|
||||||
|
callId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compat type that covers both the legacy Permission.Info payload and the new
|
||||||
|
// PermissionNext.Request payload.
|
||||||
|
export interface PermissionRequestLike {
|
||||||
|
id: string
|
||||||
|
|
||||||
|
// Legacy fields
|
||||||
|
type?: string
|
||||||
|
pattern?: string
|
||||||
|
title?: string
|
||||||
|
sessionID?: string
|
||||||
|
messageID?: string
|
||||||
|
messageId?: string
|
||||||
|
callID?: string
|
||||||
|
callId?: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
time?: { created?: number }
|
||||||
|
|
||||||
|
// New fields
|
||||||
|
permission?: string
|
||||||
|
patterns?: string[]
|
||||||
|
always?: string[]
|
||||||
|
tool?: PermissionToolRefLike
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PermissionReplyEventPropertiesLike {
|
||||||
|
sessionID?: string
|
||||||
|
sessionId?: string
|
||||||
|
permissionID?: string
|
||||||
|
permissionId?: string
|
||||||
|
requestID?: string
|
||||||
|
requestId?: string
|
||||||
|
response?: PermissionReply
|
||||||
|
reply?: PermissionReply
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionId(permission: PermissionRequestLike | null | undefined): string {
|
||||||
|
return permission?.id ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionSessionId(permission: PermissionRequestLike | null | undefined): string | undefined {
|
||||||
|
return (
|
||||||
|
(permission as any)?.sessionID ??
|
||||||
|
(permission as any)?.sessionId ??
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionMessageId(permission: PermissionRequestLike | null | undefined): string | undefined {
|
||||||
|
const tool = (permission as any)?.tool as PermissionToolRefLike | undefined
|
||||||
|
return (
|
||||||
|
tool?.messageID ??
|
||||||
|
tool?.messageId ??
|
||||||
|
(permission as any)?.messageID ??
|
||||||
|
(permission as any)?.messageId ??
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionCallId(permission: PermissionRequestLike | null | undefined): string | undefined {
|
||||||
|
const tool = (permission as any)?.tool as PermissionToolRefLike | undefined
|
||||||
|
const metadata = (permission as any)?.metadata || {}
|
||||||
|
return (
|
||||||
|
tool?.callID ??
|
||||||
|
tool?.callId ??
|
||||||
|
(permission as any)?.callID ??
|
||||||
|
(permission as any)?.callId ??
|
||||||
|
(permission as any)?.toolCallID ??
|
||||||
|
(permission as any)?.toolCallId ??
|
||||||
|
metadata.callID ??
|
||||||
|
metadata.callId ??
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionCreatedAt(permission: PermissionRequestLike | null | undefined): number {
|
||||||
|
const created = (permission as any)?.time?.created
|
||||||
|
return typeof created === "number" ? created : Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionKind(permission: PermissionRequestLike | null | undefined): string {
|
||||||
|
return (
|
||||||
|
(permission as any)?.permission ??
|
||||||
|
(permission as any)?.type ??
|
||||||
|
"permission"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionPatterns(permission: PermissionRequestLike | null | undefined): string[] {
|
||||||
|
const patterns = (permission as any)?.patterns
|
||||||
|
if (Array.isArray(patterns)) {
|
||||||
|
return patterns.filter((value) => typeof value === "string")
|
||||||
|
}
|
||||||
|
const pattern = (permission as any)?.pattern
|
||||||
|
if (typeof pattern === "string" && pattern.length > 0) {
|
||||||
|
return [pattern]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionDisplayTitle(permission: PermissionRequestLike | null | undefined): string {
|
||||||
|
const title = (permission as any)?.title
|
||||||
|
if (typeof title === "string" && title.trim().length > 0) {
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
const kind = getPermissionKind(permission)
|
||||||
|
const patterns = getPermissionPatterns(permission)
|
||||||
|
if (patterns.length > 0) {
|
||||||
|
return `${kind}: ${patterns.join(", ")}`
|
||||||
|
}
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRequestIdFromPermissionReply(properties: PermissionReplyEventPropertiesLike | null | undefined): string | undefined {
|
||||||
|
return (
|
||||||
|
(properties as any)?.requestID ??
|
||||||
|
(properties as any)?.requestId ??
|
||||||
|
(properties as any)?.permissionID ??
|
||||||
|
(properties as any)?.permissionId ??
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user