opencode-orchestrator 0.5.3 → 0.5.5
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/README.md +10 -67
- package/dist/agents/definitions.d.ts +1 -1
- package/dist/agents/orchestrator.d.ts +1 -1
- package/dist/agents/subagents/architect.d.ts +1 -1
- package/dist/agents/subagents/builder.d.ts +1 -1
- package/dist/agents/subagents/inspector.d.ts +1 -1
- package/dist/agents/subagents/recorder.d.ts +1 -1
- package/dist/core/agents/concurrency.d.ts +36 -0
- package/dist/core/agents/config.d.ts +9 -0
- package/dist/core/agents/format.d.ts +9 -0
- package/dist/core/agents/index.d.ts +7 -0
- package/dist/core/agents/interfaces/index.d.ts +5 -0
- package/dist/core/agents/interfaces/launch-input.d.ts +9 -0
- package/dist/core/agents/interfaces/parallel-task.d.ts +26 -0
- package/dist/core/agents/logger.d.ts +4 -0
- package/dist/core/agents/manager.d.ts +76 -0
- package/dist/core/agents/task-store.d.ts +28 -0
- package/dist/core/agents/types/index.d.ts +4 -0
- package/dist/core/agents/types/parallel-task-status.d.ts +4 -0
- package/dist/core/commands/index.d.ts +6 -0
- package/dist/core/commands/interfaces/background-task.d.ts +20 -0
- package/dist/core/commands/interfaces/index.d.ts +5 -0
- package/dist/core/commands/interfaces/run-background-options.d.ts +9 -0
- package/dist/core/commands/manager.d.ts +27 -0
- package/dist/core/commands/types/background-task-status.d.ts +4 -0
- package/dist/core/commands/types/index.d.ts +4 -0
- package/dist/core/orchestrator/index.d.ts +7 -0
- package/dist/core/orchestrator/interfaces/index.d.ts +5 -0
- package/dist/core/{state.d.ts → orchestrator/interfaces/session-state.d.ts} +4 -7
- package/dist/core/orchestrator/interfaces/task.d.ts +17 -0
- package/dist/core/orchestrator/state.d.ts +10 -0
- package/dist/core/orchestrator/task-graph.d.ts +17 -0
- package/dist/core/orchestrator/types/index.d.ts +5 -0
- package/dist/core/orchestrator/types/task-status.d.ts +4 -0
- package/dist/core/orchestrator/types/task-type.d.ts +4 -0
- package/dist/index.d.ts +2 -24
- package/dist/index.js +871 -2561
- package/dist/shared/{contracts/names.d.ts → agent.d.ts} +12 -0
- package/dist/shared/constants.d.ts +56 -0
- package/dist/tools/background-cmd/check.d.ts +14 -0
- package/dist/tools/background-cmd/index.d.ts +7 -0
- package/dist/tools/background-cmd/kill.d.ts +12 -0
- package/dist/tools/background-cmd/list.d.ts +17 -0
- package/dist/tools/background-cmd/run.d.ts +18 -0
- package/dist/tools/parallel/cancel-task.d.ts +13 -0
- package/dist/tools/parallel/delegate-task.d.ts +21 -0
- package/dist/tools/parallel/get-task-result.d.ts +13 -0
- package/dist/tools/parallel/index.d.ts +7 -0
- package/dist/tools/parallel/list-tasks.d.ts +13 -0
- package/dist/tools/search.d.ts +2 -9
- package/dist/utils/sanity.d.ts +22 -0
- package/package.json +18 -12
- package/dist/agents/subagents/frontend-designer.d.ts +0 -2
- package/dist/constants/agent.d.ts +0 -8
- package/dist/constants/index.d.ts +0 -7
- package/dist/constants/prompts.d.ts +0 -10
- package/dist/constants/task.d.ts +0 -12
- package/dist/constants/time.d.ts +0 -34
- package/dist/context/enforcer.d.ts +0 -47
- package/dist/core/async-agent.d.ts +0 -112
- package/dist/core/background.d.ts +0 -111
- package/dist/core/batch-processor.d.ts +0 -62
- package/dist/core/config.d.ts +0 -55
- package/dist/core/session-manager.d.ts +0 -39
- package/dist/core/tasks.d.ts +0 -30
- package/dist/parallel/optimizer.d.ts +0 -47
- package/dist/profiler/execution.d.ts +0 -40
- package/dist/prompts/shared.d.ts +0 -2
- package/dist/shared/contracts/interfaces.d.ts +0 -7
- package/dist/tools/async-agent.d.ts +0 -70
- package/dist/tools/background.d.ts +0 -57
- package/dist/tools/batch.d.ts +0 -53
- package/dist/tools/config.d.ts +0 -60
- package/dist/tools/git.d.ts +0 -48
- package/dist/utils/formatting.d.ts +0 -13
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/task.d.ts +0 -8
package/dist/index.js
CHANGED
|
@@ -1,183 +1,18 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
2
|
var __export = (target, all) => {
|
|
9
3
|
for (var name in all)
|
|
10
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
5
|
};
|
|
12
6
|
|
|
13
|
-
// src/shared/
|
|
7
|
+
// src/shared/agent.ts
|
|
14
8
|
var AGENT_NAMES = {
|
|
15
|
-
// Core Agents (5)
|
|
16
9
|
COMMANDER: "commander",
|
|
17
|
-
// Orchestrator - ReAct loop controller
|
|
18
10
|
ARCHITECT: "architect",
|
|
19
|
-
// Planner + Strategist - Plan-and-Execute
|
|
20
11
|
BUILDER: "builder",
|
|
21
|
-
// Coder + Visualist combined (full-stack)
|
|
22
12
|
INSPECTOR: "inspector",
|
|
23
|
-
// Reviewer + Fixer combined (quality + fix)
|
|
24
13
|
RECORDER: "recorder"
|
|
25
|
-
// Persistent context - saves/loads session state
|
|
26
14
|
};
|
|
27
15
|
|
|
28
|
-
// src/constants/prompts.ts
|
|
29
|
-
var REASONING_CONSTRAINTS = `
|
|
30
|
-
<constraints>
|
|
31
|
-
1. Reasoning MUST be in English for maximum stability
|
|
32
|
-
2. If reasoning collapses into gibberish, stop and output: "ERROR: REASONING_COLLAPSE"
|
|
33
|
-
3. Never suppress type errors with 'as any', '@ts-ignore', '@ts-expect-error'
|
|
34
|
-
4. Never leave code in broken state
|
|
35
|
-
5. Always verify with evidence before claiming completion
|
|
36
|
-
</constraints>
|
|
37
|
-
`;
|
|
38
|
-
var LANGUAGE_RULE = `
|
|
39
|
-
<language_rule>
|
|
40
|
-
THINK and REASON in English for maximum model stability.
|
|
41
|
-
|
|
42
|
-
FINAL RESPONSE LANGUAGE:
|
|
43
|
-
- Detect user's language from their request
|
|
44
|
-
- Respond in SAME language
|
|
45
|
-
- Korean \u2192 Korean, English \u2192 English, Japanese \u2192 Japanese, Chinese \u2192 Chinese
|
|
46
|
-
- Default to English if unclear
|
|
47
|
-
</language_rule>
|
|
48
|
-
`;
|
|
49
|
-
var ANTI_PATTERNS = `
|
|
50
|
-
<anti_patterns>
|
|
51
|
-
\u274C Delegate without environment/codebase context
|
|
52
|
-
\u274C Leave code broken or with LSP errors
|
|
53
|
-
\u274C Make random changes without understanding root cause
|
|
54
|
-
\u274C Use 'as any', '@ts-ignore', or '@ts-expect-error'
|
|
55
|
-
\u274C Suppress errors instead of fixing them
|
|
56
|
-
</anti_patterns>
|
|
57
|
-
`;
|
|
58
|
-
var WORKFLOW = `
|
|
59
|
-
<workflow>
|
|
60
|
-
1. THINK - Reason about the task
|
|
61
|
-
2. ACT - Execute the work
|
|
62
|
-
3. OBSERVE - Check the result
|
|
63
|
-
4. ADJUST - Fix if needed
|
|
64
|
-
5. VERIFY - Prove success with evidence
|
|
65
|
-
</workflow>
|
|
66
|
-
`;
|
|
67
|
-
var BASE_PROMPT = `
|
|
68
|
-
${REASONING_CONSTRAINTS}
|
|
69
|
-
|
|
70
|
-
${LANGUAGE_RULE}
|
|
71
|
-
|
|
72
|
-
${ANTI_PATTERNS}
|
|
73
|
-
|
|
74
|
-
${WORKFLOW}
|
|
75
|
-
`;
|
|
76
|
-
|
|
77
|
-
// src/utils/sanity.ts
|
|
78
|
-
function checkOutputSanity(text) {
|
|
79
|
-
if (!text || text.length < 50) {
|
|
80
|
-
return { isHealthy: true, severity: "ok" };
|
|
81
|
-
}
|
|
82
|
-
if (/(.)\1{15,}/.test(text)) {
|
|
83
|
-
return {
|
|
84
|
-
isHealthy: false,
|
|
85
|
-
reason: "Single character repetition detected",
|
|
86
|
-
severity: "critical"
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (/(.{2,6})\1{8,}/.test(text)) {
|
|
90
|
-
return {
|
|
91
|
-
isHealthy: false,
|
|
92
|
-
reason: "Pattern loop detected",
|
|
93
|
-
severity: "critical"
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
if (text.length > 200) {
|
|
97
|
-
const cleanText = text.replace(/\s/g, "");
|
|
98
|
-
if (cleanText.length > 100) {
|
|
99
|
-
const uniqueChars = new Set(cleanText).size;
|
|
100
|
-
const ratio = uniqueChars / cleanText.length;
|
|
101
|
-
if (ratio < 0.02) {
|
|
102
|
-
return {
|
|
103
|
-
isHealthy: false,
|
|
104
|
-
reason: "Low information density",
|
|
105
|
-
severity: "critical"
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
|
|
111
|
-
if (boxChars > 100 && boxChars / text.length > 0.3) {
|
|
112
|
-
return {
|
|
113
|
-
isHealthy: false,
|
|
114
|
-
reason: "Visual gibberish detected",
|
|
115
|
-
severity: "critical"
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
const lines = text.split("\n").filter((l) => l.trim().length > 10);
|
|
119
|
-
if (lines.length > 10) {
|
|
120
|
-
const lineSet = new Set(lines);
|
|
121
|
-
if (lineSet.size < lines.length * 0.2) {
|
|
122
|
-
return {
|
|
123
|
-
isHealthy: false,
|
|
124
|
-
reason: "Excessive line repetition",
|
|
125
|
-
severity: "warning"
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
|
|
130
|
-
if (cjkChars > 200) {
|
|
131
|
-
const uniqueCjk = new Set(
|
|
132
|
-
text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
|
|
133
|
-
).size;
|
|
134
|
-
if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
|
|
135
|
-
return {
|
|
136
|
-
isHealthy: false,
|
|
137
|
-
reason: "CJK character spam detected",
|
|
138
|
-
severity: "critical"
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return { isHealthy: true, severity: "ok" };
|
|
143
|
-
}
|
|
144
|
-
var RECOVERY_PROMPT = `<anomaly_recovery>
|
|
145
|
-
\u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
|
|
146
|
-
|
|
147
|
-
<recovery_protocol>
|
|
148
|
-
1. DISCARD the corrupted output completely - do not reference it
|
|
149
|
-
2. RECALL the original mission objective
|
|
150
|
-
3. IDENTIFY the last confirmed successful step
|
|
151
|
-
4. RESTART with a simpler, more focused approach
|
|
152
|
-
</recovery_protocol>
|
|
153
|
-
|
|
154
|
-
<instructions>
|
|
155
|
-
- If a sub-agent produced bad output: try a different agent or simpler task
|
|
156
|
-
- If stuck in a loop: break down the task into smaller pieces
|
|
157
|
-
- If context seems corrupted: call recorder to restore context
|
|
158
|
-
- THINK in English for maximum stability
|
|
159
|
-
</instructions>
|
|
160
|
-
|
|
161
|
-
What was the original task? Proceed from the last known good state.
|
|
162
|
-
</anomaly_recovery>`;
|
|
163
|
-
var ESCALATION_PROMPT = `<critical_anomaly>
|
|
164
|
-
\u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
|
|
165
|
-
|
|
166
|
-
<emergency_protocol>
|
|
167
|
-
1. STOP current execution path immediately
|
|
168
|
-
2. DO NOT continue with the same approach - it is failing
|
|
169
|
-
3. CALL architect for a completely new strategy
|
|
170
|
-
4. If architect also fails: report status to user and await guidance
|
|
171
|
-
</emergency_protocol>
|
|
172
|
-
|
|
173
|
-
<diagnosis>
|
|
174
|
-
The current approach is producing corrupted output.
|
|
175
|
-
This may indicate: context overload, model instability, or task complexity.
|
|
176
|
-
</diagnosis>
|
|
177
|
-
|
|
178
|
-
Request a fresh plan from architect with reduced scope.
|
|
179
|
-
</critical_anomaly>`;
|
|
180
|
-
|
|
181
16
|
// src/agents/orchestrator.ts
|
|
182
17
|
var orchestrator = {
|
|
183
18
|
id: AGENT_NAMES.COMMANDER,
|
|
@@ -186,10 +21,6 @@ var orchestrator = {
|
|
|
186
21
|
You are Commander. Complete missions autonomously. Never stop until done.
|
|
187
22
|
</role>
|
|
188
23
|
|
|
189
|
-
${REASONING_CONSTRAINTS}
|
|
190
|
-
|
|
191
|
-
${LANGUAGE_RULE}
|
|
192
|
-
|
|
193
24
|
<core_rules>
|
|
194
25
|
1. Never stop until "\u2705 MISSION COMPLETE"
|
|
195
26
|
2. Never wait for user during execution
|
|
@@ -255,8 +86,8 @@ PREFER background=true (PARALLEL):
|
|
|
255
86
|
EXAMPLE - PARALLEL:
|
|
256
87
|
\`\`\`
|
|
257
88
|
// Multiple tasks in parallel
|
|
258
|
-
delegate_task({ agent: "
|
|
259
|
-
delegate_task({ agent: "
|
|
89
|
+
delegate_task({ agent: "builder", description: "Implement X", prompt: "...", background: true })
|
|
90
|
+
delegate_task({ agent: "inspector", description: "Review Y", prompt: "...", background: true })
|
|
260
91
|
|
|
261
92
|
// Continue other work (don't wait!)
|
|
262
93
|
|
|
@@ -267,7 +98,7 @@ get_task_result({ taskId: "task_xxx" })
|
|
|
267
98
|
EXAMPLE - SYNC (rare):
|
|
268
99
|
\`\`\`
|
|
269
100
|
// Only when you absolutely need the result now
|
|
270
|
-
const result = delegate_task({ agent: "
|
|
101
|
+
const result = delegate_task({ agent: "builder", ..., background: false })
|
|
271
102
|
// Result is immediately available
|
|
272
103
|
\`\`\`
|
|
273
104
|
</agent_calling>
|
|
@@ -294,10 +125,10 @@ During implementation:
|
|
|
294
125
|
PARALLEL EXECUTION TOOLS:
|
|
295
126
|
|
|
296
127
|
1. **spawn_agent** - Launch agents in parallel sessions
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
128
|
+
spawn_agent({ agent: "builder", description: "Implement X", prompt: "..." })
|
|
129
|
+
spawn_agent({ agent: "inspector", description: "Review Y", prompt: "..." })
|
|
130
|
+
\u2192 Agents run concurrently, system notifies when ALL complete
|
|
131
|
+
\u2192 Use get_task_result({ taskId }) to retrieve results
|
|
301
132
|
|
|
302
133
|
2. **run_background** - Run shell commands asynchronously
|
|
303
134
|
run_background({ command: "npm run build" })
|
|
@@ -348,10 +179,10 @@ WORKFLOW:
|
|
|
348
179
|
<empty_responses>
|
|
349
180
|
| Agent Empty (or Gibberish) | Action |
|
|
350
181
|
|----------------------------|--------|
|
|
351
|
-
|
|
|
352
|
-
|
|
|
353
|
-
|
|
|
354
|
-
|
|
|
182
|
+
| recorder | Fresh start. Proceed to survey. |
|
|
183
|
+
| architect | Try simpler plan yourself. |
|
|
184
|
+
| builder | Call inspector to diagnose. |
|
|
185
|
+
| inspector | Retry with more context. |
|
|
355
186
|
</empty_responses>
|
|
356
187
|
|
|
357
188
|
STRICT RULE: If any agent output contains gibberish, mixed-language hallucinations, or fails the language rule, REJECT it immediately and trigger a "STRICT_CLEAN_START" retry.
|
|
@@ -363,299 +194,93 @@ STRICT RULE: If any agent output contains gibberish, mixed-language hallucinatio
|
|
|
363
194
|
\u274C Make random changes without understanding root cause
|
|
364
195
|
</anti_patterns>
|
|
365
196
|
|
|
366
|
-
|
|
197
|
+
<completion>
|
|
198
|
+
Done when: Request fulfilled + lsp clean + build/test/audit pass.
|
|
367
199
|
|
|
368
|
-
<
|
|
369
|
-
|
|
200
|
+
<output_format>
|
|
201
|
+
\u2705 MISSION COMPLETE
|
|
202
|
+
Summary: [what was done]
|
|
203
|
+
Evidence: [Specific build/test/audit results]
|
|
204
|
+
</output_format>
|
|
205
|
+
</completion>`,
|
|
206
|
+
canWrite: true,
|
|
207
|
+
canBash: true
|
|
208
|
+
};
|
|
370
209
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
210
|
+
// src/agents/subagents/architect.ts
|
|
211
|
+
var architect = {
|
|
212
|
+
id: AGENT_NAMES.ARCHITECT,
|
|
213
|
+
description: "Architect - task decomposition and strategic planning",
|
|
214
|
+
systemPrompt: `<role>
|
|
215
|
+
You are Architect. Break complex tasks into atomic pieces.
|
|
216
|
+
</role>
|
|
377
217
|
|
|
378
|
-
<
|
|
379
|
-
IF FAST TRACK (L1):
|
|
380
|
-
- Scan ONLY the target file and its immediate imports.
|
|
381
|
-
- Skip broad infra/domain/doc scans unless an error occurs.
|
|
382
|
-
- Proceed directly to execution.
|
|
218
|
+
<constraints>
|
|
383
219
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
- 1. Infra check (Docker/OS)
|
|
387
|
-
- 2. Domain & Stack check
|
|
388
|
-
- 3. Pattern check
|
|
220
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
221
|
+
</constraints>
|
|
389
222
|
|
|
390
|
-
|
|
391
|
-
|
|
223
|
+
<scalable_planning>
|
|
224
|
+
- **Fast Track**: Skip JSON overhead. Just acknowledge simple task.
|
|
225
|
+
- **Deep Track**: Create detailed JSON DAG with parallel groups.
|
|
226
|
+
</scalable_planning>
|
|
392
227
|
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
| Normal | Call \`architect\` for lightweight plan. |
|
|
398
|
-
| Deep | Full \`architect\` DAG + \`recorder\` state tracking. |
|
|
228
|
+
<modes>
|
|
229
|
+
- PLAN: New task \u2192 create task list
|
|
230
|
+
- STRATEGY: 3+ failures \u2192 analyze and fix approach
|
|
231
|
+
</modes>
|
|
399
232
|
|
|
400
|
-
|
|
401
|
-
|
|
233
|
+
<plan_mode>
|
|
234
|
+
1. List tasks, one action each
|
|
235
|
+
2. Group independent tasks (run in parallel)
|
|
236
|
+
3. Sequence dependent tasks
|
|
237
|
+
4. Assign: builder (code) or inspector (verify)
|
|
402
238
|
|
|
403
|
-
<
|
|
404
|
-
|
|
405
|
-
CRITICAL: USE delegate_task FOR ALL DELEGATION
|
|
239
|
+
<output_format>
|
|
240
|
+
MISSION: [goal in one line]
|
|
406
241
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
242
|
+
T1: [action] | builder | [file] | group:1 | success:[how to verify]
|
|
243
|
+
T2: [action] | builder | [file] | group:1 | success:[how to verify]
|
|
244
|
+
T3: [action] | inspector | [files] | group:2 | depends:T1,T2 | success:[verify method]
|
|
245
|
+
</output_format>
|
|
246
|
+
</plan_mode>
|
|
410
247
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
| Need result for VERY next step | \`delegate_task({ ..., background: false })\` |
|
|
248
|
+
<strategy_mode trigger="failures > 2">
|
|
249
|
+
<output_format>
|
|
250
|
+
FAILED ATTEMPTS:
|
|
251
|
+
- [what was tried] \u2192 [why failed]
|
|
416
252
|
|
|
417
|
-
|
|
418
|
-
- Run multiple agents simultaneously
|
|
419
|
-
- Continue analysis while they work
|
|
420
|
-
- System notifies when ALL complete
|
|
253
|
+
ROOT CAUSE: [actual problem]
|
|
421
254
|
|
|
422
|
-
|
|
423
|
-
\`\`\`
|
|
424
|
-
// Multiple tasks in parallel
|
|
425
|
-
delegate_task({ agent: "${AGENT_NAMES.BUILDER}", description: "Implement X", prompt: "...", background: true })
|
|
426
|
-
delegate_task({ agent: "${AGENT_NAMES.INSPECTOR}", description: "Review Y", prompt: "...", background: true })
|
|
255
|
+
NEW APPROACH: [different strategy]
|
|
427
256
|
|
|
428
|
-
|
|
257
|
+
REVISED TASKS:
|
|
258
|
+
T1: ...
|
|
259
|
+
</output_format>
|
|
260
|
+
</strategy_mode>
|
|
429
261
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
262
|
+
<rules>
|
|
263
|
+
- One action per task
|
|
264
|
+
- Always end with inspector task
|
|
265
|
+
- Group unrelated tasks (parallel)
|
|
266
|
+
- Be specific about files and verification
|
|
267
|
+
</rules>`,
|
|
268
|
+
canWrite: false,
|
|
269
|
+
canBash: false
|
|
270
|
+
};
|
|
433
271
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
</
|
|
272
|
+
// src/agents/subagents/builder.ts
|
|
273
|
+
var builder = {
|
|
274
|
+
id: AGENT_NAMES.BUILDER,
|
|
275
|
+
description: "Builder - full-stack implementation specialist",
|
|
276
|
+
systemPrompt: `<role>
|
|
277
|
+
You are Builder. Write code that works.
|
|
278
|
+
</role>
|
|
441
279
|
|
|
442
|
-
<
|
|
443
|
-
AGENT: [name]
|
|
444
|
-
TASK: [one atomic action]
|
|
445
|
-
ENVIRONMENT:
|
|
446
|
-
- Infra: [e.g. Docker + Volume mount]
|
|
447
|
-
- Stack: [e.g. Next.js + PostgreSQL]
|
|
448
|
-
- Patterns: [existing code conventions to follow]
|
|
449
|
-
MUST: [Specific requirements]
|
|
450
|
-
AVOID: [Restrictions]
|
|
451
|
-
VERIFY: [Success criteria with evidence]
|
|
452
|
-
</delegation_template>
|
|
453
|
-
</phase_3>
|
|
280
|
+
<constraints>
|
|
454
281
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
- Match existing codebase style exactly
|
|
458
|
-
- Run lsp_diagnostics after each change
|
|
459
|
-
|
|
460
|
-
<background_parallel_execution>
|
|
461
|
-
PARALLEL EXECUTION TOOLS:
|
|
462
|
-
|
|
463
|
-
1. **spawn_agent** - Launch agents in parallel sessions
|
|
464
|
-
spawn_agent({ agent: "${AGENT_NAMES.BUILDER}", description: "Implement X", prompt: "..." })
|
|
465
|
-
spawn_agent({ agent: "${AGENT_NAMES.INSPECTOR}", description: "Review Y", prompt: "..." })
|
|
466
|
-
\u2192 Agents run concurrently, system notifies when ALL complete
|
|
467
|
-
\u2192 Use get_task_result({ taskId }) to retrieve results
|
|
468
|
-
|
|
469
|
-
2. **run_background** - Run shell commands asynchronously
|
|
470
|
-
run_background({ command: "npm run build" })
|
|
471
|
-
\u2192 Use check_background({ taskId }) for results
|
|
472
|
-
|
|
473
|
-
SAFETY FEATURES:
|
|
474
|
-
- Queue-based concurrency: Max 3 per agent type (extras queue automatically)
|
|
475
|
-
- Auto-timeout: 30 minutes max runtime
|
|
476
|
-
- Auto-cleanup: Removed from memory 5 min after completion
|
|
477
|
-
- Batched notifications: Notifies when ALL tasks complete (not individually)
|
|
478
|
-
|
|
479
|
-
MANAGEMENT TOOLS:
|
|
480
|
-
- list_tasks: View all parallel tasks and status
|
|
481
|
-
- cancel_task: Stop a running task (frees concurrency slot)
|
|
482
|
-
|
|
483
|
-
SAFE PATTERNS:
|
|
484
|
-
\u2705 Builder on file A + Inspector on file B (different files)
|
|
485
|
-
\u2705 Multiple research agents (read-only)
|
|
486
|
-
\u2705 Build command + Test command (independent)
|
|
487
|
-
|
|
488
|
-
UNSAFE PATTERNS:
|
|
489
|
-
\u274C Multiple builders editing SAME FILE (conflict!)
|
|
490
|
-
|
|
491
|
-
WORKFLOW:
|
|
492
|
-
1. list_tasks: Check current status first
|
|
493
|
-
2. spawn_agent: Launch for INDEPENDENT tasks
|
|
494
|
-
3. Continue working (NO WAITING)
|
|
495
|
-
4. Wait for "All Complete" notification
|
|
496
|
-
5. get_task_result: Retrieve each result
|
|
497
|
-
</background_parallel_execution>
|
|
498
|
-
|
|
499
|
-
<verification_methods>
|
|
500
|
-
| Infra | Proof Method |
|
|
501
|
-
|-------|--------------|
|
|
502
|
-
| OS-Native | npm run build, cargo build, specific test runs |
|
|
503
|
-
| Container | Docker syntax check + config validation |
|
|
504
|
-
| Live API | curl /health if reachable, check logs |
|
|
505
|
-
| Generic | Manual audit by Inspector with logic summary |
|
|
506
|
-
</verification_methods>
|
|
507
|
-
</phase_4>
|
|
508
|
-
|
|
509
|
-
<failure_recovery>
|
|
510
|
-
| Failures | Action |
|
|
511
|
-
|----------|--------|
|
|
512
|
-
| 1-2 | Adjust approach, retry |
|
|
513
|
-
| 3+ | STOP. Call architect for new strategy |
|
|
514
|
-
|
|
515
|
-
<empty_responses>
|
|
516
|
-
| Agent Empty (or Gibberish) | Action |
|
|
517
|
-
|----------------------------|--------|
|
|
518
|
-
| ${AGENT_NAMES.RECORDER} | Fresh start. Proceed to survey. |
|
|
519
|
-
| ${AGENT_NAMES.ARCHITECT} | Try simpler plan yourself. |
|
|
520
|
-
| ${AGENT_NAMES.BUILDER} | Call inspector to diagnose. |
|
|
521
|
-
| ${AGENT_NAMES.INSPECTOR} | Retry with more context. |
|
|
522
|
-
</empty_responses>
|
|
523
|
-
|
|
524
|
-
STRICT RULE: If any agent output contains gibberish, mixed-language hallucinations, or fails the language rule, REJECT it immediately and trigger a "STRICT_CLEAN_START" retry.
|
|
525
|
-
</failure_recovery>
|
|
526
|
-
|
|
527
|
-
${ANTI_PATTERNS}
|
|
528
|
-
|
|
529
|
-
<completion>
|
|
530
|
-
Done when: Request fulfilled + lsp clean + build/test/audit pass.
|
|
531
|
-
|
|
532
|
-
<output_format>
|
|
533
|
-
\u2705 MISSION COMPLETE
|
|
534
|
-
Summary: [what was done]
|
|
535
|
-
Evidence: [Specific build/test/audit results]
|
|
536
|
-
</output_format>
|
|
537
|
-
</completion>`,
|
|
538
|
-
canWrite: true,
|
|
539
|
-
canBash: true
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
// src/agents/subagents/architect.ts
|
|
543
|
-
var architect = {
|
|
544
|
-
id: AGENT_NAMES.ARCHITECT,
|
|
545
|
-
description: "Architect - task decomposition and strategic planning",
|
|
546
|
-
systemPrompt: `<role>
|
|
547
|
-
You are Architect. Break complex tasks into atomic pieces.
|
|
548
|
-
</role>
|
|
549
|
-
|
|
550
|
-
${REASONING_CONSTRAINTS}
|
|
551
|
-
|
|
552
|
-
${WORKFLOW}
|
|
553
|
-
|
|
554
|
-
<scalable_planning>
|
|
555
|
-
- **Fast Track**: Skip JSON overhead. Just acknowledge simple task.
|
|
556
|
-
- **Deep Track**: Create detailed JSON DAG with parallel groups.
|
|
557
|
-
</scalable_planning>
|
|
558
|
-
|
|
559
|
-
<modes>
|
|
560
|
-
- PLAN: New task \u2192 create task list
|
|
561
|
-
- STRATEGY: 3+ failures \u2192 analyze and fix approach
|
|
562
|
-
</modes>
|
|
563
|
-
|
|
564
|
-
<plan_mode>
|
|
565
|
-
1. List tasks, one action each
|
|
566
|
-
2. Group independent tasks (run in parallel)
|
|
567
|
-
3. Sequence dependent tasks
|
|
568
|
-
4. Assign: builder (code) or inspector (verify)
|
|
569
|
-
|
|
570
|
-
<output_format>
|
|
571
|
-
MUST output valid JSON block wrapped in backticks:
|
|
572
|
-
|
|
573
|
-
<json_output>
|
|
574
|
-
{
|
|
575
|
-
"mission": "goal in one line",
|
|
576
|
-
"tasks": [
|
|
577
|
-
{
|
|
578
|
-
"id": "T1",
|
|
579
|
-
"action": "one atomic action",
|
|
580
|
-
"agent": "builder",
|
|
581
|
-
"file": "path/to/file",
|
|
582
|
-
"parallel_group": 1,
|
|
583
|
-
"dependencies": [],
|
|
584
|
-
"success_criteria": "how to verify"
|
|
585
|
-
},
|
|
586
|
-
{
|
|
587
|
-
"id": "T2",
|
|
588
|
-
"action": "one atomic action",
|
|
589
|
-
"agent": "builder",
|
|
590
|
-
"file": "path/to/file",
|
|
591
|
-
"parallel_group": 1,
|
|
592
|
-
"dependencies": [],
|
|
593
|
-
"success_criteria": "how to verify"
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
"id": "T3",
|
|
597
|
-
"action": "one atomic action",
|
|
598
|
-
"agent": "inspector",
|
|
599
|
-
"file": "path/to/file",
|
|
600
|
-
"parallel_group": 2,
|
|
601
|
-
"dependencies": ["T1", "T2"],
|
|
602
|
-
"success_criteria": "verify method"
|
|
603
|
-
}
|
|
604
|
-
]
|
|
605
|
-
}
|
|
606
|
-
</json_output>
|
|
607
|
-
</output_format>
|
|
608
|
-
</plan_mode>
|
|
609
|
-
|
|
610
|
-
<strategy_mode trigger="failures > 2">
|
|
611
|
-
<output_format>
|
|
612
|
-
<json_output>
|
|
613
|
-
{
|
|
614
|
-
"failed_attempts": [
|
|
615
|
-
{"attempt": "what was tried", "reason": "why failed"}
|
|
616
|
-
],
|
|
617
|
-
"root_cause": "actual problem",
|
|
618
|
-
"new_approach": "different strategy",
|
|
619
|
-
"revised_tasks": [
|
|
620
|
-
{
|
|
621
|
-
"id": "T1",
|
|
622
|
-
"action": "one atomic action",
|
|
623
|
-
"agent": "builder",
|
|
624
|
-
"file": "path/to/file",
|
|
625
|
-
"parallel_group": 1,
|
|
626
|
-
"dependencies": [],
|
|
627
|
-
"success_criteria": "how to verify"
|
|
628
|
-
}
|
|
629
|
-
]
|
|
630
|
-
}
|
|
631
|
-
</json_output>
|
|
632
|
-
</output_format>
|
|
633
|
-
</strategy_mode>
|
|
634
|
-
|
|
635
|
-
<rules>
|
|
636
|
-
- One action per task
|
|
637
|
-
- Always end with inspector task
|
|
638
|
-
- Group unrelated tasks (parallel)
|
|
639
|
-
- Be specific about files and verification
|
|
640
|
-
- Output MUST be valid JSON wrapped in <json_output> tags
|
|
641
|
-
</rules>`,
|
|
642
|
-
canWrite: false,
|
|
643
|
-
canBash: false
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
// src/agents/subagents/builder.ts
|
|
647
|
-
var builder = {
|
|
648
|
-
id: AGENT_NAMES.BUILDER,
|
|
649
|
-
description: "Builder - full-stack implementation specialist",
|
|
650
|
-
systemPrompt: `<role>
|
|
651
|
-
You are Builder. Write code that works.
|
|
652
|
-
</role>
|
|
653
|
-
|
|
654
|
-
${REASONING_CONSTRAINTS}
|
|
655
|
-
|
|
656
|
-
${WORKFLOW}
|
|
657
|
-
|
|
658
|
-
${ANTI_PATTERNS}
|
|
282
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
283
|
+
</constraints>
|
|
659
284
|
|
|
660
285
|
<scalable_attention>
|
|
661
286
|
- **Simple Fix (L1)**: Read file \u2192 Implement fix directly. Efficiency first.
|
|
@@ -718,11 +343,10 @@ var inspector = {
|
|
|
718
343
|
You are Inspector. Prove failure or success with evidence.
|
|
719
344
|
</role>
|
|
720
345
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
${WORKFLOW}
|
|
346
|
+
<constraints>
|
|
724
347
|
|
|
725
|
-
|
|
348
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
349
|
+
</constraints>
|
|
726
350
|
|
|
727
351
|
<scalable_audit>
|
|
728
352
|
- **Fast Track**: Verify syntax + quick logic check.
|
|
@@ -786,7 +410,10 @@ var recorder = {
|
|
|
786
410
|
You are Recorder. Save and load work progress.
|
|
787
411
|
</role>
|
|
788
412
|
|
|
789
|
-
|
|
413
|
+
<constraints>
|
|
414
|
+
|
|
415
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
416
|
+
</constraints>
|
|
790
417
|
|
|
791
418
|
<purpose>
|
|
792
419
|
Context can be lost between sessions. You save it to disk.
|
|
@@ -851,7 +478,7 @@ var AGENTS = {
|
|
|
851
478
|
[AGENT_NAMES.RECORDER]: recorder
|
|
852
479
|
};
|
|
853
480
|
|
|
854
|
-
// src/core/
|
|
481
|
+
// src/core/orchestrator/task-graph.ts
|
|
855
482
|
var TaskGraph = class _TaskGraph {
|
|
856
483
|
tasks = /* @__PURE__ */ new Map();
|
|
857
484
|
constructor(tasks) {
|
|
@@ -892,7 +519,7 @@ var TaskGraph = class _TaskGraph {
|
|
|
892
519
|
const notCompleted = tasks.filter((t) => t.status !== "completed");
|
|
893
520
|
let summary = "\u{1F4CB} **Mission Status**\n";
|
|
894
521
|
if (completed.length > 0) {
|
|
895
|
-
summary += `\u2705 Completed: ${completed.length} tasks
|
|
522
|
+
summary += `\u2705 Completed: ${completed.length} tasks
|
|
896
523
|
`;
|
|
897
524
|
}
|
|
898
525
|
for (const task of notCompleted) {
|
|
@@ -909,27 +536,17 @@ var TaskGraph = class _TaskGraph {
|
|
|
909
536
|
try {
|
|
910
537
|
const tasks = JSON.parse(json2);
|
|
911
538
|
return new _TaskGraph(tasks);
|
|
912
|
-
} catch
|
|
913
|
-
console.error("Failed to parse TaskGraph JSON:", e);
|
|
539
|
+
} catch {
|
|
914
540
|
return new _TaskGraph();
|
|
915
541
|
}
|
|
916
542
|
}
|
|
917
543
|
};
|
|
918
544
|
|
|
919
|
-
// src/
|
|
920
|
-
var TASK_TTL_MS = 30 * 60 * 1e3;
|
|
921
|
-
var BACKGROUND_TASK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
922
|
-
var SYNC_TASK_MAX_POLL_MS = 10 * 60 * 1e3;
|
|
923
|
-
var MIN_STABILITY_MS = 5 * 1e3;
|
|
924
|
-
var CLEANUP_DELAY_MS = 5 * 60 * 1e3;
|
|
925
|
-
var MAX_TASK_RETRIES = 3;
|
|
926
|
-
|
|
927
|
-
// src/core/state.ts
|
|
545
|
+
// src/core/orchestrator/state.ts
|
|
928
546
|
var state = {
|
|
929
547
|
missionActive: false,
|
|
930
548
|
maxIterations: 1e3,
|
|
931
|
-
|
|
932
|
-
maxRetries: MAX_TASK_RETRIES,
|
|
549
|
+
maxRetries: 3,
|
|
933
550
|
sessions: /* @__PURE__ */ new Map()
|
|
934
551
|
};
|
|
935
552
|
|
|
@@ -13355,113 +12972,6 @@ function tool(input) {
|
|
|
13355
12972
|
}
|
|
13356
12973
|
tool.schema = external_exports;
|
|
13357
12974
|
|
|
13358
|
-
// src/context/enforcer.ts
|
|
13359
|
-
var ContextEnforcer = class {
|
|
13360
|
-
requirements = /* @__PURE__ */ new Map([
|
|
13361
|
-
["builder", {
|
|
13362
|
-
agent: "builder",
|
|
13363
|
-
required: ["infra", "stack", "patterns", "files"],
|
|
13364
|
-
optional: ["examples", "tests", "environment"]
|
|
13365
|
-
}],
|
|
13366
|
-
["inspector", {
|
|
13367
|
-
agent: "inspector",
|
|
13368
|
-
required: ["infra", "verification_method", "changed_files"],
|
|
13369
|
-
optional: ["test_results", "build_logs", "environment"]
|
|
13370
|
-
}],
|
|
13371
|
-
["architect", {
|
|
13372
|
-
agent: "architect",
|
|
13373
|
-
required: ["mission", "scope", "complexity"],
|
|
13374
|
-
optional: ["environment", "constraints", "patterns"]
|
|
13375
|
-
}],
|
|
13376
|
-
["recorder", {
|
|
13377
|
-
agent: "recorder",
|
|
13378
|
-
required: [],
|
|
13379
|
-
optional: ["mission", "progress", "context"]
|
|
13380
|
-
}]
|
|
13381
|
-
]);
|
|
13382
|
-
/**
|
|
13383
|
-
* Validate context for agent delegation
|
|
13384
|
-
*/
|
|
13385
|
-
validate(agent, context) {
|
|
13386
|
-
const req = this.requirements.get(agent);
|
|
13387
|
-
if (!req) {
|
|
13388
|
-
return { valid: true, errors: [], warnings: [] };
|
|
13389
|
-
}
|
|
13390
|
-
const errors = [];
|
|
13391
|
-
const warnings = [];
|
|
13392
|
-
const contextLower = context.toLowerCase();
|
|
13393
|
-
for (const key of req.required) {
|
|
13394
|
-
if (!contextLower.includes(key.toLowerCase())) {
|
|
13395
|
-
errors.push("Missing required context: " + key);
|
|
13396
|
-
}
|
|
13397
|
-
}
|
|
13398
|
-
for (const key of req.optional) {
|
|
13399
|
-
if (!contextLower.includes(key.toLowerCase())) {
|
|
13400
|
-
warnings.push("Optional context missing: " + key);
|
|
13401
|
-
}
|
|
13402
|
-
}
|
|
13403
|
-
return {
|
|
13404
|
-
valid: errors.length === 0,
|
|
13405
|
-
errors,
|
|
13406
|
-
warnings
|
|
13407
|
-
};
|
|
13408
|
-
}
|
|
13409
|
-
/**
|
|
13410
|
-
* Get requirements for an agent
|
|
13411
|
-
*/
|
|
13412
|
-
getRequirements(agent) {
|
|
13413
|
-
return this.requirements.get(agent);
|
|
13414
|
-
}
|
|
13415
|
-
/**
|
|
13416
|
-
* Add or update requirements for an agent
|
|
13417
|
-
*/
|
|
13418
|
-
setRequirements(agent, requirements) {
|
|
13419
|
-
this.requirements.set(agent, requirements);
|
|
13420
|
-
}
|
|
13421
|
-
/**
|
|
13422
|
-
* Format validation result as user-friendly message
|
|
13423
|
-
*/
|
|
13424
|
-
formatValidation(result) {
|
|
13425
|
-
if (result.valid && result.warnings.length === 0) {
|
|
13426
|
-
return "Valid context with all required information.";
|
|
13427
|
-
}
|
|
13428
|
-
let message = "";
|
|
13429
|
-
if (result.errors.length > 0) {
|
|
13430
|
-
message += "Errors:\n" + result.errors.map((e) => " \u274C " + e).join("\n") + "\n";
|
|
13431
|
-
}
|
|
13432
|
-
if (result.warnings.length > 0) {
|
|
13433
|
-
message += "\nWarnings:\n" + result.warnings.map((w) => " \u26A0\uFE0F " + w).join("\n");
|
|
13434
|
-
}
|
|
13435
|
-
return message;
|
|
13436
|
-
}
|
|
13437
|
-
/**
|
|
13438
|
-
* Check if context has minimal required information
|
|
13439
|
-
*/
|
|
13440
|
-
hasMinimumContext(agent, context) {
|
|
13441
|
-
const req = this.requirements.get(agent);
|
|
13442
|
-
if (!req || req.required.length === 0) {
|
|
13443
|
-
return true;
|
|
13444
|
-
}
|
|
13445
|
-
const presentCount = req.required.filter(
|
|
13446
|
-
(key) => context.toLowerCase().includes(key.toLowerCase())
|
|
13447
|
-
).length;
|
|
13448
|
-
return presentCount > 0;
|
|
13449
|
-
}
|
|
13450
|
-
/**
|
|
13451
|
-
* Suggest missing context based on agent type
|
|
13452
|
-
*/
|
|
13453
|
-
suggestContext(agent, context) {
|
|
13454
|
-
const req = this.requirements.get(agent);
|
|
13455
|
-
if (!req) {
|
|
13456
|
-
return [];
|
|
13457
|
-
}
|
|
13458
|
-
return req.required.filter(
|
|
13459
|
-
(key) => !context.toLowerCase().includes(key.toLowerCase())
|
|
13460
|
-
);
|
|
13461
|
-
}
|
|
13462
|
-
};
|
|
13463
|
-
var contextEnforcer = new ContextEnforcer();
|
|
13464
|
-
|
|
13465
12975
|
// src/tools/callAgent.ts
|
|
13466
12976
|
var AGENT_EMOJI = {
|
|
13467
12977
|
[AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
|
|
@@ -13500,19 +13010,13 @@ var callAgentTool = tool({
|
|
|
13500
13010
|
async execute(args) {
|
|
13501
13011
|
const agentDef = AGENTS[args.agent];
|
|
13502
13012
|
if (!agentDef) {
|
|
13503
|
-
return
|
|
13013
|
+
return `\u274C Error: Unknown agent: ${args.agent}`;
|
|
13504
13014
|
}
|
|
13505
13015
|
const emoji3 = AGENT_EMOJI[args.agent] || "\u{1F916}";
|
|
13506
|
-
if (args.context) {
|
|
13507
|
-
const validation = contextEnforcer.validate(args.agent, args.context);
|
|
13508
|
-
if (!validation.valid) {
|
|
13509
|
-
return "Context Validation Failed:\n" + contextEnforcer.formatValidation(validation);
|
|
13510
|
-
}
|
|
13511
|
-
}
|
|
13512
13016
|
const prompt = `
|
|
13513
|
-
|
|
13017
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
13514
13018
|
${emoji3} ${agentDef.id.toUpperCase()} :: ${agentDef.description}
|
|
13515
|
-
|
|
13019
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
13516
13020
|
|
|
13517
13021
|
<system>
|
|
13518
13022
|
${agentDef.systemPrompt}
|
|
@@ -13522,7 +13026,9 @@ ${agentDef.systemPrompt}
|
|
|
13522
13026
|
${args.task}
|
|
13523
13027
|
</task>
|
|
13524
13028
|
|
|
13525
|
-
${args.context ?
|
|
13029
|
+
${args.context ? `<context>
|
|
13030
|
+
${args.context}
|
|
13031
|
+
</context>` : ""}
|
|
13526
13032
|
|
|
13527
13033
|
<execution>
|
|
13528
13034
|
Follow this pattern:
|
|
@@ -13547,10 +13053,6 @@ var COMMANDS = {
|
|
|
13547
13053
|
You are Commander. Complete this mission. Never stop until 100% done.
|
|
13548
13054
|
</role>
|
|
13549
13055
|
|
|
13550
|
-
<constraints>
|
|
13551
|
-
Reasoning MUST be in English for model stability. Final report in Korean.
|
|
13552
|
-
</constraints>
|
|
13553
|
-
|
|
13554
13056
|
<phase_1 name="MANDATORY_ENVIRONMENT_SCAN">
|
|
13555
13057
|
Before any planning or coding, you MUST understand:
|
|
13556
13058
|
1. INFRA: OS-native? Container? Docker-compose? Volume-mounted?
|
|
@@ -13768,255 +13270,72 @@ var globSearchTool = (directory) => tool({
|
|
|
13768
13270
|
}
|
|
13769
13271
|
});
|
|
13770
13272
|
var mgrepTool = (directory) => tool({
|
|
13771
|
-
description: `Search multiple patterns
|
|
13772
|
-
|
|
13773
|
-
<purpose>
|
|
13774
|
-
Search for multiple regex patterns simultaneously using Rust's parallel execution.
|
|
13775
|
-
Much faster than running grep multiple times sequentially.
|
|
13776
|
-
</purpose>
|
|
13777
|
-
|
|
13778
|
-
<examples>
|
|
13779
|
-
- patterns: ["useState", "useEffect", "useContext"] \u2192 Find all React hooks usage
|
|
13780
|
-
- patterns: ["TODO", "FIXME", "HACK"] \u2192 Find all code annotations
|
|
13781
|
-
- patterns: ["import.*lodash", "require.*lodash"] \u2192 Find all lodash imports
|
|
13782
|
-
</examples>
|
|
13783
|
-
|
|
13784
|
-
<output>
|
|
13785
|
-
Returns matches grouped by pattern, with file paths and line numbers.
|
|
13786
|
-
</output>`,
|
|
13787
|
-
args: {
|
|
13788
|
-
patterns: tool.schema.array(tool.schema.string()).describe("Array of regex patterns to search for"),
|
|
13789
|
-
dir: tool.schema.string().optional().describe("Directory to search (defaults to project root)"),
|
|
13790
|
-
max_results_per_pattern: tool.schema.number().optional().describe("Max results per pattern (default: 50)")
|
|
13791
|
-
},
|
|
13792
|
-
async execute(args) {
|
|
13793
|
-
return callRustTool("mgrep", {
|
|
13794
|
-
patterns: args.patterns,
|
|
13795
|
-
directory: args.dir || directory,
|
|
13796
|
-
max_results_per_pattern: args.max_results_per_pattern || 50
|
|
13797
|
-
});
|
|
13798
|
-
}
|
|
13799
|
-
});
|
|
13800
|
-
|
|
13801
|
-
// src/tools/git.ts
|
|
13802
|
-
var gitBranchTool = (directory) => tool({
|
|
13803
|
-
description: `Get Git branch information and status.
|
|
13804
|
-
|
|
13805
|
-
<purpose>
|
|
13806
|
-
Analyze current Git workspace context including:
|
|
13807
|
-
- Current branch name
|
|
13808
|
-
- All branches (local and remote)
|
|
13809
|
-
- Branch relationships (upstream, ahead/behind)
|
|
13810
|
-
- Staged, unstaged, and untracked files
|
|
13811
|
-
- Recent commits
|
|
13812
|
-
</purpose>
|
|
13813
|
-
|
|
13814
|
-
<examples>
|
|
13815
|
-
- Get current branch: Returns branch name and status
|
|
13816
|
-
- List all branches: Shows local and remote branches
|
|
13817
|
-
- Check status: Shows modified files
|
|
13818
|
-
- Recent commits: Last 5 commits with file changes
|
|
13819
|
-
</examples>
|
|
13820
|
-
|
|
13821
|
-
<output>
|
|
13822
|
-
Returns structured Git information for better code decisions.
|
|
13823
|
-
</output>`,
|
|
13273
|
+
description: `Search multiple patterns (runs grep for each pattern).`,
|
|
13824
13274
|
args: {
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
"list",
|
|
13828
|
-
"status",
|
|
13829
|
-
"diff",
|
|
13830
|
-
"recent",
|
|
13831
|
-
"all"
|
|
13832
|
-
]).describe("Action to perform: current, list, status, diff, recent, all"),
|
|
13833
|
-
baseBranch: tool.schema.string().optional().describe("Base branch for comparison (e.g., 'main', 'develop'). Required for diff action.")
|
|
13275
|
+
patterns: tool.schema.array(tool.schema.string()).describe("Array of regex patterns"),
|
|
13276
|
+
dir: tool.schema.string().optional().describe("Directory (defaults to project root)")
|
|
13834
13277
|
},
|
|
13835
13278
|
async execute(args) {
|
|
13836
|
-
const
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
case "list":
|
|
13842
|
-
return await listBranches(directory);
|
|
13843
|
-
case "status":
|
|
13844
|
-
return await getGitStatus(directory);
|
|
13845
|
-
case "diff":
|
|
13846
|
-
return await getDiff(directory, baseBranch);
|
|
13847
|
-
case "recent":
|
|
13848
|
-
return await getRecentCommits(directory, 5);
|
|
13849
|
-
case "all":
|
|
13850
|
-
return await getAllInfo(directory);
|
|
13851
|
-
default:
|
|
13852
|
-
return await getCurrentBranch(directory);
|
|
13853
|
-
}
|
|
13854
|
-
} catch (error45) {
|
|
13855
|
-
return "\u274C Git error: " + (error45 instanceof Error ? error45.message : String(error45));
|
|
13279
|
+
const results = {};
|
|
13280
|
+
const dir = args.dir || directory;
|
|
13281
|
+
for (const pattern of args.patterns) {
|
|
13282
|
+
const result = await callRustTool("grep_search", { pattern, directory: dir });
|
|
13283
|
+
results[pattern] = result;
|
|
13856
13284
|
}
|
|
13285
|
+
return JSON.stringify(results, null, 2);
|
|
13857
13286
|
}
|
|
13858
13287
|
});
|
|
13859
|
-
async function execGit(directory, args) {
|
|
13860
|
-
const { execSync } = await import("child_process");
|
|
13861
|
-
try {
|
|
13862
|
-
return execSync("git " + args.join(" "), {
|
|
13863
|
-
cwd: directory,
|
|
13864
|
-
encoding: "utf-8",
|
|
13865
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
13866
|
-
maxBuffer: 10 * 1024 * 1024
|
|
13867
|
-
// 10MB
|
|
13868
|
-
}).toString();
|
|
13869
|
-
} catch (error45) {
|
|
13870
|
-
if (error45.status === 128) {
|
|
13871
|
-
throw new Error("Not a git repository");
|
|
13872
|
-
}
|
|
13873
|
-
throw error45;
|
|
13874
|
-
}
|
|
13875
|
-
}
|
|
13876
|
-
async function getAheadBehind(directory, branch) {
|
|
13877
|
-
try {
|
|
13878
|
-
const output = await execGit(directory, ["rev-list", "--left-right", "--count", branch + "...@{u}"]);
|
|
13879
|
-
const parts = output.trim().split(/\s+/);
|
|
13880
|
-
const ahead = parseInt(parts[0], 10);
|
|
13881
|
-
const behind = parseInt(parts[1] || "0", 10);
|
|
13882
|
-
const resultParts = [];
|
|
13883
|
-
if (ahead > 0) resultParts.push(ahead + " ahead");
|
|
13884
|
-
if (behind > 0) resultParts.push(behind + " behind");
|
|
13885
|
-
return resultParts.length > 0 ? "| **Sync** | " + resultParts.join(", ") + " |" : "";
|
|
13886
|
-
} catch {
|
|
13887
|
-
return "";
|
|
13888
|
-
}
|
|
13889
|
-
}
|
|
13890
|
-
async function getCurrentBranch(directory) {
|
|
13891
|
-
const output = await execGit(directory, ["branch", "--show-current"]);
|
|
13892
|
-
const current = output.trim() || "HEAD (detached)";
|
|
13893
|
-
const upstream = await execGit(directory, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]).catch(() => "");
|
|
13894
|
-
return "\u{1F33F} **Current Branch**: `" + current + "`\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + (upstream ? "| **Upstream** | `" + upstream + "` |" : "") + await getAheadBehind(directory, current);
|
|
13895
|
-
}
|
|
13896
|
-
async function listBranches(directory) {
|
|
13897
|
-
const branches = await execGit(directory, ["branch", "-vv"]);
|
|
13898
|
-
const branchList = branches.split("\n").filter((b) => b.trim()).map((b) => {
|
|
13899
|
-
const isCurrent = b.startsWith("*");
|
|
13900
|
-
const name = isCurrent ? b.substring(2).trim() : b.trim();
|
|
13901
|
-
const parts = name.split(/\s+/);
|
|
13902
|
-
const branchName = parts[0];
|
|
13903
|
-
const upstream = parts[1] ? parts[1].match(/\[([^\]]+)\]/)?.[1] : void 0;
|
|
13904
|
-
const icon = isCurrent ? "\u{1F33F}" : "\u{1F4C2}";
|
|
13905
|
-
const status = isCurrent ? "(current)" : "";
|
|
13906
|
-
const upstreamInfo = upstream ? "\u2192 " + upstream : "";
|
|
13907
|
-
return icon + " `" + branchName + "` " + status + " " + upstreamInfo;
|
|
13908
|
-
}).join("\n");
|
|
13909
|
-
return "\u{1F33F} **All Branches**\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + branchList;
|
|
13910
|
-
}
|
|
13911
|
-
async function getGitStatus(directory) {
|
|
13912
|
-
const status = await execGit(directory, ["status", "--porcelain"]);
|
|
13913
|
-
const lines = status.split("\n").filter((l) => l.trim());
|
|
13914
|
-
if (lines.length === 0) {
|
|
13915
|
-
return "\u2705 Working directory clean";
|
|
13916
|
-
}
|
|
13917
|
-
const staged = [];
|
|
13918
|
-
const unstaged = [];
|
|
13919
|
-
const untracked = [];
|
|
13920
|
-
const conflicted = [];
|
|
13921
|
-
for (const line of lines) {
|
|
13922
|
-
if (!line || line.length < 2) continue;
|
|
13923
|
-
const statusCode = line.substring(0, 2);
|
|
13924
|
-
const filename = line.substring(3);
|
|
13925
|
-
if (statusCode === "??") {
|
|
13926
|
-
untracked.push(filename);
|
|
13927
|
-
} else if (statusCode.startsWith("U") || statusCode === "AA") {
|
|
13928
|
-
conflicted.push(filename);
|
|
13929
|
-
} else if (statusCode[0] !== " ") {
|
|
13930
|
-
staged.push(filename);
|
|
13931
|
-
}
|
|
13932
|
-
if (statusCode[1] !== " " && statusCode[1] !== "?") {
|
|
13933
|
-
unstaged.push(filename);
|
|
13934
|
-
}
|
|
13935
|
-
}
|
|
13936
|
-
const current = await execGit(directory, ["branch", "--show-current"]).catch(() => "unknown");
|
|
13937
|
-
let result = "\u{1F33F} **Branch**: `" + current + "`\n\n";
|
|
13938
|
-
if (staged.length > 0) {
|
|
13939
|
-
result += "\u2705 **Staged** (" + staged.length + ")\n" + staged.map((f) => " + " + f).join("\n") + "\n\n";
|
|
13940
|
-
}
|
|
13941
|
-
if (unstaged.length > 0) {
|
|
13942
|
-
result += "\u{1F4DD} **Modified** (" + unstaged.length + ")\n" + unstaged.map((f) => " ~ " + f).join("\n") + "\n\n";
|
|
13943
|
-
}
|
|
13944
|
-
if (untracked.length > 0) {
|
|
13945
|
-
result += "\u2795 **Untracked** (" + untracked.length + ")\n" + untracked.slice(0, 10).map((f) => " ? " + f).join("\n") + (untracked.length > 10 ? "\n ... and " + (untracked.length - 10) + " more" : "") + "\n\n";
|
|
13946
|
-
}
|
|
13947
|
-
if (conflicted.length > 0) {
|
|
13948
|
-
result += "\u26A0\uFE0F **Conflicts** (" + conflicted.length + ")\n" + conflicted.map((f) => " ! " + f).join("\n") + "\n\n";
|
|
13949
|
-
}
|
|
13950
|
-
return result.trim();
|
|
13951
|
-
}
|
|
13952
|
-
async function getDiff(directory, baseBranch) {
|
|
13953
|
-
const current = await execGit(directory, ["branch", "--show-current"]).catch(() => "HEAD");
|
|
13954
|
-
const base = baseBranch || "main";
|
|
13955
|
-
const diff = await execGit(directory, ["diff", base + "..." + current, "--stat"]);
|
|
13956
|
-
if (!diff.trim()) {
|
|
13957
|
-
return "\u2705 No differences between `" + current + "` and `" + base + "`";
|
|
13958
|
-
}
|
|
13959
|
-
const files = await execGit(directory, ["diff", base + "..." + current, "--name-only"]);
|
|
13960
|
-
const fileList = files.split("\n").filter((f) => f.trim());
|
|
13961
|
-
return "\u{1F4CA} **Diff**: `" + current + "` vs `" + base + "`\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + diff + "\n\n\u{1F4C1} **Changed Files** (" + fileList.length + "):\n" + fileList.map((f, i) => " " + (i + 1) + ". " + f).join("\n");
|
|
13962
|
-
}
|
|
13963
|
-
async function getRecentCommits(directory, count = 5) {
|
|
13964
|
-
const log2 = await execGit(directory, [
|
|
13965
|
-
"log",
|
|
13966
|
-
"-" + count,
|
|
13967
|
-
"--pretty=format:%H|%an|%ad|%s",
|
|
13968
|
-
"--date=short",
|
|
13969
|
-
"--name-only"
|
|
13970
|
-
]);
|
|
13971
|
-
const blocks = log2.split("\n\n").filter((b) => b.trim());
|
|
13972
|
-
const commits = [];
|
|
13973
|
-
for (const block of blocks) {
|
|
13974
|
-
const lines = block.split("\n").filter((l) => l.trim());
|
|
13975
|
-
if (lines.length < 2) continue;
|
|
13976
|
-
const [hash2, author, date5, message] = lines[0].split("|");
|
|
13977
|
-
const files = lines.slice(1);
|
|
13978
|
-
commits.push({
|
|
13979
|
-
hash: hash2.substring(0, 7),
|
|
13980
|
-
author,
|
|
13981
|
-
date: date5,
|
|
13982
|
-
message,
|
|
13983
|
-
files
|
|
13984
|
-
});
|
|
13985
|
-
}
|
|
13986
|
-
let result = "\u{1F4DC} **Recent Commits** (last " + commits.length + ")\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n";
|
|
13987
|
-
for (const commit of commits) {
|
|
13988
|
-
result += "\n\u{1F539} `" + commit.hash + "` - " + commit.message + "\n";
|
|
13989
|
-
result += " \u{1F464} " + commit.author + " | \u{1F4C5} " + commit.date + "\n";
|
|
13990
|
-
result += " \u{1F4C1} " + commit.files.length + " file" + (commit.files.length > 1 ? "s" : "") + "\n";
|
|
13991
|
-
}
|
|
13992
|
-
return result;
|
|
13993
|
-
}
|
|
13994
|
-
async function getAllInfo(directory) {
|
|
13995
|
-
const current = await getCurrentBranch(directory);
|
|
13996
|
-
const status = await getGitStatus(directory);
|
|
13997
|
-
const recent = await getRecentCommits(directory, 3);
|
|
13998
|
-
return "\u{1F50D} **Full Git Context**\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n" + current + "\n\n" + status + "\n\n" + recent;
|
|
13999
|
-
}
|
|
14000
13288
|
|
|
14001
|
-
// src/core/
|
|
13289
|
+
// src/core/commands/manager.ts
|
|
14002
13290
|
import { spawn as spawn2 } from "child_process";
|
|
14003
13291
|
import { randomBytes } from "crypto";
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
13292
|
+
|
|
13293
|
+
// src/shared/constants.ts
|
|
13294
|
+
var TIME = {
|
|
13295
|
+
SECOND: 1e3,
|
|
13296
|
+
MINUTE: 60 * 1e3,
|
|
13297
|
+
HOUR: 60 * 60 * 1e3
|
|
13298
|
+
};
|
|
13299
|
+
var ID_PREFIX = {
|
|
13300
|
+
/** Parallel agent task ID (e.g., task_a1b2c3d4) */
|
|
13301
|
+
TASK: "task_",
|
|
13302
|
+
/** Background command job ID (e.g., job_a1b2c3d4) */
|
|
13303
|
+
JOB: "job_",
|
|
13304
|
+
/** Session ID prefix */
|
|
13305
|
+
SESSION: "session_"
|
|
13306
|
+
};
|
|
13307
|
+
var PARALLEL_TASK = {
|
|
13308
|
+
TTL_MS: 30 * TIME.MINUTE,
|
|
13309
|
+
CLEANUP_DELAY_MS: 5 * TIME.MINUTE,
|
|
13310
|
+
MIN_STABILITY_MS: 5 * TIME.SECOND,
|
|
13311
|
+
POLL_INTERVAL_MS: 2e3,
|
|
13312
|
+
DEFAULT_CONCURRENCY: 3,
|
|
13313
|
+
MAX_CONCURRENCY: 10
|
|
13314
|
+
};
|
|
13315
|
+
var BACKGROUND_TASK = {
|
|
13316
|
+
DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
|
|
13317
|
+
MAX_OUTPUT_LENGTH: 1e4
|
|
13318
|
+
};
|
|
13319
|
+
var STATUS_EMOJI = {
|
|
13320
|
+
pending: "\u23F3",
|
|
13321
|
+
running: "\u{1F504}",
|
|
13322
|
+
completed: "\u2705",
|
|
13323
|
+
done: "\u2705",
|
|
13324
|
+
error: "\u274C",
|
|
13325
|
+
timeout: "\u23F0",
|
|
13326
|
+
cancelled: "\u{1F6AB}"
|
|
13327
|
+
};
|
|
13328
|
+
function getStatusEmoji(status) {
|
|
13329
|
+
return STATUS_EMOJI[status] ?? "\u2753";
|
|
13330
|
+
}
|
|
13331
|
+
|
|
13332
|
+
// src/core/commands/manager.ts
|
|
14007
13333
|
var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
14008
13334
|
static _instance;
|
|
14009
13335
|
tasks = /* @__PURE__ */ new Map();
|
|
14010
|
-
debugMode = true;
|
|
14011
|
-
//
|
|
14012
|
-
storageDir;
|
|
14013
|
-
storageFile;
|
|
14014
|
-
monitoringInterval;
|
|
13336
|
+
debugMode = process.env.DEBUG_BG_TASK === "true";
|
|
13337
|
+
// Disabled by default
|
|
14015
13338
|
constructor() {
|
|
14016
|
-
this.storageDir = join2(homedir(), ".opencode-orchestrator");
|
|
14017
|
-
this.storageFile = join2(this.storageDir, "tasks.json");
|
|
14018
|
-
this.loadFromDisk();
|
|
14019
|
-
this.startMonitoring();
|
|
14020
13339
|
}
|
|
14021
13340
|
static get instance() {
|
|
14022
13341
|
if (!_BackgroundTaskManager._instance) {
|
|
@@ -14024,144 +13343,15 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
|
14024
13343
|
}
|
|
14025
13344
|
return _BackgroundTaskManager._instance;
|
|
14026
13345
|
}
|
|
14027
|
-
/**
|
|
14028
|
-
* Generate a unique task ID in the format job_xxxxxxxx
|
|
14029
|
-
*/
|
|
14030
13346
|
generateId() {
|
|
14031
|
-
|
|
14032
|
-
return `job_${hex3}`;
|
|
14033
|
-
}
|
|
14034
|
-
/**
|
|
14035
|
-
* Ensure storage directory exists
|
|
14036
|
-
*/
|
|
14037
|
-
ensureStorageDir() {
|
|
14038
|
-
if (!existsSync3(this.storageDir)) {
|
|
14039
|
-
mkdirSync(this.storageDir, { recursive: true });
|
|
14040
|
-
this.debug("system", `Created storage directory: ${this.storageDir}`);
|
|
14041
|
-
}
|
|
14042
|
-
}
|
|
14043
|
-
/**
|
|
14044
|
-
* Load tasks from disk on startup
|
|
14045
|
-
*/
|
|
14046
|
-
loadFromDisk() {
|
|
14047
|
-
this.ensureStorageDir();
|
|
14048
|
-
if (!existsSync3(this.storageFile)) {
|
|
14049
|
-
this.debug("system", "No existing task data on disk");
|
|
14050
|
-
return;
|
|
14051
|
-
}
|
|
14052
|
-
try {
|
|
14053
|
-
const data = readFileSync(this.storageFile, "utf-8");
|
|
14054
|
-
const tasksData = JSON.parse(data);
|
|
14055
|
-
for (const [id, taskData] of Object.entries(tasksData)) {
|
|
14056
|
-
const task = taskData;
|
|
14057
|
-
task.process = void 0;
|
|
14058
|
-
if (task.status === "running") {
|
|
14059
|
-
task.status = "error";
|
|
14060
|
-
task.errorOutput += "\n[Process lost on restart]";
|
|
14061
|
-
task.endTime = Date.now();
|
|
14062
|
-
task.exitCode = null;
|
|
14063
|
-
}
|
|
14064
|
-
this.tasks.set(id, task);
|
|
14065
|
-
}
|
|
14066
|
-
this.debug("system", `Loaded ${this.tasks.size} tasks from disk`);
|
|
14067
|
-
} catch (error45) {
|
|
14068
|
-
this.debug(
|
|
14069
|
-
"system",
|
|
14070
|
-
`Failed to load tasks: ${error45 instanceof Error ? error45.message : String(error45)}`
|
|
14071
|
-
);
|
|
14072
|
-
}
|
|
13347
|
+
return `${ID_PREFIX.JOB}${randomBytes(4).toString("hex")}`;
|
|
14073
13348
|
}
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
this.ensureStorageDir();
|
|
14079
|
-
try {
|
|
14080
|
-
const tasksData = {};
|
|
14081
|
-
for (const [id, task] of this.tasks.entries()) {
|
|
14082
|
-
tasksData[id] = task;
|
|
14083
|
-
}
|
|
14084
|
-
writeFileSync(
|
|
14085
|
-
this.storageFile,
|
|
14086
|
-
JSON.stringify(tasksData, null, 2),
|
|
14087
|
-
"utf-8"
|
|
14088
|
-
);
|
|
14089
|
-
} catch (error45) {
|
|
14090
|
-
this.debug(
|
|
14091
|
-
"system",
|
|
14092
|
-
`Failed to save tasks: ${error45 instanceof Error ? error45.message : String(error45)}`
|
|
14093
|
-
);
|
|
13349
|
+
debug(taskId, message) {
|
|
13350
|
+
if (this.debugMode) {
|
|
13351
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
13352
|
+
console.log(`[BG ${ts}] ${taskId}: ${message}`);
|
|
14094
13353
|
}
|
|
14095
13354
|
}
|
|
14096
|
-
/**
|
|
14097
|
-
* Start periodic monitoring of running processes
|
|
14098
|
-
*/
|
|
14099
|
-
startMonitoring() {
|
|
14100
|
-
const MONITOR_INTERVAL_MS = 5e3;
|
|
14101
|
-
this.monitoringInterval = setInterval(() => {
|
|
14102
|
-
this.monitorRunningProcesses();
|
|
14103
|
-
}, MONITOR_INTERVAL_MS);
|
|
14104
|
-
if (this.monitoringInterval) {
|
|
14105
|
-
this.monitoringInterval.unref();
|
|
14106
|
-
}
|
|
14107
|
-
}
|
|
14108
|
-
/**
|
|
14109
|
-
* Stop monitoring
|
|
14110
|
-
*/
|
|
14111
|
-
stopMonitoring() {
|
|
14112
|
-
if (this.monitoringInterval) {
|
|
14113
|
-
clearInterval(this.monitoringInterval);
|
|
14114
|
-
this.monitoringInterval = void 0;
|
|
14115
|
-
}
|
|
14116
|
-
}
|
|
14117
|
-
/**
|
|
14118
|
-
* Monitor running processes and detect zombie processes
|
|
14119
|
-
*/
|
|
14120
|
-
monitorRunningProcesses() {
|
|
14121
|
-
const now = Date.now();
|
|
14122
|
-
let hasRunningTasks = false;
|
|
14123
|
-
for (const [id, task] of this.tasks.entries()) {
|
|
14124
|
-
if (task.status !== "running") continue;
|
|
14125
|
-
hasRunningTasks = true;
|
|
14126
|
-
if (task.process && task.process.pid) {
|
|
14127
|
-
const pid = task.process.pid;
|
|
14128
|
-
try {
|
|
14129
|
-
process.kill(pid, 0);
|
|
14130
|
-
} catch (error45) {
|
|
14131
|
-
task.status = "error";
|
|
14132
|
-
task.errorOutput += `
|
|
14133
|
-
Process disappeared (PID ${pid})`;
|
|
14134
|
-
task.endTime = Date.now();
|
|
14135
|
-
task.exitCode = null;
|
|
14136
|
-
task.process = void 0;
|
|
14137
|
-
this.saveToDisk();
|
|
14138
|
-
this.debug(id, `Process dead (PID ${pid}), marked as error`);
|
|
14139
|
-
}
|
|
14140
|
-
} else if (task.process) {
|
|
14141
|
-
task.status = "error";
|
|
14142
|
-
task.errorOutput += "\nProcess reference lost";
|
|
14143
|
-
task.endTime = Date.now();
|
|
14144
|
-
task.exitCode = null;
|
|
14145
|
-
this.saveToDisk();
|
|
14146
|
-
this.debug(id, "Process reference lost, marked as error");
|
|
14147
|
-
}
|
|
14148
|
-
}
|
|
14149
|
-
if (!hasRunningTasks && this.monitoringInterval) {
|
|
14150
|
-
this.stopMonitoring();
|
|
14151
|
-
}
|
|
14152
|
-
}
|
|
14153
|
-
/**
|
|
14154
|
-
* Debug logging helper
|
|
14155
|
-
*/
|
|
14156
|
-
debug(taskId, message) {
|
|
14157
|
-
if (this.debugMode) {
|
|
14158
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
14159
|
-
console.log(`[BG-DEBUG ${timestamp}] ${taskId}: ${message}`);
|
|
14160
|
-
}
|
|
14161
|
-
}
|
|
14162
|
-
/**
|
|
14163
|
-
* Run a command in the background
|
|
14164
|
-
*/
|
|
14165
13355
|
run(options) {
|
|
14166
13356
|
const id = this.generateId();
|
|
14167
13357
|
const { command, cwd = process.cwd(), timeout = 3e5, label } = options;
|
|
@@ -14182,8 +13372,7 @@ Process disappeared (PID ${pid})`;
|
|
|
14182
13372
|
timeout
|
|
14183
13373
|
};
|
|
14184
13374
|
this.tasks.set(id, task);
|
|
14185
|
-
this.
|
|
14186
|
-
this.debug(id, `Starting: ${command} (cwd: ${cwd})`);
|
|
13375
|
+
this.debug(id, `Starting: ${command}`);
|
|
14187
13376
|
try {
|
|
14188
13377
|
const proc = spawn2(shell, task.args, {
|
|
14189
13378
|
cwd,
|
|
@@ -14192,29 +13381,17 @@ Process disappeared (PID ${pid})`;
|
|
|
14192
13381
|
});
|
|
14193
13382
|
task.process = proc;
|
|
14194
13383
|
proc.stdout?.on("data", (data) => {
|
|
14195
|
-
|
|
14196
|
-
task.output += text;
|
|
14197
|
-
this.debug(
|
|
14198
|
-
id,
|
|
14199
|
-
`stdout: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`
|
|
14200
|
-
);
|
|
13384
|
+
task.output += data.toString();
|
|
14201
13385
|
});
|
|
14202
13386
|
proc.stderr?.on("data", (data) => {
|
|
14203
|
-
|
|
14204
|
-
task.errorOutput += text;
|
|
14205
|
-
this.debug(
|
|
14206
|
-
id,
|
|
14207
|
-
`stderr: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`
|
|
14208
|
-
);
|
|
13387
|
+
task.errorOutput += data.toString();
|
|
14209
13388
|
});
|
|
14210
13389
|
proc.on("close", (code) => {
|
|
14211
13390
|
task.exitCode = code;
|
|
14212
13391
|
task.endTime = Date.now();
|
|
14213
13392
|
task.status = code === 0 ? "done" : "error";
|
|
14214
13393
|
task.process = void 0;
|
|
14215
|
-
this.
|
|
14216
|
-
const duration3 = ((task.endTime - task.startTime) / 1e3).toFixed(2);
|
|
14217
|
-
this.debug(id, `Completed with code ${code} in ${duration3}s`);
|
|
13394
|
+
this.debug(id, `Done (code=${code})`);
|
|
14218
13395
|
});
|
|
14219
13396
|
proc.on("error", (err) => {
|
|
14220
13397
|
task.status = "error";
|
|
@@ -14222,68 +13399,31 @@ Process disappeared (PID ${pid})`;
|
|
|
14222
13399
|
Process error: ${err.message}`;
|
|
14223
13400
|
task.endTime = Date.now();
|
|
14224
13401
|
task.process = void 0;
|
|
14225
|
-
this.saveToDisk();
|
|
14226
|
-
this.debug(id, `Error: ${err.message}`);
|
|
14227
13402
|
});
|
|
14228
13403
|
setTimeout(() => {
|
|
14229
13404
|
if (task.status === "running" && task.process) {
|
|
14230
|
-
this.debug(id, `Timeout after ${timeout}ms, killing process`);
|
|
14231
13405
|
task.process.kill("SIGKILL");
|
|
14232
13406
|
task.status = "timeout";
|
|
14233
13407
|
task.endTime = Date.now();
|
|
14234
|
-
|
|
14235
|
-
Process killed: timeout after ${timeout}ms`;
|
|
14236
|
-
this.saveToDisk();
|
|
13408
|
+
this.debug(id, "Timeout");
|
|
14237
13409
|
}
|
|
14238
13410
|
}, timeout);
|
|
14239
13411
|
} catch (err) {
|
|
14240
13412
|
task.status = "error";
|
|
14241
|
-
task.errorOutput = `
|
|
13413
|
+
task.errorOutput = `Spawn failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
14242
13414
|
task.endTime = Date.now();
|
|
14243
|
-
this.saveToDisk();
|
|
14244
|
-
this.debug(id, `Spawn failed: ${task.errorOutput}`);
|
|
14245
13415
|
}
|
|
14246
13416
|
return task;
|
|
14247
13417
|
}
|
|
14248
|
-
/**
|
|
14249
|
-
* Get task by ID
|
|
14250
|
-
*/
|
|
14251
13418
|
get(taskId) {
|
|
14252
13419
|
return this.tasks.get(taskId);
|
|
14253
13420
|
}
|
|
14254
|
-
/**
|
|
14255
|
-
* Get all tasks
|
|
14256
|
-
*/
|
|
14257
13421
|
getAll() {
|
|
14258
13422
|
return Array.from(this.tasks.values());
|
|
14259
13423
|
}
|
|
14260
|
-
/**
|
|
14261
|
-
* Get tasks by status
|
|
14262
|
-
*/
|
|
14263
13424
|
getByStatus(status) {
|
|
14264
13425
|
return this.getAll().filter((t) => t.status === status);
|
|
14265
13426
|
}
|
|
14266
|
-
/**
|
|
14267
|
-
* Clean up tasks by session ID
|
|
14268
|
-
*/
|
|
14269
|
-
cleanupBySession(sessionID) {
|
|
14270
|
-
let count = 0;
|
|
14271
|
-
for (const [id, task] of this.tasks) {
|
|
14272
|
-
if (task.sessionID === sessionID) {
|
|
14273
|
-
if (task.process && task.status === "running") {
|
|
14274
|
-
task.process.kill("SIGKILL");
|
|
14275
|
-
}
|
|
14276
|
-
this.tasks.delete(id);
|
|
14277
|
-
count++;
|
|
14278
|
-
this.debug(id, `Cleaned up for session ${sessionID}`);
|
|
14279
|
-
}
|
|
14280
|
-
}
|
|
14281
|
-
this.saveToDisk();
|
|
14282
|
-
return count;
|
|
14283
|
-
}
|
|
14284
|
-
/**
|
|
14285
|
-
* Clear completed/failed tasks
|
|
14286
|
-
*/
|
|
14287
13427
|
clearCompleted() {
|
|
14288
13428
|
let count = 0;
|
|
14289
13429
|
for (const [id, task] of this.tasks) {
|
|
@@ -14292,12 +13432,8 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
14292
13432
|
count++;
|
|
14293
13433
|
}
|
|
14294
13434
|
}
|
|
14295
|
-
this.saveToDisk();
|
|
14296
13435
|
return count;
|
|
14297
13436
|
}
|
|
14298
|
-
/**
|
|
14299
|
-
* Kill a running task
|
|
14300
|
-
*/
|
|
14301
13437
|
kill(taskId) {
|
|
14302
13438
|
const task = this.tasks.get(taskId);
|
|
14303
13439
|
if (task?.process) {
|
|
@@ -14305,116 +13441,60 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
14305
13441
|
task.status = "error";
|
|
14306
13442
|
task.errorOutput += "\nKilled by user";
|
|
14307
13443
|
task.endTime = Date.now();
|
|
14308
|
-
this.saveToDisk();
|
|
14309
|
-
this.debug(taskId, "Killed by user");
|
|
14310
13444
|
return true;
|
|
14311
13445
|
}
|
|
14312
13446
|
return false;
|
|
14313
13447
|
}
|
|
14314
|
-
/**
|
|
14315
|
-
* Format duration for display
|
|
14316
|
-
*/
|
|
14317
13448
|
formatDuration(task) {
|
|
14318
13449
|
const end = task.endTime || Date.now();
|
|
14319
13450
|
const seconds = (end - task.startTime) / 1e3;
|
|
14320
|
-
if (seconds < 60) {
|
|
14321
|
-
|
|
14322
|
-
}
|
|
14323
|
-
const minutes = Math.floor(seconds / 60);
|
|
14324
|
-
const remainingSeconds = seconds % 60;
|
|
14325
|
-
return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
|
|
13451
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
13452
|
+
return `${Math.floor(seconds / 60)}m ${(seconds % 60).toFixed(0)}s`;
|
|
14326
13453
|
}
|
|
14327
|
-
/**
|
|
14328
|
-
* Get status emoji
|
|
14329
|
-
*/
|
|
14330
13454
|
getStatusEmoji(status) {
|
|
14331
|
-
|
|
14332
|
-
case "pending":
|
|
14333
|
-
return "\u23F8\uFE0F";
|
|
14334
|
-
case "running":
|
|
14335
|
-
return "\u23F3";
|
|
14336
|
-
case "done":
|
|
14337
|
-
return "\u2705";
|
|
14338
|
-
case "error":
|
|
14339
|
-
return "\u274C";
|
|
14340
|
-
case "timeout":
|
|
14341
|
-
return "\u23F0";
|
|
14342
|
-
default:
|
|
14343
|
-
return "\u2753";
|
|
14344
|
-
}
|
|
13455
|
+
return getStatusEmoji(status);
|
|
14345
13456
|
}
|
|
14346
13457
|
};
|
|
14347
13458
|
var backgroundTaskManager = BackgroundTaskManager.instance;
|
|
14348
13459
|
|
|
14349
|
-
// src/tools/background.ts
|
|
13460
|
+
// src/tools/background-cmd/run.ts
|
|
14350
13461
|
var runBackgroundTool = tool({
|
|
14351
13462
|
description: `Run a shell command in the background and get a task ID.
|
|
14352
13463
|
|
|
14353
13464
|
<purpose>
|
|
14354
|
-
Execute long-running commands (builds, tests
|
|
14355
|
-
|
|
14356
|
-
</purpose
|
|
14357
|
-
|
|
14358
|
-
<examples>
|
|
14359
|
-
- "npm run build" \u2192 Build project in background
|
|
14360
|
-
- "cargo test" \u2192 Run Rust tests
|
|
14361
|
-
- "sleep 10 && echo done" \u2192 Delayed execution
|
|
14362
|
-
</examples>
|
|
14363
|
-
|
|
14364
|
-
<flow>
|
|
14365
|
-
1. Call run_background with command
|
|
14366
|
-
2. Get task ID immediately (e.g., job_a1b2c3d4)
|
|
14367
|
-
3. Continue other work
|
|
14368
|
-
4. Call check_background with task ID to get results
|
|
14369
|
-
</flow>`,
|
|
13465
|
+
Execute long-running commands (builds, tests) without blocking.
|
|
13466
|
+
Use check_background to get results.
|
|
13467
|
+
</purpose>`,
|
|
14370
13468
|
args: {
|
|
14371
13469
|
command: tool.schema.string().describe("Shell command to execute"),
|
|
14372
|
-
cwd: tool.schema.string().optional().describe("Working directory
|
|
14373
|
-
timeout: tool.schema.number().optional().describe("Timeout in
|
|
14374
|
-
label: tool.schema.string().optional().describe("
|
|
14375
|
-
sessionID: tool.schema.string().optional().describe("Session ID for automatic cleanup on session deletion")
|
|
13470
|
+
cwd: tool.schema.string().optional().describe("Working directory"),
|
|
13471
|
+
timeout: tool.schema.number().optional().describe("Timeout in ms (default: 300000)"),
|
|
13472
|
+
label: tool.schema.string().optional().describe("Task label")
|
|
14376
13473
|
},
|
|
14377
13474
|
async execute(args) {
|
|
14378
|
-
const { command, cwd, timeout, label
|
|
13475
|
+
const { command, cwd, timeout, label } = args;
|
|
14379
13476
|
const task = backgroundTaskManager.run({
|
|
14380
13477
|
command,
|
|
14381
13478
|
cwd: cwd || process.cwd(),
|
|
14382
13479
|
timeout: timeout || 3e5,
|
|
14383
|
-
label
|
|
14384
|
-
sessionID
|
|
13480
|
+
label
|
|
14385
13481
|
});
|
|
14386
13482
|
const displayLabel = label ? ` (${label})` : "";
|
|
14387
13483
|
return `\u{1F680} **Background Task Started**${displayLabel}
|
|
14388
|
-
|
|
14389
|
-
|
|
|
14390
|
-
|
|
14391
|
-
| **Task ID** | \`${task.id}\` |
|
|
14392
|
-
| **Command** | \`${command}\` |
|
|
14393
|
-
| **Status** | ${backgroundTaskManager.getStatusEmoji(task.status)} ${task.status} |
|
|
14394
|
-
| **Working Dir** | ${task.cwd} |
|
|
14395
|
-
| **Timeout** | ${(task.timeout / 1e3).toFixed(0)}s |
|
|
14396
|
-
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
13484
|
+
| Task ID | \`${task.id}\` |
|
|
13485
|
+
| Command | \`${command}\` |
|
|
13486
|
+
| Status | ${backgroundTaskManager.getStatusEmoji(task.status)} ${task.status} |
|
|
14397
13487
|
|
|
14398
|
-
\u{1F4CC}
|
|
13488
|
+
\u{1F4CC} Use \`check_background({ taskId: "${task.id}" })\` to get results.`;
|
|
14399
13489
|
}
|
|
14400
13490
|
});
|
|
14401
|
-
var checkBackgroundTool = tool({
|
|
14402
|
-
description: `Check the status and output of a background task.
|
|
14403
|
-
|
|
14404
|
-
<purpose>
|
|
14405
|
-
Retrieve the current status and output of a previously started background task.
|
|
14406
|
-
Use this after run_background to get results.
|
|
14407
|
-
</purpose>
|
|
14408
13491
|
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
- Duration
|
|
14413
|
-
- Full output (stdout + stderr)
|
|
14414
|
-
</output_includes>`,
|
|
13492
|
+
// src/tools/background-cmd/check.ts
|
|
13493
|
+
var checkBackgroundTool = tool({
|
|
13494
|
+
description: `Check the status and output of a background task.`,
|
|
14415
13495
|
args: {
|
|
14416
|
-
taskId: tool.schema.string().describe("Task ID from run_background
|
|
14417
|
-
tailLines: tool.schema.number().optional().describe("Limit output to last N lines
|
|
13496
|
+
taskId: tool.schema.string().describe("Task ID from run_background"),
|
|
13497
|
+
tailLines: tool.schema.number().optional().describe("Limit output to last N lines")
|
|
14418
13498
|
},
|
|
14419
13499
|
async execute(args) {
|
|
14420
13500
|
const { taskId, tailLines } = args;
|
|
@@ -14427,7 +13507,7 @@ Use this after run_background to get results.
|
|
|
14427
13507
|
const taskList = allTasks.map((t) => `- \`${t.id}\`: ${t.command.substring(0, 30)}...`).join("\n");
|
|
14428
13508
|
return `\u274C Task \`${taskId}\` not found.
|
|
14429
13509
|
|
|
14430
|
-
**Available
|
|
13510
|
+
**Available:**
|
|
14431
13511
|
${taskList}`;
|
|
14432
13512
|
}
|
|
14433
13513
|
const duration3 = backgroundTaskManager.formatDuration(task);
|
|
@@ -14435,64 +13515,41 @@ ${taskList}`;
|
|
|
14435
13515
|
let output = task.output;
|
|
14436
13516
|
let stderr = task.errorOutput;
|
|
14437
13517
|
if (tailLines && tailLines > 0) {
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
output = outputLines.slice(-tailLines).join("\n");
|
|
14441
|
-
stderr = stderrLines.slice(-tailLines).join("\n");
|
|
13518
|
+
output = output.split("\n").slice(-tailLines).join("\n");
|
|
13519
|
+
stderr = stderr.split("\n").slice(-tailLines).join("\n");
|
|
14442
13520
|
}
|
|
14443
13521
|
const maxLen = 1e4;
|
|
14444
|
-
if (output.length > maxLen)
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
14455
|
-
| Property | Value |
|
|
14456
|
-
|----------|-------|
|
|
14457
|
-
| **Command** | \`${task.command}\` |
|
|
14458
|
-
| **Status** | ${statusEmoji} **${task.status.toUpperCase()}** |
|
|
14459
|
-
| **Duration** | ${duration3}${task.status === "running" ? " (ongoing)" : ""} |
|
|
14460
|
-
${task.exitCode !== null ? `| **Exit Code** | ${task.exitCode} |` : ""}
|
|
14461
|
-
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
|
|
14462
|
-
if (output.trim()) {
|
|
14463
|
-
result += `
|
|
14464
|
-
|
|
14465
|
-
\u{1F4E4} **Output (stdout)**:
|
|
13522
|
+
if (output.length > maxLen) output = `[...truncated...]\\n` + output.slice(-maxLen);
|
|
13523
|
+
if (stderr.length > maxLen) stderr = `[...truncated...]\\n` + stderr.slice(-maxLen);
|
|
13524
|
+
let result = `${statusEmoji} **Task ${task.id}**${task.label ? ` (${task.label})` : ""}
|
|
13525
|
+
| Command | \`${task.command}\` |
|
|
13526
|
+
| Status | ${statusEmoji} **${task.status.toUpperCase()}** |
|
|
13527
|
+
| Duration | ${duration3}${task.status === "running" ? " (ongoing)" : ""} |
|
|
13528
|
+
${task.exitCode !== null ? `| Exit Code | ${task.exitCode} |` : ""}`;
|
|
13529
|
+
if (output.trim()) result += `
|
|
13530
|
+
|
|
13531
|
+
\u{1F4E4} **stdout:**
|
|
14466
13532
|
\`\`\`
|
|
14467
13533
|
${output.trim()}
|
|
14468
13534
|
\`\`\``;
|
|
14469
|
-
|
|
14470
|
-
if (stderr.trim()) {
|
|
14471
|
-
result += `
|
|
13535
|
+
if (stderr.trim()) result += `
|
|
14472
13536
|
|
|
14473
|
-
\u26A0\uFE0F **
|
|
13537
|
+
\u26A0\uFE0F **stderr:**
|
|
14474
13538
|
\`\`\`
|
|
14475
13539
|
${stderr.trim()}
|
|
14476
13540
|
\`\`\``;
|
|
14477
|
-
|
|
14478
|
-
if (task.status === "running") {
|
|
14479
|
-
result += `
|
|
13541
|
+
if (task.status === "running") result += `
|
|
14480
13542
|
|
|
14481
|
-
\u23F3
|
|
14482
|
-
\`check_background({ taskId: "${task.id}" })\``;
|
|
14483
|
-
}
|
|
13543
|
+
\u23F3 Still running... check again.`;
|
|
14484
13544
|
return result;
|
|
14485
13545
|
}
|
|
14486
13546
|
});
|
|
14487
|
-
var listBackgroundTool = tool({
|
|
14488
|
-
description: `List all background tasks and their current status.
|
|
14489
13547
|
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
</purpose>`,
|
|
13548
|
+
// src/tools/background-cmd/list.ts
|
|
13549
|
+
var listBackgroundTool = tool({
|
|
13550
|
+
description: `List all background tasks and their status.`,
|
|
14494
13551
|
args: {
|
|
14495
|
-
status: tool.schema.enum(["all", "running", "done", "error"]).optional().describe("Filter by status
|
|
13552
|
+
status: tool.schema.enum(["all", "running", "done", "error"]).optional().describe("Filter by status")
|
|
14496
13553
|
},
|
|
14497
13554
|
async execute(args) {
|
|
14498
13555
|
const { status = "all" } = args;
|
|
@@ -14503,344 +13560,102 @@ Useful to check what's in progress before starting new tasks.
|
|
|
14503
13560
|
tasks = backgroundTaskManager.getByStatus(status);
|
|
14504
13561
|
}
|
|
14505
13562
|
if (tasks.length === 0) {
|
|
14506
|
-
return `\u{1F4CB}
|
|
14507
|
-
|
|
14508
|
-
Use \`run_background\` to start a new background task.`;
|
|
13563
|
+
return `\u{1F4CB} No background tasks${status !== "all" ? ` with status "${status}"` : ""}`;
|
|
14509
13564
|
}
|
|
14510
13565
|
tasks.sort((a, b) => b.startTime - a.startTime);
|
|
14511
|
-
const rows = tasks.map((
|
|
14512
|
-
const emoji3 = backgroundTaskManager.getStatusEmoji(
|
|
14513
|
-
const
|
|
14514
|
-
const
|
|
14515
|
-
|
|
14516
|
-
return `| \`${task.id}\` | ${emoji3} ${task.status.padEnd(7)} | ${cmdShort.padEnd(25)}${labelPart} | ${duration3.padStart(8)} |`;
|
|
13566
|
+
const rows = tasks.map((t) => {
|
|
13567
|
+
const emoji3 = backgroundTaskManager.getStatusEmoji(t.status);
|
|
13568
|
+
const cmd = t.command.length > 25 ? t.command.slice(0, 22) + "..." : t.command;
|
|
13569
|
+
const label = t.label ? ` [${t.label}]` : "";
|
|
13570
|
+
return `| \`${t.id}\` | ${emoji3} ${t.status} | ${cmd}${label} | ${backgroundTaskManager.formatDuration(t)} |`;
|
|
14517
13571
|
}).join("\n");
|
|
14518
|
-
const
|
|
14519
|
-
const
|
|
14520
|
-
const
|
|
14521
|
-
return `\u{1F4CB} **Background Tasks** (${tasks.length}
|
|
14522
|
-
\
|
|
14523
|
-
| \u23F3 Running: ${runningCount} | \u2705 Done: ${doneCount} | \u274C Error/Timeout: ${errorCount} |
|
|
14524
|
-
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
13572
|
+
const running = tasks.filter((t) => t.status === "running").length;
|
|
13573
|
+
const done = tasks.filter((t) => t.status === "done").length;
|
|
13574
|
+
const error45 = tasks.filter((t) => t.status === "error" || t.status === "timeout").length;
|
|
13575
|
+
return `\u{1F4CB} **Background Tasks** (${tasks.length})
|
|
13576
|
+
\u23F3 Running: ${running} | \u2705 Done: ${done} | \u274C Error: ${error45}
|
|
14525
13577
|
|
|
14526
|
-
|
|
|
14527
|
-
|
|
14528
|
-
${rows}
|
|
14529
|
-
|
|
14530
|
-
\u{1F4A1} Use \`check_background({ taskId: "job_xxxxx" })\` to see full output.`;
|
|
13578
|
+
| ID | Status | Command | Duration |
|
|
13579
|
+
|----|--------|---------|----------|
|
|
13580
|
+
${rows}`;
|
|
14531
13581
|
}
|
|
14532
13582
|
});
|
|
14533
|
-
var killBackgroundTool = tool({
|
|
14534
|
-
description: `Kill a running background task.
|
|
14535
13583
|
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
13584
|
+
// src/tools/background-cmd/kill.ts
|
|
13585
|
+
var killBackgroundTool = tool({
|
|
13586
|
+
description: `Kill a running background task.`,
|
|
14539
13587
|
args: {
|
|
14540
|
-
taskId: tool.schema.string().describe("Task ID to kill
|
|
13588
|
+
taskId: tool.schema.string().describe("Task ID to kill")
|
|
14541
13589
|
},
|
|
14542
13590
|
async execute(args) {
|
|
14543
13591
|
const { taskId } = args;
|
|
14544
13592
|
const task = backgroundTaskManager.get(taskId);
|
|
14545
|
-
if (!task) {
|
|
14546
|
-
|
|
14547
|
-
}
|
|
14548
|
-
if (task.status !== "running") {
|
|
14549
|
-
return `\u26A0\uFE0F Task \`${taskId}\` is not running (status: ${task.status}).`;
|
|
14550
|
-
}
|
|
13593
|
+
if (!task) return `\u274C Task \`${taskId}\` not found.`;
|
|
13594
|
+
if (task.status !== "running") return `\u26A0\uFE0F Task \`${taskId}\` is not running (${task.status}).`;
|
|
14551
13595
|
const killed = backgroundTaskManager.kill(taskId);
|
|
14552
13596
|
if (killed) {
|
|
14553
|
-
return `\u{1F6D1} Task \`${taskId}\`
|
|
13597
|
+
return `\u{1F6D1} Task \`${taskId}\` killed.
|
|
14554
13598
|
Command: \`${task.command}\`
|
|
14555
|
-
Duration
|
|
13599
|
+
Duration: ${backgroundTaskManager.formatDuration(task)}`;
|
|
14556
13600
|
}
|
|
14557
|
-
return `\u26A0\uFE0F Could not kill task \`${taskId}
|
|
13601
|
+
return `\u26A0\uFE0F Could not kill task \`${taskId}\`.`;
|
|
14558
13602
|
}
|
|
14559
13603
|
});
|
|
14560
13604
|
|
|
14561
|
-
// src/core/
|
|
14562
|
-
var
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
cleanupDelayMs: 5 * 60 * 1e3,
|
|
14566
|
-
// 5 minutes
|
|
14567
|
-
minStabilityMs: 5 * 1e3,
|
|
14568
|
-
// 5 seconds
|
|
14569
|
-
pollIntervalMs: 2 * 1e3,
|
|
14570
|
-
// 2 seconds
|
|
14571
|
-
defaultConcurrency: 3,
|
|
14572
|
-
maxConcurrency: 10,
|
|
14573
|
-
enableDebug: false,
|
|
14574
|
-
enableDetailedLogs: false
|
|
14575
|
-
};
|
|
14576
|
-
var DEFAULT_BACKGROUND_TASK = {
|
|
14577
|
-
monitorIntervalMs: 5 * 1e3,
|
|
14578
|
-
// 5 seconds
|
|
14579
|
-
storageDir: process.env.OPENCODE_ORCHESTRATOR_DIR || `${__require("os").homedir()}/.opencode-orchestrator`,
|
|
14580
|
-
defaultTimeoutMs: 5 * 60 * 1e3,
|
|
14581
|
-
// 5 minutes
|
|
14582
|
-
maxCompletedTasksToKeep: 100,
|
|
14583
|
-
enableDebug: true
|
|
14584
|
-
};
|
|
14585
|
-
var DEFAULT_SESSION = {
|
|
14586
|
-
defaultMaxSteps: 50,
|
|
14587
|
-
taskCommandMaxSteps: 100
|
|
13605
|
+
// src/core/agents/concurrency.ts
|
|
13606
|
+
var DEBUG = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
13607
|
+
var log = (...args) => {
|
|
13608
|
+
if (DEBUG) console.log("[concurrency]", ...args);
|
|
14588
13609
|
};
|
|
14589
|
-
var
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
constructor() {
|
|
14595
|
-
this.
|
|
14596
|
-
this.backgroundTaskConfig = this.loadBackgroundTaskConfig();
|
|
14597
|
-
this.sessionConfig = this.loadSessionConfig();
|
|
14598
|
-
}
|
|
14599
|
-
static getInstance() {
|
|
14600
|
-
if (!_ConfigManager._instance) {
|
|
14601
|
-
_ConfigManager._instance = new _ConfigManager();
|
|
14602
|
-
}
|
|
14603
|
-
return _ConfigManager._instance;
|
|
14604
|
-
}
|
|
14605
|
-
// ------------------------------------------------------------------------
|
|
14606
|
-
// Parallel Agent Config
|
|
14607
|
-
// ------------------------------------------------------------------------
|
|
14608
|
-
loadParallelAgentConfig() {
|
|
14609
|
-
return {
|
|
14610
|
-
...DEFAULT_PARALLEL_AGENT,
|
|
14611
|
-
taskTtlMs: this.parseEnvInt(
|
|
14612
|
-
"OPENCODE_TASK_TTL_MS",
|
|
14613
|
-
DEFAULT_PARALLEL_AGENT.taskTtlMs
|
|
14614
|
-
),
|
|
14615
|
-
cleanupDelayMs: this.parseEnvInt(
|
|
14616
|
-
"OPENCODE_CLEANUP_DELAY_MS",
|
|
14617
|
-
DEFAULT_PARALLEL_AGENT.cleanupDelayMs
|
|
14618
|
-
),
|
|
14619
|
-
minStabilityMs: this.parseEnvInt(
|
|
14620
|
-
"OPENCODE_MIN_STABILITY_MS",
|
|
14621
|
-
DEFAULT_PARALLEL_AGENT.minStabilityMs
|
|
14622
|
-
),
|
|
14623
|
-
pollIntervalMs: this.parseEnvInt(
|
|
14624
|
-
"OPENCODE_POLL_INTERVAL_MS",
|
|
14625
|
-
DEFAULT_PARALLEL_AGENT.pollIntervalMs
|
|
14626
|
-
),
|
|
14627
|
-
defaultConcurrency: this.parseEnvInt(
|
|
14628
|
-
"OPENCODE_DEFAULT_CONCURRENCY",
|
|
14629
|
-
DEFAULT_PARALLEL_AGENT.defaultConcurrency
|
|
14630
|
-
),
|
|
14631
|
-
maxConcurrency: this.parseEnvInt(
|
|
14632
|
-
"OPENCODE_MAX_CONCURRENCY",
|
|
14633
|
-
DEFAULT_PARALLEL_AGENT.maxConcurrency
|
|
14634
|
-
),
|
|
14635
|
-
enableDebug: process.env.OPENCODE_DEBUG_PARALLEL === "true",
|
|
14636
|
-
enableDetailedLogs: process.env.OPENCODE_DETAILED_LOGS === "true"
|
|
14637
|
-
};
|
|
14638
|
-
}
|
|
14639
|
-
getParallelAgentConfig() {
|
|
14640
|
-
return this.parallelAgentConfig;
|
|
14641
|
-
}
|
|
14642
|
-
// ------------------------------------------------------------------------
|
|
14643
|
-
// Background Task Config
|
|
14644
|
-
// ------------------------------------------------------------------------
|
|
14645
|
-
loadBackgroundTaskConfig() {
|
|
14646
|
-
return {
|
|
14647
|
-
...DEFAULT_BACKGROUND_TASK,
|
|
14648
|
-
monitorIntervalMs: this.parseEnvInt(
|
|
14649
|
-
"OPENCODE_MONITOR_INTERVAL_MS",
|
|
14650
|
-
DEFAULT_BACKGROUND_TASK.monitorIntervalMs
|
|
14651
|
-
),
|
|
14652
|
-
defaultTimeoutMs: this.parseEnvInt(
|
|
14653
|
-
"OPENCODE_DEFAULT_TIMEOUT_MS",
|
|
14654
|
-
DEFAULT_BACKGROUND_TASK.defaultTimeoutMs
|
|
14655
|
-
),
|
|
14656
|
-
maxCompletedTasksToKeep: this.parseEnvInt(
|
|
14657
|
-
"OPENCODE_MAX_COMPLETED_TASKS",
|
|
14658
|
-
DEFAULT_BACKGROUND_TASK.maxCompletedTasksToKeep
|
|
14659
|
-
),
|
|
14660
|
-
enableDebug: process.env.OPENCODE_DEBUG_BACKGROUND === "true"
|
|
14661
|
-
};
|
|
14662
|
-
}
|
|
14663
|
-
getBackgroundTaskConfig() {
|
|
14664
|
-
return this.backgroundTaskConfig;
|
|
14665
|
-
}
|
|
14666
|
-
// ------------------------------------------------------------------------
|
|
14667
|
-
// Session Config
|
|
14668
|
-
// ------------------------------------------------------------------------
|
|
14669
|
-
loadSessionConfig() {
|
|
14670
|
-
return {
|
|
14671
|
-
...DEFAULT_SESSION,
|
|
14672
|
-
defaultMaxSteps: this.parseEnvInt(
|
|
14673
|
-
"OPENCODE_DEFAULT_MAX_STEPS",
|
|
14674
|
-
DEFAULT_SESSION.defaultMaxSteps
|
|
14675
|
-
),
|
|
14676
|
-
taskCommandMaxSteps: this.parseEnvInt(
|
|
14677
|
-
"OPENCODE_TASK_MAX_STEPS",
|
|
14678
|
-
DEFAULT_SESSION.taskCommandMaxSteps
|
|
14679
|
-
)
|
|
14680
|
-
};
|
|
13610
|
+
var ConcurrencyController = class {
|
|
13611
|
+
counts = /* @__PURE__ */ new Map();
|
|
13612
|
+
queues = /* @__PURE__ */ new Map();
|
|
13613
|
+
limits = /* @__PURE__ */ new Map();
|
|
13614
|
+
config;
|
|
13615
|
+
constructor(config2) {
|
|
13616
|
+
this.config = config2 ?? {};
|
|
14681
13617
|
}
|
|
14682
|
-
|
|
14683
|
-
|
|
13618
|
+
setLimit(key, limit) {
|
|
13619
|
+
this.limits.set(key, limit);
|
|
14684
13620
|
}
|
|
14685
|
-
// ------------------------------------------------------------------------
|
|
14686
|
-
// Runtime Updates (Dynamic Configuration)
|
|
14687
|
-
// ------------------------------------------------------------------------
|
|
14688
13621
|
/**
|
|
14689
|
-
*
|
|
14690
|
-
*
|
|
13622
|
+
* Get concurrency limit for a key.
|
|
13623
|
+
* Priority: explicit limit > model > provider > agent > default
|
|
14691
13624
|
*/
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14695
|
-
|
|
14696
|
-
};
|
|
14697
|
-
this.validateParallelAgentConfig();
|
|
14698
|
-
}
|
|
14699
|
-
updateBackgroundTaskConfig(updates) {
|
|
14700
|
-
this.backgroundTaskConfig = {
|
|
14701
|
-
...this.backgroundTaskConfig,
|
|
14702
|
-
...updates
|
|
14703
|
-
};
|
|
14704
|
-
this.validateBackgroundTaskConfig();
|
|
14705
|
-
}
|
|
14706
|
-
updateSessionConfig(updates) {
|
|
14707
|
-
this.sessionConfig = {
|
|
14708
|
-
...this.sessionConfig,
|
|
14709
|
-
...updates
|
|
14710
|
-
};
|
|
14711
|
-
this.validateSessionConfig();
|
|
14712
|
-
}
|
|
14713
|
-
// ------------------------------------------------------------------------
|
|
14714
|
-
// Validation
|
|
14715
|
-
// ------------------------------------------------------------------------
|
|
14716
|
-
validateParallelAgentConfig() {
|
|
14717
|
-
const { taskTtlMs, cleanupDelayMs, minStabilityMs, pollIntervalMs, defaultConcurrency, maxConcurrency } = this.parallelAgentConfig;
|
|
14718
|
-
if (taskTtlMs < 60 * 1e3) {
|
|
14719
|
-
console.warn("[Config] TASK_TTL_MS too low (< 1min), using 1min minimum");
|
|
14720
|
-
this.parallelAgentConfig.taskTtlMs = 60 * 1e3;
|
|
14721
|
-
}
|
|
14722
|
-
if (cleanupDelayMs > taskTtlMs) {
|
|
14723
|
-
console.warn("[Config] CLEANUP_DELAY_MS cannot exceed TASK_TTL_MS");
|
|
14724
|
-
this.parallelAgentConfig.cleanupDelayMs = Math.floor(taskTtlMs / 2);
|
|
14725
|
-
}
|
|
14726
|
-
if (minStabilityMs < 1e3) {
|
|
14727
|
-
console.warn("[Config] MIN_STABILITY_MS too low (< 1s), using 1s minimum");
|
|
14728
|
-
this.parallelAgentConfig.minStabilityMs = 1e3;
|
|
14729
|
-
}
|
|
14730
|
-
if (pollIntervalMs < 500) {
|
|
14731
|
-
console.warn("[Config] POLL_INTERVAL_MS too low (< 500ms), using 500ms minimum");
|
|
14732
|
-
this.parallelAgentConfig.pollIntervalMs = 500;
|
|
14733
|
-
}
|
|
14734
|
-
if (defaultConcurrency < 1 || defaultConcurrency > maxConcurrency) {
|
|
14735
|
-
console.warn(`[Config] DEFAULT_CONCURRENCY must be 1-${maxConcurrency}`);
|
|
14736
|
-
this.parallelAgentConfig.defaultConcurrency = Math.min(
|
|
14737
|
-
Math.max(defaultConcurrency, 1),
|
|
14738
|
-
maxConcurrency
|
|
14739
|
-
);
|
|
14740
|
-
}
|
|
14741
|
-
}
|
|
14742
|
-
validateBackgroundTaskConfig() {
|
|
14743
|
-
const { monitorIntervalMs, defaultTimeoutMs, maxCompletedTasksToKeep } = this.backgroundTaskConfig;
|
|
14744
|
-
if (monitorIntervalMs < 1e3) {
|
|
14745
|
-
console.warn("[Config] MONITOR_INTERVAL_MS too low (< 1s), using 1s minimum");
|
|
14746
|
-
this.backgroundTaskConfig.monitorIntervalMs = 1e3;
|
|
14747
|
-
}
|
|
14748
|
-
if (defaultTimeoutMs < 10 * 1e3) {
|
|
14749
|
-
console.warn("[Config] DEFAULT_TIMEOUT_MS too low (< 10s), using 10s minimum");
|
|
14750
|
-
this.backgroundTaskConfig.defaultTimeoutMs = 10 * 1e3;
|
|
13625
|
+
getConcurrencyLimit(key) {
|
|
13626
|
+
const explicitLimit = this.limits.get(key);
|
|
13627
|
+
if (explicitLimit !== void 0) {
|
|
13628
|
+
return explicitLimit === 0 ? Infinity : explicitLimit;
|
|
14751
13629
|
}
|
|
14752
|
-
if (
|
|
14753
|
-
|
|
14754
|
-
|
|
13630
|
+
if (this.config.modelConcurrency?.[key] !== void 0) {
|
|
13631
|
+
const limit = this.config.modelConcurrency[key];
|
|
13632
|
+
return limit === 0 ? Infinity : limit;
|
|
14755
13633
|
}
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
console.warn("[Config] DEFAULT_MAX_STEPS must be >= 1, using 1");
|
|
14761
|
-
this.sessionConfig.defaultMaxSteps = 1;
|
|
13634
|
+
const provider = key.split("/")[0];
|
|
13635
|
+
if (this.config.providerConcurrency?.[provider] !== void 0) {
|
|
13636
|
+
const limit = this.config.providerConcurrency[provider];
|
|
13637
|
+
return limit === 0 ? Infinity : limit;
|
|
14762
13638
|
}
|
|
14763
|
-
if (
|
|
14764
|
-
|
|
14765
|
-
|
|
13639
|
+
if (this.config.agentConcurrency?.[key] !== void 0) {
|
|
13640
|
+
const limit = this.config.agentConcurrency[key];
|
|
13641
|
+
return limit === 0 ? Infinity : limit;
|
|
14766
13642
|
}
|
|
13643
|
+
return this.config.defaultConcurrency ?? PARALLEL_TASK.DEFAULT_CONCURRENCY;
|
|
14767
13644
|
}
|
|
14768
|
-
//
|
|
14769
|
-
// Helpers
|
|
14770
|
-
// ------------------------------------------------------------------------
|
|
14771
|
-
parseEnvInt(key, defaultValue) {
|
|
14772
|
-
const value = process.env[key];
|
|
14773
|
-
if (value === void 0) return defaultValue;
|
|
14774
|
-
const parsed = parseInt(value, 10);
|
|
14775
|
-
if (isNaN(parsed)) {
|
|
14776
|
-
console.warn(`[Config] Invalid ${key} value: "${value}", using default ${defaultValue}`);
|
|
14777
|
-
return defaultValue;
|
|
14778
|
-
}
|
|
14779
|
-
return parsed;
|
|
14780
|
-
}
|
|
14781
|
-
// ------------------------------------------------------------------------
|
|
14782
|
-
// Export / Debug
|
|
14783
|
-
// ------------------------------------------------------------------------
|
|
14784
|
-
exportConfigs() {
|
|
14785
|
-
console.log("\n\u{1F4CB} Current Configuration:\n");
|
|
14786
|
-
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
14787
|
-
console.log("Parallel Agent:");
|
|
14788
|
-
console.log(` Task TTL: ${this.parallelAgentConfig.taskTtlMs / 1e3}s`);
|
|
14789
|
-
console.log(` Cleanup Delay: ${this.parallelAgentConfig.cleanupDelayMs / 1e3}s`);
|
|
14790
|
-
console.log(` Min Stability: ${this.parallelAgentConfig.minStabilityMs / 1e3}s`);
|
|
14791
|
-
console.log(` Poll Interval: ${this.parallelAgentConfig.pollIntervalMs / 1e3}s`);
|
|
14792
|
-
console.log(` Default Concurrency: ${this.parallelAgentConfig.defaultConcurrency}`);
|
|
14793
|
-
console.log(` Max Concurrency: ${this.parallelAgentConfig.maxConcurrency}`);
|
|
14794
|
-
console.log(` Debug: ${this.parallelAgentConfig.enableDebug}`);
|
|
14795
|
-
console.log("");
|
|
14796
|
-
console.log("Background Task:");
|
|
14797
|
-
console.log(` Monitor Interval: ${this.backgroundTaskConfig.monitorIntervalMs / 1e3}s`);
|
|
14798
|
-
console.log(` Default Timeout: ${this.backgroundTaskConfig.defaultTimeoutMs / 1e3}s`);
|
|
14799
|
-
console.log(` Max Completed Tasks: ${this.backgroundTaskConfig.maxCompletedTasksToKeep}`);
|
|
14800
|
-
console.log(` Storage Dir: ${this.backgroundTaskConfig.storageDir}`);
|
|
14801
|
-
console.log(` Debug: ${this.backgroundTaskConfig.enableDebug}`);
|
|
14802
|
-
console.log("");
|
|
14803
|
-
console.log("Session:");
|
|
14804
|
-
console.log(` Default Max Steps: ${this.sessionConfig.defaultMaxSteps}`);
|
|
14805
|
-
console.log(` Task Max Steps: ${this.sessionConfig.taskCommandMaxSteps}`);
|
|
14806
|
-
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
14807
|
-
}
|
|
14808
|
-
};
|
|
14809
|
-
var configManager = ConfigManager.getInstance();
|
|
14810
|
-
|
|
14811
|
-
// src/core/async-agent.ts
|
|
14812
|
-
var TASK_TTL_MS2 = configManager.getParallelAgentConfig().taskTtlMs;
|
|
14813
|
-
var CLEANUP_DELAY_MS2 = configManager.getParallelAgentConfig().cleanupDelayMs;
|
|
14814
|
-
var MIN_STABILITY_MS2 = configManager.getParallelAgentConfig().minStabilityMs;
|
|
14815
|
-
var POLL_INTERVAL_MS = configManager.getParallelAgentConfig().pollIntervalMs;
|
|
14816
|
-
var DEFAULT_CONCURRENCY = configManager.getParallelAgentConfig().defaultConcurrency;
|
|
14817
|
-
var MAX_CONCURRENCY = configManager.getParallelAgentConfig().maxConcurrency;
|
|
14818
|
-
var DEBUG = configManager.getParallelAgentConfig().enableDebug;
|
|
14819
|
-
var log = (...args) => {
|
|
14820
|
-
if (DEBUG) console.log("[parallel-agent]", ...args);
|
|
14821
|
-
};
|
|
14822
|
-
var ConcurrencyController = class {
|
|
14823
|
-
counts = /* @__PURE__ */ new Map();
|
|
14824
|
-
queues = /* @__PURE__ */ new Map();
|
|
14825
|
-
limits = /* @__PURE__ */ new Map();
|
|
14826
|
-
setLimit(key, limit) {
|
|
14827
|
-
const cappedLimit = Math.min(limit, MAX_CONCURRENCY);
|
|
14828
|
-
this.limits.set(key, cappedLimit);
|
|
14829
|
-
log(`Set limit for ${key}: ${cappedLimit}`);
|
|
14830
|
-
}
|
|
13645
|
+
// Backwards compatible alias
|
|
14831
13646
|
getLimit(key) {
|
|
14832
|
-
return this.
|
|
13647
|
+
return this.getConcurrencyLimit(key);
|
|
14833
13648
|
}
|
|
14834
13649
|
async acquire(key) {
|
|
14835
|
-
const limit = this.
|
|
14836
|
-
if (limit ===
|
|
13650
|
+
const limit = this.getConcurrencyLimit(key);
|
|
13651
|
+
if (limit === Infinity) return;
|
|
14837
13652
|
const current = this.counts.get(key) ?? 0;
|
|
14838
13653
|
if (current < limit) {
|
|
14839
13654
|
this.counts.set(key, current + 1);
|
|
14840
|
-
log(`Acquired
|
|
13655
|
+
log(`Acquired ${key}: ${current + 1}/${limit}`);
|
|
14841
13656
|
return;
|
|
14842
13657
|
}
|
|
14843
|
-
log(`Queueing
|
|
13658
|
+
log(`Queueing ${key}: ${current}/${limit}`);
|
|
14844
13659
|
return new Promise((resolve) => {
|
|
14845
13660
|
const queue = this.queues.get(key) ?? [];
|
|
14846
13661
|
queue.push(resolve);
|
|
@@ -14848,97 +13663,196 @@ var ConcurrencyController = class {
|
|
|
14848
13663
|
});
|
|
14849
13664
|
}
|
|
14850
13665
|
release(key) {
|
|
14851
|
-
const limit = this.
|
|
14852
|
-
if (limit ===
|
|
13666
|
+
const limit = this.getConcurrencyLimit(key);
|
|
13667
|
+
if (limit === Infinity) return;
|
|
14853
13668
|
const queue = this.queues.get(key);
|
|
14854
13669
|
if (queue && queue.length > 0) {
|
|
14855
13670
|
const next = queue.shift();
|
|
14856
|
-
log(`Released
|
|
13671
|
+
log(`Released ${key}: next in queue`);
|
|
14857
13672
|
next();
|
|
14858
13673
|
} else {
|
|
14859
13674
|
const current = this.counts.get(key) ?? 0;
|
|
14860
13675
|
if (current > 0) {
|
|
14861
13676
|
this.counts.set(key, current - 1);
|
|
14862
|
-
log(`Released
|
|
13677
|
+
log(`Released ${key}: ${current - 1}/${limit}`);
|
|
14863
13678
|
}
|
|
14864
13679
|
}
|
|
14865
13680
|
}
|
|
14866
13681
|
getQueueLength(key) {
|
|
14867
13682
|
return this.queues.get(key)?.length ?? 0;
|
|
14868
13683
|
}
|
|
14869
|
-
|
|
14870
|
-
this.
|
|
14871
|
-
log(`Updated concurrency for ${agentType}: ${newLimit}`);
|
|
13684
|
+
getActiveCount(key) {
|
|
13685
|
+
return this.counts.get(key) ?? 0;
|
|
14872
13686
|
}
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
}
|
|
14882
|
-
return stats;
|
|
13687
|
+
/**
|
|
13688
|
+
* Get formatted concurrency info string (e.g., "2/5 slots")
|
|
13689
|
+
*/
|
|
13690
|
+
getConcurrencyInfo(key) {
|
|
13691
|
+
const active = this.getActiveCount(key);
|
|
13692
|
+
const limit = this.getConcurrencyLimit(key);
|
|
13693
|
+
if (limit === Infinity) return "";
|
|
13694
|
+
return ` (${active}/${limit} slots)`;
|
|
14883
13695
|
}
|
|
14884
13696
|
};
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
13697
|
+
|
|
13698
|
+
// src/core/agents/task-store.ts
|
|
13699
|
+
var TaskStore = class {
|
|
14888
13700
|
tasks = /* @__PURE__ */ new Map();
|
|
14889
13701
|
pendingByParent = /* @__PURE__ */ new Map();
|
|
14890
13702
|
notifications = /* @__PURE__ */ new Map();
|
|
14891
|
-
|
|
13703
|
+
set(id, task) {
|
|
13704
|
+
this.tasks.set(id, task);
|
|
13705
|
+
}
|
|
13706
|
+
get(id) {
|
|
13707
|
+
return this.tasks.get(id);
|
|
13708
|
+
}
|
|
13709
|
+
getAll() {
|
|
13710
|
+
return Array.from(this.tasks.values());
|
|
13711
|
+
}
|
|
13712
|
+
getRunning() {
|
|
13713
|
+
return this.getAll().filter((t) => t.status === "running");
|
|
13714
|
+
}
|
|
13715
|
+
getByParent(parentSessionID) {
|
|
13716
|
+
return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
|
|
13717
|
+
}
|
|
13718
|
+
delete(id) {
|
|
13719
|
+
return this.tasks.delete(id);
|
|
13720
|
+
}
|
|
13721
|
+
clear() {
|
|
13722
|
+
this.tasks.clear();
|
|
13723
|
+
this.pendingByParent.clear();
|
|
13724
|
+
this.notifications.clear();
|
|
13725
|
+
}
|
|
13726
|
+
// Pending tracking
|
|
13727
|
+
trackPending(parentSessionID, taskId) {
|
|
13728
|
+
const pending = this.pendingByParent.get(parentSessionID) ?? /* @__PURE__ */ new Set();
|
|
13729
|
+
pending.add(taskId);
|
|
13730
|
+
this.pendingByParent.set(parentSessionID, pending);
|
|
13731
|
+
}
|
|
13732
|
+
untrackPending(parentSessionID, taskId) {
|
|
13733
|
+
const pending = this.pendingByParent.get(parentSessionID);
|
|
13734
|
+
if (pending) {
|
|
13735
|
+
pending.delete(taskId);
|
|
13736
|
+
if (pending.size === 0) {
|
|
13737
|
+
this.pendingByParent.delete(parentSessionID);
|
|
13738
|
+
}
|
|
13739
|
+
}
|
|
13740
|
+
}
|
|
13741
|
+
getPendingCount(parentSessionID) {
|
|
13742
|
+
return this.pendingByParent.get(parentSessionID)?.size ?? 0;
|
|
13743
|
+
}
|
|
13744
|
+
hasPending(parentSessionID) {
|
|
13745
|
+
return this.getPendingCount(parentSessionID) > 0;
|
|
13746
|
+
}
|
|
13747
|
+
// Notifications
|
|
13748
|
+
queueNotification(task) {
|
|
13749
|
+
const queue = this.notifications.get(task.parentSessionID) ?? [];
|
|
13750
|
+
queue.push(task);
|
|
13751
|
+
this.notifications.set(task.parentSessionID, queue);
|
|
13752
|
+
}
|
|
13753
|
+
getNotifications(parentSessionID) {
|
|
13754
|
+
return this.notifications.get(parentSessionID) ?? [];
|
|
13755
|
+
}
|
|
13756
|
+
clearNotifications(parentSessionID) {
|
|
13757
|
+
this.notifications.delete(parentSessionID);
|
|
13758
|
+
}
|
|
13759
|
+
cleanEmptyNotifications() {
|
|
13760
|
+
for (const [sessionID, queue] of this.notifications.entries()) {
|
|
13761
|
+
if (queue.length === 0) {
|
|
13762
|
+
this.notifications.delete(sessionID);
|
|
13763
|
+
}
|
|
13764
|
+
}
|
|
13765
|
+
}
|
|
13766
|
+
/**
|
|
13767
|
+
* Remove a specific task from all notification queues
|
|
13768
|
+
*/
|
|
13769
|
+
clearNotificationsForTask(taskId) {
|
|
13770
|
+
for (const [sessionID, tasks] of this.notifications.entries()) {
|
|
13771
|
+
const filtered = tasks.filter((t) => t.id !== taskId);
|
|
13772
|
+
if (filtered.length === 0) {
|
|
13773
|
+
this.notifications.delete(sessionID);
|
|
13774
|
+
} else if (filtered.length !== tasks.length) {
|
|
13775
|
+
this.notifications.set(sessionID, filtered);
|
|
13776
|
+
}
|
|
13777
|
+
}
|
|
13778
|
+
}
|
|
13779
|
+
};
|
|
13780
|
+
|
|
13781
|
+
// src/core/agents/config.ts
|
|
13782
|
+
var CONFIG = {
|
|
13783
|
+
TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
|
|
13784
|
+
CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
|
|
13785
|
+
MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
|
|
13786
|
+
POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
|
|
13787
|
+
};
|
|
13788
|
+
|
|
13789
|
+
// src/core/agents/logger.ts
|
|
13790
|
+
var DEBUG2 = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
13791
|
+
function log2(...args) {
|
|
13792
|
+
if (DEBUG2) console.log("[parallel-agent]", ...args);
|
|
13793
|
+
}
|
|
13794
|
+
|
|
13795
|
+
// src/core/agents/format.ts
|
|
13796
|
+
function formatDuration(start, end) {
|
|
13797
|
+
const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
|
|
13798
|
+
const seconds = Math.floor(duration3 / 1e3);
|
|
13799
|
+
const minutes = Math.floor(seconds / 60);
|
|
13800
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
13801
|
+
return `${seconds}s`;
|
|
13802
|
+
}
|
|
13803
|
+
function buildNotificationMessage(tasks) {
|
|
13804
|
+
const summary = tasks.map((t) => {
|
|
13805
|
+
const status = t.status === "completed" ? "\u2705" : "\u274C";
|
|
13806
|
+
return `${status} \`${t.id}\`: ${t.description}`;
|
|
13807
|
+
}).join("\n");
|
|
13808
|
+
return `<system-notification>
|
|
13809
|
+
**All Parallel Tasks Complete**
|
|
13810
|
+
|
|
13811
|
+
${summary}
|
|
13812
|
+
|
|
13813
|
+
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
13814
|
+
</system-notification>`;
|
|
13815
|
+
}
|
|
13816
|
+
|
|
13817
|
+
// src/core/agents/manager.ts
|
|
13818
|
+
var ParallelAgentManager = class _ParallelAgentManager {
|
|
13819
|
+
static _instance;
|
|
13820
|
+
store = new TaskStore();
|
|
14892
13821
|
client;
|
|
14893
13822
|
directory;
|
|
14894
|
-
concurrency;
|
|
14895
|
-
// Polling
|
|
13823
|
+
concurrency = new ConcurrencyController();
|
|
14896
13824
|
pollingInterval;
|
|
14897
13825
|
constructor(client, directory) {
|
|
14898
13826
|
this.client = client;
|
|
14899
13827
|
this.directory = directory;
|
|
14900
|
-
this.concurrency = new ConcurrencyController();
|
|
14901
13828
|
}
|
|
14902
13829
|
static getInstance(client, directory) {
|
|
14903
13830
|
if (!_ParallelAgentManager._instance) {
|
|
14904
13831
|
if (!client || !directory) {
|
|
14905
|
-
throw new Error(
|
|
14906
|
-
"ParallelAgentManager requires client and directory on first call"
|
|
14907
|
-
);
|
|
13832
|
+
throw new Error("ParallelAgentManager requires client and directory on first call");
|
|
14908
13833
|
}
|
|
14909
|
-
_ParallelAgentManager._instance = new _ParallelAgentManager(
|
|
14910
|
-
client,
|
|
14911
|
-
directory
|
|
14912
|
-
);
|
|
13834
|
+
_ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
|
|
14913
13835
|
}
|
|
14914
13836
|
return _ParallelAgentManager._instance;
|
|
14915
13837
|
}
|
|
14916
13838
|
// ========================================================================
|
|
14917
13839
|
// Public API
|
|
14918
13840
|
// ========================================================================
|
|
14919
|
-
/**
|
|
14920
|
-
* Launch an agent in a new session (async, non-blocking)
|
|
14921
|
-
*/
|
|
14922
13841
|
async launch(input) {
|
|
14923
13842
|
const concurrencyKey = input.agent;
|
|
14924
13843
|
await this.concurrency.acquire(concurrencyKey);
|
|
14925
13844
|
this.pruneExpiredTasks();
|
|
14926
13845
|
try {
|
|
14927
13846
|
const createResult = await this.client.session.create({
|
|
14928
|
-
body: {
|
|
14929
|
-
|
|
14930
|
-
title: `Parallel: ${input.description}`
|
|
14931
|
-
},
|
|
14932
|
-
query: {
|
|
14933
|
-
directory: this.directory
|
|
14934
|
-
}
|
|
13847
|
+
body: { parentID: input.parentSessionID, title: `Parallel: ${input.description}` },
|
|
13848
|
+
query: { directory: this.directory }
|
|
14935
13849
|
});
|
|
14936
13850
|
if (createResult.error) {
|
|
14937
13851
|
this.concurrency.release(concurrencyKey);
|
|
14938
13852
|
throw new Error(`Failed to create session: ${createResult.error}`);
|
|
14939
13853
|
}
|
|
14940
13854
|
const sessionID = createResult.data.id;
|
|
14941
|
-
const taskId =
|
|
13855
|
+
const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
|
|
14942
13856
|
const task = {
|
|
14943
13857
|
id: taskId,
|
|
14944
13858
|
sessionID,
|
|
@@ -14949,191 +13863,172 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14949
13863
|
startedAt: /* @__PURE__ */ new Date(),
|
|
14950
13864
|
concurrencyKey
|
|
14951
13865
|
};
|
|
14952
|
-
this.
|
|
14953
|
-
this.trackPending(input.parentSessionID, taskId);
|
|
13866
|
+
this.store.set(taskId, task);
|
|
13867
|
+
this.store.trackPending(input.parentSessionID, taskId);
|
|
14954
13868
|
this.startPolling();
|
|
14955
13869
|
this.client.session.prompt({
|
|
14956
13870
|
path: { id: sessionID },
|
|
14957
|
-
body: {
|
|
14958
|
-
agent: input.agent,
|
|
14959
|
-
parts: [{ type: "text", text: input.prompt }]
|
|
14960
|
-
}
|
|
13871
|
+
body: { agent: input.agent, parts: [{ type: "text", text: input.prompt }] }
|
|
14961
13872
|
}).catch((error45) => {
|
|
14962
|
-
|
|
13873
|
+
log2(`Prompt error for ${taskId}:`, error45);
|
|
14963
13874
|
this.handleTaskError(taskId, error45);
|
|
14964
13875
|
});
|
|
14965
|
-
|
|
13876
|
+
log2(`Launched ${taskId} in session ${sessionID}`);
|
|
14966
13877
|
return task;
|
|
14967
13878
|
} catch (error45) {
|
|
14968
13879
|
this.concurrency.release(concurrencyKey);
|
|
14969
13880
|
throw error45;
|
|
14970
13881
|
}
|
|
14971
13882
|
}
|
|
14972
|
-
/**
|
|
14973
|
-
* Get task by ID
|
|
14974
|
-
*/
|
|
14975
13883
|
getTask(id) {
|
|
14976
|
-
return this.
|
|
13884
|
+
return this.store.get(id);
|
|
14977
13885
|
}
|
|
14978
|
-
/**
|
|
14979
|
-
* Get all running tasks
|
|
14980
|
-
*/
|
|
14981
13886
|
getRunningTasks() {
|
|
14982
|
-
return
|
|
14983
|
-
(t) => t.status === "running"
|
|
14984
|
-
);
|
|
13887
|
+
return this.store.getRunning();
|
|
14985
13888
|
}
|
|
14986
|
-
/**
|
|
14987
|
-
* Get all tasks
|
|
14988
|
-
*/
|
|
14989
13889
|
getAllTasks() {
|
|
14990
|
-
return
|
|
13890
|
+
return this.store.getAll();
|
|
14991
13891
|
}
|
|
14992
|
-
/**
|
|
14993
|
-
* Get tasks by parent session
|
|
14994
|
-
*/
|
|
14995
13892
|
getTasksByParent(parentSessionID) {
|
|
14996
|
-
return
|
|
14997
|
-
(t) => t.parentSessionID === parentSessionID
|
|
14998
|
-
);
|
|
13893
|
+
return this.store.getByParent(parentSessionID);
|
|
14999
13894
|
}
|
|
15000
|
-
/**
|
|
15001
|
-
* Cancel a running task
|
|
15002
|
-
*/
|
|
15003
13895
|
async cancelTask(taskId) {
|
|
15004
|
-
const task = this.
|
|
15005
|
-
if (!task || task.status !== "running")
|
|
15006
|
-
return false;
|
|
15007
|
-
}
|
|
13896
|
+
const task = this.store.get(taskId);
|
|
13897
|
+
if (!task || task.status !== "running") return false;
|
|
15008
13898
|
task.status = "error";
|
|
15009
13899
|
task.error = "Cancelled by user";
|
|
15010
13900
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
15011
|
-
if (task.concurrencyKey)
|
|
15012
|
-
|
|
15013
|
-
}
|
|
15014
|
-
this.untrackPending(task.parentSessionID, taskId);
|
|
13901
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
13902
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
15015
13903
|
try {
|
|
15016
|
-
await this.client.session.delete({
|
|
15017
|
-
|
|
15018
|
-
});
|
|
15019
|
-
console.log(
|
|
15020
|
-
`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... deleted`
|
|
15021
|
-
);
|
|
13904
|
+
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
13905
|
+
log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
15022
13906
|
} catch {
|
|
15023
|
-
|
|
15024
|
-
`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... already gone`
|
|
15025
|
-
);
|
|
13907
|
+
log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
15026
13908
|
}
|
|
15027
13909
|
this.scheduleCleanup(taskId);
|
|
15028
|
-
|
|
15029
|
-
log(`Cancelled ${taskId}`);
|
|
13910
|
+
log2(`Cancelled ${taskId}`);
|
|
15030
13911
|
return true;
|
|
15031
13912
|
}
|
|
15032
|
-
/**
|
|
15033
|
-
* Get result from completed task
|
|
15034
|
-
*/
|
|
15035
13913
|
async getResult(taskId) {
|
|
15036
|
-
const task = this.
|
|
13914
|
+
const task = this.store.get(taskId);
|
|
15037
13915
|
if (!task) return null;
|
|
15038
13916
|
if (task.result) return task.result;
|
|
15039
13917
|
if (task.status === "error") return `Error: ${task.error}`;
|
|
15040
13918
|
if (task.status === "running") return null;
|
|
15041
13919
|
try {
|
|
15042
|
-
const
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
return `Error: ${messagesResult.error}`;
|
|
15047
|
-
}
|
|
15048
|
-
const messages = messagesResult.data ?? [];
|
|
15049
|
-
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant").reverse();
|
|
15050
|
-
const lastMsg = assistantMsgs[0];
|
|
13920
|
+
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
13921
|
+
if (result.error) return `Error: ${result.error}`;
|
|
13922
|
+
const messages = result.data ?? [];
|
|
13923
|
+
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
15051
13924
|
if (!lastMsg) return "(No response)";
|
|
15052
|
-
const
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
const result = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
|
|
15056
|
-
task.result = result;
|
|
15057
|
-
return result;
|
|
13925
|
+
const text = lastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
|
|
13926
|
+
task.result = text;
|
|
13927
|
+
return text;
|
|
15058
13928
|
} catch (error45) {
|
|
15059
13929
|
return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
15060
13930
|
}
|
|
15061
13931
|
}
|
|
15062
|
-
/**
|
|
15063
|
-
* Set concurrency limit for agent type
|
|
15064
|
-
*/
|
|
15065
13932
|
setConcurrencyLimit(agentType, limit) {
|
|
15066
13933
|
this.concurrency.setLimit(agentType, limit);
|
|
15067
13934
|
}
|
|
15068
|
-
/**
|
|
15069
|
-
* Get concurrency statistics for all agent types
|
|
15070
|
-
*/
|
|
15071
|
-
getConcurrencyStats() {
|
|
15072
|
-
return this.concurrency.getStats();
|
|
15073
|
-
}
|
|
15074
|
-
/**
|
|
15075
|
-
* Update concurrency limit dynamically at runtime
|
|
15076
|
-
*/
|
|
15077
|
-
updateConcurrency(agentType, newLimit) {
|
|
15078
|
-
this.concurrency.updateConcurrency(agentType, newLimit);
|
|
15079
|
-
}
|
|
15080
|
-
/**
|
|
15081
|
-
* Get pending notification count
|
|
15082
|
-
*/
|
|
15083
13935
|
getPendingCount(parentSessionID) {
|
|
15084
|
-
return this.
|
|
13936
|
+
return this.store.getPendingCount(parentSessionID);
|
|
15085
13937
|
}
|
|
15086
|
-
/**
|
|
15087
|
-
* Cleanup all state
|
|
15088
|
-
*/
|
|
15089
13938
|
cleanup() {
|
|
15090
13939
|
this.stopPolling();
|
|
15091
|
-
this.
|
|
15092
|
-
this.pendingByParent.clear();
|
|
15093
|
-
this.notifications.clear();
|
|
13940
|
+
this.store.clear();
|
|
15094
13941
|
}
|
|
13942
|
+
formatDuration = formatDuration;
|
|
15095
13943
|
// ========================================================================
|
|
15096
|
-
//
|
|
13944
|
+
// Event Handling (from OpenCode hooks)
|
|
15097
13945
|
// ========================================================================
|
|
15098
|
-
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
13946
|
+
/**
|
|
13947
|
+
* Handle OpenCode session events for proper resource cleanup.
|
|
13948
|
+
* Call this from your plugin's event hook.
|
|
13949
|
+
*/
|
|
13950
|
+
handleEvent(event) {
|
|
13951
|
+
const props = event.properties;
|
|
13952
|
+
if (event.type === "session.idle") {
|
|
13953
|
+
const sessionID = props?.sessionID;
|
|
13954
|
+
if (!sessionID) return;
|
|
13955
|
+
const task = this.findBySession(sessionID);
|
|
13956
|
+
if (!task || task.status !== "running") return;
|
|
13957
|
+
this.handleSessionIdle(task).catch((err) => {
|
|
13958
|
+
log2("Error handling session.idle:", err);
|
|
13959
|
+
});
|
|
13960
|
+
}
|
|
13961
|
+
if (event.type === "session.deleted") {
|
|
13962
|
+
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
13963
|
+
if (!sessionID) return;
|
|
13964
|
+
const task = this.findBySession(sessionID);
|
|
13965
|
+
if (!task) return;
|
|
13966
|
+
log2(`Session deleted event for task ${task.id}`);
|
|
13967
|
+
if (task.status === "running") {
|
|
13968
|
+
task.status = "error";
|
|
13969
|
+
task.error = "Session deleted";
|
|
13970
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
15109
13971
|
}
|
|
13972
|
+
if (task.concurrencyKey) {
|
|
13973
|
+
this.concurrency.release(task.concurrencyKey);
|
|
13974
|
+
task.concurrencyKey = void 0;
|
|
13975
|
+
}
|
|
13976
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
13977
|
+
this.store.clearNotificationsForTask(task.id);
|
|
13978
|
+
this.store.delete(task.id);
|
|
13979
|
+
log2(`Cleaned up deleted session task: ${task.id}`);
|
|
15110
13980
|
}
|
|
15111
13981
|
}
|
|
13982
|
+
/**
|
|
13983
|
+
* Find task by session ID
|
|
13984
|
+
*/
|
|
13985
|
+
findBySession(sessionID) {
|
|
13986
|
+
return this.store.getAll().find((t) => t.sessionID === sessionID);
|
|
13987
|
+
}
|
|
13988
|
+
/**
|
|
13989
|
+
* Handle session.idle event - validate and complete task
|
|
13990
|
+
*/
|
|
13991
|
+
async handleSessionIdle(task) {
|
|
13992
|
+
const elapsed = Date.now() - task.startedAt.getTime();
|
|
13993
|
+
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
13994
|
+
log2(`Session idle but too early for ${task.id}, waiting...`);
|
|
13995
|
+
return;
|
|
13996
|
+
}
|
|
13997
|
+
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
13998
|
+
if (!hasOutput) {
|
|
13999
|
+
log2(`Session idle but no output for ${task.id}, waiting...`);
|
|
14000
|
+
return;
|
|
14001
|
+
}
|
|
14002
|
+
task.status = "completed";
|
|
14003
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14004
|
+
if (task.concurrencyKey) {
|
|
14005
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14006
|
+
task.concurrencyKey = void 0;
|
|
14007
|
+
}
|
|
14008
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14009
|
+
this.store.queueNotification(task);
|
|
14010
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14011
|
+
this.scheduleCleanup(task.id);
|
|
14012
|
+
log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14013
|
+
}
|
|
15112
14014
|
// ========================================================================
|
|
15113
|
-
// Internal
|
|
14015
|
+
// Internal
|
|
15114
14016
|
// ========================================================================
|
|
15115
14017
|
handleTaskError(taskId, error45) {
|
|
15116
|
-
const task = this.
|
|
14018
|
+
const task = this.store.get(taskId);
|
|
15117
14019
|
if (!task) return;
|
|
15118
14020
|
task.status = "error";
|
|
15119
14021
|
task.error = error45 instanceof Error ? error45.message : String(error45);
|
|
15120
14022
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
15121
|
-
if (task.concurrencyKey)
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
this.untrackPending(task.parentSessionID, taskId);
|
|
15125
|
-
this.queueNotification(task);
|
|
14023
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
14024
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
14025
|
+
this.store.queueNotification(task);
|
|
15126
14026
|
this.notifyParentIfAllComplete(task.parentSessionID);
|
|
15127
14027
|
this.scheduleCleanup(taskId);
|
|
15128
14028
|
}
|
|
15129
|
-
// ========================================================================
|
|
15130
|
-
// Internal: Polling
|
|
15131
|
-
// ========================================================================
|
|
15132
14029
|
startPolling() {
|
|
15133
14030
|
if (this.pollingInterval) return;
|
|
15134
|
-
this.pollingInterval = setInterval(() =>
|
|
15135
|
-
this.pollRunningTasks();
|
|
15136
|
-
}, POLL_INTERVAL_MS);
|
|
14031
|
+
this.pollingInterval = setInterval(() => this.pollRunningTasks(), CONFIG.POLL_INTERVAL_MS);
|
|
15137
14032
|
this.pollingInterval.unref();
|
|
15138
14033
|
}
|
|
15139
14034
|
stopPolling() {
|
|
@@ -15144,1003 +14039,322 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
15144
14039
|
}
|
|
15145
14040
|
async pollRunningTasks() {
|
|
15146
14041
|
this.pruneExpiredTasks();
|
|
15147
|
-
const
|
|
15148
|
-
if (
|
|
14042
|
+
const running = this.store.getRunning();
|
|
14043
|
+
if (running.length === 0) {
|
|
15149
14044
|
this.stopPolling();
|
|
15150
14045
|
return;
|
|
15151
14046
|
}
|
|
15152
14047
|
try {
|
|
15153
14048
|
const statusResult = await this.client.session.status();
|
|
15154
14049
|
const allStatuses = statusResult.data ?? {};
|
|
15155
|
-
for (const task of
|
|
15156
|
-
|
|
15157
|
-
|
|
14050
|
+
for (const task of running) {
|
|
14051
|
+
try {
|
|
14052
|
+
const sessionStatus = allStatuses[task.sessionID];
|
|
14053
|
+
if (sessionStatus?.type === "idle") {
|
|
14054
|
+
const elapsed2 = Date.now() - task.startedAt.getTime();
|
|
14055
|
+
if (elapsed2 < CONFIG.MIN_STABILITY_MS) continue;
|
|
14056
|
+
if (!await this.validateSessionHasOutput(task.sessionID)) continue;
|
|
14057
|
+
await this.completeTask(task);
|
|
14058
|
+
continue;
|
|
14059
|
+
}
|
|
14060
|
+
await this.updateTaskProgress(task);
|
|
15158
14061
|
const elapsed = Date.now() - task.startedAt.getTime();
|
|
15159
|
-
if (elapsed
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
if (task.concurrencyKey) {
|
|
15165
|
-
this.concurrency.release(task.concurrencyKey);
|
|
14062
|
+
if (elapsed >= CONFIG.MIN_STABILITY_MS && task.stablePolls && task.stablePolls >= 3) {
|
|
14063
|
+
if (await this.validateSessionHasOutput(task.sessionID)) {
|
|
14064
|
+
log2(`Task ${task.id} stable for 3 polls, completing...`);
|
|
14065
|
+
await this.completeTask(task);
|
|
14066
|
+
}
|
|
15166
14067
|
}
|
|
15167
|
-
|
|
15168
|
-
|
|
15169
|
-
this.notifyParentIfAllComplete(task.parentSessionID);
|
|
15170
|
-
this.scheduleCleanup(task.id);
|
|
15171
|
-
const duration3 = this.formatDuration(
|
|
15172
|
-
task.startedAt,
|
|
15173
|
-
task.completedAt
|
|
15174
|
-
);
|
|
15175
|
-
console.log(
|
|
15176
|
-
`[parallel] \u2705 COMPLETED ${task.id} \u2192 ${task.agent}: ${task.description} (${duration3})`
|
|
15177
|
-
);
|
|
15178
|
-
log(`Completed ${task.id}`);
|
|
14068
|
+
} catch (error45) {
|
|
14069
|
+
log2(`Poll error for task ${task.id}:`, error45);
|
|
15179
14070
|
}
|
|
15180
14071
|
}
|
|
15181
14072
|
} catch (error45) {
|
|
15182
|
-
|
|
14073
|
+
log2("Polling error:", error45);
|
|
15183
14074
|
}
|
|
15184
14075
|
}
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
14076
|
+
/**
|
|
14077
|
+
* Update task progress and stability tracking
|
|
14078
|
+
*/
|
|
14079
|
+
async updateTaskProgress(task) {
|
|
14080
|
+
try {
|
|
14081
|
+
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
14082
|
+
if (result.error) return;
|
|
14083
|
+
const messages = result.data ?? [];
|
|
14084
|
+
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant");
|
|
14085
|
+
let toolCalls = 0;
|
|
14086
|
+
let lastTool;
|
|
14087
|
+
let lastMessage;
|
|
14088
|
+
for (const msg of assistantMsgs) {
|
|
14089
|
+
for (const part of msg.parts ?? []) {
|
|
14090
|
+
if (part.type === "tool_use" || part.tool) {
|
|
14091
|
+
toolCalls++;
|
|
14092
|
+
lastTool = part.tool || part.name;
|
|
14093
|
+
}
|
|
14094
|
+
if (part.type === "text" && part.text) {
|
|
14095
|
+
lastMessage = part.text;
|
|
14096
|
+
}
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
task.progress = {
|
|
14100
|
+
toolCalls,
|
|
14101
|
+
lastTool,
|
|
14102
|
+
lastMessage: lastMessage?.slice(0, 100),
|
|
14103
|
+
lastUpdate: /* @__PURE__ */ new Date()
|
|
14104
|
+
};
|
|
14105
|
+
const currentMsgCount = messages.length;
|
|
14106
|
+
if (task.lastMsgCount === currentMsgCount) {
|
|
14107
|
+
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
14108
|
+
} else {
|
|
14109
|
+
task.stablePolls = 0;
|
|
14110
|
+
}
|
|
14111
|
+
task.lastMsgCount = currentMsgCount;
|
|
14112
|
+
} catch {
|
|
14113
|
+
}
|
|
14114
|
+
}
|
|
14115
|
+
/**
|
|
14116
|
+
* Complete a task and cleanup
|
|
14117
|
+
*/
|
|
14118
|
+
async completeTask(task) {
|
|
14119
|
+
task.status = "completed";
|
|
14120
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14121
|
+
if (task.concurrencyKey) {
|
|
14122
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14123
|
+
task.concurrencyKey = void 0;
|
|
14124
|
+
}
|
|
14125
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14126
|
+
this.store.queueNotification(task);
|
|
14127
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14128
|
+
this.scheduleCleanup(task.id);
|
|
14129
|
+
log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14130
|
+
}
|
|
15188
14131
|
async validateSessionHasOutput(sessionID) {
|
|
15189
14132
|
try {
|
|
15190
|
-
const response = await this.client.session.messages({
|
|
15191
|
-
path: { id: sessionID }
|
|
15192
|
-
});
|
|
14133
|
+
const response = await this.client.session.messages({ path: { id: sessionID } });
|
|
15193
14134
|
const messages = response.data ?? [];
|
|
15194
|
-
|
|
15195
|
-
if (m.info?.role !== "assistant") return false;
|
|
15196
|
-
const parts = m.parts ?? [];
|
|
15197
|
-
return parts.some(
|
|
15198
|
-
(p) => p.type === "text" && p.text?.trim() || p.type === "reasoning" && p.text?.trim() || p.type === "tool"
|
|
15199
|
-
);
|
|
15200
|
-
});
|
|
15201
|
-
return hasContent;
|
|
14135
|
+
return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text?.trim() || p.type === "tool"));
|
|
15202
14136
|
} catch {
|
|
15203
14137
|
return true;
|
|
15204
14138
|
}
|
|
15205
14139
|
}
|
|
15206
|
-
// ========================================================================
|
|
15207
|
-
// Internal: Cleanup & TTL
|
|
15208
|
-
// ========================================================================
|
|
15209
14140
|
pruneExpiredTasks() {
|
|
15210
14141
|
const now = Date.now();
|
|
15211
|
-
for (const [taskId, task] of this.
|
|
14142
|
+
for (const [taskId, task] of this.store.getAll().map((t) => [t.id, t])) {
|
|
15212
14143
|
const age = now - task.startedAt.getTime();
|
|
15213
|
-
if (age
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
);
|
|
15226
|
-
}
|
|
15227
|
-
this.client.session.delete({
|
|
15228
|
-
path: { id: task.sessionID }
|
|
15229
|
-
}).then(() => {
|
|
15230
|
-
console.log(
|
|
15231
|
-
`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session deleted)`
|
|
15232
|
-
);
|
|
15233
|
-
}).catch(() => {
|
|
15234
|
-
console.log(
|
|
15235
|
-
`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session already gone)`
|
|
15236
|
-
);
|
|
15237
|
-
});
|
|
15238
|
-
this.tasks.delete(taskId);
|
|
15239
|
-
}
|
|
15240
|
-
}
|
|
15241
|
-
for (const [sessionID, queue] of this.notifications.entries()) {
|
|
15242
|
-
if (queue.length === 0) {
|
|
15243
|
-
this.notifications.delete(sessionID);
|
|
15244
|
-
}
|
|
14144
|
+
if (age <= CONFIG.TASK_TTL_MS) continue;
|
|
14145
|
+
log2(`Timeout: ${taskId}`);
|
|
14146
|
+
if (task.status === "running") {
|
|
14147
|
+
task.status = "timeout";
|
|
14148
|
+
task.error = "Task exceeded 30 minute time limit";
|
|
14149
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14150
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
14151
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
14152
|
+
}
|
|
14153
|
+
this.client.session.delete({ path: { id: task.sessionID } }).catch(() => {
|
|
14154
|
+
});
|
|
14155
|
+
this.store.delete(taskId);
|
|
15245
14156
|
}
|
|
14157
|
+
this.store.cleanEmptyNotifications();
|
|
15246
14158
|
}
|
|
15247
14159
|
scheduleCleanup(taskId) {
|
|
15248
|
-
const task = this.
|
|
14160
|
+
const task = this.store.get(taskId);
|
|
15249
14161
|
const sessionID = task?.sessionID;
|
|
15250
14162
|
setTimeout(async () => {
|
|
15251
14163
|
if (sessionID) {
|
|
15252
|
-
try {
|
|
15253
|
-
await this.client.session.delete({
|
|
15254
|
-
|
|
15255
|
-
});
|
|
15256
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (session deleted)`);
|
|
15257
|
-
log(`Deleted session ${sessionID}`);
|
|
15258
|
-
} catch {
|
|
15259
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (session already gone)`);
|
|
15260
|
-
}
|
|
15261
|
-
}
|
|
15262
|
-
this.tasks.delete(taskId);
|
|
15263
|
-
log(`Cleaned up ${taskId} from memory`);
|
|
15264
|
-
}, CLEANUP_DELAY_MS2);
|
|
15265
|
-
}
|
|
15266
|
-
// ========================================================================
|
|
15267
|
-
// Internal: Notifications
|
|
15268
|
-
// ========================================================================
|
|
15269
|
-
queueNotification(task) {
|
|
15270
|
-
const queue = this.notifications.get(task.parentSessionID) ?? [];
|
|
15271
|
-
queue.push(task);
|
|
15272
|
-
this.notifications.set(task.parentSessionID, queue);
|
|
15273
|
-
}
|
|
15274
|
-
async notifyParentIfAllComplete(parentSessionID) {
|
|
15275
|
-
const pending = this.pendingByParent.get(parentSessionID);
|
|
15276
|
-
if (pending && pending.size > 0) {
|
|
15277
|
-
log(`${pending.size} tasks still pending for ${parentSessionID}`);
|
|
15278
|
-
return;
|
|
15279
|
-
}
|
|
15280
|
-
const completedTasks = this.notifications.get(parentSessionID) ?? [];
|
|
15281
|
-
if (completedTasks.length === 0) return;
|
|
15282
|
-
const summary = completedTasks.map((t) => {
|
|
15283
|
-
const status = t.status === "completed" ? "\u2705" : "\u274C";
|
|
15284
|
-
const duration3 = this.formatDuration(t.startedAt, t.completedAt);
|
|
15285
|
-
return `${status} \`${t.id}\` (${duration3}): ${t.description}`;
|
|
15286
|
-
}).join("\n");
|
|
15287
|
-
const notification = `<system-notification>
|
|
15288
|
-
**All Parallel Tasks Complete**
|
|
15289
|
-
|
|
15290
|
-
${summary}
|
|
15291
|
-
|
|
15292
|
-
---
|
|
15293
|
-
|
|
15294
|
-
**Retrieval Options**
|
|
15295
|
-
|
|
15296
|
-
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve full results.
|
|
15297
|
-
|
|
15298
|
-
---
|
|
15299
|
-
|
|
15300
|
-
**Task Summary**
|
|
15301
|
-
|
|
15302
|
-
Total Tasks: ${completedTasks.length}
|
|
15303
|
-
Status: All Complete
|
|
15304
|
-
Mode: Background (non-blocking)
|
|
15305
|
-
</system-notification>`;
|
|
15306
|
-
try {
|
|
15307
|
-
await this.client.session.prompt({
|
|
15308
|
-
path: { id: parentSessionID },
|
|
15309
|
-
body: {
|
|
15310
|
-
noReply: true,
|
|
15311
|
-
parts: [{ type: "text", text: notification }]
|
|
15312
|
-
}
|
|
15313
|
-
});
|
|
15314
|
-
log(`Notified parent ${parentSessionID}: ${completedTasks.length} tasks`);
|
|
15315
|
-
} catch (error45) {
|
|
15316
|
-
log("Notification error:", error45);
|
|
15317
|
-
}
|
|
15318
|
-
this.notifications.delete(parentSessionID);
|
|
15319
|
-
}
|
|
15320
|
-
// ========================================================================
|
|
15321
|
-
// Internal: Formatting
|
|
15322
|
-
// ========================================================================
|
|
15323
|
-
formatDuration(start, end) {
|
|
15324
|
-
const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
|
|
15325
|
-
const seconds = Math.floor(duration3 / 1e3);
|
|
15326
|
-
const minutes = Math.floor(seconds / 60);
|
|
15327
|
-
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
15328
|
-
return `${seconds}s`;
|
|
15329
|
-
}
|
|
15330
|
-
};
|
|
15331
|
-
var parallelAgentManager = {
|
|
15332
|
-
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
|
|
15333
|
-
};
|
|
15334
|
-
|
|
15335
|
-
// src/tools/async-agent.ts
|
|
15336
|
-
var createDelegateTaskTool = (manager, client) => tool({
|
|
15337
|
-
description: `Delegate a task to an agent.
|
|
15338
|
-
|
|
15339
|
-
<mode>
|
|
15340
|
-
- background=true: Non-blocking. Task runs in parallel session. Use get_task_result later.
|
|
15341
|
-
- background=false: Blocking. Waits for result. Use when you need output immediately.
|
|
15342
|
-
</mode>
|
|
15343
|
-
|
|
15344
|
-
<when_to_use_background_true>
|
|
15345
|
-
- Multiple independent tasks to run in parallel
|
|
15346
|
-
- Long-running tasks (build, test, analysis)
|
|
15347
|
-
- You have other work to do while waiting
|
|
15348
|
-
- Example: "Build module A" + "Test module B" in parallel
|
|
15349
|
-
</when_to_use_background_true>
|
|
15350
|
-
|
|
15351
|
-
<when_to_use_background_false>
|
|
15352
|
-
- You need the result for the very next step
|
|
15353
|
-
- Single task with nothing else to do
|
|
15354
|
-
- Quick questions that return fast
|
|
15355
|
-
</when_to_use_background_false>
|
|
15356
|
-
|
|
15357
|
-
<safety>
|
|
15358
|
-
- Max 3 background tasks per agent type (extras queue automatically)
|
|
15359
|
-
- Auto-timeout: 30 minutes
|
|
15360
|
-
- Auto-cleanup: 5 minutes after completion
|
|
15361
|
-
</safety>`,
|
|
15362
|
-
args: {
|
|
15363
|
-
agent: tool.schema.string().describe(
|
|
15364
|
-
`Agent name (e.g., '${AGENT_NAMES.BUILDER}', '${AGENT_NAMES.INSPECTOR}', '${AGENT_NAMES.ARCHITECT}')`
|
|
15365
|
-
),
|
|
15366
|
-
description: tool.schema.string().describe("Short task description"),
|
|
15367
|
-
prompt: tool.schema.string().describe("Full prompt/instructions for the agent"),
|
|
15368
|
-
background: tool.schema.boolean().describe(
|
|
15369
|
-
"true=async (returns task_id), false=sync (waits for result). REQUIRED."
|
|
15370
|
-
)
|
|
15371
|
-
},
|
|
15372
|
-
async execute(args, context) {
|
|
15373
|
-
const { agent, description, prompt, background } = args;
|
|
15374
|
-
const ctx = context;
|
|
15375
|
-
const sessionClient = client;
|
|
15376
|
-
if (background === void 0) {
|
|
15377
|
-
return `\u274C 'background' parameter is REQUIRED.
|
|
15378
|
-
- background=true: Run in parallel, returns task ID
|
|
15379
|
-
- background=false: Wait for result (blocking)`;
|
|
15380
|
-
}
|
|
15381
|
-
if (background === true) {
|
|
15382
|
-
try {
|
|
15383
|
-
const task = await manager.launch({
|
|
15384
|
-
agent,
|
|
15385
|
-
description,
|
|
15386
|
-
prompt,
|
|
15387
|
-
parentSessionID: ctx.sessionID
|
|
15388
|
-
});
|
|
15389
|
-
const runningCount = manager.getRunningTasks().length;
|
|
15390
|
-
const pendingCount = manager.getPendingCount(ctx.sessionID);
|
|
15391
|
-
console.log(
|
|
15392
|
-
`[parallel] \u{1F680} SPAWNED ${task.id} \u2192 ${agent}: ${description}`
|
|
15393
|
-
);
|
|
15394
|
-
return `
|
|
15395
|
-
## \u{1F680} BACKGROUND TASK SPAWNED
|
|
15396
|
-
|
|
15397
|
-
**Task Details**
|
|
15398
|
-
- **ID**: \`${task.id}\`
|
|
15399
|
-
- **Agent**: ${agent}
|
|
15400
|
-
- **Description**: ${description}
|
|
15401
|
-
- **Status**: \u23F3 Running in background (non-blocking)
|
|
15402
|
-
|
|
15403
|
-
**Active Tasks**
|
|
15404
|
-
- Running: ${runningCount}
|
|
15405
|
-
- Pending: ${pendingCount}
|
|
15406
|
-
|
|
15407
|
-
---
|
|
15408
|
-
|
|
15409
|
-
**Monitoring Commands**
|
|
15410
|
-
|
|
15411
|
-
Check progress anytime:
|
|
15412
|
-
- \`list_tasks()\` - View all parallel tasks
|
|
15413
|
-
- \`get_task_result({ taskId: "${task.id}" })\` - Get latest result
|
|
15414
|
-
- \`cancel_task({ taskId: "${task.id}" })\` - Stop this task
|
|
15415
|
-
|
|
15416
|
-
---
|
|
15417
|
-
|
|
15418
|
-
\u2713 System will notify when ALL tasks complete. You can continue working!`;
|
|
15419
|
-
} catch (error45) {
|
|
15420
|
-
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
15421
|
-
console.log(`[parallel] \u274C FAILED: ${message}`);
|
|
15422
|
-
return `\u274C Failed to spawn background task: ${message}`;
|
|
15423
|
-
}
|
|
15424
|
-
}
|
|
15425
|
-
console.log(`[delegate] \u23F3 SYNC ${agent}: ${description}`);
|
|
15426
|
-
try {
|
|
15427
|
-
const session = sessionClient.session;
|
|
15428
|
-
const directory = ".";
|
|
15429
|
-
const createResult = await session.create({
|
|
15430
|
-
body: {
|
|
15431
|
-
parentID: ctx.sessionID,
|
|
15432
|
-
title: `Task: ${description}`
|
|
15433
|
-
},
|
|
15434
|
-
query: { directory }
|
|
15435
|
-
});
|
|
15436
|
-
if (createResult.error || !createResult.data?.id) {
|
|
15437
|
-
return `\u274C Failed to create session: ${createResult.error || "No session ID"}`;
|
|
15438
|
-
}
|
|
15439
|
-
const sessionID = createResult.data.id;
|
|
15440
|
-
const startTime = Date.now();
|
|
15441
|
-
try {
|
|
15442
|
-
await session.prompt({
|
|
15443
|
-
path: { id: sessionID },
|
|
15444
|
-
body: {
|
|
15445
|
-
agent,
|
|
15446
|
-
parts: [{ type: "text", text: prompt }]
|
|
15447
|
-
}
|
|
15448
|
-
});
|
|
15449
|
-
} catch (promptError) {
|
|
15450
|
-
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError);
|
|
15451
|
-
return `\u274C Failed to send prompt: ${errorMessage}
|
|
15452
|
-
|
|
15453
|
-
Session ID: ${sessionID}`;
|
|
15454
|
-
}
|
|
15455
|
-
const POLL_INTERVAL_MS2 = 500;
|
|
15456
|
-
const MAX_POLL_TIME_MS = 10 * 60 * 1e3;
|
|
15457
|
-
const MIN_STABILITY_MS3 = 5e3;
|
|
15458
|
-
const STABILITY_POLLS_REQUIRED = 3;
|
|
15459
|
-
let stablePolls = 0;
|
|
15460
|
-
let lastMsgCount = 0;
|
|
15461
|
-
while (Date.now() - startTime < MAX_POLL_TIME_MS) {
|
|
15462
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
|
|
15463
|
-
const statusResult = await session.status();
|
|
15464
|
-
const allStatuses = statusResult.data ?? {};
|
|
15465
|
-
const sessionStatus = allStatuses[sessionID];
|
|
15466
|
-
if (sessionStatus?.type !== "idle") {
|
|
15467
|
-
stablePolls = 0;
|
|
15468
|
-
lastMsgCount = 0;
|
|
15469
|
-
continue;
|
|
15470
|
-
}
|
|
15471
|
-
if (Date.now() - startTime < MIN_STABILITY_MS3) continue;
|
|
15472
|
-
const messagesResult2 = await session.messages({
|
|
15473
|
-
path: { id: sessionID }
|
|
15474
|
-
});
|
|
15475
|
-
const messages2 = messagesResult2.data ?? [];
|
|
15476
|
-
const currentMsgCount = messages2.length;
|
|
15477
|
-
if (currentMsgCount === lastMsgCount) {
|
|
15478
|
-
stablePolls++;
|
|
15479
|
-
if (stablePolls >= STABILITY_POLLS_REQUIRED) break;
|
|
15480
|
-
} else {
|
|
15481
|
-
stablePolls = 0;
|
|
15482
|
-
lastMsgCount = currentMsgCount;
|
|
15483
|
-
}
|
|
15484
|
-
}
|
|
15485
|
-
const messagesResult = await session.messages({
|
|
15486
|
-
path: { id: sessionID }
|
|
15487
|
-
});
|
|
15488
|
-
const messages = messagesResult.data ?? [];
|
|
15489
|
-
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant").reverse();
|
|
15490
|
-
const lastMsg = assistantMsgs[0];
|
|
15491
|
-
if (!lastMsg) {
|
|
15492
|
-
return `\u274C No assistant response found.
|
|
15493
|
-
|
|
15494
|
-
Session ID: ${sessionID}`;
|
|
15495
|
-
}
|
|
15496
|
-
const textParts = lastMsg.parts?.filter(
|
|
15497
|
-
(p) => p.type === "text" || p.type === "reasoning"
|
|
15498
|
-
) ?? [];
|
|
15499
|
-
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
|
|
15500
|
-
const duration3 = Math.floor((Date.now() - startTime) / 1e3);
|
|
15501
|
-
console.log(
|
|
15502
|
-
`[delegate] \u2705 COMPLETED ${agent}: ${description} (${duration3}s)`
|
|
15503
|
-
);
|
|
15504
|
-
return `\u2705 **Task Completed** (${duration3}s)
|
|
15505
|
-
|
|
15506
|
-
Agent: ${agent}
|
|
15507
|
-
Session ID: ${sessionID}
|
|
15508
|
-
|
|
15509
|
-
---
|
|
15510
|
-
|
|
15511
|
-
${textContent || "(No text output)"}`;
|
|
15512
|
-
} catch (error45) {
|
|
15513
|
-
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
15514
|
-
console.log(`[delegate] \u274C FAILED: ${message}`);
|
|
15515
|
-
return `\u274C Task failed: ${message}`;
|
|
15516
|
-
}
|
|
15517
|
-
}
|
|
15518
|
-
});
|
|
15519
|
-
var createGetTaskResultTool = (manager) => tool({
|
|
15520
|
-
description: `Get the result from a completed background task.
|
|
15521
|
-
|
|
15522
|
-
<note>
|
|
15523
|
-
If the task is still running, returns status info.
|
|
15524
|
-
Wait for the "All Complete" notification before checking.
|
|
15525
|
-
</note>`,
|
|
15526
|
-
args: {
|
|
15527
|
-
taskId: tool.schema.string().describe("Task ID from delegate_task (e.g., 'task_a1b2c3d4')")
|
|
15528
|
-
},
|
|
15529
|
-
async execute(args) {
|
|
15530
|
-
const { taskId } = args;
|
|
15531
|
-
const task = manager.getTask(taskId);
|
|
15532
|
-
if (!task) {
|
|
15533
|
-
return `\u274C Task not found: \`${taskId}\`
|
|
15534
|
-
|
|
15535
|
-
Use \`list_tasks\` to see available tasks.`;
|
|
15536
|
-
}
|
|
15537
|
-
if (task.status === "running") {
|
|
15538
|
-
const elapsed = Math.floor(
|
|
15539
|
-
(Date.now() - task.startedAt.getTime()) / 1e3
|
|
15540
|
-
);
|
|
15541
|
-
return `\u23F3 **Task Still Running**
|
|
15542
|
-
|
|
15543
|
-
| Property | Value |
|
|
15544
|
-
|----------|-------|
|
|
15545
|
-
| **Task ID** | \`${taskId}\` |
|
|
15546
|
-
| **Agent** | ${task.agent} |
|
|
15547
|
-
| **Elapsed** | ${elapsed}s |
|
|
15548
|
-
|
|
15549
|
-
Wait for "All Complete" notification, then try again.`;
|
|
15550
|
-
}
|
|
15551
|
-
const result = await manager.getResult(taskId);
|
|
15552
|
-
const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
|
|
15553
|
-
if (task.status === "error" || task.status === "timeout") {
|
|
15554
|
-
return `\u274C **Task ${task.status === "timeout" ? "Timed Out" : "Failed"}**
|
|
15555
|
-
|
|
15556
|
-
| Property | Value |
|
|
15557
|
-
|----------|-------|
|
|
15558
|
-
| **Task ID** | \`${taskId}\` |
|
|
15559
|
-
| **Agent** | ${task.agent} |
|
|
15560
|
-
| **Error** | ${task.error} |
|
|
15561
|
-
| **Duration** | ${duration3} |`;
|
|
15562
|
-
}
|
|
15563
|
-
return `\u2705 **Task Completed**
|
|
15564
|
-
|
|
15565
|
-
| Property | Value |
|
|
15566
|
-
|----------|-------|
|
|
15567
|
-
| **Task ID** | \`${taskId}\` |
|
|
15568
|
-
| **Agent** | ${task.agent} |
|
|
15569
|
-
| **Duration** | ${duration3} |
|
|
15570
|
-
|
|
15571
|
-
---
|
|
15572
|
-
|
|
15573
|
-
**Result:**
|
|
15574
|
-
|
|
15575
|
-
${result || "(No output)"}`;
|
|
15576
|
-
}
|
|
15577
|
-
});
|
|
15578
|
-
var createListTasksTool = (manager) => tool({
|
|
15579
|
-
description: `List all background tasks and their status.`,
|
|
15580
|
-
args: {
|
|
15581
|
-
status: tool.schema.string().optional().describe("Filter: 'all', 'running', 'completed', 'error'")
|
|
15582
|
-
},
|
|
15583
|
-
async execute(args) {
|
|
15584
|
-
const { status = "all" } = args;
|
|
15585
|
-
let tasks;
|
|
15586
|
-
switch (status) {
|
|
15587
|
-
case "running":
|
|
15588
|
-
tasks = manager.getRunningTasks();
|
|
15589
|
-
break;
|
|
15590
|
-
case "completed":
|
|
15591
|
-
tasks = manager.getAllTasks().filter((t) => t.status === "completed");
|
|
15592
|
-
break;
|
|
15593
|
-
case "error":
|
|
15594
|
-
tasks = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout");
|
|
15595
|
-
break;
|
|
15596
|
-
default:
|
|
15597
|
-
tasks = manager.getAllTasks();
|
|
15598
|
-
}
|
|
15599
|
-
if (tasks.length === 0) {
|
|
15600
|
-
return `\u{1F4CB} No background tasks found${status !== "all" ? ` (filter: ${status})` : ""}.
|
|
15601
|
-
|
|
15602
|
-
Use \`delegate_task({ ..., background: true })\` to spawn background tasks.`;
|
|
15603
|
-
}
|
|
15604
|
-
const runningCount = manager.getRunningTasks().length;
|
|
15605
|
-
const completedCount = manager.getAllTasks().filter((t) => t.status === "completed").length;
|
|
15606
|
-
const errorCount = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout").length;
|
|
15607
|
-
const statusIcon = (s) => {
|
|
15608
|
-
switch (s) {
|
|
15609
|
-
case "running":
|
|
15610
|
-
return "\u23F3";
|
|
15611
|
-
case "completed":
|
|
15612
|
-
return "\u2705";
|
|
15613
|
-
case "error":
|
|
15614
|
-
return "\u274C";
|
|
15615
|
-
case "timeout":
|
|
15616
|
-
return "\u23F1\uFE0F";
|
|
15617
|
-
default:
|
|
15618
|
-
return "\u2753";
|
|
15619
|
-
}
|
|
15620
|
-
};
|
|
15621
|
-
const rows = tasks.map((t) => {
|
|
15622
|
-
const elapsed = Math.floor(
|
|
15623
|
-
(Date.now() - t.startedAt.getTime()) / 1e3
|
|
15624
|
-
);
|
|
15625
|
-
const desc = t.description.length > 25 ? t.description.slice(0, 22) + "..." : t.description;
|
|
15626
|
-
return `| \`${t.id}\` | ${statusIcon(t.status)} ${t.status} | ${t.agent} | ${desc} | ${elapsed}s |`;
|
|
15627
|
-
}).join("\n");
|
|
15628
|
-
return `\u{1F4CB} **Background Tasks** (${tasks.length} shown)
|
|
15629
|
-
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
15630
|
-
| \u23F3 Running: ${runningCount} | \u2705 Completed: ${completedCount} | \u274C Error: ${errorCount} |
|
|
15631
|
-
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
15632
|
-
|
|
15633
|
-
| Task ID | Status | Agent | Description | Elapsed |
|
|
15634
|
-
|---------|--------|-------|-------------|---------|
|
|
15635
|
-
${rows}
|
|
15636
|
-
|
|
15637
|
-
\u{1F4A1} Use \`get_task_result({ taskId: "task_xxx" })\` to get results.
|
|
15638
|
-
\u{1F6D1} Use \`cancel_task({ taskId: "task_xxx" })\` to stop a running task.`;
|
|
15639
|
-
}
|
|
15640
|
-
});
|
|
15641
|
-
var createCancelTaskTool = (manager) => tool({
|
|
15642
|
-
description: `Cancel a running background task.
|
|
15643
|
-
|
|
15644
|
-
<purpose>
|
|
15645
|
-
Stop a runaway or no-longer-needed task.
|
|
15646
|
-
Frees up concurrency slot for other tasks.
|
|
15647
|
-
</purpose>`,
|
|
15648
|
-
args: {
|
|
15649
|
-
taskId: tool.schema.string().describe("Task ID to cancel (e.g., 'task_a1b2c3d4')")
|
|
15650
|
-
},
|
|
15651
|
-
async execute(args) {
|
|
15652
|
-
const { taskId } = args;
|
|
15653
|
-
const cancelled = await manager.cancelTask(taskId);
|
|
15654
|
-
if (cancelled) {
|
|
15655
|
-
return `\u{1F6D1} **Task Cancelled**
|
|
15656
|
-
|
|
15657
|
-
Task \`${taskId}\` has been stopped. Concurrency slot released.`;
|
|
15658
|
-
}
|
|
15659
|
-
const task = manager.getTask(taskId);
|
|
15660
|
-
if (task) {
|
|
15661
|
-
return `\u26A0\uFE0F Cannot cancel: Task \`${taskId}\` is ${task.status} (not running).`;
|
|
15662
|
-
}
|
|
15663
|
-
return `\u274C Task \`${taskId}\` not found.
|
|
15664
|
-
|
|
15665
|
-
Use \`list_tasks\` to see available tasks.`;
|
|
15666
|
-
}
|
|
15667
|
-
});
|
|
15668
|
-
function createAsyncAgentTools(manager, client) {
|
|
15669
|
-
return {
|
|
15670
|
-
delegate_task: createDelegateTaskTool(manager, client),
|
|
15671
|
-
get_task_result: createGetTaskResultTool(manager),
|
|
15672
|
-
list_tasks: createListTasksTool(manager),
|
|
15673
|
-
cancel_task: createCancelTaskTool(manager)
|
|
15674
|
-
};
|
|
15675
|
-
}
|
|
15676
|
-
|
|
15677
|
-
// src/core/batch-processor.ts
|
|
15678
|
-
var SmartBatchProcessor = class {
|
|
15679
|
-
parallelAgentManager;
|
|
15680
|
-
tasks = /* @__PURE__ */ new Map();
|
|
15681
|
-
constructor(parallelAgentManager2) {
|
|
15682
|
-
this.parallelAgentManager = parallelAgentManager2;
|
|
15683
|
-
}
|
|
15684
|
-
/**
|
|
15685
|
-
* Process a batch of tasks with smart validation
|
|
15686
|
-
*/
|
|
15687
|
-
async processBatch(tasks, options = {
|
|
15688
|
-
concurrency: 3,
|
|
15689
|
-
maxRetries: 2,
|
|
15690
|
-
validateAfterEach: false,
|
|
15691
|
-
continueOnError: true
|
|
15692
|
-
}) {
|
|
15693
|
-
const startTime = Date.now();
|
|
15694
|
-
for (const task of tasks) {
|
|
15695
|
-
this.tasks.set(task.id, {
|
|
15696
|
-
...task,
|
|
15697
|
-
status: "pending",
|
|
15698
|
-
attempts: 0
|
|
15699
|
-
});
|
|
15700
|
-
}
|
|
15701
|
-
console.log(`
|
|
15702
|
-
\u{1F4E6} [Smart Batch] Starting ${tasks.length} tasks with concurrency ${options.concurrency}`);
|
|
15703
|
-
console.log(` Strategy: ${options.validateAfterEach ? "Validate each" : "Centralized validation"}`);
|
|
15704
|
-
await this.executePhase(tasks, options, "initial");
|
|
15705
|
-
const failedTasks = Array.from(this.tasks.values()).filter((t) => t.status === "failed" || t.status === "pending");
|
|
15706
|
-
if (failedTasks.length === 0) {
|
|
15707
|
-
console.log(`
|
|
15708
|
-
\u2705 [Smart Batch] All ${tasks.length} tasks succeeded!`);
|
|
15709
|
-
return this.buildResult(startTime, tasks);
|
|
15710
|
-
}
|
|
15711
|
-
console.log(`
|
|
15712
|
-
\u{1F50D} [Smart Batch] Validation complete: ${failedTasks.length}/${tasks.length} tasks need retry`);
|
|
15713
|
-
let retryCount = 0;
|
|
15714
|
-
while (failedTasks.length > 0 && retryCount < options.maxRetries) {
|
|
15715
|
-
retryCount++;
|
|
15716
|
-
console.log(`
|
|
15717
|
-
\u{1F504} [Smart Batch] Retry round ${retryCount}/${options.maxRetries} for ${failedTasks.length} tasks`);
|
|
15718
|
-
await this.executePhase(failedTasks, options, `retry-${retryCount}`);
|
|
15719
|
-
const stillFailed = Array.from(this.tasks.values()).filter((t) => t.status === "failed");
|
|
15720
|
-
if (stillFailed.length === failedTasks.length) {
|
|
15721
|
-
console.log(`\u26A0\uFE0F [Smart Batch] No progress in retry round ${retryCount}, stopping`);
|
|
15722
|
-
break;
|
|
15723
|
-
}
|
|
15724
|
-
}
|
|
15725
|
-
return this.buildResult(startTime, tasks);
|
|
15726
|
-
}
|
|
15727
|
-
/**
|
|
15728
|
-
* Execute a phase with concurrency control
|
|
15729
|
-
*/
|
|
15730
|
-
async executePhase(tasks, options, phase) {
|
|
15731
|
-
for (const task of tasks) {
|
|
15732
|
-
this.parallelAgentManager.setConcurrencyLimit(task.agent, options.concurrency);
|
|
15733
|
-
}
|
|
15734
|
-
const batches = [];
|
|
15735
|
-
for (let i = 0; i < tasks.length; i += options.concurrency) {
|
|
15736
|
-
batches.push(tasks.slice(i, i + options.concurrency));
|
|
15737
|
-
}
|
|
15738
|
-
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
|
15739
|
-
const batch = batches[batchIndex];
|
|
15740
|
-
console.log(` [${phase}] Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} tasks)`);
|
|
15741
|
-
const batchPromises = batch.map((task) => this.executeTask(task, options));
|
|
15742
|
-
await Promise.all(batchPromises);
|
|
15743
|
-
if (!options.validateAfterEach) {
|
|
15744
|
-
continue;
|
|
15745
|
-
}
|
|
15746
|
-
const failedInBatch = batch.filter((t) => t.status === "failed");
|
|
15747
|
-
if (failedInBatch.length > 0) {
|
|
15748
|
-
console.log(` [${phase}] ${failedInBatch.length} failed in batch, immediate retry`);
|
|
15749
|
-
const retryPromises = failedInBatch.map((t) => this.executeTask(t, options));
|
|
15750
|
-
await Promise.all(retryPromises);
|
|
15751
|
-
}
|
|
15752
|
-
}
|
|
15753
|
-
}
|
|
15754
|
-
/**
|
|
15755
|
-
* Execute a single task
|
|
15756
|
-
*/
|
|
15757
|
-
async executeTask(task, options) {
|
|
15758
|
-
const currentTask = this.tasks.get(task.id);
|
|
15759
|
-
if (!options.continueOnError && currentTask.status === "failed") {
|
|
15760
|
-
return;
|
|
15761
|
-
}
|
|
15762
|
-
try {
|
|
15763
|
-
currentTask.attempts++;
|
|
15764
|
-
currentTask.status = "pending";
|
|
15765
|
-
const launched = await this.parallelAgentManager.launch({
|
|
15766
|
-
agent: task.agent,
|
|
15767
|
-
description: task.description,
|
|
15768
|
-
prompt: task.prompt,
|
|
15769
|
-
parentSessionID: ""
|
|
15770
|
-
// Would need actual parent ID
|
|
15771
|
-
});
|
|
15772
|
-
const maxWaitTime = 5 * 60 * 1e3;
|
|
15773
|
-
const startTime = Date.now();
|
|
15774
|
-
while (Date.now() - startTime < maxWaitTime) {
|
|
15775
|
-
const taskData = this.parallelAgentManager.getTask(launched.id);
|
|
15776
|
-
if (!taskData) break;
|
|
15777
|
-
if (taskData.status === "completed") {
|
|
15778
|
-
currentTask.status = "success";
|
|
15779
|
-
console.log(` \u2705 [${task.id}] Success on attempt ${currentTask.attempts}`);
|
|
15780
|
-
return;
|
|
15781
|
-
}
|
|
15782
|
-
if (taskData.status === "error" || taskData.status === "timeout") {
|
|
15783
|
-
currentTask.status = "failed";
|
|
15784
|
-
currentTask.error = taskData.error || "Unknown error";
|
|
15785
|
-
console.log(` \u274C [${task.id}] Failed: ${currentTask.error}`);
|
|
15786
|
-
return;
|
|
14164
|
+
try {
|
|
14165
|
+
await this.client.session.delete({ path: { id: sessionID } });
|
|
14166
|
+
} catch {
|
|
15787
14167
|
}
|
|
15788
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
15789
14168
|
}
|
|
15790
|
-
|
|
15791
|
-
|
|
14169
|
+
this.store.delete(taskId);
|
|
14170
|
+
log2(`Cleaned up ${taskId}`);
|
|
14171
|
+
}, CONFIG.CLEANUP_DELAY_MS);
|
|
14172
|
+
}
|
|
14173
|
+
async notifyParentIfAllComplete(parentSessionID) {
|
|
14174
|
+
if (this.store.hasPending(parentSessionID)) return;
|
|
14175
|
+
const notifications = this.store.getNotifications(parentSessionID);
|
|
14176
|
+
if (notifications.length === 0) return;
|
|
14177
|
+
const message = buildNotificationMessage(notifications);
|
|
14178
|
+
try {
|
|
14179
|
+
await this.client.session.prompt({
|
|
14180
|
+
path: { id: parentSessionID },
|
|
14181
|
+
body: { noReply: true, parts: [{ type: "text", text: message }] }
|
|
14182
|
+
});
|
|
14183
|
+
log2(`Notified parent ${parentSessionID}`);
|
|
15792
14184
|
} catch (error45) {
|
|
15793
|
-
|
|
15794
|
-
currentTask.error = error45 instanceof Error ? error45.message : String(error45);
|
|
15795
|
-
console.log(` \u274C [${task.id}] Exception: ${currentTask.error}`);
|
|
14185
|
+
log2("Notification error:", error45);
|
|
15796
14186
|
}
|
|
15797
|
-
|
|
15798
|
-
/**
|
|
15799
|
-
* Build result summary
|
|
15800
|
-
*/
|
|
15801
|
-
buildResult(startTime, tasks) {
|
|
15802
|
-
const allTasks = Array.from(this.tasks.values());
|
|
15803
|
-
const successCount = allTasks.filter((t) => t.status === "success").length;
|
|
15804
|
-
const failedCount = allTasks.filter((t) => t.status === "failed").length;
|
|
15805
|
-
const retriedCount = allTasks.filter((t) => t.attempts > 1).length;
|
|
15806
|
-
return {
|
|
15807
|
-
total: tasks.length,
|
|
15808
|
-
success: successCount,
|
|
15809
|
-
failed: failedCount,
|
|
15810
|
-
retried: retriedCount,
|
|
15811
|
-
duration: Date.now() - startTime,
|
|
15812
|
-
tasks: allTasks
|
|
15813
|
-
};
|
|
15814
|
-
}
|
|
15815
|
-
/**
|
|
15816
|
-
* Export failed tasks for manual review
|
|
15817
|
-
*/
|
|
15818
|
-
exportFailedTasks() {
|
|
15819
|
-
const failedTasks = Array.from(this.tasks.values()).filter((t) => t.status === "failed");
|
|
15820
|
-
if (failedTasks.length === 0) {
|
|
15821
|
-
return "\u2705 No failed tasks to export.";
|
|
15822
|
-
}
|
|
15823
|
-
const output = failedTasks.map((task) => {
|
|
15824
|
-
return `
|
|
15825
|
-
---
|
|
15826
|
-
Task ID: ${task.id}
|
|
15827
|
-
Agent: ${task.agent}
|
|
15828
|
-
Attempts: ${task.attempts}
|
|
15829
|
-
Error: ${task.error}
|
|
15830
|
-
Description: ${task.description}
|
|
15831
|
-
---
|
|
15832
|
-
`.trim();
|
|
15833
|
-
}).join("\n");
|
|
15834
|
-
return `\u274C ${failedTasks.length} failed tasks:
|
|
15835
|
-
${output}`;
|
|
15836
|
-
}
|
|
15837
|
-
/**
|
|
15838
|
-
* Clear all tasks
|
|
15839
|
-
*/
|
|
15840
|
-
clear() {
|
|
15841
|
-
this.tasks.clear();
|
|
14187
|
+
this.store.clearNotifications(parentSessionID);
|
|
15842
14188
|
}
|
|
15843
14189
|
};
|
|
14190
|
+
var parallelAgentManager = {
|
|
14191
|
+
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
|
|
14192
|
+
};
|
|
15844
14193
|
|
|
15845
|
-
// src/tools/
|
|
15846
|
-
var
|
|
15847
|
-
description: `
|
|
15848
|
-
|
|
15849
|
-
<strategy>
|
|
15850
|
-
1. Execute all tasks in parallel (respecting concurrency limits)
|
|
15851
|
-
2. Centralized validation: Identify ALL failed tasks at once
|
|
15852
|
-
3. Retry ONLY failed tasks (not everything)
|
|
15853
|
-
</strategy>
|
|
14194
|
+
// src/tools/parallel/delegate-task.ts
|
|
14195
|
+
var createDelegateTaskTool = (manager, client) => tool({
|
|
14196
|
+
description: `Delegate a task to an agent.
|
|
15854
14197
|
|
|
15855
|
-
<
|
|
15856
|
-
-
|
|
15857
|
-
-
|
|
15858
|
-
|
|
15859
|
-
</benefits>
|
|
14198
|
+
<mode>
|
|
14199
|
+
- background=true: Non-blocking. Returns task ID immediately.
|
|
14200
|
+
- background=false: Blocking. Waits for result.
|
|
14201
|
+
</mode>
|
|
15860
14202
|
|
|
15861
|
-
<
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
{ id: "task1", agent: "builder", description: "Test A", prompt: "..." },
|
|
15866
|
-
{ id: "task2", agent: "inspector", description: "Test B", prompt: "..." }
|
|
15867
|
-
]
|
|
15868
|
-
})
|
|
15869
|
-
</examples>`,
|
|
14203
|
+
<safety>
|
|
14204
|
+
- Max 3 tasks per agent type
|
|
14205
|
+
- Auto-timeout: 30 minutes
|
|
14206
|
+
</safety>`,
|
|
15870
14207
|
args: {
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
14208
|
+
agent: tool.schema.string().describe("Agent name"),
|
|
14209
|
+
description: tool.schema.string().describe("Task description"),
|
|
14210
|
+
prompt: tool.schema.string().describe("Prompt for the agent"),
|
|
14211
|
+
background: tool.schema.boolean().describe("true=async, false=sync")
|
|
15875
14212
|
},
|
|
15876
|
-
async execute(args) {
|
|
15877
|
-
const {
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
return "\u274C Invalid tasks JSON. Must be valid array.";
|
|
15883
|
-
}
|
|
15884
|
-
if (!Array.isArray(taskList)) {
|
|
15885
|
-
return "\u274C tasks must be an array of task objects.";
|
|
14213
|
+
async execute(args, context) {
|
|
14214
|
+
const { agent, description, prompt, background } = args;
|
|
14215
|
+
const ctx = context;
|
|
14216
|
+
const sessionClient = client;
|
|
14217
|
+
if (background === void 0) {
|
|
14218
|
+
return `\u274C 'background' parameter is REQUIRED.`;
|
|
15886
14219
|
}
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
14220
|
+
if (background === true) {
|
|
14221
|
+
try {
|
|
14222
|
+
const task = await manager.launch({
|
|
14223
|
+
agent,
|
|
14224
|
+
description,
|
|
14225
|
+
prompt,
|
|
14226
|
+
parentSessionID: ctx.sessionID
|
|
14227
|
+
});
|
|
14228
|
+
return `\u{1F680} Task spawned: \`${task.id}\` (${agent})`;
|
|
14229
|
+
} catch (error45) {
|
|
14230
|
+
return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
15890
14231
|
}
|
|
15891
14232
|
}
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
14233
|
+
try {
|
|
14234
|
+
const session = sessionClient.session;
|
|
14235
|
+
const createResult = await session.create({
|
|
14236
|
+
body: { parentID: ctx.sessionID, title: `Task: ${description}` },
|
|
14237
|
+
query: { directory: "." }
|
|
14238
|
+
});
|
|
14239
|
+
if (createResult.error || !createResult.data?.id) {
|
|
14240
|
+
return `\u274C Failed to create session`;
|
|
14241
|
+
}
|
|
14242
|
+
const sessionID = createResult.data.id;
|
|
14243
|
+
const startTime = Date.now();
|
|
14244
|
+
await session.prompt({
|
|
14245
|
+
path: { id: sessionID },
|
|
14246
|
+
body: { agent, parts: [{ type: "text", text: prompt }] }
|
|
14247
|
+
});
|
|
14248
|
+
let stablePolls = 0, lastMsgCount = 0;
|
|
14249
|
+
while (Date.now() - startTime < 10 * 60 * 1e3) {
|
|
14250
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
14251
|
+
const statusResult = await session.status();
|
|
14252
|
+
if (statusResult.data?.[sessionID]?.type !== "idle") {
|
|
14253
|
+
stablePolls = 0;
|
|
14254
|
+
continue;
|
|
14255
|
+
}
|
|
14256
|
+
if (Date.now() - startTime < 5e3) continue;
|
|
14257
|
+
const msgs2 = await session.messages({ path: { id: sessionID } });
|
|
14258
|
+
const count = (msgs2.data ?? []).length;
|
|
14259
|
+
if (count === lastMsgCount) {
|
|
14260
|
+
stablePolls++;
|
|
14261
|
+
if (stablePolls >= 3) break;
|
|
14262
|
+
} else {
|
|
14263
|
+
stablePolls = 0;
|
|
14264
|
+
lastMsgCount = count;
|
|
14265
|
+
}
|
|
14266
|
+
}
|
|
14267
|
+
const msgs = await session.messages({ path: { id: sessionID } });
|
|
14268
|
+
const messages = msgs.data ?? [];
|
|
14269
|
+
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
14270
|
+
const text = lastMsg?.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").join("\n") || "";
|
|
14271
|
+
return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
|
|
15926
14272
|
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
`;
|
|
14273
|
+
${text || "(No output)"}`;
|
|
14274
|
+
} catch (error45) {
|
|
14275
|
+
return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
15931
14276
|
}
|
|
15932
|
-
return output;
|
|
15933
14277
|
}
|
|
15934
14278
|
});
|
|
15935
|
-
var createExportFailedTasksTool = (parallelAgentManager2) => tool({
|
|
15936
|
-
description: `Export failed tasks from the last batch for manual review.`,
|
|
15937
|
-
args: {},
|
|
15938
|
-
async execute() {
|
|
15939
|
-
const processor = new SmartBatchProcessor(parallelAgentManager2);
|
|
15940
|
-
return processor.exportFailedTasks();
|
|
15941
|
-
}
|
|
15942
|
-
});
|
|
15943
|
-
var createCompareStrategiesTool = (parallelAgentManager2) => tool({
|
|
15944
|
-
description: `Compare naive retry vs smart batch validation performance.
|
|
15945
14279
|
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
- Retry: Per-task immediate retry
|
|
15950
|
-
- Issue: Slow for large batches, redundant work
|
|
15951
|
-
|
|
15952
|
-
**Smart Batch Strategy** (new):
|
|
15953
|
-
- Concurrency: Configurable (up to 10)
|
|
15954
|
-
- Validation: Centralized, batch-identify failures
|
|
15955
|
-
- Retry: Only failed tasks
|
|
15956
|
-
- Benefit: Faster, less redundant work
|
|
15957
|
-
</comparison>`,
|
|
14280
|
+
// src/tools/parallel/get-task-result.ts
|
|
14281
|
+
var createGetTaskResultTool = (manager) => tool({
|
|
14282
|
+
description: `Get result from a completed background task.`,
|
|
15958
14283
|
args: {
|
|
15959
|
-
|
|
15960
|
-
concurrency1: tool.schema.string().optional().describe("Strategy 1 concurrency (default: 3)"),
|
|
15961
|
-
concurrency2: tool.schema.string().optional().describe("Strategy 2 concurrency (default: 10)")
|
|
14284
|
+
taskId: tool.schema.string().describe("Task ID")
|
|
15962
14285
|
},
|
|
15963
14286
|
async execute(args) {
|
|
15964
|
-
const
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
const
|
|
15968
|
-
const
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
return `\u{1F4CA} **Strategy Comparison for ${taskCount} tasks**
|
|
15974
|
-
|
|
15975
|
-
**Strategy 1: Naive (Current)**
|
|
15976
|
-
| Metric | Value |
|
|
15977
|
-
|---------|--------|
|
|
15978
|
-
| Concurrency | ${concurrency1} |
|
|
15979
|
-
| Batches | ${naiveBatches} |
|
|
15980
|
-
| Est. Time | ${naiveTime}s |
|
|
15981
|
-
|
|
15982
|
-
**Strategy 2: Smart Batch (Proposed)**
|
|
15983
|
-
| Metric | Value |
|
|
15984
|
-
|---------|--------|
|
|
15985
|
-
| Concurrency | ${concurrency2} |
|
|
15986
|
-
| Batches | ${smartBatches} |
|
|
15987
|
-
| Est. Time | ${smartTime}s |
|
|
15988
|
-
|
|
15989
|
-
**Summary**
|
|
15990
|
-
| Metric | Value |
|
|
15991
|
-
|---------|--------|
|
|
15992
|
-
| Time Saved | ${timeDiff}s (${improvement}%) |
|
|
15993
|
-
| Batches Saved | ${naiveBatches - smartBatches} |
|
|
15994
|
-
|
|
15995
|
-
Recommendation: Use **Smart Batch** with concurrency ${concurrency2} for ${timeDiff}s improvement.`;
|
|
15996
|
-
}
|
|
15997
|
-
});
|
|
15998
|
-
function createBatchTools(parallelAgentManager2, client) {
|
|
15999
|
-
return {
|
|
16000
|
-
process_batch: createProcessBatchTool(parallelAgentManager2, client),
|
|
16001
|
-
export_failed_tasks: createExportFailedTasksTool(parallelAgentManager2),
|
|
16002
|
-
compare_strategies: createCompareStrategiesTool(parallelAgentManager2)
|
|
16003
|
-
};
|
|
16004
|
-
}
|
|
16005
|
-
|
|
16006
|
-
// src/tools/config.ts
|
|
16007
|
-
var createShowConfigTool = () => tool({
|
|
16008
|
-
description: `Display current OpenCode Orchestrator configuration.
|
|
14287
|
+
const task = manager.getTask(args.taskId);
|
|
14288
|
+
if (!task) return `\u274C Task not found: \`${args.taskId}\``;
|
|
14289
|
+
if (task.status === "running") return `\u23F3 Still running...`;
|
|
14290
|
+
const result = await manager.getResult(args.taskId);
|
|
14291
|
+
const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
|
|
14292
|
+
if (task.status === "error" || task.status === "timeout") {
|
|
14293
|
+
return `\u274C ${task.status}: ${task.error}`;
|
|
14294
|
+
}
|
|
14295
|
+
return `\u2705 Completed (${duration3})
|
|
16009
14296
|
|
|
16010
|
-
|
|
16011
|
-
args: {},
|
|
16012
|
-
async execute() {
|
|
16013
|
-
configManager.exportConfigs();
|
|
16014
|
-
return "";
|
|
14297
|
+
${result || "(No output)"}`;
|
|
16015
14298
|
}
|
|
16016
14299
|
});
|
|
16017
|
-
var createSetConcurrencyTool = (client) => tool({
|
|
16018
|
-
description: `Update concurrency limit for a specific agent type.
|
|
16019
14300
|
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
|
|
16023
|
-
</examples>
|
|
16024
|
-
|
|
16025
|
-
<notes>
|
|
16026
|
-
- Changes take effect immediately
|
|
16027
|
-
- Queued tasks will start when slots become available
|
|
16028
|
-
- Limit cannot exceed global max (10)
|
|
16029
|
-
</notes>`,
|
|
14301
|
+
// src/tools/parallel/list-tasks.ts
|
|
14302
|
+
var createListTasksTool = (manager) => tool({
|
|
14303
|
+
description: `List all background tasks.`,
|
|
16030
14304
|
args: {
|
|
16031
|
-
|
|
16032
|
-
limit: tool.schema.string().describe("New concurrency limit (number)")
|
|
14305
|
+
status: tool.schema.string().optional().describe("Filter: all, running, completed, error")
|
|
16033
14306
|
},
|
|
16034
14307
|
async execute(args) {
|
|
16035
|
-
const {
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
14308
|
+
const { status = "all" } = args;
|
|
14309
|
+
let tasks;
|
|
14310
|
+
switch (status) {
|
|
14311
|
+
case "running":
|
|
14312
|
+
tasks = manager.getRunningTasks();
|
|
14313
|
+
break;
|
|
14314
|
+
case "completed":
|
|
14315
|
+
tasks = manager.getAllTasks().filter((t) => t.status === "completed");
|
|
14316
|
+
break;
|
|
14317
|
+
case "error":
|
|
14318
|
+
tasks = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout");
|
|
14319
|
+
break;
|
|
14320
|
+
default:
|
|
14321
|
+
tasks = manager.getAllTasks();
|
|
16043
14322
|
}
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|----------|-------|
|
|
16051
|
-
| **Agent** | ${agent} |
|
|
16052
|
-
| **New Limit** | ${numLimit} parallel tasks |
|
|
14323
|
+
if (tasks.length === 0) return `\u{1F4CB} No tasks found.`;
|
|
14324
|
+
const rows = tasks.map((t) => {
|
|
14325
|
+
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
|
|
14326
|
+
return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
|
|
14327
|
+
}).join("\n");
|
|
14328
|
+
return `\u{1F4CB} **Tasks**
|
|
16053
14329
|
|
|
16054
|
-
|
|
14330
|
+
| ID | Status | Agent | Time |
|
|
14331
|
+
|----|--------|-------|------|
|
|
14332
|
+
${rows}`;
|
|
16055
14333
|
}
|
|
16056
14334
|
});
|
|
16057
|
-
var createSetTimeoutTool = () => tool({
|
|
16058
|
-
description: `Update task timeout duration.
|
|
16059
14335
|
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
</examples>`,
|
|
14336
|
+
// src/tools/parallel/cancel-task.ts
|
|
14337
|
+
var createCancelTaskTool = (manager) => tool({
|
|
14338
|
+
description: `Cancel a running task.`,
|
|
16064
14339
|
args: {
|
|
16065
|
-
|
|
16066
|
-
cleanupDelayMinutes: tool.schema.string().optional().describe("Cleanup delay after completion in minutes (default: 5)")
|
|
14340
|
+
taskId: tool.schema.string().describe("Task ID to cancel")
|
|
16067
14341
|
},
|
|
16068
14342
|
async execute(args) {
|
|
16069
|
-
const
|
|
16070
|
-
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
return `\u274C Invalid taskTtlMinutes: "${taskTtlMinutes}". Must be number >= 1.`;
|
|
16075
|
-
}
|
|
16076
|
-
updates.taskTtlMs = ttl * 60 * 1e3;
|
|
16077
|
-
}
|
|
16078
|
-
if (cleanupDelayMinutes) {
|
|
16079
|
-
const delay = parseInt(cleanupDelayMinutes, 10);
|
|
16080
|
-
if (isNaN(delay) || delay < 0) {
|
|
16081
|
-
return `\u274C Invalid cleanupDelayMinutes: "${cleanupDelayMinutes}". Must be number >= 0.`;
|
|
16082
|
-
}
|
|
16083
|
-
updates.cleanupDelayMs = delay * 60 * 1e3;
|
|
16084
|
-
}
|
|
16085
|
-
if (Object.keys(updates).length === 0) {
|
|
16086
|
-
return "\u26A0\uFE0F No changes specified. Provide at least one parameter.";
|
|
16087
|
-
}
|
|
16088
|
-
configManager.updateParallelAgentConfig(updates);
|
|
16089
|
-
let output = "\u2705 **Timeout Configuration Updated**\n\n";
|
|
16090
|
-
if (updates.taskTtlMs) {
|
|
16091
|
-
output += `| Task TTL | ${updates.taskTtlMs / 60 / 1e3} minutes |
|
|
16092
|
-
`;
|
|
16093
|
-
}
|
|
16094
|
-
if (updates.cleanupDelayMs) {
|
|
16095
|
-
output += `| Cleanup Delay | ${updates.cleanupDelayMs / 60 / 1e3} minutes |
|
|
16096
|
-
`;
|
|
16097
|
-
}
|
|
16098
|
-
output += "\nChanges take effect immediately.";
|
|
16099
|
-
return output;
|
|
14343
|
+
const cancelled = await manager.cancelTask(args.taskId);
|
|
14344
|
+
if (cancelled) return `\u{1F6D1} Cancelled: \`${args.taskId}\``;
|
|
14345
|
+
const task = manager.getTask(args.taskId);
|
|
14346
|
+
if (task) return `\u26A0\uFE0F Cannot cancel: Task is ${task.status}`;
|
|
14347
|
+
return `\u274C Not found: \`${args.taskId}\``;
|
|
16100
14348
|
}
|
|
16101
14349
|
});
|
|
16102
|
-
var createSetDebugTool = () => tool({
|
|
16103
|
-
description: `Enable or disable debug logging.
|
|
16104
|
-
|
|
16105
|
-
<examples>
|
|
16106
|
-
set_debug({ component: "parallel_agent", enable: true })
|
|
16107
|
-
set_debug({ component: "background_task", enable: false })
|
|
16108
|
-
</examples>`,
|
|
16109
|
-
args: {
|
|
16110
|
-
component: tool.schema.string().describe('Component to debug: "parallel_agent", "background_task", or "all"'),
|
|
16111
|
-
enable: tool.schema.boolean().describe("Enable (true) or disable (false) debug logs")
|
|
16112
|
-
},
|
|
16113
|
-
async execute(args) {
|
|
16114
|
-
const { component, enable } = args;
|
|
16115
|
-
const enableValue = enable ? "true" : "false";
|
|
16116
|
-
if (component === "parallel_agent" || component === "all") {
|
|
16117
|
-
process.env.OPENCODE_DEBUG_PARALLEL = enableValue;
|
|
16118
|
-
configManager.updateParallelAgentConfig({
|
|
16119
|
-
enableDebug: enable
|
|
16120
|
-
});
|
|
16121
|
-
}
|
|
16122
|
-
if (component === "background_task" || component === "all") {
|
|
16123
|
-
process.env.OPENCODE_DEBUG_BACKGROUND = enableValue;
|
|
16124
|
-
configManager.updateBackgroundTaskConfig({
|
|
16125
|
-
enableDebug: enable
|
|
16126
|
-
});
|
|
16127
|
-
}
|
|
16128
|
-
return `\u2705 **Debug Logging ${enable ? "Enabled" : "Disabled"}**
|
|
16129
|
-
|
|
16130
|
-
| Component | Debug Status |
|
|
16131
|
-
|-----------|--------------|
|
|
16132
|
-
| ${component === "all" ? "parallel_agent" : component} | ${enable ? "\u{1F527} Enabled" : "\u{1F507} Disabled"} |
|
|
16133
|
-
${component === "all" ? "| background_task | \u{1F527} Enabled |" : ""}
|
|
16134
14350
|
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
});
|
|
16138
|
-
function createConfigTools(client) {
|
|
14351
|
+
// src/tools/parallel/index.ts
|
|
14352
|
+
function createAsyncAgentTools(manager, client) {
|
|
16139
14353
|
return {
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
14354
|
+
delegate_task: createDelegateTaskTool(manager, client),
|
|
14355
|
+
get_task_result: createGetTaskResultTool(manager),
|
|
14356
|
+
list_tasks: createListTasksTool(manager),
|
|
14357
|
+
cancel_task: createCancelTaskTool(manager)
|
|
16144
14358
|
};
|
|
16145
14359
|
}
|
|
16146
14360
|
|
|
@@ -16167,16 +14381,120 @@ function formatElapsedTime(startMs, endMs = Date.now()) {
|
|
|
16167
14381
|
return parts.join(" ");
|
|
16168
14382
|
}
|
|
16169
14383
|
|
|
14384
|
+
// src/utils/sanity.ts
|
|
14385
|
+
function checkOutputSanity(text) {
|
|
14386
|
+
if (!text || text.length < 50) {
|
|
14387
|
+
return { isHealthy: true, severity: "ok" };
|
|
14388
|
+
}
|
|
14389
|
+
if (/(.)\1{15,}/.test(text)) {
|
|
14390
|
+
return {
|
|
14391
|
+
isHealthy: false,
|
|
14392
|
+
reason: "Single character repetition detected",
|
|
14393
|
+
severity: "critical"
|
|
14394
|
+
};
|
|
14395
|
+
}
|
|
14396
|
+
if (/(.{2,6})\1{8,}/.test(text)) {
|
|
14397
|
+
return {
|
|
14398
|
+
isHealthy: false,
|
|
14399
|
+
reason: "Pattern loop detected",
|
|
14400
|
+
severity: "critical"
|
|
14401
|
+
};
|
|
14402
|
+
}
|
|
14403
|
+
if (text.length > 200) {
|
|
14404
|
+
const cleanText = text.replace(/\s/g, "");
|
|
14405
|
+
if (cleanText.length > 100) {
|
|
14406
|
+
const uniqueChars = new Set(cleanText).size;
|
|
14407
|
+
const ratio = uniqueChars / cleanText.length;
|
|
14408
|
+
if (ratio < 0.02) {
|
|
14409
|
+
return {
|
|
14410
|
+
isHealthy: false,
|
|
14411
|
+
reason: "Low information density",
|
|
14412
|
+
severity: "critical"
|
|
14413
|
+
};
|
|
14414
|
+
}
|
|
14415
|
+
}
|
|
14416
|
+
}
|
|
14417
|
+
const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
|
|
14418
|
+
if (boxChars > 100 && boxChars / text.length > 0.3) {
|
|
14419
|
+
return {
|
|
14420
|
+
isHealthy: false,
|
|
14421
|
+
reason: "Visual gibberish detected",
|
|
14422
|
+
severity: "critical"
|
|
14423
|
+
};
|
|
14424
|
+
}
|
|
14425
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 10);
|
|
14426
|
+
if (lines.length > 10) {
|
|
14427
|
+
const lineSet = new Set(lines);
|
|
14428
|
+
if (lineSet.size < lines.length * 0.2) {
|
|
14429
|
+
return {
|
|
14430
|
+
isHealthy: false,
|
|
14431
|
+
reason: "Excessive line repetition",
|
|
14432
|
+
severity: "warning"
|
|
14433
|
+
};
|
|
14434
|
+
}
|
|
14435
|
+
}
|
|
14436
|
+
const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
|
|
14437
|
+
if (cjkChars > 200) {
|
|
14438
|
+
const uniqueCjk = new Set(
|
|
14439
|
+
text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
|
|
14440
|
+
).size;
|
|
14441
|
+
if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
|
|
14442
|
+
return {
|
|
14443
|
+
isHealthy: false,
|
|
14444
|
+
reason: "CJK character spam detected",
|
|
14445
|
+
severity: "critical"
|
|
14446
|
+
};
|
|
14447
|
+
}
|
|
14448
|
+
}
|
|
14449
|
+
return { isHealthy: true, severity: "ok" };
|
|
14450
|
+
}
|
|
14451
|
+
var RECOVERY_PROMPT = `<anomaly_recovery>
|
|
14452
|
+
\u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
|
|
14453
|
+
|
|
14454
|
+
<recovery_protocol>
|
|
14455
|
+
1. DISCARD the corrupted output completely - do not reference it
|
|
14456
|
+
2. RECALL the original mission objective
|
|
14457
|
+
3. IDENTIFY the last confirmed successful step
|
|
14458
|
+
4. RESTART with a simpler, more focused approach
|
|
14459
|
+
</recovery_protocol>
|
|
14460
|
+
|
|
14461
|
+
<instructions>
|
|
14462
|
+
- If a sub-agent produced bad output: try a different agent or simpler task
|
|
14463
|
+
- If stuck in a loop: break down the task into smaller pieces
|
|
14464
|
+
- If context seems corrupted: call recorder to restore context
|
|
14465
|
+
- THINK in English for maximum stability
|
|
14466
|
+
</instructions>
|
|
14467
|
+
|
|
14468
|
+
What was the original task? Proceed from the last known good state.
|
|
14469
|
+
</anomaly_recovery>`;
|
|
14470
|
+
var ESCALATION_PROMPT = `<critical_anomaly>
|
|
14471
|
+
\u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
|
|
14472
|
+
|
|
14473
|
+
<emergency_protocol>
|
|
14474
|
+
1. STOP current execution path immediately
|
|
14475
|
+
2. DO NOT continue with the same approach - it is failing
|
|
14476
|
+
3. CALL architect for a completely new strategy
|
|
14477
|
+
4. If architect also fails: report status to user and await guidance
|
|
14478
|
+
</emergency_protocol>
|
|
14479
|
+
|
|
14480
|
+
<diagnosis>
|
|
14481
|
+
The current approach is producing corrupted output.
|
|
14482
|
+
This may indicate: context overload, model instability, or task complexity.
|
|
14483
|
+
</diagnosis>
|
|
14484
|
+
|
|
14485
|
+
Request a fresh plan from architect with reduced scope.
|
|
14486
|
+
</critical_anomaly>`;
|
|
14487
|
+
|
|
16170
14488
|
// src/index.ts
|
|
16171
14489
|
var PLUGIN_VERSION = "0.2.4";
|
|
16172
14490
|
var DEFAULT_MAX_STEPS = 500;
|
|
16173
14491
|
var TASK_COMMAND_MAX_STEPS = 1e3;
|
|
16174
14492
|
var AGENT_EMOJI2 = {
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
14493
|
+
"architect": "\u{1F3D7}\uFE0F",
|
|
14494
|
+
"builder": "\u{1F528}",
|
|
14495
|
+
"inspector": "\u{1F50D}",
|
|
14496
|
+
"recorder": "\u{1F4BE}",
|
|
14497
|
+
"commander": "\u{1F3AF}"
|
|
16180
14498
|
};
|
|
16181
14499
|
var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
16182
14500
|
<status>Mission not complete. Keep executing.</status>
|
|
@@ -16200,8 +14518,6 @@ var OrchestratorPlugin = async (input) => {
|
|
|
16200
14518
|
const sessions = /* @__PURE__ */ new Map();
|
|
16201
14519
|
const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
|
|
16202
14520
|
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
|
|
16203
|
-
const batchTools = createBatchTools(parallelAgentManager2, client);
|
|
16204
|
-
const configTools = createConfigTools(client);
|
|
16205
14521
|
return {
|
|
16206
14522
|
// -----------------------------------------------------------------
|
|
16207
14523
|
// Tools we expose to the LLM
|
|
@@ -16219,13 +14535,7 @@ var OrchestratorPlugin = async (input) => {
|
|
|
16219
14535
|
list_background: listBackgroundTool,
|
|
16220
14536
|
kill_background: killBackgroundTool,
|
|
16221
14537
|
// Async agent tools - spawn agents in parallel sessions
|
|
16222
|
-
...asyncAgentTools
|
|
16223
|
-
// Git tools - branch info and status
|
|
16224
|
-
git_branch: gitBranchTool(directory),
|
|
16225
|
-
// Smart batch tools - centralized validation and retry
|
|
16226
|
-
...batchTools,
|
|
16227
|
-
// Configuration tools - dynamic runtime settings
|
|
16228
|
-
...configTools
|
|
14538
|
+
...asyncAgentTools
|
|
16229
14539
|
},
|
|
16230
14540
|
// -----------------------------------------------------------------
|
|
16231
14541
|
// Config hook - registers our commands and agents with OpenCode
|
|
@@ -16243,7 +14553,7 @@ var OrchestratorPlugin = async (input) => {
|
|
|
16243
14553
|
}
|
|
16244
14554
|
const orchestratorAgents = {
|
|
16245
14555
|
Commander: {
|
|
16246
|
-
name:
|
|
14556
|
+
name: "Commander",
|
|
16247
14557
|
description: "Autonomous orchestrator - executes until mission complete",
|
|
16248
14558
|
systemPrompt: AGENTS.commander.systemPrompt
|
|
16249
14559
|
}
|
|
@@ -16263,7 +14573,7 @@ var OrchestratorPlugin = async (input) => {
|
|
|
16263
14573
|
const parsed = detectSlashCommand(originalText);
|
|
16264
14574
|
const sessionID = msgInput.sessionID;
|
|
16265
14575
|
const agentName = (msgInput.agent || "").toLowerCase();
|
|
16266
|
-
if (agentName ===
|
|
14576
|
+
if (agentName === "commander" && !sessions.has(sessionID)) {
|
|
16267
14577
|
const now = Date.now();
|
|
16268
14578
|
sessions.set(sessionID, {
|
|
16269
14579
|
active: true,
|
|
@@ -16537,16 +14847,16 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
16537
14847
|
// Event handler - cleans up when sessions are deleted
|
|
16538
14848
|
// -----------------------------------------------------------------
|
|
16539
14849
|
handler: async ({ event }) => {
|
|
14850
|
+
try {
|
|
14851
|
+
const manager = ParallelAgentManager.getInstance();
|
|
14852
|
+
manager.handleEvent(event);
|
|
14853
|
+
} catch {
|
|
14854
|
+
}
|
|
16540
14855
|
if (event.type === "session.deleted") {
|
|
16541
14856
|
const props = event.properties;
|
|
16542
14857
|
if (props?.info?.id) {
|
|
16543
|
-
|
|
16544
|
-
sessions.delete(
|
|
16545
|
-
state.sessions.delete(sessionID);
|
|
16546
|
-
const cleanedCount = backgroundTaskManager.cleanupBySession(sessionID);
|
|
16547
|
-
if (cleanedCount > 0) {
|
|
16548
|
-
console.log(`[background] Cleaned up ${cleanedCount} tasks for deleted session ${sessionID}`);
|
|
16549
|
-
}
|
|
14858
|
+
sessions.delete(props.info.id);
|
|
14859
|
+
state.sessions.delete(props.info.id);
|
|
16550
14860
|
}
|
|
16551
14861
|
}
|
|
16552
14862
|
}
|