fix(background-agent): add polling mechanism for child session tracking
- Replace unreliable event-based tracking with 2-second polling - Use SDK session.get() to detect completion (status === idle) - Use SDK session.messages() to count tool_use parts for progress - Auto-start polling on launch, auto-stop when no running tasks - Resume polling on restore if running tasks exist Fixes: Child session events not reaching plugin event handler 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -7,7 +7,21 @@
|
|||||||
"description": "Explore opencode in codebase",
|
"description": "Explore opencode in codebase",
|
||||||
"agent": "explore",
|
"agent": "explore",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startedAt": 1765434417395,
|
"startedAt": "2025-12-11T06:26:57.395Z",
|
||||||
"completedAt": 1765434456778
|
"completedAt": "2025-12-11T06:27:36.778Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bg_392b9c9b",
|
||||||
|
"sessionID": "ses_4f38ebf4fffeJZBocIn3UVv7vE",
|
||||||
|
"parentSessionID": "ses_4f38eefa0ffeKV0pVNnwT37P5L",
|
||||||
|
"parentMessageID": "msg_b0c7110d2001TMBlPeEYIrByvs",
|
||||||
|
"description": "Test explore agent",
|
||||||
|
"agent": "explore",
|
||||||
|
"status": "running",
|
||||||
|
"startedAt": "2025-12-11T08:05:07.378Z",
|
||||||
|
"progress": {
|
||||||
|
"toolCalls": 0,
|
||||||
|
"lastUpdate": "2025-12-11T08:05:07.378Z"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -36,6 +36,7 @@ export class BackgroundManager {
|
|||||||
private client: OpencodeClient
|
private client: OpencodeClient
|
||||||
private storePath: string
|
private storePath: string
|
||||||
private persistTimer?: Timer
|
private persistTimer?: Timer
|
||||||
|
private pollingInterval?: Timer
|
||||||
|
|
||||||
constructor(client: OpencodeClient, storePath: string) {
|
constructor(client: OpencodeClient, storePath: string) {
|
||||||
this.tasks = new Map()
|
this.tasks = new Map()
|
||||||
@@ -75,6 +76,7 @@ export class BackgroundManager {
|
|||||||
|
|
||||||
this.tasks.set(task.id, task)
|
this.tasks.set(task.id, task)
|
||||||
this.persist()
|
this.persist()
|
||||||
|
this.startPolling()
|
||||||
|
|
||||||
this.client.session.promptAsync({
|
this.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
@@ -207,6 +209,97 @@ export class BackgroundManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private startPolling(): void {
|
||||||
|
if (this.pollingInterval) return
|
||||||
|
|
||||||
|
this.pollingInterval = setInterval(() => {
|
||||||
|
this.pollRunningTasks()
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopPolling(): void {
|
||||||
|
if (this.pollingInterval) {
|
||||||
|
clearInterval(this.pollingInterval)
|
||||||
|
this.pollingInterval = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasRunningTasks(): boolean {
|
||||||
|
for (const task of this.tasks.values()) {
|
||||||
|
if (task.status === "running") return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private async pollRunningTasks(): Promise<void> {
|
||||||
|
for (const task of this.tasks.values()) {
|
||||||
|
if (task.status !== "running") continue
|
||||||
|
|
||||||
|
try {
|
||||||
|
const infoResult = await this.client.session.get({
|
||||||
|
path: { id: task.sessionID },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (infoResult.error) {
|
||||||
|
task.status = "error"
|
||||||
|
task.error = "Session not found"
|
||||||
|
task.completedAt = new Date()
|
||||||
|
this.persist()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionInfo = infoResult.data as { status?: string }
|
||||||
|
|
||||||
|
if (sessionInfo.status === "idle") {
|
||||||
|
task.status = "completed"
|
||||||
|
task.completedAt = new Date()
|
||||||
|
this.markForNotification(task)
|
||||||
|
this.persist()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagesResult = await this.client.session.messages({
|
||||||
|
path: { id: task.sessionID },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!messagesResult.error && messagesResult.data) {
|
||||||
|
const messages = messagesResult.data as Array<{
|
||||||
|
info?: { role?: string }
|
||||||
|
parts?: Array<{ type?: string; tool?: string; name?: string }>
|
||||||
|
}>
|
||||||
|
const assistantMsgs = messages.filter(
|
||||||
|
(m) => m.info?.role === "assistant"
|
||||||
|
)
|
||||||
|
|
||||||
|
let toolCalls = 0
|
||||||
|
let lastTool: string | undefined
|
||||||
|
|
||||||
|
for (const msg of assistantMsgs) {
|
||||||
|
const parts = msg.parts ?? []
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part.type === "tool_use" || part.tool) {
|
||||||
|
toolCalls++
|
||||||
|
lastTool = part.tool || part.name || "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.progress) {
|
||||||
|
task.progress.toolCalls = toolCalls
|
||||||
|
task.progress.lastTool = lastTool
|
||||||
|
task.progress.lastUpdate = new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
void 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hasRunningTasks()) {
|
||||||
|
this.stopPolling()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
persist(): void {
|
persist(): void {
|
||||||
if (this.persistTimer) {
|
if (this.persistTimer) {
|
||||||
clearTimeout(this.persistTimer)
|
clearTimeout(this.persistTimer)
|
||||||
@@ -271,6 +364,10 @@ export class BackgroundManager {
|
|||||||
}
|
}
|
||||||
this.tasks.set(task.id, task)
|
this.tasks.set(task.id, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.hasRunningTasks()) {
|
||||||
|
this.startPolling()
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
void 0
|
void 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user