opencode-orchestrator 0.5.16 → 0.5.18

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 (82) hide show
  1. package/README.md +360 -24
  2. package/dist/agents/subagents/librarian.d.ts +10 -0
  3. package/dist/agents/subagents/researcher.d.ts +11 -0
  4. package/dist/core/agents/concurrency.d.ts +2 -6
  5. package/dist/core/agents/config.d.ts +1 -1
  6. package/dist/core/agents/consts/index.d.ts +4 -0
  7. package/dist/core/agents/consts/task-status.const.d.ts +12 -0
  8. package/dist/core/agents/interfaces/concurrency-config.interface.d.ts +9 -0
  9. package/dist/core/agents/interfaces/index.d.ts +6 -3
  10. package/dist/core/agents/interfaces/{parallel-task.d.ts → parallel-task.interface.d.ts} +4 -8
  11. package/dist/core/agents/interfaces/resume-input.interface.d.ts +17 -0
  12. package/dist/core/agents/interfaces/task-progress.interface.d.ts +9 -0
  13. package/dist/core/agents/manager/event-handler.d.ts +34 -0
  14. package/dist/core/agents/manager/index.d.ts +10 -0
  15. package/dist/core/agents/manager/task-cleaner.d.ts +17 -0
  16. package/dist/core/agents/manager/task-launcher.d.ts +20 -0
  17. package/dist/core/agents/manager/task-poller.d.ts +26 -0
  18. package/dist/core/agents/manager/task-resumer.d.ts +18 -0
  19. package/dist/core/agents/manager.d.ts +11 -29
  20. package/dist/core/agents/task-store.d.ts +29 -3
  21. package/dist/core/agents/types/index.d.ts +1 -1
  22. package/dist/core/agents/types/parallel-task-status.type.d.ts +4 -0
  23. package/dist/core/bus/event-bus.d.ts +53 -0
  24. package/dist/core/bus/index.d.ts +19 -0
  25. package/dist/core/bus/interfaces.d.ts +34 -0
  26. package/dist/core/bus/types.d.ts +12 -0
  27. package/dist/core/cache/constants.d.ts +6 -0
  28. package/dist/core/cache/document-cache.d.ts +6 -0
  29. package/dist/core/cache/index.d.ts +4 -0
  30. package/dist/core/cache/interfaces.d.ts +53 -0
  31. package/dist/core/cache/operations.d.ts +36 -0
  32. package/dist/core/cache/utils.d.ts +20 -0
  33. package/dist/core/loop/formatters.d.ts +16 -0
  34. package/dist/core/loop/interfaces.d.ts +34 -0
  35. package/dist/core/loop/parser.d.ts +8 -0
  36. package/dist/core/loop/stats.d.ts +24 -0
  37. package/dist/core/loop/todo-enforcer.d.ts +9 -0
  38. package/dist/core/notification/event-integration.d.ts +7 -0
  39. package/dist/core/notification/presets.d.ts +14 -0
  40. package/dist/core/notification/toast-core.d.ts +28 -0
  41. package/dist/core/notification/toast.d.ts +9 -0
  42. package/dist/core/notification/types.d.ts +19 -0
  43. package/dist/core/orchestrator/types/task-status.d.ts +2 -2
  44. package/dist/core/progress/calculator.d.ts +11 -0
  45. package/dist/core/progress/formatters.d.ts +20 -0
  46. package/dist/core/progress/interfaces.d.ts +54 -0
  47. package/dist/core/progress/store.d.ts +28 -0
  48. package/dist/core/progress/tracker.d.ts +11 -0
  49. package/dist/core/queue/async-queue.d.ts +46 -0
  50. package/dist/core/queue/async-utils.d.ts +20 -0
  51. package/dist/core/queue/index.d.ts +8 -0
  52. package/dist/core/queue/work-pool.d.ts +19 -0
  53. package/dist/core/recovery/auto-recovery.d.ts +9 -0
  54. package/dist/core/recovery/constants.d.ts +6 -0
  55. package/dist/core/recovery/handler.d.ts +27 -0
  56. package/dist/core/recovery/interfaces.d.ts +63 -0
  57. package/dist/core/recovery/patterns.d.ts +8 -0
  58. package/dist/core/session/interfaces.d.ts +53 -0
  59. package/dist/core/session/shared-context.d.ts +8 -0
  60. package/dist/core/session/store.d.ts +44 -0
  61. package/dist/core/session/summary.d.ts +7 -0
  62. package/dist/core/task/interfaces.d.ts +54 -0
  63. package/dist/core/task/parser.d.ts +8 -0
  64. package/dist/core/task/scheduler.d.ts +12 -0
  65. package/dist/core/task/store.d.ts +32 -0
  66. package/dist/core/task/summary.d.ts +7 -0
  67. package/dist/core/task/task-decomposer.d.ts +10 -0
  68. package/dist/index.d.ts +2 -133
  69. package/dist/index.js +2600 -675
  70. package/dist/shared/agent.d.ts +2 -0
  71. package/dist/shared/constants.d.ts +56 -3
  72. package/dist/shared/event-types.d.ts +77 -0
  73. package/dist/tools/background-cmd/list.d.ts +2 -2
  74. package/dist/tools/parallel/delegate-task.d.ts +3 -0
  75. package/dist/tools/web/cache-docs.d.ts +21 -0
  76. package/dist/tools/web/codesearch.d.ts +19 -0
  77. package/dist/tools/web/index.d.ts +9 -0
  78. package/dist/tools/web/webfetch.d.ts +19 -0
  79. package/dist/tools/web/websearch.d.ts +17 -0
  80. package/package.json +8 -7
  81. package/dist/core/agents/types/parallel-task-status.d.ts +0 -4
  82. /package/dist/core/agents/interfaces/{launch-input.d.ts → launch-input.interface.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -4,14 +4,149 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/index.ts
8
+ import { createRequire } from "node:module";
9
+
7
10
  // src/shared/agent.ts
8
11
  var AGENT_NAMES = {
9
12
  COMMANDER: "commander",
10
13
  ARCHITECT: "architect",
11
14
  BUILDER: "builder",
12
15
  INSPECTOR: "inspector",
13
- RECORDER: "recorder"
16
+ RECORDER: "recorder",
17
+ LIBRARIAN: "librarian",
18
+ RESEARCHER: "researcher"
19
+ };
20
+
21
+ // src/core/agents/consts/task-status.const.ts
22
+ var TASK_STATUS = {
23
+ PENDING: "pending",
24
+ RUNNING: "running",
25
+ COMPLETED: "completed",
26
+ FAILED: "failed",
27
+ ERROR: "error",
28
+ TIMEOUT: "timeout",
29
+ CANCELLED: "cancelled"
30
+ };
31
+
32
+ // src/shared/constants.ts
33
+ var TIME = {
34
+ SECOND: 1e3,
35
+ MINUTE: 60 * 1e3,
36
+ HOUR: 60 * 60 * 1e3
37
+ };
38
+ var ID_PREFIX = {
39
+ /** Parallel agent task ID (e.g., task_a1b2c3d4) */
40
+ TASK: "task_",
41
+ /** Background command job ID (e.g., job_a1b2c3d4) */
42
+ JOB: "job_",
43
+ /** Session ID prefix */
44
+ SESSION: "session_"
45
+ };
46
+ var PARALLEL_TASK = {
47
+ TTL_MS: 60 * TIME.MINUTE,
48
+ // 60 minutes
49
+ CLEANUP_DELAY_MS: 10 * TIME.MINUTE,
50
+ // 10 minutes
51
+ MIN_STABILITY_MS: 3 * TIME.SECOND,
52
+ // 3 seconds
53
+ POLL_INTERVAL_MS: 1e3,
54
+ // 1 second
55
+ DEFAULT_CONCURRENCY: 10,
56
+ // 10 per agent type
57
+ MAX_CONCURRENCY: 50,
58
+ // 50 total
59
+ SYNC_TIMEOUT_MS: 10 * TIME.MINUTE
60
+ // 10 minutes for sync mode
61
+ };
62
+ var MEMORY_LIMITS = {
63
+ MAX_TASKS_IN_MEMORY: 1e3,
64
+ MAX_NOTIFICATIONS_PER_PARENT: 100,
65
+ MAX_EVENT_HISTORY: 100,
66
+ MAX_TOAST_HISTORY: 50,
67
+ MAX_PROGRESS_HISTORY_PER_SESSION: 100,
68
+ ARCHIVE_AGE_MS: 30 * TIME.MINUTE,
69
+ // Archive completed after 30 min
70
+ ERROR_CLEANUP_AGE_MS: 10 * TIME.MINUTE
71
+ // Remove errors after 10 min
72
+ };
73
+ var PATHS = {
74
+ TASK_ARCHIVE: ".cache/task-archive",
75
+ DOC_CACHE: ".cache/docs"
76
+ };
77
+ var BACKGROUND_TASK = {
78
+ DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
79
+ MAX_OUTPUT_LENGTH: 1e4
80
+ };
81
+ var TOOL_NAMES = {
82
+ // Parallel task tools
83
+ DELEGATE_TASK: "delegate_task",
84
+ GET_TASK_RESULT: "get_task_result",
85
+ LIST_TASKS: "list_tasks",
86
+ CANCEL_TASK: "cancel_task",
87
+ // Background command tools
88
+ RUN_BACKGROUND: "run_background",
89
+ CHECK_BACKGROUND: "check_background",
90
+ LIST_BACKGROUND: "list_background",
91
+ KILL_BACKGROUND: "kill_background",
92
+ // Search tools
93
+ GREP_SEARCH: "grep_search",
94
+ GLOB_SEARCH: "glob_search",
95
+ MGREP: "mgrep",
96
+ // Web tools
97
+ WEBFETCH: "webfetch",
98
+ WEBSEARCH: "websearch",
99
+ CODESEARCH: "codesearch",
100
+ CACHE_DOCS: "cache_docs",
101
+ // Other tools
102
+ CALL_AGENT: "call_agent",
103
+ SLASHCOMMAND: "slashcommand"
104
+ };
105
+ var MISSION = {
106
+ /** Mission completion marker (with emoji) */
107
+ COMPLETE: "\u2705 MISSION COMPLETE",
108
+ /** Mission completion marker (text only) */
109
+ COMPLETE_TEXT: "MISSION COMPLETE",
110
+ /** Stop command */
111
+ STOP_COMMAND: "/stop",
112
+ /** Cancel command */
113
+ CANCEL_COMMAND: "/cancel"
114
+ };
115
+ var AGENT_EMOJI = {
116
+ architect: "\u{1F3D7}\uFE0F",
117
+ builder: "\u{1F528}",
118
+ inspector: "\u{1F50D}",
119
+ recorder: "\u{1F4BE}",
120
+ commander: "\u{1F3AF}",
121
+ librarian: "\u{1F4DA}",
122
+ researcher: "\u{1F52C}"
123
+ };
124
+ var PART_TYPES = {
125
+ TEXT: "text",
126
+ REASONING: "reasoning",
127
+ TOOL_CALL: "tool_call",
128
+ TOOL_RESULT: "tool_result"
14
129
  };
130
+ var PROMPTS = {
131
+ /** Simple continue prompt for auto-continue loop */
132
+ CONTINUE: "continue",
133
+ /** Default fallback for empty arguments */
134
+ CONTINUE_PREVIOUS: "continue previous work",
135
+ /** Default fallback for slash commands */
136
+ CONTINUE_DEFAULT: "continue from where we left off"
137
+ };
138
+ var STATUS_EMOJI = {
139
+ pending: "\u23F3",
140
+ running: "\u{1F504}",
141
+ completed: "\u2705",
142
+ done: "\u2705",
143
+ error: "\u274C",
144
+ timeout: "\u23F0",
145
+ cancelled: "\u{1F6AB}"
146
+ };
147
+ function getStatusEmoji(status) {
148
+ return STATUS_EMOJI[status] ?? "\u2753";
149
+ }
15
150
 
16
151
  // src/agents/orchestrator.ts
17
152
  var orchestrator = {
@@ -22,7 +157,7 @@ You are Commander. Complete missions autonomously. Never stop until done.
22
157
  </role>
23
158
 
24
159
  <core_rules>
25
- 1. Never stop until "\u2705 MISSION COMPLETE"
160
+ 1. Never stop until "${MISSION.COMPLETE}"
26
161
  2. Never wait for user during execution
27
162
  3. Never stop because agent returned nothing
28
163
  4. Always survey environment & codebase BEFORE coding
@@ -39,6 +174,47 @@ Evaluate the complexity of the request:
39
174
  | \u{1F534} L3: Complex | Refactoring, infra change, unknown scope | **DEEP TRACK** |
40
175
  </phase_0>
41
176
 
177
+ <anti_hallucination>
178
+ CRITICAL: ELIMINATE GUESSING. VERIFY EVERYTHING.
179
+
180
+ BEFORE ANY IMPLEMENTATION:
181
+ 1. If using unfamiliar API/library \u2192 RESEARCH FIRST
182
+ 2. If uncertain about patterns/syntax \u2192 SEARCH DOCUMENTATION
183
+ 3. NEVER assume - always verify from official sources
184
+
185
+ RESEARCH WORKFLOW:
186
+ \`\`\`
187
+ // Step 1: Search for documentation
188
+ websearch({ query: "Next.js 14 app router official docs" })
189
+
190
+ // Step 2: Fetch specific documentation
191
+ webfetch({ url: "https://nextjs.org/docs/app/..." })
192
+
193
+ // Step 3: Check cached docs
194
+ cache_docs({ action: "list" })
195
+
196
+ // Step 4: For complex research, delegate to Librarian
197
+ ${TOOL_NAMES.DELEGATE_TASK}({
198
+ agent: "${AGENT_NAMES.LIBRARIAN}",
199
+ description: "Research X API",
200
+ prompt: "Find official documentation for...",
201
+ background: false // Wait for research before implementing
202
+ })
203
+ \`\`\`
204
+
205
+ MANDATORY RESEARCH TRIGGERS:
206
+ - New library/framework you haven't used in this session
207
+ - API syntax you're not 100% sure about
208
+ - Version-specific features (check version compatibility!)
209
+ - Configuration patterns (check official examples)
210
+
211
+ WHEN CAUGHT GUESSING:
212
+ 1. STOP immediately
213
+ 2. Search for official documentation
214
+ 3. Cache important findings: webfetch({ url: "...", cache: true })
215
+ 4. Then proceed with verified information
216
+ </anti_hallucination>
217
+
42
218
  <phase_1 name="CONTEXT_GATHERING">
43
219
  IF FAST TRACK (L1):
44
220
  - Scan ONLY the target file and its immediate imports.
@@ -57,26 +233,42 @@ RECORD findings if on Deep Track.
57
233
  <phase_2 name="TOOL_AGENT_SELECTION">
58
234
  | Track | Strategy |
59
235
  |-------|----------|
60
- | Fast | Use \`builder\` directly. Skip \`architect\`. |
61
- | Normal | Call \`architect\` for lightweight plan. |
62
- | Deep | Full \`architect\` DAG + \`recorder\` state tracking. |
236
+ | Fast | Use \`${AGENT_NAMES.BUILDER}\` directly. Skip \`${AGENT_NAMES.ARCHITECT}\`. |
237
+ | Normal | Call \`${AGENT_NAMES.ARCHITECT}\` for lightweight plan. |
238
+ | Deep | Full \`${AGENT_NAMES.ARCHITECT}\` DAG + \`${AGENT_NAMES.RECORDER}\` state tracking. |
239
+
240
+ AVAILABLE AGENTS:
241
+ - \`${AGENT_NAMES.ARCHITECT}\`: Task decomposition and planning
242
+ - \`${AGENT_NAMES.BUILDER}\`: Code implementation
243
+ - \`${AGENT_NAMES.INSPECTOR}\`: Verification and bug fixing
244
+ - \`${AGENT_NAMES.RECORDER}\`: State tracking (Deep Track only)
245
+ - \`${AGENT_NAMES.LIBRARIAN}\`: Documentation research (Anti-Hallucination) \u2B50 NEW
246
+
247
+ WHEN TO USE LIBRARIAN:
248
+ - Before using new APIs/libraries
249
+ - When error messages are unclear
250
+ - When implementing complex integrations
251
+ - When official documentation is needed
63
252
 
64
253
  DEFAULT to Deep Track if unsure to act safely.
65
254
  </phase_2>
66
255
 
67
256
  <phase_3 name="DELEGATION">
68
257
  <agent_calling>
69
- CRITICAL: USE delegate_task FOR ALL DELEGATION
258
+ CRITICAL: USE ${TOOL_NAMES.DELEGATE_TASK} FOR ALL DELEGATION
70
259
 
71
- delegate_task has TWO MODES:
260
+ ${TOOL_NAMES.DELEGATE_TASK} has THREE MODES:
72
261
  - background=true: Non-blocking, parallel execution
73
262
  - background=false: Blocking, waits for result
263
+ - resume: Continue existing session
74
264
 
75
265
  | Situation | How to Call |
76
266
  |-----------|-------------|
77
- | Multiple independent tasks | \`delegate_task({ ..., background: true })\` for each |
78
- | Single task, continue working | \`delegate_task({ ..., background: true })\` |
79
- | Need result for VERY next step | \`delegate_task({ ..., background: false })\` |
267
+ | Multiple independent tasks | \`${TOOL_NAMES.DELEGATE_TASK}({ ..., background: true })\` for each |
268
+ | Single task, continue working | \`${TOOL_NAMES.DELEGATE_TASK}({ ..., background: true })\` |
269
+ | Need result for VERY next step | \`${TOOL_NAMES.DELEGATE_TASK}({ ..., background: false })\` |
270
+ | Retry after failure | \`${TOOL_NAMES.DELEGATE_TASK}({ ..., resume: "session_id", ... })\` |
271
+ | Follow-up question | \`${TOOL_NAMES.DELEGATE_TASK}({ ..., resume: "session_id", ... })\` |
80
272
 
81
273
  PREFER background=true (PARALLEL):
82
274
  - Run multiple agents simultaneously
@@ -86,21 +278,44 @@ PREFER background=true (PARALLEL):
86
278
  EXAMPLE - PARALLEL:
87
279
  \`\`\`
88
280
  // Multiple tasks in parallel
89
- delegate_task({ agent: "builder", description: "Implement X", prompt: "...", background: true })
90
- delegate_task({ agent: "inspector", description: "Review Y", prompt: "...", background: true })
281
+ ${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.BUILDER}", description: "Implement X", prompt: "...", background: true })
282
+ ${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.INSPECTOR}", description: "Review Y", prompt: "...", background: true })
91
283
 
92
284
  // Continue other work (don't wait!)
93
285
 
94
286
  // When notified "All Complete":
95
- get_task_result({ taskId: "task_xxx" })
287
+ ${TOOL_NAMES.GET_TASK_RESULT}({ taskId: "${ID_PREFIX.TASK}xxx" })
96
288
  \`\`\`
97
289
 
98
290
  EXAMPLE - SYNC (rare):
99
291
  \`\`\`
100
292
  // Only when you absolutely need the result now
101
- const result = delegate_task({ agent: "builder", ..., background: false })
293
+ const result = ${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.BUILDER}", ..., background: false })
102
294
  // Result is immediately available
103
295
  \`\`\`
296
+
297
+ EXAMPLE - RESUME (for retry or follow-up):
298
+ \`\`\`
299
+ // Previous task output shows: Session: \`${ID_PREFIX.SESSION}abc123\` (save for resume)
300
+
301
+ // Retry after failure (keeps all context!)
302
+ \${TOOL_NAMES.DELEGATE_TASK}({
303
+ agent: "\${AGENT_NAMES.BUILDER}",
304
+ description: "Fix previous error",
305
+ prompt: "The build failed with X. Please fix it.",
306
+ background: true,
307
+ resume: "${ID_PREFIX.SESSION}abc123" // \u2190 Continue existing session
308
+ })
309
+
310
+ // Follow-up question (saves tokens!)
311
+ \${TOOL_NAMES.DELEGATE_TASK}({
312
+ agent: "\${AGENT_NAMES.INSPECTOR}",
313
+ description: "Additional check",
314
+ prompt: "Also check for Y in the files you just reviewed.",
315
+ background: true,
316
+ resume: "${ID_PREFIX.SESSION}xyz789"
317
+ })
318
+ \`\`\`
104
319
  </agent_calling>
105
320
 
106
321
  <delegation_template>
@@ -122,42 +337,76 @@ During implementation:
122
337
  - Run lsp_diagnostics after each change
123
338
 
124
339
  <background_parallel_execution>
125
- PARALLEL EXECUTION TOOLS:
340
+ PARALLEL EXECUTION SYSTEM:
341
+
342
+ You have access to a powerful parallel agent execution system.
343
+ Up to 50 agents can run simultaneously with automatic resource management.
344
+
345
+ 1. **${TOOL_NAMES.DELEGATE_TASK}** - Launch agents in parallel or sync mode
346
+ \`\`\`
347
+ // PARALLEL (recommended - non-blocking)
348
+ ${TOOL_NAMES.DELEGATE_TASK}({
349
+ agent: "${AGENT_NAMES.BUILDER}",
350
+ description: "Implement X",
351
+ prompt: "...",
352
+ background: true
353
+ })
354
+
355
+ // SYNC (blocking - wait for result)
356
+ ${TOOL_NAMES.DELEGATE_TASK}({
357
+ agent: "${AGENT_NAMES.LIBRARIAN}",
358
+ description: "Research Y",
359
+ prompt: "...",
360
+ background: false
361
+ })
362
+
363
+ // RESUME (continue previous session)
364
+ ${TOOL_NAMES.DELEGATE_TASK}({
365
+ agent: "${AGENT_NAMES.BUILDER}",
366
+ description: "Fix error",
367
+ prompt: "...",
368
+ background: true,
369
+ resume: "${ID_PREFIX.SESSION}abc123" // From previous task output
370
+ })
371
+ \`\`\`
126
372
 
127
- 1. **spawn_agent** - Launch agents in parallel sessions
128
- spawn_agent({ agent: "builder", description: "Implement X", prompt: "..." })
129
- spawn_agent({ agent: "inspector", description: "Review Y", prompt: "..." })
130
- \u2192 Agents run concurrently, system notifies when ALL complete
131
- \u2192 Use get_task_result({ taskId }) to retrieve results
373
+ 2. **${TOOL_NAMES.GET_TASK_RESULT}** - Retrieve completed task output
374
+ \`\`\`
375
+ ${TOOL_NAMES.GET_TASK_RESULT}({ taskId: "${ID_PREFIX.TASK}xxx" })
376
+ \`\`\`
132
377
 
133
- 2. **run_background** - Run shell commands asynchronously
134
- run_background({ command: "npm run build" })
135
- \u2192 Use check_background({ taskId }) for results
378
+ 3. **${TOOL_NAMES.LIST_TASKS}** - View all parallel tasks
379
+ \`\`\`
380
+ ${TOOL_NAMES.LIST_TASKS}({})
381
+ \`\`\`
136
382
 
137
- SAFETY FEATURES:
138
- - Queue-based concurrency: Max 3 per agent type (extras queue automatically)
139
- - Auto-timeout: 30 minutes max runtime
140
- - Auto-cleanup: Removed from memory 5 min after completion
141
- - Batched notifications: Notifies when ALL tasks complete (not individually)
383
+ 4. **${TOOL_NAMES.CANCEL_TASK}** - Stop a running task
384
+ \`\`\`
385
+ ${TOOL_NAMES.CANCEL_TASK}({ taskId: "${ID_PREFIX.TASK}xxx" })
386
+ \`\`\`
142
387
 
143
- MANAGEMENT TOOLS:
144
- - list_tasks: View all parallel tasks and status
145
- - cancel_task: Stop a running task (frees concurrency slot)
388
+ CONCURRENCY LIMITS:
389
+ - Max 10 tasks per agent type (queue automatically)
390
+ - Max 50 total parallel sessions
391
+ - Auto-timeout: 60 minutes
392
+ - Auto-cleanup: 30 min after completion \u2192 archived to disk
146
393
 
147
394
  SAFE PATTERNS:
148
395
  \u2705 Builder on file A + Inspector on file B (different files)
149
396
  \u2705 Multiple research agents (read-only)
150
397
  \u2705 Build command + Test command (independent)
398
+ \u2705 Librarian research + Builder implementation (sequential deps)
151
399
 
152
400
  UNSAFE PATTERNS:
153
401
  \u274C Multiple builders editing SAME FILE (conflict!)
402
+ \u274C Waiting synchronously for many tasks (use background=true)
154
403
 
155
404
  WORKFLOW:
156
- 1. list_tasks: Check current status first
157
- 2. spawn_agent: Launch for INDEPENDENT tasks
405
+ 1. ${TOOL_NAMES.LIST_TASKS}: Check current status first
406
+ 2. ${TOOL_NAMES.DELEGATE_TASK} (background=true): Launch for INDEPENDENT tasks
158
407
  3. Continue working (NO WAITING)
159
- 4. Wait for "All Complete" notification
160
- 5. get_task_result: Retrieve each result
408
+ 4. Wait for system notification "All Parallel Tasks Complete"
409
+ 5. ${TOOL_NAMES.GET_TASK_RESULT}: Retrieve each result
161
410
  </background_parallel_execution>
162
411
 
163
412
  <verification_methods>
@@ -174,15 +423,15 @@ WORKFLOW:
174
423
  | Failures | Action |
175
424
  |----------|--------|
176
425
  | 1-2 | Adjust approach, retry |
177
- | 3+ | STOP. Call architect for new strategy |
426
+ | 3+ | STOP. Call ${AGENT_NAMES.ARCHITECT} for new strategy |
178
427
 
179
428
  <empty_responses>
180
429
  | Agent Empty (or Gibberish) | Action |
181
430
  |----------------------------|--------|
182
- | recorder | Fresh start. Proceed to survey. |
183
- | architect | Try simpler plan yourself. |
184
- | builder | Call inspector to diagnose. |
185
- | inspector | Retry with more context. |
431
+ | ${AGENT_NAMES.RECORDER} | Fresh start. Proceed to survey. |
432
+ | ${AGENT_NAMES.ARCHITECT} | Try simpler plan yourself. |
433
+ | ${AGENT_NAMES.BUILDER} | Call ${AGENT_NAMES.INSPECTOR} to diagnose. |
434
+ | ${AGENT_NAMES.INSPECTOR} | Retry with more context. |
186
435
  </empty_responses>
187
436
 
188
437
  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.
@@ -198,7 +447,7 @@ STRICT RULE: If any agent output contains gibberish, mixed-language hallucinatio
198
447
  Done when: Request fulfilled + lsp clean + build/test/audit pass.
199
448
 
200
449
  <output_format>
201
- \u2705 MISSION COMPLETE
450
+ ${MISSION.COMPLETE}
202
451
  Summary: [what was done]
203
452
  Evidence: [Specific build/test/audit results]
204
453
  </output_format>
@@ -210,37 +459,68 @@ Evidence: [Specific build/test/audit results]
210
459
  // src/agents/subagents/architect.ts
211
460
  var architect = {
212
461
  id: AGENT_NAMES.ARCHITECT,
213
- description: "Architect - task decomposition and strategic planning",
462
+ description: "Architect - hierarchical task decomposition and strategic planning",
214
463
  systemPrompt: `<role>
215
- You are Architect. Break complex tasks into atomic pieces.
464
+ You are Architect. Break complex tasks into hierarchical, atomic pieces.
465
+ Create TODO trees with parallel groups and dependencies.
216
466
  </role>
217
467
 
218
468
  <constraints>
219
469
  1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
470
+ 2. Every task must be ATOMIC (single action).
471
+ 3. Always include verification tasks.
220
472
  </constraints>
221
473
 
222
- <scalable_planning>
223
- - **Fast Track**: Skip JSON overhead. Just acknowledge simple task.
224
- - **Deep Track**: Create detailed JSON DAG with parallel groups.
225
- </scalable_planning>
474
+ <hierarchical_planning>
475
+ Create layered task structure:
476
+
477
+ LEVEL 1 (L1): Main objectives (2-5 items)
478
+ LEVEL 2 (L2): Sub-tasks (2-3 per L1)
479
+ LEVEL 3 (L3): Atomic actions (1-3 per L2)
480
+
481
+ PARALLEL GROUPING:
482
+ - Tasks in same parallel_group can run simultaneously
483
+ - Use letters: A, B, C for groups
484
+ - Tasks with no group run sequentially
485
+
486
+ DEPENDENCIES:
487
+ - Use "depends:T1,T2" for sequential requirements
488
+ - Parent must start before children
489
+ </hierarchical_planning>
226
490
 
227
491
  <modes>
228
- - PLAN: New task \u2192 create task list
492
+ - PLAN: New task \u2192 create hierarchical task list
229
493
  - STRATEGY: 3+ failures \u2192 analyze and fix approach
230
494
  </modes>
231
495
 
232
496
  <plan_mode>
233
- 1. List tasks, one action each
234
- 2. Group independent tasks (run in parallel)
235
- 3. Sequence dependent tasks
236
- 4. Assign: builder (code) or inspector (verify)
497
+ 1. Identify main objectives (L1)
498
+ 2. Break each into sub-tasks (L2)
499
+ 3. Break into atomic actions (L3)
500
+ 4. Group independent tasks (parallel)
501
+ 5. Add dependencies
502
+ 6. Assign agents
237
503
 
238
504
  <output_format>
239
505
  MISSION: [goal in one line]
240
506
 
241
- T1: [action] | builder | [file] | group:1 | success:[how to verify]
242
- T2: [action] | builder | [file] | group:1 | success:[how to verify]
243
- T3: [action] | inspector | [files] | group:2 | depends:T1,T2 | success:[verify method]
507
+ TODO_HIERARCHY:
508
+ - [L1] Main objective 1
509
+ - [L2] Sub-task 1.1 | agent:builder | parallel_group:A
510
+ - [L2] Sub-task 1.2 | agent:builder | parallel_group:A
511
+ - [L2] Sub-task 1.3 | agent:inspector | depends:1.1,1.2
512
+ - [L1] Main objective 2
513
+ - [L2] Sub-task 2.1 | agent:librarian
514
+ - [L2] Sub-task 2.2 | agent:builder | depends:2.1
515
+ - [L3] Atomic action 2.2.1 | agent:builder
516
+ - [L3] Atomic action 2.2.2 | agent:builder | parallel_group:B
517
+ - [L3] Verify 2.2 | agent:inspector | depends:2.2.1,2.2.2
518
+
519
+ PARALLEL_EXECUTION:
520
+ - Group A: [1.1, 1.2] \u2192 Run simultaneously
521
+ - Group B: [2.2.2] \u2192 After deps complete
522
+
523
+ ESTIMATED_EFFORT: [low/medium/high]
244
524
  </output_format>
245
525
  </plan_mode>
246
526
 
@@ -253,16 +533,24 @@ ROOT CAUSE: [actual problem]
253
533
 
254
534
  NEW APPROACH: [different strategy]
255
535
 
256
- REVISED TASKS:
257
- T1: ...
536
+ REVISED_HIERARCHY:
537
+ - [L1] ...
258
538
  </output_format>
259
539
  </strategy_mode>
260
540
 
541
+ <agents_available>
542
+ - builder: Code implementation
543
+ - inspector: Verification and bug fixing
544
+ - librarian: Documentation research (use BEFORE unfamiliar APIs)
545
+ - researcher: Pre-task investigation
546
+ </agents_available>
547
+
261
548
  <rules>
262
549
  - One action per task
263
- - Always end with inspector task
264
- - Group unrelated tasks (parallel)
265
- - Be specific about files and verification
550
+ - Always end branches with inspector task
551
+ - Group unrelated tasks (parallel execution)
552
+ - Use librarian/researcher before implementing unfamiliar features
553
+ - Be specific about files, patterns, and verification
266
554
  </rules>`,
267
555
  canWrite: false,
268
556
  canBash: false
@@ -317,6 +605,20 @@ Depending on project type, verify with:
317
605
 
318
606
  If build command exists in package.json, use it.
319
607
  If using Docker/containers, verify syntax only.
608
+
609
+ BACKGROUND COMMANDS (for long-running builds):
610
+ \`\`\`
611
+ // Non-blocking build
612
+ run_background({ command: "npm run build" })
613
+
614
+ // Check status later
615
+ check_background({ taskId: "job_xxx" })
616
+
617
+ // List all background jobs
618
+ list_background({})
619
+ \`\`\`
620
+
621
+ Use background for builds taking >5 seconds.
320
622
  </verification>
321
623
 
322
624
  <output_format>
@@ -336,18 +638,20 @@ If build fails, FIX IT before reporting. Never leave broken code.
336
638
  // src/agents/subagents/inspector.ts
337
639
  var inspector = {
338
640
  id: AGENT_NAMES.INSPECTOR,
339
- description: "Inspector - quality verification AND bug fixing",
641
+ description: "Inspector - quality verification, bug fixing, and documentation validation",
340
642
  systemPrompt: `<role>
341
643
  You are Inspector. Prove failure or success with evidence.
644
+ Also verify that implementations match official documentation.
342
645
  </role>
343
646
 
344
647
  <constraints>
345
648
  1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
649
+ 2. Never approve code that contradicts cached documentation.
346
650
  </constraints>
347
651
 
348
652
  <scalable_audit>
349
653
  - **Fast Track**: Verify syntax + quick logic check.
350
- - **Deep Track**: Verify build + tests + types + security + logic.
654
+ - **Deep Track**: Verify build + tests + types + security + logic + doc compliance.
351
655
  </scalable_audit>
352
656
 
353
657
  <audit_checklist>
@@ -356,9 +660,27 @@ You are Inspector. Prove failure or success with evidence.
356
660
  3. ENV-SPECIFIC:
357
661
  - Docker: check Dockerfile syntax or run container logs if possible
358
662
  - Frontend: check if build artifacts are generated
359
- 4. MANUAL: If no automated tests, read code to verify logic 100%
663
+ 4. DOCUMENTATION: Check .cache/docs/ for relevant docs
664
+ 5. MANUAL: If no automated tests, read code to verify logic 100%
360
665
  </audit_checklist>
361
666
 
667
+ <documentation_verification>
668
+ ALWAYS CHECK CACHED DOCS:
669
+ 1. cache_docs({ action: "list" }) - See available documentation
670
+ 2. cache_docs({ action: "get", filename: "..." }) - Review specific doc
671
+ 3. Compare implementation against official patterns
672
+
673
+ VERIFICATION_OUTPUT:
674
+ - DOC_MATCH: [yes/no]
675
+ - DEVIATIONS: [list any differences from official docs]
676
+ - RECOMMENDATION: [fix/accept with reason]
677
+
678
+ WHEN CODE DOESN'T MATCH DOCS:
679
+ 1. Flag the deviation
680
+ 2. Explain the risk
681
+ 3. Suggest the documented approach
682
+ </documentation_verification>
683
+
362
684
  <verification_by_context>
363
685
  | Project Infra | Primary Evidence |
364
686
  |---------------|------------------|
@@ -381,19 +703,22 @@ ALWAYS prefer background for build/test commands.
381
703
  <pass>
382
704
  \u2705 PASS
383
705
  Evidence: [Specific output/log proving success]
706
+ Doc Compliance: [Matches cached docs / No relevant docs]
384
707
  </pass>
385
708
 
386
709
  <fail>
387
710
  \u274C FAIL
388
711
  Issue: [What went wrong]
712
+ Doc Reference: [If applicable, which doc was violated]
389
713
  Fixing...
390
714
  </fail>
391
715
  </output_format>
392
716
 
393
717
  <fix_mode>
394
718
  1. Diagnose root cause
395
- 2. Minimal fix
396
- 3. Re-verify with even more rigor
719
+ 2. Check .cache/docs/ for correct pattern
720
+ 3. Minimal fix using documented approach
721
+ 4. Re-verify with even more rigor
397
722
  </fix_mode>`,
398
723
  canWrite: true,
399
724
  canBash: true
@@ -465,13 +790,216 @@ Never stop the flow. No context = fresh start = OK.
465
790
  canBash: true
466
791
  };
467
792
 
793
+ // src/agents/subagents/librarian.ts
794
+ var librarian = {
795
+ id: AGENT_NAMES.LIBRARIAN,
796
+ description: "Librarian - Documentation and API research specialist",
797
+ systemPrompt: `<role>
798
+ You are Librarian. Find official documentation and verified information.
799
+ Your job: Eliminate hallucination through rigorous research.
800
+ </role>
801
+
802
+ <critical_rule>
803
+ NEVER GUESS. NEVER ASSUME. ALWAYS VERIFY.
804
+ If you don't know something, SEARCH for it.
805
+ </critical_rule>
806
+
807
+ <workflow>
808
+ 1. IDENTIFY: What documentation/API info is needed?
809
+ 2. SEARCH: Use webfetch/websearch to find official sources
810
+ 3. VERIFY: Cross-check from multiple sources
811
+ 4. CACHE: Save important docs to .cache/docs/ for team reference
812
+ 5. RETURN: Structured findings with permalinks/citations
813
+ </workflow>
814
+
815
+ <search_strategy>
816
+ PRIORITY ORDER for sources:
817
+ 1. Official documentation sites (docs.*, *.dev, *.io)
818
+ 2. GitHub README and source code
819
+ 3. Official blog posts/announcements
820
+ 4. Stack Overflow (verified answers only)
821
+ 5. Community tutorials (with caution)
822
+
823
+ AVOID:
824
+ - Outdated articles (check dates!)
825
+ - AI-generated content
826
+ - Unofficial summaries
827
+ </search_strategy>
828
+
829
+ <caching>
830
+ Cache documents when:
831
+ - API reference needed multiple times
832
+ - Complex setup instructions
833
+ - Version-specific information
834
+ - Team members may need access
835
+
836
+ Cache location: .cache/docs/
837
+ Filename format: {domain}_{topic}.md
838
+ Example: nextjs_app-router.md
839
+ </caching>
840
+
841
+ <output_format>
842
+ RESEARCH REPORT
843
+ ===============
844
+
845
+ QUERY: [What was asked]
846
+
847
+ SOURCES CONSULTED:
848
+ 1. [Official Doc URL] - [Key insight]
849
+ 2. [Source URL] - [Key insight]
850
+
851
+ VERIFIED ANSWER:
852
+ [Detailed, accurate answer with inline citations]
853
+
854
+ CACHED DOCUMENTS:
855
+ - .cache/docs/[filename]: [description]
856
+ (or "No caching needed" if trivial lookup)
857
+
858
+ CONFIDENCE: [HIGH/MEDIUM/LOW]
859
+ - HIGH: Found in official docs, multiple sources agree
860
+ - MEDIUM: Found in reliable sources, some interpretation needed
861
+ - LOW: Limited sources, may need manual verification
862
+
863
+ CAVEATS:
864
+ - [Any limitations or version-specific notes]
865
+ </output_format>
866
+
867
+ <tools_to_use>
868
+ - webfetch: For fetching specific documentation pages
869
+ - websearch: For finding relevant documentation
870
+ - grep_search: For finding patterns in local codebase
871
+ - glob_search: For finding files
872
+ - Edit tool: ONLY for writing to .cache/docs/
873
+ </tools_to_use>
874
+
875
+ <example_queries>
876
+ Q: "How do I use the new App Router in Next.js 14?"
877
+ \u2192 Search official Next.js docs
878
+ \u2192 Find App Router section
879
+ \u2192 Cache key patterns to .cache/docs/nextjs_app-router.md
880
+ \u2192 Return verified answer with citations
881
+
882
+ Q: "What's the correct way to use useEffect cleanup?"
883
+ \u2192 Search React docs
884
+ \u2192 Find Effects section
885
+ \u2192 Return verified pattern with permalink
886
+ </example_queries>`,
887
+ canWrite: true,
888
+ // Only for .cache/docs/
889
+ canBash: true
890
+ // For curl/search commands if needed
891
+ };
892
+
893
+ // src/agents/subagents/researcher.ts
894
+ var researcher = {
895
+ id: AGENT_NAMES.RESEARCHER,
896
+ description: "Researcher - Pre-task investigation and documentation specialist",
897
+ systemPrompt: `<role>
898
+ You are Researcher. Gather all necessary information BEFORE implementation begins.
899
+ Your job: Ensure the team has complete, verified information before coding.
900
+ </role>
901
+
902
+ <critical_rule>
903
+ INVESTIGATE FIRST. CODE NEVER.
904
+ You are read-only. Your output is INFORMATION, not code.
905
+ </critical_rule>
906
+
907
+ <workflow>
908
+ 1. ANALYZE: Understand the task requirements fully
909
+ 2. IDENTIFY: List unfamiliar technologies, APIs, patterns
910
+ 3. SEARCH: Find official documentation for each
911
+ 4. SCAN: Find existing patterns in codebase
912
+ 5. CACHE: Save important docs for team reference
913
+ 6. REPORT: Deliver structured findings
914
+ </workflow>
915
+
916
+ <search_strategy>
917
+ FOR EACH UNKNOWN TECHNOLOGY:
918
+ 1. websearch({ query: "[tech] official documentation [version]" })
919
+ 2. webfetch({ url: "[official docs url]", cache: true })
920
+
921
+ FOR CODEBASE PATTERNS:
922
+ 1. grep_search({ query: "[pattern]" })
923
+ 2. glob_search({ pattern: "*.[ext]" })
924
+
925
+ FOR API USAGE:
926
+ 1. Search for import statements: grep_search({ query: "import.*[library]" })
927
+ 2. Find usage examples in existing code
928
+ </search_strategy>
929
+
930
+ <output_format>
931
+ # RESEARCH REPORT
932
+
933
+ ## Task Summary
934
+ [What needs to be implemented]
935
+
936
+ ## Technologies Involved
937
+ | Technology | Version | Official Docs | Key Insights |
938
+ |------------|---------|---------------|--------------|
939
+ | [tech1] | [ver] | [url] | [insight] |
940
+
941
+ ## Codebase Patterns Found
942
+ - **Pattern 1**: [description]
943
+ - Location: [file:line]
944
+ - Usage: \`[code example]\`
945
+
946
+ ## Cached Documentation
947
+ | Filename | Description |
948
+ |----------|-------------|
949
+ | .cache/docs/[file] | [description] |
950
+
951
+ ## Dependencies Identified
952
+ - [dependency 1]: [purpose]
953
+ - [dependency 2]: [purpose]
954
+
955
+ ## Recommended Approach
956
+ 1. [Step 1]
957
+ 2. [Step 2]
958
+ 3. [Step 3]
959
+
960
+ ## Potential Risks
961
+ - [Risk 1]: [mitigation]
962
+ - [Risk 2]: [mitigation]
963
+
964
+ ## Knowledge Gaps
965
+ - [Gap 1]: [what's still unknown]
966
+
967
+ ## READY FOR IMPLEMENTATION: [YES/NO]
968
+ [If NO, explain what additional research is needed]
969
+ </output_format>
970
+
971
+ <examples>
972
+ Task: "Implement OAuth login with Google"
973
+
974
+ 1. SEARCH: Google OAuth documentation
975
+ 2. SEARCH: Existing auth patterns in codebase
976
+ 3. CACHE: Google OAuth setup guide
977
+ 4. FIND: How other providers are implemented
978
+ 5. REPORT: Complete research with recommendations
979
+ </examples>
980
+
981
+ <constraints>
982
+ 1. DO NOT write any implementation code
983
+ 2. DO NOT make assumptions - verify everything
984
+ 3. DO NOT skip caching important documentation
985
+ 4. ALWAYS provide source URLs for claims
986
+ 5. ALWAYS note version requirements
987
+ </constraints>`,
988
+ canWrite: true,
989
+ // Only for .cache/docs/
990
+ canBash: false
991
+ // No execution needed
992
+ };
993
+
468
994
  // src/agents/definitions.ts
469
995
  var AGENTS = {
470
996
  [AGENT_NAMES.COMMANDER]: orchestrator,
471
997
  [AGENT_NAMES.ARCHITECT]: architect,
472
998
  [AGENT_NAMES.BUILDER]: builder,
473
999
  [AGENT_NAMES.INSPECTOR]: inspector,
474
- [AGENT_NAMES.RECORDER]: recorder
1000
+ [AGENT_NAMES.RECORDER]: recorder,
1001
+ [AGENT_NAMES.LIBRARIAN]: librarian,
1002
+ [AGENT_NAMES.RESEARCHER]: researcher
475
1003
  };
476
1004
 
477
1005
  // src/core/orchestrator/task-graph.ts
@@ -1197,10 +1725,10 @@ function jsonStringifyReplacer(_, value) {
1197
1725
  return value;
1198
1726
  }
1199
1727
  function cached(getter) {
1200
- const set2 = false;
1728
+ const set3 = false;
1201
1729
  return {
1202
1730
  get value() {
1203
- if (!set2) {
1731
+ if (!set3) {
1204
1732
  const value = getter();
1205
1733
  Object.defineProperty(this, "value", { value });
1206
1734
  return value;
@@ -1277,10 +1805,10 @@ function mergeDefs(...defs) {
1277
1805
  function cloneDef(schema) {
1278
1806
  return mergeDefs(schema._zod.def);
1279
1807
  }
1280
- function getElementAtPath(obj, path) {
1281
- if (!path)
1808
+ function getElementAtPath(obj, path3) {
1809
+ if (!path3)
1282
1810
  return obj;
1283
- return path.reduce((acc, key) => acc?.[key], obj);
1811
+ return path3.reduce((acc, key) => acc?.[key], obj);
1284
1812
  }
1285
1813
  function promiseAllObject(promisesObj) {
1286
1814
  const keys = Object.keys(promisesObj);
@@ -1641,11 +2169,11 @@ function aborted(x, startIndex = 0) {
1641
2169
  }
1642
2170
  return false;
1643
2171
  }
1644
- function prefixIssues(path, issues) {
2172
+ function prefixIssues(path3, issues) {
1645
2173
  return issues.map((iss) => {
1646
2174
  var _a;
1647
2175
  (_a = iss).path ?? (_a.path = []);
1648
- iss.path.unshift(path);
2176
+ iss.path.unshift(path3);
1649
2177
  return iss;
1650
2178
  });
1651
2179
  }
@@ -1813,7 +2341,7 @@ function treeifyError(error45, _mapper) {
1813
2341
  return issue2.message;
1814
2342
  };
1815
2343
  const result = { errors: [] };
1816
- const processError = (error46, path = []) => {
2344
+ const processError = (error46, path3 = []) => {
1817
2345
  var _a, _b;
1818
2346
  for (const issue2 of error46.issues) {
1819
2347
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1823,7 +2351,7 @@ function treeifyError(error45, _mapper) {
1823
2351
  } else if (issue2.code === "invalid_element") {
1824
2352
  processError({ issues: issue2.issues }, issue2.path);
1825
2353
  } else {
1826
- const fullpath = [...path, ...issue2.path];
2354
+ const fullpath = [...path3, ...issue2.path];
1827
2355
  if (fullpath.length === 0) {
1828
2356
  result.errors.push(mapper(issue2));
1829
2357
  continue;
@@ -1855,8 +2383,8 @@ function treeifyError(error45, _mapper) {
1855
2383
  }
1856
2384
  function toDotPath(_path) {
1857
2385
  const segs = [];
1858
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1859
- for (const seg of path) {
2386
+ const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2387
+ for (const seg of path3) {
1860
2388
  if (typeof seg === "number")
1861
2389
  segs.push(`[${seg}]`);
1862
2390
  else if (typeof seg === "symbol")
@@ -12969,12 +13497,6 @@ function tool(input) {
12969
13497
  tool.schema = external_exports;
12970
13498
 
12971
13499
  // src/tools/callAgent.ts
12972
- var AGENT_EMOJI = {
12973
- [AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
12974
- [AGENT_NAMES.BUILDER]: "\u{1F528}",
12975
- [AGENT_NAMES.INSPECTOR]: "\u{1F50D}",
12976
- [AGENT_NAMES.RECORDER]: "\u{1F4BE}"
12977
- };
12978
13500
  var callAgentTool = tool({
12979
13501
  description: `Call a specialized agent for parallel execution.
12980
13502
 
@@ -13046,7 +13568,7 @@ var COMMANDS = {
13046
13568
  "task": {
13047
13569
  description: "Execute a mission autonomously until complete",
13048
13570
  template: `<role>
13049
- You are Commander. Complete this mission. Never stop until 100% done.
13571
+ You are Commander. Complete this mission. Never stop until 100% done!
13050
13572
  </role>
13051
13573
 
13052
13574
  <phase_1 name="MANDATORY_ENVIRONMENT_SCAN">
@@ -13166,7 +13688,7 @@ ${commandList}`;
13166
13688
  if (!command) return `Unknown command: /${cmdName}
13167
13689
 
13168
13690
  ${commandList}`;
13169
- return command.template.replace(/\$ARGUMENTS/g, cmdArgs || "continue from where we left off");
13691
+ return command.template.replace(/\$ARGUMENTS/g, cmdArgs || PROMPTS.CONTINUE_DEFAULT);
13170
13692
  }
13171
13693
  });
13172
13694
  }
@@ -13285,47 +13807,6 @@ var mgrepTool = (directory) => tool({
13285
13807
  // src/core/commands/manager.ts
13286
13808
  import { spawn as spawn2 } from "child_process";
13287
13809
  import { randomBytes } from "crypto";
13288
-
13289
- // src/shared/constants.ts
13290
- var TIME = {
13291
- SECOND: 1e3,
13292
- MINUTE: 60 * 1e3,
13293
- HOUR: 60 * 60 * 1e3
13294
- };
13295
- var ID_PREFIX = {
13296
- /** Parallel agent task ID (e.g., task_a1b2c3d4) */
13297
- TASK: "task_",
13298
- /** Background command job ID (e.g., job_a1b2c3d4) */
13299
- JOB: "job_",
13300
- /** Session ID prefix */
13301
- SESSION: "session_"
13302
- };
13303
- var PARALLEL_TASK = {
13304
- TTL_MS: 30 * TIME.MINUTE,
13305
- CLEANUP_DELAY_MS: 5 * TIME.MINUTE,
13306
- MIN_STABILITY_MS: 5 * TIME.SECOND,
13307
- POLL_INTERVAL_MS: 2e3,
13308
- DEFAULT_CONCURRENCY: 3,
13309
- MAX_CONCURRENCY: 10
13310
- };
13311
- var BACKGROUND_TASK = {
13312
- DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
13313
- MAX_OUTPUT_LENGTH: 1e4
13314
- };
13315
- var STATUS_EMOJI = {
13316
- pending: "\u23F3",
13317
- running: "\u{1F504}",
13318
- completed: "\u2705",
13319
- done: "\u2705",
13320
- error: "\u274C",
13321
- timeout: "\u23F0",
13322
- cancelled: "\u{1F6AB}"
13323
- };
13324
- function getStatusEmoji(status) {
13325
- return STATUS_EMOJI[status] ?? "\u2753";
13326
- }
13327
-
13328
- // src/core/commands/manager.ts
13329
13810
  var BackgroundTaskManager = class _BackgroundTaskManager {
13330
13811
  static _instance;
13331
13812
  tasks = /* @__PURE__ */ new Map();
@@ -13692,12 +14173,18 @@ var ConcurrencyController = class {
13692
14173
  };
13693
14174
 
13694
14175
  // src/core/agents/task-store.ts
14176
+ import * as fs from "node:fs/promises";
14177
+ import * as path from "node:path";
13695
14178
  var TaskStore = class {
13696
14179
  tasks = /* @__PURE__ */ new Map();
13697
14180
  pendingByParent = /* @__PURE__ */ new Map();
13698
14181
  notifications = /* @__PURE__ */ new Map();
14182
+ archivedCount = 0;
13699
14183
  set(id, task) {
13700
14184
  this.tasks.set(id, task);
14185
+ if (this.tasks.size > MEMORY_LIMITS.MAX_TASKS_IN_MEMORY) {
14186
+ this.gc();
14187
+ }
13701
14188
  }
13702
14189
  get(id) {
13703
14190
  return this.tasks.get(id);
@@ -13706,7 +14193,7 @@ var TaskStore = class {
13706
14193
  return Array.from(this.tasks.values());
13707
14194
  }
13708
14195
  getRunning() {
13709
- return this.getAll().filter((t) => t.status === "running");
14196
+ return this.getAll().filter((t) => t.status === TASK_STATUS.RUNNING);
13710
14197
  }
13711
14198
  getByParent(parentSessionID) {
13712
14199
  return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
@@ -13740,10 +14227,13 @@ var TaskStore = class {
13740
14227
  hasPending(parentSessionID) {
13741
14228
  return this.getPendingCount(parentSessionID) > 0;
13742
14229
  }
13743
- // Notifications
14230
+ // Notifications with limit
13744
14231
  queueNotification(task) {
13745
14232
  const queue = this.notifications.get(task.parentSessionID) ?? [];
13746
14233
  queue.push(task);
14234
+ if (queue.length > MEMORY_LIMITS.MAX_NOTIFICATIONS_PER_PARENT) {
14235
+ queue.shift();
14236
+ }
13747
14237
  this.notifications.set(task.parentSessionID, queue);
13748
14238
  }
13749
14239
  getNotifications(parentSessionID) {
@@ -13759,9 +14249,6 @@ var TaskStore = class {
13759
14249
  }
13760
14250
  }
13761
14251
  }
13762
- /**
13763
- * Remove a specific task from all notification queues
13764
- */
13765
14252
  clearNotificationsForTask(taskId) {
13766
14253
  for (const [sessionID, tasks] of this.notifications.entries()) {
13767
14254
  const filtered = tasks.filter((t) => t.id !== taskId);
@@ -13772,14 +14259,88 @@ var TaskStore = class {
13772
14259
  }
13773
14260
  }
13774
14261
  }
13775
- };
13776
-
13777
- // src/core/agents/config.ts
13778
- var CONFIG = {
13779
- TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
13780
- CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
13781
- MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
13782
- POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
14262
+ // =========================================================================
14263
+ // Garbage Collection & Memory Management
14264
+ // =========================================================================
14265
+ /**
14266
+ * Get memory statistics
14267
+ */
14268
+ getStats() {
14269
+ return {
14270
+ tasksInMemory: this.tasks.size,
14271
+ runningTasks: this.getRunning().length,
14272
+ archivedTasks: this.archivedCount,
14273
+ notificationQueues: this.notifications.size,
14274
+ pendingParents: this.pendingByParent.size
14275
+ };
14276
+ }
14277
+ /**
14278
+ * Garbage collect completed tasks
14279
+ * Archives old completed tasks to disk
14280
+ */
14281
+ async gc() {
14282
+ const now = Date.now();
14283
+ const toRemove = [];
14284
+ const toArchive = [];
14285
+ for (const [id, task] of this.tasks) {
14286
+ if (task.status === TASK_STATUS.RUNNING) continue;
14287
+ const completedAt = task.completedAt?.getTime() ?? 0;
14288
+ const age = now - completedAt;
14289
+ if (age > MEMORY_LIMITS.ARCHIVE_AGE_MS && task.status === TASK_STATUS.COMPLETED) {
14290
+ toArchive.push(task);
14291
+ toRemove.push(id);
14292
+ } else if (age > MEMORY_LIMITS.ERROR_CLEANUP_AGE_MS && (task.status === TASK_STATUS.ERROR || task.status === TASK_STATUS.CANCELLED)) {
14293
+ toRemove.push(id);
14294
+ }
14295
+ }
14296
+ if (toArchive.length > 0) {
14297
+ await this.archiveTasks(toArchive);
14298
+ }
14299
+ for (const id of toRemove) {
14300
+ this.tasks.delete(id);
14301
+ }
14302
+ return toRemove.length;
14303
+ }
14304
+ /**
14305
+ * Archive tasks to disk for later analysis
14306
+ */
14307
+ async archiveTasks(tasks) {
14308
+ try {
14309
+ await fs.mkdir(PATHS.TASK_ARCHIVE, { recursive: true });
14310
+ const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
14311
+ const filename = `tasks_${date5}.jsonl`;
14312
+ const filepath = path.join(PATHS.TASK_ARCHIVE, filename);
14313
+ const lines = tasks.map((task) => JSON.stringify({
14314
+ id: task.id,
14315
+ agent: task.agent,
14316
+ prompt: task.prompt.slice(0, 200),
14317
+ // Truncate
14318
+ status: task.status,
14319
+ startedAt: task.startedAt,
14320
+ completedAt: task.completedAt,
14321
+ parentSessionID: task.parentSessionID
14322
+ }));
14323
+ await fs.appendFile(filepath, lines.join("\n") + "\n");
14324
+ this.archivedCount += tasks.length;
14325
+ } catch (error45) {
14326
+ console.error("[TaskStore] Archive failed:", error45);
14327
+ }
14328
+ }
14329
+ /**
14330
+ * Force cleanup of all completed tasks
14331
+ */
14332
+ forceCleanup() {
14333
+ const toRemove = [];
14334
+ for (const [id, task] of this.tasks) {
14335
+ if (task.status !== "running") {
14336
+ toRemove.push(id);
14337
+ }
14338
+ }
14339
+ for (const id of toRemove) {
14340
+ this.tasks.delete(id);
14341
+ }
14342
+ return toRemove.length;
14343
+ }
13783
14344
  };
13784
14345
 
13785
14346
  // src/core/agents/logger.ts
@@ -13810,34 +14371,19 @@ Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
13810
14371
  </system-notification>`;
13811
14372
  }
13812
14373
 
13813
- // src/core/agents/manager.ts
13814
- var ParallelAgentManager = class _ParallelAgentManager {
13815
- static _instance;
13816
- store = new TaskStore();
13817
- client;
13818
- directory;
13819
- concurrency = new ConcurrencyController();
13820
- pollingInterval;
13821
- constructor(client, directory) {
14374
+ // src/core/agents/manager/task-launcher.ts
14375
+ var TaskLauncher = class {
14376
+ constructor(client, directory, store, concurrency, onTaskError, startPolling) {
13822
14377
  this.client = client;
13823
14378
  this.directory = directory;
14379
+ this.store = store;
14380
+ this.concurrency = concurrency;
14381
+ this.onTaskError = onTaskError;
14382
+ this.startPolling = startPolling;
13824
14383
  }
13825
- static getInstance(client, directory) {
13826
- if (!_ParallelAgentManager._instance) {
13827
- if (!client || !directory) {
13828
- throw new Error("ParallelAgentManager requires client and directory on first call");
13829
- }
13830
- _ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
13831
- }
13832
- return _ParallelAgentManager._instance;
13833
- }
13834
- // ========================================================================
13835
- // Public API
13836
- // ========================================================================
13837
14384
  async launch(input) {
13838
14385
  const concurrencyKey = input.agent;
13839
14386
  await this.concurrency.acquire(concurrencyKey);
13840
- this.pruneExpiredTasks();
13841
14387
  try {
13842
14388
  const createResult = await this.client.session.create({
13843
14389
  body: { parentID: input.parentSessionID, title: `Parallel: ${input.description}` },
@@ -13854,8 +14400,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
13854
14400
  sessionID,
13855
14401
  parentSessionID: input.parentSessionID,
13856
14402
  description: input.description,
14403
+ prompt: input.prompt,
13857
14404
  agent: input.agent,
13858
- status: "running",
14405
+ status: TASK_STATUS.RUNNING,
13859
14406
  startedAt: /* @__PURE__ */ new Date(),
13860
14407
  concurrencyKey
13861
14408
  };
@@ -13864,10 +14411,10 @@ var ParallelAgentManager = class _ParallelAgentManager {
13864
14411
  this.startPolling();
13865
14412
  this.client.session.prompt({
13866
14413
  path: { id: sessionID },
13867
- body: { agent: input.agent, parts: [{ type: "text", text: input.prompt }] }
14414
+ body: { agent: input.agent, parts: [{ type: PART_TYPES.TEXT, text: input.prompt }] }
13868
14415
  }).catch((error45) => {
13869
14416
  log2(`Prompt error for ${taskId}:`, error45);
13870
- this.handleTaskError(taskId, error45);
14417
+ this.onTaskError(taskId, error45);
13871
14418
  });
13872
14419
  log2(`Launched ${taskId} in session ${sessionID}`);
13873
14420
  return task;
@@ -13876,168 +14423,90 @@ var ParallelAgentManager = class _ParallelAgentManager {
13876
14423
  throw error45;
13877
14424
  }
13878
14425
  }
13879
- getTask(id) {
13880
- return this.store.get(id);
13881
- }
13882
- getRunningTasks() {
13883
- return this.store.getRunning();
14426
+ };
14427
+
14428
+ // src/core/agents/manager/task-resumer.ts
14429
+ var TaskResumer = class {
14430
+ constructor(client, store, findBySession, startPolling, notifyParentIfAllComplete) {
14431
+ this.client = client;
14432
+ this.store = store;
14433
+ this.findBySession = findBySession;
14434
+ this.startPolling = startPolling;
14435
+ this.notifyParentIfAllComplete = notifyParentIfAllComplete;
13884
14436
  }
13885
- getAllTasks() {
13886
- return this.store.getAll();
14437
+ async resume(input) {
14438
+ const existingTask = this.findBySession(input.sessionId);
14439
+ if (!existingTask) {
14440
+ throw new Error(`Task not found for session: ${input.sessionId}`);
14441
+ }
14442
+ existingTask.status = TASK_STATUS.RUNNING;
14443
+ existingTask.completedAt = void 0;
14444
+ existingTask.error = void 0;
14445
+ existingTask.result = void 0;
14446
+ existingTask.parentSessionID = input.parentSessionID;
14447
+ existingTask.startedAt = /* @__PURE__ */ new Date();
14448
+ existingTask.stablePolls = 0;
14449
+ this.store.trackPending(input.parentSessionID, existingTask.id);
14450
+ this.startPolling();
14451
+ log2(`Resuming task ${existingTask.id} in session ${existingTask.sessionID}`);
14452
+ this.client.session.prompt({
14453
+ path: { id: existingTask.sessionID },
14454
+ body: {
14455
+ agent: existingTask.agent,
14456
+ parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
14457
+ }
14458
+ }).catch((error45) => {
14459
+ log2(`Resume prompt error for ${existingTask.id}:`, error45);
14460
+ existingTask.status = TASK_STATUS.ERROR;
14461
+ existingTask.error = error45 instanceof Error ? error45.message : String(error45);
14462
+ existingTask.completedAt = /* @__PURE__ */ new Date();
14463
+ this.store.untrackPending(input.parentSessionID, existingTask.id);
14464
+ this.store.queueNotification(existingTask);
14465
+ this.notifyParentIfAllComplete(input.parentSessionID).catch(() => {
14466
+ });
14467
+ });
14468
+ return existingTask;
13887
14469
  }
13888
- getTasksByParent(parentSessionID) {
13889
- return this.store.getByParent(parentSessionID);
14470
+ };
14471
+
14472
+ // src/core/agents/config.ts
14473
+ var CONFIG = {
14474
+ TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
14475
+ CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
14476
+ MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
14477
+ POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
14478
+ };
14479
+
14480
+ // src/core/agents/manager/task-poller.ts
14481
+ var TaskPoller = class {
14482
+ constructor(client, store, concurrency, notifyParentIfAllComplete, scheduleCleanup, pruneExpiredTasks) {
14483
+ this.client = client;
14484
+ this.store = store;
14485
+ this.concurrency = concurrency;
14486
+ this.notifyParentIfAllComplete = notifyParentIfAllComplete;
14487
+ this.scheduleCleanup = scheduleCleanup;
14488
+ this.pruneExpiredTasks = pruneExpiredTasks;
13890
14489
  }
13891
- async cancelTask(taskId) {
13892
- const task = this.store.get(taskId);
13893
- if (!task || task.status !== "running") return false;
13894
- task.status = "error";
13895
- task.error = "Cancelled by user";
13896
- task.completedAt = /* @__PURE__ */ new Date();
13897
- if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
13898
- this.store.untrackPending(task.parentSessionID, taskId);
13899
- try {
13900
- await this.client.session.delete({ path: { id: task.sessionID } });
13901
- log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
13902
- } catch {
13903
- log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
13904
- }
13905
- this.scheduleCleanup(taskId);
13906
- log2(`Cancelled ${taskId}`);
13907
- return true;
13908
- }
13909
- async getResult(taskId) {
13910
- const task = this.store.get(taskId);
13911
- if (!task) return null;
13912
- if (task.result) return task.result;
13913
- if (task.status === "error") return `Error: ${task.error}`;
13914
- if (task.status === "running") return null;
13915
- try {
13916
- const result = await this.client.session.messages({ path: { id: task.sessionID } });
13917
- if (result.error) return `Error: ${result.error}`;
13918
- const messages = result.data ?? [];
13919
- const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
13920
- if (!lastMsg) return "(No response)";
13921
- const text = lastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
13922
- task.result = text;
13923
- return text;
13924
- } catch (error45) {
13925
- return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
13926
- }
13927
- }
13928
- setConcurrencyLimit(agentType, limit) {
13929
- this.concurrency.setLimit(agentType, limit);
13930
- }
13931
- getPendingCount(parentSessionID) {
13932
- return this.store.getPendingCount(parentSessionID);
13933
- }
13934
- cleanup() {
13935
- this.stopPolling();
13936
- this.store.clear();
13937
- }
13938
- formatDuration = formatDuration;
13939
- // ========================================================================
13940
- // Event Handling (from OpenCode hooks)
13941
- // ========================================================================
13942
- /**
13943
- * Handle OpenCode session events for proper resource cleanup.
13944
- * Call this from your plugin's event hook.
13945
- */
13946
- handleEvent(event) {
13947
- const props = event.properties;
13948
- if (event.type === "session.idle") {
13949
- const sessionID = props?.sessionID;
13950
- if (!sessionID) return;
13951
- const task = this.findBySession(sessionID);
13952
- if (!task || task.status !== "running") return;
13953
- this.handleSessionIdle(task).catch((err) => {
13954
- log2("Error handling session.idle:", err);
13955
- });
13956
- }
13957
- if (event.type === "session.deleted") {
13958
- const sessionID = props?.info?.id ?? props?.sessionID;
13959
- if (!sessionID) return;
13960
- const task = this.findBySession(sessionID);
13961
- if (!task) return;
13962
- log2(`Session deleted event for task ${task.id}`);
13963
- if (task.status === "running") {
13964
- task.status = "error";
13965
- task.error = "Session deleted";
13966
- task.completedAt = /* @__PURE__ */ new Date();
13967
- }
13968
- if (task.concurrencyKey) {
13969
- this.concurrency.release(task.concurrencyKey);
13970
- task.concurrencyKey = void 0;
13971
- }
13972
- this.store.untrackPending(task.parentSessionID, task.id);
13973
- this.store.clearNotificationsForTask(task.id);
13974
- this.store.delete(task.id);
13975
- log2(`Cleaned up deleted session task: ${task.id}`);
13976
- }
13977
- }
13978
- /**
13979
- * Find task by session ID
13980
- */
13981
- findBySession(sessionID) {
13982
- return this.store.getAll().find((t) => t.sessionID === sessionID);
13983
- }
13984
- /**
13985
- * Handle session.idle event - validate and complete task
13986
- */
13987
- async handleSessionIdle(task) {
13988
- const elapsed = Date.now() - task.startedAt.getTime();
13989
- if (elapsed < CONFIG.MIN_STABILITY_MS) {
13990
- log2(`Session idle but too early for ${task.id}, waiting...`);
13991
- return;
13992
- }
13993
- const hasOutput = await this.validateSessionHasOutput(task.sessionID);
13994
- if (!hasOutput) {
13995
- log2(`Session idle but no output for ${task.id}, waiting...`);
13996
- return;
13997
- }
13998
- task.status = "completed";
13999
- task.completedAt = /* @__PURE__ */ new Date();
14000
- if (task.concurrencyKey) {
14001
- this.concurrency.release(task.concurrencyKey);
14002
- task.concurrencyKey = void 0;
14003
- }
14004
- this.store.untrackPending(task.parentSessionID, task.id);
14005
- this.store.queueNotification(task);
14006
- await this.notifyParentIfAllComplete(task.parentSessionID);
14007
- this.scheduleCleanup(task.id);
14008
- log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
14009
- }
14010
- // ========================================================================
14011
- // Internal
14012
- // ========================================================================
14013
- handleTaskError(taskId, error45) {
14014
- const task = this.store.get(taskId);
14015
- if (!task) return;
14016
- task.status = "error";
14017
- task.error = error45 instanceof Error ? error45.message : String(error45);
14018
- task.completedAt = /* @__PURE__ */ new Date();
14019
- if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
14020
- this.store.untrackPending(task.parentSessionID, taskId);
14021
- this.store.queueNotification(task);
14022
- this.notifyParentIfAllComplete(task.parentSessionID);
14023
- this.scheduleCleanup(taskId);
14024
- }
14025
- startPolling() {
14490
+ pollingInterval;
14491
+ start() {
14026
14492
  if (this.pollingInterval) return;
14027
- this.pollingInterval = setInterval(() => this.pollRunningTasks(), CONFIG.POLL_INTERVAL_MS);
14493
+ this.pollingInterval = setInterval(() => this.poll(), CONFIG.POLL_INTERVAL_MS);
14028
14494
  this.pollingInterval.unref();
14029
14495
  }
14030
- stopPolling() {
14496
+ stop() {
14031
14497
  if (this.pollingInterval) {
14032
14498
  clearInterval(this.pollingInterval);
14033
14499
  this.pollingInterval = void 0;
14034
14500
  }
14035
14501
  }
14036
- async pollRunningTasks() {
14502
+ isRunning() {
14503
+ return !!this.pollingInterval;
14504
+ }
14505
+ async poll() {
14037
14506
  this.pruneExpiredTasks();
14038
14507
  const running = this.store.getRunning();
14039
14508
  if (running.length === 0) {
14040
- this.stopPolling();
14509
+ this.stop();
14041
14510
  return;
14042
14511
  }
14043
14512
  try {
@@ -14069,9 +14538,28 @@ var ParallelAgentManager = class _ParallelAgentManager {
14069
14538
  log2("Polling error:", error45);
14070
14539
  }
14071
14540
  }
14072
- /**
14073
- * Update task progress and stability tracking
14074
- */
14541
+ async validateSessionHasOutput(sessionID) {
14542
+ try {
14543
+ const response = await this.client.session.messages({ path: { id: sessionID } });
14544
+ const messages = response.data ?? [];
14545
+ return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === PART_TYPES.TEXT && p.text?.trim() || p.type === "tool"));
14546
+ } catch {
14547
+ return true;
14548
+ }
14549
+ }
14550
+ async completeTask(task) {
14551
+ task.status = TASK_STATUS.COMPLETED;
14552
+ task.completedAt = /* @__PURE__ */ new Date();
14553
+ if (task.concurrencyKey) {
14554
+ this.concurrency.release(task.concurrencyKey);
14555
+ task.concurrencyKey = void 0;
14556
+ }
14557
+ this.store.untrackPending(task.parentSessionID, task.id);
14558
+ this.store.queueNotification(task);
14559
+ await this.notifyParentIfAllComplete(task.parentSessionID);
14560
+ this.scheduleCleanup(task.id);
14561
+ log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
14562
+ }
14075
14563
  async updateTaskProgress(task) {
14076
14564
  try {
14077
14565
  const result = await this.client.session.messages({ path: { id: task.sessionID } });
@@ -14087,7 +14575,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14087
14575
  toolCalls++;
14088
14576
  lastTool = part.tool || part.name;
14089
14577
  }
14090
- if (part.type === "text" && part.text) {
14578
+ if (part.type === PART_TYPES.TEXT && part.text) {
14091
14579
  lastMessage = part.text;
14092
14580
  }
14093
14581
  }
@@ -14108,30 +14596,14 @@ var ParallelAgentManager = class _ParallelAgentManager {
14108
14596
  } catch {
14109
14597
  }
14110
14598
  }
14111
- /**
14112
- * Complete a task and cleanup
14113
- */
14114
- async completeTask(task) {
14115
- task.status = "completed";
14116
- task.completedAt = /* @__PURE__ */ new Date();
14117
- if (task.concurrencyKey) {
14118
- this.concurrency.release(task.concurrencyKey);
14119
- task.concurrencyKey = void 0;
14120
- }
14121
- this.store.untrackPending(task.parentSessionID, task.id);
14122
- this.store.queueNotification(task);
14123
- await this.notifyParentIfAllComplete(task.parentSessionID);
14124
- this.scheduleCleanup(task.id);
14125
- log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
14126
- }
14127
- async validateSessionHasOutput(sessionID) {
14128
- try {
14129
- const response = await this.client.session.messages({ path: { id: sessionID } });
14130
- const messages = response.data ?? [];
14131
- return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text?.trim() || p.type === "tool"));
14132
- } catch {
14133
- return true;
14134
- }
14599
+ };
14600
+
14601
+ // src/core/agents/manager/task-cleaner.ts
14602
+ var TaskCleaner = class {
14603
+ constructor(client, store, concurrency) {
14604
+ this.client = client;
14605
+ this.store = store;
14606
+ this.concurrency = concurrency;
14135
14607
  }
14136
14608
  pruneExpiredTasks() {
14137
14609
  const now = Date.now();
@@ -14139,8 +14611,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
14139
14611
  const age = now - task.startedAt.getTime();
14140
14612
  if (age <= CONFIG.TASK_TTL_MS) continue;
14141
14613
  log2(`Timeout: ${taskId}`);
14142
- if (task.status === "running") {
14143
- task.status = "timeout";
14614
+ if (task.status === TASK_STATUS.RUNNING) {
14615
+ task.status = TASK_STATUS.TIMEOUT;
14144
14616
  task.error = "Task exceeded 30 minute time limit";
14145
14617
  task.completedAt = /* @__PURE__ */ new Date();
14146
14618
  if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
@@ -14174,7 +14646,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14174
14646
  try {
14175
14647
  await this.client.session.prompt({
14176
14648
  path: { id: parentSessionID },
14177
- body: { noReply: true, parts: [{ type: "text", text: message }] }
14649
+ body: { noReply: true, parts: [{ type: PART_TYPES.TEXT, text: message }] }
14178
14650
  });
14179
14651
  log2(`Notified parent ${parentSessionID}`);
14180
14652
  } catch (error45) {
@@ -14183,315 +14655,1746 @@ var ParallelAgentManager = class _ParallelAgentManager {
14183
14655
  this.store.clearNotifications(parentSessionID);
14184
14656
  }
14185
14657
  };
14186
- var parallelAgentManager = {
14187
- getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
14188
- };
14189
-
14190
- // src/tools/parallel/delegate-task.ts
14191
- var createDelegateTaskTool = (manager, client) => tool({
14192
- description: `Delegate a task to an agent.
14193
14658
 
14194
- <mode>
14195
- - background=true: Non-blocking. Returns task ID immediately.
14196
- - background=false: Blocking. Waits for result.
14197
- </mode>
14659
+ // src/shared/event-types.ts
14660
+ var TASK_EVENTS = {
14661
+ STARTED: "task.started",
14662
+ COMPLETED: "task.completed",
14663
+ FAILED: "task.failed",
14664
+ CANCELLED: "task.cancelled"
14665
+ };
14666
+ var TODO_EVENTS = {
14667
+ CREATED: "todo.created",
14668
+ UPDATED: "todo.updated",
14669
+ COMPLETED: "todo.completed"
14670
+ };
14671
+ var SESSION_EVENTS = {
14672
+ IDLE: "session.idle",
14673
+ BUSY: "session.busy",
14674
+ ERROR: "session.error",
14675
+ DELETED: "session.deleted"
14676
+ };
14677
+ var DOCUMENT_EVENTS = {
14678
+ CACHED: "document.cached",
14679
+ EXPIRED: "document.expired"
14680
+ };
14681
+ var MISSION_EVENTS = {
14682
+ COMPLETE: "mission.complete",
14683
+ FAILED: "mission.failed",
14684
+ ALL_TASKS_COMPLETE: "all_tasks.complete"
14685
+ };
14686
+ var SPECIAL_EVENTS = {
14687
+ WILDCARD: "*"
14688
+ };
14689
+ var EVENT_TYPES = {
14690
+ ...TASK_EVENTS,
14691
+ ...TODO_EVENTS,
14692
+ ...SESSION_EVENTS,
14693
+ ...DOCUMENT_EVENTS,
14694
+ ...MISSION_EVENTS,
14695
+ ...SPECIAL_EVENTS
14696
+ };
14198
14697
 
14199
- <safety>
14200
- - Max 3 tasks per agent type
14201
- - Auto-timeout: 30 minutes
14202
- </safety>`,
14203
- args: {
14204
- agent: tool.schema.string().describe("Agent name"),
14205
- description: tool.schema.string().describe("Task description"),
14206
- prompt: tool.schema.string().describe("Prompt for the agent"),
14207
- background: tool.schema.boolean().describe("true=async, false=sync")
14208
- },
14209
- async execute(args, context) {
14210
- const { agent, description, prompt, background } = args;
14211
- const ctx = context;
14212
- const sessionClient = client;
14213
- if (background === void 0) {
14214
- return `\u274C 'background' parameter is REQUIRED.`;
14698
+ // src/core/bus/event-bus.ts
14699
+ var EventBusImpl = class {
14700
+ subscriptions = /* @__PURE__ */ new Map();
14701
+ eventHistory = [];
14702
+ maxHistorySize = 100;
14703
+ subscriptionCounter = 0;
14704
+ /**
14705
+ * Subscribe to an event type
14706
+ * Returns unsubscribe function
14707
+ */
14708
+ subscribe(type, handler) {
14709
+ const id = `sub_${++this.subscriptionCounter}`;
14710
+ const subscription = { id, type, handler, once: false };
14711
+ const existing = this.subscriptions.get(type) || [];
14712
+ existing.push(subscription);
14713
+ this.subscriptions.set(type, existing);
14714
+ return () => this.unsubscribe(id, type);
14715
+ }
14716
+ /**
14717
+ * Subscribe to an event type, auto-unsubscribe after first event
14718
+ */
14719
+ once(type, handler) {
14720
+ const id = `sub_${++this.subscriptionCounter}`;
14721
+ const subscription = { id, type, handler, once: true };
14722
+ const existing = this.subscriptions.get(type) || [];
14723
+ existing.push(subscription);
14724
+ this.subscriptions.set(type, existing);
14725
+ return () => this.unsubscribe(id, type);
14726
+ }
14727
+ /**
14728
+ * Unsubscribe from an event
14729
+ */
14730
+ unsubscribe(id, type) {
14731
+ const subs = this.subscriptions.get(type);
14732
+ if (subs) {
14733
+ const filtered = subs.filter((s) => s.id !== id);
14734
+ if (filtered.length > 0) {
14735
+ this.subscriptions.set(type, filtered);
14736
+ } else {
14737
+ this.subscriptions.delete(type);
14738
+ }
14215
14739
  }
14216
- if (background === true) {
14740
+ }
14741
+ /**
14742
+ * Publish an event
14743
+ */
14744
+ async publish(type, properties = {}, options = {}) {
14745
+ const event = {
14746
+ type,
14747
+ timestamp: /* @__PURE__ */ new Date(),
14748
+ source: options.source || "unknown",
14749
+ sessionId: options.sessionId,
14750
+ properties
14751
+ };
14752
+ this.eventHistory.push(event);
14753
+ if (this.eventHistory.length > this.maxHistorySize) {
14754
+ this.eventHistory.shift();
14755
+ }
14756
+ const toNotify = [];
14757
+ const typeSubs = this.subscriptions.get(type) || [];
14758
+ toNotify.push(...typeSubs);
14759
+ const wildcardSubs = this.subscriptions.get(SPECIAL_EVENTS.WILDCARD) || [];
14760
+ toNotify.push(...wildcardSubs);
14761
+ const toRemove = [];
14762
+ for (const sub of toNotify) {
14217
14763
  try {
14218
- const task = await manager.launch({
14219
- agent,
14220
- description,
14221
- prompt,
14222
- parentSessionID: ctx.sessionID
14223
- });
14224
- return `\u{1F680} Task spawned: \`${task.id}\` (${agent})`;
14764
+ await sub.handler(event);
14225
14765
  } catch (error45) {
14226
- return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
14766
+ console.error(`[EventBus] Handler error for ${type}:`, error45);
14227
14767
  }
14228
- }
14229
- try {
14230
- const session = sessionClient.session;
14231
- const createResult = await session.create({
14232
- body: { parentID: ctx.sessionID, title: `Task: ${description}` },
14233
- query: { directory: "." }
14234
- });
14235
- if (createResult.error || !createResult.data?.id) {
14236
- return `\u274C Failed to create session`;
14768
+ if (sub.once) {
14769
+ toRemove.push({ id: sub.id, type: sub.type });
14237
14770
  }
14238
- const sessionID = createResult.data.id;
14239
- const startTime = Date.now();
14240
- await session.prompt({
14241
- path: { id: sessionID },
14242
- body: { agent, parts: [{ type: "text", text: prompt }] }
14771
+ }
14772
+ for (const { id, type: t } of toRemove) {
14773
+ this.unsubscribe(id, t);
14774
+ }
14775
+ }
14776
+ /**
14777
+ * Emit (alias for publish, sync-looking API)
14778
+ */
14779
+ emit(type, properties = {}) {
14780
+ this.publish(type, properties).catch(console.error);
14781
+ }
14782
+ /**
14783
+ * Get recent event history
14784
+ */
14785
+ getHistory(type, limit = 20) {
14786
+ let events = this.eventHistory;
14787
+ if (type && type !== SPECIAL_EVENTS.WILDCARD) {
14788
+ events = events.filter((e) => e.type === type);
14789
+ }
14790
+ return events.slice(-limit);
14791
+ }
14792
+ /**
14793
+ * Clear all subscriptions
14794
+ */
14795
+ clear() {
14796
+ this.subscriptions.clear();
14797
+ this.eventHistory = [];
14798
+ }
14799
+ /**
14800
+ * Get subscription count
14801
+ */
14802
+ getSubscriptionCount() {
14803
+ let count = 0;
14804
+ for (const subs of this.subscriptions.values()) {
14805
+ count += subs.length;
14806
+ }
14807
+ return count;
14808
+ }
14809
+ /**
14810
+ * Wait for a specific event (Promise-based)
14811
+ */
14812
+ waitFor(type, timeout = 3e4) {
14813
+ return new Promise((resolve, reject) => {
14814
+ const timer = setTimeout(() => {
14815
+ unsubscribe();
14816
+ reject(new Error(`Timeout waiting for event: ${type}`));
14817
+ }, timeout);
14818
+ const unsubscribe = this.once(type, (event) => {
14819
+ clearTimeout(timer);
14820
+ resolve(event);
14243
14821
  });
14244
- let stablePolls = 0, lastMsgCount = 0;
14245
- while (Date.now() - startTime < 10 * 60 * 1e3) {
14246
- await new Promise((r) => setTimeout(r, 500));
14247
- const statusResult = await session.status();
14248
- if (statusResult.data?.[sessionID]?.type !== "idle") {
14249
- stablePolls = 0;
14250
- continue;
14251
- }
14252
- if (Date.now() - startTime < 5e3) continue;
14253
- const msgs2 = await session.messages({ path: { id: sessionID } });
14254
- const count = (msgs2.data ?? []).length;
14255
- if (count === lastMsgCount) {
14256
- stablePolls++;
14822
+ });
14823
+ }
14824
+ };
14825
+
14826
+ // src/core/bus/index.ts
14827
+ var EventBus = new EventBusImpl();
14828
+ function emit(type, properties) {
14829
+ EventBus.emit(type, properties);
14830
+ }
14831
+
14832
+ // src/core/agents/manager/event-handler.ts
14833
+ var EventHandler = class {
14834
+ constructor(client, store, concurrency, findBySession, notifyParentIfAllComplete, scheduleCleanup, validateSessionHasOutput) {
14835
+ this.client = client;
14836
+ this.store = store;
14837
+ this.concurrency = concurrency;
14838
+ this.findBySession = findBySession;
14839
+ this.notifyParentIfAllComplete = notifyParentIfAllComplete;
14840
+ this.scheduleCleanup = scheduleCleanup;
14841
+ this.validateSessionHasOutput = validateSessionHasOutput;
14842
+ }
14843
+ /**
14844
+ * Handle OpenCode session events for proper resource cleanup.
14845
+ * Call this from your plugin's event hook.
14846
+ */
14847
+ handle(event) {
14848
+ const props = event.properties;
14849
+ if (event.type === SESSION_EVENTS.IDLE) {
14850
+ const sessionID = props?.sessionID;
14851
+ if (!sessionID) return;
14852
+ const task = this.findBySession(sessionID);
14853
+ if (!task || task.status !== TASK_STATUS.RUNNING) return;
14854
+ this.handleSessionIdle(task).catch((err) => {
14855
+ log2("Error handling session.idle:", err);
14856
+ });
14857
+ }
14858
+ if (event.type === SESSION_EVENTS.DELETED) {
14859
+ const sessionID = props?.info?.id ?? props?.sessionID;
14860
+ if (!sessionID) return;
14861
+ const task = this.findBySession(sessionID);
14862
+ if (!task) return;
14863
+ this.handleSessionDeleted(task);
14864
+ }
14865
+ }
14866
+ async handleSessionIdle(task) {
14867
+ const elapsed = Date.now() - task.startedAt.getTime();
14868
+ if (elapsed < CONFIG.MIN_STABILITY_MS) {
14869
+ log2(`Session idle but too early for ${task.id}, waiting...`);
14870
+ return;
14871
+ }
14872
+ const hasOutput = await this.validateSessionHasOutput(task.sessionID);
14873
+ if (!hasOutput) {
14874
+ log2(`Session idle but no output for ${task.id}, waiting...`);
14875
+ return;
14876
+ }
14877
+ task.status = TASK_STATUS.COMPLETED;
14878
+ task.completedAt = /* @__PURE__ */ new Date();
14879
+ if (task.concurrencyKey) {
14880
+ this.concurrency.release(task.concurrencyKey);
14881
+ task.concurrencyKey = void 0;
14882
+ }
14883
+ this.store.untrackPending(task.parentSessionID, task.id);
14884
+ this.store.queueNotification(task);
14885
+ await this.notifyParentIfAllComplete(task.parentSessionID);
14886
+ this.scheduleCleanup(task.id);
14887
+ log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
14888
+ }
14889
+ handleSessionDeleted(task) {
14890
+ log2(`Session deleted event for task ${task.id}`);
14891
+ if (task.status === TASK_STATUS.RUNNING) {
14892
+ task.status = TASK_STATUS.ERROR;
14893
+ task.error = "Session deleted";
14894
+ task.completedAt = /* @__PURE__ */ new Date();
14895
+ }
14896
+ if (task.concurrencyKey) {
14897
+ this.concurrency.release(task.concurrencyKey);
14898
+ task.concurrencyKey = void 0;
14899
+ }
14900
+ this.store.untrackPending(task.parentSessionID, task.id);
14901
+ this.store.clearNotificationsForTask(task.id);
14902
+ this.store.delete(task.id);
14903
+ log2(`Cleaned up deleted session task: ${task.id}`);
14904
+ }
14905
+ };
14906
+
14907
+ // src/core/agents/manager.ts
14908
+ var ParallelAgentManager = class _ParallelAgentManager {
14909
+ static _instance;
14910
+ store = new TaskStore();
14911
+ client;
14912
+ directory;
14913
+ concurrency = new ConcurrencyController();
14914
+ // Composed components
14915
+ launcher;
14916
+ resumer;
14917
+ poller;
14918
+ cleaner;
14919
+ eventHandler;
14920
+ constructor(client, directory) {
14921
+ this.client = client;
14922
+ this.directory = directory;
14923
+ this.cleaner = new TaskCleaner(client, this.store, this.concurrency);
14924
+ this.poller = new TaskPoller(
14925
+ client,
14926
+ this.store,
14927
+ this.concurrency,
14928
+ (parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
14929
+ (taskId) => this.cleaner.scheduleCleanup(taskId),
14930
+ () => this.cleaner.pruneExpiredTasks()
14931
+ );
14932
+ this.launcher = new TaskLauncher(
14933
+ client,
14934
+ directory,
14935
+ this.store,
14936
+ this.concurrency,
14937
+ (taskId, error45) => this.handleTaskError(taskId, error45),
14938
+ () => this.poller.start()
14939
+ );
14940
+ this.resumer = new TaskResumer(
14941
+ client,
14942
+ this.store,
14943
+ (sessionID) => this.findBySession(sessionID),
14944
+ () => this.poller.start(),
14945
+ (parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID)
14946
+ );
14947
+ this.eventHandler = new EventHandler(
14948
+ client,
14949
+ this.store,
14950
+ this.concurrency,
14951
+ (sessionID) => this.findBySession(sessionID),
14952
+ (parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
14953
+ (taskId) => this.cleaner.scheduleCleanup(taskId),
14954
+ (sessionID) => this.poller.validateSessionHasOutput(sessionID)
14955
+ );
14956
+ }
14957
+ static getInstance(client, directory) {
14958
+ if (!_ParallelAgentManager._instance) {
14959
+ if (!client || !directory) {
14960
+ throw new Error("ParallelAgentManager requires client and directory on first call");
14961
+ }
14962
+ _ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
14963
+ }
14964
+ return _ParallelAgentManager._instance;
14965
+ }
14966
+ // ========================================================================
14967
+ // Public API
14968
+ // ========================================================================
14969
+ async launch(input) {
14970
+ this.cleaner.pruneExpiredTasks();
14971
+ return this.launcher.launch(input);
14972
+ }
14973
+ async resume(input) {
14974
+ return this.resumer.resume(input);
14975
+ }
14976
+ getTask(id) {
14977
+ return this.store.get(id);
14978
+ }
14979
+ getRunningTasks() {
14980
+ return this.store.getRunning();
14981
+ }
14982
+ getAllTasks() {
14983
+ return this.store.getAll();
14984
+ }
14985
+ getTasksByParent(parentSessionID) {
14986
+ return this.store.getByParent(parentSessionID);
14987
+ }
14988
+ async cancelTask(taskId) {
14989
+ const task = this.store.get(taskId);
14990
+ if (!task || task.status !== TASK_STATUS.RUNNING) return false;
14991
+ task.status = TASK_STATUS.ERROR;
14992
+ task.error = "Cancelled by user";
14993
+ task.completedAt = /* @__PURE__ */ new Date();
14994
+ if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
14995
+ this.store.untrackPending(task.parentSessionID, taskId);
14996
+ try {
14997
+ await this.client.session.delete({ path: { id: task.sessionID } });
14998
+ log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
14999
+ } catch {
15000
+ log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
15001
+ }
15002
+ this.cleaner.scheduleCleanup(taskId);
15003
+ log2(`Cancelled ${taskId}`);
15004
+ return true;
15005
+ }
15006
+ async getResult(taskId) {
15007
+ const task = this.store.get(taskId);
15008
+ if (!task) return null;
15009
+ if (task.result) return task.result;
15010
+ if (task.status === TASK_STATUS.ERROR) return `Error: ${task.error}`;
15011
+ if (task.status === TASK_STATUS.RUNNING) return null;
15012
+ try {
15013
+ const result = await this.client.session.messages({ path: { id: task.sessionID } });
15014
+ if (result.error) return `Error: ${result.error}`;
15015
+ const messages = result.data ?? [];
15016
+ const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
15017
+ if (!lastMsg) return "(No response)";
15018
+ const text = lastMsg.parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
15019
+ task.result = text;
15020
+ return text;
15021
+ } catch (error45) {
15022
+ return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
15023
+ }
15024
+ }
15025
+ setConcurrencyLimit(agentType, limit) {
15026
+ this.concurrency.setLimit(agentType, limit);
15027
+ }
15028
+ getPendingCount(parentSessionID) {
15029
+ return this.store.getPendingCount(parentSessionID);
15030
+ }
15031
+ cleanup() {
15032
+ this.poller.stop();
15033
+ this.store.clear();
15034
+ }
15035
+ formatDuration = formatDuration;
15036
+ // ========================================================================
15037
+ // Event Handling
15038
+ // ========================================================================
15039
+ handleEvent(event) {
15040
+ this.eventHandler.handle(event);
15041
+ }
15042
+ // ========================================================================
15043
+ // Private Helpers
15044
+ // ========================================================================
15045
+ findBySession(sessionID) {
15046
+ return this.store.getAll().find((t) => t.sessionID === sessionID);
15047
+ }
15048
+ handleTaskError(taskId, error45) {
15049
+ const task = this.store.get(taskId);
15050
+ if (!task) return;
15051
+ task.status = "error";
15052
+ task.error = error45 instanceof Error ? error45.message : String(error45);
15053
+ task.completedAt = /* @__PURE__ */ new Date();
15054
+ if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
15055
+ this.store.untrackPending(task.parentSessionID, taskId);
15056
+ this.store.queueNotification(task);
15057
+ this.cleaner.notifyParentIfAllComplete(task.parentSessionID);
15058
+ this.cleaner.scheduleCleanup(taskId);
15059
+ }
15060
+ };
15061
+ var parallelAgentManager = {
15062
+ getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
15063
+ };
15064
+
15065
+ // src/tools/parallel/delegate-task.ts
15066
+ var createDelegateTaskTool = (manager, client) => tool({
15067
+ description: `Delegate a task to an agent.
15068
+
15069
+ <mode>
15070
+ - background=true: Non-blocking. Returns task ID immediately.
15071
+ - background=false: Blocking. Waits for result.
15072
+ </mode>
15073
+
15074
+ <resume>
15075
+ - resume: Optional session ID to continue existing session.
15076
+ - When set, continues previous work instead of starting fresh.
15077
+ - Preserves all context from previous conversation.
15078
+ - Use for: retry after failure, follow-up questions, token efficiency.
15079
+ </resume>
15080
+
15081
+ <safety>
15082
+ - Max 10 tasks per agent type (configurable)
15083
+ - Auto-timeout: 60 minutes
15084
+ </safety>`,
15085
+ args: {
15086
+ agent: tool.schema.string().describe("Agent name"),
15087
+ description: tool.schema.string().describe("Task description"),
15088
+ prompt: tool.schema.string().describe("Prompt for the agent"),
15089
+ background: tool.schema.boolean().describe("true=async, false=sync"),
15090
+ resume: tool.schema.string().optional().describe("Session ID to resume (from previous task.sessionID)")
15091
+ },
15092
+ async execute(args, context) {
15093
+ const { agent, description, prompt, background, resume } = args;
15094
+ const ctx = context;
15095
+ const sessionClient = client;
15096
+ if (background === void 0) {
15097
+ return `\u274C 'background' parameter is REQUIRED.`;
15098
+ }
15099
+ if (resume) {
15100
+ try {
15101
+ const task = await manager.resume({
15102
+ sessionId: resume,
15103
+ prompt,
15104
+ parentSessionID: ctx.sessionID
15105
+ });
15106
+ if (background === true) {
15107
+ return `\u{1F504} Resumed task: \`${task.id}\` (${task.agent}) in session \`${task.sessionID}\`
15108
+
15109
+ Previous context preserved. Use \`get_task_result({ taskId: "${task.id}" })\` when complete.`;
15110
+ }
15111
+ const startTime = Date.now();
15112
+ const session = sessionClient.session;
15113
+ let stablePolls = 0, lastMsgCount = 0;
15114
+ while (Date.now() - startTime < PARALLEL_TASK.SYNC_TIMEOUT_MS) {
15115
+ await new Promise((r) => setTimeout(r, 500));
15116
+ const statusResult = await session.status();
15117
+ if (statusResult.data?.[task.sessionID]?.type !== "idle") {
15118
+ stablePolls = 0;
15119
+ continue;
15120
+ }
15121
+ if (Date.now() - startTime < 5e3) continue;
15122
+ const msgs2 = await session.messages({ path: { id: task.sessionID } });
15123
+ const count = (msgs2.data ?? []).length;
15124
+ if (count === lastMsgCount) {
15125
+ stablePolls++;
15126
+ if (stablePolls >= 3) break;
15127
+ } else {
15128
+ stablePolls = 0;
15129
+ lastMsgCount = count;
15130
+ }
15131
+ }
15132
+ const msgs = await session.messages({ path: { id: task.sessionID } });
15133
+ const messages = msgs.data ?? [];
15134
+ const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
15135
+ const text = lastMsg?.parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text ?? "").join("\n") || "";
15136
+ return `\u{1F504} Resumed & Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
15137
+
15138
+ ${text || "(No output)"}`;
15139
+ } catch (error45) {
15140
+ return `\u274C Resume failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
15141
+ }
15142
+ }
15143
+ if (background === true) {
15144
+ try {
15145
+ const task = await manager.launch({
15146
+ agent,
15147
+ description,
15148
+ prompt,
15149
+ parentSessionID: ctx.sessionID
15150
+ });
15151
+ return `\u{1F680} Task spawned: \`${task.id}\` (${agent})
15152
+ Session: \`${task.sessionID}\` (save for resume)`;
15153
+ } catch (error45) {
15154
+ return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
15155
+ }
15156
+ }
15157
+ try {
15158
+ const session = sessionClient.session;
15159
+ const createResult = await session.create({
15160
+ body: { parentID: ctx.sessionID, title: `Task: ${description}` },
15161
+ query: { directory: "." }
15162
+ });
15163
+ if (createResult.error || !createResult.data?.id) {
15164
+ return `\u274C Failed to create session`;
15165
+ }
15166
+ const sessionID = createResult.data.id;
15167
+ const startTime = Date.now();
15168
+ await session.prompt({
15169
+ path: { id: sessionID },
15170
+ body: { agent, parts: [{ type: PART_TYPES.TEXT, text: prompt }] }
15171
+ });
15172
+ let stablePolls = 0, lastMsgCount = 0;
15173
+ while (Date.now() - startTime < 10 * 60 * 1e3) {
15174
+ await new Promise((r) => setTimeout(r, 500));
15175
+ const statusResult = await session.status();
15176
+ if (statusResult.data?.[sessionID]?.type !== "idle") {
15177
+ stablePolls = 0;
15178
+ continue;
15179
+ }
15180
+ if (Date.now() - startTime < 5e3) continue;
15181
+ const msgs2 = await session.messages({ path: { id: sessionID } });
15182
+ const count = (msgs2.data ?? []).length;
15183
+ if (count === lastMsgCount) {
15184
+ stablePolls++;
14257
15185
  if (stablePolls >= 3) break;
14258
15186
  } else {
14259
15187
  stablePolls = 0;
14260
15188
  lastMsgCount = count;
14261
15189
  }
14262
15190
  }
14263
- const msgs = await session.messages({ path: { id: sessionID } });
14264
- const messages = msgs.data ?? [];
14265
- const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
14266
- const text = lastMsg?.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").join("\n") || "";
14267
- return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
14268
-
14269
- ${text || "(No output)"}`;
14270
- } catch (error45) {
14271
- return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
15191
+ const msgs = await session.messages({ path: { id: sessionID } });
15192
+ const messages = msgs.data ?? [];
15193
+ const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
15194
+ const text = lastMsg?.parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text ?? "").join("\n") || "";
15195
+ return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
15196
+ Session: \`${sessionID}\` (save for resume)
15197
+
15198
+ ${text || "(No output)"}`;
15199
+ } catch (error45) {
15200
+ return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
15201
+ }
15202
+ }
15203
+ });
15204
+
15205
+ // src/tools/parallel/get-task-result.ts
15206
+ var createGetTaskResultTool = (manager) => tool({
15207
+ description: `Get result from a completed background task.`,
15208
+ args: {
15209
+ taskId: tool.schema.string().describe("Task ID")
15210
+ },
15211
+ async execute(args) {
15212
+ const task = manager.getTask(args.taskId);
15213
+ if (!task) return `\u274C Task not found: \`${args.taskId}\``;
15214
+ if (task.status === "running") return `\u23F3 Still running...`;
15215
+ const result = await manager.getResult(args.taskId);
15216
+ const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
15217
+ if (task.status === "error" || task.status === "timeout") {
15218
+ return `\u274C ${task.status}: ${task.error}`;
15219
+ }
15220
+ return `\u2705 Completed (${duration3})
15221
+
15222
+ ${result || "(No output)"}`;
15223
+ }
15224
+ });
15225
+
15226
+ // src/tools/parallel/list-tasks.ts
15227
+ var createListTasksTool = (manager) => tool({
15228
+ description: `List all background tasks.`,
15229
+ args: {
15230
+ status: tool.schema.string().optional().describe("Filter: all, running, completed, error")
15231
+ },
15232
+ async execute(args) {
15233
+ const { status = "all" } = args;
15234
+ let tasks;
15235
+ switch (status) {
15236
+ case "running":
15237
+ tasks = manager.getRunningTasks();
15238
+ break;
15239
+ case "completed":
15240
+ tasks = manager.getAllTasks().filter((t) => t.status === "completed");
15241
+ break;
15242
+ case "error":
15243
+ tasks = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout");
15244
+ break;
15245
+ default:
15246
+ tasks = manager.getAllTasks();
15247
+ }
15248
+ if (tasks.length === 0) return `\u{1F4CB} No tasks found.`;
15249
+ const rows = tasks.map((t) => {
15250
+ const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
15251
+ return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
15252
+ }).join("\n");
15253
+ return `\u{1F4CB} **Tasks**
15254
+
15255
+ | ID | Status | Agent | Time |
15256
+ |----|--------|-------|------|
15257
+ ${rows}`;
15258
+ }
15259
+ });
15260
+
15261
+ // src/tools/parallel/cancel-task.ts
15262
+ var createCancelTaskTool = (manager) => tool({
15263
+ description: `Cancel a running task.`,
15264
+ args: {
15265
+ taskId: tool.schema.string().describe("Task ID to cancel")
15266
+ },
15267
+ async execute(args) {
15268
+ const cancelled = await manager.cancelTask(args.taskId);
15269
+ if (cancelled) return `\u{1F6D1} Cancelled: \`${args.taskId}\``;
15270
+ const task = manager.getTask(args.taskId);
15271
+ if (task) return `\u26A0\uFE0F Cannot cancel: Task is ${task.status}`;
15272
+ return `\u274C Not found: \`${args.taskId}\``;
15273
+ }
15274
+ });
15275
+
15276
+ // src/tools/parallel/index.ts
15277
+ function createAsyncAgentTools(manager, client) {
15278
+ return {
15279
+ delegate_task: createDelegateTaskTool(manager, client),
15280
+ get_task_result: createGetTaskResultTool(manager),
15281
+ list_tasks: createListTasksTool(manager),
15282
+ cancel_task: createCancelTaskTool(manager)
15283
+ };
15284
+ }
15285
+
15286
+ // src/utils/common.ts
15287
+ function detectSlashCommand(text) {
15288
+ const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
15289
+ if (!match) return null;
15290
+ return { command: match[1], args: match[2] || "" };
15291
+ }
15292
+ function formatTimestamp(date5 = /* @__PURE__ */ new Date()) {
15293
+ const pad = (n) => n.toString().padStart(2, "0");
15294
+ return `${date5.getFullYear()}-${pad(date5.getMonth() + 1)}-${pad(date5.getDate())} ${pad(date5.getHours())}:${pad(date5.getMinutes())}:${pad(date5.getSeconds())}`;
15295
+ }
15296
+ function formatElapsedTime(startMs, endMs = Date.now()) {
15297
+ const elapsed = endMs - startMs;
15298
+ if (elapsed < 0) return "0s";
15299
+ const seconds = Math.floor(elapsed / 1e3) % 60;
15300
+ const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
15301
+ const hours = Math.floor(elapsed / (1e3 * 60 * 60));
15302
+ const parts = [];
15303
+ if (hours > 0) parts.push(`${hours}h`);
15304
+ if (minutes > 0) parts.push(`${minutes}m`);
15305
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
15306
+ return parts.join(" ");
15307
+ }
15308
+
15309
+ // src/utils/sanity.ts
15310
+ function checkOutputSanity(text) {
15311
+ if (!text || text.length < 50) {
15312
+ return { isHealthy: true, severity: "ok" };
15313
+ }
15314
+ if (/(.)\1{15,}/.test(text)) {
15315
+ return {
15316
+ isHealthy: false,
15317
+ reason: "Single character repetition detected",
15318
+ severity: "critical"
15319
+ };
15320
+ }
15321
+ if (/(.{2,6})\1{8,}/.test(text)) {
15322
+ return {
15323
+ isHealthy: false,
15324
+ reason: "Pattern loop detected",
15325
+ severity: "critical"
15326
+ };
15327
+ }
15328
+ if (text.length > 200) {
15329
+ const cleanText = text.replace(/\s/g, "");
15330
+ if (cleanText.length > 100) {
15331
+ const uniqueChars = new Set(cleanText).size;
15332
+ const ratio = uniqueChars / cleanText.length;
15333
+ if (ratio < 0.02) {
15334
+ return {
15335
+ isHealthy: false,
15336
+ reason: "Low information density",
15337
+ severity: "critical"
15338
+ };
15339
+ }
15340
+ }
15341
+ }
15342
+ const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
15343
+ if (boxChars > 100 && boxChars / text.length > 0.3) {
15344
+ return {
15345
+ isHealthy: false,
15346
+ reason: "Visual gibberish detected",
15347
+ severity: "critical"
15348
+ };
15349
+ }
15350
+ const lines = text.split("\n").filter((l) => l.trim().length > 10);
15351
+ if (lines.length > 10) {
15352
+ const lineSet = new Set(lines);
15353
+ if (lineSet.size < lines.length * 0.2) {
15354
+ return {
15355
+ isHealthy: false,
15356
+ reason: "Excessive line repetition",
15357
+ severity: "warning"
15358
+ };
15359
+ }
15360
+ }
15361
+ const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
15362
+ if (cjkChars > 200) {
15363
+ const uniqueCjk = new Set(
15364
+ text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
15365
+ ).size;
15366
+ if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
15367
+ return {
15368
+ isHealthy: false,
15369
+ reason: "CJK character spam detected",
15370
+ severity: "critical"
15371
+ };
15372
+ }
15373
+ }
15374
+ return { isHealthy: true, severity: "ok" };
15375
+ }
15376
+ var RECOVERY_PROMPT = `<anomaly_recovery>
15377
+ \u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
15378
+
15379
+ <recovery_protocol>
15380
+ 1. DISCARD the corrupted output completely - do not reference it
15381
+ 2. RECALL the original mission objective
15382
+ 3. IDENTIFY the last confirmed successful step
15383
+ 4. RESTART with a simpler, more focused approach
15384
+ </recovery_protocol>
15385
+
15386
+ <instructions>
15387
+ - If a sub-agent produced bad output: try a different agent or simpler task
15388
+ - If stuck in a loop: break down the task into smaller pieces
15389
+ - If context seems corrupted: call recorder to restore context
15390
+ - THINK in English for maximum stability
15391
+ </instructions>
15392
+
15393
+ What was the original task? Proceed from the last known good state.
15394
+ </anomaly_recovery>`;
15395
+ var ESCALATION_PROMPT = `<critical_anomaly>
15396
+ \u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
15397
+
15398
+ <emergency_protocol>
15399
+ 1. STOP current execution path immediately
15400
+ 2. DO NOT continue with the same approach - it is failing
15401
+ 3. CALL architect for a completely new strategy
15402
+ 4. If architect also fails: report status to user and await guidance
15403
+ </emergency_protocol>
15404
+
15405
+ <diagnosis>
15406
+ The current approach is producing corrupted output.
15407
+ This may indicate: context overload, model instability, or task complexity.
15408
+ </diagnosis>
15409
+
15410
+ Request a fresh plan from architect with reduced scope.
15411
+ </critical_anomaly>`;
15412
+
15413
+ // src/core/cache/constants.ts
15414
+ var CACHE_DIR = ".cache/docs";
15415
+ var METADATA_FILE = ".cache/docs/_metadata.json";
15416
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
15417
+
15418
+ // src/core/cache/operations.ts
15419
+ import * as fs3 from "node:fs/promises";
15420
+ import * as path2 from "node:path";
15421
+
15422
+ // src/core/cache/utils.ts
15423
+ import * as fs2 from "node:fs/promises";
15424
+ import { existsSync as existsSync3 } from "node:fs";
15425
+ async function ensureCacheDir() {
15426
+ if (!existsSync3(CACHE_DIR)) {
15427
+ await fs2.mkdir(CACHE_DIR, { recursive: true });
15428
+ }
15429
+ }
15430
+ function urlToFilename(url2) {
15431
+ try {
15432
+ const parsed = new URL(url2);
15433
+ const domain2 = parsed.hostname.replace(/\./g, "_");
15434
+ const pathPart = parsed.pathname.replace(/^\//, "").replace(/\//g, "_").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 50);
15435
+ return `${domain2}${pathPart ? "_" + pathPart : ""}.md`;
15436
+ } catch {
15437
+ return url2.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 60) + ".md";
15438
+ }
15439
+ }
15440
+ async function readMetadata() {
15441
+ try {
15442
+ const content = await fs2.readFile(METADATA_FILE, "utf-8");
15443
+ return JSON.parse(content);
15444
+ } catch {
15445
+ return { documents: {}, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
15446
+ }
15447
+ }
15448
+ async function writeMetadata(metadata) {
15449
+ await ensureCacheDir();
15450
+ metadata.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
15451
+ await fs2.writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
15452
+ }
15453
+
15454
+ // src/core/cache/operations.ts
15455
+ async function get(url2) {
15456
+ const metadata = await readMetadata();
15457
+ const filename = urlToFilename(url2);
15458
+ const entry = metadata.documents[filename];
15459
+ if (!entry) return null;
15460
+ if (new Date(entry.expiresAt) < /* @__PURE__ */ new Date()) {
15461
+ await remove(url2);
15462
+ return null;
15463
+ }
15464
+ try {
15465
+ const filepath = path2.join(CACHE_DIR, filename);
15466
+ const content = await fs3.readFile(filepath, "utf-8");
15467
+ return { ...entry, content };
15468
+ } catch {
15469
+ return null;
15470
+ }
15471
+ }
15472
+ async function getByFilename(filename) {
15473
+ const metadata = await readMetadata();
15474
+ const entry = metadata.documents[filename];
15475
+ if (!entry) return null;
15476
+ try {
15477
+ const filepath = path2.join(CACHE_DIR, filename);
15478
+ const content = await fs3.readFile(filepath, "utf-8");
15479
+ return { ...entry, content };
15480
+ } catch {
15481
+ return null;
15482
+ }
15483
+ }
15484
+ async function set2(url2, content, title, ttlMs = DEFAULT_TTL_MS) {
15485
+ await ensureCacheDir();
15486
+ const filename = urlToFilename(url2);
15487
+ const filepath = path2.join(CACHE_DIR, filename);
15488
+ const now = /* @__PURE__ */ new Date();
15489
+ const header = `# ${title}
15490
+
15491
+ > Source: ${url2}
15492
+ > Cached: ${now.toISOString()}
15493
+
15494
+ ---
15495
+
15496
+ `;
15497
+ const fullContent = header + content;
15498
+ await fs3.writeFile(filepath, fullContent);
15499
+ const metadata = await readMetadata();
15500
+ metadata.documents[filename] = {
15501
+ url: url2,
15502
+ title,
15503
+ fetchedAt: now.toISOString(),
15504
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
15505
+ size: fullContent.length
15506
+ };
15507
+ await writeMetadata(metadata);
15508
+ return filename;
15509
+ }
15510
+ async function remove(url2) {
15511
+ const filename = urlToFilename(url2);
15512
+ const filepath = path2.join(CACHE_DIR, filename);
15513
+ try {
15514
+ await fs3.unlink(filepath);
15515
+ const metadata = await readMetadata();
15516
+ delete metadata.documents[filename];
15517
+ await writeMetadata(metadata);
15518
+ return true;
15519
+ } catch {
15520
+ return false;
15521
+ }
15522
+ }
15523
+ async function list() {
15524
+ const metadata = await readMetadata();
15525
+ const now = /* @__PURE__ */ new Date();
15526
+ return Object.entries(metadata.documents).map(([filename, entry]) => ({
15527
+ filename,
15528
+ ...entry,
15529
+ expired: new Date(entry.expiresAt) < now
15530
+ }));
15531
+ }
15532
+ async function clear() {
15533
+ const metadata = await readMetadata();
15534
+ const count = Object.keys(metadata.documents).length;
15535
+ for (const filename of Object.keys(metadata.documents)) {
15536
+ const filepath = path2.join(CACHE_DIR, filename);
15537
+ try {
15538
+ await fs3.unlink(filepath);
15539
+ } catch {
15540
+ }
15541
+ }
15542
+ await writeMetadata({ documents: {}, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
15543
+ return count;
15544
+ }
15545
+ async function stats() {
15546
+ const docs = await list();
15547
+ if (docs.length === 0) {
15548
+ return {
15549
+ totalDocuments: 0,
15550
+ totalSize: 0,
15551
+ expiredCount: 0,
15552
+ oldestDocument: null,
15553
+ newestDocument: null
15554
+ };
15555
+ }
15556
+ const sorted = docs.sort(
15557
+ (a, b) => new Date(a.fetchedAt).getTime() - new Date(b.fetchedAt).getTime()
15558
+ );
15559
+ return {
15560
+ totalDocuments: docs.length,
15561
+ totalSize: docs.reduce((sum, d) => sum + d.size, 0),
15562
+ expiredCount: docs.filter((d) => d.expired).length,
15563
+ oldestDocument: sorted[0]?.filename ?? null,
15564
+ newestDocument: sorted[sorted.length - 1]?.filename ?? null
15565
+ };
15566
+ }
15567
+
15568
+ // src/tools/web/webfetch.ts
15569
+ function htmlToMarkdown(html) {
15570
+ return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<h1[^>]*>(.*?)<\/h1>/gi, "# $1\n\n").replace(/<h2[^>]*>(.*?)<\/h2>/gi, "## $1\n\n").replace(/<h3[^>]*>(.*?)<\/h3>/gi, "### $1\n\n").replace(/<h4[^>]*>(.*?)<\/h4>/gi, "#### $1\n\n").replace(/<h5[^>]*>(.*?)<\/h5>/gi, "##### $1\n\n").replace(/<h6[^>]*>(.*?)<\/h6>/gi, "###### $1\n\n").replace(/<p[^>]*>(.*?)<\/p>/gi, "$1\n\n").replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, "```\n$1\n```\n\n").replace(/<code[^>]*>(.*?)<\/code>/gi, "`$1`").replace(/<li[^>]*>(.*?)<\/li>/gi, "- $1\n").replace(/<ul[^>]*>/gi, "\n").replace(/<\/ul>/gi, "\n").replace(/<ol[^>]*>/gi, "\n").replace(/<\/ol>/gi, "\n").replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)").replace(/<strong[^>]*>(.*?)<\/strong>/gi, "**$1**").replace(/<b[^>]*>(.*?)<\/b>/gi, "**$1**").replace(/<em[^>]*>(.*?)<\/em>/gi, "*$1*").replace(/<i[^>]*>(.*?)<\/i>/gi, "*$1*").replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, "> $1\n\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\n{3,}/g, "\n\n").trim();
15571
+ }
15572
+ function extractTitle(html) {
15573
+ const match = html.match(/<title[^>]*>(.*?)<\/title>/i);
15574
+ return match ? match[1].trim() : "Untitled";
15575
+ }
15576
+ function extractMainContent(html) {
15577
+ const patterns = [
15578
+ /<article[^>]*>([\s\S]*?)<\/article>/i,
15579
+ /<main[^>]*>([\s\S]*?)<\/main>/i,
15580
+ /<div[^>]*class="[^"]*content[^"]*"[^>]*>([\s\S]*?)<\/div>/i,
15581
+ /<div[^>]*class="[^"]*article[^"]*"[^>]*>([\s\S]*?)<\/div>/i,
15582
+ /<div[^>]*class="[^"]*post[^"]*"[^>]*>([\s\S]*?)<\/div>/i
15583
+ ];
15584
+ for (const pattern of patterns) {
15585
+ const match = html.match(pattern);
15586
+ if (match) {
15587
+ return match[1];
15588
+ }
15589
+ }
15590
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
15591
+ return bodyMatch ? bodyMatch[1] : html;
15592
+ }
15593
+ var webfetchTool = tool({
15594
+ description: `Fetch content from a URL and convert to markdown.
15595
+
15596
+ <usage>
15597
+ - Fetches web pages and converts HTML to readable markdown
15598
+ - Automatically caches content for future reference
15599
+ - Use for documentation, API references, blog posts
15600
+ </usage>
15601
+
15602
+ <examples>
15603
+ webfetch({ url: "https://nextjs.org/docs/app/building-your-application" })
15604
+ webfetch({ url: "https://react.dev/reference/react/useEffect", cache: true })
15605
+ </examples>`,
15606
+ args: {
15607
+ url: tool.schema.string().describe("URL to fetch"),
15608
+ cache: tool.schema.boolean().optional().describe("Cache the result (default: true)"),
15609
+ selector: tool.schema.string().optional().describe("CSS selector to extract specific content (not implemented yet)")
15610
+ },
15611
+ async execute(args) {
15612
+ const { url: url2, cache = true } = args;
15613
+ if (cache) {
15614
+ const cached2 = await get(url2);
15615
+ if (cached2) {
15616
+ return `\u{1F4DA} **CACHED** (fetched: ${cached2.fetchedAt})
15617
+
15618
+ ${cached2.content}`;
15619
+ }
15620
+ }
15621
+ try {
15622
+ const response = await fetch(url2, {
15623
+ headers: {
15624
+ "User-Agent": "Mozilla/5.0 (compatible; OpenCode-Orchestrator/1.0)",
15625
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
15626
+ },
15627
+ signal: AbortSignal.timeout(3e4)
15628
+ // 30 second timeout
15629
+ });
15630
+ if (!response.ok) {
15631
+ return `\u274C Failed to fetch: HTTP ${response.status} ${response.statusText}`;
15632
+ }
15633
+ const contentType = response.headers.get("content-type") || "";
15634
+ const html = await response.text();
15635
+ if (contentType.includes("application/json")) {
15636
+ const content = JSON.stringify(JSON.parse(html), null, 2);
15637
+ if (cache) {
15638
+ const filename = await set2(url2, content, "JSON Response");
15639
+ return `\u{1F4C4} **JSON fetched** (cached: .cache/docs/${filename})
15640
+
15641
+ \`\`\`json
15642
+ ${content.slice(0, 5e3)}
15643
+ \`\`\``;
15644
+ }
15645
+ return `\u{1F4C4} **JSON fetched**
15646
+
15647
+ \`\`\`json
15648
+ ${content.slice(0, 5e3)}
15649
+ \`\`\``;
15650
+ }
15651
+ if (contentType.includes("text/plain")) {
15652
+ if (cache) {
15653
+ const filename = await set2(url2, html, "Plain Text");
15654
+ return `\u{1F4C4} **Text fetched** (cached: .cache/docs/${filename})
15655
+
15656
+ ${html.slice(0, 1e4)}`;
15657
+ }
15658
+ return `\u{1F4C4} **Text fetched**
15659
+
15660
+ ${html.slice(0, 1e4)}`;
15661
+ }
15662
+ const title = extractTitle(html);
15663
+ const mainContent = extractMainContent(html);
15664
+ const markdown = htmlToMarkdown(mainContent);
15665
+ const truncated = markdown.length > 15e3 ? markdown.slice(0, 15e3) + "\n\n... [Content truncated]" : markdown;
15666
+ if (cache) {
15667
+ const filename = await set2(url2, truncated, title);
15668
+ return `\u{1F4DA} **${title}**
15669
+ Source: ${url2}
15670
+ Cached: .cache/docs/${filename}
15671
+
15672
+ ---
15673
+
15674
+ ${truncated}`;
15675
+ }
15676
+ return `\u{1F4DA} **${title}**
15677
+ Source: ${url2}
15678
+
15679
+ ---
15680
+
15681
+ ${truncated}`;
15682
+ } catch (error45) {
15683
+ if (error45 instanceof Error) {
15684
+ if (error45.name === "TimeoutError") {
15685
+ return `\u274C Request timed out after 30 seconds`;
15686
+ }
15687
+ return `\u274C Fetch error: ${error45.message}`;
15688
+ }
15689
+ return `\u274C Unknown error occurred`;
15690
+ }
15691
+ }
15692
+ });
15693
+
15694
+ // src/tools/web/websearch.ts
15695
+ async function searchSearXNG(query) {
15696
+ const instances = [
15697
+ "https://searxng.site",
15698
+ "https://search.bus-hit.me",
15699
+ "https://paulgo.io"
15700
+ ];
15701
+ for (const instance of instances) {
15702
+ try {
15703
+ const url2 = `${instance}/search?q=${encodeURIComponent(query)}&format=json&engines=google,duckduckgo,bing`;
15704
+ const response = await fetch(url2, {
15705
+ headers: {
15706
+ "User-Agent": "Mozilla/5.0 (compatible; OpenCode/1.0)",
15707
+ "Accept": "application/json"
15708
+ },
15709
+ signal: AbortSignal.timeout(8e3)
15710
+ });
15711
+ if (!response.ok) continue;
15712
+ const data = await response.json();
15713
+ if (data.results && data.results.length > 0) {
15714
+ return data.results.slice(0, 15).map((r) => ({
15715
+ title: r.title || "",
15716
+ url: r.url || "",
15717
+ snippet: r.content || "",
15718
+ source: r.engine || "searxng"
15719
+ }));
15720
+ }
15721
+ } catch {
15722
+ continue;
15723
+ }
15724
+ }
15725
+ return [];
15726
+ }
15727
+ async function searchBrave(query) {
15728
+ const url2 = `https://search.brave.com/api/suggest?q=${encodeURIComponent(query)}`;
15729
+ try {
15730
+ const webUrl = `https://search.brave.com/search?q=${encodeURIComponent(query)}&source=web`;
15731
+ const response = await fetch(webUrl, {
15732
+ headers: {
15733
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
15734
+ "Accept": "text/html"
15735
+ },
15736
+ signal: AbortSignal.timeout(1e4)
15737
+ });
15738
+ if (!response.ok) {
15739
+ throw new Error(`HTTP ${response.status}`);
15740
+ }
15741
+ const html = await response.text();
15742
+ const results = [];
15743
+ const snippetPattern = /<div class="snippet[^"]*"[^>]*>([\s\S]*?)<\/div>/gi;
15744
+ const titlePattern = /<a[^>]*class="[^"]*result-header[^"]*"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
15745
+ let match;
15746
+ const titles = [];
15747
+ const urls = [];
15748
+ const snippets = [];
15749
+ while ((match = titlePattern.exec(html)) !== null) {
15750
+ urls.push(match[1]);
15751
+ titles.push(match[2].replace(/<[^>]+>/g, "").trim());
15752
+ }
15753
+ while ((match = snippetPattern.exec(html)) !== null) {
15754
+ const text = match[1].replace(/<[^>]+>/g, "").trim();
15755
+ if (text.length > 20) {
15756
+ snippets.push(text);
15757
+ }
15758
+ }
15759
+ for (let i = 0; i < Math.min(titles.length, 10); i++) {
15760
+ if (titles[i] && urls[i]) {
15761
+ results.push({
15762
+ title: titles[i],
15763
+ url: urls[i],
15764
+ snippet: snippets[i] || "",
15765
+ source: "brave"
15766
+ });
15767
+ }
15768
+ }
15769
+ return results;
15770
+ } catch (error45) {
15771
+ console.error("Brave search error:", error45);
15772
+ return [];
15773
+ }
15774
+ }
15775
+ async function searchDuckDuckGo(query) {
15776
+ const encodedQuery = encodeURIComponent(query);
15777
+ const url2 = `https://api.duckduckgo.com/?q=${encodedQuery}&format=json&no_html=1&skip_disambig=1`;
15778
+ try {
15779
+ const response = await fetch(url2, {
15780
+ headers: {
15781
+ "User-Agent": "Mozilla/5.0 (compatible; OpenCode-Orchestrator/1.0)"
15782
+ },
15783
+ signal: AbortSignal.timeout(1e4)
15784
+ });
15785
+ if (!response.ok) {
15786
+ throw new Error(`HTTP ${response.status}`);
15787
+ }
15788
+ const data = await response.json();
15789
+ const results = [];
15790
+ if (data.Abstract && data.AbstractURL) {
15791
+ results.push({
15792
+ title: data.Heading || data.AbstractSource || "Main Result",
15793
+ url: data.AbstractURL,
15794
+ snippet: data.Abstract,
15795
+ source: "duckduckgo"
15796
+ });
15797
+ }
15798
+ for (const topic of (data.RelatedTopics || []).slice(0, 8)) {
15799
+ if (topic.Text && topic.FirstURL) {
15800
+ results.push({
15801
+ title: topic.Text.split(" - ")[0] || topic.Text.slice(0, 50),
15802
+ url: topic.FirstURL,
15803
+ snippet: topic.Text,
15804
+ source: "duckduckgo"
15805
+ });
15806
+ }
15807
+ }
15808
+ for (const result of (data.Results || []).slice(0, 5)) {
15809
+ if (result.Text && result.FirstURL) {
15810
+ results.push({
15811
+ title: result.Text.split(" - ")[0] || result.Text.slice(0, 50),
15812
+ url: result.FirstURL,
15813
+ snippet: result.Text,
15814
+ source: "duckduckgo"
15815
+ });
15816
+ }
15817
+ }
15818
+ return results;
15819
+ } catch (error45) {
15820
+ console.error("DuckDuckGo search error:", error45);
15821
+ return [];
15822
+ }
15823
+ }
15824
+ async function searchDuckDuckGoHtml(query) {
15825
+ const encodedQuery = encodeURIComponent(query);
15826
+ const url2 = `https://html.duckduckgo.com/html/?q=${encodedQuery}`;
15827
+ try {
15828
+ const response = await fetch(url2, {
15829
+ headers: {
15830
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
15831
+ },
15832
+ signal: AbortSignal.timeout(1e4)
15833
+ });
15834
+ if (!response.ok) {
15835
+ throw new Error(`HTTP ${response.status}`);
15836
+ }
15837
+ const html = await response.text();
15838
+ const results = [];
15839
+ const resultPattern = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/gi;
15840
+ let match;
15841
+ while ((match = resultPattern.exec(html)) !== null && results.length < 10) {
15842
+ results.push({
15843
+ title: match[2].replace(/<[^>]+>/g, "").trim(),
15844
+ url: decodeURIComponent(match[1].replace(/.*uddg=/, "").split("&")[0] || match[1]),
15845
+ snippet: match[3].replace(/<[^>]+>/g, "").trim(),
15846
+ source: "duckduckgo_html"
15847
+ });
15848
+ }
15849
+ return results;
15850
+ } catch (error45) {
15851
+ console.error("DuckDuckGo HTML search error:", error45);
15852
+ return [];
15853
+ }
15854
+ }
15855
+ var websearchTool = tool({
15856
+ description: `Search the web for information using multiple search providers.
15857
+
15858
+ <usage>
15859
+ - Uses SearXNG (meta-search) > Brave Search > DuckDuckGo
15860
+ - Returns relevant results with URLs
15861
+ - Use for finding documentation, tutorials, solutions
15862
+ </usage>
15863
+
15864
+ <tips>
15865
+ - Add "docs" or "documentation" for official docs
15866
+ - Add "site:github.com" for GitHub results
15867
+ - Add specific version numbers when relevant
15868
+ - Add current year for latest information
15869
+ </tips>
15870
+
15871
+ <examples>
15872
+ websearch({ query: "Next.js 14 app router documentation" })
15873
+ websearch({ query: "React useEffect cleanup best practices 2025" })
15874
+ websearch({ query: "TypeScript generic constraints site:typescriptlang.org" })
15875
+ </examples>`,
15876
+ args: {
15877
+ query: tool.schema.string().describe("Search query"),
15878
+ maxResults: tool.schema.number().optional().describe("Maximum number of results (default: 10)")
15879
+ },
15880
+ async execute(args) {
15881
+ const { query, maxResults = 10 } = args;
15882
+ let results = [];
15883
+ let provider = "";
15884
+ results = await searchSearXNG(query);
15885
+ if (results.length > 0) {
15886
+ provider = "SearXNG";
15887
+ }
15888
+ if (results.length === 0) {
15889
+ results = await searchBrave(query);
15890
+ if (results.length > 0) {
15891
+ provider = "Brave";
15892
+ }
15893
+ }
15894
+ if (results.length === 0) {
15895
+ results = await searchDuckDuckGo(query);
15896
+ if (results.length > 0) {
15897
+ provider = "DuckDuckGo";
15898
+ }
15899
+ }
15900
+ if (results.length === 0) {
15901
+ results = await searchDuckDuckGoHtml(query);
15902
+ if (results.length > 0) {
15903
+ provider = "DuckDuckGo HTML";
15904
+ }
15905
+ }
15906
+ if (results.length === 0) {
15907
+ return `\u{1F50D} No results found for: "${query}"
15908
+
15909
+ Try:
15910
+ - Different keywords
15911
+ - More specific terms
15912
+ - Check spelling
15913
+ - Add "docs" or "official" for documentation`;
15914
+ }
15915
+ const limitedResults = results.slice(0, maxResults);
15916
+ let output = `\u{1F50D} **Web Search Results for: "${query}"**
15917
+
15918
+ `;
15919
+ output += `\u{1F4E1} Provider: ${provider} | Found ${results.length} results (showing ${limitedResults.length})
15920
+
15921
+ ---
15922
+
15923
+ `;
15924
+ for (let i = 0; i < limitedResults.length; i++) {
15925
+ const result = limitedResults[i];
15926
+ output += `### ${i + 1}. ${result.title}
15927
+ `;
15928
+ output += `\u{1F517} ${result.url}
15929
+
15930
+ `;
15931
+ if (result.snippet) {
15932
+ output += `${result.snippet}
15933
+
15934
+ `;
15935
+ }
15936
+ }
15937
+ output += `---
15938
+
15939
+ `;
15940
+ output += `\u{1F4A1} **Tip**: Use \`webfetch\` to get full content from any of these URLs.`;
15941
+ return output;
15942
+ }
15943
+ });
15944
+
15945
+ // src/tools/web/cache-docs.ts
15946
+ var cacheDocsTool = tool({
15947
+ description: `Manage cached documentation.
15948
+
15949
+ <usage>
15950
+ - list: Show all cached documents
15951
+ - get: Retrieve a specific cached document
15952
+ - clear: Clear all cached documents
15953
+ - stats: Show cache statistics
15954
+ </usage>
15955
+
15956
+ <examples>
15957
+ cache_docs({ action: "list" })
15958
+ cache_docs({ action: "get", filename: "nextjs_app-router.md" })
15959
+ cache_docs({ action: "stats" })
15960
+ cache_docs({ action: "clear" })
15961
+ </examples>`,
15962
+ args: {
15963
+ action: tool.schema.enum(["list", "get", "clear", "stats"]).describe("Action to perform"),
15964
+ filename: tool.schema.string().optional().describe("Filename for 'get' action")
15965
+ },
15966
+ async execute(args) {
15967
+ const { action, filename } = args;
15968
+ switch (action) {
15969
+ case "list": {
15970
+ const docs = await list();
15971
+ if (docs.length === 0) {
15972
+ return "\u{1F4DA} **Document Cache**: Empty\n\nNo documents cached yet. Use `webfetch` with `cache: true` to cache documents.";
15973
+ }
15974
+ let output = `\u{1F4DA} **Document Cache** (${docs.length} documents)
15975
+
15976
+ `;
15977
+ for (const doc of docs) {
15978
+ const status = doc.expired ? "\u26A0\uFE0F EXPIRED" : "\u2705";
15979
+ const size = doc.size > 1024 ? `${(doc.size / 1024).toFixed(1)}KB` : `${doc.size}B`;
15980
+ output += `${status} **${doc.filename}** (${size})
15981
+ `;
15982
+ output += ` Source: ${doc.url}
15983
+ `;
15984
+ output += ` Cached: ${new Date(doc.fetchedAt).toLocaleString()}
15985
+
15986
+ `;
15987
+ }
15988
+ return output;
15989
+ }
15990
+ case "get": {
15991
+ if (!filename) {
15992
+ return "\u274C Please specify `filename` to retrieve";
15993
+ }
15994
+ const doc = await getByFilename(filename);
15995
+ if (!doc) {
15996
+ return `\u274C Document not found: ${filename}
15997
+
15998
+ Use \`cache_docs({ action: "list" })\` to see available documents.`;
15999
+ }
16000
+ return `\u{1F4DA} **${doc.title}**
16001
+ Source: ${doc.url}
16002
+ Cached: ${doc.fetchedAt}
16003
+
16004
+ ---
16005
+
16006
+ ${doc.content}`;
16007
+ }
16008
+ case "clear": {
16009
+ const count = await clear();
16010
+ return `\u{1F5D1}\uFE0F Cleared ${count} cached documents`;
16011
+ }
16012
+ case "stats": {
16013
+ const stats2 = await stats();
16014
+ if (stats2.totalDocuments === 0) {
16015
+ return "\u{1F4CA} **Cache Statistics**\n\nCache is empty.";
16016
+ }
16017
+ const sizeStr = stats2.totalSize > 1024 * 1024 ? `${(stats2.totalSize / (1024 * 1024)).toFixed(2)}MB` : stats2.totalSize > 1024 ? `${(stats2.totalSize / 1024).toFixed(1)}KB` : `${stats2.totalSize}B`;
16018
+ return `\u{1F4CA} **Cache Statistics**
16019
+
16020
+ - Total Documents: ${stats2.totalDocuments}
16021
+ - Total Size: ${sizeStr}
16022
+ - Expired: ${stats2.expiredCount}
16023
+ - Oldest: ${stats2.oldestDocument || "N/A"}
16024
+ - Newest: ${stats2.newestDocument || "N/A"}`;
16025
+ }
16026
+ default:
16027
+ return `\u274C Unknown action: ${action}`;
16028
+ }
16029
+ }
16030
+ });
16031
+
16032
+ // src/tools/web/codesearch.ts
16033
+ async function searchGrepApp(query, options) {
16034
+ const params = new URLSearchParams({
16035
+ q: query,
16036
+ ...options.language && { filter: `lang:${options.language}` },
16037
+ ...options.repo && { filter: `repo:${options.repo}` }
16038
+ });
16039
+ const url2 = `https://grep.app/api/search?${params}`;
16040
+ try {
16041
+ const response = await fetch(url2, {
16042
+ headers: {
16043
+ "User-Agent": "Mozilla/5.0 (compatible; OpenCode-Orchestrator/1.0)",
16044
+ "Accept": "application/json"
16045
+ },
16046
+ signal: AbortSignal.timeout(15e3)
16047
+ });
16048
+ if (!response.ok) {
16049
+ return [];
16050
+ }
16051
+ const data = await response.json();
16052
+ const results = [];
16053
+ for (const hit of (data.hits?.hits || []).slice(0, 10)) {
16054
+ const repo = hit.repo?.raw || "";
16055
+ const file2 = hit.path?.raw || "";
16056
+ const line = hit.lineno || 0;
16057
+ const content = hit.content?.snippet?.replace(/<[^>]+>/g, "") || "";
16058
+ if (repo && file2) {
16059
+ results.push({
16060
+ repo,
16061
+ file: file2,
16062
+ line,
16063
+ content: content.slice(0, 200),
16064
+ url: `https://github.com/${repo}/blob/main/${file2}#L${line}`
16065
+ });
16066
+ }
16067
+ }
16068
+ return results;
16069
+ } catch (error45) {
16070
+ console.error("grep.app search error:", error45);
16071
+ return [];
16072
+ }
16073
+ }
16074
+ async function searchGitHub(query, options) {
16075
+ const params = new URLSearchParams({
16076
+ q: query + (options.language ? ` language:${options.language}` : ""),
16077
+ type: "code"
16078
+ });
16079
+ const url2 = `https://github.com/search?${params}`;
16080
+ try {
16081
+ const response = await fetch(url2, {
16082
+ headers: {
16083
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
16084
+ "Accept": "text/html"
16085
+ },
16086
+ signal: AbortSignal.timeout(15e3)
16087
+ });
16088
+ if (!response.ok) {
16089
+ return [];
16090
+ }
16091
+ const html = await response.text();
16092
+ const results = [];
16093
+ const repoPattern = /href="\/([^"]+)\/blob\/([^"]+)"/g;
16094
+ let match;
16095
+ let count = 0;
16096
+ while ((match = repoPattern.exec(html)) !== null && count < 10) {
16097
+ const [, repoPath, filePath] = match;
16098
+ if (repoPath && filePath) {
16099
+ results.push({
16100
+ repo: repoPath,
16101
+ file: filePath,
16102
+ line: 0,
16103
+ content: "(Use webfetch for full content)",
16104
+ url: `https://github.com/${repoPath}/blob/${filePath}`
16105
+ });
16106
+ count++;
16107
+ }
14272
16108
  }
16109
+ return results;
16110
+ } catch (error45) {
16111
+ console.error("GitHub search error:", error45);
16112
+ return [];
14273
16113
  }
14274
- });
16114
+ }
16115
+ var codesearchTool = tool({
16116
+ description: `Search open source code for patterns and examples.
14275
16117
 
14276
- // src/tools/parallel/get-task-result.ts
14277
- var createGetTaskResultTool = (manager) => tool({
14278
- description: `Get result from a completed background task.`,
16118
+ <usage>
16119
+ - Find real-world usage patterns from verified repositories
16120
+ - Discover best practices from popular projects
16121
+ - Verify API usage with actual examples
16122
+ </usage>
16123
+
16124
+ <tips>
16125
+ - Be specific with search queries
16126
+ - Add language filter for better results
16127
+ - Use for verification, not just discovery
16128
+ </tips>
16129
+
16130
+ <examples>
16131
+ codesearch({ query: "useEffect cleanup function", language: "typescript" })
16132
+ codesearch({ query: "prisma middleware logging" })
16133
+ codesearch({ query: "next.js middleware redirect", repo: "vercel/next.js" })
16134
+ </examples>`,
14279
16135
  args: {
14280
- taskId: tool.schema.string().describe("Task ID")
16136
+ query: tool.schema.string().describe("Code pattern to search for"),
16137
+ language: tool.schema.string().optional().describe("Programming language filter"),
16138
+ repo: tool.schema.string().optional().describe("Specific repository (owner/repo)")
14281
16139
  },
14282
16140
  async execute(args) {
14283
- const task = manager.getTask(args.taskId);
14284
- if (!task) return `\u274C Task not found: \`${args.taskId}\``;
14285
- if (task.status === "running") return `\u23F3 Still running...`;
14286
- const result = await manager.getResult(args.taskId);
14287
- const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
14288
- if (task.status === "error" || task.status === "timeout") {
14289
- return `\u274C ${task.status}: ${task.error}`;
16141
+ const { query, language, repo } = args;
16142
+ let results = await searchGrepApp(query, { language, repo });
16143
+ if (results.length === 0) {
16144
+ results = await searchGitHub(query, { language });
14290
16145
  }
14291
- return `\u2705 Completed (${duration3})
16146
+ if (results.length === 0) {
16147
+ return `\u{1F50D} No code results found for: "${query}"
14292
16148
 
14293
- ${result || "(No output)"}`;
14294
- }
14295
- });
16149
+ Try:
16150
+ - Different search terms
16151
+ - Broader language filter
16152
+ - Check spelling`;
16153
+ }
16154
+ let output = `\u{1F50D} **Code Search Results for: "${query}"**
14296
16155
 
14297
- // src/tools/parallel/list-tasks.ts
14298
- var createListTasksTool = (manager) => tool({
14299
- description: `List all background tasks.`,
14300
- args: {
14301
- status: tool.schema.string().optional().describe("Filter: all, running, completed, error")
14302
- },
14303
- async execute(args) {
14304
- const { status = "all" } = args;
14305
- let tasks;
14306
- switch (status) {
14307
- case "running":
14308
- tasks = manager.getRunningTasks();
14309
- break;
14310
- case "completed":
14311
- tasks = manager.getAllTasks().filter((t) => t.status === "completed");
14312
- break;
14313
- case "error":
14314
- tasks = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout");
14315
- break;
14316
- default:
14317
- tasks = manager.getAllTasks();
16156
+ `;
16157
+ output += `Found ${results.length} results${language ? ` (${language})` : ""}
16158
+
16159
+ ---
16160
+
16161
+ `;
16162
+ for (let i = 0; i < results.length; i++) {
16163
+ const r = results[i];
16164
+ output += `### ${i + 1}. ${r.repo}
16165
+ `;
16166
+ output += `\u{1F4C4} \`${r.file}\`${r.line ? `:${r.line}` : ""}
16167
+ `;
16168
+ output += `\u{1F517} [View on GitHub](${r.url})
16169
+
16170
+ `;
16171
+ if (r.content && r.content !== "(Use webfetch for full content)") {
16172
+ output += `\`\`\`
16173
+ ${r.content}
16174
+ \`\`\`
16175
+
16176
+ `;
16177
+ }
14318
16178
  }
14319
- if (tasks.length === 0) return `\u{1F4CB} No tasks found.`;
14320
- const rows = tasks.map((t) => {
14321
- const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
14322
- return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
14323
- }).join("\n");
14324
- return `\u{1F4CB} **Tasks**
16179
+ output += `---
14325
16180
 
14326
- | ID | Status | Agent | Time |
14327
- |----|--------|-------|------|
14328
- ${rows}`;
16181
+ `;
16182
+ output += `\u{1F4A1} **Tip**: Use \`webfetch\` to get the full file content from any of these URLs.`;
16183
+ return output;
14329
16184
  }
14330
16185
  });
14331
16186
 
14332
- // src/tools/parallel/cancel-task.ts
14333
- var createCancelTaskTool = (manager) => tool({
14334
- description: `Cancel a running task.`,
14335
- args: {
14336
- taskId: tool.schema.string().describe("Task ID to cancel")
14337
- },
14338
- async execute(args) {
14339
- const cancelled = await manager.cancelTask(args.taskId);
14340
- if (cancelled) return `\u{1F6D1} Cancelled: \`${args.taskId}\``;
14341
- const task = manager.getTask(args.taskId);
14342
- if (task) return `\u26A0\uFE0F Cannot cancel: Task is ${task.status}`;
14343
- return `\u274C Not found: \`${args.taskId}\``;
16187
+ // src/core/notification/toast-core.ts
16188
+ var toasts = [];
16189
+ var MAX_HISTORY = 50;
16190
+ var handlers = [];
16191
+ function show(options) {
16192
+ const toast = {
16193
+ id: `toast_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
16194
+ title: options.title,
16195
+ message: options.message,
16196
+ variant: options.variant || "info",
16197
+ timestamp: /* @__PURE__ */ new Date(),
16198
+ duration: options.duration ?? 5e3,
16199
+ dismissed: false
16200
+ };
16201
+ toasts.push(toast);
16202
+ if (toasts.length > MAX_HISTORY) {
16203
+ toasts.shift();
14344
16204
  }
14345
- });
16205
+ for (const handler of handlers) {
16206
+ try {
16207
+ handler(toast);
16208
+ } catch (error45) {
16209
+ console.error("[Toast] Handler error:", error45);
16210
+ }
16211
+ }
16212
+ const icons = { info: "\u2139\uFE0F", success: "\u2705", warning: "\u26A0\uFE0F", error: "\u274C" };
16213
+ console.log(`${icons[toast.variant]} [${toast.title}] ${toast.message}`);
16214
+ return toast;
16215
+ }
14346
16216
 
14347
- // src/tools/parallel/index.ts
14348
- function createAsyncAgentTools(manager, client) {
14349
- return {
14350
- delegate_task: createDelegateTaskTool(manager, client),
14351
- get_task_result: createGetTaskResultTool(manager),
14352
- list_tasks: createListTasksTool(manager),
14353
- cancel_task: createCancelTaskTool(manager)
16217
+ // src/core/notification/presets.ts
16218
+ var presets = {
16219
+ taskStarted: (taskId, agent) => show({
16220
+ title: "Task Started",
16221
+ message: `${agent}: ${taskId}`,
16222
+ variant: "info",
16223
+ duration: 3e3
16224
+ }),
16225
+ taskCompleted: (taskId, agent) => show({
16226
+ title: "Task Completed",
16227
+ message: `${agent}: ${taskId}`,
16228
+ variant: "success",
16229
+ duration: 3e3
16230
+ }),
16231
+ taskFailed: (taskId, error45) => show({
16232
+ title: "Task Failed",
16233
+ message: `${taskId}: ${error45}`,
16234
+ variant: "error",
16235
+ duration: 0
16236
+ // Persistent
16237
+ }),
16238
+ allTasksComplete: (count) => show({
16239
+ title: "All Tasks Complete",
16240
+ message: `${count} tasks finished successfully`,
16241
+ variant: "success",
16242
+ duration: 5e3
16243
+ }),
16244
+ missionComplete: (summary) => show({
16245
+ title: "\u{1F389} Mission Complete",
16246
+ message: summary,
16247
+ variant: "success",
16248
+ duration: 0
16249
+ }),
16250
+ documentCached: (filename) => show({
16251
+ title: "Document Cached",
16252
+ message: `.cache/docs/${filename}`,
16253
+ variant: "info",
16254
+ duration: 2e3
16255
+ }),
16256
+ researchStarted: (topic) => show({
16257
+ title: "Research Started",
16258
+ message: topic,
16259
+ variant: "info",
16260
+ duration: 3e3
16261
+ }),
16262
+ warningRateLimited: () => show({
16263
+ title: "Rate Limited",
16264
+ message: "Waiting before retry...",
16265
+ variant: "warning",
16266
+ duration: 5e3
16267
+ }),
16268
+ errorRecovery: (action) => show({
16269
+ title: "Error Recovery",
16270
+ message: `Attempting: ${action}`,
16271
+ variant: "warning",
16272
+ duration: 3e3
16273
+ })
16274
+ };
16275
+
16276
+ // src/core/notification/event-integration.ts
16277
+ function enableAutoToasts() {
16278
+ const unsubscribers = [];
16279
+ unsubscribers.push(EventBus.subscribe(TASK_EVENTS.STARTED, (event) => {
16280
+ const { taskId, agent } = event.properties;
16281
+ presets.taskStarted(taskId, agent);
16282
+ }));
16283
+ unsubscribers.push(EventBus.subscribe(TASK_EVENTS.COMPLETED, (event) => {
16284
+ const { taskId, agent } = event.properties;
16285
+ presets.taskCompleted(taskId, agent);
16286
+ }));
16287
+ unsubscribers.push(EventBus.subscribe(TASK_EVENTS.FAILED, (event) => {
16288
+ const { taskId, error: error45 } = event.properties;
16289
+ presets.taskFailed(taskId, error45);
16290
+ }));
16291
+ unsubscribers.push(EventBus.subscribe(MISSION_EVENTS.ALL_TASKS_COMPLETE, (event) => {
16292
+ const { count } = event.properties;
16293
+ presets.allTasksComplete(count);
16294
+ }));
16295
+ unsubscribers.push(EventBus.subscribe(MISSION_EVENTS.COMPLETE, (event) => {
16296
+ const { summary } = event.properties;
16297
+ presets.missionComplete(summary);
16298
+ }));
16299
+ unsubscribers.push(EventBus.subscribe(DOCUMENT_EVENTS.CACHED, (event) => {
16300
+ const { filename } = event.properties;
16301
+ presets.documentCached(filename);
16302
+ }));
16303
+ return () => {
16304
+ for (const unsub of unsubscribers) {
16305
+ unsub();
16306
+ }
14354
16307
  };
14355
16308
  }
14356
16309
 
14357
- // src/utils/common.ts
14358
- function detectSlashCommand(text) {
14359
- const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
14360
- if (!match) return null;
14361
- return { command: match[1], args: match[2] || "" };
16310
+ // src/core/progress/store.ts
16311
+ var progressHistory = /* @__PURE__ */ new Map();
16312
+ var sessionStartTimes = /* @__PURE__ */ new Map();
16313
+ var MAX_HISTORY2 = 100;
16314
+ function startSession(sessionId) {
16315
+ sessionStartTimes.set(sessionId, /* @__PURE__ */ new Date());
16316
+ progressHistory.set(sessionId, []);
16317
+ }
16318
+ function recordSnapshot(sessionId, data) {
16319
+ const startedAt = sessionStartTimes.get(sessionId) || /* @__PURE__ */ new Date();
16320
+ const now = /* @__PURE__ */ new Date();
16321
+ const snapshot = {
16322
+ sessionId,
16323
+ timestamp: now,
16324
+ todos: {
16325
+ total: data.todoTotal || 0,
16326
+ completed: data.todoCompleted || 0,
16327
+ pending: (data.todoTotal || 0) - (data.todoCompleted || 0),
16328
+ percentage: data.todoTotal ? Math.round((data.todoCompleted || 0) / data.todoTotal * 100) : 0
16329
+ },
16330
+ tasks: {
16331
+ total: data.taskTotal || 0,
16332
+ running: data.taskRunning || 0,
16333
+ completed: data.taskCompleted || 0,
16334
+ failed: data.taskFailed || 0,
16335
+ percentage: data.taskTotal ? Math.round(((data.taskCompleted || 0) + (data.taskFailed || 0)) / data.taskTotal * 100) : 0
16336
+ },
16337
+ steps: {
16338
+ current: data.currentStep || 0,
16339
+ max: data.maxSteps || Infinity
16340
+ },
16341
+ startedAt,
16342
+ elapsedMs: now.getTime() - startedAt.getTime()
16343
+ };
16344
+ const history = progressHistory.get(sessionId) || [];
16345
+ history.push(snapshot);
16346
+ if (history.length > MAX_HISTORY2) {
16347
+ history.shift();
16348
+ }
16349
+ progressHistory.set(sessionId, history);
16350
+ return snapshot;
14362
16351
  }
14363
- function formatTimestamp(date5 = /* @__PURE__ */ new Date()) {
14364
- const pad = (n) => n.toString().padStart(2, "0");
14365
- return `${date5.getFullYear()}-${pad(date5.getMonth() + 1)}-${pad(date5.getDate())} ${pad(date5.getHours())}:${pad(date5.getMinutes())}:${pad(date5.getSeconds())}`;
16352
+ function getLatest(sessionId) {
16353
+ const history = progressHistory.get(sessionId);
16354
+ return history?.[history.length - 1];
14366
16355
  }
14367
- function formatElapsedTime(startMs, endMs = Date.now()) {
14368
- const elapsed = endMs - startMs;
14369
- if (elapsed < 0) return "0s";
14370
- const seconds = Math.floor(elapsed / 1e3) % 60;
14371
- const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
14372
- const hours = Math.floor(elapsed / (1e3 * 60 * 60));
14373
- const parts = [];
14374
- if (hours > 0) parts.push(`${hours}h`);
14375
- if (minutes > 0) parts.push(`${minutes}m`);
14376
- if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
14377
- return parts.join(" ");
16356
+ function clearSession(sessionId) {
16357
+ progressHistory.delete(sessionId);
16358
+ sessionStartTimes.delete(sessionId);
14378
16359
  }
14379
16360
 
14380
- // src/utils/sanity.ts
14381
- function checkOutputSanity(text) {
14382
- if (!text || text.length < 50) {
14383
- return { isHealthy: true, severity: "ok" };
14384
- }
14385
- if (/(.)\1{15,}/.test(text)) {
14386
- return {
14387
- isHealthy: false,
14388
- reason: "Single character repetition detected",
14389
- severity: "critical"
14390
- };
14391
- }
14392
- if (/(.{2,6})\1{8,}/.test(text)) {
14393
- return {
14394
- isHealthy: false,
14395
- reason: "Pattern loop detected",
14396
- severity: "critical"
14397
- };
14398
- }
14399
- if (text.length > 200) {
14400
- const cleanText = text.replace(/\s/g, "");
14401
- if (cleanText.length > 100) {
14402
- const uniqueChars = new Set(cleanText).size;
14403
- const ratio = uniqueChars / cleanText.length;
14404
- if (ratio < 0.02) {
14405
- return {
14406
- isHealthy: false,
14407
- reason: "Low information density",
14408
- severity: "critical"
14409
- };
14410
- }
14411
- }
14412
- }
14413
- const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
14414
- if (boxChars > 100 && boxChars / text.length > 0.3) {
14415
- return {
14416
- isHealthy: false,
14417
- reason: "Visual gibberish detected",
14418
- severity: "critical"
14419
- };
16361
+ // src/core/progress/formatters.ts
16362
+ function formatElapsed(ms) {
16363
+ const seconds = Math.floor(ms / 1e3);
16364
+ const minutes = Math.floor(seconds / 60);
16365
+ const hours = Math.floor(minutes / 60);
16366
+ if (hours > 0) {
16367
+ return `${hours}h ${minutes % 60}m`;
16368
+ } else if (minutes > 0) {
16369
+ return `${minutes}m ${seconds % 60}s`;
16370
+ } else {
16371
+ return `${seconds}s`;
14420
16372
  }
14421
- const lines = text.split("\n").filter((l) => l.trim().length > 10);
14422
- if (lines.length > 10) {
14423
- const lineSet = new Set(lines);
14424
- if (lineSet.size < lines.length * 0.2) {
14425
- return {
14426
- isHealthy: false,
14427
- reason: "Excessive line repetition",
14428
- severity: "warning"
14429
- };
14430
- }
16373
+ }
16374
+ function formatCompact(snapshot) {
16375
+ const parts = [];
16376
+ if (snapshot.todos.total > 0) {
16377
+ parts.push(`\u2705${snapshot.todos.completed}/${snapshot.todos.total}`);
14431
16378
  }
14432
- const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
14433
- if (cjkChars > 200) {
14434
- const uniqueCjk = new Set(
14435
- text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
14436
- ).size;
14437
- if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
14438
- return {
14439
- isHealthy: false,
14440
- reason: "CJK character spam detected",
14441
- severity: "critical"
14442
- };
14443
- }
16379
+ if (snapshot.tasks.running > 0) {
16380
+ parts.push(`\u26A1${snapshot.tasks.running}`);
14444
16381
  }
14445
- return { isHealthy: true, severity: "ok" };
16382
+ parts.push(`\u23F1${formatElapsed(snapshot.elapsedMs)}`);
16383
+ return parts.join(" | ");
14446
16384
  }
14447
- var RECOVERY_PROMPT = `<anomaly_recovery>
14448
- \u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
14449
-
14450
- <recovery_protocol>
14451
- 1. DISCARD the corrupted output completely - do not reference it
14452
- 2. RECALL the original mission objective
14453
- 3. IDENTIFY the last confirmed successful step
14454
- 4. RESTART with a simpler, more focused approach
14455
- </recovery_protocol>
14456
-
14457
- <instructions>
14458
- - If a sub-agent produced bad output: try a different agent or simpler task
14459
- - If stuck in a loop: break down the task into smaller pieces
14460
- - If context seems corrupted: call recorder to restore context
14461
- - THINK in English for maximum stability
14462
- </instructions>
14463
-
14464
- What was the original task? Proceed from the last known good state.
14465
- </anomaly_recovery>`;
14466
- var ESCALATION_PROMPT = `<critical_anomaly>
14467
- \u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
14468
-
14469
- <emergency_protocol>
14470
- 1. STOP current execution path immediately
14471
- 2. DO NOT continue with the same approach - it is failing
14472
- 3. CALL architect for a completely new strategy
14473
- 4. If architect also fails: report status to user and await guidance
14474
- </emergency_protocol>
14475
-
14476
- <diagnosis>
14477
- The current approach is producing corrupted output.
14478
- This may indicate: context overload, model instability, or task complexity.
14479
- </diagnosis>
14480
16385
 
14481
- Request a fresh plan from architect with reduced scope.
14482
- </critical_anomaly>`;
16386
+ // src/core/progress/tracker.ts
16387
+ function formatCompact2(sessionId) {
16388
+ const snapshot = getLatest(sessionId);
16389
+ if (!snapshot) return "...";
16390
+ return formatCompact(snapshot);
16391
+ }
14483
16392
 
14484
16393
  // src/index.ts
14485
- var PLUGIN_VERSION = "0.2.4";
14486
- var DEFAULT_MAX_STEPS = 500;
14487
- var TASK_COMMAND_MAX_STEPS = 1e3;
14488
- var AGENT_EMOJI2 = {
14489
- "architect": "\u{1F3D7}\uFE0F",
14490
- "builder": "\u{1F528}",
14491
- "inspector": "\u{1F50D}",
14492
- "recorder": "\u{1F4BE}",
14493
- "commander": "\u{1F3AF}"
14494
- };
16394
+ var require2 = createRequire(import.meta.url);
16395
+ var { version: PLUGIN_VERSION } = require2("../package.json");
16396
+ var UNLIMITED_MODE = true;
16397
+ var DEFAULT_MAX_STEPS = UNLIMITED_MODE ? Infinity : 500;
14495
16398
  var CONTINUE_INSTRUCTION = `<auto_continue>
14496
16399
  <status>Mission not complete. Keep executing.</status>
14497
16400
 
@@ -14500,17 +16403,30 @@ var CONTINUE_INSTRUCTION = `<auto_continue>
14500
16403
  2. DO NOT wait for user input
14501
16404
  3. If previous action failed, try different approach
14502
16405
  4. If agent returned nothing, proceed to next step
16406
+ 5. Check your todo list - complete ALL pending items
14503
16407
  </rules>
14504
16408
 
14505
16409
  <next_step>
14506
- What is the current state?
14507
- What is the next action?
14508
- Execute it NOW.
16410
+ 1. Check todo list for incomplete items
16411
+ 2. Identify the highest priority pending task
16412
+ 3. Execute it NOW
16413
+ 4. Mark complete when done
16414
+ 5. Continue until ALL todos are complete
14509
16415
  </next_step>
16416
+
16417
+ <completion_criteria>
16418
+ You are ONLY done when:
16419
+ - All todos are marked complete or cancelled
16420
+ - All features are implemented and tested
16421
+ - Final verification passes
16422
+ Then output: \u2705 MISSION COMPLETE
16423
+ </completion_criteria>
14510
16424
  </auto_continue>`;
14511
16425
  var OrchestratorPlugin = async (input) => {
14512
16426
  const { directory, client } = input;
14513
16427
  console.log(`[orchestrator] v${PLUGIN_VERSION} loaded`);
16428
+ const disableAutoToasts = enableAutoToasts();
16429
+ console.log(`[orchestrator] Toast notifications enabled`);
14514
16430
  const sessions = /* @__PURE__ */ new Map();
14515
16431
  const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
14516
16432
  const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
@@ -14519,17 +16435,22 @@ var OrchestratorPlugin = async (input) => {
14519
16435
  // Tools we expose to the LLM
14520
16436
  // -----------------------------------------------------------------
14521
16437
  tool: {
14522
- call_agent: callAgentTool,
14523
- slashcommand: createSlashcommandTool(),
14524
- grep_search: grepSearchTool(directory),
14525
- glob_search: globSearchTool(directory),
14526
- mgrep: mgrepTool(directory),
16438
+ [TOOL_NAMES.CALL_AGENT]: callAgentTool,
16439
+ [TOOL_NAMES.SLASHCOMMAND]: createSlashcommandTool(),
16440
+ [TOOL_NAMES.GREP_SEARCH]: grepSearchTool(directory),
16441
+ [TOOL_NAMES.GLOB_SEARCH]: globSearchTool(directory),
16442
+ [TOOL_NAMES.MGREP]: mgrepTool(directory),
14527
16443
  // Multi-pattern grep (parallel, Rust-powered)
14528
16444
  // Background task tools - run shell commands asynchronously
14529
- run_background: runBackgroundTool,
14530
- check_background: checkBackgroundTool,
14531
- list_background: listBackgroundTool,
14532
- kill_background: killBackgroundTool,
16445
+ [TOOL_NAMES.RUN_BACKGROUND]: runBackgroundTool,
16446
+ [TOOL_NAMES.CHECK_BACKGROUND]: checkBackgroundTool,
16447
+ [TOOL_NAMES.LIST_BACKGROUND]: listBackgroundTool,
16448
+ [TOOL_NAMES.KILL_BACKGROUND]: killBackgroundTool,
16449
+ // Web tools - documentation research and caching
16450
+ [TOOL_NAMES.WEBFETCH]: webfetchTool,
16451
+ [TOOL_NAMES.WEBSEARCH]: websearchTool,
16452
+ [TOOL_NAMES.CACHE_DOCS]: cacheDocsTool,
16453
+ [TOOL_NAMES.CODESEARCH]: codesearchTool,
14533
16454
  // Async agent tools - spawn agents in parallel sessions
14534
16455
  ...asyncAgentTools
14535
16456
  },
@@ -14548,10 +16469,20 @@ var OrchestratorPlugin = async (input) => {
14548
16469
  };
14549
16470
  }
14550
16471
  const orchestratorAgents = {
14551
- Commander: {
14552
- name: "Commander",
16472
+ [AGENT_NAMES.COMMANDER]: {
16473
+ name: AGENT_NAMES.COMMANDER,
14553
16474
  description: "Autonomous orchestrator - executes until mission complete",
14554
16475
  systemPrompt: AGENTS.commander.systemPrompt
16476
+ },
16477
+ [AGENT_NAMES.LIBRARIAN]: {
16478
+ name: AGENT_NAMES.LIBRARIAN,
16479
+ description: "Documentation research specialist - reduces hallucination",
16480
+ systemPrompt: AGENTS.librarian?.systemPrompt || ""
16481
+ },
16482
+ [AGENT_NAMES.RESEARCHER]: {
16483
+ name: AGENT_NAMES.RESEARCHER,
16484
+ description: "Pre-task investigation - gathers all info before implementation",
16485
+ systemPrompt: AGENTS.researcher?.systemPrompt || ""
14555
16486
  }
14556
16487
  };
14557
16488
  config2.command = { ...orchestratorCommands, ...existingCommands };
@@ -14563,13 +16494,13 @@ var OrchestratorPlugin = async (input) => {
14563
16494
  // -----------------------------------------------------------------
14564
16495
  "chat.message": async (msgInput, msgOutput) => {
14565
16496
  const parts = msgOutput.parts;
14566
- const textPartIndex = parts.findIndex((p) => p.type === "text" && p.text);
16497
+ const textPartIndex = parts.findIndex((p) => p.type === PART_TYPES.TEXT && p.text);
14567
16498
  if (textPartIndex === -1) return;
14568
16499
  const originalText = parts[textPartIndex].text || "";
14569
16500
  const parsed = detectSlashCommand(originalText);
14570
16501
  const sessionID = msgInput.sessionID;
14571
16502
  const agentName = (msgInput.agent || "").toLowerCase();
14572
- if (agentName === "commander" && !sessions.has(sessionID)) {
16503
+ if (agentName === AGENT_NAMES.COMMANDER && !sessions.has(sessionID)) {
14573
16504
  const now = Date.now();
14574
16505
  sessions.set(sessionID, {
14575
16506
  active: true,
@@ -14587,44 +16518,19 @@ var OrchestratorPlugin = async (input) => {
14587
16518
  currentTask: "",
14588
16519
  anomalyCount: 0
14589
16520
  });
14590
- if (!parsed) {
14591
- const userMessage = originalText.trim();
14592
- if (userMessage) {
14593
- parts[textPartIndex].text = COMMANDS["task"].template.replace(
14594
- /\$ARGUMENTS/g,
14595
- userMessage
14596
- );
14597
- }
14598
- }
14599
- }
14600
- if (parsed?.command === "task") {
14601
- const now = Date.now();
14602
- sessions.set(sessionID, {
14603
- active: true,
14604
- step: 0,
14605
- maxSteps: TASK_COMMAND_MAX_STEPS,
14606
- timestamp: now,
14607
- startTime: now,
14608
- lastStepTime: now
14609
- });
14610
- state.missionActive = true;
14611
- state.sessions.set(sessionID, {
14612
- enabled: true,
14613
- iterations: 0,
14614
- taskRetries: /* @__PURE__ */ new Map(),
14615
- currentTask: "",
14616
- anomalyCount: 0
16521
+ startSession(sessionID);
16522
+ emit(TASK_EVENTS.STARTED, {
16523
+ taskId: sessionID,
16524
+ agent: AGENT_NAMES.COMMANDER,
16525
+ description: "Mission started"
14617
16526
  });
14618
- parts[textPartIndex].text = COMMANDS["task"].template.replace(
14619
- /\$ARGUMENTS/g,
14620
- parsed.args || "continue previous work"
14621
- );
14622
- } else if (parsed) {
16527
+ }
16528
+ if (parsed) {
14623
16529
  const command = COMMANDS[parsed.command];
14624
16530
  if (command) {
14625
16531
  parts[textPartIndex].text = command.template.replace(
14626
16532
  /\$ARGUMENTS/g,
14627
- parsed.args || "continue"
16533
+ parsed.args || PROMPTS.CONTINUE
14628
16534
  );
14629
16535
  }
14630
16536
  }
@@ -14643,7 +16549,7 @@ var OrchestratorPlugin = async (input) => {
14643
16549
  session.timestamp = now;
14644
16550
  session.lastStepTime = now;
14645
16551
  const stateSession = state.sessions.get(toolInput.sessionID);
14646
- if (toolInput.tool === "call_agent" && stateSession) {
16552
+ if (toolInput.tool === TOOL_NAMES.CALL_AGENT && stateSession) {
14647
16553
  const sanityResult = checkOutputSanity(toolOutput.output);
14648
16554
  if (!sanityResult.isHealthy) {
14649
16555
  stateSession.anomalyCount = (stateSession.anomalyCount || 0) + 1;
@@ -14666,14 +16572,14 @@ Anomaly count: ${stateSession.anomalyCount}
14666
16572
  }
14667
16573
  }
14668
16574
  }
14669
- if (toolInput.tool === "call_agent" && toolInput.arguments?.task && stateSession) {
16575
+ if (toolInput.tool === TOOL_NAMES.CALL_AGENT && toolInput.arguments?.task && stateSession) {
14670
16576
  const taskIdMatch = toolInput.arguments.task.match(/\[(TASK-\d+)\]/i);
14671
16577
  if (taskIdMatch) {
14672
16578
  stateSession.currentTask = taskIdMatch[1].toUpperCase();
14673
- stateSession.graph?.updateTask(stateSession.currentTask, { status: "running" });
16579
+ stateSession.graph?.updateTask(stateSession.currentTask, { status: TASK_STATUS.RUNNING });
14674
16580
  }
14675
16581
  const agentName = toolInput.arguments.agent;
14676
- const emoji3 = AGENT_EMOJI2[agentName] || "\u{1F916}";
16582
+ const emoji3 = AGENT_EMOJI[agentName] || "\u{1F916}";
14677
16583
  toolOutput.output = `${emoji3} [${agentName.toUpperCase()}] Working...
14678
16584
 
14679
16585
  ` + toolOutput.output;
@@ -14683,7 +16589,7 @@ Anomaly count: ${stateSession.anomalyCount}
14683
16589
  state.missionActive = false;
14684
16590
  return;
14685
16591
  }
14686
- if (toolOutput.output.includes("[") && toolOutput.output.includes("{") && toolInput.tool === "call_agent" && stateSession) {
16592
+ if (toolOutput.output.includes("[") && toolOutput.output.includes("{") && toolInput.tool === TOOL_NAMES.CALL_AGENT && stateSession) {
14687
16593
  const jsonMatch = toolOutput.output.match(/```json\n([\s\S]*?)\n```/) || toolOutput.output.match(/\[\s*\{[\s\S]*?\}\s*\]/);
14688
16594
  if (jsonMatch) {
14689
16595
  try {
@@ -14704,7 +16610,7 @@ ${stateSession.graph.getTaskSummary()}`;
14704
16610
  const taskId = stateSession.currentTask;
14705
16611
  if (toolOutput.output.includes("\u2705 PASS") || toolOutput.output.includes("AUDIT RESULT: PASS")) {
14706
16612
  if (taskId) {
14707
- stateSession.graph.updateTask(taskId, { status: "completed" });
16613
+ stateSession.graph.updateTask(taskId, { status: TASK_STATUS.COMPLETED });
14708
16614
  stateSession.taskRetries.clear();
14709
16615
  toolOutput.output += `
14710
16616
 
@@ -14717,7 +16623,7 @@ ${stateSession.graph.getTaskSummary()}`;
14717
16623
  const retries = (stateSession.taskRetries.get(taskId) || 0) + 1;
14718
16624
  stateSession.taskRetries.set(taskId, retries);
14719
16625
  if (retries >= state.maxRetries) {
14720
- stateSession.graph.updateTask(taskId, { status: "failed" });
16626
+ stateSession.graph.updateTask(taskId, { status: TASK_STATUS.FAILED });
14721
16627
  toolOutput.output += `
14722
16628
 
14723
16629
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
@@ -14751,7 +16657,7 @@ ${stateSession.graph.getTaskSummary()}`;
14751
16657
  const session = sessions.get(sessionID);
14752
16658
  if (!session?.active) return;
14753
16659
  const parts = assistantOutput.parts;
14754
- const textContent = parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text || "").join("\n") || "";
16660
+ const textContent = parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text || "").join("\n") || "";
14755
16661
  const stateSession = state.sessions.get(sessionID);
14756
16662
  const sanityResult = checkOutputSanity(textContent);
14757
16663
  if (!sanityResult.isHealthy && stateSession) {
@@ -14765,7 +16671,7 @@ ${stateSession.graph.getTaskSummary()}`;
14765
16671
  path: { id: sessionID },
14766
16672
  body: {
14767
16673
  parts: [{
14768
- type: "text",
16674
+ type: PART_TYPES.TEXT,
14769
16675
  text: `\u26A0\uFE0F ANOMALY #${stateSession.anomalyCount}: ${sanityResult.reason}
14770
16676
 
14771
16677
  ` + recoveryText + `
@@ -14784,16 +16690,26 @@ ${stateSession.graph.getTaskSummary()}`;
14784
16690
  if (stateSession && stateSession.anomalyCount > 0) {
14785
16691
  stateSession.anomalyCount = 0;
14786
16692
  }
14787
- if (textContent.includes("\u2705 MISSION COMPLETE") || textContent.includes("MISSION COMPLETE")) {
16693
+ if (textContent.includes(MISSION.COMPLETE) || textContent.includes(MISSION.COMPLETE_TEXT)) {
14788
16694
  session.active = false;
14789
16695
  state.missionActive = false;
16696
+ emit(MISSION_EVENTS.COMPLETE, {
16697
+ sessionId: sessionID,
16698
+ summary: "Mission completed successfully"
16699
+ });
16700
+ clearSession(sessionID);
14790
16701
  sessions.delete(sessionID);
14791
16702
  state.sessions.delete(sessionID);
14792
16703
  return;
14793
16704
  }
14794
- if (textContent.includes("/stop") || textContent.includes("/cancel")) {
16705
+ if (textContent.includes(MISSION.STOP_COMMAND) || textContent.includes(MISSION.CANCEL_COMMAND)) {
14795
16706
  session.active = false;
14796
16707
  state.missionActive = false;
16708
+ emit(TASK_EVENTS.FAILED, {
16709
+ taskId: sessionID,
16710
+ error: "Cancelled by user"
16711
+ });
16712
+ clearSession(sessionID);
14797
16713
  sessions.delete(sessionID);
14798
16714
  state.sessions.delete(sessionID);
14799
16715
  return;
@@ -14810,16 +16726,21 @@ ${stateSession.graph.getTaskSummary()}`;
14810
16726
  state.missionActive = false;
14811
16727
  return;
14812
16728
  }
16729
+ recordSnapshot(sessionID, {
16730
+ currentStep: session.step,
16731
+ maxSteps: session.maxSteps
16732
+ });
16733
+ const progressInfo = formatCompact2(sessionID);
14813
16734
  try {
14814
16735
  if (client?.session?.prompt) {
14815
16736
  await client.session.prompt({
14816
16737
  path: { id: sessionID },
14817
16738
  body: {
14818
16739
  parts: [{
14819
- type: "text",
16740
+ type: PART_TYPES.TEXT,
14820
16741
  text: CONTINUE_INSTRUCTION + `
14821
16742
 
14822
- \u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`
16743
+ \u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | ${progressInfo} | This step: ${stepDuration} | Total: ${totalElapsed}`
14823
16744
  }]
14824
16745
  }
14825
16746
  });
@@ -14830,7 +16751,7 @@ ${stateSession.graph.getTaskSummary()}`;
14830
16751
  if (client?.session?.prompt) {
14831
16752
  await client.session.prompt({
14832
16753
  path: { id: sessionID },
14833
- body: { parts: [{ type: "text", text: "continue" }] }
16754
+ body: { parts: [{ type: PART_TYPES.TEXT, text: PROMPTS.CONTINUE }] }
14834
16755
  });
14835
16756
  }
14836
16757
  } catch {
@@ -14841,18 +16762,22 @@ ${stateSession.graph.getTaskSummary()}`;
14841
16762
  },
14842
16763
  // -----------------------------------------------------------------
14843
16764
  // Event handler - cleans up when sessions are deleted
16765
+ // Uses 'event' hook (not 'handler') to match oh-my-opencode pattern
14844
16766
  // -----------------------------------------------------------------
14845
- handler: async ({ event }) => {
16767
+ event: async (input2) => {
16768
+ const { event } = input2;
14846
16769
  try {
14847
16770
  const manager = ParallelAgentManager.getInstance();
14848
16771
  manager.handleEvent(event);
14849
16772
  } catch {
14850
16773
  }
14851
- if (event.type === "session.deleted") {
16774
+ if (event.type === SESSION_EVENTS.DELETED) {
14852
16775
  const props = event.properties;
14853
16776
  if (props?.info?.id) {
14854
- sessions.delete(props.info.id);
14855
- state.sessions.delete(props.info.id);
16777
+ const sessionId = props.info.id;
16778
+ sessions.delete(sessionId);
16779
+ state.sessions.delete(sessionId);
16780
+ clearSession(sessionId);
14856
16781
  }
14857
16782
  }
14858
16783
  }