opencode-orchestrator 0.5.2 → 0.5.4
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/coder.d.ts +2 -0
- package/dist/agents/definitions.d.ts +0 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/names.d.ts +12 -0
- package/dist/agents/orchestrator.d.ts +0 -0
- package/dist/agents/planner.d.ts +2 -0
- package/dist/agents/reviewer.d.ts +2 -0
- package/dist/agents/searcher.d.ts +2 -0
- package/dist/agents/subagents/architect.d.ts +0 -0
- package/dist/agents/subagents/builder.d.ts +0 -0
- package/dist/agents/subagents/coder.d.ts +2 -0
- package/dist/agents/subagents/executor.d.ts +2 -0
- package/dist/agents/subagents/fixer.d.ts +2 -0
- package/dist/agents/subagents/inspector.d.ts +0 -0
- package/dist/agents/subagents/memory.d.ts +2 -0
- package/dist/agents/subagents/planner.d.ts +2 -0
- package/dist/agents/subagents/publisher.d.ts +2 -0
- package/dist/agents/subagents/recorder.d.ts +0 -0
- package/dist/agents/subagents/reviewer.d.ts +2 -0
- package/dist/agents/subagents/searcher.d.ts +2 -0
- package/dist/agents/subagents/strategist.d.ts +2 -0
- package/dist/agents/subagents/surgeon.d.ts +2 -0
- package/dist/agents/subagents/types.d.ts +7 -0
- package/dist/agents/subagents/visualist.d.ts +2 -0
- package/dist/agents/types.d.ts +7 -0
- package/dist/cli.d.ts +2 -0
- package/dist/core/async-agent.d.ts +2 -14
- package/dist/core/background.d.ts +0 -33
- package/dist/core/state.d.ts +0 -0
- package/dist/core/tasks.d.ts +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +342 -1128
- package/dist/shared/contracts/interfaces.d.ts +0 -0
- package/dist/shared/contracts/names.d.ts +0 -0
- package/dist/tasks.d.ts +29 -0
- package/dist/tools/background.d.ts +0 -2
- package/dist/tools/callAgent.d.ts +0 -0
- package/dist/tools/git.d.ts +2 -2
- package/dist/tools/rust.d.ts +0 -0
- package/dist/tools/search.d.ts +0 -0
- package/dist/tools/slashCommand.d.ts +0 -0
- package/dist/utils/binary.d.ts +0 -0
- package/dist/utils/common.d.ts +0 -0
- package/dist/utils/sanity.d.ts +22 -0
- package/package.json +6 -10
package/dist/index.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
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 });
|
|
@@ -25,159 +19,6 @@ var AGENT_NAMES = {
|
|
|
25
19
|
// Persistent context - saves/loads session state
|
|
26
20
|
};
|
|
27
21
|
|
|
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
22
|
// src/agents/orchestrator.ts
|
|
182
23
|
var orchestrator = {
|
|
183
24
|
id: AGENT_NAMES.COMMANDER,
|
|
@@ -186,16 +27,19 @@ var orchestrator = {
|
|
|
186
27
|
You are Commander. Complete missions autonomously. Never stop until done.
|
|
187
28
|
</role>
|
|
188
29
|
|
|
189
|
-
${REASONING_CONSTRAINTS}
|
|
190
|
-
|
|
191
|
-
${LANGUAGE_RULE}
|
|
192
|
-
|
|
193
30
|
<core_rules>
|
|
194
31
|
1. Never stop until "\u2705 MISSION COMPLETE"
|
|
195
32
|
2. Never wait for user during execution
|
|
196
33
|
3. Never stop because agent returned nothing
|
|
197
34
|
4. Always survey environment & codebase BEFORE coding
|
|
198
35
|
5. Always verify with evidence based on runtime context
|
|
36
|
+
6. LANGUAGE:
|
|
37
|
+
- THINK and REASON in English for maximum stability
|
|
38
|
+
- FINAL REPORT: Detect the user's language from their request and respond in the SAME language
|
|
39
|
+
- If user writes in Korean \u2192 Report in Korean
|
|
40
|
+
- If user writes in English \u2192 Report in English
|
|
41
|
+
- If user writes in Japanese \u2192 Report in Japanese
|
|
42
|
+
- Default to English if language is unclear
|
|
199
43
|
</core_rules>
|
|
200
44
|
|
|
201
45
|
<phase_0 name="TRIAGE">
|
|
@@ -255,8 +99,8 @@ PREFER background=true (PARALLEL):
|
|
|
255
99
|
EXAMPLE - PARALLEL:
|
|
256
100
|
\`\`\`
|
|
257
101
|
// Multiple tasks in parallel
|
|
258
|
-
delegate_task({ agent: "
|
|
259
|
-
delegate_task({ agent: "
|
|
102
|
+
delegate_task({ agent: "builder", description: "Implement X", prompt: "...", background: true })
|
|
103
|
+
delegate_task({ agent: "inspector", description: "Review Y", prompt: "...", background: true })
|
|
260
104
|
|
|
261
105
|
// Continue other work (don't wait!)
|
|
262
106
|
|
|
@@ -267,7 +111,7 @@ get_task_result({ taskId: "task_xxx" })
|
|
|
267
111
|
EXAMPLE - SYNC (rare):
|
|
268
112
|
\`\`\`
|
|
269
113
|
// Only when you absolutely need the result now
|
|
270
|
-
const result = delegate_task({ agent: "
|
|
114
|
+
const result = delegate_task({ agent: "builder", ..., background: false })
|
|
271
115
|
// Result is immediately available
|
|
272
116
|
\`\`\`
|
|
273
117
|
</agent_calling>
|
|
@@ -294,10 +138,10 @@ During implementation:
|
|
|
294
138
|
PARALLEL EXECUTION TOOLS:
|
|
295
139
|
|
|
296
140
|
1. **spawn_agent** - Launch agents in parallel sessions
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
141
|
+
spawn_agent({ agent: "builder", description: "Implement X", prompt: "..." })
|
|
142
|
+
spawn_agent({ agent: "inspector", description: "Review Y", prompt: "..." })
|
|
143
|
+
\u2192 Agents run concurrently, system notifies when ALL complete
|
|
144
|
+
\u2192 Use get_task_result({ taskId }) to retrieve results
|
|
301
145
|
|
|
302
146
|
2. **run_background** - Run shell commands asynchronously
|
|
303
147
|
run_background({ command: "npm run build" })
|
|
@@ -348,10 +192,10 @@ WORKFLOW:
|
|
|
348
192
|
<empty_responses>
|
|
349
193
|
| Agent Empty (or Gibberish) | Action |
|
|
350
194
|
|----------------------------|--------|
|
|
351
|
-
|
|
|
352
|
-
|
|
|
353
|
-
|
|
|
354
|
-
|
|
|
195
|
+
| recorder | Fresh start. Proceed to survey. |
|
|
196
|
+
| architect | Try simpler plan yourself. |
|
|
197
|
+
| builder | Call inspector to diagnose. |
|
|
198
|
+
| inspector | Retry with more context. |
|
|
355
199
|
</empty_responses>
|
|
356
200
|
|
|
357
201
|
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,169 +207,6 @@ STRICT RULE: If any agent output contains gibberish, mixed-language hallucinatio
|
|
|
363
207
|
\u274C Make random changes without understanding root cause
|
|
364
208
|
</anti_patterns>
|
|
365
209
|
|
|
366
|
-
${WORKFLOW}
|
|
367
|
-
|
|
368
|
-
<phase_0 name="TRIAGE">
|
|
369
|
-
Evaluate the complexity of the request:
|
|
370
|
-
|
|
371
|
-
| Level | Signal | Track |
|
|
372
|
-
|-------|--------|-------|
|
|
373
|
-
| \u{1F7E2} L1: Simple | One file, clear fix, no dependencies | **FAST TRACK** |
|
|
374
|
-
| \u{1F7E1} L2: Feature | New functionality, clear patterns | **NORMAL TRACK** |
|
|
375
|
-
| \u{1F534} L3: Complex | Refactoring, infra change, unknown scope | **DEEP TRACK** |
|
|
376
|
-
</phase_0>
|
|
377
|
-
|
|
378
|
-
<phase_1 name="CONTEXT_GATHERING">
|
|
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.
|
|
383
|
-
|
|
384
|
-
IF NORMAL/DEEP TRACK (L2/L3):
|
|
385
|
-
- **Deep Scan Required**: Execute the full "MANDATORY ENVIRONMENT SCAN".
|
|
386
|
-
- 1. Infra check (Docker/OS)
|
|
387
|
-
- 2. Domain & Stack check
|
|
388
|
-
- 3. Pattern check
|
|
389
|
-
|
|
390
|
-
RECORD findings if on Deep Track.
|
|
391
|
-
</phase_1>
|
|
392
|
-
|
|
393
|
-
<phase_2 name="TOOL_AGENT_SELECTION">
|
|
394
|
-
| Track | Strategy |
|
|
395
|
-
|-------|----------|
|
|
396
|
-
| Fast | Use \`builder\` directly. Skip \`architect\`. |
|
|
397
|
-
| Normal | Call \`architect\` for lightweight plan. |
|
|
398
|
-
| Deep | Full \`architect\` DAG + \`recorder\` state tracking. |
|
|
399
|
-
|
|
400
|
-
DEFAULT to Deep Track if unsure to act safely.
|
|
401
|
-
</phase_2>
|
|
402
|
-
|
|
403
|
-
<phase_3 name="DELEGATION">
|
|
404
|
-
<agent_calling>
|
|
405
|
-
CRITICAL: USE delegate_task FOR ALL DELEGATION
|
|
406
|
-
|
|
407
|
-
delegate_task has TWO MODES:
|
|
408
|
-
- background=true: Non-blocking, parallel execution
|
|
409
|
-
- background=false: Blocking, waits for result
|
|
410
|
-
|
|
411
|
-
| Situation | How to Call |
|
|
412
|
-
|-----------|-------------|
|
|
413
|
-
| Multiple independent tasks | \`delegate_task({ ..., background: true })\` for each |
|
|
414
|
-
| Single task, continue working | \`delegate_task({ ..., background: true })\` |
|
|
415
|
-
| Need result for VERY next step | \`delegate_task({ ..., background: false })\` |
|
|
416
|
-
|
|
417
|
-
PREFER background=true (PARALLEL):
|
|
418
|
-
- Run multiple agents simultaneously
|
|
419
|
-
- Continue analysis while they work
|
|
420
|
-
- System notifies when ALL complete
|
|
421
|
-
|
|
422
|
-
EXAMPLE - PARALLEL:
|
|
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 })
|
|
427
|
-
|
|
428
|
-
// Continue other work (don't wait!)
|
|
429
|
-
|
|
430
|
-
// When notified "All Complete":
|
|
431
|
-
get_task_result({ taskId: "task_xxx" })
|
|
432
|
-
\`\`\`
|
|
433
|
-
|
|
434
|
-
EXAMPLE - SYNC (rare):
|
|
435
|
-
\`\`\`
|
|
436
|
-
// Only when you absolutely need the result now
|
|
437
|
-
const result = delegate_task({ agent: "${AGENT_NAMES.BUILDER}", ..., background: false })
|
|
438
|
-
// Result is immediately available
|
|
439
|
-
\`\`\`
|
|
440
|
-
</agent_calling>
|
|
441
|
-
|
|
442
|
-
<delegation_template>
|
|
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>
|
|
454
|
-
|
|
455
|
-
<phase_4 name="EXECUTION_VERIFICATION">
|
|
456
|
-
During implementation:
|
|
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
210
|
<completion>
|
|
530
211
|
Done when: Request fulfilled + lsp clean + build/test/audit pass.
|
|
531
212
|
|
|
@@ -547,9 +228,10 @@ var architect = {
|
|
|
547
228
|
You are Architect. Break complex tasks into atomic pieces.
|
|
548
229
|
</role>
|
|
549
230
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
231
|
+
<constraints>
|
|
232
|
+
Reasoning MUST be in English for model stability.
|
|
233
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
234
|
+
</constraints>
|
|
553
235
|
|
|
554
236
|
<scalable_planning>
|
|
555
237
|
- **Fast Track**: Skip JSON overhead. Just acknowledge simple task.
|
|
@@ -568,67 +250,25 @@ ${WORKFLOW}
|
|
|
568
250
|
4. Assign: builder (code) or inspector (verify)
|
|
569
251
|
|
|
570
252
|
<output_format>
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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>
|
|
253
|
+
MISSION: [goal in one line]
|
|
254
|
+
|
|
255
|
+
T1: [action] | builder | [file] | group:1 | success:[how to verify]
|
|
256
|
+
T2: [action] | builder | [file] | group:1 | success:[how to verify]
|
|
257
|
+
T3: [action] | inspector | [files] | group:2 | depends:T1,T2 | success:[verify method]
|
|
607
258
|
</output_format>
|
|
608
259
|
</plan_mode>
|
|
609
260
|
|
|
610
261
|
<strategy_mode trigger="failures > 2">
|
|
611
262
|
<output_format>
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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>
|
|
263
|
+
FAILED ATTEMPTS:
|
|
264
|
+
- [what was tried] \u2192 [why failed]
|
|
265
|
+
|
|
266
|
+
ROOT CAUSE: [actual problem]
|
|
267
|
+
|
|
268
|
+
NEW APPROACH: [different strategy]
|
|
269
|
+
|
|
270
|
+
REVISED TASKS:
|
|
271
|
+
T1: ...
|
|
632
272
|
</output_format>
|
|
633
273
|
</strategy_mode>
|
|
634
274
|
|
|
@@ -637,7 +277,6 @@ MUST output valid JSON block wrapped in backticks:
|
|
|
637
277
|
- Always end with inspector task
|
|
638
278
|
- Group unrelated tasks (parallel)
|
|
639
279
|
- Be specific about files and verification
|
|
640
|
-
- Output MUST be valid JSON wrapped in <json_output> tags
|
|
641
280
|
</rules>`,
|
|
642
281
|
canWrite: false,
|
|
643
282
|
canBash: false
|
|
@@ -651,11 +290,10 @@ var builder = {
|
|
|
651
290
|
You are Builder. Write code that works.
|
|
652
291
|
</role>
|
|
653
292
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
${ANTI_PATTERNS}
|
|
293
|
+
<constraints>
|
|
294
|
+
Reasoning MUST be in English for model stability.
|
|
295
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
296
|
+
</constraints>
|
|
659
297
|
|
|
660
298
|
<scalable_attention>
|
|
661
299
|
- **Simple Fix (L1)**: Read file \u2192 Implement fix directly. Efficiency first.
|
|
@@ -718,11 +356,10 @@ var inspector = {
|
|
|
718
356
|
You are Inspector. Prove failure or success with evidence.
|
|
719
357
|
</role>
|
|
720
358
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
${ANTI_PATTERNS}
|
|
359
|
+
<constraints>
|
|
360
|
+
Reasoning MUST be in English for model stability.
|
|
361
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
362
|
+
</constraints>
|
|
726
363
|
|
|
727
364
|
<scalable_audit>
|
|
728
365
|
- **Fast Track**: Verify syntax + quick logic check.
|
|
@@ -786,7 +423,10 @@ var recorder = {
|
|
|
786
423
|
You are Recorder. Save and load work progress.
|
|
787
424
|
</role>
|
|
788
425
|
|
|
789
|
-
|
|
426
|
+
<constraints>
|
|
427
|
+
Reasoning MUST be in English for model stability.
|
|
428
|
+
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
429
|
+
</constraints>
|
|
790
430
|
|
|
791
431
|
<purpose>
|
|
792
432
|
Context can be lost between sessions. You save it to disk.
|
|
@@ -916,20 +556,12 @@ var TaskGraph = class _TaskGraph {
|
|
|
916
556
|
}
|
|
917
557
|
};
|
|
918
558
|
|
|
919
|
-
// src/constants/time.ts
|
|
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
559
|
// src/core/state.ts
|
|
928
560
|
var state = {
|
|
929
561
|
missionActive: false,
|
|
930
562
|
maxIterations: 1e3,
|
|
931
563
|
// Effectively infinite - "Relentless" mode
|
|
932
|
-
maxRetries:
|
|
564
|
+
maxRetries: 3,
|
|
933
565
|
sessions: /* @__PURE__ */ new Map()
|
|
934
566
|
};
|
|
935
567
|
|
|
@@ -13355,113 +12987,6 @@ function tool(input) {
|
|
|
13355
12987
|
}
|
|
13356
12988
|
tool.schema = external_exports;
|
|
13357
12989
|
|
|
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
12990
|
// src/tools/callAgent.ts
|
|
13466
12991
|
var AGENT_EMOJI = {
|
|
13467
12992
|
[AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
|
|
@@ -13500,19 +13025,13 @@ var callAgentTool = tool({
|
|
|
13500
13025
|
async execute(args) {
|
|
13501
13026
|
const agentDef = AGENTS[args.agent];
|
|
13502
13027
|
if (!agentDef) {
|
|
13503
|
-
return
|
|
13028
|
+
return `\u274C Error: Unknown agent: ${args.agent}`;
|
|
13504
13029
|
}
|
|
13505
13030
|
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
13031
|
const prompt = `
|
|
13513
|
-
|
|
13032
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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
13033
|
${emoji3} ${agentDef.id.toUpperCase()} :: ${agentDef.description}
|
|
13515
|
-
|
|
13034
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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
13035
|
|
|
13517
13036
|
<system>
|
|
13518
13037
|
${agentDef.systemPrompt}
|
|
@@ -13522,7 +13041,9 @@ ${agentDef.systemPrompt}
|
|
|
13522
13041
|
${args.task}
|
|
13523
13042
|
</task>
|
|
13524
13043
|
|
|
13525
|
-
${args.context ?
|
|
13044
|
+
${args.context ? `<context>
|
|
13045
|
+
${args.context}
|
|
13046
|
+
</context>` : ""}
|
|
13526
13047
|
|
|
13527
13048
|
<execution>
|
|
13528
13049
|
Follow this pattern:
|
|
@@ -13801,154 +13322,25 @@ Returns matches grouped by pattern, with file paths and line numbers.
|
|
|
13801
13322
|
// src/core/background.ts
|
|
13802
13323
|
import { spawn as spawn2 } from "child_process";
|
|
13803
13324
|
import { randomBytes } from "crypto";
|
|
13804
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
13805
|
-
import { join as join2 } from "path";
|
|
13806
|
-
import { homedir } from "os";
|
|
13807
13325
|
var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
13808
13326
|
static _instance;
|
|
13809
13327
|
tasks = /* @__PURE__ */ new Map();
|
|
13810
13328
|
debugMode = true;
|
|
13811
13329
|
// Enable debug mode
|
|
13812
|
-
storageDir;
|
|
13813
|
-
storageFile;
|
|
13814
|
-
monitoringInterval;
|
|
13815
13330
|
constructor() {
|
|
13816
|
-
this.storageDir = join2(homedir(), ".opencode-orchestrator");
|
|
13817
|
-
this.storageFile = join2(this.storageDir, "tasks.json");
|
|
13818
|
-
this.loadFromDisk();
|
|
13819
|
-
this.startMonitoring();
|
|
13820
|
-
}
|
|
13821
|
-
static get instance() {
|
|
13822
|
-
if (!_BackgroundTaskManager._instance) {
|
|
13823
|
-
_BackgroundTaskManager._instance = new _BackgroundTaskManager();
|
|
13824
|
-
}
|
|
13825
|
-
return _BackgroundTaskManager._instance;
|
|
13826
|
-
}
|
|
13827
|
-
/**
|
|
13828
|
-
* Generate a unique task ID in the format job_xxxxxxxx
|
|
13829
|
-
*/
|
|
13830
|
-
generateId() {
|
|
13831
|
-
const hex3 = randomBytes(4).toString("hex");
|
|
13832
|
-
return `job_${hex3}`;
|
|
13833
|
-
}
|
|
13834
|
-
/**
|
|
13835
|
-
* Ensure storage directory exists
|
|
13836
|
-
*/
|
|
13837
|
-
ensureStorageDir() {
|
|
13838
|
-
if (!existsSync3(this.storageDir)) {
|
|
13839
|
-
mkdirSync(this.storageDir, { recursive: true });
|
|
13840
|
-
this.debug("system", `Created storage directory: ${this.storageDir}`);
|
|
13841
|
-
}
|
|
13842
|
-
}
|
|
13843
|
-
/**
|
|
13844
|
-
* Load tasks from disk on startup
|
|
13845
|
-
*/
|
|
13846
|
-
loadFromDisk() {
|
|
13847
|
-
this.ensureStorageDir();
|
|
13848
|
-
if (!existsSync3(this.storageFile)) {
|
|
13849
|
-
this.debug("system", "No existing task data on disk");
|
|
13850
|
-
return;
|
|
13851
|
-
}
|
|
13852
|
-
try {
|
|
13853
|
-
const data = readFileSync(this.storageFile, "utf-8");
|
|
13854
|
-
const tasksData = JSON.parse(data);
|
|
13855
|
-
for (const [id, taskData] of Object.entries(tasksData)) {
|
|
13856
|
-
const task = taskData;
|
|
13857
|
-
task.process = void 0;
|
|
13858
|
-
if (task.status === "running") {
|
|
13859
|
-
task.status = "error";
|
|
13860
|
-
task.errorOutput += "\n[Process lost on restart]";
|
|
13861
|
-
task.endTime = Date.now();
|
|
13862
|
-
task.exitCode = null;
|
|
13863
|
-
}
|
|
13864
|
-
this.tasks.set(id, task);
|
|
13865
|
-
}
|
|
13866
|
-
this.debug("system", `Loaded ${this.tasks.size} tasks from disk`);
|
|
13867
|
-
} catch (error45) {
|
|
13868
|
-
this.debug(
|
|
13869
|
-
"system",
|
|
13870
|
-
`Failed to load tasks: ${error45 instanceof Error ? error45.message : String(error45)}`
|
|
13871
|
-
);
|
|
13872
|
-
}
|
|
13873
|
-
}
|
|
13874
|
-
/**
|
|
13875
|
-
* Save tasks to disk
|
|
13876
|
-
*/
|
|
13877
|
-
saveToDisk() {
|
|
13878
|
-
this.ensureStorageDir();
|
|
13879
|
-
try {
|
|
13880
|
-
const tasksData = {};
|
|
13881
|
-
for (const [id, task] of this.tasks.entries()) {
|
|
13882
|
-
tasksData[id] = task;
|
|
13883
|
-
}
|
|
13884
|
-
writeFileSync(
|
|
13885
|
-
this.storageFile,
|
|
13886
|
-
JSON.stringify(tasksData, null, 2),
|
|
13887
|
-
"utf-8"
|
|
13888
|
-
);
|
|
13889
|
-
} catch (error45) {
|
|
13890
|
-
this.debug(
|
|
13891
|
-
"system",
|
|
13892
|
-
`Failed to save tasks: ${error45 instanceof Error ? error45.message : String(error45)}`
|
|
13893
|
-
);
|
|
13894
|
-
}
|
|
13895
|
-
}
|
|
13896
|
-
/**
|
|
13897
|
-
* Start periodic monitoring of running processes
|
|
13898
|
-
*/
|
|
13899
|
-
startMonitoring() {
|
|
13900
|
-
const MONITOR_INTERVAL_MS = 5e3;
|
|
13901
|
-
this.monitoringInterval = setInterval(() => {
|
|
13902
|
-
this.monitorRunningProcesses();
|
|
13903
|
-
}, MONITOR_INTERVAL_MS);
|
|
13904
|
-
if (this.monitoringInterval) {
|
|
13905
|
-
this.monitoringInterval.unref();
|
|
13906
|
-
}
|
|
13907
13331
|
}
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
stopMonitoring() {
|
|
13912
|
-
if (this.monitoringInterval) {
|
|
13913
|
-
clearInterval(this.monitoringInterval);
|
|
13914
|
-
this.monitoringInterval = void 0;
|
|
13332
|
+
static get instance() {
|
|
13333
|
+
if (!_BackgroundTaskManager._instance) {
|
|
13334
|
+
_BackgroundTaskManager._instance = new _BackgroundTaskManager();
|
|
13915
13335
|
}
|
|
13336
|
+
return _BackgroundTaskManager._instance;
|
|
13916
13337
|
}
|
|
13917
13338
|
/**
|
|
13918
|
-
*
|
|
13339
|
+
* Generate a unique task ID in the format job_xxxxxxxx
|
|
13919
13340
|
*/
|
|
13920
|
-
|
|
13921
|
-
const
|
|
13922
|
-
|
|
13923
|
-
for (const [id, task] of this.tasks.entries()) {
|
|
13924
|
-
if (task.status !== "running") continue;
|
|
13925
|
-
hasRunningTasks = true;
|
|
13926
|
-
if (task.process && task.process.pid) {
|
|
13927
|
-
const pid = task.process.pid;
|
|
13928
|
-
try {
|
|
13929
|
-
process.kill(pid, 0);
|
|
13930
|
-
} catch (error45) {
|
|
13931
|
-
task.status = "error";
|
|
13932
|
-
task.errorOutput += `
|
|
13933
|
-
Process disappeared (PID ${pid})`;
|
|
13934
|
-
task.endTime = Date.now();
|
|
13935
|
-
task.exitCode = null;
|
|
13936
|
-
task.process = void 0;
|
|
13937
|
-
this.saveToDisk();
|
|
13938
|
-
this.debug(id, `Process dead (PID ${pid}), marked as error`);
|
|
13939
|
-
}
|
|
13940
|
-
} else if (task.process) {
|
|
13941
|
-
task.status = "error";
|
|
13942
|
-
task.errorOutput += "\nProcess reference lost";
|
|
13943
|
-
task.endTime = Date.now();
|
|
13944
|
-
task.exitCode = null;
|
|
13945
|
-
this.saveToDisk();
|
|
13946
|
-
this.debug(id, "Process reference lost, marked as error");
|
|
13947
|
-
}
|
|
13948
|
-
}
|
|
13949
|
-
if (!hasRunningTasks && this.monitoringInterval) {
|
|
13950
|
-
this.stopMonitoring();
|
|
13951
|
-
}
|
|
13341
|
+
generateId() {
|
|
13342
|
+
const hex3 = randomBytes(4).toString("hex");
|
|
13343
|
+
return `job_${hex3}`;
|
|
13952
13344
|
}
|
|
13953
13345
|
/**
|
|
13954
13346
|
* Debug logging helper
|
|
@@ -13982,7 +13374,6 @@ Process disappeared (PID ${pid})`;
|
|
|
13982
13374
|
timeout
|
|
13983
13375
|
};
|
|
13984
13376
|
this.tasks.set(id, task);
|
|
13985
|
-
this.saveToDisk();
|
|
13986
13377
|
this.debug(id, `Starting: ${command} (cwd: ${cwd})`);
|
|
13987
13378
|
try {
|
|
13988
13379
|
const proc = spawn2(shell, task.args, {
|
|
@@ -13994,25 +13385,18 @@ Process disappeared (PID ${pid})`;
|
|
|
13994
13385
|
proc.stdout?.on("data", (data) => {
|
|
13995
13386
|
const text = data.toString();
|
|
13996
13387
|
task.output += text;
|
|
13997
|
-
this.debug(
|
|
13998
|
-
id,
|
|
13999
|
-
`stdout: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`
|
|
14000
|
-
);
|
|
13388
|
+
this.debug(id, `stdout: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
|
|
14001
13389
|
});
|
|
14002
13390
|
proc.stderr?.on("data", (data) => {
|
|
14003
13391
|
const text = data.toString();
|
|
14004
13392
|
task.errorOutput += text;
|
|
14005
|
-
this.debug(
|
|
14006
|
-
id,
|
|
14007
|
-
`stderr: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`
|
|
14008
|
-
);
|
|
13393
|
+
this.debug(id, `stderr: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
|
|
14009
13394
|
});
|
|
14010
13395
|
proc.on("close", (code) => {
|
|
14011
13396
|
task.exitCode = code;
|
|
14012
13397
|
task.endTime = Date.now();
|
|
14013
13398
|
task.status = code === 0 ? "done" : "error";
|
|
14014
13399
|
task.process = void 0;
|
|
14015
|
-
this.saveToDisk();
|
|
14016
13400
|
const duration3 = ((task.endTime - task.startTime) / 1e3).toFixed(2);
|
|
14017
13401
|
this.debug(id, `Completed with code ${code} in ${duration3}s`);
|
|
14018
13402
|
});
|
|
@@ -14022,7 +13406,6 @@ Process disappeared (PID ${pid})`;
|
|
|
14022
13406
|
Process error: ${err.message}`;
|
|
14023
13407
|
task.endTime = Date.now();
|
|
14024
13408
|
task.process = void 0;
|
|
14025
|
-
this.saveToDisk();
|
|
14026
13409
|
this.debug(id, `Error: ${err.message}`);
|
|
14027
13410
|
});
|
|
14028
13411
|
setTimeout(() => {
|
|
@@ -14033,14 +13416,12 @@ Process error: ${err.message}`;
|
|
|
14033
13416
|
task.endTime = Date.now();
|
|
14034
13417
|
task.errorOutput += `
|
|
14035
13418
|
Process killed: timeout after ${timeout}ms`;
|
|
14036
|
-
this.saveToDisk();
|
|
14037
13419
|
}
|
|
14038
13420
|
}, timeout);
|
|
14039
13421
|
} catch (err) {
|
|
14040
13422
|
task.status = "error";
|
|
14041
13423
|
task.errorOutput = `Failed to spawn: ${err instanceof Error ? err.message : String(err)}`;
|
|
14042
13424
|
task.endTime = Date.now();
|
|
14043
|
-
this.saveToDisk();
|
|
14044
13425
|
this.debug(id, `Spawn failed: ${task.errorOutput}`);
|
|
14045
13426
|
}
|
|
14046
13427
|
return task;
|
|
@@ -14063,24 +13444,6 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
14063
13444
|
getByStatus(status) {
|
|
14064
13445
|
return this.getAll().filter((t) => t.status === status);
|
|
14065
13446
|
}
|
|
14066
|
-
/**
|
|
14067
|
-
* Clean up tasks by session ID
|
|
14068
|
-
*/
|
|
14069
|
-
cleanupBySession(sessionID) {
|
|
14070
|
-
let count = 0;
|
|
14071
|
-
for (const [id, task] of this.tasks) {
|
|
14072
|
-
if (task.sessionID === sessionID) {
|
|
14073
|
-
if (task.process && task.status === "running") {
|
|
14074
|
-
task.process.kill("SIGKILL");
|
|
14075
|
-
}
|
|
14076
|
-
this.tasks.delete(id);
|
|
14077
|
-
count++;
|
|
14078
|
-
this.debug(id, `Cleaned up for session ${sessionID}`);
|
|
14079
|
-
}
|
|
14080
|
-
}
|
|
14081
|
-
this.saveToDisk();
|
|
14082
|
-
return count;
|
|
14083
|
-
}
|
|
14084
13447
|
/**
|
|
14085
13448
|
* Clear completed/failed tasks
|
|
14086
13449
|
*/
|
|
@@ -14092,7 +13455,6 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
14092
13455
|
count++;
|
|
14093
13456
|
}
|
|
14094
13457
|
}
|
|
14095
|
-
this.saveToDisk();
|
|
14096
13458
|
return count;
|
|
14097
13459
|
}
|
|
14098
13460
|
/**
|
|
@@ -14105,7 +13467,6 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
14105
13467
|
task.status = "error";
|
|
14106
13468
|
task.errorOutput += "\nKilled by user";
|
|
14107
13469
|
task.endTime = Date.now();
|
|
14108
|
-
this.saveToDisk();
|
|
14109
13470
|
this.debug(taskId, "Killed by user");
|
|
14110
13471
|
return true;
|
|
14111
13472
|
}
|
|
@@ -14171,17 +13532,15 @@ The command runs asynchronously - use check_background to get results.
|
|
|
14171
13532
|
command: tool.schema.string().describe("Shell command to execute"),
|
|
14172
13533
|
cwd: tool.schema.string().optional().describe("Working directory (default: project root)"),
|
|
14173
13534
|
timeout: tool.schema.number().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
|
|
14174
|
-
label: tool.schema.string().optional().describe("Human-readable label for this task")
|
|
14175
|
-
sessionID: tool.schema.string().optional().describe("Session ID for automatic cleanup on session deletion")
|
|
13535
|
+
label: tool.schema.string().optional().describe("Human-readable label for this task")
|
|
14176
13536
|
},
|
|
14177
13537
|
async execute(args) {
|
|
14178
|
-
const { command, cwd, timeout, label
|
|
13538
|
+
const { command, cwd, timeout, label } = args;
|
|
14179
13539
|
const task = backgroundTaskManager.run({
|
|
14180
13540
|
command,
|
|
14181
13541
|
cwd: cwd || process.cwd(),
|
|
14182
13542
|
timeout: timeout || 3e5,
|
|
14183
|
-
label
|
|
14184
|
-
sessionID
|
|
13543
|
+
label
|
|
14185
13544
|
});
|
|
14186
13545
|
const displayLabel = label ? ` (${label})` : "";
|
|
14187
13546
|
return `\u{1F680} **Background Task Started**${displayLabel}
|
|
@@ -14358,264 +13717,13 @@ Duration before kill: ${backgroundTaskManager.formatDuration(task)}`;
|
|
|
14358
13717
|
}
|
|
14359
13718
|
});
|
|
14360
13719
|
|
|
14361
|
-
// src/core/config.ts
|
|
14362
|
-
var DEFAULT_PARALLEL_AGENT = {
|
|
14363
|
-
taskTtlMs: 30 * 60 * 1e3,
|
|
14364
|
-
// 30 minutes
|
|
14365
|
-
cleanupDelayMs: 5 * 60 * 1e3,
|
|
14366
|
-
// 5 minutes
|
|
14367
|
-
minStabilityMs: 5 * 1e3,
|
|
14368
|
-
// 5 seconds
|
|
14369
|
-
pollIntervalMs: 2 * 1e3,
|
|
14370
|
-
// 2 seconds
|
|
14371
|
-
defaultConcurrency: 3,
|
|
14372
|
-
maxConcurrency: 10,
|
|
14373
|
-
enableDebug: false,
|
|
14374
|
-
enableDetailedLogs: false
|
|
14375
|
-
};
|
|
14376
|
-
var DEFAULT_BACKGROUND_TASK = {
|
|
14377
|
-
monitorIntervalMs: 5 * 1e3,
|
|
14378
|
-
// 5 seconds
|
|
14379
|
-
storageDir: process.env.OPENCODE_ORCHESTRATOR_DIR || `${__require("os").homedir()}/.opencode-orchestrator`,
|
|
14380
|
-
defaultTimeoutMs: 5 * 60 * 1e3,
|
|
14381
|
-
// 5 minutes
|
|
14382
|
-
maxCompletedTasksToKeep: 100,
|
|
14383
|
-
enableDebug: true
|
|
14384
|
-
};
|
|
14385
|
-
var DEFAULT_SESSION = {
|
|
14386
|
-
defaultMaxSteps: 50,
|
|
14387
|
-
taskCommandMaxSteps: 100
|
|
14388
|
-
};
|
|
14389
|
-
var ConfigManager = class _ConfigManager {
|
|
14390
|
-
static _instance;
|
|
14391
|
-
parallelAgentConfig;
|
|
14392
|
-
backgroundTaskConfig;
|
|
14393
|
-
sessionConfig;
|
|
14394
|
-
constructor() {
|
|
14395
|
-
this.parallelAgentConfig = this.loadParallelAgentConfig();
|
|
14396
|
-
this.backgroundTaskConfig = this.loadBackgroundTaskConfig();
|
|
14397
|
-
this.sessionConfig = this.loadSessionConfig();
|
|
14398
|
-
}
|
|
14399
|
-
static getInstance() {
|
|
14400
|
-
if (!_ConfigManager._instance) {
|
|
14401
|
-
_ConfigManager._instance = new _ConfigManager();
|
|
14402
|
-
}
|
|
14403
|
-
return _ConfigManager._instance;
|
|
14404
|
-
}
|
|
14405
|
-
// ------------------------------------------------------------------------
|
|
14406
|
-
// Parallel Agent Config
|
|
14407
|
-
// ------------------------------------------------------------------------
|
|
14408
|
-
loadParallelAgentConfig() {
|
|
14409
|
-
return {
|
|
14410
|
-
...DEFAULT_PARALLEL_AGENT,
|
|
14411
|
-
taskTtlMs: this.parseEnvInt(
|
|
14412
|
-
"OPENCODE_TASK_TTL_MS",
|
|
14413
|
-
DEFAULT_PARALLEL_AGENT.taskTtlMs
|
|
14414
|
-
),
|
|
14415
|
-
cleanupDelayMs: this.parseEnvInt(
|
|
14416
|
-
"OPENCODE_CLEANUP_DELAY_MS",
|
|
14417
|
-
DEFAULT_PARALLEL_AGENT.cleanupDelayMs
|
|
14418
|
-
),
|
|
14419
|
-
minStabilityMs: this.parseEnvInt(
|
|
14420
|
-
"OPENCODE_MIN_STABILITY_MS",
|
|
14421
|
-
DEFAULT_PARALLEL_AGENT.minStabilityMs
|
|
14422
|
-
),
|
|
14423
|
-
pollIntervalMs: this.parseEnvInt(
|
|
14424
|
-
"OPENCODE_POLL_INTERVAL_MS",
|
|
14425
|
-
DEFAULT_PARALLEL_AGENT.pollIntervalMs
|
|
14426
|
-
),
|
|
14427
|
-
defaultConcurrency: this.parseEnvInt(
|
|
14428
|
-
"OPENCODE_DEFAULT_CONCURRENCY",
|
|
14429
|
-
DEFAULT_PARALLEL_AGENT.defaultConcurrency
|
|
14430
|
-
),
|
|
14431
|
-
maxConcurrency: this.parseEnvInt(
|
|
14432
|
-
"OPENCODE_MAX_CONCURRENCY",
|
|
14433
|
-
DEFAULT_PARALLEL_AGENT.maxConcurrency
|
|
14434
|
-
),
|
|
14435
|
-
enableDebug: process.env.OPENCODE_DEBUG_PARALLEL === "true",
|
|
14436
|
-
enableDetailedLogs: process.env.OPENCODE_DETAILED_LOGS === "true"
|
|
14437
|
-
};
|
|
14438
|
-
}
|
|
14439
|
-
getParallelAgentConfig() {
|
|
14440
|
-
return this.parallelAgentConfig;
|
|
14441
|
-
}
|
|
14442
|
-
// ------------------------------------------------------------------------
|
|
14443
|
-
// Background Task Config
|
|
14444
|
-
// ------------------------------------------------------------------------
|
|
14445
|
-
loadBackgroundTaskConfig() {
|
|
14446
|
-
return {
|
|
14447
|
-
...DEFAULT_BACKGROUND_TASK,
|
|
14448
|
-
monitorIntervalMs: this.parseEnvInt(
|
|
14449
|
-
"OPENCODE_MONITOR_INTERVAL_MS",
|
|
14450
|
-
DEFAULT_BACKGROUND_TASK.monitorIntervalMs
|
|
14451
|
-
),
|
|
14452
|
-
defaultTimeoutMs: this.parseEnvInt(
|
|
14453
|
-
"OPENCODE_DEFAULT_TIMEOUT_MS",
|
|
14454
|
-
DEFAULT_BACKGROUND_TASK.defaultTimeoutMs
|
|
14455
|
-
),
|
|
14456
|
-
maxCompletedTasksToKeep: this.parseEnvInt(
|
|
14457
|
-
"OPENCODE_MAX_COMPLETED_TASKS",
|
|
14458
|
-
DEFAULT_BACKGROUND_TASK.maxCompletedTasksToKeep
|
|
14459
|
-
),
|
|
14460
|
-
enableDebug: process.env.OPENCODE_DEBUG_BACKGROUND === "true"
|
|
14461
|
-
};
|
|
14462
|
-
}
|
|
14463
|
-
getBackgroundTaskConfig() {
|
|
14464
|
-
return this.backgroundTaskConfig;
|
|
14465
|
-
}
|
|
14466
|
-
// ------------------------------------------------------------------------
|
|
14467
|
-
// Session Config
|
|
14468
|
-
// ------------------------------------------------------------------------
|
|
14469
|
-
loadSessionConfig() {
|
|
14470
|
-
return {
|
|
14471
|
-
...DEFAULT_SESSION,
|
|
14472
|
-
defaultMaxSteps: this.parseEnvInt(
|
|
14473
|
-
"OPENCODE_DEFAULT_MAX_STEPS",
|
|
14474
|
-
DEFAULT_SESSION.defaultMaxSteps
|
|
14475
|
-
),
|
|
14476
|
-
taskCommandMaxSteps: this.parseEnvInt(
|
|
14477
|
-
"OPENCODE_TASK_MAX_STEPS",
|
|
14478
|
-
DEFAULT_SESSION.taskCommandMaxSteps
|
|
14479
|
-
)
|
|
14480
|
-
};
|
|
14481
|
-
}
|
|
14482
|
-
getSessionConfig() {
|
|
14483
|
-
return this.sessionConfig;
|
|
14484
|
-
}
|
|
14485
|
-
// ------------------------------------------------------------------------
|
|
14486
|
-
// Runtime Updates (Dynamic Configuration)
|
|
14487
|
-
// ------------------------------------------------------------------------
|
|
14488
|
-
/**
|
|
14489
|
-
* Update configuration at runtime
|
|
14490
|
-
* Useful for adaptive behavior or user preferences
|
|
14491
|
-
*/
|
|
14492
|
-
updateParallelAgentConfig(updates) {
|
|
14493
|
-
this.parallelAgentConfig = {
|
|
14494
|
-
...this.parallelAgentConfig,
|
|
14495
|
-
...updates
|
|
14496
|
-
};
|
|
14497
|
-
this.validateParallelAgentConfig();
|
|
14498
|
-
}
|
|
14499
|
-
updateBackgroundTaskConfig(updates) {
|
|
14500
|
-
this.backgroundTaskConfig = {
|
|
14501
|
-
...this.backgroundTaskConfig,
|
|
14502
|
-
...updates
|
|
14503
|
-
};
|
|
14504
|
-
this.validateBackgroundTaskConfig();
|
|
14505
|
-
}
|
|
14506
|
-
updateSessionConfig(updates) {
|
|
14507
|
-
this.sessionConfig = {
|
|
14508
|
-
...this.sessionConfig,
|
|
14509
|
-
...updates
|
|
14510
|
-
};
|
|
14511
|
-
this.validateSessionConfig();
|
|
14512
|
-
}
|
|
14513
|
-
// ------------------------------------------------------------------------
|
|
14514
|
-
// Validation
|
|
14515
|
-
// ------------------------------------------------------------------------
|
|
14516
|
-
validateParallelAgentConfig() {
|
|
14517
|
-
const { taskTtlMs, cleanupDelayMs, minStabilityMs, pollIntervalMs, defaultConcurrency, maxConcurrency } = this.parallelAgentConfig;
|
|
14518
|
-
if (taskTtlMs < 60 * 1e3) {
|
|
14519
|
-
console.warn("[Config] TASK_TTL_MS too low (< 1min), using 1min minimum");
|
|
14520
|
-
this.parallelAgentConfig.taskTtlMs = 60 * 1e3;
|
|
14521
|
-
}
|
|
14522
|
-
if (cleanupDelayMs > taskTtlMs) {
|
|
14523
|
-
console.warn("[Config] CLEANUP_DELAY_MS cannot exceed TASK_TTL_MS");
|
|
14524
|
-
this.parallelAgentConfig.cleanupDelayMs = Math.floor(taskTtlMs / 2);
|
|
14525
|
-
}
|
|
14526
|
-
if (minStabilityMs < 1e3) {
|
|
14527
|
-
console.warn("[Config] MIN_STABILITY_MS too low (< 1s), using 1s minimum");
|
|
14528
|
-
this.parallelAgentConfig.minStabilityMs = 1e3;
|
|
14529
|
-
}
|
|
14530
|
-
if (pollIntervalMs < 500) {
|
|
14531
|
-
console.warn("[Config] POLL_INTERVAL_MS too low (< 500ms), using 500ms minimum");
|
|
14532
|
-
this.parallelAgentConfig.pollIntervalMs = 500;
|
|
14533
|
-
}
|
|
14534
|
-
if (defaultConcurrency < 1 || defaultConcurrency > maxConcurrency) {
|
|
14535
|
-
console.warn(`[Config] DEFAULT_CONCURRENCY must be 1-${maxConcurrency}`);
|
|
14536
|
-
this.parallelAgentConfig.defaultConcurrency = Math.min(
|
|
14537
|
-
Math.max(defaultConcurrency, 1),
|
|
14538
|
-
maxConcurrency
|
|
14539
|
-
);
|
|
14540
|
-
}
|
|
14541
|
-
}
|
|
14542
|
-
validateBackgroundTaskConfig() {
|
|
14543
|
-
const { monitorIntervalMs, defaultTimeoutMs, maxCompletedTasksToKeep } = this.backgroundTaskConfig;
|
|
14544
|
-
if (monitorIntervalMs < 1e3) {
|
|
14545
|
-
console.warn("[Config] MONITOR_INTERVAL_MS too low (< 1s), using 1s minimum");
|
|
14546
|
-
this.backgroundTaskConfig.monitorIntervalMs = 1e3;
|
|
14547
|
-
}
|
|
14548
|
-
if (defaultTimeoutMs < 10 * 1e3) {
|
|
14549
|
-
console.warn("[Config] DEFAULT_TIMEOUT_MS too low (< 10s), using 10s minimum");
|
|
14550
|
-
this.backgroundTaskConfig.defaultTimeoutMs = 10 * 1e3;
|
|
14551
|
-
}
|
|
14552
|
-
if (maxCompletedTasksToKeep < 0) {
|
|
14553
|
-
console.warn("[Config] MAX_COMPLETED_TASKS must be >= 0, using 0");
|
|
14554
|
-
this.backgroundTaskConfig.maxCompletedTasksToKeep = 0;
|
|
14555
|
-
}
|
|
14556
|
-
}
|
|
14557
|
-
validateSessionConfig() {
|
|
14558
|
-
const { defaultMaxSteps, taskCommandMaxSteps } = this.sessionConfig;
|
|
14559
|
-
if (defaultMaxSteps < 1) {
|
|
14560
|
-
console.warn("[Config] DEFAULT_MAX_STEPS must be >= 1, using 1");
|
|
14561
|
-
this.sessionConfig.defaultMaxSteps = 1;
|
|
14562
|
-
}
|
|
14563
|
-
if (taskCommandMaxSteps < defaultMaxSteps) {
|
|
14564
|
-
console.warn("[Config] TASK_MAX_STEPS should be >= DEFAULT_MAX_STEPS");
|
|
14565
|
-
this.sessionConfig.taskCommandMaxSteps = defaultMaxSteps;
|
|
14566
|
-
}
|
|
14567
|
-
}
|
|
14568
|
-
// ------------------------------------------------------------------------
|
|
14569
|
-
// Helpers
|
|
14570
|
-
// ------------------------------------------------------------------------
|
|
14571
|
-
parseEnvInt(key, defaultValue) {
|
|
14572
|
-
const value = process.env[key];
|
|
14573
|
-
if (value === void 0) return defaultValue;
|
|
14574
|
-
const parsed = parseInt(value, 10);
|
|
14575
|
-
if (isNaN(parsed)) {
|
|
14576
|
-
console.warn(`[Config] Invalid ${key} value: "${value}", using default ${defaultValue}`);
|
|
14577
|
-
return defaultValue;
|
|
14578
|
-
}
|
|
14579
|
-
return parsed;
|
|
14580
|
-
}
|
|
14581
|
-
// ------------------------------------------------------------------------
|
|
14582
|
-
// Export / Debug
|
|
14583
|
-
// ------------------------------------------------------------------------
|
|
14584
|
-
exportConfigs() {
|
|
14585
|
-
console.log("\n\u{1F4CB} Current Configuration:\n");
|
|
14586
|
-
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");
|
|
14587
|
-
console.log("Parallel Agent:");
|
|
14588
|
-
console.log(` Task TTL: ${this.parallelAgentConfig.taskTtlMs / 1e3}s`);
|
|
14589
|
-
console.log(` Cleanup Delay: ${this.parallelAgentConfig.cleanupDelayMs / 1e3}s`);
|
|
14590
|
-
console.log(` Min Stability: ${this.parallelAgentConfig.minStabilityMs / 1e3}s`);
|
|
14591
|
-
console.log(` Poll Interval: ${this.parallelAgentConfig.pollIntervalMs / 1e3}s`);
|
|
14592
|
-
console.log(` Default Concurrency: ${this.parallelAgentConfig.defaultConcurrency}`);
|
|
14593
|
-
console.log(` Max Concurrency: ${this.parallelAgentConfig.maxConcurrency}`);
|
|
14594
|
-
console.log(` Debug: ${this.parallelAgentConfig.enableDebug}`);
|
|
14595
|
-
console.log("");
|
|
14596
|
-
console.log("Background Task:");
|
|
14597
|
-
console.log(` Monitor Interval: ${this.backgroundTaskConfig.monitorIntervalMs / 1e3}s`);
|
|
14598
|
-
console.log(` Default Timeout: ${this.backgroundTaskConfig.defaultTimeoutMs / 1e3}s`);
|
|
14599
|
-
console.log(` Max Completed Tasks: ${this.backgroundTaskConfig.maxCompletedTasksToKeep}`);
|
|
14600
|
-
console.log(` Storage Dir: ${this.backgroundTaskConfig.storageDir}`);
|
|
14601
|
-
console.log(` Debug: ${this.backgroundTaskConfig.enableDebug}`);
|
|
14602
|
-
console.log("");
|
|
14603
|
-
console.log("Session:");
|
|
14604
|
-
console.log(` Default Max Steps: ${this.sessionConfig.defaultMaxSteps}`);
|
|
14605
|
-
console.log(` Task Max Steps: ${this.sessionConfig.taskCommandMaxSteps}`);
|
|
14606
|
-
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");
|
|
14607
|
-
}
|
|
14608
|
-
};
|
|
14609
|
-
var configManager = ConfigManager.getInstance();
|
|
14610
|
-
|
|
14611
13720
|
// src/core/async-agent.ts
|
|
14612
|
-
var
|
|
14613
|
-
var
|
|
14614
|
-
var
|
|
14615
|
-
var POLL_INTERVAL_MS =
|
|
14616
|
-
var DEFAULT_CONCURRENCY =
|
|
14617
|
-
var
|
|
14618
|
-
var DEBUG = configManager.getParallelAgentConfig().enableDebug;
|
|
13721
|
+
var TASK_TTL_MS = 30 * 60 * 1e3;
|
|
13722
|
+
var CLEANUP_DELAY_MS = 5 * 60 * 1e3;
|
|
13723
|
+
var MIN_STABILITY_MS = 5 * 1e3;
|
|
13724
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
13725
|
+
var DEFAULT_CONCURRENCY = 3;
|
|
13726
|
+
var DEBUG = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
14619
13727
|
var log = (...args) => {
|
|
14620
13728
|
if (DEBUG) console.log("[parallel-agent]", ...args);
|
|
14621
13729
|
};
|
|
@@ -14624,9 +13732,7 @@ var ConcurrencyController = class {
|
|
|
14624
13732
|
queues = /* @__PURE__ */ new Map();
|
|
14625
13733
|
limits = /* @__PURE__ */ new Map();
|
|
14626
13734
|
setLimit(key, limit) {
|
|
14627
|
-
|
|
14628
|
-
this.limits.set(key, cappedLimit);
|
|
14629
|
-
log(`Set limit for ${key}: ${cappedLimit}`);
|
|
13735
|
+
this.limits.set(key, limit);
|
|
14630
13736
|
}
|
|
14631
13737
|
getLimit(key) {
|
|
14632
13738
|
return this.limits.get(key) ?? DEFAULT_CONCURRENCY;
|
|
@@ -14666,21 +13772,6 @@ var ConcurrencyController = class {
|
|
|
14666
13772
|
getQueueLength(key) {
|
|
14667
13773
|
return this.queues.get(key)?.length ?? 0;
|
|
14668
13774
|
}
|
|
14669
|
-
updateConcurrency(agentType, newLimit) {
|
|
14670
|
-
this.setLimit(agentType, newLimit);
|
|
14671
|
-
log(`Updated concurrency for ${agentType}: ${newLimit}`);
|
|
14672
|
-
}
|
|
14673
|
-
getStats() {
|
|
14674
|
-
const stats = {};
|
|
14675
|
-
for (const [agentType, limit] of this.limits.entries()) {
|
|
14676
|
-
stats[agentType] = {
|
|
14677
|
-
running: this.counts.get(agentType) ?? 0,
|
|
14678
|
-
queued: this.getQueueLength(agentType),
|
|
14679
|
-
limit
|
|
14680
|
-
};
|
|
14681
|
-
}
|
|
14682
|
-
return stats;
|
|
14683
|
-
}
|
|
14684
13775
|
};
|
|
14685
13776
|
var ParallelAgentManager = class _ParallelAgentManager {
|
|
14686
13777
|
static _instance;
|
|
@@ -14702,14 +13793,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14702
13793
|
static getInstance(client, directory) {
|
|
14703
13794
|
if (!_ParallelAgentManager._instance) {
|
|
14704
13795
|
if (!client || !directory) {
|
|
14705
|
-
throw new Error(
|
|
14706
|
-
"ParallelAgentManager requires client and directory on first call"
|
|
14707
|
-
);
|
|
13796
|
+
throw new Error("ParallelAgentManager requires client and directory on first call");
|
|
14708
13797
|
}
|
|
14709
|
-
_ParallelAgentManager._instance = new _ParallelAgentManager(
|
|
14710
|
-
client,
|
|
14711
|
-
directory
|
|
14712
|
-
);
|
|
13798
|
+
_ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
|
|
14713
13799
|
}
|
|
14714
13800
|
return _ParallelAgentManager._instance;
|
|
14715
13801
|
}
|
|
@@ -14779,9 +13865,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14779
13865
|
* Get all running tasks
|
|
14780
13866
|
*/
|
|
14781
13867
|
getRunningTasks() {
|
|
14782
|
-
return Array.from(this.tasks.values()).filter(
|
|
14783
|
-
(t) => t.status === "running"
|
|
14784
|
-
);
|
|
13868
|
+
return Array.from(this.tasks.values()).filter((t) => t.status === "running");
|
|
14785
13869
|
}
|
|
14786
13870
|
/**
|
|
14787
13871
|
* Get all tasks
|
|
@@ -14793,9 +13877,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14793
13877
|
* Get tasks by parent session
|
|
14794
13878
|
*/
|
|
14795
13879
|
getTasksByParent(parentSessionID) {
|
|
14796
|
-
return Array.from(this.tasks.values()).filter(
|
|
14797
|
-
(t) => t.parentSessionID === parentSessionID
|
|
14798
|
-
);
|
|
13880
|
+
return Array.from(this.tasks.values()).filter((t) => t.parentSessionID === parentSessionID);
|
|
14799
13881
|
}
|
|
14800
13882
|
/**
|
|
14801
13883
|
* Cancel a running task
|
|
@@ -14816,13 +13898,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14816
13898
|
await this.client.session.delete({
|
|
14817
13899
|
path: { id: task.sessionID }
|
|
14818
13900
|
});
|
|
14819
|
-
console.log(
|
|
14820
|
-
`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... deleted`
|
|
14821
|
-
);
|
|
13901
|
+
console.log(`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
14822
13902
|
} catch {
|
|
14823
|
-
console.log(
|
|
14824
|
-
`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... already gone`
|
|
14825
|
-
);
|
|
13903
|
+
console.log(`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
14826
13904
|
}
|
|
14827
13905
|
this.scheduleCleanup(taskId);
|
|
14828
13906
|
console.log(`[parallel] \u{1F6D1} CANCELLED ${taskId}`);
|
|
@@ -14860,23 +13938,11 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14860
13938
|
}
|
|
14861
13939
|
}
|
|
14862
13940
|
/**
|
|
14863
|
-
|
|
14864
|
-
|
|
13941
|
+
* Set concurrency limit for agent type
|
|
13942
|
+
*/
|
|
14865
13943
|
setConcurrencyLimit(agentType, limit) {
|
|
14866
13944
|
this.concurrency.setLimit(agentType, limit);
|
|
14867
13945
|
}
|
|
14868
|
-
/**
|
|
14869
|
-
* Get concurrency statistics for all agent types
|
|
14870
|
-
*/
|
|
14871
|
-
getConcurrencyStats() {
|
|
14872
|
-
return this.concurrency.getStats();
|
|
14873
|
-
}
|
|
14874
|
-
/**
|
|
14875
|
-
* Update concurrency limit dynamically at runtime
|
|
14876
|
-
*/
|
|
14877
|
-
updateConcurrency(agentType, newLimit) {
|
|
14878
|
-
this.concurrency.updateConcurrency(agentType, newLimit);
|
|
14879
|
-
}
|
|
14880
13946
|
/**
|
|
14881
13947
|
* Get pending notification count
|
|
14882
13948
|
*/
|
|
@@ -14956,7 +14022,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14956
14022
|
const sessionStatus = allStatuses[task.sessionID];
|
|
14957
14023
|
if (sessionStatus?.type === "idle") {
|
|
14958
14024
|
const elapsed = Date.now() - task.startedAt.getTime();
|
|
14959
|
-
if (elapsed <
|
|
14025
|
+
if (elapsed < MIN_STABILITY_MS) continue;
|
|
14960
14026
|
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
14961
14027
|
if (!hasOutput) continue;
|
|
14962
14028
|
task.status = "completed";
|
|
@@ -14968,13 +14034,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14968
14034
|
this.queueNotification(task);
|
|
14969
14035
|
this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14970
14036
|
this.scheduleCleanup(task.id);
|
|
14971
|
-
const duration3 = this.formatDuration(
|
|
14972
|
-
|
|
14973
|
-
task.completedAt
|
|
14974
|
-
);
|
|
14975
|
-
console.log(
|
|
14976
|
-
`[parallel] \u2705 COMPLETED ${task.id} \u2192 ${task.agent}: ${task.description} (${duration3})`
|
|
14977
|
-
);
|
|
14037
|
+
const duration3 = this.formatDuration(task.startedAt, task.completedAt);
|
|
14038
|
+
console.log(`[parallel] \u2705 COMPLETED ${task.id} \u2192 ${task.agent}: ${task.description} (${duration3})`);
|
|
14978
14039
|
log(`Completed ${task.id}`);
|
|
14979
14040
|
}
|
|
14980
14041
|
}
|
|
@@ -15010,7 +14071,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
15010
14071
|
const now = Date.now();
|
|
15011
14072
|
for (const [taskId, task] of this.tasks.entries()) {
|
|
15012
14073
|
const age = now - task.startedAt.getTime();
|
|
15013
|
-
if (age >
|
|
14074
|
+
if (age > TASK_TTL_MS) {
|
|
15014
14075
|
log(`Timeout: ${taskId} (${Math.round(age / 1e3)}s)`);
|
|
15015
14076
|
if (task.status === "running") {
|
|
15016
14077
|
task.status = "timeout";
|
|
@@ -15020,20 +14081,14 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
15020
14081
|
this.concurrency.release(task.concurrencyKey);
|
|
15021
14082
|
}
|
|
15022
14083
|
this.untrackPending(task.parentSessionID, taskId);
|
|
15023
|
-
console.log(
|
|
15024
|
-
`[parallel] \u23F1\uFE0F TIMEOUT ${taskId} \u2192 ${task.agent}: ${task.description}`
|
|
15025
|
-
);
|
|
14084
|
+
console.log(`[parallel] \u23F1\uFE0F TIMEOUT ${taskId} \u2192 ${task.agent}: ${task.description}`);
|
|
15026
14085
|
}
|
|
15027
14086
|
this.client.session.delete({
|
|
15028
14087
|
path: { id: task.sessionID }
|
|
15029
14088
|
}).then(() => {
|
|
15030
|
-
console.log(
|
|
15031
|
-
`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session deleted)`
|
|
15032
|
-
);
|
|
14089
|
+
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session deleted)`);
|
|
15033
14090
|
}).catch(() => {
|
|
15034
|
-
console.log(
|
|
15035
|
-
`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session already gone)`
|
|
15036
|
-
);
|
|
14091
|
+
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session already gone)`);
|
|
15037
14092
|
});
|
|
15038
14093
|
this.tasks.delete(taskId);
|
|
15039
14094
|
}
|
|
@@ -15061,7 +14116,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
15061
14116
|
}
|
|
15062
14117
|
this.tasks.delete(taskId);
|
|
15063
14118
|
log(`Cleaned up ${taskId} from memory`);
|
|
15064
|
-
},
|
|
14119
|
+
}, CLEANUP_DELAY_MS);
|
|
15065
14120
|
}
|
|
15066
14121
|
// ========================================================================
|
|
15067
14122
|
// Internal: Notifications
|
|
@@ -15081,27 +14136,14 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
15081
14136
|
if (completedTasks.length === 0) return;
|
|
15082
14137
|
const summary = completedTasks.map((t) => {
|
|
15083
14138
|
const status = t.status === "completed" ? "\u2705" : "\u274C";
|
|
15084
|
-
|
|
15085
|
-
return `${status} \`${t.id}\` (${duration3}): ${t.description}`;
|
|
14139
|
+
return `${status} \`${t.id}\`: ${t.description}`;
|
|
15086
14140
|
}).join("\n");
|
|
15087
14141
|
const notification = `<system-notification>
|
|
15088
14142
|
**All Parallel Tasks Complete**
|
|
15089
14143
|
|
|
15090
14144
|
${summary}
|
|
15091
14145
|
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
**Retrieval Options**
|
|
15095
|
-
|
|
15096
|
-
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve full results.
|
|
15097
|
-
|
|
15098
|
-
---
|
|
15099
|
-
|
|
15100
|
-
**Task Summary**
|
|
15101
|
-
|
|
15102
|
-
Total Tasks: ${completedTasks.length}
|
|
15103
|
-
Status: All Complete
|
|
15104
|
-
Mode: Background (non-blocking)
|
|
14146
|
+
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
15105
14147
|
</system-notification>`;
|
|
15106
14148
|
try {
|
|
15107
14149
|
await this.client.session.prompt({
|
|
@@ -15160,14 +14202,10 @@ var createDelegateTaskTool = (manager, client) => tool({
|
|
|
15160
14202
|
- Auto-cleanup: 5 minutes after completion
|
|
15161
14203
|
</safety>`,
|
|
15162
14204
|
args: {
|
|
15163
|
-
agent: tool.schema.string().describe(
|
|
15164
|
-
`Agent name (e.g., '${AGENT_NAMES.BUILDER}', '${AGENT_NAMES.INSPECTOR}', '${AGENT_NAMES.ARCHITECT}')`
|
|
15165
|
-
),
|
|
14205
|
+
agent: tool.schema.string().describe("Agent name (e.g., 'builder', 'inspector', 'architect')"),
|
|
15166
14206
|
description: tool.schema.string().describe("Short task description"),
|
|
15167
14207
|
prompt: tool.schema.string().describe("Full prompt/instructions for the agent"),
|
|
15168
|
-
background: tool.schema.boolean().describe(
|
|
15169
|
-
"true=async (returns task_id), false=sync (waits for result). REQUIRED."
|
|
15170
|
-
)
|
|
14208
|
+
background: tool.schema.boolean().describe("true=async (returns task_id), false=sync (waits for result). REQUIRED.")
|
|
15171
14209
|
},
|
|
15172
14210
|
async execute(args, context) {
|
|
15173
14211
|
const { agent, description, prompt, background } = args;
|
|
@@ -15188,34 +14226,21 @@ var createDelegateTaskTool = (manager, client) => tool({
|
|
|
15188
14226
|
});
|
|
15189
14227
|
const runningCount = manager.getRunningTasks().length;
|
|
15190
14228
|
const pendingCount = manager.getPendingCount(ctx.sessionID);
|
|
15191
|
-
console.log(
|
|
15192
|
-
`[parallel] \u{1F680} SPAWNED ${task.id} \u2192 ${agent}: ${description}`
|
|
15193
|
-
);
|
|
14229
|
+
console.log(`[parallel] \u{1F680} SPAWNED ${task.id} \u2192 ${agent}: ${description}`);
|
|
15194
14230
|
return `
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
**Monitoring Commands**
|
|
15210
|
-
|
|
15211
|
-
Check progress anytime:
|
|
15212
|
-
- \`list_tasks()\` - View all parallel tasks
|
|
15213
|
-
- \`get_task_result({ taskId: "${task.id}" })\` - Get latest result
|
|
15214
|
-
- \`cancel_task({ taskId: "${task.id}" })\` - Stop this task
|
|
15215
|
-
|
|
15216
|
-
---
|
|
15217
|
-
|
|
15218
|
-
\u2713 System will notify when ALL tasks complete. You can continue working!`;
|
|
14231
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
14232
|
+
\u2551 \u{1F680} BACKGROUND TASK SPAWNED \u2551
|
|
14233
|
+
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
14234
|
+
\u2551 Task ID: ${task.id.padEnd(45)}\u2551
|
|
14235
|
+
\u2551 Agent: ${task.agent.padEnd(45)}\u2551
|
|
14236
|
+
\u2551 Description: ${task.description.slice(0, 45).padEnd(45)}\u2551
|
|
14237
|
+
\u2551 Status: \u23F3 RUNNING (background) \u2551
|
|
14238
|
+
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
14239
|
+
\u2551 Running: ${String(runningCount).padEnd(5)} \u2502 Pending: ${String(pendingCount).padEnd(5)} \u2551
|
|
14240
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
14241
|
+
|
|
14242
|
+
\u{1F4CC} Continue your work! System notifies when ALL complete.
|
|
14243
|
+
\u{1F50D} Use \`get_task_result({ taskId: "${task.id}" })\` later.`;
|
|
15219
14244
|
} catch (error45) {
|
|
15220
14245
|
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
15221
14246
|
console.log(`[parallel] \u274C FAILED: ${message}`);
|
|
@@ -15254,7 +14279,7 @@ Session ID: ${sessionID}`;
|
|
|
15254
14279
|
}
|
|
15255
14280
|
const POLL_INTERVAL_MS2 = 500;
|
|
15256
14281
|
const MAX_POLL_TIME_MS = 10 * 60 * 1e3;
|
|
15257
|
-
const
|
|
14282
|
+
const MIN_STABILITY_MS2 = 5e3;
|
|
15258
14283
|
const STABILITY_POLLS_REQUIRED = 3;
|
|
15259
14284
|
let stablePolls = 0;
|
|
15260
14285
|
let lastMsgCount = 0;
|
|
@@ -15268,10 +14293,8 @@ Session ID: ${sessionID}`;
|
|
|
15268
14293
|
lastMsgCount = 0;
|
|
15269
14294
|
continue;
|
|
15270
14295
|
}
|
|
15271
|
-
if (Date.now() - startTime <
|
|
15272
|
-
const messagesResult2 = await session.messages({
|
|
15273
|
-
path: { id: sessionID }
|
|
15274
|
-
});
|
|
14296
|
+
if (Date.now() - startTime < MIN_STABILITY_MS2) continue;
|
|
14297
|
+
const messagesResult2 = await session.messages({ path: { id: sessionID } });
|
|
15275
14298
|
const messages2 = messagesResult2.data ?? [];
|
|
15276
14299
|
const currentMsgCount = messages2.length;
|
|
15277
14300
|
if (currentMsgCount === lastMsgCount) {
|
|
@@ -15282,9 +14305,7 @@ Session ID: ${sessionID}`;
|
|
|
15282
14305
|
lastMsgCount = currentMsgCount;
|
|
15283
14306
|
}
|
|
15284
14307
|
}
|
|
15285
|
-
const messagesResult = await session.messages({
|
|
15286
|
-
path: { id: sessionID }
|
|
15287
|
-
});
|
|
14308
|
+
const messagesResult = await session.messages({ path: { id: sessionID } });
|
|
15288
14309
|
const messages = messagesResult.data ?? [];
|
|
15289
14310
|
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant").reverse();
|
|
15290
14311
|
const lastMsg = assistantMsgs[0];
|
|
@@ -15298,9 +14319,7 @@ Session ID: ${sessionID}`;
|
|
|
15298
14319
|
) ?? [];
|
|
15299
14320
|
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
|
|
15300
14321
|
const duration3 = Math.floor((Date.now() - startTime) / 1e3);
|
|
15301
|
-
console.log(
|
|
15302
|
-
`[delegate] \u2705 COMPLETED ${agent}: ${description} (${duration3}s)`
|
|
15303
|
-
);
|
|
14322
|
+
console.log(`[delegate] \u2705 COMPLETED ${agent}: ${description} (${duration3}s)`);
|
|
15304
14323
|
return `\u2705 **Task Completed** (${duration3}s)
|
|
15305
14324
|
|
|
15306
14325
|
Agent: ${agent}
|
|
@@ -15335,9 +14354,7 @@ Wait for the "All Complete" notification before checking.
|
|
|
15335
14354
|
Use \`list_tasks\` to see available tasks.`;
|
|
15336
14355
|
}
|
|
15337
14356
|
if (task.status === "running") {
|
|
15338
|
-
const elapsed = Math.floor(
|
|
15339
|
-
(Date.now() - task.startedAt.getTime()) / 1e3
|
|
15340
|
-
);
|
|
14357
|
+
const elapsed = Math.floor((Date.now() - task.startedAt.getTime()) / 1e3);
|
|
15341
14358
|
return `\u23F3 **Task Still Running**
|
|
15342
14359
|
|
|
15343
14360
|
| Property | Value |
|
|
@@ -15419,9 +14436,7 @@ Use \`delegate_task({ ..., background: true })\` to spawn background tasks.`;
|
|
|
15419
14436
|
}
|
|
15420
14437
|
};
|
|
15421
14438
|
const rows = tasks.map((t) => {
|
|
15422
|
-
const elapsed = Math.floor(
|
|
15423
|
-
(Date.now() - t.startedAt.getTime()) / 1e3
|
|
15424
|
-
);
|
|
14439
|
+
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
|
|
15425
14440
|
const desc = t.description.length > 25 ? t.description.slice(0, 22) + "..." : t.description;
|
|
15426
14441
|
return `| \`${t.id}\` | ${statusIcon(t.status)} ${t.status} | ${t.agent} | ${desc} | ${elapsed}s |`;
|
|
15427
14442
|
}).join("\n");
|
|
@@ -15497,17 +14512,137 @@ function formatElapsedTime(startMs, endMs = Date.now()) {
|
|
|
15497
14512
|
return parts.join(" ");
|
|
15498
14513
|
}
|
|
15499
14514
|
|
|
14515
|
+
// src/utils/sanity.ts
|
|
14516
|
+
function checkOutputSanity(text) {
|
|
14517
|
+
if (!text || text.length < 50) {
|
|
14518
|
+
return { isHealthy: true, severity: "ok" };
|
|
14519
|
+
}
|
|
14520
|
+
if (/(.)\1{15,}/.test(text)) {
|
|
14521
|
+
return {
|
|
14522
|
+
isHealthy: false,
|
|
14523
|
+
reason: "Single character repetition detected",
|
|
14524
|
+
severity: "critical"
|
|
14525
|
+
};
|
|
14526
|
+
}
|
|
14527
|
+
if (/(.{2,6})\1{8,}/.test(text)) {
|
|
14528
|
+
return {
|
|
14529
|
+
isHealthy: false,
|
|
14530
|
+
reason: "Pattern loop detected",
|
|
14531
|
+
severity: "critical"
|
|
14532
|
+
};
|
|
14533
|
+
}
|
|
14534
|
+
if (text.length > 200) {
|
|
14535
|
+
const cleanText = text.replace(/\s/g, "");
|
|
14536
|
+
if (cleanText.length > 100) {
|
|
14537
|
+
const uniqueChars = new Set(cleanText).size;
|
|
14538
|
+
const ratio = uniqueChars / cleanText.length;
|
|
14539
|
+
if (ratio < 0.02) {
|
|
14540
|
+
return {
|
|
14541
|
+
isHealthy: false,
|
|
14542
|
+
reason: "Low information density",
|
|
14543
|
+
severity: "critical"
|
|
14544
|
+
};
|
|
14545
|
+
}
|
|
14546
|
+
}
|
|
14547
|
+
}
|
|
14548
|
+
const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
|
|
14549
|
+
if (boxChars > 100 && boxChars / text.length > 0.3) {
|
|
14550
|
+
return {
|
|
14551
|
+
isHealthy: false,
|
|
14552
|
+
reason: "Visual gibberish detected",
|
|
14553
|
+
severity: "critical"
|
|
14554
|
+
};
|
|
14555
|
+
}
|
|
14556
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 10);
|
|
14557
|
+
if (lines.length > 10) {
|
|
14558
|
+
const lineSet = new Set(lines);
|
|
14559
|
+
if (lineSet.size < lines.length * 0.2) {
|
|
14560
|
+
return {
|
|
14561
|
+
isHealthy: false,
|
|
14562
|
+
reason: "Excessive line repetition",
|
|
14563
|
+
severity: "warning"
|
|
14564
|
+
};
|
|
14565
|
+
}
|
|
14566
|
+
}
|
|
14567
|
+
const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
|
|
14568
|
+
if (cjkChars > 200) {
|
|
14569
|
+
const uniqueCjk = new Set(
|
|
14570
|
+
text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
|
|
14571
|
+
).size;
|
|
14572
|
+
if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
|
|
14573
|
+
return {
|
|
14574
|
+
isHealthy: false,
|
|
14575
|
+
reason: "CJK character spam detected",
|
|
14576
|
+
severity: "critical"
|
|
14577
|
+
};
|
|
14578
|
+
}
|
|
14579
|
+
}
|
|
14580
|
+
return { isHealthy: true, severity: "ok" };
|
|
14581
|
+
}
|
|
14582
|
+
var RECOVERY_PROMPT = `<anomaly_recovery>
|
|
14583
|
+
\u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
|
|
14584
|
+
|
|
14585
|
+
<recovery_protocol>
|
|
14586
|
+
1. DISCARD the corrupted output completely - do not reference it
|
|
14587
|
+
2. RECALL the original mission objective
|
|
14588
|
+
3. IDENTIFY the last confirmed successful step
|
|
14589
|
+
4. RESTART with a simpler, more focused approach
|
|
14590
|
+
</recovery_protocol>
|
|
14591
|
+
|
|
14592
|
+
<instructions>
|
|
14593
|
+
- If a sub-agent produced bad output: try a different agent or simpler task
|
|
14594
|
+
- If stuck in a loop: break down the task into smaller pieces
|
|
14595
|
+
- If context seems corrupted: call recorder to restore context
|
|
14596
|
+
- THINK in English for maximum stability
|
|
14597
|
+
</instructions>
|
|
14598
|
+
|
|
14599
|
+
What was the original task? Proceed from the last known good state.
|
|
14600
|
+
</anomaly_recovery>`;
|
|
14601
|
+
var ESCALATION_PROMPT = `<critical_anomaly>
|
|
14602
|
+
\u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
|
|
14603
|
+
|
|
14604
|
+
<emergency_protocol>
|
|
14605
|
+
1. STOP current execution path immediately
|
|
14606
|
+
2. DO NOT continue with the same approach - it is failing
|
|
14607
|
+
3. CALL architect for a completely new strategy
|
|
14608
|
+
4. If architect also fails: report status to user and await guidance
|
|
14609
|
+
</emergency_protocol>
|
|
14610
|
+
|
|
14611
|
+
<diagnosis>
|
|
14612
|
+
The current approach is producing corrupted output.
|
|
14613
|
+
This may indicate: context overload, model instability, or task complexity.
|
|
14614
|
+
</diagnosis>
|
|
14615
|
+
|
|
14616
|
+
Request a fresh plan from architect with reduced scope.
|
|
14617
|
+
</critical_anomaly>`;
|
|
14618
|
+
|
|
15500
14619
|
// src/index.ts
|
|
15501
14620
|
var PLUGIN_VERSION = "0.2.4";
|
|
15502
14621
|
var DEFAULT_MAX_STEPS = 500;
|
|
15503
14622
|
var TASK_COMMAND_MAX_STEPS = 1e3;
|
|
15504
14623
|
var AGENT_EMOJI2 = {
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
14624
|
+
"architect": "\u{1F3D7}\uFE0F",
|
|
14625
|
+
"builder": "\u{1F528}",
|
|
14626
|
+
"inspector": "\u{1F50D}",
|
|
14627
|
+
"recorder": "\u{1F4BE}",
|
|
14628
|
+
"commander": "\u{1F3AF}"
|
|
15510
14629
|
};
|
|
14630
|
+
var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
14631
|
+
<status>Mission not complete. Keep executing.</status>
|
|
14632
|
+
|
|
14633
|
+
<rules>
|
|
14634
|
+
1. DO NOT stop - mission is incomplete
|
|
14635
|
+
2. DO NOT wait for user input
|
|
14636
|
+
3. If previous action failed, try different approach
|
|
14637
|
+
4. If agent returned nothing, proceed to next step
|
|
14638
|
+
</rules>
|
|
14639
|
+
|
|
14640
|
+
<next_step>
|
|
14641
|
+
What is the current state?
|
|
14642
|
+
What is the next action?
|
|
14643
|
+
Execute it NOW.
|
|
14644
|
+
</next_step>
|
|
14645
|
+
</auto_continue>`;
|
|
15511
14646
|
var OrchestratorPlugin = async (input) => {
|
|
15512
14647
|
const { directory, client } = input;
|
|
15513
14648
|
console.log(`[orchestrator] v${PLUGIN_VERSION} loaded`);
|
|
@@ -15532,12 +14667,6 @@ var OrchestratorPlugin = async (input) => {
|
|
|
15532
14667
|
kill_background: killBackgroundTool,
|
|
15533
14668
|
// Async agent tools - spawn agents in parallel sessions
|
|
15534
14669
|
...asyncAgentTools
|
|
15535
|
-
// Git tools - branch info and status
|
|
15536
|
-
// git_branch: gitBranchTool(directory),
|
|
15537
|
-
// Smart batch tools - centralized validation and retry
|
|
15538
|
-
// ...batchTools,
|
|
15539
|
-
// Configuration tools - dynamic runtime settings
|
|
15540
|
-
// ...configTools,
|
|
15541
14670
|
},
|
|
15542
14671
|
// -----------------------------------------------------------------
|
|
15543
14672
|
// Config hook - registers our commands and agents with OpenCode
|
|
@@ -15555,7 +14684,7 @@ var OrchestratorPlugin = async (input) => {
|
|
|
15555
14684
|
}
|
|
15556
14685
|
const orchestratorAgents = {
|
|
15557
14686
|
Commander: {
|
|
15558
|
-
name:
|
|
14687
|
+
name: "Commander",
|
|
15559
14688
|
description: "Autonomous orchestrator - executes until mission complete",
|
|
15560
14689
|
systemPrompt: AGENTS.commander.systemPrompt
|
|
15561
14690
|
}
|
|
@@ -15575,7 +14704,7 @@ var OrchestratorPlugin = async (input) => {
|
|
|
15575
14704
|
const parsed = detectSlashCommand(originalText);
|
|
15576
14705
|
const sessionID = msgInput.sessionID;
|
|
15577
14706
|
const agentName = (msgInput.agent || "").toLowerCase();
|
|
15578
|
-
if (agentName ===
|
|
14707
|
+
if (agentName === "commander" && !sessions.has(sessionID)) {
|
|
15579
14708
|
const now = Date.now();
|
|
15580
14709
|
sessions.set(sessionID, {
|
|
15581
14710
|
active: true,
|
|
@@ -15748,27 +14877,112 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
15748
14877
|
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`;
|
|
15749
14878
|
},
|
|
15750
14879
|
// -----------------------------------------------------------------
|
|
15751
|
-
//
|
|
15752
|
-
//
|
|
15753
|
-
//
|
|
15754
|
-
// reimplemented using supported hooks like tool.execute.after
|
|
15755
|
-
// or the event hook.
|
|
14880
|
+
// assistant.done hook - runs when the LLM finishes responding
|
|
14881
|
+
// This is the heart of the "relentless loop" - we keep pushing it
|
|
14882
|
+
// to continue until we see MISSION COMPLETE or hit the limit
|
|
15756
14883
|
// -----------------------------------------------------------------
|
|
14884
|
+
"assistant.done": async (assistantInput, assistantOutput) => {
|
|
14885
|
+
const sessionID = assistantInput.sessionID;
|
|
14886
|
+
const session = sessions.get(sessionID);
|
|
14887
|
+
if (!session?.active) return;
|
|
14888
|
+
const parts = assistantOutput.parts;
|
|
14889
|
+
const textContent = parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text || "").join("\n") || "";
|
|
14890
|
+
const stateSession = state.sessions.get(sessionID);
|
|
14891
|
+
const sanityResult = checkOutputSanity(textContent);
|
|
14892
|
+
if (!sanityResult.isHealthy && stateSession) {
|
|
14893
|
+
stateSession.anomalyCount = (stateSession.anomalyCount || 0) + 1;
|
|
14894
|
+
session.step++;
|
|
14895
|
+
session.timestamp = Date.now();
|
|
14896
|
+
const recoveryText = stateSession.anomalyCount >= 2 ? ESCALATION_PROMPT : RECOVERY_PROMPT;
|
|
14897
|
+
try {
|
|
14898
|
+
if (client?.session?.prompt) {
|
|
14899
|
+
await client.session.prompt({
|
|
14900
|
+
path: { id: sessionID },
|
|
14901
|
+
body: {
|
|
14902
|
+
parts: [{
|
|
14903
|
+
type: "text",
|
|
14904
|
+
text: `\u26A0\uFE0F ANOMALY #${stateSession.anomalyCount}: ${sanityResult.reason}
|
|
14905
|
+
|
|
14906
|
+
` + recoveryText + `
|
|
14907
|
+
|
|
14908
|
+
[Recovery Step ${session.step}/${session.maxSteps}]`
|
|
14909
|
+
}]
|
|
14910
|
+
}
|
|
14911
|
+
});
|
|
14912
|
+
}
|
|
14913
|
+
} catch {
|
|
14914
|
+
session.active = false;
|
|
14915
|
+
state.missionActive = false;
|
|
14916
|
+
}
|
|
14917
|
+
return;
|
|
14918
|
+
}
|
|
14919
|
+
if (stateSession && stateSession.anomalyCount > 0) {
|
|
14920
|
+
stateSession.anomalyCount = 0;
|
|
14921
|
+
}
|
|
14922
|
+
if (textContent.includes("\u2705 MISSION COMPLETE") || textContent.includes("MISSION COMPLETE")) {
|
|
14923
|
+
session.active = false;
|
|
14924
|
+
state.missionActive = false;
|
|
14925
|
+
sessions.delete(sessionID);
|
|
14926
|
+
state.sessions.delete(sessionID);
|
|
14927
|
+
return;
|
|
14928
|
+
}
|
|
14929
|
+
if (textContent.includes("/stop") || textContent.includes("/cancel")) {
|
|
14930
|
+
session.active = false;
|
|
14931
|
+
state.missionActive = false;
|
|
14932
|
+
sessions.delete(sessionID);
|
|
14933
|
+
state.sessions.delete(sessionID);
|
|
14934
|
+
return;
|
|
14935
|
+
}
|
|
14936
|
+
const now = Date.now();
|
|
14937
|
+
const stepDuration = formatElapsedTime(session.lastStepTime, now);
|
|
14938
|
+
const totalElapsed = formatElapsedTime(session.startTime, now);
|
|
14939
|
+
session.step++;
|
|
14940
|
+
session.timestamp = now;
|
|
14941
|
+
session.lastStepTime = now;
|
|
14942
|
+
const currentTime = formatTimestamp();
|
|
14943
|
+
if (session.step >= session.maxSteps) {
|
|
14944
|
+
session.active = false;
|
|
14945
|
+
state.missionActive = false;
|
|
14946
|
+
return;
|
|
14947
|
+
}
|
|
14948
|
+
try {
|
|
14949
|
+
if (client?.session?.prompt) {
|
|
14950
|
+
await client.session.prompt({
|
|
14951
|
+
path: { id: sessionID },
|
|
14952
|
+
body: {
|
|
14953
|
+
parts: [{
|
|
14954
|
+
type: "text",
|
|
14955
|
+
text: CONTINUE_INSTRUCTION + `
|
|
14956
|
+
|
|
14957
|
+
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`
|
|
14958
|
+
}]
|
|
14959
|
+
}
|
|
14960
|
+
});
|
|
14961
|
+
}
|
|
14962
|
+
} catch {
|
|
14963
|
+
try {
|
|
14964
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
14965
|
+
if (client?.session?.prompt) {
|
|
14966
|
+
await client.session.prompt({
|
|
14967
|
+
path: { id: sessionID },
|
|
14968
|
+
body: { parts: [{ type: "text", text: "continue" }] }
|
|
14969
|
+
});
|
|
14970
|
+
}
|
|
14971
|
+
} catch {
|
|
14972
|
+
session.active = false;
|
|
14973
|
+
state.missionActive = false;
|
|
14974
|
+
}
|
|
14975
|
+
}
|
|
14976
|
+
},
|
|
15757
14977
|
// -----------------------------------------------------------------
|
|
15758
14978
|
// Event handler - cleans up when sessions are deleted
|
|
15759
14979
|
// -----------------------------------------------------------------
|
|
15760
|
-
|
|
15761
|
-
const { event } = input2;
|
|
14980
|
+
handler: async ({ event }) => {
|
|
15762
14981
|
if (event.type === "session.deleted") {
|
|
15763
14982
|
const props = event.properties;
|
|
15764
14983
|
if (props?.info?.id) {
|
|
15765
|
-
|
|
15766
|
-
sessions.delete(
|
|
15767
|
-
state.sessions.delete(sessionID);
|
|
15768
|
-
const cleanedCount = backgroundTaskManager.cleanupBySession(sessionID);
|
|
15769
|
-
if (cleanedCount > 0) {
|
|
15770
|
-
console.log(`[background] Cleaned up ${cleanedCount} tasks for deleted session ${sessionID}`);
|
|
15771
|
-
}
|
|
14984
|
+
sessions.delete(props.info.id);
|
|
14985
|
+
state.sessions.delete(props.info.id);
|
|
15772
14986
|
}
|
|
15773
14987
|
}
|
|
15774
14988
|
}
|