opencode-orchestrator 0.4.19 → 0.4.21

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 (44) hide show
  1. package/README.md +61 -3
  2. package/dist/agents/definitions.d.ts +0 -0
  3. package/dist/agents/orchestrator.d.ts +0 -0
  4. package/dist/agents/subagents/architect.d.ts +0 -0
  5. package/dist/agents/subagents/builder.d.ts +0 -0
  6. package/dist/agents/subagents/inspector.d.ts +0 -0
  7. package/dist/agents/subagents/recorder.d.ts +0 -0
  8. package/dist/core/async-agent.d.ts +14 -2
  9. package/dist/core/state.d.ts +0 -0
  10. package/dist/core/tasks.d.ts +1 -0
  11. package/dist/index.d.ts +20 -2
  12. package/dist/index.js +1497 -164
  13. package/dist/shared/contracts/interfaces.d.ts +0 -0
  14. package/dist/shared/contracts/names.d.ts +0 -0
  15. package/dist/tools/background.d.ts +2 -2
  16. package/dist/tools/callAgent.d.ts +0 -0
  17. package/dist/tools/rust.d.ts +0 -0
  18. package/dist/tools/search.d.ts +0 -0
  19. package/dist/tools/slashCommand.d.ts +0 -0
  20. package/dist/utils/binary.d.ts +0 -0
  21. package/dist/utils/common.d.ts +0 -0
  22. package/dist/utils/sanity.d.ts +0 -22
  23. package/package.json +6 -4
  24. package/dist/agents/coder.d.ts +0 -2
  25. package/dist/agents/fixer.d.ts +0 -2
  26. package/dist/agents/names.d.ts +0 -12
  27. package/dist/agents/planner.d.ts +0 -2
  28. package/dist/agents/reviewer.d.ts +0 -2
  29. package/dist/agents/searcher.d.ts +0 -2
  30. package/dist/agents/subagents/coder.d.ts +0 -2
  31. package/dist/agents/subagents/executor.d.ts +0 -2
  32. package/dist/agents/subagents/fixer.d.ts +0 -2
  33. package/dist/agents/subagents/memory.d.ts +0 -2
  34. package/dist/agents/subagents/planner.d.ts +0 -2
  35. package/dist/agents/subagents/publisher.d.ts +0 -2
  36. package/dist/agents/subagents/reviewer.d.ts +0 -2
  37. package/dist/agents/subagents/searcher.d.ts +0 -2
  38. package/dist/agents/subagents/strategist.d.ts +0 -2
  39. package/dist/agents/subagents/surgeon.d.ts +0 -2
  40. package/dist/agents/subagents/types.d.ts +0 -7
  41. package/dist/agents/subagents/visualist.d.ts +0 -2
  42. package/dist/agents/types.d.ts +0 -7
  43. package/dist/cli.d.ts +0 -2
  44. package/dist/tasks.d.ts +0 -29
package/dist/index.js CHANGED
@@ -1,4 +1,10 @@
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
+ });
2
8
  var __export = (target, all) => {
3
9
  for (var name in all)
4
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -19,6 +25,159 @@ var AGENT_NAMES = {
19
25
  // Persistent context - saves/loads session state
20
26
  };
21
27
 
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
+
22
181
  // src/agents/orchestrator.ts
23
182
  var orchestrator = {
24
183
  id: AGENT_NAMES.COMMANDER,
@@ -27,19 +186,16 @@ var orchestrator = {
27
186
  You are Commander. Complete missions autonomously. Never stop until done.
28
187
  </role>
29
188
 
189
+ ${REASONING_CONSTRAINTS}
190
+
191
+ ${LANGUAGE_RULE}
192
+
30
193
  <core_rules>
31
194
  1. Never stop until "\u2705 MISSION COMPLETE"
32
195
  2. Never wait for user during execution
33
196
  3. Never stop because agent returned nothing
34
197
  4. Always survey environment & codebase BEFORE coding
35
198
  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
43
199
  </core_rules>
44
200
 
45
201
  <phase_0 name="TRIAGE">
@@ -207,6 +363,169 @@ STRICT RULE: If any agent output contains gibberish, mixed-language hallucinatio
207
363
  \u274C Make random changes without understanding root cause
208
364
  </anti_patterns>
209
365
 
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
+
210
529
  <completion>
211
530
  Done when: Request fulfilled + lsp clean + build/test/audit pass.
212
531
 
@@ -228,10 +547,9 @@ var architect = {
228
547
  You are Architect. Break complex tasks into atomic pieces.
229
548
  </role>
230
549
 
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>
550
+ ${REASONING_CONSTRAINTS}
551
+
552
+ ${WORKFLOW}
235
553
 
236
554
  <scalable_planning>
237
555
  - **Fast Track**: Skip JSON overhead. Just acknowledge simple task.
@@ -250,25 +568,67 @@ If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_CO
250
568
  4. Assign: builder (code) or inspector (verify)
251
569
 
252
570
  <output_format>
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]
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>
258
607
  </output_format>
259
608
  </plan_mode>
260
609
 
261
610
  <strategy_mode trigger="failures > 2">
262
611
  <output_format>
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: ...
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>
272
632
  </output_format>
273
633
  </strategy_mode>
274
634
 
@@ -277,6 +637,7 @@ T1: ...
277
637
  - Always end with inspector task
278
638
  - Group unrelated tasks (parallel)
279
639
  - Be specific about files and verification
640
+ - Output MUST be valid JSON wrapped in <json_output> tags
280
641
  </rules>`,
281
642
  canWrite: false,
282
643
  canBash: false
@@ -290,10 +651,11 @@ var builder = {
290
651
  You are Builder. Write code that works.
291
652
  </role>
292
653
 
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>
654
+ ${REASONING_CONSTRAINTS}
655
+
656
+ ${WORKFLOW}
657
+
658
+ ${ANTI_PATTERNS}
297
659
 
298
660
  <scalable_attention>
299
661
  - **Simple Fix (L1)**: Read file \u2192 Implement fix directly. Efficiency first.
@@ -356,10 +718,11 @@ var inspector = {
356
718
  You are Inspector. Prove failure or success with evidence.
357
719
  </role>
358
720
 
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>
721
+ ${REASONING_CONSTRAINTS}
722
+
723
+ ${WORKFLOW}
724
+
725
+ ${ANTI_PATTERNS}
363
726
 
364
727
  <scalable_audit>
365
728
  - **Fast Track**: Verify syntax + quick logic check.
@@ -423,10 +786,7 @@ var recorder = {
423
786
  You are Recorder. Save and load work progress.
424
787
  </role>
425
788
 
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>
789
+ ${REASONING_CONSTRAINTS}
430
790
 
431
791
  <purpose>
432
792
  Context can be lost between sessions. You save it to disk.
@@ -556,12 +916,20 @@ var TaskGraph = class _TaskGraph {
556
916
  }
557
917
  };
558
918
 
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
+
559
927
  // src/core/state.ts
560
928
  var state = {
561
929
  missionActive: false,
562
930
  maxIterations: 1e3,
563
931
  // Effectively infinite - "Relentless" mode
564
- maxRetries: 3,
932
+ maxRetries: MAX_TASK_RETRIES,
565
933
  sessions: /* @__PURE__ */ new Map()
566
934
  };
567
935
 
@@ -12987,6 +13355,113 @@ function tool(input) {
12987
13355
  }
12988
13356
  tool.schema = external_exports;
12989
13357
 
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
+
12990
13465
  // src/tools/callAgent.ts
12991
13466
  var AGENT_EMOJI = {
12992
13467
  [AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
@@ -13025,13 +13500,19 @@ var callAgentTool = tool({
13025
13500
  async execute(args) {
13026
13501
  const agentDef = AGENTS[args.agent];
13027
13502
  if (!agentDef) {
13028
- return `\u274C Error: Unknown agent: ${args.agent}`;
13503
+ return "Error: Unknown agent: " + args.agent;
13029
13504
  }
13030
13505
  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
+ }
13031
13512
  const prompt = `
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
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
13033
13514
  ${emoji3} ${agentDef.id.toUpperCase()} :: ${agentDef.description}
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
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
13035
13516
 
13036
13517
  <system>
13037
13518
  ${agentDef.systemPrompt}
@@ -13041,9 +13522,7 @@ ${agentDef.systemPrompt}
13041
13522
  ${args.task}
13042
13523
  </task>
13043
13524
 
13044
- ${args.context ? `<context>
13045
- ${args.context}
13046
- </context>` : ""}
13525
+ ${args.context ? "<context>\n" + args.context + "\n</context>" : ""}
13047
13526
 
13048
13527
  <execution>
13049
13528
  Follow this pattern:
@@ -13319,6 +13798,206 @@ Returns matches grouped by pattern, with file paths and line numbers.
13319
13798
  }
13320
13799
  });
13321
13800
 
13801
+ // src/tools/git.ts
13802
+ var gitBranchTool = (directory) => tool({
13803
+ description: `Get Git branch information and status.
13804
+
13805
+ <purpose>
13806
+ Analyze current Git workspace context including:
13807
+ - Current branch name
13808
+ - All branches (local and remote)
13809
+ - Branch relationships (upstream, ahead/behind)
13810
+ - Staged, unstaged, and untracked files
13811
+ - Recent commits
13812
+ </purpose>
13813
+
13814
+ <examples>
13815
+ - Get current branch: Returns branch name and status
13816
+ - List all branches: Shows local and remote branches
13817
+ - Check status: Shows modified files
13818
+ - Recent commits: Last 5 commits with file changes
13819
+ </examples>
13820
+
13821
+ <output>
13822
+ Returns structured Git information for better code decisions.
13823
+ </output>`,
13824
+ args: {
13825
+ action: tool.schema.enum([
13826
+ "current",
13827
+ "list",
13828
+ "status",
13829
+ "diff",
13830
+ "recent",
13831
+ "all"
13832
+ ]).describe("Action to perform: current, list, status, diff, recent, all"),
13833
+ baseBranch: tool.schema.string().optional().describe("Base branch for comparison (e.g., 'main', 'develop'). Required for diff action.")
13834
+ },
13835
+ async execute(args) {
13836
+ const { action, baseBranch } = args;
13837
+ try {
13838
+ switch (action) {
13839
+ case "current":
13840
+ return await getCurrentBranch(directory);
13841
+ case "list":
13842
+ return await listBranches(directory);
13843
+ case "status":
13844
+ return await getGitStatus(directory);
13845
+ case "diff":
13846
+ return await getDiff(directory, baseBranch);
13847
+ case "recent":
13848
+ return await getRecentCommits(directory, 5);
13849
+ case "all":
13850
+ return await getAllInfo(directory);
13851
+ default:
13852
+ return await getCurrentBranch(directory);
13853
+ }
13854
+ } catch (error45) {
13855
+ return "\u274C Git error: " + (error45 instanceof Error ? error45.message : String(error45));
13856
+ }
13857
+ }
13858
+ });
13859
+ async function execGit(directory, args) {
13860
+ const { execSync } = await import("child_process");
13861
+ try {
13862
+ return execSync("git " + args.join(" "), {
13863
+ cwd: directory,
13864
+ encoding: "utf-8",
13865
+ stdio: ["ignore", "pipe", "pipe"],
13866
+ maxBuffer: 10 * 1024 * 1024
13867
+ // 10MB
13868
+ }).toString();
13869
+ } catch (error45) {
13870
+ if (error45.status === 128) {
13871
+ throw new Error("Not a git repository");
13872
+ }
13873
+ throw error45;
13874
+ }
13875
+ }
13876
+ async function getAheadBehind(directory, branch) {
13877
+ try {
13878
+ const output = await execGit(directory, ["rev-list", "--left-right", "--count", branch + "...@{u}"]);
13879
+ const parts = output.trim().split(/\s+/);
13880
+ const ahead = parseInt(parts[0], 10);
13881
+ const behind = parseInt(parts[1] || "0", 10);
13882
+ const resultParts = [];
13883
+ if (ahead > 0) resultParts.push(ahead + " ahead");
13884
+ if (behind > 0) resultParts.push(behind + " behind");
13885
+ return resultParts.length > 0 ? "| **Sync** | " + resultParts.join(", ") + " |" : "";
13886
+ } catch {
13887
+ return "";
13888
+ }
13889
+ }
13890
+ async function getCurrentBranch(directory) {
13891
+ const output = await execGit(directory, ["branch", "--show-current"]);
13892
+ const current = output.trim() || "HEAD (detached)";
13893
+ const upstream = await execGit(directory, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]).catch(() => "");
13894
+ return "\u{1F33F} **Current Branch**: `" + current + "`\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + (upstream ? "| **Upstream** | `" + upstream + "` |" : "") + await getAheadBehind(directory, current);
13895
+ }
13896
+ async function listBranches(directory) {
13897
+ const branches = await execGit(directory, ["branch", "-vv"]);
13898
+ const branchList = branches.split("\n").filter((b) => b.trim()).map((b) => {
13899
+ const isCurrent = b.startsWith("*");
13900
+ const name = isCurrent ? b.substring(2).trim() : b.trim();
13901
+ const parts = name.split(/\s+/);
13902
+ const branchName = parts[0];
13903
+ const upstream = parts[1] ? parts[1].match(/\[([^\]]+)\]/)?.[1] : void 0;
13904
+ const icon = isCurrent ? "\u{1F33F}" : "\u{1F4C2}";
13905
+ const status = isCurrent ? "(current)" : "";
13906
+ const upstreamInfo = upstream ? "\u2192 " + upstream : "";
13907
+ return icon + " `" + branchName + "` " + status + " " + upstreamInfo;
13908
+ }).join("\n");
13909
+ return "\u{1F33F} **All Branches**\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + branchList;
13910
+ }
13911
+ async function getGitStatus(directory) {
13912
+ const status = await execGit(directory, ["status", "--porcelain"]);
13913
+ const lines = status.split("\n").filter((l) => l.trim());
13914
+ if (lines.length === 0) {
13915
+ return "\u2705 Working directory clean";
13916
+ }
13917
+ const staged = [];
13918
+ const unstaged = [];
13919
+ const untracked = [];
13920
+ const conflicted = [];
13921
+ for (const line of lines) {
13922
+ if (!line || line.length < 2) continue;
13923
+ const statusCode = line.substring(0, 2);
13924
+ const filename = line.substring(3);
13925
+ if (statusCode === "??") {
13926
+ untracked.push(filename);
13927
+ } else if (statusCode.startsWith("U") || statusCode === "AA") {
13928
+ conflicted.push(filename);
13929
+ } else if (statusCode[0] !== " ") {
13930
+ staged.push(filename);
13931
+ }
13932
+ if (statusCode[1] !== " " && statusCode[1] !== "?") {
13933
+ unstaged.push(filename);
13934
+ }
13935
+ }
13936
+ const current = await execGit(directory, ["branch", "--show-current"]).catch(() => "unknown");
13937
+ let result = "\u{1F33F} **Branch**: `" + current + "`\n\n";
13938
+ if (staged.length > 0) {
13939
+ result += "\u2705 **Staged** (" + staged.length + ")\n" + staged.map((f) => " + " + f).join("\n") + "\n\n";
13940
+ }
13941
+ if (unstaged.length > 0) {
13942
+ result += "\u{1F4DD} **Modified** (" + unstaged.length + ")\n" + unstaged.map((f) => " ~ " + f).join("\n") + "\n\n";
13943
+ }
13944
+ if (untracked.length > 0) {
13945
+ result += "\u2795 **Untracked** (" + untracked.length + ")\n" + untracked.slice(0, 10).map((f) => " ? " + f).join("\n") + (untracked.length > 10 ? "\n ... and " + (untracked.length - 10) + " more" : "") + "\n\n";
13946
+ }
13947
+ if (conflicted.length > 0) {
13948
+ result += "\u26A0\uFE0F **Conflicts** (" + conflicted.length + ")\n" + conflicted.map((f) => " ! " + f).join("\n") + "\n\n";
13949
+ }
13950
+ return result.trim();
13951
+ }
13952
+ async function getDiff(directory, baseBranch) {
13953
+ const current = await execGit(directory, ["branch", "--show-current"]).catch(() => "HEAD");
13954
+ const base = baseBranch || "main";
13955
+ const diff = await execGit(directory, ["diff", base + "..." + current, "--stat"]);
13956
+ if (!diff.trim()) {
13957
+ return "\u2705 No differences between `" + current + "` and `" + base + "`";
13958
+ }
13959
+ const files = await execGit(directory, ["diff", base + "..." + current, "--name-only"]);
13960
+ const fileList = files.split("\n").filter((f) => f.trim());
13961
+ return "\u{1F4CA} **Diff**: `" + current + "` vs `" + base + "`\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + diff + "\n\n\u{1F4C1} **Changed Files** (" + fileList.length + "):\n" + fileList.map((f, i) => " " + (i + 1) + ". " + f).join("\n");
13962
+ }
13963
+ async function getRecentCommits(directory, count = 5) {
13964
+ const log2 = await execGit(directory, [
13965
+ "log",
13966
+ "-" + count,
13967
+ "--pretty=format:%H|%an|%ad|%s",
13968
+ "--date=short",
13969
+ "--name-only"
13970
+ ]);
13971
+ const blocks = log2.split("\n\n").filter((b) => b.trim());
13972
+ const commits = [];
13973
+ for (const block of blocks) {
13974
+ const lines = block.split("\n").filter((l) => l.trim());
13975
+ if (lines.length < 2) continue;
13976
+ const [hash2, author, date5, message] = lines[0].split("|");
13977
+ const files = lines.slice(1);
13978
+ commits.push({
13979
+ hash: hash2.substring(0, 7),
13980
+ author,
13981
+ date: date5,
13982
+ message,
13983
+ files
13984
+ });
13985
+ }
13986
+ let result = "\u{1F4DC} **Recent Commits** (last " + commits.length + ")\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n";
13987
+ for (const commit of commits) {
13988
+ result += "\n\u{1F539} `" + commit.hash + "` - " + commit.message + "\n";
13989
+ result += " \u{1F464} " + commit.author + " | \u{1F4C5} " + commit.date + "\n";
13990
+ result += " \u{1F4C1} " + commit.files.length + " file" + (commit.files.length > 1 ? "s" : "") + "\n";
13991
+ }
13992
+ return result;
13993
+ }
13994
+ async function getAllInfo(directory) {
13995
+ const current = await getCurrentBranch(directory);
13996
+ const status = await getGitStatus(directory);
13997
+ const recent = await getRecentCommits(directory, 3);
13998
+ return "\u{1F50D} **Full Git Context**\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n" + current + "\n\n" + status + "\n\n" + recent;
13999
+ }
14000
+
13322
14001
  // src/core/background.ts
13323
14002
  import { spawn as spawn2 } from "child_process";
13324
14003
  import { randomBytes } from "crypto";
@@ -13879,13 +14558,264 @@ Duration before kill: ${backgroundTaskManager.formatDuration(task)}`;
13879
14558
  }
13880
14559
  });
13881
14560
 
13882
- // src/core/async-agent.ts
13883
- var TASK_TTL_MS = 30 * 60 * 1e3;
13884
- var CLEANUP_DELAY_MS = 5 * 60 * 1e3;
13885
- var MIN_STABILITY_MS = 5 * 1e3;
13886
- var POLL_INTERVAL_MS = 2e3;
13887
- var DEFAULT_CONCURRENCY = 3;
13888
- var DEBUG = process.env.DEBUG_PARALLEL_AGENT === "true";
14561
+ // src/core/config.ts
14562
+ var DEFAULT_PARALLEL_AGENT = {
14563
+ taskTtlMs: 30 * 60 * 1e3,
14564
+ // 30 minutes
14565
+ cleanupDelayMs: 5 * 60 * 1e3,
14566
+ // 5 minutes
14567
+ minStabilityMs: 5 * 1e3,
14568
+ // 5 seconds
14569
+ pollIntervalMs: 2 * 1e3,
14570
+ // 2 seconds
14571
+ defaultConcurrency: 3,
14572
+ maxConcurrency: 10,
14573
+ enableDebug: false,
14574
+ enableDetailedLogs: false
14575
+ };
14576
+ var DEFAULT_BACKGROUND_TASK = {
14577
+ monitorIntervalMs: 5 * 1e3,
14578
+ // 5 seconds
14579
+ storageDir: process.env.OPENCODE_ORCHESTRATOR_DIR || `${__require("os").homedir()}/.opencode-orchestrator`,
14580
+ defaultTimeoutMs: 5 * 60 * 1e3,
14581
+ // 5 minutes
14582
+ maxCompletedTasksToKeep: 100,
14583
+ enableDebug: true
14584
+ };
14585
+ var DEFAULT_SESSION = {
14586
+ defaultMaxSteps: 50,
14587
+ taskCommandMaxSteps: 100
14588
+ };
14589
+ var ConfigManager = class _ConfigManager {
14590
+ static _instance;
14591
+ parallelAgentConfig;
14592
+ backgroundTaskConfig;
14593
+ sessionConfig;
14594
+ constructor() {
14595
+ this.parallelAgentConfig = this.loadParallelAgentConfig();
14596
+ this.backgroundTaskConfig = this.loadBackgroundTaskConfig();
14597
+ this.sessionConfig = this.loadSessionConfig();
14598
+ }
14599
+ static getInstance() {
14600
+ if (!_ConfigManager._instance) {
14601
+ _ConfigManager._instance = new _ConfigManager();
14602
+ }
14603
+ return _ConfigManager._instance;
14604
+ }
14605
+ // ------------------------------------------------------------------------
14606
+ // Parallel Agent Config
14607
+ // ------------------------------------------------------------------------
14608
+ loadParallelAgentConfig() {
14609
+ return {
14610
+ ...DEFAULT_PARALLEL_AGENT,
14611
+ taskTtlMs: this.parseEnvInt(
14612
+ "OPENCODE_TASK_TTL_MS",
14613
+ DEFAULT_PARALLEL_AGENT.taskTtlMs
14614
+ ),
14615
+ cleanupDelayMs: this.parseEnvInt(
14616
+ "OPENCODE_CLEANUP_DELAY_MS",
14617
+ DEFAULT_PARALLEL_AGENT.cleanupDelayMs
14618
+ ),
14619
+ minStabilityMs: this.parseEnvInt(
14620
+ "OPENCODE_MIN_STABILITY_MS",
14621
+ DEFAULT_PARALLEL_AGENT.minStabilityMs
14622
+ ),
14623
+ pollIntervalMs: this.parseEnvInt(
14624
+ "OPENCODE_POLL_INTERVAL_MS",
14625
+ DEFAULT_PARALLEL_AGENT.pollIntervalMs
14626
+ ),
14627
+ defaultConcurrency: this.parseEnvInt(
14628
+ "OPENCODE_DEFAULT_CONCURRENCY",
14629
+ DEFAULT_PARALLEL_AGENT.defaultConcurrency
14630
+ ),
14631
+ maxConcurrency: this.parseEnvInt(
14632
+ "OPENCODE_MAX_CONCURRENCY",
14633
+ DEFAULT_PARALLEL_AGENT.maxConcurrency
14634
+ ),
14635
+ enableDebug: process.env.OPENCODE_DEBUG_PARALLEL === "true",
14636
+ enableDetailedLogs: process.env.OPENCODE_DETAILED_LOGS === "true"
14637
+ };
14638
+ }
14639
+ getParallelAgentConfig() {
14640
+ return this.parallelAgentConfig;
14641
+ }
14642
+ // ------------------------------------------------------------------------
14643
+ // Background Task Config
14644
+ // ------------------------------------------------------------------------
14645
+ loadBackgroundTaskConfig() {
14646
+ return {
14647
+ ...DEFAULT_BACKGROUND_TASK,
14648
+ monitorIntervalMs: this.parseEnvInt(
14649
+ "OPENCODE_MONITOR_INTERVAL_MS",
14650
+ DEFAULT_BACKGROUND_TASK.monitorIntervalMs
14651
+ ),
14652
+ defaultTimeoutMs: this.parseEnvInt(
14653
+ "OPENCODE_DEFAULT_TIMEOUT_MS",
14654
+ DEFAULT_BACKGROUND_TASK.defaultTimeoutMs
14655
+ ),
14656
+ maxCompletedTasksToKeep: this.parseEnvInt(
14657
+ "OPENCODE_MAX_COMPLETED_TASKS",
14658
+ DEFAULT_BACKGROUND_TASK.maxCompletedTasksToKeep
14659
+ ),
14660
+ enableDebug: process.env.OPENCODE_DEBUG_BACKGROUND === "true"
14661
+ };
14662
+ }
14663
+ getBackgroundTaskConfig() {
14664
+ return this.backgroundTaskConfig;
14665
+ }
14666
+ // ------------------------------------------------------------------------
14667
+ // Session Config
14668
+ // ------------------------------------------------------------------------
14669
+ loadSessionConfig() {
14670
+ return {
14671
+ ...DEFAULT_SESSION,
14672
+ defaultMaxSteps: this.parseEnvInt(
14673
+ "OPENCODE_DEFAULT_MAX_STEPS",
14674
+ DEFAULT_SESSION.defaultMaxSteps
14675
+ ),
14676
+ taskCommandMaxSteps: this.parseEnvInt(
14677
+ "OPENCODE_TASK_MAX_STEPS",
14678
+ DEFAULT_SESSION.taskCommandMaxSteps
14679
+ )
14680
+ };
14681
+ }
14682
+ getSessionConfig() {
14683
+ return this.sessionConfig;
14684
+ }
14685
+ // ------------------------------------------------------------------------
14686
+ // Runtime Updates (Dynamic Configuration)
14687
+ // ------------------------------------------------------------------------
14688
+ /**
14689
+ * Update configuration at runtime
14690
+ * Useful for adaptive behavior or user preferences
14691
+ */
14692
+ updateParallelAgentConfig(updates) {
14693
+ this.parallelAgentConfig = {
14694
+ ...this.parallelAgentConfig,
14695
+ ...updates
14696
+ };
14697
+ this.validateParallelAgentConfig();
14698
+ }
14699
+ updateBackgroundTaskConfig(updates) {
14700
+ this.backgroundTaskConfig = {
14701
+ ...this.backgroundTaskConfig,
14702
+ ...updates
14703
+ };
14704
+ this.validateBackgroundTaskConfig();
14705
+ }
14706
+ updateSessionConfig(updates) {
14707
+ this.sessionConfig = {
14708
+ ...this.sessionConfig,
14709
+ ...updates
14710
+ };
14711
+ this.validateSessionConfig();
14712
+ }
14713
+ // ------------------------------------------------------------------------
14714
+ // Validation
14715
+ // ------------------------------------------------------------------------
14716
+ validateParallelAgentConfig() {
14717
+ const { taskTtlMs, cleanupDelayMs, minStabilityMs, pollIntervalMs, defaultConcurrency, maxConcurrency } = this.parallelAgentConfig;
14718
+ if (taskTtlMs < 60 * 1e3) {
14719
+ console.warn("[Config] TASK_TTL_MS too low (< 1min), using 1min minimum");
14720
+ this.parallelAgentConfig.taskTtlMs = 60 * 1e3;
14721
+ }
14722
+ if (cleanupDelayMs > taskTtlMs) {
14723
+ console.warn("[Config] CLEANUP_DELAY_MS cannot exceed TASK_TTL_MS");
14724
+ this.parallelAgentConfig.cleanupDelayMs = Math.floor(taskTtlMs / 2);
14725
+ }
14726
+ if (minStabilityMs < 1e3) {
14727
+ console.warn("[Config] MIN_STABILITY_MS too low (< 1s), using 1s minimum");
14728
+ this.parallelAgentConfig.minStabilityMs = 1e3;
14729
+ }
14730
+ if (pollIntervalMs < 500) {
14731
+ console.warn("[Config] POLL_INTERVAL_MS too low (< 500ms), using 500ms minimum");
14732
+ this.parallelAgentConfig.pollIntervalMs = 500;
14733
+ }
14734
+ if (defaultConcurrency < 1 || defaultConcurrency > maxConcurrency) {
14735
+ console.warn(`[Config] DEFAULT_CONCURRENCY must be 1-${maxConcurrency}`);
14736
+ this.parallelAgentConfig.defaultConcurrency = Math.min(
14737
+ Math.max(defaultConcurrency, 1),
14738
+ maxConcurrency
14739
+ );
14740
+ }
14741
+ }
14742
+ validateBackgroundTaskConfig() {
14743
+ const { monitorIntervalMs, defaultTimeoutMs, maxCompletedTasksToKeep } = this.backgroundTaskConfig;
14744
+ if (monitorIntervalMs < 1e3) {
14745
+ console.warn("[Config] MONITOR_INTERVAL_MS too low (< 1s), using 1s minimum");
14746
+ this.backgroundTaskConfig.monitorIntervalMs = 1e3;
14747
+ }
14748
+ if (defaultTimeoutMs < 10 * 1e3) {
14749
+ console.warn("[Config] DEFAULT_TIMEOUT_MS too low (< 10s), using 10s minimum");
14750
+ this.backgroundTaskConfig.defaultTimeoutMs = 10 * 1e3;
14751
+ }
14752
+ if (maxCompletedTasksToKeep < 0) {
14753
+ console.warn("[Config] MAX_COMPLETED_TASKS must be >= 0, using 0");
14754
+ this.backgroundTaskConfig.maxCompletedTasksToKeep = 0;
14755
+ }
14756
+ }
14757
+ validateSessionConfig() {
14758
+ const { defaultMaxSteps, taskCommandMaxSteps } = this.sessionConfig;
14759
+ if (defaultMaxSteps < 1) {
14760
+ console.warn("[Config] DEFAULT_MAX_STEPS must be >= 1, using 1");
14761
+ this.sessionConfig.defaultMaxSteps = 1;
14762
+ }
14763
+ if (taskCommandMaxSteps < defaultMaxSteps) {
14764
+ console.warn("[Config] TASK_MAX_STEPS should be >= DEFAULT_MAX_STEPS");
14765
+ this.sessionConfig.taskCommandMaxSteps = defaultMaxSteps;
14766
+ }
14767
+ }
14768
+ // ------------------------------------------------------------------------
14769
+ // Helpers
14770
+ // ------------------------------------------------------------------------
14771
+ parseEnvInt(key, defaultValue) {
14772
+ const value = process.env[key];
14773
+ if (value === void 0) return defaultValue;
14774
+ const parsed = parseInt(value, 10);
14775
+ if (isNaN(parsed)) {
14776
+ console.warn(`[Config] Invalid ${key} value: "${value}", using default ${defaultValue}`);
14777
+ return defaultValue;
14778
+ }
14779
+ return parsed;
14780
+ }
14781
+ // ------------------------------------------------------------------------
14782
+ // Export / Debug
14783
+ // ------------------------------------------------------------------------
14784
+ exportConfigs() {
14785
+ console.log("\n\u{1F4CB} Current Configuration:\n");
14786
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
14787
+ console.log("Parallel Agent:");
14788
+ console.log(` Task TTL: ${this.parallelAgentConfig.taskTtlMs / 1e3}s`);
14789
+ console.log(` Cleanup Delay: ${this.parallelAgentConfig.cleanupDelayMs / 1e3}s`);
14790
+ console.log(` Min Stability: ${this.parallelAgentConfig.minStabilityMs / 1e3}s`);
14791
+ console.log(` Poll Interval: ${this.parallelAgentConfig.pollIntervalMs / 1e3}s`);
14792
+ console.log(` Default Concurrency: ${this.parallelAgentConfig.defaultConcurrency}`);
14793
+ console.log(` Max Concurrency: ${this.parallelAgentConfig.maxConcurrency}`);
14794
+ console.log(` Debug: ${this.parallelAgentConfig.enableDebug}`);
14795
+ console.log("");
14796
+ console.log("Background Task:");
14797
+ console.log(` Monitor Interval: ${this.backgroundTaskConfig.monitorIntervalMs / 1e3}s`);
14798
+ console.log(` Default Timeout: ${this.backgroundTaskConfig.defaultTimeoutMs / 1e3}s`);
14799
+ console.log(` Max Completed Tasks: ${this.backgroundTaskConfig.maxCompletedTasksToKeep}`);
14800
+ console.log(` Storage Dir: ${this.backgroundTaskConfig.storageDir}`);
14801
+ console.log(` Debug: ${this.backgroundTaskConfig.enableDebug}`);
14802
+ console.log("");
14803
+ console.log("Session:");
14804
+ console.log(` Default Max Steps: ${this.sessionConfig.defaultMaxSteps}`);
14805
+ console.log(` Task Max Steps: ${this.sessionConfig.taskCommandMaxSteps}`);
14806
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
14807
+ }
14808
+ };
14809
+ var configManager = ConfigManager.getInstance();
14810
+
14811
+ // src/core/async-agent.ts
14812
+ var TASK_TTL_MS2 = configManager.getParallelAgentConfig().taskTtlMs;
14813
+ var CLEANUP_DELAY_MS2 = configManager.getParallelAgentConfig().cleanupDelayMs;
14814
+ var MIN_STABILITY_MS2 = configManager.getParallelAgentConfig().minStabilityMs;
14815
+ var POLL_INTERVAL_MS = configManager.getParallelAgentConfig().pollIntervalMs;
14816
+ var DEFAULT_CONCURRENCY = configManager.getParallelAgentConfig().defaultConcurrency;
14817
+ var MAX_CONCURRENCY = configManager.getParallelAgentConfig().maxConcurrency;
14818
+ var DEBUG = configManager.getParallelAgentConfig().enableDebug;
13889
14819
  var log = (...args) => {
13890
14820
  if (DEBUG) console.log("[parallel-agent]", ...args);
13891
14821
  };
@@ -13894,7 +14824,9 @@ var ConcurrencyController = class {
13894
14824
  queues = /* @__PURE__ */ new Map();
13895
14825
  limits = /* @__PURE__ */ new Map();
13896
14826
  setLimit(key, limit) {
13897
- this.limits.set(key, limit);
14827
+ const cappedLimit = Math.min(limit, MAX_CONCURRENCY);
14828
+ this.limits.set(key, cappedLimit);
14829
+ log(`Set limit for ${key}: ${cappedLimit}`);
13898
14830
  }
13899
14831
  getLimit(key) {
13900
14832
  return this.limits.get(key) ?? DEFAULT_CONCURRENCY;
@@ -13934,6 +14866,21 @@ var ConcurrencyController = class {
13934
14866
  getQueueLength(key) {
13935
14867
  return this.queues.get(key)?.length ?? 0;
13936
14868
  }
14869
+ updateConcurrency(agentType, newLimit) {
14870
+ this.setLimit(agentType, newLimit);
14871
+ log(`Updated concurrency for ${agentType}: ${newLimit}`);
14872
+ }
14873
+ getStats() {
14874
+ const stats = {};
14875
+ for (const [agentType, limit] of this.limits.entries()) {
14876
+ stats[agentType] = {
14877
+ running: this.counts.get(agentType) ?? 0,
14878
+ queued: this.getQueueLength(agentType),
14879
+ limit
14880
+ };
14881
+ }
14882
+ return stats;
14883
+ }
13937
14884
  };
13938
14885
  var ParallelAgentManager = class _ParallelAgentManager {
13939
14886
  static _instance;
@@ -14113,11 +15060,23 @@ var ParallelAgentManager = class _ParallelAgentManager {
14113
15060
  }
14114
15061
  }
14115
15062
  /**
14116
- * Set concurrency limit for agent type
14117
- */
15063
+ * Set concurrency limit for agent type
15064
+ */
14118
15065
  setConcurrencyLimit(agentType, limit) {
14119
15066
  this.concurrency.setLimit(agentType, limit);
14120
15067
  }
15068
+ /**
15069
+ * Get concurrency statistics for all agent types
15070
+ */
15071
+ getConcurrencyStats() {
15072
+ return this.concurrency.getStats();
15073
+ }
15074
+ /**
15075
+ * Update concurrency limit dynamically at runtime
15076
+ */
15077
+ updateConcurrency(agentType, newLimit) {
15078
+ this.concurrency.updateConcurrency(agentType, newLimit);
15079
+ }
14121
15080
  /**
14122
15081
  * Get pending notification count
14123
15082
  */
@@ -14197,7 +15156,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14197
15156
  const sessionStatus = allStatuses[task.sessionID];
14198
15157
  if (sessionStatus?.type === "idle") {
14199
15158
  const elapsed = Date.now() - task.startedAt.getTime();
14200
- if (elapsed < MIN_STABILITY_MS) continue;
15159
+ if (elapsed < MIN_STABILITY_MS2) continue;
14201
15160
  const hasOutput = await this.validateSessionHasOutput(task.sessionID);
14202
15161
  if (!hasOutput) continue;
14203
15162
  task.status = "completed";
@@ -14251,7 +15210,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14251
15210
  const now = Date.now();
14252
15211
  for (const [taskId, task] of this.tasks.entries()) {
14253
15212
  const age = now - task.startedAt.getTime();
14254
- if (age > TASK_TTL_MS) {
15213
+ if (age > TASK_TTL_MS2) {
14255
15214
  log(`Timeout: ${taskId} (${Math.round(age / 1e3)}s)`);
14256
15215
  if (task.status === "running") {
14257
15216
  task.status = "timeout";
@@ -14302,7 +15261,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14302
15261
  }
14303
15262
  this.tasks.delete(taskId);
14304
15263
  log(`Cleaned up ${taskId} from memory`);
14305
- }, CLEANUP_DELAY_MS);
15264
+ }, CLEANUP_DELAY_MS2);
14306
15265
  }
14307
15266
  // ========================================================================
14308
15267
  // Internal: Notifications
@@ -14495,7 +15454,7 @@ Session ID: ${sessionID}`;
14495
15454
  }
14496
15455
  const POLL_INTERVAL_MS2 = 500;
14497
15456
  const MAX_POLL_TIME_MS = 10 * 60 * 1e3;
14498
- const MIN_STABILITY_MS2 = 5e3;
15457
+ const MIN_STABILITY_MS3 = 5e3;
14499
15458
  const STABILITY_POLLS_REQUIRED = 3;
14500
15459
  let stablePolls = 0;
14501
15460
  let lastMsgCount = 0;
@@ -14509,7 +15468,7 @@ Session ID: ${sessionID}`;
14509
15468
  lastMsgCount = 0;
14510
15469
  continue;
14511
15470
  }
14512
- if (Date.now() - startTime < MIN_STABILITY_MS2) continue;
15471
+ if (Date.now() - startTime < MIN_STABILITY_MS3) continue;
14513
15472
  const messagesResult2 = await session.messages({
14514
15473
  path: { id: sessionID }
14515
15474
  });
@@ -14715,6 +15674,476 @@ function createAsyncAgentTools(manager, client) {
14715
15674
  };
14716
15675
  }
14717
15676
 
15677
+ // src/core/batch-processor.ts
15678
+ var SmartBatchProcessor = class {
15679
+ parallelAgentManager;
15680
+ tasks = /* @__PURE__ */ new Map();
15681
+ constructor(parallelAgentManager2) {
15682
+ this.parallelAgentManager = parallelAgentManager2;
15683
+ }
15684
+ /**
15685
+ * Process a batch of tasks with smart validation
15686
+ */
15687
+ async processBatch(tasks, options = {
15688
+ concurrency: 3,
15689
+ maxRetries: 2,
15690
+ validateAfterEach: false,
15691
+ continueOnError: true
15692
+ }) {
15693
+ const startTime = Date.now();
15694
+ for (const task of tasks) {
15695
+ this.tasks.set(task.id, {
15696
+ ...task,
15697
+ status: "pending",
15698
+ attempts: 0
15699
+ });
15700
+ }
15701
+ console.log(`
15702
+ \u{1F4E6} [Smart Batch] Starting ${tasks.length} tasks with concurrency ${options.concurrency}`);
15703
+ console.log(` Strategy: ${options.validateAfterEach ? "Validate each" : "Centralized validation"}`);
15704
+ await this.executePhase(tasks, options, "initial");
15705
+ const failedTasks = Array.from(this.tasks.values()).filter((t) => t.status === "failed" || t.status === "pending");
15706
+ if (failedTasks.length === 0) {
15707
+ console.log(`
15708
+ \u2705 [Smart Batch] All ${tasks.length} tasks succeeded!`);
15709
+ return this.buildResult(startTime, tasks);
15710
+ }
15711
+ console.log(`
15712
+ \u{1F50D} [Smart Batch] Validation complete: ${failedTasks.length}/${tasks.length} tasks need retry`);
15713
+ let retryCount = 0;
15714
+ while (failedTasks.length > 0 && retryCount < options.maxRetries) {
15715
+ retryCount++;
15716
+ console.log(`
15717
+ \u{1F504} [Smart Batch] Retry round ${retryCount}/${options.maxRetries} for ${failedTasks.length} tasks`);
15718
+ await this.executePhase(failedTasks, options, `retry-${retryCount}`);
15719
+ const stillFailed = Array.from(this.tasks.values()).filter((t) => t.status === "failed");
15720
+ if (stillFailed.length === failedTasks.length) {
15721
+ console.log(`\u26A0\uFE0F [Smart Batch] No progress in retry round ${retryCount}, stopping`);
15722
+ break;
15723
+ }
15724
+ }
15725
+ return this.buildResult(startTime, tasks);
15726
+ }
15727
+ /**
15728
+ * Execute a phase with concurrency control
15729
+ */
15730
+ async executePhase(tasks, options, phase) {
15731
+ for (const task of tasks) {
15732
+ this.parallelAgentManager.setConcurrencyLimit(task.agent, options.concurrency);
15733
+ }
15734
+ const batches = [];
15735
+ for (let i = 0; i < tasks.length; i += options.concurrency) {
15736
+ batches.push(tasks.slice(i, i + options.concurrency));
15737
+ }
15738
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
15739
+ const batch = batches[batchIndex];
15740
+ console.log(` [${phase}] Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} tasks)`);
15741
+ const batchPromises = batch.map((task) => this.executeTask(task, options));
15742
+ await Promise.all(batchPromises);
15743
+ if (!options.validateAfterEach) {
15744
+ continue;
15745
+ }
15746
+ const failedInBatch = batch.filter((t) => t.status === "failed");
15747
+ if (failedInBatch.length > 0) {
15748
+ console.log(` [${phase}] ${failedInBatch.length} failed in batch, immediate retry`);
15749
+ const retryPromises = failedInBatch.map((t) => this.executeTask(t, options));
15750
+ await Promise.all(retryPromises);
15751
+ }
15752
+ }
15753
+ }
15754
+ /**
15755
+ * Execute a single task
15756
+ */
15757
+ async executeTask(task, options) {
15758
+ const currentTask = this.tasks.get(task.id);
15759
+ if (!options.continueOnError && currentTask.status === "failed") {
15760
+ return;
15761
+ }
15762
+ try {
15763
+ currentTask.attempts++;
15764
+ currentTask.status = "pending";
15765
+ const launched = await this.parallelAgentManager.launch({
15766
+ agent: task.agent,
15767
+ description: task.description,
15768
+ prompt: task.prompt,
15769
+ parentSessionID: ""
15770
+ // Would need actual parent ID
15771
+ });
15772
+ const maxWaitTime = 5 * 60 * 1e3;
15773
+ const startTime = Date.now();
15774
+ while (Date.now() - startTime < maxWaitTime) {
15775
+ const taskData = this.parallelAgentManager.getTask(launched.id);
15776
+ if (!taskData) break;
15777
+ if (taskData.status === "completed") {
15778
+ currentTask.status = "success";
15779
+ console.log(` \u2705 [${task.id}] Success on attempt ${currentTask.attempts}`);
15780
+ return;
15781
+ }
15782
+ if (taskData.status === "error" || taskData.status === "timeout") {
15783
+ currentTask.status = "failed";
15784
+ currentTask.error = taskData.error || "Unknown error";
15785
+ console.log(` \u274C [${task.id}] Failed: ${currentTask.error}`);
15786
+ return;
15787
+ }
15788
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
15789
+ }
15790
+ currentTask.status = "failed";
15791
+ currentTask.error = "Timeout";
15792
+ } catch (error45) {
15793
+ currentTask.status = "failed";
15794
+ currentTask.error = error45 instanceof Error ? error45.message : String(error45);
15795
+ console.log(` \u274C [${task.id}] Exception: ${currentTask.error}`);
15796
+ }
15797
+ }
15798
+ /**
15799
+ * Build result summary
15800
+ */
15801
+ buildResult(startTime, tasks) {
15802
+ const allTasks = Array.from(this.tasks.values());
15803
+ const successCount = allTasks.filter((t) => t.status === "success").length;
15804
+ const failedCount = allTasks.filter((t) => t.status === "failed").length;
15805
+ const retriedCount = allTasks.filter((t) => t.attempts > 1).length;
15806
+ return {
15807
+ total: tasks.length,
15808
+ success: successCount,
15809
+ failed: failedCount,
15810
+ retried: retriedCount,
15811
+ duration: Date.now() - startTime,
15812
+ tasks: allTasks
15813
+ };
15814
+ }
15815
+ /**
15816
+ * Export failed tasks for manual review
15817
+ */
15818
+ exportFailedTasks() {
15819
+ const failedTasks = Array.from(this.tasks.values()).filter((t) => t.status === "failed");
15820
+ if (failedTasks.length === 0) {
15821
+ return "\u2705 No failed tasks to export.";
15822
+ }
15823
+ const output = failedTasks.map((task) => {
15824
+ return `
15825
+ ---
15826
+ Task ID: ${task.id}
15827
+ Agent: ${task.agent}
15828
+ Attempts: ${task.attempts}
15829
+ Error: ${task.error}
15830
+ Description: ${task.description}
15831
+ ---
15832
+ `.trim();
15833
+ }).join("\n");
15834
+ return `\u274C ${failedTasks.length} failed tasks:
15835
+ ${output}`;
15836
+ }
15837
+ /**
15838
+ * Clear all tasks
15839
+ */
15840
+ clear() {
15841
+ this.tasks.clear();
15842
+ }
15843
+ };
15844
+
15845
+ // src/tools/batch.ts
15846
+ var createProcessBatchTool = (parallelAgentManager2, client) => tool({
15847
+ description: `Execute a batch of tasks with intelligent validation and retry.
15848
+
15849
+ <strategy>
15850
+ 1. Execute all tasks in parallel (respecting concurrency limits)
15851
+ 2. Centralized validation: Identify ALL failed tasks at once
15852
+ 3. Retry ONLY failed tasks (not everything)
15853
+ </strategy>
15854
+
15855
+ <benefits>
15856
+ - Faster than naive retry: Failed tasks batch-identified
15857
+ - More efficient: No redundant work on successful tasks
15858
+ - Controlled: Concurrency limits prevent API overload
15859
+ </benefits>
15860
+
15861
+ <examples>
15862
+ process_batch({
15863
+ concurrency: 10,
15864
+ tasks: [
15865
+ { id: "task1", agent: "builder", description: "Test A", prompt: "..." },
15866
+ { id: "task2", agent: "inspector", description: "Test B", prompt: "..." }
15867
+ ]
15868
+ })
15869
+ </examples>`,
15870
+ args: {
15871
+ concurrency: tool.schema.string().describe("Concurrency limit (default: 3, max: 10)"),
15872
+ maxRetries: tool.schema.string().optional().describe("Maximum retry rounds (default: 2)"),
15873
+ validateAfterEach: tool.schema.boolean().optional().describe("Validate after each task (default: false = centralized validation)"),
15874
+ tasks: tool.schema.string().describe("Array of task objects in JSON format")
15875
+ },
15876
+ async execute(args) {
15877
+ const { concurrency, maxRetries = "2", validateAfterEach = false, tasks } = args;
15878
+ let taskList;
15879
+ try {
15880
+ taskList = JSON.parse(tasks);
15881
+ } catch {
15882
+ return "\u274C Invalid tasks JSON. Must be valid array.";
15883
+ }
15884
+ if (!Array.isArray(taskList)) {
15885
+ return "\u274C tasks must be an array of task objects.";
15886
+ }
15887
+ for (const task of taskList) {
15888
+ if (!task.id || !task.agent || !task.description || !task.prompt) {
15889
+ return `\u274C Task missing required fields (id, agent, description, prompt)`;
15890
+ }
15891
+ }
15892
+ const numConcurrency = parseInt(concurrency, 10);
15893
+ const numRetries = parseInt(maxRetries, 10);
15894
+ if (isNaN(numConcurrency) || numConcurrency < 1 || numConcurrency > 10) {
15895
+ return "\u274C Invalid concurrency. Must be 1-10.";
15896
+ }
15897
+ if (isNaN(numRetries) || numRetries < 0 || numRetries > 5) {
15898
+ return "\u274C Invalid maxRetries. Must be 0-5.";
15899
+ }
15900
+ const processor = new SmartBatchProcessor(parallelAgentManager2);
15901
+ const result = await processor.processBatch(taskList, {
15902
+ concurrency: numConcurrency,
15903
+ maxRetries: numRetries,
15904
+ validateAfterEach,
15905
+ continueOnError: true
15906
+ });
15907
+ const durationSecs = Math.floor(result.duration / 1e3);
15908
+ const successRate = (result.success / result.total * 100).toFixed(1);
15909
+ let output = `\u2705 **Batch Processing Complete**
15910
+
15911
+ | Metric | Value |
15912
+ |---------|--------|
15913
+ | **Total Tasks** | ${result.total} |
15914
+ | **Successful** | ${result.success} (${successRate}%) |
15915
+ | **Failed** | ${result.failed} |
15916
+ | **Retried** | ${result.retried} |
15917
+ | **Duration** | ${durationSecs}s |
15918
+
15919
+ `;
15920
+ if (result.failed > 0) {
15921
+ output += `\u26A0\uFE0F **Failed Tasks**
15922
+
15923
+ Use \`export_failed_tasks()\` to review failed tasks and manually fix issues.
15924
+
15925
+ ---
15926
+
15927
+ Failed Task IDs:
15928
+ ${result.tasks.filter((t) => t.status === "failed").map((t) => ` - ${t.id}`).join("\n")}
15929
+ ---
15930
+ `;
15931
+ }
15932
+ return output;
15933
+ }
15934
+ });
15935
+ var createExportFailedTasksTool = (parallelAgentManager2) => tool({
15936
+ description: `Export failed tasks from the last batch for manual review.`,
15937
+ args: {},
15938
+ async execute() {
15939
+ const processor = new SmartBatchProcessor(parallelAgentManager2);
15940
+ return processor.exportFailedTasks();
15941
+ }
15942
+ });
15943
+ var createCompareStrategiesTool = (parallelAgentManager2) => tool({
15944
+ description: `Compare naive retry vs smart batch validation performance.
15945
+
15946
+ <comparison>
15947
+ **Naive Strategy** (current):
15948
+ - Concurrency: 3 fixed
15949
+ - Retry: Per-task immediate retry
15950
+ - Issue: Slow for large batches, redundant work
15951
+
15952
+ **Smart Batch Strategy** (new):
15953
+ - Concurrency: Configurable (up to 10)
15954
+ - Validation: Centralized, batch-identify failures
15955
+ - Retry: Only failed tasks
15956
+ - Benefit: Faster, less redundant work
15957
+ </comparison>`,
15958
+ args: {
15959
+ taskCount: tool.schema.string().describe("Number of simulated tasks (default: 100)"),
15960
+ concurrency1: tool.schema.string().optional().describe("Strategy 1 concurrency (default: 3)"),
15961
+ concurrency2: tool.schema.string().optional().describe("Strategy 2 concurrency (default: 10)")
15962
+ },
15963
+ async execute(args) {
15964
+ const taskCount = parseInt(args.taskCount || "100", 10);
15965
+ const concurrency1 = parseInt(args.concurrency1 || "3", 10);
15966
+ const concurrency2 = parseInt(args.concurrency2 || "10", 10);
15967
+ const naiveBatches = Math.ceil(taskCount / concurrency1);
15968
+ const naiveTime = naiveBatches * 60;
15969
+ const smartBatches = Math.ceil(taskCount / concurrency2);
15970
+ const smartTime = smartBatches * 60 + 30;
15971
+ const timeDiff = naiveTime - smartTime;
15972
+ const improvement = (timeDiff / naiveTime * 100).toFixed(1);
15973
+ return `\u{1F4CA} **Strategy Comparison for ${taskCount} tasks**
15974
+
15975
+ **Strategy 1: Naive (Current)**
15976
+ | Metric | Value |
15977
+ |---------|--------|
15978
+ | Concurrency | ${concurrency1} |
15979
+ | Batches | ${naiveBatches} |
15980
+ | Est. Time | ${naiveTime}s |
15981
+
15982
+ **Strategy 2: Smart Batch (Proposed)**
15983
+ | Metric | Value |
15984
+ |---------|--------|
15985
+ | Concurrency | ${concurrency2} |
15986
+ | Batches | ${smartBatches} |
15987
+ | Est. Time | ${smartTime}s |
15988
+
15989
+ **Summary**
15990
+ | Metric | Value |
15991
+ |---------|--------|
15992
+ | Time Saved | ${timeDiff}s (${improvement}%) |
15993
+ | Batches Saved | ${naiveBatches - smartBatches} |
15994
+
15995
+ Recommendation: Use **Smart Batch** with concurrency ${concurrency2} for ${timeDiff}s improvement.`;
15996
+ }
15997
+ });
15998
+ function createBatchTools(parallelAgentManager2, client) {
15999
+ return {
16000
+ process_batch: createProcessBatchTool(parallelAgentManager2, client),
16001
+ export_failed_tasks: createExportFailedTasksTool(parallelAgentManager2),
16002
+ compare_strategies: createCompareStrategiesTool(parallelAgentManager2)
16003
+ };
16004
+ }
16005
+
16006
+ // src/tools/config.ts
16007
+ var createShowConfigTool = () => tool({
16008
+ description: `Display current OpenCode Orchestrator configuration.
16009
+
16010
+ Shows all dynamic settings including timeouts, concurrency limits, and debug flags.`,
16011
+ args: {},
16012
+ async execute() {
16013
+ configManager.exportConfigs();
16014
+ return "";
16015
+ }
16016
+ });
16017
+ var createSetConcurrencyTool = (client) => tool({
16018
+ description: `Update concurrency limit for a specific agent type.
16019
+
16020
+ <examples>
16021
+ set_concurrency({ agent: "builder", limit: 5 })
16022
+ set_concurrency({ agent: "inspector", limit: 2 })
16023
+ </examples>
16024
+
16025
+ <notes>
16026
+ - Changes take effect immediately
16027
+ - Queued tasks will start when slots become available
16028
+ - Limit cannot exceed global max (10)
16029
+ </notes>`,
16030
+ args: {
16031
+ agent: tool.schema.string().describe('Agent type (e.g., "builder", "inspector", "architect")'),
16032
+ limit: tool.schema.string().describe("New concurrency limit (number)")
16033
+ },
16034
+ async execute(args) {
16035
+ const { agent, limit } = args;
16036
+ const numLimit = parseInt(limit, 10);
16037
+ if (isNaN(numLimit) || numLimit < 1) {
16038
+ return `\u274C Invalid limit: "${limit}". Must be a number >= 1.`;
16039
+ }
16040
+ const maxLimit = configManager.getParallelAgentConfig().maxConcurrency;
16041
+ if (numLimit > maxLimit) {
16042
+ return `\u274C Limit ${numLimit} exceeds global max ${maxLimit}. Using ${maxLimit}.`;
16043
+ }
16044
+ configManager.updateParallelAgentConfig({
16045
+ defaultConcurrency: numLimit
16046
+ });
16047
+ return `\u2705 **Concurrency Updated**
16048
+
16049
+ | Property | Value |
16050
+ |----------|-------|
16051
+ | **Agent** | ${agent} |
16052
+ | **New Limit** | ${numLimit} parallel tasks |
16053
+
16054
+ Changes take effect immediately. New tasks will respect to the new limit.`;
16055
+ }
16056
+ });
16057
+ var createSetTimeoutTool = () => tool({
16058
+ description: `Update task timeout duration.
16059
+
16060
+ <examples>
16061
+ set_timeout({ taskTtlMinutes: 45 })
16062
+ set_timeout({ cleanupDelayMinutes: 2 })
16063
+ </examples>`,
16064
+ args: {
16065
+ taskTtlMinutes: tool.schema.string().optional().describe("Task timeout in minutes (default: 30)"),
16066
+ cleanupDelayMinutes: tool.schema.string().optional().describe("Cleanup delay after completion in minutes (default: 5)")
16067
+ },
16068
+ async execute(args) {
16069
+ const { taskTtlMinutes, cleanupDelayMinutes } = args;
16070
+ const updates = {};
16071
+ if (taskTtlMinutes) {
16072
+ const ttl = parseInt(taskTtlMinutes, 10);
16073
+ if (isNaN(ttl) || ttl < 1) {
16074
+ return `\u274C Invalid taskTtlMinutes: "${taskTtlMinutes}". Must be number >= 1.`;
16075
+ }
16076
+ updates.taskTtlMs = ttl * 60 * 1e3;
16077
+ }
16078
+ if (cleanupDelayMinutes) {
16079
+ const delay = parseInt(cleanupDelayMinutes, 10);
16080
+ if (isNaN(delay) || delay < 0) {
16081
+ return `\u274C Invalid cleanupDelayMinutes: "${cleanupDelayMinutes}". Must be number >= 0.`;
16082
+ }
16083
+ updates.cleanupDelayMs = delay * 60 * 1e3;
16084
+ }
16085
+ if (Object.keys(updates).length === 0) {
16086
+ return "\u26A0\uFE0F No changes specified. Provide at least one parameter.";
16087
+ }
16088
+ configManager.updateParallelAgentConfig(updates);
16089
+ let output = "\u2705 **Timeout Configuration Updated**\n\n";
16090
+ if (updates.taskTtlMs) {
16091
+ output += `| Task TTL | ${updates.taskTtlMs / 60 / 1e3} minutes |
16092
+ `;
16093
+ }
16094
+ if (updates.cleanupDelayMs) {
16095
+ output += `| Cleanup Delay | ${updates.cleanupDelayMs / 60 / 1e3} minutes |
16096
+ `;
16097
+ }
16098
+ output += "\nChanges take effect immediately.";
16099
+ return output;
16100
+ }
16101
+ });
16102
+ var createSetDebugTool = () => tool({
16103
+ description: `Enable or disable debug logging.
16104
+
16105
+ <examples>
16106
+ set_debug({ component: "parallel_agent", enable: true })
16107
+ set_debug({ component: "background_task", enable: false })
16108
+ </examples>`,
16109
+ args: {
16110
+ component: tool.schema.string().describe('Component to debug: "parallel_agent", "background_task", or "all"'),
16111
+ enable: tool.schema.boolean().describe("Enable (true) or disable (false) debug logs")
16112
+ },
16113
+ async execute(args) {
16114
+ const { component, enable } = args;
16115
+ const enableValue = enable ? "true" : "false";
16116
+ if (component === "parallel_agent" || component === "all") {
16117
+ process.env.OPENCODE_DEBUG_PARALLEL = enableValue;
16118
+ configManager.updateParallelAgentConfig({
16119
+ enableDebug: enable
16120
+ });
16121
+ }
16122
+ if (component === "background_task" || component === "all") {
16123
+ process.env.OPENCODE_DEBUG_BACKGROUND = enableValue;
16124
+ configManager.updateBackgroundTaskConfig({
16125
+ enableDebug: enable
16126
+ });
16127
+ }
16128
+ return `\u2705 **Debug Logging ${enable ? "Enabled" : "Disabled"}**
16129
+
16130
+ | Component | Debug Status |
16131
+ |-----------|--------------|
16132
+ | ${component === "all" ? "parallel_agent" : component} | ${enable ? "\u{1F527} Enabled" : "\u{1F507} Disabled"} |
16133
+ ${component === "all" ? "| background_task | \u{1F527} Enabled |" : ""}
16134
+
16135
+ Changes take effect immediately.`;
16136
+ }
16137
+ });
16138
+ function createConfigTools(client) {
16139
+ return {
16140
+ show_config: createShowConfigTool(),
16141
+ set_concurrency: createSetConcurrencyTool(client),
16142
+ set_timeout: createSetTimeoutTool(),
16143
+ set_debug: createSetDebugTool()
16144
+ };
16145
+ }
16146
+
14718
16147
  // src/utils/common.ts
14719
16148
  function detectSlashCommand(text) {
14720
16149
  const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
@@ -14738,110 +16167,6 @@ function formatElapsedTime(startMs, endMs = Date.now()) {
14738
16167
  return parts.join(" ");
14739
16168
  }
14740
16169
 
14741
- // src/utils/sanity.ts
14742
- function checkOutputSanity(text) {
14743
- if (!text || text.length < 50) {
14744
- return { isHealthy: true, severity: "ok" };
14745
- }
14746
- if (/(.)\1{15,}/.test(text)) {
14747
- return {
14748
- isHealthy: false,
14749
- reason: "Single character repetition detected",
14750
- severity: "critical"
14751
- };
14752
- }
14753
- if (/(.{2,6})\1{8,}/.test(text)) {
14754
- return {
14755
- isHealthy: false,
14756
- reason: "Pattern loop detected",
14757
- severity: "critical"
14758
- };
14759
- }
14760
- if (text.length > 200) {
14761
- const cleanText = text.replace(/\s/g, "");
14762
- if (cleanText.length > 100) {
14763
- const uniqueChars = new Set(cleanText).size;
14764
- const ratio = uniqueChars / cleanText.length;
14765
- if (ratio < 0.02) {
14766
- return {
14767
- isHealthy: false,
14768
- reason: "Low information density",
14769
- severity: "critical"
14770
- };
14771
- }
14772
- }
14773
- }
14774
- const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
14775
- if (boxChars > 100 && boxChars / text.length > 0.3) {
14776
- return {
14777
- isHealthy: false,
14778
- reason: "Visual gibberish detected",
14779
- severity: "critical"
14780
- };
14781
- }
14782
- const lines = text.split("\n").filter((l) => l.trim().length > 10);
14783
- if (lines.length > 10) {
14784
- const lineSet = new Set(lines);
14785
- if (lineSet.size < lines.length * 0.2) {
14786
- return {
14787
- isHealthy: false,
14788
- reason: "Excessive line repetition",
14789
- severity: "warning"
14790
- };
14791
- }
14792
- }
14793
- const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
14794
- if (cjkChars > 200) {
14795
- const uniqueCjk = new Set(
14796
- text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
14797
- ).size;
14798
- if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
14799
- return {
14800
- isHealthy: false,
14801
- reason: "CJK character spam detected",
14802
- severity: "critical"
14803
- };
14804
- }
14805
- }
14806
- return { isHealthy: true, severity: "ok" };
14807
- }
14808
- var RECOVERY_PROMPT = `<anomaly_recovery>
14809
- \u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
14810
-
14811
- <recovery_protocol>
14812
- 1. DISCARD the corrupted output completely - do not reference it
14813
- 2. RECALL the original mission objective
14814
- 3. IDENTIFY the last confirmed successful step
14815
- 4. RESTART with a simpler, more focused approach
14816
- </recovery_protocol>
14817
-
14818
- <instructions>
14819
- - If a sub-agent produced bad output: try a different agent or simpler task
14820
- - If stuck in a loop: break down the task into smaller pieces
14821
- - If context seems corrupted: call recorder to restore context
14822
- - THINK in English for maximum stability
14823
- </instructions>
14824
-
14825
- What was the original task? Proceed from the last known good state.
14826
- </anomaly_recovery>`;
14827
- var ESCALATION_PROMPT = `<critical_anomaly>
14828
- \u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
14829
-
14830
- <emergency_protocol>
14831
- 1. STOP current execution path immediately
14832
- 2. DO NOT continue with the same approach - it is failing
14833
- 3. CALL architect for a completely new strategy
14834
- 4. If architect also fails: report status to user and await guidance
14835
- </emergency_protocol>
14836
-
14837
- <diagnosis>
14838
- The current approach is producing corrupted output.
14839
- This may indicate: context overload, model instability, or task complexity.
14840
- </diagnosis>
14841
-
14842
- Request a fresh plan from architect with reduced scope.
14843
- </critical_anomaly>`;
14844
-
14845
16170
  // src/index.ts
14846
16171
  var PLUGIN_VERSION = "0.2.4";
14847
16172
  var DEFAULT_MAX_STEPS = 500;
@@ -14875,6 +16200,8 @@ var OrchestratorPlugin = async (input) => {
14875
16200
  const sessions = /* @__PURE__ */ new Map();
14876
16201
  const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
14877
16202
  const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
16203
+ const batchTools = createBatchTools(parallelAgentManager2, client);
16204
+ const configTools = createConfigTools(client);
14878
16205
  return {
14879
16206
  // -----------------------------------------------------------------
14880
16207
  // Tools we expose to the LLM
@@ -14892,7 +16219,13 @@ var OrchestratorPlugin = async (input) => {
14892
16219
  list_background: listBackgroundTool,
14893
16220
  kill_background: killBackgroundTool,
14894
16221
  // Async agent tools - spawn agents in parallel sessions
14895
- ...asyncAgentTools
16222
+ ...asyncAgentTools,
16223
+ // Git tools - branch info and status
16224
+ git_branch: gitBranchTool(directory),
16225
+ // Smart batch tools - centralized validation and retry
16226
+ ...batchTools,
16227
+ // Configuration tools - dynamic runtime settings
16228
+ ...configTools
14896
16229
  },
14897
16230
  // -----------------------------------------------------------------
14898
16231
  // Config hook - registers our commands and agents with OpenCode