align permission attachments with SSE stream

This commit is contained in:
Shantur Rathore
2025-12-02 23:53:34 +00:00
parent 78338f33c1
commit 6a16dd8f98
6 changed files with 53 additions and 160 deletions

View File

@@ -1,8 +1,6 @@
import { createSignal } from "solid-js"
import { produce } from "solid-js/store"
import type { Instance, LogEntry } from "../types/instance"
import type { LspStatus, Permission } from "@opencode-ai/sdk"
import type { ClientPart } from "../types/message"
import { sdkManager } from "../lib/sdk-manager"
import { sseManager } from "../lib/sse-manager"
import { serverApi } from "../lib/api-client"
@@ -21,10 +19,9 @@ import { setSessionPendingPermission } from "./session-state"
import { setHasInstances } from "./ui"
import { messageStoreBus } from "./message-v2/bus"
import { clearCacheForInstance } from "../lib/global-cache"
import type { MessageRecord } from "./message-v2/types"
const [instances, setInstances] = createSignal<Map<string, Instance>>(new Map())
const [activeInstanceId, setActiveInstanceId] = createSignal<string | null>(null)
const [instanceLogs, setInstanceLogs] = createSignal<Map<string, LogEntry[]>>(new Map())
const [logStreamingState, setLogStreamingState] = createSignal<Map<string, boolean>>(new Map())
@@ -461,17 +458,6 @@ function addPermissionToQueue(instanceId: string, permission: Permission): void
const sessionId = getPermissionSessionId(permission)
incrementSessionPendingCount(instanceId, sessionId)
setSessionPendingPermission(instanceId, sessionId, true)
const isActive = getActivePermission(instanceId)?.id === permission.id
attachPermissionToToolPart(instanceId, permission, isActive)
}
function getActivePermission(instanceId: string): Permission | null {
const activeId = activePermissionId().get(instanceId)
if (!activeId) return null
const queue = getPermissionQueue(instanceId)
return queue.find(p => p.id === activeId) ?? null
}
function removePermissionFromQueue(instanceId: string, permissionId: string): void {
@@ -512,16 +498,10 @@ function removePermissionFromQueue(instanceId: string, permissionId: string): vo
const removed = removedPermission
if (removed) {
clearPermissionFromToolPart(instanceId, removed)
const removedSessionId = getPermissionSessionId(removed)
const remaining = decrementSessionPendingCount(instanceId, removedSessionId)
setSessionPendingPermission(instanceId, removedSessionId, remaining > 0)
}
const nextActivePermission = getActivePermission(instanceId)
if (nextActivePermission) {
attachPermissionToToolPart(instanceId, nextActivePermission, true)
}
}
function clearPermissionQueue(instanceId: string): void {
@@ -542,131 +522,6 @@ function getPermissionSessionId(permission: Permission): string {
return (permission as any).sessionID
}
function getPermissionMessageId(permission: Permission): string | undefined {
return (permission as any).messageID ?? (permission as any).messageId ?? undefined
}
function getPermissionCallIdentifier(permission: Permission): string | undefined {
return (
(permission as any).callID ??
(permission as any).callId ??
(permission as any).toolCallID ??
(permission as any).toolCallId ??
undefined
)
}
function findToolPartForPermission(record: MessageRecord, permission: Permission): { partId: string; part: ClientPart } | null {
const expectedCallId = getPermissionCallIdentifier(permission)
const permissionId = permission.id
const permissionMessageId = getPermissionMessageId(permission)
for (const partId of record.partIds) {
const entry = record.parts[partId]
if (!entry) continue
const part = entry.data
if (!part || part.type !== "tool") continue
const toolCallId = (part as any).callID ?? (part as any).callId
const partMessageId = (part as any).messageID ?? (part as any).messageId
if (expectedCallId) {
if (toolCallId === expectedCallId) {
return { partId, part }
}
if (!toolCallId && (part.id === expectedCallId || (permissionMessageId && partMessageId === permissionMessageId))) {
return { partId, part }
}
continue
}
if (
(toolCallId && toolCallId === permissionId) ||
part.id === permissionId ||
(permissionMessageId && partMessageId === permissionMessageId)
) {
return { partId, part }
}
}
return null
}
function mutateToolPartPermission(
instanceId: string,
permission: Permission,
mutator: (part: ClientPart) => boolean,
): void {
const messageId = getPermissionMessageId(permission)
if (!messageId) return
const store = messageStoreBus.getOrCreate(instanceId)
const messageRecord = store.getMessage(messageId)
if (!messageRecord) return
const targetPart = findToolPartForPermission(messageRecord, permission)
if (!targetPart) return
store.setState(
"messages",
messageId,
produce((draft: MessageRecord) => {
const partRecord = draft.parts[targetPart.partId]
if (!partRecord) return
const changed = mutator(partRecord.data)
if (!changed) return
const nextVersion = typeof partRecord.data.version === "number" ? partRecord.data.version + 1 : 1
partRecord.data.version = nextVersion
partRecord.revision += 1
draft.revision += 1
draft.updatedAt = Date.now()
}),
)
// Permission attachment/removal can change the rendered height of the
// message list (e.g., permission blocks or diffs), so bump the
// session revision to ensure auto-scroll reacts.
if (messageRecord.sessionId) {
store.setState("sessionRevisions", messageRecord.sessionId, (value: number = 0) => value + 1)
}
}
function attachPermissionToToolPart(instanceId: string, permission: Permission, active: boolean): void {
mutateToolPartPermission(instanceId, permission, (part) => {
const existing = part.pendingPermission
if (existing && existing.permission.id === permission.id && existing.active === active) {
return false
}
part.pendingPermission = { permission, active }
return true
})
}
function clearPermissionFromToolPart(instanceId: string, permission: Permission): void {
mutateToolPartPermission(instanceId, permission, (part) => {
if (!part.pendingPermission || part.pendingPermission.permission.id !== permission.id) {
return false
}
delete part.pendingPermission
return true
})
}
function refreshPermissionsForSession(instanceId: string, sessionId: string): void {
const queue = getPermissionQueue(instanceId)
if (queue.length === 0) {
setSessionPendingPermission(instanceId, sessionId, false)
return
}
const activeId = activePermissionId().get(instanceId)
for (const permission of queue) {
if (getPermissionSessionId(permission) !== sessionId) continue
const isActive = permission.id === activeId
attachPermissionToToolPart(instanceId, permission, isActive)
}
const pendingCount = permissionSessionCounts.get(instanceId)?.get(sessionId) ?? 0
setSessionPendingPermission(instanceId, sessionId, pendingCount > 0)
}
async function sendPermissionResponse(
instanceId: string,
sessionId: string,
@@ -768,10 +623,8 @@ export {
getPermissionQueue,
getPermissionQueueLength,
addPermissionToQueue,
getActivePermission,
removePermissionFromQueue,
clearPermissionQueue,
refreshPermissionsForSession,
sendPermissionResponse,
disconnectedInstance,
acknowledgeDisconnectedInstance,