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