refactor(background-agent): remove file persistence, use memory-only

- Remove background_tasks.json persistence (race condition with multiple instances)
- Pure memory-based task management
- Add logging for promptAsync errors
- Remove unused persist/restore methods

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-11 17:29:20 +09:00
parent 80cfe87390
commit 24a7f333a2
3 changed files with 10 additions and 95 deletions

View File

@@ -1,9 +1,9 @@
import type { PluginInput } from "@opencode-ai/plugin" import type { PluginInput } from "@opencode-ai/plugin"
import type { import type {
BackgroundTask, BackgroundTask,
BackgroundTaskStatus,
LaunchInput, LaunchInput,
} from "./types" } from "./types"
import { log } from "../../shared/logger"
type OpencodeClient = PluginInput["client"] type OpencodeClient = PluginInput["client"]
@@ -34,15 +34,12 @@ export class BackgroundManager {
private tasks: Map<string, BackgroundTask> private tasks: Map<string, BackgroundTask>
private notifications: Map<string, BackgroundTask[]> private notifications: Map<string, BackgroundTask[]>
private client: OpencodeClient private client: OpencodeClient
private storePath: string
private persistTimer?: Timer
private pollingInterval?: Timer private pollingInterval?: Timer
constructor(client: OpencodeClient, storePath: string) { constructor(client: OpencodeClient) {
this.tasks = new Map() this.tasks = new Map()
this.notifications = new Map() this.notifications = new Map()
this.client = client this.client = client
this.storePath = storePath
} }
async launch(input: LaunchInput): Promise<BackgroundTask> { async launch(input: LaunchInput): Promise<BackgroundTask> {
@@ -75,7 +72,6 @@ export class BackgroundManager {
} }
this.tasks.set(task.id, task) this.tasks.set(task.id, task)
this.persist()
this.startPolling() this.startPolling()
this.client.session.promptAsync({ this.client.session.promptAsync({
@@ -85,12 +81,12 @@ export class BackgroundManager {
parts: [{ type: "text", text: input.prompt }], parts: [{ type: "text", text: input.prompt }],
}, },
}).catch((error) => { }).catch((error) => {
log("[background-agent] promptAsync error:", error)
const existingTask = this.findBySession(sessionID) const existingTask = this.findBySession(sessionID)
if (existingTask) { if (existingTask) {
existingTask.status = "error" existingTask.status = "error"
existingTask.error = String(error) existingTask.error = String(error)
existingTask.completedAt = new Date() existingTask.completedAt = new Date()
this.persist()
} }
}) })
@@ -142,7 +138,6 @@ export class BackgroundManager {
task.progress.toolCalls += 1 task.progress.toolCalls += 1
task.progress.lastTool = partInfo.tool task.progress.lastTool = partInfo.tool
task.progress.lastUpdate = new Date() task.progress.lastUpdate = new Date()
this.persist()
} }
} }
@@ -159,7 +154,6 @@ export class BackgroundManager {
task.status = "completed" task.status = "completed"
task.completedAt = new Date() task.completedAt = new Date()
this.markForNotification(task) this.markForNotification(task)
this.persist()
} }
} }
@@ -178,8 +172,6 @@ export class BackgroundManager {
} }
this.tasks.delete(task.id) this.tasks.delete(task.id)
this.persist()
this.clearNotificationsForTask(task.id) this.clearNotificationsForTask(task.id)
} }
} }
@@ -237,13 +229,17 @@ export class BackgroundManager {
Use \`background_result\` tool with taskId="${task.id}" to retrieve the full result.` Use \`background_result\` tool with taskId="${task.id}" to retrieve the full result.`
log("[background-agent] Notifying parent session:", task.parentSessionID)
this.client.session.promptAsync({ this.client.session.promptAsync({
path: { id: task.parentSessionID }, path: { id: task.parentSessionID },
body: { body: {
parts: [{ type: "text", text: message }], parts: [{ type: "text", text: message }],
}, },
}).catch(() => { }).then(() => {
void 0 log("[background-agent] Parent session notified successfully")
}).catch((error) => {
log("[background-agent] Failed to notify parent session:", error)
}) })
} }
@@ -283,7 +279,6 @@ Use \`background_result\` tool with taskId="${task.id}" to retrieve the full res
task.status = "error" task.status = "error"
task.error = "Session not found" task.error = "Session not found"
task.completedAt = new Date() task.completedAt = new Date()
this.persist()
} }
continue continue
} }
@@ -294,7 +289,6 @@ Use \`background_result\` tool with taskId="${task.id}" to retrieve the full res
task.status = "completed" task.status = "completed"
task.completedAt = new Date() task.completedAt = new Date()
this.markForNotification(task) this.markForNotification(task)
this.persist()
this.notifyParentSession(task) this.notifyParentSession(task)
continue continue
} }
@@ -341,77 +335,4 @@ Use \`background_result\` tool with taskId="${task.id}" to retrieve the full res
this.stopPolling() this.stopPolling()
} }
} }
persist(): void {
if (this.persistTimer) {
clearTimeout(this.persistTimer)
}
this.persistTimer = setTimeout(() => {
const data = Array.from(this.tasks.values())
const serialized = data.map((task) => ({
...task,
startedAt: task.startedAt.toISOString(),
completedAt: task.completedAt?.toISOString(),
progress: task.progress
? {
...task.progress,
lastUpdate: task.progress.lastUpdate.toISOString(),
}
: undefined,
}))
Bun.write(this.storePath, JSON.stringify(serialized, null, 2)).catch(() => {
void 0
})
}, 500)
}
async restore(): Promise<void> {
try {
const file = Bun.file(this.storePath)
const exists = await file.exists()
if (!exists) return
const content = await file.text()
const data = JSON.parse(content) as Array<{
id: string
sessionID: string
parentSessionID: string
parentMessageID: string
description: string
agent: string
status: BackgroundTaskStatus
startedAt: string
completedAt?: string
result?: string
error?: string
progress?: {
toolCalls: number
lastTool?: string
lastUpdate: string
}
}>
for (const item of data) {
const task: BackgroundTask = {
...item,
startedAt: new Date(item.startedAt),
completedAt: item.completedAt ? new Date(item.completedAt) : undefined,
progress: item.progress
? {
...item.progress,
lastUpdate: new Date(item.progress.lastUpdate),
}
: undefined,
}
this.tasks.set(task.id, task)
}
if (this.hasRunningTasks()) {
this.startPolling()
}
} catch {
void 0
}
}
} }

View File

@@ -164,11 +164,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
updateTerminalTitle({ sessionId: "main" }); updateTerminalTitle({ sessionId: "main" });
const backgroundManager = new BackgroundManager( const backgroundManager = new BackgroundManager(ctx.client);
ctx.client,
path.join(ctx.directory, ".opencode", "background-tasks.json")
);
await backgroundManager.restore();
const backgroundNotificationHook = createBackgroundNotificationHook(backgroundManager); const backgroundNotificationHook = createBackgroundNotificationHook(backgroundManager);
const backgroundTools = createBackgroundTools(backgroundManager, ctx.client); const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);

View File

@@ -207,8 +207,6 @@ Only running tasks can be cancelled.`
task.status = "cancelled" task.status = "cancelled"
task.completedAt = new Date() task.completedAt = new Date()
manager.persist()
return `✅ Task cancelled successfully return `✅ Task cancelled successfully
Task ID: ${task.id} Task ID: ${task.id}