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.
Files changed (46) hide show
  1. package/README.md +10 -67
  2. package/dist/agents/coder.d.ts +2 -0
  3. package/dist/agents/definitions.d.ts +0 -0
  4. package/dist/agents/fixer.d.ts +2 -0
  5. package/dist/agents/names.d.ts +12 -0
  6. package/dist/agents/orchestrator.d.ts +0 -0
  7. package/dist/agents/planner.d.ts +2 -0
  8. package/dist/agents/reviewer.d.ts +2 -0
  9. package/dist/agents/searcher.d.ts +2 -0
  10. package/dist/agents/subagents/architect.d.ts +0 -0
  11. package/dist/agents/subagents/builder.d.ts +0 -0
  12. package/dist/agents/subagents/coder.d.ts +2 -0
  13. package/dist/agents/subagents/executor.d.ts +2 -0
  14. package/dist/agents/subagents/fixer.d.ts +2 -0
  15. package/dist/agents/subagents/inspector.d.ts +0 -0
  16. package/dist/agents/subagents/memory.d.ts +2 -0
  17. package/dist/agents/subagents/planner.d.ts +2 -0
  18. package/dist/agents/subagents/publisher.d.ts +2 -0
  19. package/dist/agents/subagents/recorder.d.ts +0 -0
  20. package/dist/agents/subagents/reviewer.d.ts +2 -0
  21. package/dist/agents/subagents/searcher.d.ts +2 -0
  22. package/dist/agents/subagents/strategist.d.ts +2 -0
  23. package/dist/agents/subagents/surgeon.d.ts +2 -0
  24. package/dist/agents/subagents/types.d.ts +7 -0
  25. package/dist/agents/subagents/visualist.d.ts +2 -0
  26. package/dist/agents/types.d.ts +7 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/core/async-agent.d.ts +2 -14
  29. package/dist/core/background.d.ts +0 -33
  30. package/dist/core/state.d.ts +0 -0
  31. package/dist/core/tasks.d.ts +0 -1
  32. package/dist/index.d.ts +2 -3
  33. package/dist/index.js +342 -1128
  34. package/dist/shared/contracts/interfaces.d.ts +0 -0
  35. package/dist/shared/contracts/names.d.ts +0 -0
  36. package/dist/tasks.d.ts +29 -0
  37. package/dist/tools/background.d.ts +0 -2
  38. package/dist/tools/callAgent.d.ts +0 -0
  39. package/dist/tools/git.d.ts +2 -2
  40. package/dist/tools/rust.d.ts +0 -0
  41. package/dist/tools/search.d.ts +0 -0
  42. package/dist/tools/slashCommand.d.ts +0 -0
  43. package/dist/utils/binary.d.ts +0 -0
  44. package/dist/utils/common.d.ts +0 -0
  45. package/dist/utils/sanity.d.ts +22 -0
  46. 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: "${AGENT_NAMES.BUILDER}", description: "Implement X", prompt: "...", background: true })
259
- delegate_task({ agent: "${AGENT_NAMES.INSPECTOR}", description: "Review Y", prompt: "...", background: true })
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: "${AGENT_NAMES.BUILDER}", ..., background: false })
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
- spawn_agent({ agent: "${AGENT_NAMES.BUILDER}", description: "Implement X", prompt: "..." })
298
- spawn_agent({ agent: "${AGENT_NAMES.INSPECTOR}", description: "Review Y", prompt: "..." })
299
- \u2192 Agents run concurrently, system notifies when ALL complete
300
- \u2192 Use get_task_result({ taskId }) to retrieve results
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
- | ${AGENT_NAMES.RECORDER} | Fresh start. Proceed to survey. |
352
- | ${AGENT_NAMES.ARCHITECT} | Try simpler plan yourself. |
353
- | ${AGENT_NAMES.BUILDER} | Call inspector to diagnose. |
354
- | ${AGENT_NAMES.INSPECTOR} | Retry with more context. |
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
- ${REASONING_CONSTRAINTS}
551
-
552
- ${WORKFLOW}
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
- 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>
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
- <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>
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
- ${REASONING_CONSTRAINTS}
655
-
656
- ${WORKFLOW}
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
- ${REASONING_CONSTRAINTS}
722
-
723
- ${WORKFLOW}
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
- ${REASONING_CONSTRAINTS}
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: MAX_TASK_RETRIES,
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 "Error: Unknown agent: " + args.agent;
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
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
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
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
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 ? "<context>\n" + args.context + "\n</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
- * Stop monitoring
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
- * Monitor running processes and detect zombie processes
13339
+ * Generate a unique task ID in the format job_xxxxxxxx
13919
13340
  */
13920
- monitorRunningProcesses() {
13921
- const now = Date.now();
13922
- let hasRunningTasks = false;
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, sessionID } = args;
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 TASK_TTL_MS2 = configManager.getParallelAgentConfig().taskTtlMs;
14613
- var CLEANUP_DELAY_MS2 = configManager.getParallelAgentConfig().cleanupDelayMs;
14614
- var MIN_STABILITY_MS2 = configManager.getParallelAgentConfig().minStabilityMs;
14615
- var POLL_INTERVAL_MS = configManager.getParallelAgentConfig().pollIntervalMs;
14616
- var DEFAULT_CONCURRENCY = configManager.getParallelAgentConfig().defaultConcurrency;
14617
- var MAX_CONCURRENCY = configManager.getParallelAgentConfig().maxConcurrency;
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
- const cappedLimit = Math.min(limit, MAX_CONCURRENCY);
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
- * Set concurrency limit for agent type
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 < MIN_STABILITY_MS2) continue;
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
- task.startedAt,
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 > TASK_TTL_MS2) {
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
- }, CLEANUP_DELAY_MS2);
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
- const duration3 = this.formatDuration(t.startedAt, t.completedAt);
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
- ## \u{1F680} BACKGROUND TASK SPAWNED
15196
-
15197
- **Task Details**
15198
- - **ID**: \`${task.id}\`
15199
- - **Agent**: ${agent}
15200
- - **Description**: ${description}
15201
- - **Status**: \u23F3 Running in background (non-blocking)
15202
-
15203
- **Active Tasks**
15204
- - Running: ${runningCount}
15205
- - Pending: ${pendingCount}
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 MIN_STABILITY_MS3 = 5e3;
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 < MIN_STABILITY_MS3) continue;
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
- [AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
15506
- [AGENT_NAMES.BUILDER]: "\u{1F528}",
15507
- [AGENT_NAMES.INSPECTOR]: "\u{1F50D}",
15508
- [AGENT_NAMES.RECORDER]: "\u{1F4BE}",
15509
- [AGENT_NAMES.COMMANDER]: "\u{1F3AF}"
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: AGENT_NAMES.COMMANDER,
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 === AGENT_NAMES.COMMANDER && !sessions.has(sessionID)) {
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
- // NOTE: assistant.done hook has been REMOVED
15752
- // It was NOT in the official OpenCode plugin API and was causing
15753
- // UI rendering issues. The "relentless loop" feature needs to be
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
- event: async (input2) => {
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
- const sessionID = props.info.id;
15766
- sessions.delete(sessionID);
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
  }