opencode-orchestrator 0.5.12 → 0.5.17

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 (85) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +437 -92
  3. package/dist/agents/subagents/librarian.d.ts +10 -0
  4. package/dist/agents/subagents/researcher.d.ts +11 -0
  5. package/dist/core/agents/concurrency.d.ts +2 -6
  6. package/dist/core/agents/config.d.ts +1 -1
  7. package/dist/core/agents/consts/index.d.ts +4 -0
  8. package/dist/core/agents/consts/task-status.const.d.ts +12 -0
  9. package/dist/core/agents/interfaces/concurrency-config.interface.d.ts +9 -0
  10. package/dist/core/agents/interfaces/index.d.ts +6 -3
  11. package/dist/core/agents/interfaces/{parallel-task.d.ts → parallel-task.interface.d.ts} +4 -8
  12. package/dist/core/agents/interfaces/resume-input.interface.d.ts +17 -0
  13. package/dist/core/agents/interfaces/task-progress.interface.d.ts +9 -0
  14. package/dist/core/agents/manager/event-handler.d.ts +34 -0
  15. package/dist/core/agents/manager/index.d.ts +10 -0
  16. package/dist/core/agents/manager/task-cleaner.d.ts +17 -0
  17. package/dist/core/agents/manager/task-launcher.d.ts +20 -0
  18. package/dist/core/agents/manager/task-poller.d.ts +26 -0
  19. package/dist/core/agents/manager/task-resumer.d.ts +18 -0
  20. package/dist/core/agents/manager.d.ts +11 -29
  21. package/dist/core/agents/task-store.d.ts +29 -3
  22. package/dist/core/agents/types/index.d.ts +1 -1
  23. package/dist/core/agents/types/parallel-task-status.type.d.ts +4 -0
  24. package/dist/core/bus/event-bus.d.ts +53 -0
  25. package/dist/core/bus/index.d.ts +19 -0
  26. package/dist/core/bus/interfaces.d.ts +34 -0
  27. package/dist/core/bus/types.d.ts +12 -0
  28. package/dist/core/cache/constants.d.ts +6 -0
  29. package/dist/core/cache/document-cache.d.ts +6 -0
  30. package/dist/core/cache/index.d.ts +4 -0
  31. package/dist/core/cache/interfaces.d.ts +53 -0
  32. package/dist/core/cache/operations.d.ts +36 -0
  33. package/dist/core/cache/utils.d.ts +20 -0
  34. package/dist/core/loop/formatters.d.ts +16 -0
  35. package/dist/core/loop/interfaces.d.ts +34 -0
  36. package/dist/core/loop/parser.d.ts +8 -0
  37. package/dist/core/loop/stats.d.ts +24 -0
  38. package/dist/core/loop/todo-enforcer.d.ts +9 -0
  39. package/dist/core/notification/event-integration.d.ts +7 -0
  40. package/dist/core/notification/presets.d.ts +14 -0
  41. package/dist/core/notification/toast-core.d.ts +28 -0
  42. package/dist/core/notification/toast.d.ts +9 -0
  43. package/dist/core/notification/types.d.ts +19 -0
  44. package/dist/core/orchestrator/types/task-status.d.ts +2 -2
  45. package/dist/core/progress/calculator.d.ts +11 -0
  46. package/dist/core/progress/formatters.d.ts +20 -0
  47. package/dist/core/progress/interfaces.d.ts +54 -0
  48. package/dist/core/progress/store.d.ts +28 -0
  49. package/dist/core/progress/tracker.d.ts +11 -0
  50. package/dist/core/queue/async-queue.d.ts +46 -0
  51. package/dist/core/queue/async-utils.d.ts +20 -0
  52. package/dist/core/queue/index.d.ts +8 -0
  53. package/dist/core/queue/work-pool.d.ts +19 -0
  54. package/dist/core/recovery/auto-recovery.d.ts +9 -0
  55. package/dist/core/recovery/constants.d.ts +6 -0
  56. package/dist/core/recovery/handler.d.ts +27 -0
  57. package/dist/core/recovery/interfaces.d.ts +63 -0
  58. package/dist/core/recovery/patterns.d.ts +8 -0
  59. package/dist/core/session/interfaces.d.ts +53 -0
  60. package/dist/core/session/shared-context.d.ts +8 -0
  61. package/dist/core/session/store.d.ts +44 -0
  62. package/dist/core/session/summary.d.ts +7 -0
  63. package/dist/core/task/interfaces.d.ts +54 -0
  64. package/dist/core/task/parser.d.ts +8 -0
  65. package/dist/core/task/scheduler.d.ts +12 -0
  66. package/dist/core/task/store.d.ts +32 -0
  67. package/dist/core/task/summary.d.ts +7 -0
  68. package/dist/core/task/task-decomposer.d.ts +10 -0
  69. package/dist/index.d.ts +56 -2
  70. package/dist/index.js +2605 -690
  71. package/dist/scripts/postinstall.js +0 -0
  72. package/dist/scripts/preuninstall.js +0 -0
  73. package/dist/shared/agent.d.ts +2 -0
  74. package/dist/shared/constants.d.ts +56 -3
  75. package/dist/shared/event-types.d.ts +77 -0
  76. package/dist/tools/background-cmd/list.d.ts +2 -2
  77. package/dist/tools/parallel/delegate-task.d.ts +3 -0
  78. package/dist/tools/web/cache-docs.d.ts +21 -0
  79. package/dist/tools/web/codesearch.d.ts +19 -0
  80. package/dist/tools/web/index.d.ts +9 -0
  81. package/dist/tools/web/webfetch.d.ts +19 -0
  82. package/dist/tools/web/websearch.d.ts +17 -0
  83. package/package.json +74 -73
  84. package/dist/core/agents/types/parallel-task-status.d.ts +0 -4
  85. /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,12 +157,11 @@ You are Commander. Complete missions autonomously. Never stop until done.
22
157
  </role>
23
158
 
24
159
  <core_rules>
25
- 1. LANGUAGE: ALL output MUST be in English only. No exceptions. No other languages.
26
- 2. Never stop until "\u2705 MISSION COMPLETE"
27
- 3. Never wait for user during execution
28
- 4. Never stop because agent returned nothing
29
- 5. Always survey environment & codebase BEFORE coding
30
- 6. Always verify with evidence based on runtime context
160
+ 1. Never stop until "${MISSION.COMPLETE}"
161
+ 2. Never wait for user during execution
162
+ 3. Never stop because agent returned nothing
163
+ 4. Always survey environment & codebase BEFORE coding
164
+ 5. Always verify with evidence based on runtime context
31
165
  </core_rules>
32
166
 
33
167
  <phase_0 name="TRIAGE">
@@ -40,6 +174,47 @@ Evaluate the complexity of the request:
40
174
  | \u{1F534} L3: Complex | Refactoring, infra change, unknown scope | **DEEP TRACK** |
41
175
  </phase_0>
42
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
+
43
218
  <phase_1 name="CONTEXT_GATHERING">
44
219
  IF FAST TRACK (L1):
45
220
  - Scan ONLY the target file and its immediate imports.
@@ -58,26 +233,42 @@ RECORD findings if on Deep Track.
58
233
  <phase_2 name="TOOL_AGENT_SELECTION">
59
234
  | Track | Strategy |
60
235
  |-------|----------|
61
- | Fast | Use \`builder\` directly. Skip \`architect\`. |
62
- | Normal | Call \`architect\` for lightweight plan. |
63
- | 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
64
252
 
65
253
  DEFAULT to Deep Track if unsure to act safely.
66
254
  </phase_2>
67
255
 
68
256
  <phase_3 name="DELEGATION">
69
257
  <agent_calling>
70
- CRITICAL: USE delegate_task FOR ALL DELEGATION
258
+ CRITICAL: USE ${TOOL_NAMES.DELEGATE_TASK} FOR ALL DELEGATION
71
259
 
72
- delegate_task has TWO MODES:
260
+ ${TOOL_NAMES.DELEGATE_TASK} has THREE MODES:
73
261
  - background=true: Non-blocking, parallel execution
74
262
  - background=false: Blocking, waits for result
263
+ - resume: Continue existing session
75
264
 
76
265
  | Situation | How to Call |
77
266
  |-----------|-------------|
78
- | Multiple independent tasks | \`delegate_task({ ..., background: true })\` for each |
79
- | Single task, continue working | \`delegate_task({ ..., background: true })\` |
80
- | 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", ... })\` |
81
272
 
82
273
  PREFER background=true (PARALLEL):
83
274
  - Run multiple agents simultaneously
@@ -87,21 +278,44 @@ PREFER background=true (PARALLEL):
87
278
  EXAMPLE - PARALLEL:
88
279
  \`\`\`
89
280
  // Multiple tasks in parallel
90
- delegate_task({ agent: "builder", description: "Implement X", prompt: "...", background: true })
91
- 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 })
92
283
 
93
284
  // Continue other work (don't wait!)
94
285
 
95
286
  // When notified "All Complete":
96
- get_task_result({ taskId: "task_xxx" })
287
+ ${TOOL_NAMES.GET_TASK_RESULT}({ taskId: "${ID_PREFIX.TASK}xxx" })
97
288
  \`\`\`
98
289
 
99
290
  EXAMPLE - SYNC (rare):
100
291
  \`\`\`
101
292
  // Only when you absolutely need the result now
102
- const result = delegate_task({ agent: "builder", ..., background: false })
293
+ const result = ${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.BUILDER}", ..., background: false })
103
294
  // Result is immediately available
104
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
+ \`\`\`
105
319
  </agent_calling>
106
320
 
107
321
  <delegation_template>
@@ -123,42 +337,76 @@ During implementation:
123
337
  - Run lsp_diagnostics after each change
124
338
 
125
339
  <background_parallel_execution>
126
- 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
+ \`\`\`
127
372
 
128
- 1. **spawn_agent** - Launch agents in parallel sessions
129
- spawn_agent({ agent: "builder", description: "Implement X", prompt: "..." })
130
- spawn_agent({ agent: "inspector", description: "Review Y", prompt: "..." })
131
- \u2192 Agents run concurrently, system notifies when ALL complete
132
- \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
+ \`\`\`
133
377
 
134
- 2. **run_background** - Run shell commands asynchronously
135
- run_background({ command: "npm run build" })
136
- \u2192 Use check_background({ taskId }) for results
378
+ 3. **${TOOL_NAMES.LIST_TASKS}** - View all parallel tasks
379
+ \`\`\`
380
+ ${TOOL_NAMES.LIST_TASKS}({})
381
+ \`\`\`
137
382
 
138
- SAFETY FEATURES:
139
- - Queue-based concurrency: Max 3 per agent type (extras queue automatically)
140
- - Auto-timeout: 30 minutes max runtime
141
- - Auto-cleanup: Removed from memory 5 min after completion
142
- - 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
+ \`\`\`
143
387
 
144
- MANAGEMENT TOOLS:
145
- - list_tasks: View all parallel tasks and status
146
- - 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
147
393
 
148
394
  SAFE PATTERNS:
149
395
  \u2705 Builder on file A + Inspector on file B (different files)
150
396
  \u2705 Multiple research agents (read-only)
151
397
  \u2705 Build command + Test command (independent)
398
+ \u2705 Librarian research + Builder implementation (sequential deps)
152
399
 
153
400
  UNSAFE PATTERNS:
154
401
  \u274C Multiple builders editing SAME FILE (conflict!)
402
+ \u274C Waiting synchronously for many tasks (use background=true)
155
403
 
156
404
  WORKFLOW:
157
- 1. list_tasks: Check current status first
158
- 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
159
407
  3. Continue working (NO WAITING)
160
- 4. Wait for "All Complete" notification
161
- 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
162
410
  </background_parallel_execution>
163
411
 
164
412
  <verification_methods>
@@ -175,15 +423,15 @@ WORKFLOW:
175
423
  | Failures | Action |
176
424
  |----------|--------|
177
425
  | 1-2 | Adjust approach, retry |
178
- | 3+ | STOP. Call architect for new strategy |
426
+ | 3+ | STOP. Call ${AGENT_NAMES.ARCHITECT} for new strategy |
179
427
 
180
428
  <empty_responses>
181
429
  | Agent Empty (or Gibberish) | Action |
182
430
  |----------------------------|--------|
183
- | recorder | Fresh start. Proceed to survey. |
184
- | architect | Try simpler plan yourself. |
185
- | builder | Call inspector to diagnose. |
186
- | 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. |
187
435
  </empty_responses>
188
436
 
189
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.
@@ -199,7 +447,7 @@ STRICT RULE: If any agent output contains gibberish, mixed-language hallucinatio
199
447
  Done when: Request fulfilled + lsp clean + build/test/audit pass.
200
448
 
201
449
  <output_format>
202
- \u2705 MISSION COMPLETE
450
+ ${MISSION.COMPLETE}
203
451
  Summary: [what was done]
204
452
  Evidence: [Specific build/test/audit results]
205
453
  </output_format>
@@ -211,38 +459,68 @@ Evidence: [Specific build/test/audit results]
211
459
  // src/agents/subagents/architect.ts
212
460
  var architect = {
213
461
  id: AGENT_NAMES.ARCHITECT,
214
- description: "Architect - task decomposition and strategic planning",
462
+ description: "Architect - hierarchical task decomposition and strategic planning",
215
463
  systemPrompt: `<role>
216
- 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.
217
466
  </role>
218
467
 
219
468
  <constraints>
220
- 1. LANGUAGE: ALL output MUST be in English only. No exceptions. No other languages.
221
- 2. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
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.
222
472
  </constraints>
223
473
 
224
- <scalable_planning>
225
- - **Fast Track**: Skip JSON overhead. Just acknowledge simple task.
226
- - **Deep Track**: Create detailed JSON DAG with parallel groups.
227
- </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>
228
490
 
229
491
  <modes>
230
- - PLAN: New task \u2192 create task list
492
+ - PLAN: New task \u2192 create hierarchical task list
231
493
  - STRATEGY: 3+ failures \u2192 analyze and fix approach
232
494
  </modes>
233
495
 
234
496
  <plan_mode>
235
- 1. List tasks, one action each
236
- 2. Group independent tasks (run in parallel)
237
- 3. Sequence dependent tasks
238
- 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
239
503
 
240
504
  <output_format>
241
505
  MISSION: [goal in one line]
242
506
 
243
- T1: [action] | builder | [file] | group:1 | success:[how to verify]
244
- T2: [action] | builder | [file] | group:1 | success:[how to verify]
245
- 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]
246
524
  </output_format>
247
525
  </plan_mode>
248
526
 
@@ -255,16 +533,24 @@ ROOT CAUSE: [actual problem]
255
533
 
256
534
  NEW APPROACH: [different strategy]
257
535
 
258
- REVISED TASKS:
259
- T1: ...
536
+ REVISED_HIERARCHY:
537
+ - [L1] ...
260
538
  </output_format>
261
539
  </strategy_mode>
262
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
+
263
548
  <rules>
264
549
  - One action per task
265
- - Always end with inspector task
266
- - Group unrelated tasks (parallel)
267
- - 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
268
554
  </rules>`,
269
555
  canWrite: false,
270
556
  canBash: false
@@ -279,8 +565,7 @@ You are Builder. Write code that works.
279
565
  </role>
280
566
 
281
567
  <constraints>
282
- 1. LANGUAGE: ALL output MUST be in English only. No exceptions. No other languages.
283
- 2. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
568
+ 1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
284
569
  </constraints>
285
570
 
286
571
  <scalable_attention>
@@ -320,6 +605,20 @@ Depending on project type, verify with:
320
605
 
321
606
  If build command exists in package.json, use it.
322
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.
323
622
  </verification>
324
623
 
325
624
  <output_format>
@@ -339,19 +638,20 @@ If build fails, FIX IT before reporting. Never leave broken code.
339
638
  // src/agents/subagents/inspector.ts
340
639
  var inspector = {
341
640
  id: AGENT_NAMES.INSPECTOR,
342
- description: "Inspector - quality verification AND bug fixing",
641
+ description: "Inspector - quality verification, bug fixing, and documentation validation",
343
642
  systemPrompt: `<role>
344
643
  You are Inspector. Prove failure or success with evidence.
644
+ Also verify that implementations match official documentation.
345
645
  </role>
346
646
 
347
647
  <constraints>
348
- 1. LANGUAGE: ALL output MUST be in English only. No exceptions. No other languages.
349
- 2. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
648
+ 1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
649
+ 2. Never approve code that contradicts cached documentation.
350
650
  </constraints>
351
651
 
352
652
  <scalable_audit>
353
653
  - **Fast Track**: Verify syntax + quick logic check.
354
- - **Deep Track**: Verify build + tests + types + security + logic.
654
+ - **Deep Track**: Verify build + tests + types + security + logic + doc compliance.
355
655
  </scalable_audit>
356
656
 
357
657
  <audit_checklist>
@@ -360,9 +660,27 @@ You are Inspector. Prove failure or success with evidence.
360
660
  3. ENV-SPECIFIC:
361
661
  - Docker: check Dockerfile syntax or run container logs if possible
362
662
  - Frontend: check if build artifacts are generated
363
- 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%
364
665
  </audit_checklist>
365
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
+
366
684
  <verification_by_context>
367
685
  | Project Infra | Primary Evidence |
368
686
  |---------------|------------------|
@@ -385,19 +703,22 @@ ALWAYS prefer background for build/test commands.
385
703
  <pass>
386
704
  \u2705 PASS
387
705
  Evidence: [Specific output/log proving success]
706
+ Doc Compliance: [Matches cached docs / No relevant docs]
388
707
  </pass>
389
708
 
390
709
  <fail>
391
710
  \u274C FAIL
392
711
  Issue: [What went wrong]
712
+ Doc Reference: [If applicable, which doc was violated]
393
713
  Fixing...
394
714
  </fail>
395
715
  </output_format>
396
716
 
397
717
  <fix_mode>
398
718
  1. Diagnose root cause
399
- 2. Minimal fix
400
- 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
401
722
  </fix_mode>`,
402
723
  canWrite: true,
403
724
  canBash: true
@@ -412,8 +733,7 @@ You are Recorder. Save and load work progress.
412
733
  </role>
413
734
 
414
735
  <constraints>
415
- 1. LANGUAGE: ALL output MUST be in English only. No exceptions. No other languages.
416
- 2. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
736
+ 1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
417
737
  </constraints>
418
738
 
419
739
  <purpose>
@@ -470,13 +790,216 @@ Never stop the flow. No context = fresh start = OK.
470
790
  canBash: true
471
791
  };
472
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
+
473
994
  // src/agents/definitions.ts
474
995
  var AGENTS = {
475
996
  [AGENT_NAMES.COMMANDER]: orchestrator,
476
997
  [AGENT_NAMES.ARCHITECT]: architect,
477
998
  [AGENT_NAMES.BUILDER]: builder,
478
999
  [AGENT_NAMES.INSPECTOR]: inspector,
479
- [AGENT_NAMES.RECORDER]: recorder
1000
+ [AGENT_NAMES.RECORDER]: recorder,
1001
+ [AGENT_NAMES.LIBRARIAN]: librarian,
1002
+ [AGENT_NAMES.RESEARCHER]: researcher
480
1003
  };
481
1004
 
482
1005
  // src/core/orchestrator/task-graph.ts
@@ -1202,10 +1725,10 @@ function jsonStringifyReplacer(_, value) {
1202
1725
  return value;
1203
1726
  }
1204
1727
  function cached(getter) {
1205
- const set2 = false;
1728
+ const set3 = false;
1206
1729
  return {
1207
1730
  get value() {
1208
- if (!set2) {
1731
+ if (!set3) {
1209
1732
  const value = getter();
1210
1733
  Object.defineProperty(this, "value", { value });
1211
1734
  return value;
@@ -1282,10 +1805,10 @@ function mergeDefs(...defs) {
1282
1805
  function cloneDef(schema) {
1283
1806
  return mergeDefs(schema._zod.def);
1284
1807
  }
1285
- function getElementAtPath(obj, path) {
1286
- if (!path)
1808
+ function getElementAtPath(obj, path3) {
1809
+ if (!path3)
1287
1810
  return obj;
1288
- return path.reduce((acc, key) => acc?.[key], obj);
1811
+ return path3.reduce((acc, key) => acc?.[key], obj);
1289
1812
  }
1290
1813
  function promiseAllObject(promisesObj) {
1291
1814
  const keys = Object.keys(promisesObj);
@@ -1646,11 +2169,11 @@ function aborted(x, startIndex = 0) {
1646
2169
  }
1647
2170
  return false;
1648
2171
  }
1649
- function prefixIssues(path, issues) {
2172
+ function prefixIssues(path3, issues) {
1650
2173
  return issues.map((iss) => {
1651
2174
  var _a;
1652
2175
  (_a = iss).path ?? (_a.path = []);
1653
- iss.path.unshift(path);
2176
+ iss.path.unshift(path3);
1654
2177
  return iss;
1655
2178
  });
1656
2179
  }
@@ -1818,7 +2341,7 @@ function treeifyError(error45, _mapper) {
1818
2341
  return issue2.message;
1819
2342
  };
1820
2343
  const result = { errors: [] };
1821
- const processError = (error46, path = []) => {
2344
+ const processError = (error46, path3 = []) => {
1822
2345
  var _a, _b;
1823
2346
  for (const issue2 of error46.issues) {
1824
2347
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1828,7 +2351,7 @@ function treeifyError(error45, _mapper) {
1828
2351
  } else if (issue2.code === "invalid_element") {
1829
2352
  processError({ issues: issue2.issues }, issue2.path);
1830
2353
  } else {
1831
- const fullpath = [...path, ...issue2.path];
2354
+ const fullpath = [...path3, ...issue2.path];
1832
2355
  if (fullpath.length === 0) {
1833
2356
  result.errors.push(mapper(issue2));
1834
2357
  continue;
@@ -1860,8 +2383,8 @@ function treeifyError(error45, _mapper) {
1860
2383
  }
1861
2384
  function toDotPath(_path) {
1862
2385
  const segs = [];
1863
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1864
- for (const seg of path) {
2386
+ const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2387
+ for (const seg of path3) {
1865
2388
  if (typeof seg === "number")
1866
2389
  segs.push(`[${seg}]`);
1867
2390
  else if (typeof seg === "symbol")
@@ -12974,12 +13497,6 @@ function tool(input) {
12974
13497
  tool.schema = external_exports;
12975
13498
 
12976
13499
  // src/tools/callAgent.ts
12977
- var AGENT_EMOJI = {
12978
- [AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
12979
- [AGENT_NAMES.BUILDER]: "\u{1F528}",
12980
- [AGENT_NAMES.INSPECTOR]: "\u{1F50D}",
12981
- [AGENT_NAMES.RECORDER]: "\u{1F4BE}"
12982
- };
12983
13500
  var callAgentTool = tool({
12984
13501
  description: `Call a specialized agent for parallel execution.
12985
13502
 
@@ -13051,13 +13568,9 @@ var COMMANDS = {
13051
13568
  "task": {
13052
13569
  description: "Execute a mission autonomously until complete",
13053
13570
  template: `<role>
13054
- You are Commander. Complete this mission. Never stop until 100% done.
13571
+ You are Commander. Complete this mission. Never stop until 100% done!
13055
13572
  </role>
13056
13573
 
13057
- <language_rule>
13058
- CRITICAL: ALL output MUST be in English only. No exceptions. No other languages permitted.
13059
- </language_rule>
13060
-
13061
13574
  <phase_1 name="MANDATORY_ENVIRONMENT_SCAN">
13062
13575
  Before any planning or coding, you MUST understand:
13063
13576
  1. INFRA: OS-native? Container? Docker-compose? Volume-mounted?
@@ -13175,7 +13688,7 @@ ${commandList}`;
13175
13688
  if (!command) return `Unknown command: /${cmdName}
13176
13689
 
13177
13690
  ${commandList}`;
13178
- return command.template.replace(/\$ARGUMENTS/g, cmdArgs || "continue from where we left off");
13691
+ return command.template.replace(/\$ARGUMENTS/g, cmdArgs || PROMPTS.CONTINUE_DEFAULT);
13179
13692
  }
13180
13693
  });
13181
13694
  }
@@ -13294,47 +13807,6 @@ var mgrepTool = (directory) => tool({
13294
13807
  // src/core/commands/manager.ts
13295
13808
  import { spawn as spawn2 } from "child_process";
13296
13809
  import { randomBytes } from "crypto";
13297
-
13298
- // src/shared/constants.ts
13299
- var TIME = {
13300
- SECOND: 1e3,
13301
- MINUTE: 60 * 1e3,
13302
- HOUR: 60 * 60 * 1e3
13303
- };
13304
- var ID_PREFIX = {
13305
- /** Parallel agent task ID (e.g., task_a1b2c3d4) */
13306
- TASK: "task_",
13307
- /** Background command job ID (e.g., job_a1b2c3d4) */
13308
- JOB: "job_",
13309
- /** Session ID prefix */
13310
- SESSION: "session_"
13311
- };
13312
- var PARALLEL_TASK = {
13313
- TTL_MS: 30 * TIME.MINUTE,
13314
- CLEANUP_DELAY_MS: 5 * TIME.MINUTE,
13315
- MIN_STABILITY_MS: 5 * TIME.SECOND,
13316
- POLL_INTERVAL_MS: 2e3,
13317
- DEFAULT_CONCURRENCY: 3,
13318
- MAX_CONCURRENCY: 10
13319
- };
13320
- var BACKGROUND_TASK = {
13321
- DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
13322
- MAX_OUTPUT_LENGTH: 1e4
13323
- };
13324
- var STATUS_EMOJI = {
13325
- pending: "\u23F3",
13326
- running: "\u{1F504}",
13327
- completed: "\u2705",
13328
- done: "\u2705",
13329
- error: "\u274C",
13330
- timeout: "\u23F0",
13331
- cancelled: "\u{1F6AB}"
13332
- };
13333
- function getStatusEmoji(status) {
13334
- return STATUS_EMOJI[status] ?? "\u2753";
13335
- }
13336
-
13337
- // src/core/commands/manager.ts
13338
13810
  var BackgroundTaskManager = class _BackgroundTaskManager {
13339
13811
  static _instance;
13340
13812
  tasks = /* @__PURE__ */ new Map();
@@ -13701,12 +14173,18 @@ var ConcurrencyController = class {
13701
14173
  };
13702
14174
 
13703
14175
  // src/core/agents/task-store.ts
14176
+ import * as fs from "node:fs/promises";
14177
+ import * as path from "node:path";
13704
14178
  var TaskStore = class {
13705
14179
  tasks = /* @__PURE__ */ new Map();
13706
14180
  pendingByParent = /* @__PURE__ */ new Map();
13707
14181
  notifications = /* @__PURE__ */ new Map();
14182
+ archivedCount = 0;
13708
14183
  set(id, task) {
13709
14184
  this.tasks.set(id, task);
14185
+ if (this.tasks.size > MEMORY_LIMITS.MAX_TASKS_IN_MEMORY) {
14186
+ this.gc();
14187
+ }
13710
14188
  }
13711
14189
  get(id) {
13712
14190
  return this.tasks.get(id);
@@ -13715,7 +14193,7 @@ var TaskStore = class {
13715
14193
  return Array.from(this.tasks.values());
13716
14194
  }
13717
14195
  getRunning() {
13718
- return this.getAll().filter((t) => t.status === "running");
14196
+ return this.getAll().filter((t) => t.status === TASK_STATUS.RUNNING);
13719
14197
  }
13720
14198
  getByParent(parentSessionID) {
13721
14199
  return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
@@ -13749,10 +14227,13 @@ var TaskStore = class {
13749
14227
  hasPending(parentSessionID) {
13750
14228
  return this.getPendingCount(parentSessionID) > 0;
13751
14229
  }
13752
- // Notifications
14230
+ // Notifications with limit
13753
14231
  queueNotification(task) {
13754
14232
  const queue = this.notifications.get(task.parentSessionID) ?? [];
13755
14233
  queue.push(task);
14234
+ if (queue.length > MEMORY_LIMITS.MAX_NOTIFICATIONS_PER_PARENT) {
14235
+ queue.shift();
14236
+ }
13756
14237
  this.notifications.set(task.parentSessionID, queue);
13757
14238
  }
13758
14239
  getNotifications(parentSessionID) {
@@ -13768,9 +14249,6 @@ var TaskStore = class {
13768
14249
  }
13769
14250
  }
13770
14251
  }
13771
- /**
13772
- * Remove a specific task from all notification queues
13773
- */
13774
14252
  clearNotificationsForTask(taskId) {
13775
14253
  for (const [sessionID, tasks] of this.notifications.entries()) {
13776
14254
  const filtered = tasks.filter((t) => t.id !== taskId);
@@ -13781,14 +14259,88 @@ var TaskStore = class {
13781
14259
  }
13782
14260
  }
13783
14261
  }
13784
- };
13785
-
13786
- // src/core/agents/config.ts
13787
- var CONFIG = {
13788
- TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
13789
- CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
13790
- MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
13791
- 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
+ }
13792
14344
  };
13793
14345
 
13794
14346
  // src/core/agents/logger.ts
@@ -13819,34 +14371,19 @@ Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
13819
14371
  </system-notification>`;
13820
14372
  }
13821
14373
 
13822
- // src/core/agents/manager.ts
13823
- var ParallelAgentManager = class _ParallelAgentManager {
13824
- static _instance;
13825
- store = new TaskStore();
13826
- client;
13827
- directory;
13828
- concurrency = new ConcurrencyController();
13829
- pollingInterval;
13830
- constructor(client, directory) {
14374
+ // src/core/agents/manager/task-launcher.ts
14375
+ var TaskLauncher = class {
14376
+ constructor(client, directory, store, concurrency, onTaskError, startPolling) {
13831
14377
  this.client = client;
13832
14378
  this.directory = directory;
14379
+ this.store = store;
14380
+ this.concurrency = concurrency;
14381
+ this.onTaskError = onTaskError;
14382
+ this.startPolling = startPolling;
13833
14383
  }
13834
- static getInstance(client, directory) {
13835
- if (!_ParallelAgentManager._instance) {
13836
- if (!client || !directory) {
13837
- throw new Error("ParallelAgentManager requires client and directory on first call");
13838
- }
13839
- _ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
13840
- }
13841
- return _ParallelAgentManager._instance;
13842
- }
13843
- // ========================================================================
13844
- // Public API
13845
- // ========================================================================
13846
14384
  async launch(input) {
13847
14385
  const concurrencyKey = input.agent;
13848
14386
  await this.concurrency.acquire(concurrencyKey);
13849
- this.pruneExpiredTasks();
13850
14387
  try {
13851
14388
  const createResult = await this.client.session.create({
13852
14389
  body: { parentID: input.parentSessionID, title: `Parallel: ${input.description}` },
@@ -13863,8 +14400,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
13863
14400
  sessionID,
13864
14401
  parentSessionID: input.parentSessionID,
13865
14402
  description: input.description,
14403
+ prompt: input.prompt,
13866
14404
  agent: input.agent,
13867
- status: "running",
14405
+ status: TASK_STATUS.RUNNING,
13868
14406
  startedAt: /* @__PURE__ */ new Date(),
13869
14407
  concurrencyKey
13870
14408
  };
@@ -13873,10 +14411,10 @@ var ParallelAgentManager = class _ParallelAgentManager {
13873
14411
  this.startPolling();
13874
14412
  this.client.session.prompt({
13875
14413
  path: { id: sessionID },
13876
- body: { agent: input.agent, parts: [{ type: "text", text: input.prompt }] }
14414
+ body: { agent: input.agent, parts: [{ type: PART_TYPES.TEXT, text: input.prompt }] }
13877
14415
  }).catch((error45) => {
13878
14416
  log2(`Prompt error for ${taskId}:`, error45);
13879
- this.handleTaskError(taskId, error45);
14417
+ this.onTaskError(taskId, error45);
13880
14418
  });
13881
14419
  log2(`Launched ${taskId} in session ${sessionID}`);
13882
14420
  return task;
@@ -13885,168 +14423,90 @@ var ParallelAgentManager = class _ParallelAgentManager {
13885
14423
  throw error45;
13886
14424
  }
13887
14425
  }
13888
- getTask(id) {
13889
- return this.store.get(id);
13890
- }
13891
- getRunningTasks() {
13892
- 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;
13893
14436
  }
13894
- getAllTasks() {
13895
- 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;
13896
14469
  }
13897
- getTasksByParent(parentSessionID) {
13898
- 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;
13899
14489
  }
13900
- async cancelTask(taskId) {
13901
- const task = this.store.get(taskId);
13902
- if (!task || task.status !== "running") return false;
13903
- task.status = "error";
13904
- task.error = "Cancelled by user";
13905
- task.completedAt = /* @__PURE__ */ new Date();
13906
- if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
13907
- this.store.untrackPending(task.parentSessionID, taskId);
13908
- try {
13909
- await this.client.session.delete({ path: { id: task.sessionID } });
13910
- log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
13911
- } catch {
13912
- log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
13913
- }
13914
- this.scheduleCleanup(taskId);
13915
- log2(`Cancelled ${taskId}`);
13916
- return true;
13917
- }
13918
- async getResult(taskId) {
13919
- const task = this.store.get(taskId);
13920
- if (!task) return null;
13921
- if (task.result) return task.result;
13922
- if (task.status === "error") return `Error: ${task.error}`;
13923
- if (task.status === "running") return null;
13924
- try {
13925
- const result = await this.client.session.messages({ path: { id: task.sessionID } });
13926
- if (result.error) return `Error: ${result.error}`;
13927
- const messages = result.data ?? [];
13928
- const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
13929
- if (!lastMsg) return "(No response)";
13930
- const text = lastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
13931
- task.result = text;
13932
- return text;
13933
- } catch (error45) {
13934
- return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
13935
- }
13936
- }
13937
- setConcurrencyLimit(agentType, limit) {
13938
- this.concurrency.setLimit(agentType, limit);
13939
- }
13940
- getPendingCount(parentSessionID) {
13941
- return this.store.getPendingCount(parentSessionID);
13942
- }
13943
- cleanup() {
13944
- this.stopPolling();
13945
- this.store.clear();
13946
- }
13947
- formatDuration = formatDuration;
13948
- // ========================================================================
13949
- // Event Handling (from OpenCode hooks)
13950
- // ========================================================================
13951
- /**
13952
- * Handle OpenCode session events for proper resource cleanup.
13953
- * Call this from your plugin's event hook.
13954
- */
13955
- handleEvent(event) {
13956
- const props = event.properties;
13957
- if (event.type === "session.idle") {
13958
- const sessionID = props?.sessionID;
13959
- if (!sessionID) return;
13960
- const task = this.findBySession(sessionID);
13961
- if (!task || task.status !== "running") return;
13962
- this.handleSessionIdle(task).catch((err) => {
13963
- log2("Error handling session.idle:", err);
13964
- });
13965
- }
13966
- if (event.type === "session.deleted") {
13967
- const sessionID = props?.info?.id ?? props?.sessionID;
13968
- if (!sessionID) return;
13969
- const task = this.findBySession(sessionID);
13970
- if (!task) return;
13971
- log2(`Session deleted event for task ${task.id}`);
13972
- if (task.status === "running") {
13973
- task.status = "error";
13974
- task.error = "Session deleted";
13975
- task.completedAt = /* @__PURE__ */ new Date();
13976
- }
13977
- if (task.concurrencyKey) {
13978
- this.concurrency.release(task.concurrencyKey);
13979
- task.concurrencyKey = void 0;
13980
- }
13981
- this.store.untrackPending(task.parentSessionID, task.id);
13982
- this.store.clearNotificationsForTask(task.id);
13983
- this.store.delete(task.id);
13984
- log2(`Cleaned up deleted session task: ${task.id}`);
13985
- }
13986
- }
13987
- /**
13988
- * Find task by session ID
13989
- */
13990
- findBySession(sessionID) {
13991
- return this.store.getAll().find((t) => t.sessionID === sessionID);
13992
- }
13993
- /**
13994
- * Handle session.idle event - validate and complete task
13995
- */
13996
- async handleSessionIdle(task) {
13997
- const elapsed = Date.now() - task.startedAt.getTime();
13998
- if (elapsed < CONFIG.MIN_STABILITY_MS) {
13999
- log2(`Session idle but too early for ${task.id}, waiting...`);
14000
- return;
14001
- }
14002
- const hasOutput = await this.validateSessionHasOutput(task.sessionID);
14003
- if (!hasOutput) {
14004
- log2(`Session idle but no output for ${task.id}, waiting...`);
14005
- return;
14006
- }
14007
- task.status = "completed";
14008
- task.completedAt = /* @__PURE__ */ new Date();
14009
- if (task.concurrencyKey) {
14010
- this.concurrency.release(task.concurrencyKey);
14011
- task.concurrencyKey = void 0;
14012
- }
14013
- this.store.untrackPending(task.parentSessionID, task.id);
14014
- this.store.queueNotification(task);
14015
- await this.notifyParentIfAllComplete(task.parentSessionID);
14016
- this.scheduleCleanup(task.id);
14017
- log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
14018
- }
14019
- // ========================================================================
14020
- // Internal
14021
- // ========================================================================
14022
- handleTaskError(taskId, error45) {
14023
- const task = this.store.get(taskId);
14024
- if (!task) return;
14025
- task.status = "error";
14026
- task.error = error45 instanceof Error ? error45.message : String(error45);
14027
- task.completedAt = /* @__PURE__ */ new Date();
14028
- if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
14029
- this.store.untrackPending(task.parentSessionID, taskId);
14030
- this.store.queueNotification(task);
14031
- this.notifyParentIfAllComplete(task.parentSessionID);
14032
- this.scheduleCleanup(taskId);
14033
- }
14034
- startPolling() {
14490
+ pollingInterval;
14491
+ start() {
14035
14492
  if (this.pollingInterval) return;
14036
- this.pollingInterval = setInterval(() => this.pollRunningTasks(), CONFIG.POLL_INTERVAL_MS);
14493
+ this.pollingInterval = setInterval(() => this.poll(), CONFIG.POLL_INTERVAL_MS);
14037
14494
  this.pollingInterval.unref();
14038
14495
  }
14039
- stopPolling() {
14496
+ stop() {
14040
14497
  if (this.pollingInterval) {
14041
14498
  clearInterval(this.pollingInterval);
14042
14499
  this.pollingInterval = void 0;
14043
14500
  }
14044
14501
  }
14045
- async pollRunningTasks() {
14502
+ isRunning() {
14503
+ return !!this.pollingInterval;
14504
+ }
14505
+ async poll() {
14046
14506
  this.pruneExpiredTasks();
14047
14507
  const running = this.store.getRunning();
14048
14508
  if (running.length === 0) {
14049
- this.stopPolling();
14509
+ this.stop();
14050
14510
  return;
14051
14511
  }
14052
14512
  try {
@@ -14078,9 +14538,28 @@ var ParallelAgentManager = class _ParallelAgentManager {
14078
14538
  log2("Polling error:", error45);
14079
14539
  }
14080
14540
  }
14081
- /**
14082
- * Update task progress and stability tracking
14083
- */
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
+ }
14084
14563
  async updateTaskProgress(task) {
14085
14564
  try {
14086
14565
  const result = await this.client.session.messages({ path: { id: task.sessionID } });
@@ -14096,7 +14575,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14096
14575
  toolCalls++;
14097
14576
  lastTool = part.tool || part.name;
14098
14577
  }
14099
- if (part.type === "text" && part.text) {
14578
+ if (part.type === PART_TYPES.TEXT && part.text) {
14100
14579
  lastMessage = part.text;
14101
14580
  }
14102
14581
  }
@@ -14117,30 +14596,14 @@ var ParallelAgentManager = class _ParallelAgentManager {
14117
14596
  } catch {
14118
14597
  }
14119
14598
  }
14120
- /**
14121
- * Complete a task and cleanup
14122
- */
14123
- async completeTask(task) {
14124
- task.status = "completed";
14125
- task.completedAt = /* @__PURE__ */ new Date();
14126
- if (task.concurrencyKey) {
14127
- this.concurrency.release(task.concurrencyKey);
14128
- task.concurrencyKey = void 0;
14129
- }
14130
- this.store.untrackPending(task.parentSessionID, task.id);
14131
- this.store.queueNotification(task);
14132
- await this.notifyParentIfAllComplete(task.parentSessionID);
14133
- this.scheduleCleanup(task.id);
14134
- log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
14135
- }
14136
- async validateSessionHasOutput(sessionID) {
14137
- try {
14138
- const response = await this.client.session.messages({ path: { id: sessionID } });
14139
- const messages = response.data ?? [];
14140
- return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text?.trim() || p.type === "tool"));
14141
- } catch {
14142
- return true;
14143
- }
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;
14144
14607
  }
14145
14608
  pruneExpiredTasks() {
14146
14609
  const now = Date.now();
@@ -14148,8 +14611,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
14148
14611
  const age = now - task.startedAt.getTime();
14149
14612
  if (age <= CONFIG.TASK_TTL_MS) continue;
14150
14613
  log2(`Timeout: ${taskId}`);
14151
- if (task.status === "running") {
14152
- task.status = "timeout";
14614
+ if (task.status === TASK_STATUS.RUNNING) {
14615
+ task.status = TASK_STATUS.TIMEOUT;
14153
14616
  task.error = "Task exceeded 30 minute time limit";
14154
14617
  task.completedAt = /* @__PURE__ */ new Date();
14155
14618
  if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
@@ -14183,7 +14646,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
14183
14646
  try {
14184
14647
  await this.client.session.prompt({
14185
14648
  path: { id: parentSessionID },
14186
- body: { noReply: true, parts: [{ type: "text", text: message }] }
14649
+ body: { noReply: true, parts: [{ type: PART_TYPES.TEXT, text: message }] }
14187
14650
  });
14188
14651
  log2(`Notified parent ${parentSessionID}`);
14189
14652
  } catch (error45) {
@@ -14192,76 +14655,532 @@ var ParallelAgentManager = class _ParallelAgentManager {
14192
14655
  this.store.clearNotifications(parentSessionID);
14193
14656
  }
14194
14657
  };
14195
- var parallelAgentManager = {
14196
- getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
14197
- };
14198
-
14199
- // src/tools/parallel/delegate-task.ts
14200
- var createDelegateTaskTool = (manager, client) => tool({
14201
- description: `Delegate a task to an agent.
14202
14658
 
14203
- <mode>
14204
- - background=true: Non-blocking. Returns task ID immediately.
14205
- - background=false: Blocking. Waits for result.
14206
- </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
+ };
14207
14697
 
14208
- <safety>
14209
- - Max 3 tasks per agent type
14210
- - Auto-timeout: 30 minutes
14211
- </safety>`,
14212
- args: {
14213
- agent: tool.schema.string().describe("Agent name"),
14214
- description: tool.schema.string().describe("Task description"),
14215
- prompt: tool.schema.string().describe("Prompt for the agent"),
14216
- background: tool.schema.boolean().describe("true=async, false=sync")
14217
- },
14218
- async execute(args, context) {
14219
- const { agent, description, prompt, background } = args;
14220
- const ctx = context;
14221
- const sessionClient = client;
14222
- if (background === void 0) {
14223
- 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
+ }
14224
14739
  }
14225
- 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) {
14226
14763
  try {
14227
- const task = await manager.launch({
14228
- agent,
14229
- description,
14230
- prompt,
14231
- parentSessionID: ctx.sessionID
14232
- });
14233
- return `\u{1F680} Task spawned: \`${task.id}\` (${agent})`;
14764
+ await sub.handler(event);
14234
14765
  } catch (error45) {
14235
- return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
14766
+ console.error(`[EventBus] Handler error for ${type}:`, error45);
14236
14767
  }
14237
- }
14238
- try {
14239
- const session = sessionClient.session;
14240
- const createResult = await session.create({
14241
- body: { parentID: ctx.sessionID, title: `Task: ${description}` },
14242
- query: { directory: "." }
14243
- });
14244
- if (createResult.error || !createResult.data?.id) {
14245
- return `\u274C Failed to create session`;
14768
+ if (sub.once) {
14769
+ toRemove.push({ id: sub.id, type: sub.type });
14246
14770
  }
14247
- const sessionID = createResult.data.id;
14248
- const startTime = Date.now();
14249
- await session.prompt({
14250
- path: { id: sessionID },
14251
- 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);
14252
14821
  });
14253
- let stablePolls = 0, lastMsgCount = 0;
14254
- while (Date.now() - startTime < 10 * 60 * 1e3) {
14255
- await new Promise((r) => setTimeout(r, 500));
14256
- const statusResult = await session.status();
14257
- if (statusResult.data?.[sessionID]?.type !== "idle") {
14258
- stablePolls = 0;
14259
- continue;
14260
- }
14261
- if (Date.now() - startTime < 5e3) continue;
14262
- const msgs2 = await session.messages({ path: { id: sessionID } });
14263
- const count = (msgs2.data ?? []).length;
14264
- if (count === lastMsgCount) {
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) {
14265
15184
  stablePolls++;
14266
15185
  if (stablePolls >= 3) break;
14267
15186
  } else {
@@ -14269,238 +15188,1213 @@ var createDelegateTaskTool = (manager, client) => tool({
14269
15188
  lastMsgCount = count;
14270
15189
  }
14271
15190
  }
14272
- const msgs = await session.messages({ path: { id: sessionID } });
14273
- const messages = msgs.data ?? [];
14274
- const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
14275
- const text = lastMsg?.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").join("\n") || "";
14276
- return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
14277
-
14278
- ${text || "(No output)"}`;
14279
- } catch (error45) {
14280
- 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
+ }
14281
16108
  }
16109
+ return results;
16110
+ } catch (error45) {
16111
+ console.error("GitHub search error:", error45);
16112
+ return [];
14282
16113
  }
14283
- });
16114
+ }
16115
+ var codesearchTool = tool({
16116
+ description: `Search open source code for patterns and examples.
14284
16117
 
14285
- // src/tools/parallel/get-task-result.ts
14286
- var createGetTaskResultTool = (manager) => tool({
14287
- 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>`,
14288
16135
  args: {
14289
- 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)")
14290
16139
  },
14291
16140
  async execute(args) {
14292
- const task = manager.getTask(args.taskId);
14293
- if (!task) return `\u274C Task not found: \`${args.taskId}\``;
14294
- if (task.status === "running") return `\u23F3 Still running...`;
14295
- const result = await manager.getResult(args.taskId);
14296
- const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
14297
- if (task.status === "error" || task.status === "timeout") {
14298
- 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 });
14299
16145
  }
14300
- return `\u2705 Completed (${duration3})
16146
+ if (results.length === 0) {
16147
+ return `\u{1F50D} No code results found for: "${query}"
14301
16148
 
14302
- ${result || "(No output)"}`;
14303
- }
14304
- });
16149
+ Try:
16150
+ - Different search terms
16151
+ - Broader language filter
16152
+ - Check spelling`;
16153
+ }
16154
+ let output = `\u{1F50D} **Code Search Results for: "${query}"**
14305
16155
 
14306
- // src/tools/parallel/list-tasks.ts
14307
- var createListTasksTool = (manager) => tool({
14308
- description: `List all background tasks.`,
14309
- args: {
14310
- status: tool.schema.string().optional().describe("Filter: all, running, completed, error")
14311
- },
14312
- async execute(args) {
14313
- const { status = "all" } = args;
14314
- let tasks;
14315
- switch (status) {
14316
- case "running":
14317
- tasks = manager.getRunningTasks();
14318
- break;
14319
- case "completed":
14320
- tasks = manager.getAllTasks().filter((t) => t.status === "completed");
14321
- break;
14322
- case "error":
14323
- tasks = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout");
14324
- break;
14325
- default:
14326
- 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
+ }
14327
16178
  }
14328
- if (tasks.length === 0) return `\u{1F4CB} No tasks found.`;
14329
- const rows = tasks.map((t) => {
14330
- const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
14331
- return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
14332
- }).join("\n");
14333
- return `\u{1F4CB} **Tasks**
16179
+ output += `---
14334
16180
 
14335
- | ID | Status | Agent | Time |
14336
- |----|--------|-------|------|
14337
- ${rows}`;
16181
+ `;
16182
+ output += `\u{1F4A1} **Tip**: Use \`webfetch\` to get the full file content from any of these URLs.`;
16183
+ return output;
14338
16184
  }
14339
16185
  });
14340
16186
 
14341
- // src/tools/parallel/cancel-task.ts
14342
- var createCancelTaskTool = (manager) => tool({
14343
- description: `Cancel a running task.`,
14344
- args: {
14345
- taskId: tool.schema.string().describe("Task ID to cancel")
14346
- },
14347
- async execute(args) {
14348
- const cancelled = await manager.cancelTask(args.taskId);
14349
- if (cancelled) return `\u{1F6D1} Cancelled: \`${args.taskId}\``;
14350
- const task = manager.getTask(args.taskId);
14351
- if (task) return `\u26A0\uFE0F Cannot cancel: Task is ${task.status}`;
14352
- 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();
14353
16204
  }
14354
- });
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
+ }
14355
16216
 
14356
- // src/tools/parallel/index.ts
14357
- function createAsyncAgentTools(manager, client) {
14358
- return {
14359
- delegate_task: createDelegateTaskTool(manager, client),
14360
- get_task_result: createGetTaskResultTool(manager),
14361
- list_tasks: createListTasksTool(manager),
14362
- 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
+ }
14363
16307
  };
14364
16308
  }
14365
16309
 
14366
- // src/utils/common.ts
14367
- function detectSlashCommand(text) {
14368
- const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
14369
- if (!match) return null;
14370
- 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;
14371
16351
  }
14372
- function formatTimestamp(date5 = /* @__PURE__ */ new Date()) {
14373
- const pad = (n) => n.toString().padStart(2, "0");
14374
- 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];
14375
16355
  }
14376
- function formatElapsedTime(startMs, endMs = Date.now()) {
14377
- const elapsed = endMs - startMs;
14378
- if (elapsed < 0) return "0s";
14379
- const seconds = Math.floor(elapsed / 1e3) % 60;
14380
- const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
14381
- const hours = Math.floor(elapsed / (1e3 * 60 * 60));
14382
- const parts = [];
14383
- if (hours > 0) parts.push(`${hours}h`);
14384
- if (minutes > 0) parts.push(`${minutes}m`);
14385
- if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
14386
- return parts.join(" ");
16356
+ function clearSession(sessionId) {
16357
+ progressHistory.delete(sessionId);
16358
+ sessionStartTimes.delete(sessionId);
14387
16359
  }
14388
16360
 
14389
- // src/utils/sanity.ts
14390
- function checkOutputSanity(text) {
14391
- if (!text || text.length < 50) {
14392
- return { isHealthy: true, severity: "ok" };
14393
- }
14394
- if (/(.)\1{15,}/.test(text)) {
14395
- return {
14396
- isHealthy: false,
14397
- reason: "Single character repetition detected",
14398
- severity: "critical"
14399
- };
14400
- }
14401
- if (/(.{2,6})\1{8,}/.test(text)) {
14402
- return {
14403
- isHealthy: false,
14404
- reason: "Pattern loop detected",
14405
- severity: "critical"
14406
- };
14407
- }
14408
- if (text.length > 200) {
14409
- const cleanText = text.replace(/\s/g, "");
14410
- if (cleanText.length > 100) {
14411
- const uniqueChars = new Set(cleanText).size;
14412
- const ratio = uniqueChars / cleanText.length;
14413
- if (ratio < 0.02) {
14414
- return {
14415
- isHealthy: false,
14416
- reason: "Low information density",
14417
- severity: "critical"
14418
- };
14419
- }
14420
- }
14421
- }
14422
- const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
14423
- if (boxChars > 100 && boxChars / text.length > 0.3) {
14424
- return {
14425
- isHealthy: false,
14426
- reason: "Visual gibberish detected",
14427
- severity: "critical"
14428
- };
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`;
14429
16372
  }
14430
- const lines = text.split("\n").filter((l) => l.trim().length > 10);
14431
- if (lines.length > 10) {
14432
- const lineSet = new Set(lines);
14433
- if (lineSet.size < lines.length * 0.2) {
14434
- return {
14435
- isHealthy: false,
14436
- reason: "Excessive line repetition",
14437
- severity: "warning"
14438
- };
14439
- }
16373
+ }
16374
+ function formatCompact(snapshot) {
16375
+ const parts = [];
16376
+ if (snapshot.todos.total > 0) {
16377
+ parts.push(`\u2705${snapshot.todos.completed}/${snapshot.todos.total}`);
14440
16378
  }
14441
- const cjkChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
14442
- if (cjkChars > 200) {
14443
- const uniqueCjk = new Set(
14444
- text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
14445
- ).size;
14446
- if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
14447
- return {
14448
- isHealthy: false,
14449
- reason: "CJK character spam detected",
14450
- severity: "critical"
14451
- };
14452
- }
16379
+ if (snapshot.tasks.running > 0) {
16380
+ parts.push(`\u26A1${snapshot.tasks.running}`);
14453
16381
  }
14454
- return { isHealthy: true, severity: "ok" };
16382
+ parts.push(`\u23F1${formatElapsed(snapshot.elapsedMs)}`);
16383
+ return parts.join(" | ");
14455
16384
  }
14456
- var RECOVERY_PROMPT = `<anomaly_recovery>
14457
- \u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
14458
-
14459
- <recovery_protocol>
14460
- 1. DISCARD the corrupted output completely - do not reference it
14461
- 2. RECALL the original mission objective
14462
- 3. IDENTIFY the last confirmed successful step
14463
- 4. RESTART with a simpler, more focused approach
14464
- </recovery_protocol>
14465
-
14466
- <instructions>
14467
- - If a sub-agent produced bad output: try a different agent or simpler task
14468
- - If stuck in a loop: break down the task into smaller pieces
14469
- - If context seems corrupted: call recorder to restore context
14470
- - THINK in English for maximum stability
14471
- </instructions>
14472
-
14473
- What was the original task? Proceed from the last known good state.
14474
- </anomaly_recovery>`;
14475
- var ESCALATION_PROMPT = `<critical_anomaly>
14476
- \u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
14477
-
14478
- <emergency_protocol>
14479
- 1. STOP current execution path immediately
14480
- 2. DO NOT continue with the same approach - it is failing
14481
- 3. CALL architect for a completely new strategy
14482
- 4. If architect also fails: report status to user and await guidance
14483
- </emergency_protocol>
14484
-
14485
- <diagnosis>
14486
- The current approach is producing corrupted output.
14487
- This may indicate: context overload, model instability, or task complexity.
14488
- </diagnosis>
14489
16385
 
14490
- Request a fresh plan from architect with reduced scope.
14491
- </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
+ }
14492
16392
 
14493
16393
  // src/index.ts
14494
- var PLUGIN_VERSION = "0.2.4";
14495
- var DEFAULT_MAX_STEPS = 500;
14496
- var TASK_COMMAND_MAX_STEPS = 1e3;
14497
- var AGENT_EMOJI2 = {
14498
- "architect": "\u{1F3D7}\uFE0F",
14499
- "builder": "\u{1F528}",
14500
- "inspector": "\u{1F50D}",
14501
- "recorder": "\u{1F4BE}",
14502
- "commander": "\u{1F3AF}"
14503
- };
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;
14504
16398
  var CONTINUE_INSTRUCTION = `<auto_continue>
14505
16399
  <status>Mission not complete. Keep executing.</status>
14506
16400
 
@@ -14509,17 +16403,30 @@ var CONTINUE_INSTRUCTION = `<auto_continue>
14509
16403
  2. DO NOT wait for user input
14510
16404
  3. If previous action failed, try different approach
14511
16405
  4. If agent returned nothing, proceed to next step
16406
+ 5. Check your todo list - complete ALL pending items
14512
16407
  </rules>
14513
16408
 
14514
16409
  <next_step>
14515
- What is the current state?
14516
- What is the next action?
14517
- 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
14518
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>
14519
16424
  </auto_continue>`;
14520
16425
  var OrchestratorPlugin = async (input) => {
14521
16426
  const { directory, client } = input;
14522
16427
  console.log(`[orchestrator] v${PLUGIN_VERSION} loaded`);
16428
+ const disableAutoToasts = enableAutoToasts();
16429
+ console.log(`[orchestrator] Toast notifications enabled`);
14523
16430
  const sessions = /* @__PURE__ */ new Map();
14524
16431
  const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
14525
16432
  const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
@@ -14528,17 +16435,22 @@ var OrchestratorPlugin = async (input) => {
14528
16435
  // Tools we expose to the LLM
14529
16436
  // -----------------------------------------------------------------
14530
16437
  tool: {
14531
- call_agent: callAgentTool,
14532
- slashcommand: createSlashcommandTool(),
14533
- grep_search: grepSearchTool(directory),
14534
- glob_search: globSearchTool(directory),
14535
- 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),
14536
16443
  // Multi-pattern grep (parallel, Rust-powered)
14537
16444
  // Background task tools - run shell commands asynchronously
14538
- run_background: runBackgroundTool,
14539
- check_background: checkBackgroundTool,
14540
- list_background: listBackgroundTool,
14541
- 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,
14542
16454
  // Async agent tools - spawn agents in parallel sessions
14543
16455
  ...asyncAgentTools
14544
16456
  },
@@ -14557,10 +16469,20 @@ var OrchestratorPlugin = async (input) => {
14557
16469
  };
14558
16470
  }
14559
16471
  const orchestratorAgents = {
14560
- Commander: {
14561
- name: "Commander",
16472
+ [AGENT_NAMES.COMMANDER]: {
16473
+ name: AGENT_NAMES.COMMANDER,
14562
16474
  description: "Autonomous orchestrator - executes until mission complete",
14563
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 || ""
14564
16486
  }
14565
16487
  };
14566
16488
  config2.command = { ...orchestratorCommands, ...existingCommands };
@@ -14572,13 +16494,13 @@ var OrchestratorPlugin = async (input) => {
14572
16494
  // -----------------------------------------------------------------
14573
16495
  "chat.message": async (msgInput, msgOutput) => {
14574
16496
  const parts = msgOutput.parts;
14575
- const textPartIndex = parts.findIndex((p) => p.type === "text" && p.text);
16497
+ const textPartIndex = parts.findIndex((p) => p.type === PART_TYPES.TEXT && p.text);
14576
16498
  if (textPartIndex === -1) return;
14577
16499
  const originalText = parts[textPartIndex].text || "";
14578
16500
  const parsed = detectSlashCommand(originalText);
14579
16501
  const sessionID = msgInput.sessionID;
14580
16502
  const agentName = (msgInput.agent || "").toLowerCase();
14581
- if (agentName === "commander" && !sessions.has(sessionID)) {
16503
+ if (agentName === AGENT_NAMES.COMMANDER && !sessions.has(sessionID)) {
14582
16504
  const now = Date.now();
14583
16505
  sessions.set(sessionID, {
14584
16506
  active: true,
@@ -14596,44 +16518,19 @@ var OrchestratorPlugin = async (input) => {
14596
16518
  currentTask: "",
14597
16519
  anomalyCount: 0
14598
16520
  });
14599
- if (!parsed) {
14600
- const userMessage = originalText.trim();
14601
- if (userMessage) {
14602
- parts[textPartIndex].text = COMMANDS["task"].template.replace(
14603
- /\$ARGUMENTS/g,
14604
- userMessage
14605
- );
14606
- }
14607
- }
14608
- }
14609
- if (parsed?.command === "task") {
14610
- const now = Date.now();
14611
- sessions.set(sessionID, {
14612
- active: true,
14613
- step: 0,
14614
- maxSteps: TASK_COMMAND_MAX_STEPS,
14615
- timestamp: now,
14616
- startTime: now,
14617
- lastStepTime: now
14618
- });
14619
- state.missionActive = true;
14620
- state.sessions.set(sessionID, {
14621
- enabled: true,
14622
- iterations: 0,
14623
- taskRetries: /* @__PURE__ */ new Map(),
14624
- currentTask: "",
14625
- anomalyCount: 0
16521
+ startSession(sessionID);
16522
+ emit(TASK_EVENTS.STARTED, {
16523
+ taskId: sessionID,
16524
+ agent: AGENT_NAMES.COMMANDER,
16525
+ description: "Mission started"
14626
16526
  });
14627
- parts[textPartIndex].text = COMMANDS["task"].template.replace(
14628
- /\$ARGUMENTS/g,
14629
- parsed.args || "continue previous work"
14630
- );
14631
- } else if (parsed) {
16527
+ }
16528
+ if (parsed) {
14632
16529
  const command = COMMANDS[parsed.command];
14633
16530
  if (command) {
14634
16531
  parts[textPartIndex].text = command.template.replace(
14635
16532
  /\$ARGUMENTS/g,
14636
- parsed.args || "continue"
16533
+ parsed.args || PROMPTS.CONTINUE
14637
16534
  );
14638
16535
  }
14639
16536
  }
@@ -14652,7 +16549,7 @@ var OrchestratorPlugin = async (input) => {
14652
16549
  session.timestamp = now;
14653
16550
  session.lastStepTime = now;
14654
16551
  const stateSession = state.sessions.get(toolInput.sessionID);
14655
- if (toolInput.tool === "call_agent" && stateSession) {
16552
+ if (toolInput.tool === TOOL_NAMES.CALL_AGENT && stateSession) {
14656
16553
  const sanityResult = checkOutputSanity(toolOutput.output);
14657
16554
  if (!sanityResult.isHealthy) {
14658
16555
  stateSession.anomalyCount = (stateSession.anomalyCount || 0) + 1;
@@ -14675,14 +16572,14 @@ Anomaly count: ${stateSession.anomalyCount}
14675
16572
  }
14676
16573
  }
14677
16574
  }
14678
- if (toolInput.tool === "call_agent" && toolInput.arguments?.task && stateSession) {
16575
+ if (toolInput.tool === TOOL_NAMES.CALL_AGENT && toolInput.arguments?.task && stateSession) {
14679
16576
  const taskIdMatch = toolInput.arguments.task.match(/\[(TASK-\d+)\]/i);
14680
16577
  if (taskIdMatch) {
14681
16578
  stateSession.currentTask = taskIdMatch[1].toUpperCase();
14682
- stateSession.graph?.updateTask(stateSession.currentTask, { status: "running" });
16579
+ stateSession.graph?.updateTask(stateSession.currentTask, { status: TASK_STATUS.RUNNING });
14683
16580
  }
14684
16581
  const agentName = toolInput.arguments.agent;
14685
- const emoji3 = AGENT_EMOJI2[agentName] || "\u{1F916}";
16582
+ const emoji3 = AGENT_EMOJI[agentName] || "\u{1F916}";
14686
16583
  toolOutput.output = `${emoji3} [${agentName.toUpperCase()}] Working...
14687
16584
 
14688
16585
  ` + toolOutput.output;
@@ -14692,7 +16589,7 @@ Anomaly count: ${stateSession.anomalyCount}
14692
16589
  state.missionActive = false;
14693
16590
  return;
14694
16591
  }
14695
- 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) {
14696
16593
  const jsonMatch = toolOutput.output.match(/```json\n([\s\S]*?)\n```/) || toolOutput.output.match(/\[\s*\{[\s\S]*?\}\s*\]/);
14697
16594
  if (jsonMatch) {
14698
16595
  try {
@@ -14713,7 +16610,7 @@ ${stateSession.graph.getTaskSummary()}`;
14713
16610
  const taskId = stateSession.currentTask;
14714
16611
  if (toolOutput.output.includes("\u2705 PASS") || toolOutput.output.includes("AUDIT RESULT: PASS")) {
14715
16612
  if (taskId) {
14716
- stateSession.graph.updateTask(taskId, { status: "completed" });
16613
+ stateSession.graph.updateTask(taskId, { status: TASK_STATUS.COMPLETED });
14717
16614
  stateSession.taskRetries.clear();
14718
16615
  toolOutput.output += `
14719
16616
 
@@ -14726,7 +16623,7 @@ ${stateSession.graph.getTaskSummary()}`;
14726
16623
  const retries = (stateSession.taskRetries.get(taskId) || 0) + 1;
14727
16624
  stateSession.taskRetries.set(taskId, retries);
14728
16625
  if (retries >= state.maxRetries) {
14729
- stateSession.graph.updateTask(taskId, { status: "failed" });
16626
+ stateSession.graph.updateTask(taskId, { status: TASK_STATUS.FAILED });
14730
16627
  toolOutput.output += `
14731
16628
 
14732
16629
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
@@ -14760,7 +16657,7 @@ ${stateSession.graph.getTaskSummary()}`;
14760
16657
  const session = sessions.get(sessionID);
14761
16658
  if (!session?.active) return;
14762
16659
  const parts = assistantOutput.parts;
14763
- 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") || "";
14764
16661
  const stateSession = state.sessions.get(sessionID);
14765
16662
  const sanityResult = checkOutputSanity(textContent);
14766
16663
  if (!sanityResult.isHealthy && stateSession) {
@@ -14774,7 +16671,7 @@ ${stateSession.graph.getTaskSummary()}`;
14774
16671
  path: { id: sessionID },
14775
16672
  body: {
14776
16673
  parts: [{
14777
- type: "text",
16674
+ type: PART_TYPES.TEXT,
14778
16675
  text: `\u26A0\uFE0F ANOMALY #${stateSession.anomalyCount}: ${sanityResult.reason}
14779
16676
 
14780
16677
  ` + recoveryText + `
@@ -14793,16 +16690,26 @@ ${stateSession.graph.getTaskSummary()}`;
14793
16690
  if (stateSession && stateSession.anomalyCount > 0) {
14794
16691
  stateSession.anomalyCount = 0;
14795
16692
  }
14796
- if (textContent.includes("\u2705 MISSION COMPLETE") || textContent.includes("MISSION COMPLETE")) {
16693
+ if (textContent.includes(MISSION.COMPLETE) || textContent.includes(MISSION.COMPLETE_TEXT)) {
14797
16694
  session.active = false;
14798
16695
  state.missionActive = false;
16696
+ emit(MISSION_EVENTS.COMPLETE, {
16697
+ sessionId: sessionID,
16698
+ summary: "Mission completed successfully"
16699
+ });
16700
+ clearSession(sessionID);
14799
16701
  sessions.delete(sessionID);
14800
16702
  state.sessions.delete(sessionID);
14801
16703
  return;
14802
16704
  }
14803
- if (textContent.includes("/stop") || textContent.includes("/cancel")) {
16705
+ if (textContent.includes(MISSION.STOP_COMMAND) || textContent.includes(MISSION.CANCEL_COMMAND)) {
14804
16706
  session.active = false;
14805
16707
  state.missionActive = false;
16708
+ emit(TASK_EVENTS.FAILED, {
16709
+ taskId: sessionID,
16710
+ error: "Cancelled by user"
16711
+ });
16712
+ clearSession(sessionID);
14806
16713
  sessions.delete(sessionID);
14807
16714
  state.sessions.delete(sessionID);
14808
16715
  return;
@@ -14819,16 +16726,21 @@ ${stateSession.graph.getTaskSummary()}`;
14819
16726
  state.missionActive = false;
14820
16727
  return;
14821
16728
  }
16729
+ recordSnapshot(sessionID, {
16730
+ currentStep: session.step,
16731
+ maxSteps: session.maxSteps
16732
+ });
16733
+ const progressInfo = formatCompact2(sessionID);
14822
16734
  try {
14823
16735
  if (client?.session?.prompt) {
14824
16736
  await client.session.prompt({
14825
16737
  path: { id: sessionID },
14826
16738
  body: {
14827
16739
  parts: [{
14828
- type: "text",
16740
+ type: PART_TYPES.TEXT,
14829
16741
  text: CONTINUE_INSTRUCTION + `
14830
16742
 
14831
- \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}`
14832
16744
  }]
14833
16745
  }
14834
16746
  });
@@ -14839,7 +16751,7 @@ ${stateSession.graph.getTaskSummary()}`;
14839
16751
  if (client?.session?.prompt) {
14840
16752
  await client.session.prompt({
14841
16753
  path: { id: sessionID },
14842
- body: { parts: [{ type: "text", text: "continue" }] }
16754
+ body: { parts: [{ type: PART_TYPES.TEXT, text: PROMPTS.CONTINUE }] }
14843
16755
  });
14844
16756
  }
14845
16757
  } catch {
@@ -14857,11 +16769,13 @@ ${stateSession.graph.getTaskSummary()}`;
14857
16769
  manager.handleEvent(event);
14858
16770
  } catch {
14859
16771
  }
14860
- if (event.type === "session.deleted") {
16772
+ if (event.type === SESSION_EVENTS.DELETED) {
14861
16773
  const props = event.properties;
14862
16774
  if (props?.info?.id) {
14863
- sessions.delete(props.info.id);
14864
- state.sessions.delete(props.info.id);
16775
+ const sessionId = props.info.id;
16776
+ sessions.delete(sessionId);
16777
+ state.sessions.delete(sessionId);
16778
+ clearSession(sessionId);
14865
16779
  }
14866
16780
  }
14867
16781
  }
@@ -14869,5 +16783,6 @@ ${stateSession.graph.getTaskSummary()}`;
14869
16783
  };
14870
16784
  var index_default = OrchestratorPlugin;
14871
16785
  export {
16786
+ OrchestratorPlugin,
14872
16787
  index_default as default
14873
16788
  };