From ef1224c4f7dc342dcffb53aea985c8677875657d Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:33:28 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"Preserve=20OpenCode=20session=20acros?= =?UTF-8?q?s=20command=20completion,=20control=20SessionS=E2=80=A6"=20(#14?= =?UTF-8?q?943)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1da1bdc01c3cc25218cd0a47687dcdee210dfa5e. --- .../server-opencode-lifecycle.test.ts | 159 ------------------ src/main/agent-hooks/server.ts | 62 +------ src/main/mimo/hook-service.test.ts | 4 +- src/main/mimo/hook-service.ts | 2 +- .../hook-plugin-lifecycle-delivery.test.ts | 16 -- src/main/opencode/hook-service.ts | 21 +-- src/main/runtime/orca-runtime.test.ts | 104 ------------ src/main/runtime/orca-runtime.ts | 34 +--- ...listener-claude-compatible-vendors.test.ts | 10 -- src/shared/agent-hook-listener.ts | 19 +-- 10 files changed, 20 insertions(+), 411 deletions(-) delete mode 100644 src/main/agent-hooks/server-opencode-lifecycle.test.ts diff --git a/src/main/agent-hooks/server-opencode-lifecycle.test.ts b/src/main/agent-hooks/server-opencode-lifecycle.test.ts deleted file mode 100644 index 9d1fb08541b..00000000000 --- a/src/main/agent-hooks/server-opencode-lifecycle.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { makePaneKey } from '../../shared/stable-pane-id' -import { AgentHookServer } from './server' - -const PANE = makePaneKey('tab-opencode', '11111111-1111-4111-8111-111111111111') -const TARGET_PANE = makePaneKey('tab-opencode', '22222222-2222-4222-8222-222222222222') - -describe('AgentHookServer OpenCode lifecycle', () => { - const servers: AgentHookServer[] = [] - - afterEach(() => { - for (const server of servers) { - server.stop() - } - servers.length = 0 - }) - - async function setup(): Promise<{ - server: AgentHookServer - post: ( - payload: Record, - launchToken: string, - paneKey?: string - ) => Promise - }> { - const server = new AgentHookServer() - servers.push(server) - await server.start({ env: 'production' }) - const env = server.buildPtyEnv() - return { - server, - post: (payload, launchToken, paneKey = PANE) => - fetch(`http://127.0.0.1:${env.ORCA_AGENT_HOOK_PORT}/hook/opencode`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Orca-Agent-Hook-Token': env.ORCA_AGENT_HOOK_TOKEN - }, - body: JSON.stringify({ - paneKey, - launchToken, - tabId: 'tab-opencode', - worktreeId: 'wt-opencode', - env: 'production', - payload - }) - }) - } - } - - it('accepts Busy after a retired pane receives a root SessionStart', async () => { - const { server, post } = await setup() - await post({ hook_event_name: 'SessionBusy', sessionID: 'old' }, 'old-token') - server.retirePaneAuthority(PANE) - - await post({ hook_event_name: 'SessionStart', sessionID: 'fresh' }, 'fresh-token') - expect(server.getStatusSnapshot()).toEqual([ - expect.objectContaining({ - paneKey: PANE, - state: 'done', - sessionBoundary: true, - providerSession: { key: 'session_id', id: 'fresh' } - }) - ]) - - await post({ hook_event_name: 'SessionBusy', sessionID: 'fresh' }, 'fresh-token') - - expect(server.getStatusSnapshot()).toEqual([ - expect.objectContaining({ paneKey: PANE, state: 'working', agentType: 'opencode' }) - ]) - }) - - it('accepts a resumed fresh user MessagePart but not arbitrary Busy', async () => { - const { server, post } = await setup() - await post({ hook_event_name: 'SessionBusy', sessionID: 'old' }, 'old-token') - server.retirePaneAuthority(PANE) - - await post({ hook_event_name: 'SessionBusy', sessionID: 'resumed' }, 'resume-token') - expect(server.getStatusSnapshot()).toEqual([]) - - await post( - { - hook_event_name: 'MessagePart', - role: 'user', - text: 'continue the task', - messageID: 'message-resumed', - sessionID: 'resumed' - }, - 'resume-token' - ) - - expect(server.getStatusSnapshot()).toEqual([ - expect.objectContaining({ state: 'working', prompt: 'continue the task' }) - ]) - }) - - it('maps question.asked attention to Waiting after restart', async () => { - const { server, post } = await setup() - await post({ hook_event_name: 'SessionBusy', sessionID: 'old' }, 'old-token') - server.retirePaneAuthority(PANE) - await post({ hook_event_name: 'SessionStart', sessionID: 'fresh' }, 'fresh-token') - - await post( - { hook_event_name: 'AskUserQuestion', id: 'question-1', sessionID: 'fresh' }, - 'fresh-token' - ) - - expect(server.getStatusSnapshot()).toEqual([ - expect.objectContaining({ state: 'waiting', agentType: 'opencode' }) - ]) - }) - - it('suppresses stale old-token Busy after a fresh restart', async () => { - const { server, post } = await setup() - await post({ hook_event_name: 'SessionBusy', sessionID: 'old' }, 'old-token') - server.retirePaneAuthority(PANE) - await post({ hook_event_name: 'SessionStart', sessionID: 'fresh' }, 'fresh-token') - await post({ hook_event_name: 'SessionBusy', sessionID: 'fresh' }, 'fresh-token') - - await post( - { hook_event_name: 'SessionBusy', sessionID: 'old', prompt: 'stale prompt' }, - 'old-token' - ) - - expect(server.getStatusSnapshot()).toEqual([ - expect.objectContaining({ state: 'working', prompt: '' }) - ]) - }) - - it('replaces a destination token fence when pane authority transfers', async () => { - const { server, post } = await setup() - await post( - { hook_event_name: 'SessionBusy', sessionID: 'target-old' }, - 'target-old-token', - TARGET_PANE - ) - server.retirePaneAuthority(TARGET_PANE) - await post( - { hook_event_name: 'SessionStart', sessionID: 'target-fresh' }, - 'target-fresh-token', - TARGET_PANE - ) - await post({ hook_event_name: 'SessionBusy', sessionID: 'source' }, 'source-token') - - server.transferPaneAuthority(PANE, TARGET_PANE, 'pty-opencode') - await post( - { hook_event_name: 'SessionBusy', sessionID: 'source-after-transfer' }, - 'source-token', - TARGET_PANE - ) - - expect(server.getStatusSnapshot()).toEqual([ - expect.objectContaining({ - paneKey: TARGET_PANE, - providerSession: { key: 'session_id', id: 'source-after-transfer' } - }) - ]) - }) -}) diff --git a/src/main/agent-hooks/server.ts b/src/main/agent-hooks/server.ts index 81452af847d..f819414ad4b 100644 --- a/src/main/agent-hooks/server.ts +++ b/src/main/agent-hooks/server.ts @@ -633,7 +633,6 @@ export class AgentHookServer { private promptSentHashSalt = randomBytes(16).toString('hex') private closedAgentStatusTabIds = new Set() private closedAgentStatusPaneKeys = new Set() - private restartedStatusLaunchTokenHashByPaneKey = new Map() private connectionTimestampWatermarkById = new Map() // Why: skip disk writes when the JSON exactly matches the last write; guards against re-firing trailing timers when nothing changed. private lastWrittenJson: string | null = null @@ -997,13 +996,7 @@ export class AgentHookServer { private getAgentStatusDisposition( paneKey: string, - event?: { - hookEventName?: string - isReplay?: boolean - source?: AgentHookSource - hasExplicitPrompt?: boolean - launchToken?: string - } + event?: { hookEventName?: string; isReplay?: boolean } ): 'accept' | 'restart' | 'suppress' { const ownerPaneKey = this.resolvePaneKeyAlias(paneKey) const paneRetired = @@ -1014,38 +1007,17 @@ export class AgentHookServer { return 'suppress' } if (!paneRetired) { - const tokenFence = this.restartedStatusLaunchTokenHashByPaneKey.get(ownerPaneKey) - if (event && tokenFence) { - const launchToken = event.launchToken?.trim() - if (!launchToken || createHash('sha256').update(launchToken).digest('hex') !== tokenFence) { - return 'suppress' - } - } return 'accept' } - // Why: a new session boundary or explicit prompt proves a live lifecycle, while its - // token fences follow-up status without restoring retired orchestration authority. - const freshOpenCodePrompt = - event?.source === 'opencode' && - event.hookEventName === 'MessagePart' && - event.hasExplicitPrompt === true + // Why: command completion retires launch authority but leaves its shell pane reusable. + // A live SessionStart proves a new agent process owns the retired pane just like a + // fresh prompt does — without it, a session resumed in a reused pane stays rowless (STA-3386). if ( - (event?.hookEventName === 'UserPromptSubmit' || - event?.hookEventName === 'SessionStart' || - freshOpenCodePrompt) && - event?.isReplay !== true + (event?.hookEventName === 'UserPromptSubmit' || event?.hookEventName === 'SessionStart') && + event.isReplay !== true ) { this.closedAgentStatusPaneKeys.delete(paneKey) this.closedAgentStatusPaneKeys.delete(ownerPaneKey) - const launchToken = event.launchToken?.trim() - if (launchToken) { - this.restartedStatusLaunchTokenHashByPaneKey.set( - ownerPaneKey, - createHash('sha256').update(launchToken).digest('hex') - ) - } else { - this.restartedStatusLaunchTokenHashByPaneKey.delete(ownerPaneKey) - } return 'restart' } return 'suppress' @@ -1619,13 +1591,6 @@ export class AgentHookServer { if (this.runtimeObservedStatusPaneKeys.delete(previousOwnerPaneKey)) { this.runtimeObservedStatusPaneKeys.add(toPaneKey) } - const restartedTokenHash = - this.restartedStatusLaunchTokenHashByPaneKey.get(previousOwnerPaneKey) - this.restartedStatusLaunchTokenHashByPaneKey.delete(previousOwnerPaneKey) - this.restartedStatusLaunchTokenHashByPaneKey.delete(toPaneKey) - if (restartedTokenHash) { - this.restartedStatusLaunchTokenHashByPaneKey.set(toPaneKey, restartedTokenHash) - } const authorityObservation = this.currentAuthorityObservations.get(previousOwnerPaneKey) if (authorityObservation) { const owner = parsePaneKey(toPaneKey) @@ -1678,7 +1643,6 @@ export class AgentHookServer { const hadStatus = [...paneKeys].some((key) => this.state.lastStatusByPaneKey.has(key)) for (const key of paneKeys) { this.markPaneClosedForAgentStatus(key) - this.restartedStatusLaunchTokenHashByPaneKey.delete(key) this.clearAssistantMessageRetry(key) this.clearCodexSubagentPoll(key) clearPaneCacheState(this.state, key) @@ -1989,10 +1953,7 @@ export class AgentHookServer { : undefined const statusDisposition = this.getAgentStatusDisposition(paneKey, { hookEventName, - isReplay: envelope.isReplay === true, - source, - hasExplicitPrompt: envelope.hasExplicitPrompt === true, - launchToken: envelope.launchToken + isReplay: envelope.isReplay === true }) if (statusDisposition === 'suppress') { return @@ -2196,10 +2157,7 @@ export class AgentHookServer { const statusDisposition = normalized.event ? this.getAgentStatusDisposition(normalized.event.paneKey, { hookEventName: normalized.event.hookEventName, - isReplay: normalized.event.isReplay, - source: normalized.event.source, - hasExplicitPrompt: normalized.event.hasExplicitPrompt, - launchToken: normalized.event.launchToken + isReplay: normalized.event.isReplay }) : 'suppress' if (normalized.event && statusDisposition !== 'suppress') { @@ -2282,7 +2240,6 @@ export class AgentHookServer { this.promptSentDedupeByPaneKey.clear() this.closedAgentStatusTabIds.clear() this.closedAgentStatusPaneKeys.clear() - this.restartedStatusLaunchTokenHashByPaneKey.clear() this.connectionTimestampWatermarkById.clear() this.legacyPaneKeyAliases.clear() clearAllListenerCaches(this.state) @@ -2457,7 +2414,6 @@ export class AgentHookServer { this.runtimeObservedStatusPaneKeys.delete(paneKey) this.currentAuthorityObservations.delete(paneKey) this.promptSentDedupeByPaneKey.delete(paneKey) - this.restartedStatusLaunchTokenHashByPaneKey.delete(paneKey) } if (aliasChanged) { this.notifyPaneKeyAliasPersistenceListener() @@ -2478,7 +2434,6 @@ export class AgentHookServer { clearPaneCacheState(this.state, resolvedPaneKey) this.currentAuthorityObservations.delete(resolvedPaneKey) this.promptSentDedupeByPaneKey.delete(resolvedPaneKey) - this.restartedStatusLaunchTokenHashByPaneKey.delete(resolvedPaneKey) let clearedAlias = false for (const [legacyPaneKey, stablePaneKey] of this.legacyPaneKeyAliases) { if (stablePaneKey.stablePaneKey === resolvedPaneKey) { @@ -2488,7 +2443,6 @@ export class AgentHookServer { clearPaneCacheState(this.state, legacyPaneKey) this.currentAuthorityObservations.delete(legacyPaneKey) this.promptSentDedupeByPaneKey.delete(legacyPaneKey) - this.restartedStatusLaunchTokenHashByPaneKey.delete(legacyPaneKey) clearedAlias = true } } diff --git a/src/main/mimo/hook-service.test.ts b/src/main/mimo/hook-service.test.ts index 709b3bddae3..e30e415ca97 100644 --- a/src/main/mimo/hook-service.test.ts +++ b/src/main/mimo/hook-service.test.ts @@ -56,9 +56,7 @@ describe('MimoCodeHookService buildPtyEnv', () => { const orcaPlugin = join(overlayHome, 'config', 'plugins', 'orca-mimocode-status.js') expect(existsSync(orcaPlugin)).toBe(true) - const pluginSource = readFileSync(orcaPlugin, 'utf8') - expect(pluginSource).toContain('/hook/mimo-code') - expect(pluginSource).not.toContain('post("SessionStart"') + expect(readFileSync(orcaPlugin, 'utf8')).toContain('/hook/mimo-code') expect( readFileSync(join(mimocodeHome, 'config', 'plugins', 'orca-mimocode-status.js'), 'utf8') diff --git a/src/main/mimo/hook-service.ts b/src/main/mimo/hook-service.ts index eb991f59db2..3f879d93063 100644 --- a/src/main/mimo/hook-service.ts +++ b/src/main/mimo/hook-service.ts @@ -68,7 +68,7 @@ export class MimoCodeHookService { mkdirSync(pluginsDir, { recursive: true }) writeFileSync( join(pluginsDir, ORCA_MIMOCODE_PLUGIN_FILE), - getOpenCodeFamilyPluginSource('/hook/mimo-code', { emitSessionStart: false }) + getOpenCodeFamilyPluginSource('/hook/mimo-code') ) } catch { return existingMimocodeHome ? { MIMOCODE_HOME: existingMimocodeHome } : {} diff --git a/src/main/opencode/hook-plugin-lifecycle-delivery.test.ts b/src/main/opencode/hook-plugin-lifecycle-delivery.test.ts index 5e87a90991a..b6684ea38af 100644 --- a/src/main/opencode/hook-plugin-lifecycle-delivery.test.ts +++ b/src/main/opencode/hook-plugin-lifecycle-delivery.test.ts @@ -131,22 +131,6 @@ describe('OpenCode plugin lifecycle delivery', () => { }) } - it('maps only root session.created to SessionStart', async () => { - const handler = await loadHandler() - - await handler({ - event: { type: 'session.created', properties: { info: { id: 'root' } } } - }) - await handler({ - event: { - type: 'session.created', - properties: { info: { id: 'child', parentID: 'root' } } - } - }) - - expect(posts).toEqual([{ hook_event_name: 'SessionStart', sessionID: 'root' }]) - }) - it('preserves FIFO lifecycle order while the first session lookup is delayed', async () => { let releaseFirstLookup: (() => void) | undefined const firstLookup = new Promise((resolve) => { diff --git a/src/main/opencode/hook-service.ts b/src/main/opencode/hook-service.ts index 6face9d652b..706b39151d9 100644 --- a/src/main/opencode/hook-service.ts +++ b/src/main/opencode/hook-service.ts @@ -36,13 +36,10 @@ function toSafeDirName(id: string): string { } export function getOpenCodePluginSource(): string { - return getOpenCodeFamilyPluginSource('/hook/opencode', { emitSessionStart: true }) + return getOpenCodeFamilyPluginSource('/hook/opencode') } -export function getOpenCodeFamilyPluginSource( - hookPathname: string, - options: { emitSessionStart: boolean } -): string { +export function getOpenCodeFamilyPluginSource(hookPathname: string): string { // Why: the plugin posts PTY environment data from OpenCode to the shared hooks server. return [ '// Why: process-lifetime guard so a recurring parse error on a malformed', @@ -838,20 +835,6 @@ export function getOpenCodeFamilyPluginSource( '', ' const sessionID = event.properties?.sessionID;', ' const updatedPart = event.properties?.part;', - ...(options.emitSessionStart - ? [ - ' if (event.type === "session.created") {', - ' const info = event.properties?.info;', - ' if (!info?.id || info.parentID) return;', - ' rememberSessionRoot(info.id, info.id);', - ' await enqueueLifecycle(() =>', - ' disposed ? undefined : post("SessionStart", { sessionID: info.id })', - ' );', - ' return;', - ' }', - '' - ] - : []), ' if (', ' event.type === "message.part.updated" &&', ' updatedPart?.type === "tool" &&', diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index e13b56b069c..4c2dd2fbdd7 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -13092,110 +13092,6 @@ describe('OrcaRuntimeService', () => { ).toBeUndefined() }) - it('keeps OpenCode launch authority while command-finished leaves it in foreground', async () => { - const spawn = vi.fn().mockResolvedValue({ id: 'pty-opencode', incarnationId: 'process-1' }) - const retireAuthority = vi.fn() - const getForegroundProcess = vi.fn(async () => 'opencode') - const runtime = new OrcaRuntimeService(store, undefined, { - attestAgentHookCompatibilityAuthority: (candidate) => ({ - paneKey: candidate.paneKey, - source: 'current_hook' - }), - retireAgentHookCompatibilityAuthority: retireAuthority - }) - runtime.setPtyController({ - spawn, - write: () => true, - kill: () => true, - getForegroundProcess - }) - runtime.setNotifier({ - worktreesChanged: vi.fn(), - reposChanged: vi.fn(), - activateWorktree: vi.fn(), - createTerminal: vi.fn(), - revealTerminalSession: vi.fn().mockResolvedValue({ tabId: 'tab-opencode' }), - splitTerminal: vi.fn(), - renameTerminal: vi.fn(), - focusTerminal: vi.fn(), - closeTerminal: vi.fn(), - sleepWorktree: vi.fn(), - terminalFitOverrideChanged: vi.fn(), - terminalDriverChanged: vi.fn() - }) - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [] }) - - const terminal = await runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, { - command: 'opencode', - launchConfig: { agentCommand: 'opencode', agentArgs: '', agentEnv: {} }, - launchAgent: 'opencode' - }) - const spawnEnv = - (spawn.mock.calls[0]?.[0] as { env?: Record } | undefined)?.env ?? {} - const evidence = { - terminalHandle: terminal.handle, - paneKey: spawnEnv.ORCA_PANE_KEY, - launchToken: spawnEnv.ORCA_AGENT_LAUNCH_TOKEN - } - - runtime.onPtyData('pty-opencode', '\x1b]133;D;0\x07', 100) - await vi.waitFor(() => expect(getForegroundProcess).toHaveBeenCalled()) - - expect(retireAuthority).not.toHaveBeenCalled() - expect(runtime.verifyOrchestrationCompatibilityCaller(evidence)).not.toBeNull() - }) - - it('ignores a stale OpenCode foreground result after a newer title observation', async () => { - let resolveForegroundProcess: ((process: string | null) => void) | undefined - const foregroundProcess = new Promise((resolve) => { - resolveForegroundProcess = resolve - }) - const spawn = vi.fn().mockResolvedValue({ id: 'pty-opencode-race', incarnationId: 'process-1' }) - const retireAuthority = vi.fn() - const getForegroundProcess = vi.fn(() => foregroundProcess) - const runtime = new OrcaRuntimeService(store, undefined, { - retireAgentHookCompatibilityAuthority: retireAuthority - }) - runtime.setPtyController({ - spawn, - write: () => true, - kill: () => true, - getForegroundProcess - }) - runtime.setNotifier({ - worktreesChanged: vi.fn(), - reposChanged: vi.fn(), - activateWorktree: vi.fn(), - createTerminal: vi.fn(), - revealTerminalSession: vi.fn().mockResolvedValue({ tabId: 'tab-opencode-race' }), - splitTerminal: vi.fn(), - renameTerminal: vi.fn(), - focusTerminal: vi.fn(), - closeTerminal: vi.fn(), - sleepWorktree: vi.fn(), - terminalFitOverrideChanged: vi.fn(), - terminalDriverChanged: vi.fn() - }) - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [] }) - - await runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, { - command: 'opencode', - launchConfig: { agentCommand: 'opencode', agentArgs: '', agentEnv: {} }, - launchAgent: 'opencode' - }) - - runtime.onPtyData('pty-opencode-race', '\x1b]133;D;0\x07', 100) - await vi.waitFor(() => expect(getForegroundProcess).toHaveBeenCalled()) - runtime.onPtyData('pty-opencode-race', '\x1b]0;OpenCode working\x07', 101) - resolveForegroundProcess?.(null) - await foregroundProcess - await Promise.resolve() - - expect(retireAuthority).not.toHaveBeenCalled() - }) - it('retires only receipted restored PTY authority on command completion and exit', () => { const retireAuthority = vi.fn() const runtime = new OrcaRuntimeService(store, undefined, { diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 3fd22d81446..d0780614ebf 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -10325,7 +10325,7 @@ export class OrcaRuntimeService { this.recordTerminalSideEffectFact(ptyId, { kind: 'bell' }) return case 'command-finished': - this.retirePtyAgentLaunchAuthorityAfterCommandFinished(ptyId) + this.retirePtyAgentLaunchAuthority(ptyId) this.recordTerminalSideEffectFact(ptyId, { kind: 'command-finished', exitCode: fact.exitCode @@ -10614,7 +10614,7 @@ export class OrcaRuntimeService { this.confirmPtyAgentExit(ptyId) }, onCommandFinished: (exitCode: number | null) => { - this.retirePtyAgentLaunchAuthorityAfterCommandFinished(ptyId) + this.retirePtyAgentLaunchAuthority(ptyId) this.recordTerminalSideEffectFact(ptyId, { kind: 'command-finished', exitCode }) }, onBell: () => { @@ -12742,36 +12742,6 @@ export class OrcaRuntimeService { } } - private retirePtyAgentLaunchAuthorityAfterCommandFinished(ptyId: string): void { - const pty = this.ptysById.get(ptyId) - if (pty?.launchAgent !== 'opencode') { - this.retirePtyAgentLaunchAuthority(ptyId) - return - } - const titleObservedAt = pty.lastOscTitleAt ?? null - const foregroundRead = this.readPtyForegroundProcessFromController(ptyId, titleObservedAt ?? 0) - if (!foregroundRead) { - this.retirePtyAgentLaunchAuthority(ptyId) - return - } - const incarnationId = pty.incarnationId - void foregroundRead.then((result) => { - const current = this.ptysById.get(ptyId) - if ( - current !== pty || - current.incarnationId !== incarnationId || - current.lastOscTitleAt !== titleObservedAt || - result.controller !== this.ptyController - ) { - return - } - if (result.available && recognizeAgentProcess(result.process)?.agent === 'opencode') { - return - } - this.retirePtyAgentLaunchAuthority(ptyId) - }) - } - async resolveTerminalCwd(handle: string): Promise { const ptyId = this.resolveLeafForHandle(handle)?.ptyId if (!ptyId) { diff --git a/src/shared/agent-hook-listener-claude-compatible-vendors.test.ts b/src/shared/agent-hook-listener-claude-compatible-vendors.test.ts index 64390067fcf..b7e3dc362db 100644 --- a/src/shared/agent-hook-listener-claude-compatible-vendors.test.ts +++ b/src/shared/agent-hook-listener-claude-compatible-vendors.test.ts @@ -216,15 +216,6 @@ describe('shared agent-hook-listener', () => { }, 'production' ) - const sessionStart = normalizeHookPayload( - state, - 'mimo-code', - { - paneKey: PANE_KEY, - payload: { hook_event_name: 'SessionStart', sessionID: 'mimo-session' } - }, - 'production' - ) expect(message?.payload).toMatchObject({ agentType: 'mimo-code', @@ -235,7 +226,6 @@ describe('shared agent-hook-listener', () => { expect(message?.providerSession).toMatchObject({ key: 'session_id', id: 'mimo-session' }) expect(tool?.payload).toMatchObject({ agentType: 'mimo-code', state: 'working' }) expect(idle?.payload).toMatchObject({ agentType: 'mimo-code', state: 'done' }) - expect(sessionStart).toBeNull() }) it('maps Kimi AskUserQuestion PreToolUse to waiting, then back to working on answer', () => { diff --git a/src/shared/agent-hook-listener.ts b/src/shared/agent-hook-listener.ts index bbfd0c459ad..82c9bfc9fec 100644 --- a/src/shared/agent-hook-listener.ts +++ b/src/shared/agent-hook-listener.ts @@ -2437,7 +2437,6 @@ function isNewTurnEvent(source: AgentHookSource, eventName: unknown): boolean { case 'amp': return eventName === 'agent.start' case 'opencode': - return eventName === 'SessionStart' case 'mimo-code': return false case 'cursor': @@ -3774,19 +3773,14 @@ function normalizeOpenCodeFamilyEvent( paneKey: string, hookPayload: Record ): ParsedAgentStatusPayload | null { - const resetsTurn = - isNewTurnEvent(source, eventName) || - (eventName === 'MessagePart' && hookPayload.role === 'user') const stateName = eventName === 'SessionBusy' || eventName === 'MessagePart' ? 'working' : eventName === 'SessionIdle' ? 'done' - : source === 'opencode' && eventName === 'SessionStart' - ? 'done' - : eventName === 'PermissionRequest' || eventName === 'AskUserQuestion' - ? 'waiting' - : null + : eventName === 'PermissionRequest' || eventName === 'AskUserQuestion' + ? 'waiting' + : null if (!stateName) { return null @@ -3796,20 +3790,19 @@ function normalizeOpenCodeFamilyEvent( state, paneKey, extractToolFields(source, eventName, hookPayload), - { resetOnNewTurn: resetsTurn } + { resetOnNewTurn: isNewTurnEvent(source, eventName) } ) return normalizeAgentStatusPayload({ state: stateName, prompt: resolvePrompt(state, paneKey, promptText, { - resetOnNewTurn: resetsTurn + resetOnNewTurn: isNewTurnEvent(source, eventName) }), agentType: source, toolName: snapshot.toolName, toolInput: snapshot.toolInput, interactivePrompt: snapshot.interactivePrompt, - lastAssistantMessage: snapshot.lastAssistantMessage, - sessionBoundary: source === 'opencode' && eventName === 'SessionStart' ? true : undefined + lastAssistantMessage: snapshot.lastAssistantMessage }) }