bobs-workshop 0.3.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/README.md +199 -210
- package/bin/bobs-workshop.js +109 -0
- package/config/agents.json +27 -0
- package/dist/plugins/bobs-workshop.js +34 -0
- package/dist/tools/background-agent/cancel.d.ts +3 -0
- package/dist/tools/background-agent/cancel.d.ts.map +1 -0
- package/dist/tools/background-agent/cancel.js +52 -0
- package/dist/tools/background-agent/concurrency.d.ts +15 -0
- package/dist/tools/background-agent/concurrency.d.ts.map +1 -0
- package/dist/tools/background-agent/concurrency.js +61 -0
- package/dist/tools/background-agent/index.d.ts +8 -0
- package/dist/tools/background-agent/index.d.ts.map +1 -0
- package/dist/tools/background-agent/index.js +7 -0
- package/dist/tools/background-agent/launch.d.ts +6 -0
- package/dist/tools/background-agent/launch.d.ts.map +1 -0
- package/dist/tools/background-agent/launch.js +33 -0
- package/dist/tools/background-agent/list.d.ts +7 -0
- package/dist/tools/background-agent/list.d.ts.map +1 -0
- package/dist/tools/background-agent/list.js +40 -0
- package/dist/tools/background-agent/manager.d.ts +29 -0
- package/dist/tools/background-agent/manager.d.ts.map +1 -0
- package/dist/tools/background-agent/manager.js +377 -0
- package/dist/tools/background-agent/output.d.ts +3 -0
- package/dist/tools/background-agent/output.d.ts.map +1 -0
- package/dist/tools/background-agent/output.js +41 -0
- package/dist/tools/background-agent/types.d.ts +46 -0
- package/dist/tools/background-agent/types.d.ts.map +1 -0
- package/dist/tools/background-agent/types.js +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/manual/index.d.ts +3 -0
- package/dist/tools/manual/index.d.ts.map +1 -0
- package/dist/tools/manual/index.js +2 -0
- package/dist/tools/manual/manual-update.d.ts +4 -0
- package/dist/tools/manual/manual-update.d.ts.map +1 -0
- package/dist/tools/manual/manual-update.js +190 -0
- package/dist/tools/manual/verify-manual.d.ts +4 -0
- package/dist/tools/manual/verify-manual.d.ts.map +1 -0
- package/dist/tools/manual/verify-manual.js +46 -0
- package/package.json +35 -67
- package/postinstall.js +190 -0
- package/src/agents/alice.md +466 -0
- package/src/agents/bob-rev.md +493 -0
- package/src/agents/bob-send.md +277 -0
- package/src/agents/bob.md +442 -0
- package/src/agents/trace.md +451 -0
- package/src/plugins/bobs-workshop.ts +45 -0
- package/src/skills/api-patterns/SKILL.md +376 -0
- package/src/skills/architecture/SKILL.md +271 -0
- package/src/skills/bobs-workshop/performance/icon.svg +3 -0
- package/src/skills/brainstorming/SKILL.md +210 -0
- package/src/skills/clean-code/SKILL.md +151 -0
- package/src/skills/code-review-checklist/SKILL.md +220 -0
- package/src/skills/database-design/SKILL.md +271 -0
- package/src/skills/exploration/SKILL.md +257 -0
- package/src/skills/frontend-ui-ux/SKILL.md +78 -0
- package/src/skills/git-master/SKILL.md +1105 -0
- package/src/skills/performance/SKILL.md +144 -0
- package/src/skills/performance/icon.svg +3 -0
- package/src/skills/plan-writing/SKILL.md +225 -0
- package/src/skills/security/SKILL.md +410 -0
- package/src/skills/simplification/SKILL.md +238 -0
- package/src/skills/systematic-debugging/SKILL.md +175 -0
- package/src/skills/testing-patterns/SKILL.md +305 -0
- package/src/skills/verification/SKILL.md +286 -0
- package/src/tools/background-agent/cancel.ts +67 -0
- package/src/tools/background-agent/concurrency.ts +71 -0
- package/src/tools/background-agent/index.ts +7 -0
- package/src/tools/background-agent/launch.ts +39 -0
- package/src/tools/background-agent/list.ts +50 -0
- package/src/tools/background-agent/manager.ts +455 -0
- package/src/tools/background-agent/output.ts +57 -0
- package/src/tools/background-agent/types.ts +55 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/manual/index.ts +2 -0
- package/src/tools/manual/manual-update.ts +197 -0
- package/src/tools/manual/verify-manual.ts +55 -0
- package/uninstall.js +64 -0
- package/Claude.md +0 -162
- package/bin/bobs-mcp-server.js +0 -11
- package/bin/bobs-mcp.js +0 -130
- package/dist/api/taskLogger.js +0 -106
- package/dist/api/taskLogger.js.map +0 -1
- package/dist/cli/checker.js +0 -401
- package/dist/cli/checker.js.map +0 -1
- package/dist/cli/cleanup.js +0 -131
- package/dist/cli/cleanup.js.map +0 -1
- package/dist/cli/debug.js +0 -157
- package/dist/cli/debug.js.map +0 -1
- package/dist/cli/health.js +0 -97
- package/dist/cli/health.js.map +0 -1
- package/dist/cli/setup.js +0 -81
- package/dist/cli/setup.js.map +0 -1
- package/dist/cli/workshop.js +0 -42
- package/dist/cli/workshop.js.map +0 -1
- package/dist/dashboard/server.js +0 -1203
- package/dist/dashboard/server.js.map +0 -1
- package/dist/index.js +0 -960
- package/dist/index.js.map +0 -1
- package/dist/prompts/architect.js +0 -221
- package/dist/prompts/architect.js.map +0 -1
- package/dist/prompts/debugger.js +0 -257
- package/dist/prompts/debugger.js.map +0 -1
- package/dist/prompts/engineer.js +0 -249
- package/dist/prompts/engineer.js.map +0 -1
- package/dist/prompts/orchestrator.js +0 -304
- package/dist/prompts/orchestrator.js.map +0 -1
- package/dist/prompts/reviewer.js +0 -289
- package/dist/prompts/reviewer.js.map +0 -1
- package/dist/services/activitySummarizer.js +0 -388
- package/dist/services/activitySummarizer.js.map +0 -1
- package/dist/services/changeValidator.js +0 -396
- package/dist/services/changeValidator.js.map +0 -1
- package/dist/services/claudeOrchestrator.js +0 -343
- package/dist/services/claudeOrchestrator.js.map +0 -1
- package/dist/services/fileMonitor.js +0 -250
- package/dist/services/fileMonitor.js.map +0 -1
- package/dist/services/implementationSummarizer.js +0 -306
- package/dist/services/implementationSummarizer.js.map +0 -1
- package/dist/services/liveMonitor.js +0 -315
- package/dist/services/liveMonitor.js.map +0 -1
- package/dist/services/mcpAuditLogger.js +0 -104
- package/dist/services/mcpAuditLogger.js.map +0 -1
- package/dist/services/mcpLogger.js +0 -223
- package/dist/services/mcpLogger.js.map +0 -1
- package/dist/services/tmuxManager.js +0 -541
- package/dist/services/tmuxManager.js.map +0 -1
- package/dist/tools/approvalTools.js +0 -244
- package/dist/tools/approvalTools.js.map +0 -1
- package/dist/tools/autoDebugger.js +0 -147
- package/dist/tools/autoDebugger.js.map +0 -1
- package/dist/tools/cleanupService.js +0 -221
- package/dist/tools/cleanupService.js.map +0 -1
- package/dist/tools/dashboardTools.js +0 -342
- package/dist/tools/dashboardTools.js.map +0 -1
- package/dist/tools/developmentNudges.js +0 -336
- package/dist/tools/developmentNudges.js.map +0 -1
- package/dist/tools/gitTools.js +0 -741
- package/dist/tools/gitTools.js.map +0 -1
- package/dist/tools/orchestratorTools.js +0 -832
- package/dist/tools/orchestratorTools.js.map +0 -1
- package/dist/tools/searchCache.js +0 -64
- package/dist/tools/searchCache.js.map +0 -1
- package/dist/tools/searchTools.js +0 -1107
- package/dist/tools/searchTools.js.map +0 -1
- package/dist/tools/semgrep-patterns.js +0 -296
- package/dist/tools/semgrep-patterns.js.map +0 -1
- package/dist/tools/specTools.js +0 -332
- package/dist/tools/specTools.js.map +0 -1
- package/dist/tools/structural/__tests__/orchestrator.test.js +0 -61
- package/dist/tools/structural/__tests__/orchestrator.test.js.map +0 -1
- package/dist/tools/structural/cache.js +0 -226
- package/dist/tools/structural/cache.js.map +0 -1
- package/dist/tools/structural/engines/python/index.js +0 -118
- package/dist/tools/structural/engines/python/index.js.map +0 -1
- package/dist/tools/structural/engines/typescript/__tests__/typescript-engine.test.js +0 -97
- package/dist/tools/structural/engines/typescript/__tests__/typescript-engine.test.js.map +0 -1
- package/dist/tools/structural/engines/typescript/analyzer.js +0 -433
- package/dist/tools/structural/engines/typescript/analyzer.js.map +0 -1
- package/dist/tools/structural/engines/typescript/index.js +0 -381
- package/dist/tools/structural/engines/typescript/index.js.map +0 -1
- package/dist/tools/structural/engines/typescript/utils.js +0 -279
- package/dist/tools/structural/engines/typescript/utils.js.map +0 -1
- package/dist/tools/structural/index.js +0 -248
- package/dist/tools/structural/index.js.map +0 -1
- package/dist/tools/structural/types.js +0 -18
- package/dist/tools/structural/types.js.map +0 -1
- package/dist/tools/tmuxTools.js +0 -100
- package/dist/tools/tmuxTools.js.map +0 -1
- package/dist/tools/workRecorder.js +0 -215
- package/dist/tools/workRecorder.js.map +0 -1
- package/dist/tools/worktreeTools.js +0 -705
- package/dist/tools/worktreeTools.js.map +0 -1
- package/dist/utils/__tests__/integration.test.js +0 -57
- package/dist/utils/__tests__/integration.test.js.map +0 -1
- package/dist/utils/__tests__/serverDetection.test.js +0 -151
- package/dist/utils/__tests__/serverDetection.test.js.map +0 -1
- package/dist/utils/errorHandling.js +0 -336
- package/dist/utils/errorHandling.js.map +0 -1
- package/dist/utils/processManager.js +0 -172
- package/dist/utils/processManager.js.map +0 -1
- package/dist/utils/reliability.js +0 -263
- package/dist/utils/reliability.js.map +0 -1
- package/dist/utils/responseFormatter.js +0 -250
- package/dist/utils/responseFormatter.js.map +0 -1
- package/dist/utils/serverDetection.js +0 -133
- package/dist/utils/serverDetection.js.map +0 -1
- package/dist/utils/specMigration.js +0 -105
- package/dist/utils/specMigration.js.map +0 -1
- package/dist/validation/schemas.js +0 -299
- package/dist/validation/schemas.js.map +0 -1
- package/public/.well-known/mcp/manifest.json +0 -473
- package/public/index.html +0 -3157
- package/public/index.html.backup +0 -2805
- package/public/index.html.backup2 +0 -1292
- package/scripts/cleanup-system-logs.ts +0 -121
- package/scripts/init-workspace.js +0 -63
- package/scripts/install-search-tools.js +0 -116
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { ConcurrencyManager } from "./concurrency";
|
|
2
|
+
const TASK_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
3
|
+
const MIN_STABILITY_TIME_MS = 10 * 1000; // 10 seconds before stability detection
|
|
4
|
+
export class BackgroundManager {
|
|
5
|
+
tasks;
|
|
6
|
+
client;
|
|
7
|
+
directory;
|
|
8
|
+
pollingInterval;
|
|
9
|
+
concurrencyManager;
|
|
10
|
+
shutdownTriggered = false;
|
|
11
|
+
static instance = null;
|
|
12
|
+
static getInstance(ctx) {
|
|
13
|
+
if (!BackgroundManager.instance) {
|
|
14
|
+
BackgroundManager.instance = new BackgroundManager(ctx);
|
|
15
|
+
}
|
|
16
|
+
return BackgroundManager.instance;
|
|
17
|
+
}
|
|
18
|
+
constructor(ctx) {
|
|
19
|
+
this.tasks = new Map();
|
|
20
|
+
this.client = ctx.client;
|
|
21
|
+
this.directory = ctx.directory;
|
|
22
|
+
this.concurrencyManager = new ConcurrencyManager();
|
|
23
|
+
}
|
|
24
|
+
async launch(input) {
|
|
25
|
+
console.log("[background-agent] Launching task:", {
|
|
26
|
+
agent: input.agent,
|
|
27
|
+
model: input.model,
|
|
28
|
+
description: input.description,
|
|
29
|
+
});
|
|
30
|
+
if (!input.agent || input.agent.trim() === "") {
|
|
31
|
+
throw new Error("Agent parameter is required");
|
|
32
|
+
}
|
|
33
|
+
const task = {
|
|
34
|
+
id: `bg_${crypto.randomUUID().slice(0, 8)}`,
|
|
35
|
+
status: "pending",
|
|
36
|
+
queuedAt: new Date(),
|
|
37
|
+
description: input.description,
|
|
38
|
+
prompt: input.prompt,
|
|
39
|
+
agent: input.agent,
|
|
40
|
+
parentSessionID: input.parentSessionID,
|
|
41
|
+
model: input.model,
|
|
42
|
+
skills: input.skills,
|
|
43
|
+
};
|
|
44
|
+
this.tasks.set(task.id, task);
|
|
45
|
+
// Trigger processing
|
|
46
|
+
this.startTask(task, input).catch((error) => {
|
|
47
|
+
console.error("[background-agent] Error starting task:", error);
|
|
48
|
+
task.status = "error";
|
|
49
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
50
|
+
task.completedAt = new Date();
|
|
51
|
+
});
|
|
52
|
+
return task;
|
|
53
|
+
}
|
|
54
|
+
async startTask(task, input) {
|
|
55
|
+
const concurrencyKey = input.model || input.agent;
|
|
56
|
+
await this.concurrencyManager.acquire(concurrencyKey);
|
|
57
|
+
try {
|
|
58
|
+
const createResult = await this.client.session.create({
|
|
59
|
+
body: {
|
|
60
|
+
parentID: input.parentSessionID,
|
|
61
|
+
title: `Background: ${input.description}`,
|
|
62
|
+
},
|
|
63
|
+
query: {
|
|
64
|
+
directory: this.directory,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
if (createResult.error) {
|
|
68
|
+
throw new Error(`Failed to create background session: ${createResult.error}`);
|
|
69
|
+
}
|
|
70
|
+
const sessionID = createResult.data.id;
|
|
71
|
+
task.status = "running";
|
|
72
|
+
task.startedAt = new Date();
|
|
73
|
+
task.sessionID = sessionID;
|
|
74
|
+
task.progress = {
|
|
75
|
+
toolCalls: 0,
|
|
76
|
+
lastUpdate: new Date(),
|
|
77
|
+
};
|
|
78
|
+
this.startPolling();
|
|
79
|
+
console.log("[background-agent] Launching task:", {
|
|
80
|
+
taskId: task.id,
|
|
81
|
+
sessionID,
|
|
82
|
+
agent: input.agent,
|
|
83
|
+
});
|
|
84
|
+
// Build skill content
|
|
85
|
+
let skillContent = input.skillContent || "";
|
|
86
|
+
if (input.skills && input.skills.length > 0) {
|
|
87
|
+
const { join } = await import("node:path");
|
|
88
|
+
const { readFileSync, existsSync } = await import("node:fs");
|
|
89
|
+
for (const skillName of input.skills) {
|
|
90
|
+
const skillPath = join(this.directory, ".opencode", "skill", "bobs-workshop", skillName, "SKILL.md");
|
|
91
|
+
if (existsSync(skillPath)) {
|
|
92
|
+
const skillFile = readFileSync(skillPath, "utf8");
|
|
93
|
+
skillContent += `\n\n---\n## Skill: ${skillName}\n\n${skillFile}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Add MANUAL content if provided
|
|
98
|
+
if (input.manual_path) {
|
|
99
|
+
const { existsSync, readFileSync } = await import("node:fs");
|
|
100
|
+
if (existsSync(input.manual_path)) {
|
|
101
|
+
const manualContent = readFileSync(input.manual_path, "utf8");
|
|
102
|
+
skillContent = skillContent
|
|
103
|
+
? `${skillContent}\n\n## MANUAL Context:\n${manualContent}`
|
|
104
|
+
: manualContent;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Fire-and-forget prompt
|
|
108
|
+
this.client.session.prompt({
|
|
109
|
+
path: { id: sessionID },
|
|
110
|
+
body: {
|
|
111
|
+
agent: input.agent,
|
|
112
|
+
...(input.model ? { model: input.model } : {}),
|
|
113
|
+
system: skillContent || undefined,
|
|
114
|
+
tools: {
|
|
115
|
+
task: false,
|
|
116
|
+
delegate_task: false,
|
|
117
|
+
},
|
|
118
|
+
parts: [{ type: "text", text: input.prompt }],
|
|
119
|
+
},
|
|
120
|
+
}).catch((error) => {
|
|
121
|
+
console.error("[background-agent] Prompt error:", error);
|
|
122
|
+
const existingTask = this.tasks.get(task.id);
|
|
123
|
+
if (existingTask && existingTask.status === "running") {
|
|
124
|
+
existingTask.status = "error";
|
|
125
|
+
existingTask.error = error instanceof Error ? error.message : String(error);
|
|
126
|
+
existingTask.completedAt = new Date();
|
|
127
|
+
if (concurrencyKey) {
|
|
128
|
+
this.concurrencyManager.release(concurrencyKey);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.concurrencyManager.release(concurrencyKey);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
getTask(id) {
|
|
139
|
+
return this.tasks.get(id);
|
|
140
|
+
}
|
|
141
|
+
getAllTasks() {
|
|
142
|
+
return Array.from(this.tasks.values());
|
|
143
|
+
}
|
|
144
|
+
getRunningTasks() {
|
|
145
|
+
return Array.from(this.tasks.values()).filter((t) => t.status === "running");
|
|
146
|
+
}
|
|
147
|
+
handleEvent(event) {
|
|
148
|
+
if (event.type === "session.idle") {
|
|
149
|
+
const sessionID = event.properties?.sessionID;
|
|
150
|
+
if (!sessionID)
|
|
151
|
+
return;
|
|
152
|
+
const task = Array.from(this.tasks.values()).find((t) => t.sessionID === sessionID);
|
|
153
|
+
if (!task || task.status !== "running")
|
|
154
|
+
return;
|
|
155
|
+
const startedAt = task.startedAt;
|
|
156
|
+
if (!startedAt)
|
|
157
|
+
return;
|
|
158
|
+
const elapsedMs = Date.now() - startedAt.getTime();
|
|
159
|
+
const MIN_IDLE_TIME_MS = 5000; // 5 seconds
|
|
160
|
+
if (elapsedMs < MIN_IDLE_TIME_MS) {
|
|
161
|
+
console.log("[background-agent] Ignoring early session.idle");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.tryCompleteTask(task, "session.idle");
|
|
165
|
+
}
|
|
166
|
+
if (event.type === "session.deleted") {
|
|
167
|
+
const info = event.properties?.info;
|
|
168
|
+
if (!info?.id)
|
|
169
|
+
return;
|
|
170
|
+
const task = Array.from(this.tasks.values()).find((t) => t.sessionID === info.id);
|
|
171
|
+
if (!task)
|
|
172
|
+
return;
|
|
173
|
+
if (task.status === "running") {
|
|
174
|
+
task.status = "cancelled";
|
|
175
|
+
task.completedAt = new Date();
|
|
176
|
+
task.error = "Session deleted";
|
|
177
|
+
}
|
|
178
|
+
this.tasks.delete(task.id);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async tryCompleteTask(task, source) {
|
|
182
|
+
if (task.status !== "running") {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
// Validate session has output before completing
|
|
186
|
+
if (!task.sessionID)
|
|
187
|
+
return false;
|
|
188
|
+
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
189
|
+
if (!hasOutput) {
|
|
190
|
+
console.log("[background-agent] No valid output yet, waiting");
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
task.status = "completed";
|
|
194
|
+
task.completedAt = new Date();
|
|
195
|
+
// Release concurrency
|
|
196
|
+
if (task.model) {
|
|
197
|
+
this.concurrencyManager.release(task.model);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this.concurrencyManager.release(task.agent);
|
|
201
|
+
}
|
|
202
|
+
console.log(`[background-agent] Task completed via ${source}:`, task.id);
|
|
203
|
+
// Auto-remove after 5 minutes
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
this.tasks.delete(task.id);
|
|
206
|
+
console.log("[background-agent] Removed completed task from memory:", task.id);
|
|
207
|
+
}, 5 * 60 * 1000);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
async validateSessionHasOutput(sessionID) {
|
|
211
|
+
try {
|
|
212
|
+
const response = await this.client.session.messages({
|
|
213
|
+
path: { id: sessionID },
|
|
214
|
+
});
|
|
215
|
+
const messages = (response.data ?? []);
|
|
216
|
+
const hasAssistantOrToolMessage = messages.some((m) => m.info?.role === "assistant" || m.info?.role === "tool");
|
|
217
|
+
if (!hasAssistantOrToolMessage)
|
|
218
|
+
return false;
|
|
219
|
+
const hasContent = messages.some((m) => {
|
|
220
|
+
if (m.info?.role !== "assistant" && m.info?.role !== "tool")
|
|
221
|
+
return false;
|
|
222
|
+
const parts = m.parts ?? [];
|
|
223
|
+
return parts.some((p) => (p.type === "text" && p.text && p.text.trim().length > 0) ||
|
|
224
|
+
(p.type === "reasoning" && p.text && p.text.trim().length > 0) ||
|
|
225
|
+
p.type === "tool" ||
|
|
226
|
+
(p.type === "tool_result" && p.content !== undefined));
|
|
227
|
+
});
|
|
228
|
+
return hasContent;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error("[background-agent] Error validating session output:", error);
|
|
232
|
+
return true; // Allow completion on error
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
startPolling() {
|
|
236
|
+
if (this.pollingInterval)
|
|
237
|
+
return;
|
|
238
|
+
this.pollingInterval = setInterval(() => {
|
|
239
|
+
this.pollRunningTasks();
|
|
240
|
+
}, 2000);
|
|
241
|
+
this.pollingInterval.unref();
|
|
242
|
+
}
|
|
243
|
+
stopPolling() {
|
|
244
|
+
if (this.pollingInterval) {
|
|
245
|
+
clearInterval(this.pollingInterval);
|
|
246
|
+
this.pollingInterval = undefined;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async pollRunningTasks() {
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
// Prune stale tasks
|
|
252
|
+
for (const [taskId, task] of this.tasks.entries()) {
|
|
253
|
+
const timestamp = task.status === "pending"
|
|
254
|
+
? task.queuedAt?.getTime()
|
|
255
|
+
: task.startedAt?.getTime();
|
|
256
|
+
if (!timestamp)
|
|
257
|
+
continue;
|
|
258
|
+
const age = now - timestamp;
|
|
259
|
+
if (age > TASK_TTL_MS) {
|
|
260
|
+
const errorMessage = task.status === "pending"
|
|
261
|
+
? "Task timed out while queued (30 minutes)"
|
|
262
|
+
: "Task timed out after 30 minutes";
|
|
263
|
+
console.log("[background-agent] Pruning stale task:", { taskId, age });
|
|
264
|
+
task.status = "error";
|
|
265
|
+
task.error = errorMessage;
|
|
266
|
+
task.completedAt = new Date();
|
|
267
|
+
this.tasks.delete(taskId);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Check running tasks
|
|
271
|
+
const statusResult = await this.client.session.status();
|
|
272
|
+
const allStatuses = (statusResult.data ?? {});
|
|
273
|
+
for (const task of this.tasks.values()) {
|
|
274
|
+
if (task.status !== "running")
|
|
275
|
+
continue;
|
|
276
|
+
const sessionID = task.sessionID;
|
|
277
|
+
if (!sessionID)
|
|
278
|
+
continue;
|
|
279
|
+
try {
|
|
280
|
+
const sessionStatus = allStatuses[sessionID];
|
|
281
|
+
if (sessionStatus?.type === "idle") {
|
|
282
|
+
const hasValidOutput = await this.validateSessionHasOutput(sessionID);
|
|
283
|
+
if (!hasValidOutput) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
// Re-check status after async operation
|
|
287
|
+
if (task.status !== "running")
|
|
288
|
+
continue;
|
|
289
|
+
await this.tryCompleteTask(task, "polling (idle status)");
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// Stability detection
|
|
293
|
+
const messagesResult = await this.client.session.messages({
|
|
294
|
+
path: { id: sessionID },
|
|
295
|
+
});
|
|
296
|
+
if (!messagesResult.error && messagesResult.data) {
|
|
297
|
+
const messages = messagesResult.data;
|
|
298
|
+
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant");
|
|
299
|
+
let toolCalls = 0;
|
|
300
|
+
let lastTool;
|
|
301
|
+
for (const msg of assistantMsgs) {
|
|
302
|
+
const parts = msg.parts ?? [];
|
|
303
|
+
for (const part of parts) {
|
|
304
|
+
if (part.type === "tool_use" || part.tool) {
|
|
305
|
+
toolCalls++;
|
|
306
|
+
lastTool = part.tool || part.name || "unknown";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (!task.progress) {
|
|
311
|
+
task.progress = { toolCalls: 0, lastUpdate: new Date() };
|
|
312
|
+
}
|
|
313
|
+
task.progress.toolCalls = toolCalls;
|
|
314
|
+
task.progress.lastTool = lastTool;
|
|
315
|
+
task.progress.lastUpdate = new Date();
|
|
316
|
+
const currentMsgCount = messages.length;
|
|
317
|
+
const startedAt = task.startedAt;
|
|
318
|
+
if (!startedAt)
|
|
319
|
+
continue;
|
|
320
|
+
const elapsedMs = Date.now() - startedAt.getTime();
|
|
321
|
+
if (elapsedMs >= MIN_STABILITY_TIME_MS) {
|
|
322
|
+
if (task.lastMsgCount === currentMsgCount) {
|
|
323
|
+
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
324
|
+
if ((task.stablePolls ?? 0) >= 3) {
|
|
325
|
+
const recheckStatus = await this.client.session.status();
|
|
326
|
+
const recheckData = (recheckStatus.data ?? {});
|
|
327
|
+
const currentStatus = recheckData[sessionID];
|
|
328
|
+
if (currentStatus?.type !== "idle") {
|
|
329
|
+
task.stablePolls = 0;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const hasValidOutput = await this.validateSessionHasOutput(sessionID);
|
|
333
|
+
if (!hasValidOutput) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (task.status !== "running")
|
|
337
|
+
continue;
|
|
338
|
+
await this.tryCompleteTask(task, "stability detection");
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
task.stablePolls = 0;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
task.lastMsgCount = currentMsgCount;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error("[background-agent] Poll error:", error);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Stop polling if no running tasks
|
|
354
|
+
if (!this.getRunningTasks().length) {
|
|
355
|
+
this.stopPolling();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
shutdown() {
|
|
359
|
+
if (this.shutdownTriggered)
|
|
360
|
+
return;
|
|
361
|
+
this.shutdownTriggered = true;
|
|
362
|
+
console.log("[background-agent] Shutting down BackgroundManager");
|
|
363
|
+
this.stopPolling();
|
|
364
|
+
// Release concurrency for all running tasks
|
|
365
|
+
for (const task of this.tasks.values()) {
|
|
366
|
+
if (task.model) {
|
|
367
|
+
this.concurrencyManager.release(task.model);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
this.concurrencyManager.release(task.agent);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.concurrencyManager.clear();
|
|
374
|
+
this.tasks.clear();
|
|
375
|
+
console.log("[background-agent] Shutdown complete");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../../src/tools/background-agent/output.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,oBAAoB,EAAE,GAkD1B,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { BackgroundManager } from "./manager.js";
|
|
3
|
+
const BackgroundOutputTool = tool({
|
|
4
|
+
description: "Collect output from a specific background agent session",
|
|
5
|
+
args: {
|
|
6
|
+
task_id: tool.schema.string().describe("Background task ID to retrieve output from"),
|
|
7
|
+
},
|
|
8
|
+
async execute(args, context) {
|
|
9
|
+
const ctx = context;
|
|
10
|
+
const manager = BackgroundManager.getInstance(ctx);
|
|
11
|
+
const task = manager.getTask(args.task_id);
|
|
12
|
+
if (!task) {
|
|
13
|
+
return `❌ Task ${args.task_id} not found`;
|
|
14
|
+
}
|
|
15
|
+
if (task.status === "pending") {
|
|
16
|
+
return `⏳ Task ${args.task_id} is still queued...`;
|
|
17
|
+
}
|
|
18
|
+
if (task.status === "running") {
|
|
19
|
+
return `🔄 Task ${args.task_id} still running...`;
|
|
20
|
+
}
|
|
21
|
+
if (task.status === "error") {
|
|
22
|
+
return `❌ Task ${args.task_id} failed: ${task.error}`;
|
|
23
|
+
}
|
|
24
|
+
if (!task.sessionID) {
|
|
25
|
+
return `❌ Task ${args.task_id} has no session`;
|
|
26
|
+
}
|
|
27
|
+
const messagesResult = await ctx.client.session.messages({
|
|
28
|
+
path: { id: task.sessionID },
|
|
29
|
+
});
|
|
30
|
+
if (messagesResult.error) {
|
|
31
|
+
return `❌ Failed to retrieve output: ${messagesResult.error}`;
|
|
32
|
+
}
|
|
33
|
+
const messages = messagesResult.data;
|
|
34
|
+
const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
|
|
35
|
+
const output = assistantMessages
|
|
36
|
+
.map((m) => m.parts?.map((p) => p.text).join("\n"))
|
|
37
|
+
.join("\n\n---\n\n");
|
|
38
|
+
return `📥 Output from ${args.task_id}:\n\n${output}`;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
export default BackgroundOutputTool;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type BackgroundTaskStatus = "pending" | "running" | "completed" | "error" | "cancelled";
|
|
2
|
+
export interface TaskProgress {
|
|
3
|
+
toolCalls: number;
|
|
4
|
+
lastTool?: string;
|
|
5
|
+
lastUpdate: Date;
|
|
6
|
+
lastMessage?: string;
|
|
7
|
+
lastMessageAt?: Date;
|
|
8
|
+
}
|
|
9
|
+
export interface BackgroundTask {
|
|
10
|
+
id: string;
|
|
11
|
+
sessionID?: string;
|
|
12
|
+
parentSessionID: string;
|
|
13
|
+
description: string;
|
|
14
|
+
prompt: string;
|
|
15
|
+
agent: string;
|
|
16
|
+
status: BackgroundTaskStatus;
|
|
17
|
+
queuedAt?: Date;
|
|
18
|
+
startedAt?: Date;
|
|
19
|
+
completedAt?: Date;
|
|
20
|
+
error?: string;
|
|
21
|
+
progress?: TaskProgress;
|
|
22
|
+
model?: string;
|
|
23
|
+
skills?: string[];
|
|
24
|
+
lastMsgCount?: number;
|
|
25
|
+
stablePolls?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface LaunchInput {
|
|
28
|
+
description: string;
|
|
29
|
+
prompt: string;
|
|
30
|
+
agent: string;
|
|
31
|
+
parentSessionID: string;
|
|
32
|
+
model?: string;
|
|
33
|
+
skills?: string[];
|
|
34
|
+
skillContent?: string;
|
|
35
|
+
manual_path?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ModelConfig {
|
|
38
|
+
providerID: string;
|
|
39
|
+
modelID: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ResumeInput {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
prompt: string;
|
|
44
|
+
parentSessionID: string;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/background-agent/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,SAAS,GACT,WAAW,GACX,OAAO,GACP,WAAW,CAAC;AAEhB,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as background_agent } from "./background-agent/launch.js";
|
|
2
|
+
export { default as list_background_tasks } from "./background-agent/list.js";
|
|
3
|
+
export { default as background_output } from "./background-agent/output.js";
|
|
4
|
+
export { default as background_cancel } from "./background-agent/cancel.js";
|
|
5
|
+
export { default as manual_update } from "./manual/manual-update.js";
|
|
6
|
+
export { default as verify_manual } from "./manual/verify-manual.js";
|
|
7
|
+
export * from "./background-agent/index.js";
|
|
8
|
+
export * from "./manual/index.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACrE,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as background_agent } from "./background-agent/launch.js";
|
|
2
|
+
export { default as list_background_tasks } from "./background-agent/list.js";
|
|
3
|
+
export { default as background_output } from "./background-agent/output.js";
|
|
4
|
+
export { default as background_cancel } from "./background-agent/cancel.js";
|
|
5
|
+
export { default as manual_update } from "./manual/manual-update.js";
|
|
6
|
+
export { default as verify_manual } from "./manual/verify-manual.js";
|
|
7
|
+
export * from "./background-agent/index.js";
|
|
8
|
+
export * from "./manual/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/manual/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual-update.d.ts","sourceRoot":"","sources":["../../../src/tools/manual/manual-update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA6IrE,QAAA,MAAM,gBAAgB,EAAE,cAqDtB,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
|