fix(background-agent): handle message.part.delta for heartbeat (OpenCode >=1.2.0)

OpenCode 1.2.0+ changed reasoning-delta and text-delta to emit
'message.part.delta' instead of 'message.part.updated'. Without
handling this event, lastUpdate was only refreshed at reasoning-start
and reasoning-end, leaving a gap where extended thinking (>3min)
could trigger stale timeout.

Accept both event types as heartbeat sources for forward compatibility.
This commit is contained in:
YeonGyu-Kim
2026-02-15 14:26:25 +09:00
parent 3f557e593c
commit 8a5f61724d
3 changed files with 42 additions and 2 deletions

View File

@@ -52,7 +52,7 @@ export function handleBackgroundEvent(args: {
const props = event.properties
if (event.type === "message.part.updated") {
if (event.type === "message.part.updated" || event.type === "message.part.delta") {
if (!props || !isRecord(props)) return
const sessionID = getString(props, "sessionID")
if (!sessionID) return

View File

@@ -3413,4 +3413,44 @@ describe("BackgroundManager.handleEvent - non-tool event lastUpdate", () => {
//#then - task should still be running (text event refreshed lastUpdate)
expect(task.status).toBe("running")
})
test("should refresh lastUpdate on message.part.delta events (OpenCode >=1.2.0)", async () => {
//#given - a running task with stale lastUpdate
const client = {
session: {
prompt: async () => ({}),
promptAsync: async () => ({}),
abort: async () => ({}),
},
}
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput, { staleTimeoutMs: 180_000 })
stubNotifyParentSession(manager)
const task: BackgroundTask = {
id: "task-delta-1",
sessionID: "session-delta-1",
parentSessionID: "parent-1",
parentMessageID: "msg-1",
description: "Reasoning task with delta events",
prompt: "Extended thinking",
agent: "oracle",
status: "running",
startedAt: new Date(Date.now() - 600_000),
progress: {
toolCalls: 0,
lastUpdate: new Date(Date.now() - 300_000),
},
}
getTaskMap(manager).set(task.id, task)
//#when - a message.part.delta event arrives (reasoning-delta or text-delta in OpenCode >=1.2.0)
manager.handleEvent({
type: "message.part.delta",
properties: { sessionID: "session-delta-1", field: "text", delta: "thinking..." },
})
await manager["checkAndInterruptStaleTasks"]()
//#then - task should still be running (delta event refreshed lastUpdate)
expect(task.status).toBe("running")
})
})

View File

@@ -660,7 +660,7 @@ export class BackgroundManager {
handleEvent(event: Event): void {
const props = event.properties
if (event.type === "message.part.updated") {
if (event.type === "message.part.updated" || event.type === "message.part.delta") {
if (!props || typeof props !== "object" || !("sessionID" in props)) return
const partInfo = props as unknown as MessagePartInfo
const sessionID = partInfo?.sessionID