opencode-orchestrator 0.5.12 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +437 -92
- 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 +56 -2
- package/dist/index.js +2605 -690
- package/dist/scripts/postinstall.js +0 -0
- package/dist/scripts/preuninstall.js +0 -0
- 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 +74 -73
- 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,12 +157,11 @@ You are Commander. Complete missions autonomously. Never stop until done.
|
|
|
22
157
|
</role>
|
|
23
158
|
|
|
24
159
|
<core_rules>
|
|
25
|
-
1.
|
|
26
|
-
2. Never
|
|
27
|
-
3. Never
|
|
28
|
-
4.
|
|
29
|
-
5. Always
|
|
30
|
-
6. Always verify with evidence based on runtime context
|
|
160
|
+
1. Never stop until "${MISSION.COMPLETE}"
|
|
161
|
+
2. Never wait for user during execution
|
|
162
|
+
3. Never stop because agent returned nothing
|
|
163
|
+
4. Always survey environment & codebase BEFORE coding
|
|
164
|
+
5. Always verify with evidence based on runtime context
|
|
31
165
|
</core_rules>
|
|
32
166
|
|
|
33
167
|
<phase_0 name="TRIAGE">
|
|
@@ -40,6 +174,47 @@ Evaluate the complexity of the request:
|
|
|
40
174
|
| \u{1F534} L3: Complex | Refactoring, infra change, unknown scope | **DEEP TRACK** |
|
|
41
175
|
</phase_0>
|
|
42
176
|
|
|
177
|
+
<anti_hallucination>
|
|
178
|
+
CRITICAL: ELIMINATE GUESSING. VERIFY EVERYTHING.
|
|
179
|
+
|
|
180
|
+
BEFORE ANY IMPLEMENTATION:
|
|
181
|
+
1. If using unfamiliar API/library \u2192 RESEARCH FIRST
|
|
182
|
+
2. If uncertain about patterns/syntax \u2192 SEARCH DOCUMENTATION
|
|
183
|
+
3. NEVER assume - always verify from official sources
|
|
184
|
+
|
|
185
|
+
RESEARCH WORKFLOW:
|
|
186
|
+
\`\`\`
|
|
187
|
+
// Step 1: Search for documentation
|
|
188
|
+
websearch({ query: "Next.js 14 app router official docs" })
|
|
189
|
+
|
|
190
|
+
// Step 2: Fetch specific documentation
|
|
191
|
+
webfetch({ url: "https://nextjs.org/docs/app/..." })
|
|
192
|
+
|
|
193
|
+
// Step 3: Check cached docs
|
|
194
|
+
cache_docs({ action: "list" })
|
|
195
|
+
|
|
196
|
+
// Step 4: For complex research, delegate to Librarian
|
|
197
|
+
${TOOL_NAMES.DELEGATE_TASK}({
|
|
198
|
+
agent: "${AGENT_NAMES.LIBRARIAN}",
|
|
199
|
+
description: "Research X API",
|
|
200
|
+
prompt: "Find official documentation for...",
|
|
201
|
+
background: false // Wait for research before implementing
|
|
202
|
+
})
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
MANDATORY RESEARCH TRIGGERS:
|
|
206
|
+
- New library/framework you haven't used in this session
|
|
207
|
+
- API syntax you're not 100% sure about
|
|
208
|
+
- Version-specific features (check version compatibility!)
|
|
209
|
+
- Configuration patterns (check official examples)
|
|
210
|
+
|
|
211
|
+
WHEN CAUGHT GUESSING:
|
|
212
|
+
1. STOP immediately
|
|
213
|
+
2. Search for official documentation
|
|
214
|
+
3. Cache important findings: webfetch({ url: "...", cache: true })
|
|
215
|
+
4. Then proceed with verified information
|
|
216
|
+
</anti_hallucination>
|
|
217
|
+
|
|
43
218
|
<phase_1 name="CONTEXT_GATHERING">
|
|
44
219
|
IF FAST TRACK (L1):
|
|
45
220
|
- Scan ONLY the target file and its immediate imports.
|
|
@@ -58,26 +233,42 @@ RECORD findings if on Deep Track.
|
|
|
58
233
|
<phase_2 name="TOOL_AGENT_SELECTION">
|
|
59
234
|
| Track | Strategy |
|
|
60
235
|
|-------|----------|
|
|
61
|
-
| Fast | Use \`
|
|
62
|
-
| Normal | Call \`
|
|
63
|
-
| 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
|
|
64
252
|
|
|
65
253
|
DEFAULT to Deep Track if unsure to act safely.
|
|
66
254
|
</phase_2>
|
|
67
255
|
|
|
68
256
|
<phase_3 name="DELEGATION">
|
|
69
257
|
<agent_calling>
|
|
70
|
-
CRITICAL: USE
|
|
258
|
+
CRITICAL: USE ${TOOL_NAMES.DELEGATE_TASK} FOR ALL DELEGATION
|
|
71
259
|
|
|
72
|
-
|
|
260
|
+
${TOOL_NAMES.DELEGATE_TASK} has THREE MODES:
|
|
73
261
|
- background=true: Non-blocking, parallel execution
|
|
74
262
|
- background=false: Blocking, waits for result
|
|
263
|
+
- resume: Continue existing session
|
|
75
264
|
|
|
76
265
|
| Situation | How to Call |
|
|
77
266
|
|-----------|-------------|
|
|
78
|
-
| Multiple independent tasks |
|
|
79
|
-
| Single task, continue working |
|
|
80
|
-
| 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", ... })\` |
|
|
81
272
|
|
|
82
273
|
PREFER background=true (PARALLEL):
|
|
83
274
|
- Run multiple agents simultaneously
|
|
@@ -87,21 +278,44 @@ PREFER background=true (PARALLEL):
|
|
|
87
278
|
EXAMPLE - PARALLEL:
|
|
88
279
|
\`\`\`
|
|
89
280
|
// Multiple tasks in parallel
|
|
90
|
-
|
|
91
|
-
|
|
281
|
+
${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.BUILDER}", description: "Implement X", prompt: "...", background: true })
|
|
282
|
+
${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.INSPECTOR}", description: "Review Y", prompt: "...", background: true })
|
|
92
283
|
|
|
93
284
|
// Continue other work (don't wait!)
|
|
94
285
|
|
|
95
286
|
// When notified "All Complete":
|
|
96
|
-
|
|
287
|
+
${TOOL_NAMES.GET_TASK_RESULT}({ taskId: "${ID_PREFIX.TASK}xxx" })
|
|
97
288
|
\`\`\`
|
|
98
289
|
|
|
99
290
|
EXAMPLE - SYNC (rare):
|
|
100
291
|
\`\`\`
|
|
101
292
|
// Only when you absolutely need the result now
|
|
102
|
-
const result =
|
|
293
|
+
const result = ${TOOL_NAMES.DELEGATE_TASK}({ agent: "${AGENT_NAMES.BUILDER}", ..., background: false })
|
|
103
294
|
// Result is immediately available
|
|
104
295
|
\`\`\`
|
|
296
|
+
|
|
297
|
+
EXAMPLE - RESUME (for retry or follow-up):
|
|
298
|
+
\`\`\`
|
|
299
|
+
// Previous task output shows: Session: \`${ID_PREFIX.SESSION}abc123\` (save for resume)
|
|
300
|
+
|
|
301
|
+
// Retry after failure (keeps all context!)
|
|
302
|
+
\${TOOL_NAMES.DELEGATE_TASK}({
|
|
303
|
+
agent: "\${AGENT_NAMES.BUILDER}",
|
|
304
|
+
description: "Fix previous error",
|
|
305
|
+
prompt: "The build failed with X. Please fix it.",
|
|
306
|
+
background: true,
|
|
307
|
+
resume: "${ID_PREFIX.SESSION}abc123" // \u2190 Continue existing session
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Follow-up question (saves tokens!)
|
|
311
|
+
\${TOOL_NAMES.DELEGATE_TASK}({
|
|
312
|
+
agent: "\${AGENT_NAMES.INSPECTOR}",
|
|
313
|
+
description: "Additional check",
|
|
314
|
+
prompt: "Also check for Y in the files you just reviewed.",
|
|
315
|
+
background: true,
|
|
316
|
+
resume: "${ID_PREFIX.SESSION}xyz789"
|
|
317
|
+
})
|
|
318
|
+
\`\`\`
|
|
105
319
|
</agent_calling>
|
|
106
320
|
|
|
107
321
|
<delegation_template>
|
|
@@ -123,42 +337,76 @@ During implementation:
|
|
|
123
337
|
- Run lsp_diagnostics after each change
|
|
124
338
|
|
|
125
339
|
<background_parallel_execution>
|
|
126
|
-
PARALLEL EXECUTION
|
|
340
|
+
PARALLEL EXECUTION SYSTEM:
|
|
341
|
+
|
|
342
|
+
You have access to a powerful parallel agent execution system.
|
|
343
|
+
Up to 50 agents can run simultaneously with automatic resource management.
|
|
344
|
+
|
|
345
|
+
1. **${TOOL_NAMES.DELEGATE_TASK}** - Launch agents in parallel or sync mode
|
|
346
|
+
\`\`\`
|
|
347
|
+
// PARALLEL (recommended - non-blocking)
|
|
348
|
+
${TOOL_NAMES.DELEGATE_TASK}({
|
|
349
|
+
agent: "${AGENT_NAMES.BUILDER}",
|
|
350
|
+
description: "Implement X",
|
|
351
|
+
prompt: "...",
|
|
352
|
+
background: true
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
// SYNC (blocking - wait for result)
|
|
356
|
+
${TOOL_NAMES.DELEGATE_TASK}({
|
|
357
|
+
agent: "${AGENT_NAMES.LIBRARIAN}",
|
|
358
|
+
description: "Research Y",
|
|
359
|
+
prompt: "...",
|
|
360
|
+
background: false
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
// RESUME (continue previous session)
|
|
364
|
+
${TOOL_NAMES.DELEGATE_TASK}({
|
|
365
|
+
agent: "${AGENT_NAMES.BUILDER}",
|
|
366
|
+
description: "Fix error",
|
|
367
|
+
prompt: "...",
|
|
368
|
+
background: true,
|
|
369
|
+
resume: "${ID_PREFIX.SESSION}abc123" // From previous task output
|
|
370
|
+
})
|
|
371
|
+
\`\`\`
|
|
127
372
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
\u2192 Use get_task_result({ taskId }) to retrieve results
|
|
373
|
+
2. **${TOOL_NAMES.GET_TASK_RESULT}** - Retrieve completed task output
|
|
374
|
+
\`\`\`
|
|
375
|
+
${TOOL_NAMES.GET_TASK_RESULT}({ taskId: "${ID_PREFIX.TASK}xxx" })
|
|
376
|
+
\`\`\`
|
|
133
377
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
378
|
+
3. **${TOOL_NAMES.LIST_TASKS}** - View all parallel tasks
|
|
379
|
+
\`\`\`
|
|
380
|
+
${TOOL_NAMES.LIST_TASKS}({})
|
|
381
|
+
\`\`\`
|
|
137
382
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- Batched notifications: Notifies when ALL tasks complete (not individually)
|
|
383
|
+
4. **${TOOL_NAMES.CANCEL_TASK}** - Stop a running task
|
|
384
|
+
\`\`\`
|
|
385
|
+
${TOOL_NAMES.CANCEL_TASK}({ taskId: "${ID_PREFIX.TASK}xxx" })
|
|
386
|
+
\`\`\`
|
|
143
387
|
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
388
|
+
CONCURRENCY LIMITS:
|
|
389
|
+
- Max 10 tasks per agent type (queue automatically)
|
|
390
|
+
- Max 50 total parallel sessions
|
|
391
|
+
- Auto-timeout: 60 minutes
|
|
392
|
+
- Auto-cleanup: 30 min after completion \u2192 archived to disk
|
|
147
393
|
|
|
148
394
|
SAFE PATTERNS:
|
|
149
395
|
\u2705 Builder on file A + Inspector on file B (different files)
|
|
150
396
|
\u2705 Multiple research agents (read-only)
|
|
151
397
|
\u2705 Build command + Test command (independent)
|
|
398
|
+
\u2705 Librarian research + Builder implementation (sequential deps)
|
|
152
399
|
|
|
153
400
|
UNSAFE PATTERNS:
|
|
154
401
|
\u274C Multiple builders editing SAME FILE (conflict!)
|
|
402
|
+
\u274C Waiting synchronously for many tasks (use background=true)
|
|
155
403
|
|
|
156
404
|
WORKFLOW:
|
|
157
|
-
1.
|
|
158
|
-
2.
|
|
405
|
+
1. ${TOOL_NAMES.LIST_TASKS}: Check current status first
|
|
406
|
+
2. ${TOOL_NAMES.DELEGATE_TASK} (background=true): Launch for INDEPENDENT tasks
|
|
159
407
|
3. Continue working (NO WAITING)
|
|
160
|
-
4. Wait for "All Complete"
|
|
161
|
-
5.
|
|
408
|
+
4. Wait for system notification "All Parallel Tasks Complete"
|
|
409
|
+
5. ${TOOL_NAMES.GET_TASK_RESULT}: Retrieve each result
|
|
162
410
|
</background_parallel_execution>
|
|
163
411
|
|
|
164
412
|
<verification_methods>
|
|
@@ -175,15 +423,15 @@ WORKFLOW:
|
|
|
175
423
|
| Failures | Action |
|
|
176
424
|
|----------|--------|
|
|
177
425
|
| 1-2 | Adjust approach, retry |
|
|
178
|
-
| 3+ | STOP. Call
|
|
426
|
+
| 3+ | STOP. Call ${AGENT_NAMES.ARCHITECT} for new strategy |
|
|
179
427
|
|
|
180
428
|
<empty_responses>
|
|
181
429
|
| Agent Empty (or Gibberish) | Action |
|
|
182
430
|
|----------------------------|--------|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
431
|
+
| ${AGENT_NAMES.RECORDER} | Fresh start. Proceed to survey. |
|
|
432
|
+
| ${AGENT_NAMES.ARCHITECT} | Try simpler plan yourself. |
|
|
433
|
+
| ${AGENT_NAMES.BUILDER} | Call ${AGENT_NAMES.INSPECTOR} to diagnose. |
|
|
434
|
+
| ${AGENT_NAMES.INSPECTOR} | Retry with more context. |
|
|
187
435
|
</empty_responses>
|
|
188
436
|
|
|
189
437
|
STRICT RULE: If any agent output contains gibberish, mixed-language hallucinations, or fails the language rule, REJECT it immediately and trigger a "STRICT_CLEAN_START" retry.
|
|
@@ -199,7 +447,7 @@ STRICT RULE: If any agent output contains gibberish, mixed-language hallucinatio
|
|
|
199
447
|
Done when: Request fulfilled + lsp clean + build/test/audit pass.
|
|
200
448
|
|
|
201
449
|
<output_format>
|
|
202
|
-
|
|
450
|
+
${MISSION.COMPLETE}
|
|
203
451
|
Summary: [what was done]
|
|
204
452
|
Evidence: [Specific build/test/audit results]
|
|
205
453
|
</output_format>
|
|
@@ -211,38 +459,68 @@ Evidence: [Specific build/test/audit results]
|
|
|
211
459
|
// src/agents/subagents/architect.ts
|
|
212
460
|
var architect = {
|
|
213
461
|
id: AGENT_NAMES.ARCHITECT,
|
|
214
|
-
description: "Architect - task decomposition and strategic planning",
|
|
462
|
+
description: "Architect - hierarchical task decomposition and strategic planning",
|
|
215
463
|
systemPrompt: `<role>
|
|
216
|
-
You are Architect. Break complex tasks into atomic pieces.
|
|
464
|
+
You are Architect. Break complex tasks into hierarchical, atomic pieces.
|
|
465
|
+
Create TODO trees with parallel groups and dependencies.
|
|
217
466
|
</role>
|
|
218
467
|
|
|
219
468
|
<constraints>
|
|
220
|
-
1.
|
|
221
|
-
2.
|
|
469
|
+
1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
470
|
+
2. Every task must be ATOMIC (single action).
|
|
471
|
+
3. Always include verification tasks.
|
|
222
472
|
</constraints>
|
|
223
473
|
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
474
|
+
<hierarchical_planning>
|
|
475
|
+
Create layered task structure:
|
|
476
|
+
|
|
477
|
+
LEVEL 1 (L1): Main objectives (2-5 items)
|
|
478
|
+
LEVEL 2 (L2): Sub-tasks (2-3 per L1)
|
|
479
|
+
LEVEL 3 (L3): Atomic actions (1-3 per L2)
|
|
480
|
+
|
|
481
|
+
PARALLEL GROUPING:
|
|
482
|
+
- Tasks in same parallel_group can run simultaneously
|
|
483
|
+
- Use letters: A, B, C for groups
|
|
484
|
+
- Tasks with no group run sequentially
|
|
485
|
+
|
|
486
|
+
DEPENDENCIES:
|
|
487
|
+
- Use "depends:T1,T2" for sequential requirements
|
|
488
|
+
- Parent must start before children
|
|
489
|
+
</hierarchical_planning>
|
|
228
490
|
|
|
229
491
|
<modes>
|
|
230
|
-
- PLAN: New task \u2192 create task list
|
|
492
|
+
- PLAN: New task \u2192 create hierarchical task list
|
|
231
493
|
- STRATEGY: 3+ failures \u2192 analyze and fix approach
|
|
232
494
|
</modes>
|
|
233
495
|
|
|
234
496
|
<plan_mode>
|
|
235
|
-
1.
|
|
236
|
-
2.
|
|
237
|
-
3.
|
|
238
|
-
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
|
|
239
503
|
|
|
240
504
|
<output_format>
|
|
241
505
|
MISSION: [goal in one line]
|
|
242
506
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
507
|
+
TODO_HIERARCHY:
|
|
508
|
+
- [L1] Main objective 1
|
|
509
|
+
- [L2] Sub-task 1.1 | agent:builder | parallel_group:A
|
|
510
|
+
- [L2] Sub-task 1.2 | agent:builder | parallel_group:A
|
|
511
|
+
- [L2] Sub-task 1.3 | agent:inspector | depends:1.1,1.2
|
|
512
|
+
- [L1] Main objective 2
|
|
513
|
+
- [L2] Sub-task 2.1 | agent:librarian
|
|
514
|
+
- [L2] Sub-task 2.2 | agent:builder | depends:2.1
|
|
515
|
+
- [L3] Atomic action 2.2.1 | agent:builder
|
|
516
|
+
- [L3] Atomic action 2.2.2 | agent:builder | parallel_group:B
|
|
517
|
+
- [L3] Verify 2.2 | agent:inspector | depends:2.2.1,2.2.2
|
|
518
|
+
|
|
519
|
+
PARALLEL_EXECUTION:
|
|
520
|
+
- Group A: [1.1, 1.2] \u2192 Run simultaneously
|
|
521
|
+
- Group B: [2.2.2] \u2192 After deps complete
|
|
522
|
+
|
|
523
|
+
ESTIMATED_EFFORT: [low/medium/high]
|
|
246
524
|
</output_format>
|
|
247
525
|
</plan_mode>
|
|
248
526
|
|
|
@@ -255,16 +533,24 @@ ROOT CAUSE: [actual problem]
|
|
|
255
533
|
|
|
256
534
|
NEW APPROACH: [different strategy]
|
|
257
535
|
|
|
258
|
-
|
|
259
|
-
|
|
536
|
+
REVISED_HIERARCHY:
|
|
537
|
+
- [L1] ...
|
|
260
538
|
</output_format>
|
|
261
539
|
</strategy_mode>
|
|
262
540
|
|
|
541
|
+
<agents_available>
|
|
542
|
+
- builder: Code implementation
|
|
543
|
+
- inspector: Verification and bug fixing
|
|
544
|
+
- librarian: Documentation research (use BEFORE unfamiliar APIs)
|
|
545
|
+
- researcher: Pre-task investigation
|
|
546
|
+
</agents_available>
|
|
547
|
+
|
|
263
548
|
<rules>
|
|
264
549
|
- One action per task
|
|
265
|
-
- Always end with inspector task
|
|
266
|
-
- Group unrelated tasks (parallel)
|
|
267
|
-
-
|
|
550
|
+
- Always end branches with inspector task
|
|
551
|
+
- Group unrelated tasks (parallel execution)
|
|
552
|
+
- Use librarian/researcher before implementing unfamiliar features
|
|
553
|
+
- Be specific about files, patterns, and verification
|
|
268
554
|
</rules>`,
|
|
269
555
|
canWrite: false,
|
|
270
556
|
canBash: false
|
|
@@ -279,8 +565,7 @@ You are Builder. Write code that works.
|
|
|
279
565
|
</role>
|
|
280
566
|
|
|
281
567
|
<constraints>
|
|
282
|
-
1.
|
|
283
|
-
2. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
568
|
+
1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
284
569
|
</constraints>
|
|
285
570
|
|
|
286
571
|
<scalable_attention>
|
|
@@ -320,6 +605,20 @@ Depending on project type, verify with:
|
|
|
320
605
|
|
|
321
606
|
If build command exists in package.json, use it.
|
|
322
607
|
If using Docker/containers, verify syntax only.
|
|
608
|
+
|
|
609
|
+
BACKGROUND COMMANDS (for long-running builds):
|
|
610
|
+
\`\`\`
|
|
611
|
+
// Non-blocking build
|
|
612
|
+
run_background({ command: "npm run build" })
|
|
613
|
+
|
|
614
|
+
// Check status later
|
|
615
|
+
check_background({ taskId: "job_xxx" })
|
|
616
|
+
|
|
617
|
+
// List all background jobs
|
|
618
|
+
list_background({})
|
|
619
|
+
\`\`\`
|
|
620
|
+
|
|
621
|
+
Use background for builds taking >5 seconds.
|
|
323
622
|
</verification>
|
|
324
623
|
|
|
325
624
|
<output_format>
|
|
@@ -339,19 +638,20 @@ If build fails, FIX IT before reporting. Never leave broken code.
|
|
|
339
638
|
// src/agents/subagents/inspector.ts
|
|
340
639
|
var inspector = {
|
|
341
640
|
id: AGENT_NAMES.INSPECTOR,
|
|
342
|
-
description: "Inspector - quality verification
|
|
641
|
+
description: "Inspector - quality verification, bug fixing, and documentation validation",
|
|
343
642
|
systemPrompt: `<role>
|
|
344
643
|
You are Inspector. Prove failure or success with evidence.
|
|
644
|
+
Also verify that implementations match official documentation.
|
|
345
645
|
</role>
|
|
346
646
|
|
|
347
647
|
<constraints>
|
|
348
|
-
1.
|
|
349
|
-
2.
|
|
648
|
+
1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
649
|
+
2. Never approve code that contradicts cached documentation.
|
|
350
650
|
</constraints>
|
|
351
651
|
|
|
352
652
|
<scalable_audit>
|
|
353
653
|
- **Fast Track**: Verify syntax + quick logic check.
|
|
354
|
-
- **Deep Track**: Verify build + tests + types + security + logic.
|
|
654
|
+
- **Deep Track**: Verify build + tests + types + security + logic + doc compliance.
|
|
355
655
|
</scalable_audit>
|
|
356
656
|
|
|
357
657
|
<audit_checklist>
|
|
@@ -360,9 +660,27 @@ You are Inspector. Prove failure or success with evidence.
|
|
|
360
660
|
3. ENV-SPECIFIC:
|
|
361
661
|
- Docker: check Dockerfile syntax or run container logs if possible
|
|
362
662
|
- Frontend: check if build artifacts are generated
|
|
363
|
-
4.
|
|
663
|
+
4. DOCUMENTATION: Check .cache/docs/ for relevant docs
|
|
664
|
+
5. MANUAL: If no automated tests, read code to verify logic 100%
|
|
364
665
|
</audit_checklist>
|
|
365
666
|
|
|
667
|
+
<documentation_verification>
|
|
668
|
+
ALWAYS CHECK CACHED DOCS:
|
|
669
|
+
1. cache_docs({ action: "list" }) - See available documentation
|
|
670
|
+
2. cache_docs({ action: "get", filename: "..." }) - Review specific doc
|
|
671
|
+
3. Compare implementation against official patterns
|
|
672
|
+
|
|
673
|
+
VERIFICATION_OUTPUT:
|
|
674
|
+
- DOC_MATCH: [yes/no]
|
|
675
|
+
- DEVIATIONS: [list any differences from official docs]
|
|
676
|
+
- RECOMMENDATION: [fix/accept with reason]
|
|
677
|
+
|
|
678
|
+
WHEN CODE DOESN'T MATCH DOCS:
|
|
679
|
+
1. Flag the deviation
|
|
680
|
+
2. Explain the risk
|
|
681
|
+
3. Suggest the documented approach
|
|
682
|
+
</documentation_verification>
|
|
683
|
+
|
|
366
684
|
<verification_by_context>
|
|
367
685
|
| Project Infra | Primary Evidence |
|
|
368
686
|
|---------------|------------------|
|
|
@@ -385,19 +703,22 @@ ALWAYS prefer background for build/test commands.
|
|
|
385
703
|
<pass>
|
|
386
704
|
\u2705 PASS
|
|
387
705
|
Evidence: [Specific output/log proving success]
|
|
706
|
+
Doc Compliance: [Matches cached docs / No relevant docs]
|
|
388
707
|
</pass>
|
|
389
708
|
|
|
390
709
|
<fail>
|
|
391
710
|
\u274C FAIL
|
|
392
711
|
Issue: [What went wrong]
|
|
712
|
+
Doc Reference: [If applicable, which doc was violated]
|
|
393
713
|
Fixing...
|
|
394
714
|
</fail>
|
|
395
715
|
</output_format>
|
|
396
716
|
|
|
397
717
|
<fix_mode>
|
|
398
718
|
1. Diagnose root cause
|
|
399
|
-
2.
|
|
400
|
-
3.
|
|
719
|
+
2. Check .cache/docs/ for correct pattern
|
|
720
|
+
3. Minimal fix using documented approach
|
|
721
|
+
4. Re-verify with even more rigor
|
|
401
722
|
</fix_mode>`,
|
|
402
723
|
canWrite: true,
|
|
403
724
|
canBash: true
|
|
@@ -412,8 +733,7 @@ You are Recorder. Save and load work progress.
|
|
|
412
733
|
</role>
|
|
413
734
|
|
|
414
735
|
<constraints>
|
|
415
|
-
1.
|
|
416
|
-
2. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
736
|
+
1. If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
417
737
|
</constraints>
|
|
418
738
|
|
|
419
739
|
<purpose>
|
|
@@ -470,13 +790,216 @@ Never stop the flow. No context = fresh start = OK.
|
|
|
470
790
|
canBash: true
|
|
471
791
|
};
|
|
472
792
|
|
|
793
|
+
// src/agents/subagents/librarian.ts
|
|
794
|
+
var librarian = {
|
|
795
|
+
id: AGENT_NAMES.LIBRARIAN,
|
|
796
|
+
description: "Librarian - Documentation and API research specialist",
|
|
797
|
+
systemPrompt: `<role>
|
|
798
|
+
You are Librarian. Find official documentation and verified information.
|
|
799
|
+
Your job: Eliminate hallucination through rigorous research.
|
|
800
|
+
</role>
|
|
801
|
+
|
|
802
|
+
<critical_rule>
|
|
803
|
+
NEVER GUESS. NEVER ASSUME. ALWAYS VERIFY.
|
|
804
|
+
If you don't know something, SEARCH for it.
|
|
805
|
+
</critical_rule>
|
|
806
|
+
|
|
807
|
+
<workflow>
|
|
808
|
+
1. IDENTIFY: What documentation/API info is needed?
|
|
809
|
+
2. SEARCH: Use webfetch/websearch to find official sources
|
|
810
|
+
3. VERIFY: Cross-check from multiple sources
|
|
811
|
+
4. CACHE: Save important docs to .cache/docs/ for team reference
|
|
812
|
+
5. RETURN: Structured findings with permalinks/citations
|
|
813
|
+
</workflow>
|
|
814
|
+
|
|
815
|
+
<search_strategy>
|
|
816
|
+
PRIORITY ORDER for sources:
|
|
817
|
+
1. Official documentation sites (docs.*, *.dev, *.io)
|
|
818
|
+
2. GitHub README and source code
|
|
819
|
+
3. Official blog posts/announcements
|
|
820
|
+
4. Stack Overflow (verified answers only)
|
|
821
|
+
5. Community tutorials (with caution)
|
|
822
|
+
|
|
823
|
+
AVOID:
|
|
824
|
+
- Outdated articles (check dates!)
|
|
825
|
+
- AI-generated content
|
|
826
|
+
- Unofficial summaries
|
|
827
|
+
</search_strategy>
|
|
828
|
+
|
|
829
|
+
<caching>
|
|
830
|
+
Cache documents when:
|
|
831
|
+
- API reference needed multiple times
|
|
832
|
+
- Complex setup instructions
|
|
833
|
+
- Version-specific information
|
|
834
|
+
- Team members may need access
|
|
835
|
+
|
|
836
|
+
Cache location: .cache/docs/
|
|
837
|
+
Filename format: {domain}_{topic}.md
|
|
838
|
+
Example: nextjs_app-router.md
|
|
839
|
+
</caching>
|
|
840
|
+
|
|
841
|
+
<output_format>
|
|
842
|
+
RESEARCH REPORT
|
|
843
|
+
===============
|
|
844
|
+
|
|
845
|
+
QUERY: [What was asked]
|
|
846
|
+
|
|
847
|
+
SOURCES CONSULTED:
|
|
848
|
+
1. [Official Doc URL] - [Key insight]
|
|
849
|
+
2. [Source URL] - [Key insight]
|
|
850
|
+
|
|
851
|
+
VERIFIED ANSWER:
|
|
852
|
+
[Detailed, accurate answer with inline citations]
|
|
853
|
+
|
|
854
|
+
CACHED DOCUMENTS:
|
|
855
|
+
- .cache/docs/[filename]: [description]
|
|
856
|
+
(or "No caching needed" if trivial lookup)
|
|
857
|
+
|
|
858
|
+
CONFIDENCE: [HIGH/MEDIUM/LOW]
|
|
859
|
+
- HIGH: Found in official docs, multiple sources agree
|
|
860
|
+
- MEDIUM: Found in reliable sources, some interpretation needed
|
|
861
|
+
- LOW: Limited sources, may need manual verification
|
|
862
|
+
|
|
863
|
+
CAVEATS:
|
|
864
|
+
- [Any limitations or version-specific notes]
|
|
865
|
+
</output_format>
|
|
866
|
+
|
|
867
|
+
<tools_to_use>
|
|
868
|
+
- webfetch: For fetching specific documentation pages
|
|
869
|
+
- websearch: For finding relevant documentation
|
|
870
|
+
- grep_search: For finding patterns in local codebase
|
|
871
|
+
- glob_search: For finding files
|
|
872
|
+
- Edit tool: ONLY for writing to .cache/docs/
|
|
873
|
+
</tools_to_use>
|
|
874
|
+
|
|
875
|
+
<example_queries>
|
|
876
|
+
Q: "How do I use the new App Router in Next.js 14?"
|
|
877
|
+
\u2192 Search official Next.js docs
|
|
878
|
+
\u2192 Find App Router section
|
|
879
|
+
\u2192 Cache key patterns to .cache/docs/nextjs_app-router.md
|
|
880
|
+
\u2192 Return verified answer with citations
|
|
881
|
+
|
|
882
|
+
Q: "What's the correct way to use useEffect cleanup?"
|
|
883
|
+
\u2192 Search React docs
|
|
884
|
+
\u2192 Find Effects section
|
|
885
|
+
\u2192 Return verified pattern with permalink
|
|
886
|
+
</example_queries>`,
|
|
887
|
+
canWrite: true,
|
|
888
|
+
// Only for .cache/docs/
|
|
889
|
+
canBash: true
|
|
890
|
+
// For curl/search commands if needed
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// src/agents/subagents/researcher.ts
|
|
894
|
+
var researcher = {
|
|
895
|
+
id: AGENT_NAMES.RESEARCHER,
|
|
896
|
+
description: "Researcher - Pre-task investigation and documentation specialist",
|
|
897
|
+
systemPrompt: `<role>
|
|
898
|
+
You are Researcher. Gather all necessary information BEFORE implementation begins.
|
|
899
|
+
Your job: Ensure the team has complete, verified information before coding.
|
|
900
|
+
</role>
|
|
901
|
+
|
|
902
|
+
<critical_rule>
|
|
903
|
+
INVESTIGATE FIRST. CODE NEVER.
|
|
904
|
+
You are read-only. Your output is INFORMATION, not code.
|
|
905
|
+
</critical_rule>
|
|
906
|
+
|
|
907
|
+
<workflow>
|
|
908
|
+
1. ANALYZE: Understand the task requirements fully
|
|
909
|
+
2. IDENTIFY: List unfamiliar technologies, APIs, patterns
|
|
910
|
+
3. SEARCH: Find official documentation for each
|
|
911
|
+
4. SCAN: Find existing patterns in codebase
|
|
912
|
+
5. CACHE: Save important docs for team reference
|
|
913
|
+
6. REPORT: Deliver structured findings
|
|
914
|
+
</workflow>
|
|
915
|
+
|
|
916
|
+
<search_strategy>
|
|
917
|
+
FOR EACH UNKNOWN TECHNOLOGY:
|
|
918
|
+
1. websearch({ query: "[tech] official documentation [version]" })
|
|
919
|
+
2. webfetch({ url: "[official docs url]", cache: true })
|
|
920
|
+
|
|
921
|
+
FOR CODEBASE PATTERNS:
|
|
922
|
+
1. grep_search({ query: "[pattern]" })
|
|
923
|
+
2. glob_search({ pattern: "*.[ext]" })
|
|
924
|
+
|
|
925
|
+
FOR API USAGE:
|
|
926
|
+
1. Search for import statements: grep_search({ query: "import.*[library]" })
|
|
927
|
+
2. Find usage examples in existing code
|
|
928
|
+
</search_strategy>
|
|
929
|
+
|
|
930
|
+
<output_format>
|
|
931
|
+
# RESEARCH REPORT
|
|
932
|
+
|
|
933
|
+
## Task Summary
|
|
934
|
+
[What needs to be implemented]
|
|
935
|
+
|
|
936
|
+
## Technologies Involved
|
|
937
|
+
| Technology | Version | Official Docs | Key Insights |
|
|
938
|
+
|------------|---------|---------------|--------------|
|
|
939
|
+
| [tech1] | [ver] | [url] | [insight] |
|
|
940
|
+
|
|
941
|
+
## Codebase Patterns Found
|
|
942
|
+
- **Pattern 1**: [description]
|
|
943
|
+
- Location: [file:line]
|
|
944
|
+
- Usage: \`[code example]\`
|
|
945
|
+
|
|
946
|
+
## Cached Documentation
|
|
947
|
+
| Filename | Description |
|
|
948
|
+
|----------|-------------|
|
|
949
|
+
| .cache/docs/[file] | [description] |
|
|
950
|
+
|
|
951
|
+
## Dependencies Identified
|
|
952
|
+
- [dependency 1]: [purpose]
|
|
953
|
+
- [dependency 2]: [purpose]
|
|
954
|
+
|
|
955
|
+
## Recommended Approach
|
|
956
|
+
1. [Step 1]
|
|
957
|
+
2. [Step 2]
|
|
958
|
+
3. [Step 3]
|
|
959
|
+
|
|
960
|
+
## Potential Risks
|
|
961
|
+
- [Risk 1]: [mitigation]
|
|
962
|
+
- [Risk 2]: [mitigation]
|
|
963
|
+
|
|
964
|
+
## Knowledge Gaps
|
|
965
|
+
- [Gap 1]: [what's still unknown]
|
|
966
|
+
|
|
967
|
+
## READY FOR IMPLEMENTATION: [YES/NO]
|
|
968
|
+
[If NO, explain what additional research is needed]
|
|
969
|
+
</output_format>
|
|
970
|
+
|
|
971
|
+
<examples>
|
|
972
|
+
Task: "Implement OAuth login with Google"
|
|
973
|
+
|
|
974
|
+
1. SEARCH: Google OAuth documentation
|
|
975
|
+
2. SEARCH: Existing auth patterns in codebase
|
|
976
|
+
3. CACHE: Google OAuth setup guide
|
|
977
|
+
4. FIND: How other providers are implemented
|
|
978
|
+
5. REPORT: Complete research with recommendations
|
|
979
|
+
</examples>
|
|
980
|
+
|
|
981
|
+
<constraints>
|
|
982
|
+
1. DO NOT write any implementation code
|
|
983
|
+
2. DO NOT make assumptions - verify everything
|
|
984
|
+
3. DO NOT skip caching important documentation
|
|
985
|
+
4. ALWAYS provide source URLs for claims
|
|
986
|
+
5. ALWAYS note version requirements
|
|
987
|
+
</constraints>`,
|
|
988
|
+
canWrite: true,
|
|
989
|
+
// Only for .cache/docs/
|
|
990
|
+
canBash: false
|
|
991
|
+
// No execution needed
|
|
992
|
+
};
|
|
993
|
+
|
|
473
994
|
// src/agents/definitions.ts
|
|
474
995
|
var AGENTS = {
|
|
475
996
|
[AGENT_NAMES.COMMANDER]: orchestrator,
|
|
476
997
|
[AGENT_NAMES.ARCHITECT]: architect,
|
|
477
998
|
[AGENT_NAMES.BUILDER]: builder,
|
|
478
999
|
[AGENT_NAMES.INSPECTOR]: inspector,
|
|
479
|
-
[AGENT_NAMES.RECORDER]: recorder
|
|
1000
|
+
[AGENT_NAMES.RECORDER]: recorder,
|
|
1001
|
+
[AGENT_NAMES.LIBRARIAN]: librarian,
|
|
1002
|
+
[AGENT_NAMES.RESEARCHER]: researcher
|
|
480
1003
|
};
|
|
481
1004
|
|
|
482
1005
|
// src/core/orchestrator/task-graph.ts
|
|
@@ -1202,10 +1725,10 @@ function jsonStringifyReplacer(_, value) {
|
|
|
1202
1725
|
return value;
|
|
1203
1726
|
}
|
|
1204
1727
|
function cached(getter) {
|
|
1205
|
-
const
|
|
1728
|
+
const set3 = false;
|
|
1206
1729
|
return {
|
|
1207
1730
|
get value() {
|
|
1208
|
-
if (!
|
|
1731
|
+
if (!set3) {
|
|
1209
1732
|
const value = getter();
|
|
1210
1733
|
Object.defineProperty(this, "value", { value });
|
|
1211
1734
|
return value;
|
|
@@ -1282,10 +1805,10 @@ function mergeDefs(...defs) {
|
|
|
1282
1805
|
function cloneDef(schema) {
|
|
1283
1806
|
return mergeDefs(schema._zod.def);
|
|
1284
1807
|
}
|
|
1285
|
-
function getElementAtPath(obj,
|
|
1286
|
-
if (!
|
|
1808
|
+
function getElementAtPath(obj, path3) {
|
|
1809
|
+
if (!path3)
|
|
1287
1810
|
return obj;
|
|
1288
|
-
return
|
|
1811
|
+
return path3.reduce((acc, key) => acc?.[key], obj);
|
|
1289
1812
|
}
|
|
1290
1813
|
function promiseAllObject(promisesObj) {
|
|
1291
1814
|
const keys = Object.keys(promisesObj);
|
|
@@ -1646,11 +2169,11 @@ function aborted(x, startIndex = 0) {
|
|
|
1646
2169
|
}
|
|
1647
2170
|
return false;
|
|
1648
2171
|
}
|
|
1649
|
-
function prefixIssues(
|
|
2172
|
+
function prefixIssues(path3, issues) {
|
|
1650
2173
|
return issues.map((iss) => {
|
|
1651
2174
|
var _a;
|
|
1652
2175
|
(_a = iss).path ?? (_a.path = []);
|
|
1653
|
-
iss.path.unshift(
|
|
2176
|
+
iss.path.unshift(path3);
|
|
1654
2177
|
return iss;
|
|
1655
2178
|
});
|
|
1656
2179
|
}
|
|
@@ -1818,7 +2341,7 @@ function treeifyError(error45, _mapper) {
|
|
|
1818
2341
|
return issue2.message;
|
|
1819
2342
|
};
|
|
1820
2343
|
const result = { errors: [] };
|
|
1821
|
-
const processError = (error46,
|
|
2344
|
+
const processError = (error46, path3 = []) => {
|
|
1822
2345
|
var _a, _b;
|
|
1823
2346
|
for (const issue2 of error46.issues) {
|
|
1824
2347
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -1828,7 +2351,7 @@ function treeifyError(error45, _mapper) {
|
|
|
1828
2351
|
} else if (issue2.code === "invalid_element") {
|
|
1829
2352
|
processError({ issues: issue2.issues }, issue2.path);
|
|
1830
2353
|
} else {
|
|
1831
|
-
const fullpath = [...
|
|
2354
|
+
const fullpath = [...path3, ...issue2.path];
|
|
1832
2355
|
if (fullpath.length === 0) {
|
|
1833
2356
|
result.errors.push(mapper(issue2));
|
|
1834
2357
|
continue;
|
|
@@ -1860,8 +2383,8 @@ function treeifyError(error45, _mapper) {
|
|
|
1860
2383
|
}
|
|
1861
2384
|
function toDotPath(_path) {
|
|
1862
2385
|
const segs = [];
|
|
1863
|
-
const
|
|
1864
|
-
for (const seg of
|
|
2386
|
+
const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2387
|
+
for (const seg of path3) {
|
|
1865
2388
|
if (typeof seg === "number")
|
|
1866
2389
|
segs.push(`[${seg}]`);
|
|
1867
2390
|
else if (typeof seg === "symbol")
|
|
@@ -12974,12 +13497,6 @@ function tool(input) {
|
|
|
12974
13497
|
tool.schema = external_exports;
|
|
12975
13498
|
|
|
12976
13499
|
// src/tools/callAgent.ts
|
|
12977
|
-
var AGENT_EMOJI = {
|
|
12978
|
-
[AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
|
|
12979
|
-
[AGENT_NAMES.BUILDER]: "\u{1F528}",
|
|
12980
|
-
[AGENT_NAMES.INSPECTOR]: "\u{1F50D}",
|
|
12981
|
-
[AGENT_NAMES.RECORDER]: "\u{1F4BE}"
|
|
12982
|
-
};
|
|
12983
13500
|
var callAgentTool = tool({
|
|
12984
13501
|
description: `Call a specialized agent for parallel execution.
|
|
12985
13502
|
|
|
@@ -13051,13 +13568,9 @@ var COMMANDS = {
|
|
|
13051
13568
|
"task": {
|
|
13052
13569
|
description: "Execute a mission autonomously until complete",
|
|
13053
13570
|
template: `<role>
|
|
13054
|
-
You are Commander. Complete this mission. Never stop until 100% done
|
|
13571
|
+
You are Commander. Complete this mission. Never stop until 100% done!
|
|
13055
13572
|
</role>
|
|
13056
13573
|
|
|
13057
|
-
<language_rule>
|
|
13058
|
-
CRITICAL: ALL output MUST be in English only. No exceptions. No other languages permitted.
|
|
13059
|
-
</language_rule>
|
|
13060
|
-
|
|
13061
13574
|
<phase_1 name="MANDATORY_ENVIRONMENT_SCAN">
|
|
13062
13575
|
Before any planning or coding, you MUST understand:
|
|
13063
13576
|
1. INFRA: OS-native? Container? Docker-compose? Volume-mounted?
|
|
@@ -13175,7 +13688,7 @@ ${commandList}`;
|
|
|
13175
13688
|
if (!command) return `Unknown command: /${cmdName}
|
|
13176
13689
|
|
|
13177
13690
|
${commandList}`;
|
|
13178
|
-
return command.template.replace(/\$ARGUMENTS/g, cmdArgs ||
|
|
13691
|
+
return command.template.replace(/\$ARGUMENTS/g, cmdArgs || PROMPTS.CONTINUE_DEFAULT);
|
|
13179
13692
|
}
|
|
13180
13693
|
});
|
|
13181
13694
|
}
|
|
@@ -13294,47 +13807,6 @@ var mgrepTool = (directory) => tool({
|
|
|
13294
13807
|
// src/core/commands/manager.ts
|
|
13295
13808
|
import { spawn as spawn2 } from "child_process";
|
|
13296
13809
|
import { randomBytes } from "crypto";
|
|
13297
|
-
|
|
13298
|
-
// src/shared/constants.ts
|
|
13299
|
-
var TIME = {
|
|
13300
|
-
SECOND: 1e3,
|
|
13301
|
-
MINUTE: 60 * 1e3,
|
|
13302
|
-
HOUR: 60 * 60 * 1e3
|
|
13303
|
-
};
|
|
13304
|
-
var ID_PREFIX = {
|
|
13305
|
-
/** Parallel agent task ID (e.g., task_a1b2c3d4) */
|
|
13306
|
-
TASK: "task_",
|
|
13307
|
-
/** Background command job ID (e.g., job_a1b2c3d4) */
|
|
13308
|
-
JOB: "job_",
|
|
13309
|
-
/** Session ID prefix */
|
|
13310
|
-
SESSION: "session_"
|
|
13311
|
-
};
|
|
13312
|
-
var PARALLEL_TASK = {
|
|
13313
|
-
TTL_MS: 30 * TIME.MINUTE,
|
|
13314
|
-
CLEANUP_DELAY_MS: 5 * TIME.MINUTE,
|
|
13315
|
-
MIN_STABILITY_MS: 5 * TIME.SECOND,
|
|
13316
|
-
POLL_INTERVAL_MS: 2e3,
|
|
13317
|
-
DEFAULT_CONCURRENCY: 3,
|
|
13318
|
-
MAX_CONCURRENCY: 10
|
|
13319
|
-
};
|
|
13320
|
-
var BACKGROUND_TASK = {
|
|
13321
|
-
DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
|
|
13322
|
-
MAX_OUTPUT_LENGTH: 1e4
|
|
13323
|
-
};
|
|
13324
|
-
var STATUS_EMOJI = {
|
|
13325
|
-
pending: "\u23F3",
|
|
13326
|
-
running: "\u{1F504}",
|
|
13327
|
-
completed: "\u2705",
|
|
13328
|
-
done: "\u2705",
|
|
13329
|
-
error: "\u274C",
|
|
13330
|
-
timeout: "\u23F0",
|
|
13331
|
-
cancelled: "\u{1F6AB}"
|
|
13332
|
-
};
|
|
13333
|
-
function getStatusEmoji(status) {
|
|
13334
|
-
return STATUS_EMOJI[status] ?? "\u2753";
|
|
13335
|
-
}
|
|
13336
|
-
|
|
13337
|
-
// src/core/commands/manager.ts
|
|
13338
13810
|
var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
13339
13811
|
static _instance;
|
|
13340
13812
|
tasks = /* @__PURE__ */ new Map();
|
|
@@ -13701,12 +14173,18 @@ var ConcurrencyController = class {
|
|
|
13701
14173
|
};
|
|
13702
14174
|
|
|
13703
14175
|
// src/core/agents/task-store.ts
|
|
14176
|
+
import * as fs from "node:fs/promises";
|
|
14177
|
+
import * as path from "node:path";
|
|
13704
14178
|
var TaskStore = class {
|
|
13705
14179
|
tasks = /* @__PURE__ */ new Map();
|
|
13706
14180
|
pendingByParent = /* @__PURE__ */ new Map();
|
|
13707
14181
|
notifications = /* @__PURE__ */ new Map();
|
|
14182
|
+
archivedCount = 0;
|
|
13708
14183
|
set(id, task) {
|
|
13709
14184
|
this.tasks.set(id, task);
|
|
14185
|
+
if (this.tasks.size > MEMORY_LIMITS.MAX_TASKS_IN_MEMORY) {
|
|
14186
|
+
this.gc();
|
|
14187
|
+
}
|
|
13710
14188
|
}
|
|
13711
14189
|
get(id) {
|
|
13712
14190
|
return this.tasks.get(id);
|
|
@@ -13715,7 +14193,7 @@ var TaskStore = class {
|
|
|
13715
14193
|
return Array.from(this.tasks.values());
|
|
13716
14194
|
}
|
|
13717
14195
|
getRunning() {
|
|
13718
|
-
return this.getAll().filter((t) => t.status ===
|
|
14196
|
+
return this.getAll().filter((t) => t.status === TASK_STATUS.RUNNING);
|
|
13719
14197
|
}
|
|
13720
14198
|
getByParent(parentSessionID) {
|
|
13721
14199
|
return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
|
|
@@ -13749,10 +14227,13 @@ var TaskStore = class {
|
|
|
13749
14227
|
hasPending(parentSessionID) {
|
|
13750
14228
|
return this.getPendingCount(parentSessionID) > 0;
|
|
13751
14229
|
}
|
|
13752
|
-
// Notifications
|
|
14230
|
+
// Notifications with limit
|
|
13753
14231
|
queueNotification(task) {
|
|
13754
14232
|
const queue = this.notifications.get(task.parentSessionID) ?? [];
|
|
13755
14233
|
queue.push(task);
|
|
14234
|
+
if (queue.length > MEMORY_LIMITS.MAX_NOTIFICATIONS_PER_PARENT) {
|
|
14235
|
+
queue.shift();
|
|
14236
|
+
}
|
|
13756
14237
|
this.notifications.set(task.parentSessionID, queue);
|
|
13757
14238
|
}
|
|
13758
14239
|
getNotifications(parentSessionID) {
|
|
@@ -13768,9 +14249,6 @@ var TaskStore = class {
|
|
|
13768
14249
|
}
|
|
13769
14250
|
}
|
|
13770
14251
|
}
|
|
13771
|
-
/**
|
|
13772
|
-
* Remove a specific task from all notification queues
|
|
13773
|
-
*/
|
|
13774
14252
|
clearNotificationsForTask(taskId) {
|
|
13775
14253
|
for (const [sessionID, tasks] of this.notifications.entries()) {
|
|
13776
14254
|
const filtered = tasks.filter((t) => t.id !== taskId);
|
|
@@ -13781,14 +14259,88 @@ var TaskStore = class {
|
|
|
13781
14259
|
}
|
|
13782
14260
|
}
|
|
13783
14261
|
}
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
//
|
|
13787
|
-
|
|
13788
|
-
|
|
13789
|
-
|
|
13790
|
-
|
|
13791
|
-
|
|
14262
|
+
// =========================================================================
|
|
14263
|
+
// Garbage Collection & Memory Management
|
|
14264
|
+
// =========================================================================
|
|
14265
|
+
/**
|
|
14266
|
+
* Get memory statistics
|
|
14267
|
+
*/
|
|
14268
|
+
getStats() {
|
|
14269
|
+
return {
|
|
14270
|
+
tasksInMemory: this.tasks.size,
|
|
14271
|
+
runningTasks: this.getRunning().length,
|
|
14272
|
+
archivedTasks: this.archivedCount,
|
|
14273
|
+
notificationQueues: this.notifications.size,
|
|
14274
|
+
pendingParents: this.pendingByParent.size
|
|
14275
|
+
};
|
|
14276
|
+
}
|
|
14277
|
+
/**
|
|
14278
|
+
* Garbage collect completed tasks
|
|
14279
|
+
* Archives old completed tasks to disk
|
|
14280
|
+
*/
|
|
14281
|
+
async gc() {
|
|
14282
|
+
const now = Date.now();
|
|
14283
|
+
const toRemove = [];
|
|
14284
|
+
const toArchive = [];
|
|
14285
|
+
for (const [id, task] of this.tasks) {
|
|
14286
|
+
if (task.status === TASK_STATUS.RUNNING) continue;
|
|
14287
|
+
const completedAt = task.completedAt?.getTime() ?? 0;
|
|
14288
|
+
const age = now - completedAt;
|
|
14289
|
+
if (age > MEMORY_LIMITS.ARCHIVE_AGE_MS && task.status === TASK_STATUS.COMPLETED) {
|
|
14290
|
+
toArchive.push(task);
|
|
14291
|
+
toRemove.push(id);
|
|
14292
|
+
} else if (age > MEMORY_LIMITS.ERROR_CLEANUP_AGE_MS && (task.status === TASK_STATUS.ERROR || task.status === TASK_STATUS.CANCELLED)) {
|
|
14293
|
+
toRemove.push(id);
|
|
14294
|
+
}
|
|
14295
|
+
}
|
|
14296
|
+
if (toArchive.length > 0) {
|
|
14297
|
+
await this.archiveTasks(toArchive);
|
|
14298
|
+
}
|
|
14299
|
+
for (const id of toRemove) {
|
|
14300
|
+
this.tasks.delete(id);
|
|
14301
|
+
}
|
|
14302
|
+
return toRemove.length;
|
|
14303
|
+
}
|
|
14304
|
+
/**
|
|
14305
|
+
* Archive tasks to disk for later analysis
|
|
14306
|
+
*/
|
|
14307
|
+
async archiveTasks(tasks) {
|
|
14308
|
+
try {
|
|
14309
|
+
await fs.mkdir(PATHS.TASK_ARCHIVE, { recursive: true });
|
|
14310
|
+
const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
14311
|
+
const filename = `tasks_${date5}.jsonl`;
|
|
14312
|
+
const filepath = path.join(PATHS.TASK_ARCHIVE, filename);
|
|
14313
|
+
const lines = tasks.map((task) => JSON.stringify({
|
|
14314
|
+
id: task.id,
|
|
14315
|
+
agent: task.agent,
|
|
14316
|
+
prompt: task.prompt.slice(0, 200),
|
|
14317
|
+
// Truncate
|
|
14318
|
+
status: task.status,
|
|
14319
|
+
startedAt: task.startedAt,
|
|
14320
|
+
completedAt: task.completedAt,
|
|
14321
|
+
parentSessionID: task.parentSessionID
|
|
14322
|
+
}));
|
|
14323
|
+
await fs.appendFile(filepath, lines.join("\n") + "\n");
|
|
14324
|
+
this.archivedCount += tasks.length;
|
|
14325
|
+
} catch (error45) {
|
|
14326
|
+
console.error("[TaskStore] Archive failed:", error45);
|
|
14327
|
+
}
|
|
14328
|
+
}
|
|
14329
|
+
/**
|
|
14330
|
+
* Force cleanup of all completed tasks
|
|
14331
|
+
*/
|
|
14332
|
+
forceCleanup() {
|
|
14333
|
+
const toRemove = [];
|
|
14334
|
+
for (const [id, task] of this.tasks) {
|
|
14335
|
+
if (task.status !== "running") {
|
|
14336
|
+
toRemove.push(id);
|
|
14337
|
+
}
|
|
14338
|
+
}
|
|
14339
|
+
for (const id of toRemove) {
|
|
14340
|
+
this.tasks.delete(id);
|
|
14341
|
+
}
|
|
14342
|
+
return toRemove.length;
|
|
14343
|
+
}
|
|
13792
14344
|
};
|
|
13793
14345
|
|
|
13794
14346
|
// src/core/agents/logger.ts
|
|
@@ -13819,34 +14371,19 @@ Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
|
13819
14371
|
</system-notification>`;
|
|
13820
14372
|
}
|
|
13821
14373
|
|
|
13822
|
-
// src/core/agents/manager.ts
|
|
13823
|
-
var
|
|
13824
|
-
|
|
13825
|
-
store = new TaskStore();
|
|
13826
|
-
client;
|
|
13827
|
-
directory;
|
|
13828
|
-
concurrency = new ConcurrencyController();
|
|
13829
|
-
pollingInterval;
|
|
13830
|
-
constructor(client, directory) {
|
|
14374
|
+
// src/core/agents/manager/task-launcher.ts
|
|
14375
|
+
var TaskLauncher = class {
|
|
14376
|
+
constructor(client, directory, store, concurrency, onTaskError, startPolling) {
|
|
13831
14377
|
this.client = client;
|
|
13832
14378
|
this.directory = directory;
|
|
14379
|
+
this.store = store;
|
|
14380
|
+
this.concurrency = concurrency;
|
|
14381
|
+
this.onTaskError = onTaskError;
|
|
14382
|
+
this.startPolling = startPolling;
|
|
13833
14383
|
}
|
|
13834
|
-
static getInstance(client, directory) {
|
|
13835
|
-
if (!_ParallelAgentManager._instance) {
|
|
13836
|
-
if (!client || !directory) {
|
|
13837
|
-
throw new Error("ParallelAgentManager requires client and directory on first call");
|
|
13838
|
-
}
|
|
13839
|
-
_ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
|
|
13840
|
-
}
|
|
13841
|
-
return _ParallelAgentManager._instance;
|
|
13842
|
-
}
|
|
13843
|
-
// ========================================================================
|
|
13844
|
-
// Public API
|
|
13845
|
-
// ========================================================================
|
|
13846
14384
|
async launch(input) {
|
|
13847
14385
|
const concurrencyKey = input.agent;
|
|
13848
14386
|
await this.concurrency.acquire(concurrencyKey);
|
|
13849
|
-
this.pruneExpiredTasks();
|
|
13850
14387
|
try {
|
|
13851
14388
|
const createResult = await this.client.session.create({
|
|
13852
14389
|
body: { parentID: input.parentSessionID, title: `Parallel: ${input.description}` },
|
|
@@ -13863,8 +14400,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
13863
14400
|
sessionID,
|
|
13864
14401
|
parentSessionID: input.parentSessionID,
|
|
13865
14402
|
description: input.description,
|
|
14403
|
+
prompt: input.prompt,
|
|
13866
14404
|
agent: input.agent,
|
|
13867
|
-
status:
|
|
14405
|
+
status: TASK_STATUS.RUNNING,
|
|
13868
14406
|
startedAt: /* @__PURE__ */ new Date(),
|
|
13869
14407
|
concurrencyKey
|
|
13870
14408
|
};
|
|
@@ -13873,10 +14411,10 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
13873
14411
|
this.startPolling();
|
|
13874
14412
|
this.client.session.prompt({
|
|
13875
14413
|
path: { id: sessionID },
|
|
13876
|
-
body: { agent: input.agent, parts: [{ type:
|
|
14414
|
+
body: { agent: input.agent, parts: [{ type: PART_TYPES.TEXT, text: input.prompt }] }
|
|
13877
14415
|
}).catch((error45) => {
|
|
13878
14416
|
log2(`Prompt error for ${taskId}:`, error45);
|
|
13879
|
-
this.
|
|
14417
|
+
this.onTaskError(taskId, error45);
|
|
13880
14418
|
});
|
|
13881
14419
|
log2(`Launched ${taskId} in session ${sessionID}`);
|
|
13882
14420
|
return task;
|
|
@@ -13885,168 +14423,90 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
13885
14423
|
throw error45;
|
|
13886
14424
|
}
|
|
13887
14425
|
}
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13891
|
-
|
|
13892
|
-
|
|
14426
|
+
};
|
|
14427
|
+
|
|
14428
|
+
// src/core/agents/manager/task-resumer.ts
|
|
14429
|
+
var TaskResumer = class {
|
|
14430
|
+
constructor(client, store, findBySession, startPolling, notifyParentIfAllComplete) {
|
|
14431
|
+
this.client = client;
|
|
14432
|
+
this.store = store;
|
|
14433
|
+
this.findBySession = findBySession;
|
|
14434
|
+
this.startPolling = startPolling;
|
|
14435
|
+
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
13893
14436
|
}
|
|
13894
|
-
|
|
13895
|
-
|
|
14437
|
+
async resume(input) {
|
|
14438
|
+
const existingTask = this.findBySession(input.sessionId);
|
|
14439
|
+
if (!existingTask) {
|
|
14440
|
+
throw new Error(`Task not found for session: ${input.sessionId}`);
|
|
14441
|
+
}
|
|
14442
|
+
existingTask.status = TASK_STATUS.RUNNING;
|
|
14443
|
+
existingTask.completedAt = void 0;
|
|
14444
|
+
existingTask.error = void 0;
|
|
14445
|
+
existingTask.result = void 0;
|
|
14446
|
+
existingTask.parentSessionID = input.parentSessionID;
|
|
14447
|
+
existingTask.startedAt = /* @__PURE__ */ new Date();
|
|
14448
|
+
existingTask.stablePolls = 0;
|
|
14449
|
+
this.store.trackPending(input.parentSessionID, existingTask.id);
|
|
14450
|
+
this.startPolling();
|
|
14451
|
+
log2(`Resuming task ${existingTask.id} in session ${existingTask.sessionID}`);
|
|
14452
|
+
this.client.session.prompt({
|
|
14453
|
+
path: { id: existingTask.sessionID },
|
|
14454
|
+
body: {
|
|
14455
|
+
agent: existingTask.agent,
|
|
14456
|
+
parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
|
|
14457
|
+
}
|
|
14458
|
+
}).catch((error45) => {
|
|
14459
|
+
log2(`Resume prompt error for ${existingTask.id}:`, error45);
|
|
14460
|
+
existingTask.status = TASK_STATUS.ERROR;
|
|
14461
|
+
existingTask.error = error45 instanceof Error ? error45.message : String(error45);
|
|
14462
|
+
existingTask.completedAt = /* @__PURE__ */ new Date();
|
|
14463
|
+
this.store.untrackPending(input.parentSessionID, existingTask.id);
|
|
14464
|
+
this.store.queueNotification(existingTask);
|
|
14465
|
+
this.notifyParentIfAllComplete(input.parentSessionID).catch(() => {
|
|
14466
|
+
});
|
|
14467
|
+
});
|
|
14468
|
+
return existingTask;
|
|
13896
14469
|
}
|
|
13897
|
-
|
|
13898
|
-
|
|
14470
|
+
};
|
|
14471
|
+
|
|
14472
|
+
// src/core/agents/config.ts
|
|
14473
|
+
var CONFIG = {
|
|
14474
|
+
TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
|
|
14475
|
+
CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
|
|
14476
|
+
MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
|
|
14477
|
+
POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
|
|
14478
|
+
};
|
|
14479
|
+
|
|
14480
|
+
// src/core/agents/manager/task-poller.ts
|
|
14481
|
+
var TaskPoller = class {
|
|
14482
|
+
constructor(client, store, concurrency, notifyParentIfAllComplete, scheduleCleanup, pruneExpiredTasks) {
|
|
14483
|
+
this.client = client;
|
|
14484
|
+
this.store = store;
|
|
14485
|
+
this.concurrency = concurrency;
|
|
14486
|
+
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
14487
|
+
this.scheduleCleanup = scheduleCleanup;
|
|
14488
|
+
this.pruneExpiredTasks = pruneExpiredTasks;
|
|
13899
14489
|
}
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
if (!task || task.status !== "running") return false;
|
|
13903
|
-
task.status = "error";
|
|
13904
|
-
task.error = "Cancelled by user";
|
|
13905
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
13906
|
-
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
13907
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
13908
|
-
try {
|
|
13909
|
-
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
13910
|
-
log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
13911
|
-
} catch {
|
|
13912
|
-
log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
13913
|
-
}
|
|
13914
|
-
this.scheduleCleanup(taskId);
|
|
13915
|
-
log2(`Cancelled ${taskId}`);
|
|
13916
|
-
return true;
|
|
13917
|
-
}
|
|
13918
|
-
async getResult(taskId) {
|
|
13919
|
-
const task = this.store.get(taskId);
|
|
13920
|
-
if (!task) return null;
|
|
13921
|
-
if (task.result) return task.result;
|
|
13922
|
-
if (task.status === "error") return `Error: ${task.error}`;
|
|
13923
|
-
if (task.status === "running") return null;
|
|
13924
|
-
try {
|
|
13925
|
-
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
13926
|
-
if (result.error) return `Error: ${result.error}`;
|
|
13927
|
-
const messages = result.data ?? [];
|
|
13928
|
-
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
13929
|
-
if (!lastMsg) return "(No response)";
|
|
13930
|
-
const text = lastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
|
|
13931
|
-
task.result = text;
|
|
13932
|
-
return text;
|
|
13933
|
-
} catch (error45) {
|
|
13934
|
-
return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
13935
|
-
}
|
|
13936
|
-
}
|
|
13937
|
-
setConcurrencyLimit(agentType, limit) {
|
|
13938
|
-
this.concurrency.setLimit(agentType, limit);
|
|
13939
|
-
}
|
|
13940
|
-
getPendingCount(parentSessionID) {
|
|
13941
|
-
return this.store.getPendingCount(parentSessionID);
|
|
13942
|
-
}
|
|
13943
|
-
cleanup() {
|
|
13944
|
-
this.stopPolling();
|
|
13945
|
-
this.store.clear();
|
|
13946
|
-
}
|
|
13947
|
-
formatDuration = formatDuration;
|
|
13948
|
-
// ========================================================================
|
|
13949
|
-
// Event Handling (from OpenCode hooks)
|
|
13950
|
-
// ========================================================================
|
|
13951
|
-
/**
|
|
13952
|
-
* Handle OpenCode session events for proper resource cleanup.
|
|
13953
|
-
* Call this from your plugin's event hook.
|
|
13954
|
-
*/
|
|
13955
|
-
handleEvent(event) {
|
|
13956
|
-
const props = event.properties;
|
|
13957
|
-
if (event.type === "session.idle") {
|
|
13958
|
-
const sessionID = props?.sessionID;
|
|
13959
|
-
if (!sessionID) return;
|
|
13960
|
-
const task = this.findBySession(sessionID);
|
|
13961
|
-
if (!task || task.status !== "running") return;
|
|
13962
|
-
this.handleSessionIdle(task).catch((err) => {
|
|
13963
|
-
log2("Error handling session.idle:", err);
|
|
13964
|
-
});
|
|
13965
|
-
}
|
|
13966
|
-
if (event.type === "session.deleted") {
|
|
13967
|
-
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
13968
|
-
if (!sessionID) return;
|
|
13969
|
-
const task = this.findBySession(sessionID);
|
|
13970
|
-
if (!task) return;
|
|
13971
|
-
log2(`Session deleted event for task ${task.id}`);
|
|
13972
|
-
if (task.status === "running") {
|
|
13973
|
-
task.status = "error";
|
|
13974
|
-
task.error = "Session deleted";
|
|
13975
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
13976
|
-
}
|
|
13977
|
-
if (task.concurrencyKey) {
|
|
13978
|
-
this.concurrency.release(task.concurrencyKey);
|
|
13979
|
-
task.concurrencyKey = void 0;
|
|
13980
|
-
}
|
|
13981
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
13982
|
-
this.store.clearNotificationsForTask(task.id);
|
|
13983
|
-
this.store.delete(task.id);
|
|
13984
|
-
log2(`Cleaned up deleted session task: ${task.id}`);
|
|
13985
|
-
}
|
|
13986
|
-
}
|
|
13987
|
-
/**
|
|
13988
|
-
* Find task by session ID
|
|
13989
|
-
*/
|
|
13990
|
-
findBySession(sessionID) {
|
|
13991
|
-
return this.store.getAll().find((t) => t.sessionID === sessionID);
|
|
13992
|
-
}
|
|
13993
|
-
/**
|
|
13994
|
-
* Handle session.idle event - validate and complete task
|
|
13995
|
-
*/
|
|
13996
|
-
async handleSessionIdle(task) {
|
|
13997
|
-
const elapsed = Date.now() - task.startedAt.getTime();
|
|
13998
|
-
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
13999
|
-
log2(`Session idle but too early for ${task.id}, waiting...`);
|
|
14000
|
-
return;
|
|
14001
|
-
}
|
|
14002
|
-
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
14003
|
-
if (!hasOutput) {
|
|
14004
|
-
log2(`Session idle but no output for ${task.id}, waiting...`);
|
|
14005
|
-
return;
|
|
14006
|
-
}
|
|
14007
|
-
task.status = "completed";
|
|
14008
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
14009
|
-
if (task.concurrencyKey) {
|
|
14010
|
-
this.concurrency.release(task.concurrencyKey);
|
|
14011
|
-
task.concurrencyKey = void 0;
|
|
14012
|
-
}
|
|
14013
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14014
|
-
this.store.queueNotification(task);
|
|
14015
|
-
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14016
|
-
this.scheduleCleanup(task.id);
|
|
14017
|
-
log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14018
|
-
}
|
|
14019
|
-
// ========================================================================
|
|
14020
|
-
// Internal
|
|
14021
|
-
// ========================================================================
|
|
14022
|
-
handleTaskError(taskId, error45) {
|
|
14023
|
-
const task = this.store.get(taskId);
|
|
14024
|
-
if (!task) return;
|
|
14025
|
-
task.status = "error";
|
|
14026
|
-
task.error = error45 instanceof Error ? error45.message : String(error45);
|
|
14027
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
14028
|
-
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
14029
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
14030
|
-
this.store.queueNotification(task);
|
|
14031
|
-
this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14032
|
-
this.scheduleCleanup(taskId);
|
|
14033
|
-
}
|
|
14034
|
-
startPolling() {
|
|
14490
|
+
pollingInterval;
|
|
14491
|
+
start() {
|
|
14035
14492
|
if (this.pollingInterval) return;
|
|
14036
|
-
this.pollingInterval = setInterval(() => this.
|
|
14493
|
+
this.pollingInterval = setInterval(() => this.poll(), CONFIG.POLL_INTERVAL_MS);
|
|
14037
14494
|
this.pollingInterval.unref();
|
|
14038
14495
|
}
|
|
14039
|
-
|
|
14496
|
+
stop() {
|
|
14040
14497
|
if (this.pollingInterval) {
|
|
14041
14498
|
clearInterval(this.pollingInterval);
|
|
14042
14499
|
this.pollingInterval = void 0;
|
|
14043
14500
|
}
|
|
14044
14501
|
}
|
|
14045
|
-
|
|
14502
|
+
isRunning() {
|
|
14503
|
+
return !!this.pollingInterval;
|
|
14504
|
+
}
|
|
14505
|
+
async poll() {
|
|
14046
14506
|
this.pruneExpiredTasks();
|
|
14047
14507
|
const running = this.store.getRunning();
|
|
14048
14508
|
if (running.length === 0) {
|
|
14049
|
-
this.
|
|
14509
|
+
this.stop();
|
|
14050
14510
|
return;
|
|
14051
14511
|
}
|
|
14052
14512
|
try {
|
|
@@ -14078,9 +14538,28 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14078
14538
|
log2("Polling error:", error45);
|
|
14079
14539
|
}
|
|
14080
14540
|
}
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14541
|
+
async validateSessionHasOutput(sessionID) {
|
|
14542
|
+
try {
|
|
14543
|
+
const response = await this.client.session.messages({ path: { id: sessionID } });
|
|
14544
|
+
const messages = response.data ?? [];
|
|
14545
|
+
return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === PART_TYPES.TEXT && p.text?.trim() || p.type === "tool"));
|
|
14546
|
+
} catch {
|
|
14547
|
+
return true;
|
|
14548
|
+
}
|
|
14549
|
+
}
|
|
14550
|
+
async completeTask(task) {
|
|
14551
|
+
task.status = TASK_STATUS.COMPLETED;
|
|
14552
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14553
|
+
if (task.concurrencyKey) {
|
|
14554
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14555
|
+
task.concurrencyKey = void 0;
|
|
14556
|
+
}
|
|
14557
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14558
|
+
this.store.queueNotification(task);
|
|
14559
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14560
|
+
this.scheduleCleanup(task.id);
|
|
14561
|
+
log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14562
|
+
}
|
|
14084
14563
|
async updateTaskProgress(task) {
|
|
14085
14564
|
try {
|
|
14086
14565
|
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
@@ -14096,7 +14575,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14096
14575
|
toolCalls++;
|
|
14097
14576
|
lastTool = part.tool || part.name;
|
|
14098
14577
|
}
|
|
14099
|
-
if (part.type ===
|
|
14578
|
+
if (part.type === PART_TYPES.TEXT && part.text) {
|
|
14100
14579
|
lastMessage = part.text;
|
|
14101
14580
|
}
|
|
14102
14581
|
}
|
|
@@ -14117,30 +14596,14 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14117
14596
|
} catch {
|
|
14118
14597
|
}
|
|
14119
14598
|
}
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
task.concurrencyKey = void 0;
|
|
14129
|
-
}
|
|
14130
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14131
|
-
this.store.queueNotification(task);
|
|
14132
|
-
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14133
|
-
this.scheduleCleanup(task.id);
|
|
14134
|
-
log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14135
|
-
}
|
|
14136
|
-
async validateSessionHasOutput(sessionID) {
|
|
14137
|
-
try {
|
|
14138
|
-
const response = await this.client.session.messages({ path: { id: sessionID } });
|
|
14139
|
-
const messages = response.data ?? [];
|
|
14140
|
-
return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text?.trim() || p.type === "tool"));
|
|
14141
|
-
} catch {
|
|
14142
|
-
return true;
|
|
14143
|
-
}
|
|
14599
|
+
};
|
|
14600
|
+
|
|
14601
|
+
// src/core/agents/manager/task-cleaner.ts
|
|
14602
|
+
var TaskCleaner = class {
|
|
14603
|
+
constructor(client, store, concurrency) {
|
|
14604
|
+
this.client = client;
|
|
14605
|
+
this.store = store;
|
|
14606
|
+
this.concurrency = concurrency;
|
|
14144
14607
|
}
|
|
14145
14608
|
pruneExpiredTasks() {
|
|
14146
14609
|
const now = Date.now();
|
|
@@ -14148,8 +14611,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14148
14611
|
const age = now - task.startedAt.getTime();
|
|
14149
14612
|
if (age <= CONFIG.TASK_TTL_MS) continue;
|
|
14150
14613
|
log2(`Timeout: ${taskId}`);
|
|
14151
|
-
if (task.status ===
|
|
14152
|
-
task.status =
|
|
14614
|
+
if (task.status === TASK_STATUS.RUNNING) {
|
|
14615
|
+
task.status = TASK_STATUS.TIMEOUT;
|
|
14153
14616
|
task.error = "Task exceeded 30 minute time limit";
|
|
14154
14617
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
14155
14618
|
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
@@ -14183,7 +14646,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14183
14646
|
try {
|
|
14184
14647
|
await this.client.session.prompt({
|
|
14185
14648
|
path: { id: parentSessionID },
|
|
14186
|
-
body: { noReply: true, parts: [{ type:
|
|
14649
|
+
body: { noReply: true, parts: [{ type: PART_TYPES.TEXT, text: message }] }
|
|
14187
14650
|
});
|
|
14188
14651
|
log2(`Notified parent ${parentSessionID}`);
|
|
14189
14652
|
} catch (error45) {
|
|
@@ -14192,76 +14655,532 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14192
14655
|
this.store.clearNotifications(parentSessionID);
|
|
14193
14656
|
}
|
|
14194
14657
|
};
|
|
14195
|
-
var parallelAgentManager = {
|
|
14196
|
-
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
|
|
14197
|
-
};
|
|
14198
|
-
|
|
14199
|
-
// src/tools/parallel/delegate-task.ts
|
|
14200
|
-
var createDelegateTaskTool = (manager, client) => tool({
|
|
14201
|
-
description: `Delegate a task to an agent.
|
|
14202
14658
|
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14659
|
+
// src/shared/event-types.ts
|
|
14660
|
+
var TASK_EVENTS = {
|
|
14661
|
+
STARTED: "task.started",
|
|
14662
|
+
COMPLETED: "task.completed",
|
|
14663
|
+
FAILED: "task.failed",
|
|
14664
|
+
CANCELLED: "task.cancelled"
|
|
14665
|
+
};
|
|
14666
|
+
var TODO_EVENTS = {
|
|
14667
|
+
CREATED: "todo.created",
|
|
14668
|
+
UPDATED: "todo.updated",
|
|
14669
|
+
COMPLETED: "todo.completed"
|
|
14670
|
+
};
|
|
14671
|
+
var SESSION_EVENTS = {
|
|
14672
|
+
IDLE: "session.idle",
|
|
14673
|
+
BUSY: "session.busy",
|
|
14674
|
+
ERROR: "session.error",
|
|
14675
|
+
DELETED: "session.deleted"
|
|
14676
|
+
};
|
|
14677
|
+
var DOCUMENT_EVENTS = {
|
|
14678
|
+
CACHED: "document.cached",
|
|
14679
|
+
EXPIRED: "document.expired"
|
|
14680
|
+
};
|
|
14681
|
+
var MISSION_EVENTS = {
|
|
14682
|
+
COMPLETE: "mission.complete",
|
|
14683
|
+
FAILED: "mission.failed",
|
|
14684
|
+
ALL_TASKS_COMPLETE: "all_tasks.complete"
|
|
14685
|
+
};
|
|
14686
|
+
var SPECIAL_EVENTS = {
|
|
14687
|
+
WILDCARD: "*"
|
|
14688
|
+
};
|
|
14689
|
+
var EVENT_TYPES = {
|
|
14690
|
+
...TASK_EVENTS,
|
|
14691
|
+
...TODO_EVENTS,
|
|
14692
|
+
...SESSION_EVENTS,
|
|
14693
|
+
...DOCUMENT_EVENTS,
|
|
14694
|
+
...MISSION_EVENTS,
|
|
14695
|
+
...SPECIAL_EVENTS
|
|
14696
|
+
};
|
|
14207
14697
|
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
const
|
|
14220
|
-
const
|
|
14221
|
-
const
|
|
14222
|
-
|
|
14223
|
-
|
|
14698
|
+
// src/core/bus/event-bus.ts
|
|
14699
|
+
var EventBusImpl = class {
|
|
14700
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
14701
|
+
eventHistory = [];
|
|
14702
|
+
maxHistorySize = 100;
|
|
14703
|
+
subscriptionCounter = 0;
|
|
14704
|
+
/**
|
|
14705
|
+
* Subscribe to an event type
|
|
14706
|
+
* Returns unsubscribe function
|
|
14707
|
+
*/
|
|
14708
|
+
subscribe(type, handler) {
|
|
14709
|
+
const id = `sub_${++this.subscriptionCounter}`;
|
|
14710
|
+
const subscription = { id, type, handler, once: false };
|
|
14711
|
+
const existing = this.subscriptions.get(type) || [];
|
|
14712
|
+
existing.push(subscription);
|
|
14713
|
+
this.subscriptions.set(type, existing);
|
|
14714
|
+
return () => this.unsubscribe(id, type);
|
|
14715
|
+
}
|
|
14716
|
+
/**
|
|
14717
|
+
* Subscribe to an event type, auto-unsubscribe after first event
|
|
14718
|
+
*/
|
|
14719
|
+
once(type, handler) {
|
|
14720
|
+
const id = `sub_${++this.subscriptionCounter}`;
|
|
14721
|
+
const subscription = { id, type, handler, once: true };
|
|
14722
|
+
const existing = this.subscriptions.get(type) || [];
|
|
14723
|
+
existing.push(subscription);
|
|
14724
|
+
this.subscriptions.set(type, existing);
|
|
14725
|
+
return () => this.unsubscribe(id, type);
|
|
14726
|
+
}
|
|
14727
|
+
/**
|
|
14728
|
+
* Unsubscribe from an event
|
|
14729
|
+
*/
|
|
14730
|
+
unsubscribe(id, type) {
|
|
14731
|
+
const subs = this.subscriptions.get(type);
|
|
14732
|
+
if (subs) {
|
|
14733
|
+
const filtered = subs.filter((s) => s.id !== id);
|
|
14734
|
+
if (filtered.length > 0) {
|
|
14735
|
+
this.subscriptions.set(type, filtered);
|
|
14736
|
+
} else {
|
|
14737
|
+
this.subscriptions.delete(type);
|
|
14738
|
+
}
|
|
14224
14739
|
}
|
|
14225
|
-
|
|
14740
|
+
}
|
|
14741
|
+
/**
|
|
14742
|
+
* Publish an event
|
|
14743
|
+
*/
|
|
14744
|
+
async publish(type, properties = {}, options = {}) {
|
|
14745
|
+
const event = {
|
|
14746
|
+
type,
|
|
14747
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
14748
|
+
source: options.source || "unknown",
|
|
14749
|
+
sessionId: options.sessionId,
|
|
14750
|
+
properties
|
|
14751
|
+
};
|
|
14752
|
+
this.eventHistory.push(event);
|
|
14753
|
+
if (this.eventHistory.length > this.maxHistorySize) {
|
|
14754
|
+
this.eventHistory.shift();
|
|
14755
|
+
}
|
|
14756
|
+
const toNotify = [];
|
|
14757
|
+
const typeSubs = this.subscriptions.get(type) || [];
|
|
14758
|
+
toNotify.push(...typeSubs);
|
|
14759
|
+
const wildcardSubs = this.subscriptions.get(SPECIAL_EVENTS.WILDCARD) || [];
|
|
14760
|
+
toNotify.push(...wildcardSubs);
|
|
14761
|
+
const toRemove = [];
|
|
14762
|
+
for (const sub of toNotify) {
|
|
14226
14763
|
try {
|
|
14227
|
-
|
|
14228
|
-
agent,
|
|
14229
|
-
description,
|
|
14230
|
-
prompt,
|
|
14231
|
-
parentSessionID: ctx.sessionID
|
|
14232
|
-
});
|
|
14233
|
-
return `\u{1F680} Task spawned: \`${task.id}\` (${agent})`;
|
|
14764
|
+
await sub.handler(event);
|
|
14234
14765
|
} catch (error45) {
|
|
14235
|
-
|
|
14766
|
+
console.error(`[EventBus] Handler error for ${type}:`, error45);
|
|
14236
14767
|
}
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
const session = sessionClient.session;
|
|
14240
|
-
const createResult = await session.create({
|
|
14241
|
-
body: { parentID: ctx.sessionID, title: `Task: ${description}` },
|
|
14242
|
-
query: { directory: "." }
|
|
14243
|
-
});
|
|
14244
|
-
if (createResult.error || !createResult.data?.id) {
|
|
14245
|
-
return `\u274C Failed to create session`;
|
|
14768
|
+
if (sub.once) {
|
|
14769
|
+
toRemove.push({ id: sub.id, type: sub.type });
|
|
14246
14770
|
}
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14771
|
+
}
|
|
14772
|
+
for (const { id, type: t } of toRemove) {
|
|
14773
|
+
this.unsubscribe(id, t);
|
|
14774
|
+
}
|
|
14775
|
+
}
|
|
14776
|
+
/**
|
|
14777
|
+
* Emit (alias for publish, sync-looking API)
|
|
14778
|
+
*/
|
|
14779
|
+
emit(type, properties = {}) {
|
|
14780
|
+
this.publish(type, properties).catch(console.error);
|
|
14781
|
+
}
|
|
14782
|
+
/**
|
|
14783
|
+
* Get recent event history
|
|
14784
|
+
*/
|
|
14785
|
+
getHistory(type, limit = 20) {
|
|
14786
|
+
let events = this.eventHistory;
|
|
14787
|
+
if (type && type !== SPECIAL_EVENTS.WILDCARD) {
|
|
14788
|
+
events = events.filter((e) => e.type === type);
|
|
14789
|
+
}
|
|
14790
|
+
return events.slice(-limit);
|
|
14791
|
+
}
|
|
14792
|
+
/**
|
|
14793
|
+
* Clear all subscriptions
|
|
14794
|
+
*/
|
|
14795
|
+
clear() {
|
|
14796
|
+
this.subscriptions.clear();
|
|
14797
|
+
this.eventHistory = [];
|
|
14798
|
+
}
|
|
14799
|
+
/**
|
|
14800
|
+
* Get subscription count
|
|
14801
|
+
*/
|
|
14802
|
+
getSubscriptionCount() {
|
|
14803
|
+
let count = 0;
|
|
14804
|
+
for (const subs of this.subscriptions.values()) {
|
|
14805
|
+
count += subs.length;
|
|
14806
|
+
}
|
|
14807
|
+
return count;
|
|
14808
|
+
}
|
|
14809
|
+
/**
|
|
14810
|
+
* Wait for a specific event (Promise-based)
|
|
14811
|
+
*/
|
|
14812
|
+
waitFor(type, timeout = 3e4) {
|
|
14813
|
+
return new Promise((resolve, reject) => {
|
|
14814
|
+
const timer = setTimeout(() => {
|
|
14815
|
+
unsubscribe();
|
|
14816
|
+
reject(new Error(`Timeout waiting for event: ${type}`));
|
|
14817
|
+
}, timeout);
|
|
14818
|
+
const unsubscribe = this.once(type, (event) => {
|
|
14819
|
+
clearTimeout(timer);
|
|
14820
|
+
resolve(event);
|
|
14252
14821
|
});
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14822
|
+
});
|
|
14823
|
+
}
|
|
14824
|
+
};
|
|
14825
|
+
|
|
14826
|
+
// src/core/bus/index.ts
|
|
14827
|
+
var EventBus = new EventBusImpl();
|
|
14828
|
+
function emit(type, properties) {
|
|
14829
|
+
EventBus.emit(type, properties);
|
|
14830
|
+
}
|
|
14831
|
+
|
|
14832
|
+
// src/core/agents/manager/event-handler.ts
|
|
14833
|
+
var EventHandler = class {
|
|
14834
|
+
constructor(client, store, concurrency, findBySession, notifyParentIfAllComplete, scheduleCleanup, validateSessionHasOutput) {
|
|
14835
|
+
this.client = client;
|
|
14836
|
+
this.store = store;
|
|
14837
|
+
this.concurrency = concurrency;
|
|
14838
|
+
this.findBySession = findBySession;
|
|
14839
|
+
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
14840
|
+
this.scheduleCleanup = scheduleCleanup;
|
|
14841
|
+
this.validateSessionHasOutput = validateSessionHasOutput;
|
|
14842
|
+
}
|
|
14843
|
+
/**
|
|
14844
|
+
* Handle OpenCode session events for proper resource cleanup.
|
|
14845
|
+
* Call this from your plugin's event hook.
|
|
14846
|
+
*/
|
|
14847
|
+
handle(event) {
|
|
14848
|
+
const props = event.properties;
|
|
14849
|
+
if (event.type === SESSION_EVENTS.IDLE) {
|
|
14850
|
+
const sessionID = props?.sessionID;
|
|
14851
|
+
if (!sessionID) return;
|
|
14852
|
+
const task = this.findBySession(sessionID);
|
|
14853
|
+
if (!task || task.status !== TASK_STATUS.RUNNING) return;
|
|
14854
|
+
this.handleSessionIdle(task).catch((err) => {
|
|
14855
|
+
log2("Error handling session.idle:", err);
|
|
14856
|
+
});
|
|
14857
|
+
}
|
|
14858
|
+
if (event.type === SESSION_EVENTS.DELETED) {
|
|
14859
|
+
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
14860
|
+
if (!sessionID) return;
|
|
14861
|
+
const task = this.findBySession(sessionID);
|
|
14862
|
+
if (!task) return;
|
|
14863
|
+
this.handleSessionDeleted(task);
|
|
14864
|
+
}
|
|
14865
|
+
}
|
|
14866
|
+
async handleSessionIdle(task) {
|
|
14867
|
+
const elapsed = Date.now() - task.startedAt.getTime();
|
|
14868
|
+
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
14869
|
+
log2(`Session idle but too early for ${task.id}, waiting...`);
|
|
14870
|
+
return;
|
|
14871
|
+
}
|
|
14872
|
+
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
14873
|
+
if (!hasOutput) {
|
|
14874
|
+
log2(`Session idle but no output for ${task.id}, waiting...`);
|
|
14875
|
+
return;
|
|
14876
|
+
}
|
|
14877
|
+
task.status = TASK_STATUS.COMPLETED;
|
|
14878
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14879
|
+
if (task.concurrencyKey) {
|
|
14880
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14881
|
+
task.concurrencyKey = void 0;
|
|
14882
|
+
}
|
|
14883
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14884
|
+
this.store.queueNotification(task);
|
|
14885
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14886
|
+
this.scheduleCleanup(task.id);
|
|
14887
|
+
log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14888
|
+
}
|
|
14889
|
+
handleSessionDeleted(task) {
|
|
14890
|
+
log2(`Session deleted event for task ${task.id}`);
|
|
14891
|
+
if (task.status === TASK_STATUS.RUNNING) {
|
|
14892
|
+
task.status = TASK_STATUS.ERROR;
|
|
14893
|
+
task.error = "Session deleted";
|
|
14894
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14895
|
+
}
|
|
14896
|
+
if (task.concurrencyKey) {
|
|
14897
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14898
|
+
task.concurrencyKey = void 0;
|
|
14899
|
+
}
|
|
14900
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14901
|
+
this.store.clearNotificationsForTask(task.id);
|
|
14902
|
+
this.store.delete(task.id);
|
|
14903
|
+
log2(`Cleaned up deleted session task: ${task.id}`);
|
|
14904
|
+
}
|
|
14905
|
+
};
|
|
14906
|
+
|
|
14907
|
+
// src/core/agents/manager.ts
|
|
14908
|
+
var ParallelAgentManager = class _ParallelAgentManager {
|
|
14909
|
+
static _instance;
|
|
14910
|
+
store = new TaskStore();
|
|
14911
|
+
client;
|
|
14912
|
+
directory;
|
|
14913
|
+
concurrency = new ConcurrencyController();
|
|
14914
|
+
// Composed components
|
|
14915
|
+
launcher;
|
|
14916
|
+
resumer;
|
|
14917
|
+
poller;
|
|
14918
|
+
cleaner;
|
|
14919
|
+
eventHandler;
|
|
14920
|
+
constructor(client, directory) {
|
|
14921
|
+
this.client = client;
|
|
14922
|
+
this.directory = directory;
|
|
14923
|
+
this.cleaner = new TaskCleaner(client, this.store, this.concurrency);
|
|
14924
|
+
this.poller = new TaskPoller(
|
|
14925
|
+
client,
|
|
14926
|
+
this.store,
|
|
14927
|
+
this.concurrency,
|
|
14928
|
+
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
14929
|
+
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
14930
|
+
() => this.cleaner.pruneExpiredTasks()
|
|
14931
|
+
);
|
|
14932
|
+
this.launcher = new TaskLauncher(
|
|
14933
|
+
client,
|
|
14934
|
+
directory,
|
|
14935
|
+
this.store,
|
|
14936
|
+
this.concurrency,
|
|
14937
|
+
(taskId, error45) => this.handleTaskError(taskId, error45),
|
|
14938
|
+
() => this.poller.start()
|
|
14939
|
+
);
|
|
14940
|
+
this.resumer = new TaskResumer(
|
|
14941
|
+
client,
|
|
14942
|
+
this.store,
|
|
14943
|
+
(sessionID) => this.findBySession(sessionID),
|
|
14944
|
+
() => this.poller.start(),
|
|
14945
|
+
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID)
|
|
14946
|
+
);
|
|
14947
|
+
this.eventHandler = new EventHandler(
|
|
14948
|
+
client,
|
|
14949
|
+
this.store,
|
|
14950
|
+
this.concurrency,
|
|
14951
|
+
(sessionID) => this.findBySession(sessionID),
|
|
14952
|
+
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
14953
|
+
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
14954
|
+
(sessionID) => this.poller.validateSessionHasOutput(sessionID)
|
|
14955
|
+
);
|
|
14956
|
+
}
|
|
14957
|
+
static getInstance(client, directory) {
|
|
14958
|
+
if (!_ParallelAgentManager._instance) {
|
|
14959
|
+
if (!client || !directory) {
|
|
14960
|
+
throw new Error("ParallelAgentManager requires client and directory on first call");
|
|
14961
|
+
}
|
|
14962
|
+
_ParallelAgentManager._instance = new _ParallelAgentManager(client, directory);
|
|
14963
|
+
}
|
|
14964
|
+
return _ParallelAgentManager._instance;
|
|
14965
|
+
}
|
|
14966
|
+
// ========================================================================
|
|
14967
|
+
// Public API
|
|
14968
|
+
// ========================================================================
|
|
14969
|
+
async launch(input) {
|
|
14970
|
+
this.cleaner.pruneExpiredTasks();
|
|
14971
|
+
return this.launcher.launch(input);
|
|
14972
|
+
}
|
|
14973
|
+
async resume(input) {
|
|
14974
|
+
return this.resumer.resume(input);
|
|
14975
|
+
}
|
|
14976
|
+
getTask(id) {
|
|
14977
|
+
return this.store.get(id);
|
|
14978
|
+
}
|
|
14979
|
+
getRunningTasks() {
|
|
14980
|
+
return this.store.getRunning();
|
|
14981
|
+
}
|
|
14982
|
+
getAllTasks() {
|
|
14983
|
+
return this.store.getAll();
|
|
14984
|
+
}
|
|
14985
|
+
getTasksByParent(parentSessionID) {
|
|
14986
|
+
return this.store.getByParent(parentSessionID);
|
|
14987
|
+
}
|
|
14988
|
+
async cancelTask(taskId) {
|
|
14989
|
+
const task = this.store.get(taskId);
|
|
14990
|
+
if (!task || task.status !== TASK_STATUS.RUNNING) return false;
|
|
14991
|
+
task.status = TASK_STATUS.ERROR;
|
|
14992
|
+
task.error = "Cancelled by user";
|
|
14993
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14994
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
14995
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
14996
|
+
try {
|
|
14997
|
+
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
14998
|
+
log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
14999
|
+
} catch {
|
|
15000
|
+
log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
15001
|
+
}
|
|
15002
|
+
this.cleaner.scheduleCleanup(taskId);
|
|
15003
|
+
log2(`Cancelled ${taskId}`);
|
|
15004
|
+
return true;
|
|
15005
|
+
}
|
|
15006
|
+
async getResult(taskId) {
|
|
15007
|
+
const task = this.store.get(taskId);
|
|
15008
|
+
if (!task) return null;
|
|
15009
|
+
if (task.result) return task.result;
|
|
15010
|
+
if (task.status === TASK_STATUS.ERROR) return `Error: ${task.error}`;
|
|
15011
|
+
if (task.status === TASK_STATUS.RUNNING) return null;
|
|
15012
|
+
try {
|
|
15013
|
+
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
15014
|
+
if (result.error) return `Error: ${result.error}`;
|
|
15015
|
+
const messages = result.data ?? [];
|
|
15016
|
+
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
15017
|
+
if (!lastMsg) return "(No response)";
|
|
15018
|
+
const text = lastMsg.parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
|
|
15019
|
+
task.result = text;
|
|
15020
|
+
return text;
|
|
15021
|
+
} catch (error45) {
|
|
15022
|
+
return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
15023
|
+
}
|
|
15024
|
+
}
|
|
15025
|
+
setConcurrencyLimit(agentType, limit) {
|
|
15026
|
+
this.concurrency.setLimit(agentType, limit);
|
|
15027
|
+
}
|
|
15028
|
+
getPendingCount(parentSessionID) {
|
|
15029
|
+
return this.store.getPendingCount(parentSessionID);
|
|
15030
|
+
}
|
|
15031
|
+
cleanup() {
|
|
15032
|
+
this.poller.stop();
|
|
15033
|
+
this.store.clear();
|
|
15034
|
+
}
|
|
15035
|
+
formatDuration = formatDuration;
|
|
15036
|
+
// ========================================================================
|
|
15037
|
+
// Event Handling
|
|
15038
|
+
// ========================================================================
|
|
15039
|
+
handleEvent(event) {
|
|
15040
|
+
this.eventHandler.handle(event);
|
|
15041
|
+
}
|
|
15042
|
+
// ========================================================================
|
|
15043
|
+
// Private Helpers
|
|
15044
|
+
// ========================================================================
|
|
15045
|
+
findBySession(sessionID) {
|
|
15046
|
+
return this.store.getAll().find((t) => t.sessionID === sessionID);
|
|
15047
|
+
}
|
|
15048
|
+
handleTaskError(taskId, error45) {
|
|
15049
|
+
const task = this.store.get(taskId);
|
|
15050
|
+
if (!task) return;
|
|
15051
|
+
task.status = "error";
|
|
15052
|
+
task.error = error45 instanceof Error ? error45.message : String(error45);
|
|
15053
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
15054
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
15055
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
15056
|
+
this.store.queueNotification(task);
|
|
15057
|
+
this.cleaner.notifyParentIfAllComplete(task.parentSessionID);
|
|
15058
|
+
this.cleaner.scheduleCleanup(taskId);
|
|
15059
|
+
}
|
|
15060
|
+
};
|
|
15061
|
+
var parallelAgentManager = {
|
|
15062
|
+
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
|
|
15063
|
+
};
|
|
15064
|
+
|
|
15065
|
+
// src/tools/parallel/delegate-task.ts
|
|
15066
|
+
var createDelegateTaskTool = (manager, client) => tool({
|
|
15067
|
+
description: `Delegate a task to an agent.
|
|
15068
|
+
|
|
15069
|
+
<mode>
|
|
15070
|
+
- background=true: Non-blocking. Returns task ID immediately.
|
|
15071
|
+
- background=false: Blocking. Waits for result.
|
|
15072
|
+
</mode>
|
|
15073
|
+
|
|
15074
|
+
<resume>
|
|
15075
|
+
- resume: Optional session ID to continue existing session.
|
|
15076
|
+
- When set, continues previous work instead of starting fresh.
|
|
15077
|
+
- Preserves all context from previous conversation.
|
|
15078
|
+
- Use for: retry after failure, follow-up questions, token efficiency.
|
|
15079
|
+
</resume>
|
|
15080
|
+
|
|
15081
|
+
<safety>
|
|
15082
|
+
- Max 10 tasks per agent type (configurable)
|
|
15083
|
+
- Auto-timeout: 60 minutes
|
|
15084
|
+
</safety>`,
|
|
15085
|
+
args: {
|
|
15086
|
+
agent: tool.schema.string().describe("Agent name"),
|
|
15087
|
+
description: tool.schema.string().describe("Task description"),
|
|
15088
|
+
prompt: tool.schema.string().describe("Prompt for the agent"),
|
|
15089
|
+
background: tool.schema.boolean().describe("true=async, false=sync"),
|
|
15090
|
+
resume: tool.schema.string().optional().describe("Session ID to resume (from previous task.sessionID)")
|
|
15091
|
+
},
|
|
15092
|
+
async execute(args, context) {
|
|
15093
|
+
const { agent, description, prompt, background, resume } = args;
|
|
15094
|
+
const ctx = context;
|
|
15095
|
+
const sessionClient = client;
|
|
15096
|
+
if (background === void 0) {
|
|
15097
|
+
return `\u274C 'background' parameter is REQUIRED.`;
|
|
15098
|
+
}
|
|
15099
|
+
if (resume) {
|
|
15100
|
+
try {
|
|
15101
|
+
const task = await manager.resume({
|
|
15102
|
+
sessionId: resume,
|
|
15103
|
+
prompt,
|
|
15104
|
+
parentSessionID: ctx.sessionID
|
|
15105
|
+
});
|
|
15106
|
+
if (background === true) {
|
|
15107
|
+
return `\u{1F504} Resumed task: \`${task.id}\` (${task.agent}) in session \`${task.sessionID}\`
|
|
15108
|
+
|
|
15109
|
+
Previous context preserved. Use \`get_task_result({ taskId: "${task.id}" })\` when complete.`;
|
|
15110
|
+
}
|
|
15111
|
+
const startTime = Date.now();
|
|
15112
|
+
const session = sessionClient.session;
|
|
15113
|
+
let stablePolls = 0, lastMsgCount = 0;
|
|
15114
|
+
while (Date.now() - startTime < PARALLEL_TASK.SYNC_TIMEOUT_MS) {
|
|
15115
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
15116
|
+
const statusResult = await session.status();
|
|
15117
|
+
if (statusResult.data?.[task.sessionID]?.type !== "idle") {
|
|
15118
|
+
stablePolls = 0;
|
|
15119
|
+
continue;
|
|
15120
|
+
}
|
|
15121
|
+
if (Date.now() - startTime < 5e3) continue;
|
|
15122
|
+
const msgs2 = await session.messages({ path: { id: task.sessionID } });
|
|
15123
|
+
const count = (msgs2.data ?? []).length;
|
|
15124
|
+
if (count === lastMsgCount) {
|
|
15125
|
+
stablePolls++;
|
|
15126
|
+
if (stablePolls >= 3) break;
|
|
15127
|
+
} else {
|
|
15128
|
+
stablePolls = 0;
|
|
15129
|
+
lastMsgCount = count;
|
|
15130
|
+
}
|
|
15131
|
+
}
|
|
15132
|
+
const msgs = await session.messages({ path: { id: task.sessionID } });
|
|
15133
|
+
const messages = msgs.data ?? [];
|
|
15134
|
+
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
15135
|
+
const text = lastMsg?.parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text ?? "").join("\n") || "";
|
|
15136
|
+
return `\u{1F504} Resumed & Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
|
|
15137
|
+
|
|
15138
|
+
${text || "(No output)"}`;
|
|
15139
|
+
} catch (error45) {
|
|
15140
|
+
return `\u274C Resume failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
15141
|
+
}
|
|
15142
|
+
}
|
|
15143
|
+
if (background === true) {
|
|
15144
|
+
try {
|
|
15145
|
+
const task = await manager.launch({
|
|
15146
|
+
agent,
|
|
15147
|
+
description,
|
|
15148
|
+
prompt,
|
|
15149
|
+
parentSessionID: ctx.sessionID
|
|
15150
|
+
});
|
|
15151
|
+
return `\u{1F680} Task spawned: \`${task.id}\` (${agent})
|
|
15152
|
+
Session: \`${task.sessionID}\` (save for resume)`;
|
|
15153
|
+
} catch (error45) {
|
|
15154
|
+
return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
15155
|
+
}
|
|
15156
|
+
}
|
|
15157
|
+
try {
|
|
15158
|
+
const session = sessionClient.session;
|
|
15159
|
+
const createResult = await session.create({
|
|
15160
|
+
body: { parentID: ctx.sessionID, title: `Task: ${description}` },
|
|
15161
|
+
query: { directory: "." }
|
|
15162
|
+
});
|
|
15163
|
+
if (createResult.error || !createResult.data?.id) {
|
|
15164
|
+
return `\u274C Failed to create session`;
|
|
15165
|
+
}
|
|
15166
|
+
const sessionID = createResult.data.id;
|
|
15167
|
+
const startTime = Date.now();
|
|
15168
|
+
await session.prompt({
|
|
15169
|
+
path: { id: sessionID },
|
|
15170
|
+
body: { agent, parts: [{ type: PART_TYPES.TEXT, text: prompt }] }
|
|
15171
|
+
});
|
|
15172
|
+
let stablePolls = 0, lastMsgCount = 0;
|
|
15173
|
+
while (Date.now() - startTime < 10 * 60 * 1e3) {
|
|
15174
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
15175
|
+
const statusResult = await session.status();
|
|
15176
|
+
if (statusResult.data?.[sessionID]?.type !== "idle") {
|
|
15177
|
+
stablePolls = 0;
|
|
15178
|
+
continue;
|
|
15179
|
+
}
|
|
15180
|
+
if (Date.now() - startTime < 5e3) continue;
|
|
15181
|
+
const msgs2 = await session.messages({ path: { id: sessionID } });
|
|
15182
|
+
const count = (msgs2.data ?? []).length;
|
|
15183
|
+
if (count === lastMsgCount) {
|
|
14265
15184
|
stablePolls++;
|
|
14266
15185
|
if (stablePolls >= 3) break;
|
|
14267
15186
|
} else {
|
|
@@ -14269,238 +15188,1213 @@ var createDelegateTaskTool = (manager, client) => tool({
|
|
|
14269
15188
|
lastMsgCount = count;
|
|
14270
15189
|
}
|
|
14271
15190
|
}
|
|
14272
|
-
const msgs = await session.messages({ path: { id: sessionID } });
|
|
14273
|
-
const messages = msgs.data ?? [];
|
|
14274
|
-
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
14275
|
-
const text = lastMsg?.parts?.filter((p) => p.type ===
|
|
14276
|
-
return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
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
|
+
}
|
|
14281
16108
|
}
|
|
16109
|
+
return results;
|
|
16110
|
+
} catch (error45) {
|
|
16111
|
+
console.error("GitHub search error:", error45);
|
|
16112
|
+
return [];
|
|
14282
16113
|
}
|
|
14283
|
-
}
|
|
16114
|
+
}
|
|
16115
|
+
var codesearchTool = tool({
|
|
16116
|
+
description: `Search open source code for patterns and examples.
|
|
14284
16117
|
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
16118
|
+
<usage>
|
|
16119
|
+
- Find real-world usage patterns from verified repositories
|
|
16120
|
+
- Discover best practices from popular projects
|
|
16121
|
+
- Verify API usage with actual examples
|
|
16122
|
+
</usage>
|
|
16123
|
+
|
|
16124
|
+
<tips>
|
|
16125
|
+
- Be specific with search queries
|
|
16126
|
+
- Add language filter for better results
|
|
16127
|
+
- Use for verification, not just discovery
|
|
16128
|
+
</tips>
|
|
16129
|
+
|
|
16130
|
+
<examples>
|
|
16131
|
+
codesearch({ query: "useEffect cleanup function", language: "typescript" })
|
|
16132
|
+
codesearch({ query: "prisma middleware logging" })
|
|
16133
|
+
codesearch({ query: "next.js middleware redirect", repo: "vercel/next.js" })
|
|
16134
|
+
</examples>`,
|
|
14288
16135
|
args: {
|
|
14289
|
-
|
|
16136
|
+
query: tool.schema.string().describe("Code pattern to search for"),
|
|
16137
|
+
language: tool.schema.string().optional().describe("Programming language filter"),
|
|
16138
|
+
repo: tool.schema.string().optional().describe("Specific repository (owner/repo)")
|
|
14290
16139
|
},
|
|
14291
16140
|
async execute(args) {
|
|
14292
|
-
const
|
|
14293
|
-
|
|
14294
|
-
if (
|
|
14295
|
-
|
|
14296
|
-
const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
|
|
14297
|
-
if (task.status === "error" || task.status === "timeout") {
|
|
14298
|
-
return `\u274C ${task.status}: ${task.error}`;
|
|
16141
|
+
const { query, language, repo } = args;
|
|
16142
|
+
let results = await searchGrepApp(query, { language, repo });
|
|
16143
|
+
if (results.length === 0) {
|
|
16144
|
+
results = await searchGitHub(query, { language });
|
|
14299
16145
|
}
|
|
14300
|
-
|
|
16146
|
+
if (results.length === 0) {
|
|
16147
|
+
return `\u{1F50D} No code results found for: "${query}"
|
|
14301
16148
|
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
16149
|
+
Try:
|
|
16150
|
+
- Different search terms
|
|
16151
|
+
- Broader language filter
|
|
16152
|
+
- Check spelling`;
|
|
16153
|
+
}
|
|
16154
|
+
let output = `\u{1F50D} **Code Search Results for: "${query}"**
|
|
14305
16155
|
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
16156
|
+
`;
|
|
16157
|
+
output += `Found ${results.length} results${language ? ` (${language})` : ""}
|
|
16158
|
+
|
|
16159
|
+
---
|
|
16160
|
+
|
|
16161
|
+
`;
|
|
16162
|
+
for (let i = 0; i < results.length; i++) {
|
|
16163
|
+
const r = results[i];
|
|
16164
|
+
output += `### ${i + 1}. ${r.repo}
|
|
16165
|
+
`;
|
|
16166
|
+
output += `\u{1F4C4} \`${r.file}\`${r.line ? `:${r.line}` : ""}
|
|
16167
|
+
`;
|
|
16168
|
+
output += `\u{1F517} [View on GitHub](${r.url})
|
|
16169
|
+
|
|
16170
|
+
`;
|
|
16171
|
+
if (r.content && r.content !== "(Use webfetch for full content)") {
|
|
16172
|
+
output += `\`\`\`
|
|
16173
|
+
${r.content}
|
|
16174
|
+
\`\`\`
|
|
16175
|
+
|
|
16176
|
+
`;
|
|
16177
|
+
}
|
|
14327
16178
|
}
|
|
14328
|
-
|
|
14329
|
-
const rows = tasks.map((t) => {
|
|
14330
|
-
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
|
|
14331
|
-
return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
|
|
14332
|
-
}).join("\n");
|
|
14333
|
-
return `\u{1F4CB} **Tasks**
|
|
16179
|
+
output += `---
|
|
14334
16180
|
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
16181
|
+
`;
|
|
16182
|
+
output += `\u{1F4A1} **Tip**: Use \`webfetch\` to get the full file content from any of these URLs.`;
|
|
16183
|
+
return output;
|
|
14338
16184
|
}
|
|
14339
16185
|
});
|
|
14340
16186
|
|
|
14341
|
-
// src/
|
|
14342
|
-
var
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
16187
|
+
// src/core/notification/toast-core.ts
|
|
16188
|
+
var toasts = [];
|
|
16189
|
+
var MAX_HISTORY = 50;
|
|
16190
|
+
var handlers = [];
|
|
16191
|
+
function show(options) {
|
|
16192
|
+
const toast = {
|
|
16193
|
+
id: `toast_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
16194
|
+
title: options.title,
|
|
16195
|
+
message: options.message,
|
|
16196
|
+
variant: options.variant || "info",
|
|
16197
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
16198
|
+
duration: options.duration ?? 5e3,
|
|
16199
|
+
dismissed: false
|
|
16200
|
+
};
|
|
16201
|
+
toasts.push(toast);
|
|
16202
|
+
if (toasts.length > MAX_HISTORY) {
|
|
16203
|
+
toasts.shift();
|
|
14353
16204
|
}
|
|
14354
|
-
|
|
16205
|
+
for (const handler of handlers) {
|
|
16206
|
+
try {
|
|
16207
|
+
handler(toast);
|
|
16208
|
+
} catch (error45) {
|
|
16209
|
+
console.error("[Toast] Handler error:", error45);
|
|
16210
|
+
}
|
|
16211
|
+
}
|
|
16212
|
+
const icons = { info: "\u2139\uFE0F", success: "\u2705", warning: "\u26A0\uFE0F", error: "\u274C" };
|
|
16213
|
+
console.log(`${icons[toast.variant]} [${toast.title}] ${toast.message}`);
|
|
16214
|
+
return toast;
|
|
16215
|
+
}
|
|
14355
16216
|
|
|
14356
|
-
// src/
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
16217
|
+
// src/core/notification/presets.ts
|
|
16218
|
+
var presets = {
|
|
16219
|
+
taskStarted: (taskId, agent) => show({
|
|
16220
|
+
title: "Task Started",
|
|
16221
|
+
message: `${agent}: ${taskId}`,
|
|
16222
|
+
variant: "info",
|
|
16223
|
+
duration: 3e3
|
|
16224
|
+
}),
|
|
16225
|
+
taskCompleted: (taskId, agent) => show({
|
|
16226
|
+
title: "Task Completed",
|
|
16227
|
+
message: `${agent}: ${taskId}`,
|
|
16228
|
+
variant: "success",
|
|
16229
|
+
duration: 3e3
|
|
16230
|
+
}),
|
|
16231
|
+
taskFailed: (taskId, error45) => show({
|
|
16232
|
+
title: "Task Failed",
|
|
16233
|
+
message: `${taskId}: ${error45}`,
|
|
16234
|
+
variant: "error",
|
|
16235
|
+
duration: 0
|
|
16236
|
+
// Persistent
|
|
16237
|
+
}),
|
|
16238
|
+
allTasksComplete: (count) => show({
|
|
16239
|
+
title: "All Tasks Complete",
|
|
16240
|
+
message: `${count} tasks finished successfully`,
|
|
16241
|
+
variant: "success",
|
|
16242
|
+
duration: 5e3
|
|
16243
|
+
}),
|
|
16244
|
+
missionComplete: (summary) => show({
|
|
16245
|
+
title: "\u{1F389} Mission Complete",
|
|
16246
|
+
message: summary,
|
|
16247
|
+
variant: "success",
|
|
16248
|
+
duration: 0
|
|
16249
|
+
}),
|
|
16250
|
+
documentCached: (filename) => show({
|
|
16251
|
+
title: "Document Cached",
|
|
16252
|
+
message: `.cache/docs/${filename}`,
|
|
16253
|
+
variant: "info",
|
|
16254
|
+
duration: 2e3
|
|
16255
|
+
}),
|
|
16256
|
+
researchStarted: (topic) => show({
|
|
16257
|
+
title: "Research Started",
|
|
16258
|
+
message: topic,
|
|
16259
|
+
variant: "info",
|
|
16260
|
+
duration: 3e3
|
|
16261
|
+
}),
|
|
16262
|
+
warningRateLimited: () => show({
|
|
16263
|
+
title: "Rate Limited",
|
|
16264
|
+
message: "Waiting before retry...",
|
|
16265
|
+
variant: "warning",
|
|
16266
|
+
duration: 5e3
|
|
16267
|
+
}),
|
|
16268
|
+
errorRecovery: (action) => show({
|
|
16269
|
+
title: "Error Recovery",
|
|
16270
|
+
message: `Attempting: ${action}`,
|
|
16271
|
+
variant: "warning",
|
|
16272
|
+
duration: 3e3
|
|
16273
|
+
})
|
|
16274
|
+
};
|
|
16275
|
+
|
|
16276
|
+
// src/core/notification/event-integration.ts
|
|
16277
|
+
function enableAutoToasts() {
|
|
16278
|
+
const unsubscribers = [];
|
|
16279
|
+
unsubscribers.push(EventBus.subscribe(TASK_EVENTS.STARTED, (event) => {
|
|
16280
|
+
const { taskId, agent } = event.properties;
|
|
16281
|
+
presets.taskStarted(taskId, agent);
|
|
16282
|
+
}));
|
|
16283
|
+
unsubscribers.push(EventBus.subscribe(TASK_EVENTS.COMPLETED, (event) => {
|
|
16284
|
+
const { taskId, agent } = event.properties;
|
|
16285
|
+
presets.taskCompleted(taskId, agent);
|
|
16286
|
+
}));
|
|
16287
|
+
unsubscribers.push(EventBus.subscribe(TASK_EVENTS.FAILED, (event) => {
|
|
16288
|
+
const { taskId, error: error45 } = event.properties;
|
|
16289
|
+
presets.taskFailed(taskId, error45);
|
|
16290
|
+
}));
|
|
16291
|
+
unsubscribers.push(EventBus.subscribe(MISSION_EVENTS.ALL_TASKS_COMPLETE, (event) => {
|
|
16292
|
+
const { count } = event.properties;
|
|
16293
|
+
presets.allTasksComplete(count);
|
|
16294
|
+
}));
|
|
16295
|
+
unsubscribers.push(EventBus.subscribe(MISSION_EVENTS.COMPLETE, (event) => {
|
|
16296
|
+
const { summary } = event.properties;
|
|
16297
|
+
presets.missionComplete(summary);
|
|
16298
|
+
}));
|
|
16299
|
+
unsubscribers.push(EventBus.subscribe(DOCUMENT_EVENTS.CACHED, (event) => {
|
|
16300
|
+
const { filename } = event.properties;
|
|
16301
|
+
presets.documentCached(filename);
|
|
16302
|
+
}));
|
|
16303
|
+
return () => {
|
|
16304
|
+
for (const unsub of unsubscribers) {
|
|
16305
|
+
unsub();
|
|
16306
|
+
}
|
|
14363
16307
|
};
|
|
14364
16308
|
}
|
|
14365
16309
|
|
|
14366
|
-
// src/
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
16310
|
+
// src/core/progress/store.ts
|
|
16311
|
+
var progressHistory = /* @__PURE__ */ new Map();
|
|
16312
|
+
var sessionStartTimes = /* @__PURE__ */ new Map();
|
|
16313
|
+
var MAX_HISTORY2 = 100;
|
|
16314
|
+
function startSession(sessionId) {
|
|
16315
|
+
sessionStartTimes.set(sessionId, /* @__PURE__ */ new Date());
|
|
16316
|
+
progressHistory.set(sessionId, []);
|
|
16317
|
+
}
|
|
16318
|
+
function recordSnapshot(sessionId, data) {
|
|
16319
|
+
const startedAt = sessionStartTimes.get(sessionId) || /* @__PURE__ */ new Date();
|
|
16320
|
+
const now = /* @__PURE__ */ new Date();
|
|
16321
|
+
const snapshot = {
|
|
16322
|
+
sessionId,
|
|
16323
|
+
timestamp: now,
|
|
16324
|
+
todos: {
|
|
16325
|
+
total: data.todoTotal || 0,
|
|
16326
|
+
completed: data.todoCompleted || 0,
|
|
16327
|
+
pending: (data.todoTotal || 0) - (data.todoCompleted || 0),
|
|
16328
|
+
percentage: data.todoTotal ? Math.round((data.todoCompleted || 0) / data.todoTotal * 100) : 0
|
|
16329
|
+
},
|
|
16330
|
+
tasks: {
|
|
16331
|
+
total: data.taskTotal || 0,
|
|
16332
|
+
running: data.taskRunning || 0,
|
|
16333
|
+
completed: data.taskCompleted || 0,
|
|
16334
|
+
failed: data.taskFailed || 0,
|
|
16335
|
+
percentage: data.taskTotal ? Math.round(((data.taskCompleted || 0) + (data.taskFailed || 0)) / data.taskTotal * 100) : 0
|
|
16336
|
+
},
|
|
16337
|
+
steps: {
|
|
16338
|
+
current: data.currentStep || 0,
|
|
16339
|
+
max: data.maxSteps || Infinity
|
|
16340
|
+
},
|
|
16341
|
+
startedAt,
|
|
16342
|
+
elapsedMs: now.getTime() - startedAt.getTime()
|
|
16343
|
+
};
|
|
16344
|
+
const history = progressHistory.get(sessionId) || [];
|
|
16345
|
+
history.push(snapshot);
|
|
16346
|
+
if (history.length > MAX_HISTORY2) {
|
|
16347
|
+
history.shift();
|
|
16348
|
+
}
|
|
16349
|
+
progressHistory.set(sessionId, history);
|
|
16350
|
+
return snapshot;
|
|
14371
16351
|
}
|
|
14372
|
-
function
|
|
14373
|
-
const
|
|
14374
|
-
return
|
|
16352
|
+
function getLatest(sessionId) {
|
|
16353
|
+
const history = progressHistory.get(sessionId);
|
|
16354
|
+
return history?.[history.length - 1];
|
|
14375
16355
|
}
|
|
14376
|
-
function
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
const seconds = Math.floor(elapsed / 1e3) % 60;
|
|
14380
|
-
const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
|
|
14381
|
-
const hours = Math.floor(elapsed / (1e3 * 60 * 60));
|
|
14382
|
-
const parts = [];
|
|
14383
|
-
if (hours > 0) parts.push(`${hours}h`);
|
|
14384
|
-
if (minutes > 0) parts.push(`${minutes}m`);
|
|
14385
|
-
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
|
|
14386
|
-
return parts.join(" ");
|
|
16356
|
+
function clearSession(sessionId) {
|
|
16357
|
+
progressHistory.delete(sessionId);
|
|
16358
|
+
sessionStartTimes.delete(sessionId);
|
|
14387
16359
|
}
|
|
14388
16360
|
|
|
14389
|
-
// src/
|
|
14390
|
-
function
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
if (
|
|
14395
|
-
return {
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
}
|
|
14400
|
-
}
|
|
14401
|
-
if (/(.{2,6})\1{8,}/.test(text)) {
|
|
14402
|
-
return {
|
|
14403
|
-
isHealthy: false,
|
|
14404
|
-
reason: "Pattern loop detected",
|
|
14405
|
-
severity: "critical"
|
|
14406
|
-
};
|
|
14407
|
-
}
|
|
14408
|
-
if (text.length > 200) {
|
|
14409
|
-
const cleanText = text.replace(/\s/g, "");
|
|
14410
|
-
if (cleanText.length > 100) {
|
|
14411
|
-
const uniqueChars = new Set(cleanText).size;
|
|
14412
|
-
const ratio = uniqueChars / cleanText.length;
|
|
14413
|
-
if (ratio < 0.02) {
|
|
14414
|
-
return {
|
|
14415
|
-
isHealthy: false,
|
|
14416
|
-
reason: "Low information density",
|
|
14417
|
-
severity: "critical"
|
|
14418
|
-
};
|
|
14419
|
-
}
|
|
14420
|
-
}
|
|
14421
|
-
}
|
|
14422
|
-
const boxChars = (text.match(/[\u2500-\u257f\u2580-\u259f\u2800-\u28ff]/g) || []).length;
|
|
14423
|
-
if (boxChars > 100 && boxChars / text.length > 0.3) {
|
|
14424
|
-
return {
|
|
14425
|
-
isHealthy: false,
|
|
14426
|
-
reason: "Visual gibberish detected",
|
|
14427
|
-
severity: "critical"
|
|
14428
|
-
};
|
|
16361
|
+
// src/core/progress/formatters.ts
|
|
16362
|
+
function formatElapsed(ms) {
|
|
16363
|
+
const seconds = Math.floor(ms / 1e3);
|
|
16364
|
+
const minutes = Math.floor(seconds / 60);
|
|
16365
|
+
const hours = Math.floor(minutes / 60);
|
|
16366
|
+
if (hours > 0) {
|
|
16367
|
+
return `${hours}h ${minutes % 60}m`;
|
|
16368
|
+
} else if (minutes > 0) {
|
|
16369
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
16370
|
+
} else {
|
|
16371
|
+
return `${seconds}s`;
|
|
14429
16372
|
}
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
isHealthy: false,
|
|
14436
|
-
reason: "Excessive line repetition",
|
|
14437
|
-
severity: "warning"
|
|
14438
|
-
};
|
|
14439
|
-
}
|
|
16373
|
+
}
|
|
16374
|
+
function formatCompact(snapshot) {
|
|
16375
|
+
const parts = [];
|
|
16376
|
+
if (snapshot.todos.total > 0) {
|
|
16377
|
+
parts.push(`\u2705${snapshot.todos.completed}/${snapshot.todos.total}`);
|
|
14440
16378
|
}
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
const uniqueCjk = new Set(
|
|
14444
|
-
text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []
|
|
14445
|
-
).size;
|
|
14446
|
-
if (uniqueCjk < 10 && cjkChars / uniqueCjk > 20) {
|
|
14447
|
-
return {
|
|
14448
|
-
isHealthy: false,
|
|
14449
|
-
reason: "CJK character spam detected",
|
|
14450
|
-
severity: "critical"
|
|
14451
|
-
};
|
|
14452
|
-
}
|
|
16379
|
+
if (snapshot.tasks.running > 0) {
|
|
16380
|
+
parts.push(`\u26A1${snapshot.tasks.running}`);
|
|
14453
16381
|
}
|
|
14454
|
-
|
|
16382
|
+
parts.push(`\u23F1${formatElapsed(snapshot.elapsedMs)}`);
|
|
16383
|
+
return parts.join(" | ");
|
|
14455
16384
|
}
|
|
14456
|
-
var RECOVERY_PROMPT = `<anomaly_recovery>
|
|
14457
|
-
\u26A0\uFE0F SYSTEM NOTICE: Previous output was malformed (gibberish/loop detected).
|
|
14458
|
-
|
|
14459
|
-
<recovery_protocol>
|
|
14460
|
-
1. DISCARD the corrupted output completely - do not reference it
|
|
14461
|
-
2. RECALL the original mission objective
|
|
14462
|
-
3. IDENTIFY the last confirmed successful step
|
|
14463
|
-
4. RESTART with a simpler, more focused approach
|
|
14464
|
-
</recovery_protocol>
|
|
14465
|
-
|
|
14466
|
-
<instructions>
|
|
14467
|
-
- If a sub-agent produced bad output: try a different agent or simpler task
|
|
14468
|
-
- If stuck in a loop: break down the task into smaller pieces
|
|
14469
|
-
- If context seems corrupted: call recorder to restore context
|
|
14470
|
-
- THINK in English for maximum stability
|
|
14471
|
-
</instructions>
|
|
14472
|
-
|
|
14473
|
-
What was the original task? Proceed from the last known good state.
|
|
14474
|
-
</anomaly_recovery>`;
|
|
14475
|
-
var ESCALATION_PROMPT = `<critical_anomaly>
|
|
14476
|
-
\u{1F6A8} CRITICAL: Multiple consecutive malformed outputs detected.
|
|
14477
|
-
|
|
14478
|
-
<emergency_protocol>
|
|
14479
|
-
1. STOP current execution path immediately
|
|
14480
|
-
2. DO NOT continue with the same approach - it is failing
|
|
14481
|
-
3. CALL architect for a completely new strategy
|
|
14482
|
-
4. If architect also fails: report status to user and await guidance
|
|
14483
|
-
</emergency_protocol>
|
|
14484
|
-
|
|
14485
|
-
<diagnosis>
|
|
14486
|
-
The current approach is producing corrupted output.
|
|
14487
|
-
This may indicate: context overload, model instability, or task complexity.
|
|
14488
|
-
</diagnosis>
|
|
14489
16385
|
|
|
14490
|
-
|
|
14491
|
-
|
|
16386
|
+
// src/core/progress/tracker.ts
|
|
16387
|
+
function formatCompact2(sessionId) {
|
|
16388
|
+
const snapshot = getLatest(sessionId);
|
|
16389
|
+
if (!snapshot) return "...";
|
|
16390
|
+
return formatCompact(snapshot);
|
|
16391
|
+
}
|
|
14492
16392
|
|
|
14493
16393
|
// src/index.ts
|
|
14494
|
-
var
|
|
14495
|
-
var
|
|
14496
|
-
var
|
|
14497
|
-
var
|
|
14498
|
-
"architect": "\u{1F3D7}\uFE0F",
|
|
14499
|
-
"builder": "\u{1F528}",
|
|
14500
|
-
"inspector": "\u{1F50D}",
|
|
14501
|
-
"recorder": "\u{1F4BE}",
|
|
14502
|
-
"commander": "\u{1F3AF}"
|
|
14503
|
-
};
|
|
16394
|
+
var require2 = createRequire(import.meta.url);
|
|
16395
|
+
var { version: PLUGIN_VERSION } = require2("../package.json");
|
|
16396
|
+
var UNLIMITED_MODE = true;
|
|
16397
|
+
var DEFAULT_MAX_STEPS = UNLIMITED_MODE ? Infinity : 500;
|
|
14504
16398
|
var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
14505
16399
|
<status>Mission not complete. Keep executing.</status>
|
|
14506
16400
|
|
|
@@ -14509,17 +16403,30 @@ var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
|
14509
16403
|
2. DO NOT wait for user input
|
|
14510
16404
|
3. If previous action failed, try different approach
|
|
14511
16405
|
4. If agent returned nothing, proceed to next step
|
|
16406
|
+
5. Check your todo list - complete ALL pending items
|
|
14512
16407
|
</rules>
|
|
14513
16408
|
|
|
14514
16409
|
<next_step>
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
Execute it NOW
|
|
16410
|
+
1. Check todo list for incomplete items
|
|
16411
|
+
2. Identify the highest priority pending task
|
|
16412
|
+
3. Execute it NOW
|
|
16413
|
+
4. Mark complete when done
|
|
16414
|
+
5. Continue until ALL todos are complete
|
|
14518
16415
|
</next_step>
|
|
16416
|
+
|
|
16417
|
+
<completion_criteria>
|
|
16418
|
+
You are ONLY done when:
|
|
16419
|
+
- All todos are marked complete or cancelled
|
|
16420
|
+
- All features are implemented and tested
|
|
16421
|
+
- Final verification passes
|
|
16422
|
+
Then output: \u2705 MISSION COMPLETE
|
|
16423
|
+
</completion_criteria>
|
|
14519
16424
|
</auto_continue>`;
|
|
14520
16425
|
var OrchestratorPlugin = async (input) => {
|
|
14521
16426
|
const { directory, client } = input;
|
|
14522
16427
|
console.log(`[orchestrator] v${PLUGIN_VERSION} loaded`);
|
|
16428
|
+
const disableAutoToasts = enableAutoToasts();
|
|
16429
|
+
console.log(`[orchestrator] Toast notifications enabled`);
|
|
14523
16430
|
const sessions = /* @__PURE__ */ new Map();
|
|
14524
16431
|
const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
|
|
14525
16432
|
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
|
|
@@ -14528,17 +16435,22 @@ var OrchestratorPlugin = async (input) => {
|
|
|
14528
16435
|
// Tools we expose to the LLM
|
|
14529
16436
|
// -----------------------------------------------------------------
|
|
14530
16437
|
tool: {
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
16438
|
+
[TOOL_NAMES.CALL_AGENT]: callAgentTool,
|
|
16439
|
+
[TOOL_NAMES.SLASHCOMMAND]: createSlashcommandTool(),
|
|
16440
|
+
[TOOL_NAMES.GREP_SEARCH]: grepSearchTool(directory),
|
|
16441
|
+
[TOOL_NAMES.GLOB_SEARCH]: globSearchTool(directory),
|
|
16442
|
+
[TOOL_NAMES.MGREP]: mgrepTool(directory),
|
|
14536
16443
|
// Multi-pattern grep (parallel, Rust-powered)
|
|
14537
16444
|
// Background task tools - run shell commands asynchronously
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
16445
|
+
[TOOL_NAMES.RUN_BACKGROUND]: runBackgroundTool,
|
|
16446
|
+
[TOOL_NAMES.CHECK_BACKGROUND]: checkBackgroundTool,
|
|
16447
|
+
[TOOL_NAMES.LIST_BACKGROUND]: listBackgroundTool,
|
|
16448
|
+
[TOOL_NAMES.KILL_BACKGROUND]: killBackgroundTool,
|
|
16449
|
+
// Web tools - documentation research and caching
|
|
16450
|
+
[TOOL_NAMES.WEBFETCH]: webfetchTool,
|
|
16451
|
+
[TOOL_NAMES.WEBSEARCH]: websearchTool,
|
|
16452
|
+
[TOOL_NAMES.CACHE_DOCS]: cacheDocsTool,
|
|
16453
|
+
[TOOL_NAMES.CODESEARCH]: codesearchTool,
|
|
14542
16454
|
// Async agent tools - spawn agents in parallel sessions
|
|
14543
16455
|
...asyncAgentTools
|
|
14544
16456
|
},
|
|
@@ -14557,10 +16469,20 @@ var OrchestratorPlugin = async (input) => {
|
|
|
14557
16469
|
};
|
|
14558
16470
|
}
|
|
14559
16471
|
const orchestratorAgents = {
|
|
14560
|
-
|
|
14561
|
-
name:
|
|
16472
|
+
[AGENT_NAMES.COMMANDER]: {
|
|
16473
|
+
name: AGENT_NAMES.COMMANDER,
|
|
14562
16474
|
description: "Autonomous orchestrator - executes until mission complete",
|
|
14563
16475
|
systemPrompt: AGENTS.commander.systemPrompt
|
|
16476
|
+
},
|
|
16477
|
+
[AGENT_NAMES.LIBRARIAN]: {
|
|
16478
|
+
name: AGENT_NAMES.LIBRARIAN,
|
|
16479
|
+
description: "Documentation research specialist - reduces hallucination",
|
|
16480
|
+
systemPrompt: AGENTS.librarian?.systemPrompt || ""
|
|
16481
|
+
},
|
|
16482
|
+
[AGENT_NAMES.RESEARCHER]: {
|
|
16483
|
+
name: AGENT_NAMES.RESEARCHER,
|
|
16484
|
+
description: "Pre-task investigation - gathers all info before implementation",
|
|
16485
|
+
systemPrompt: AGENTS.researcher?.systemPrompt || ""
|
|
14564
16486
|
}
|
|
14565
16487
|
};
|
|
14566
16488
|
config2.command = { ...orchestratorCommands, ...existingCommands };
|
|
@@ -14572,13 +16494,13 @@ var OrchestratorPlugin = async (input) => {
|
|
|
14572
16494
|
// -----------------------------------------------------------------
|
|
14573
16495
|
"chat.message": async (msgInput, msgOutput) => {
|
|
14574
16496
|
const parts = msgOutput.parts;
|
|
14575
|
-
const textPartIndex = parts.findIndex((p) => p.type ===
|
|
16497
|
+
const textPartIndex = parts.findIndex((p) => p.type === PART_TYPES.TEXT && p.text);
|
|
14576
16498
|
if (textPartIndex === -1) return;
|
|
14577
16499
|
const originalText = parts[textPartIndex].text || "";
|
|
14578
16500
|
const parsed = detectSlashCommand(originalText);
|
|
14579
16501
|
const sessionID = msgInput.sessionID;
|
|
14580
16502
|
const agentName = (msgInput.agent || "").toLowerCase();
|
|
14581
|
-
if (agentName ===
|
|
16503
|
+
if (agentName === AGENT_NAMES.COMMANDER && !sessions.has(sessionID)) {
|
|
14582
16504
|
const now = Date.now();
|
|
14583
16505
|
sessions.set(sessionID, {
|
|
14584
16506
|
active: true,
|
|
@@ -14596,44 +16518,19 @@ var OrchestratorPlugin = async (input) => {
|
|
|
14596
16518
|
currentTask: "",
|
|
14597
16519
|
anomalyCount: 0
|
|
14598
16520
|
});
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
userMessage
|
|
14605
|
-
);
|
|
14606
|
-
}
|
|
14607
|
-
}
|
|
14608
|
-
}
|
|
14609
|
-
if (parsed?.command === "task") {
|
|
14610
|
-
const now = Date.now();
|
|
14611
|
-
sessions.set(sessionID, {
|
|
14612
|
-
active: true,
|
|
14613
|
-
step: 0,
|
|
14614
|
-
maxSteps: TASK_COMMAND_MAX_STEPS,
|
|
14615
|
-
timestamp: now,
|
|
14616
|
-
startTime: now,
|
|
14617
|
-
lastStepTime: now
|
|
14618
|
-
});
|
|
14619
|
-
state.missionActive = true;
|
|
14620
|
-
state.sessions.set(sessionID, {
|
|
14621
|
-
enabled: true,
|
|
14622
|
-
iterations: 0,
|
|
14623
|
-
taskRetries: /* @__PURE__ */ new Map(),
|
|
14624
|
-
currentTask: "",
|
|
14625
|
-
anomalyCount: 0
|
|
16521
|
+
startSession(sessionID);
|
|
16522
|
+
emit(TASK_EVENTS.STARTED, {
|
|
16523
|
+
taskId: sessionID,
|
|
16524
|
+
agent: AGENT_NAMES.COMMANDER,
|
|
16525
|
+
description: "Mission started"
|
|
14626
16526
|
});
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
parsed.args || "continue previous work"
|
|
14630
|
-
);
|
|
14631
|
-
} else if (parsed) {
|
|
16527
|
+
}
|
|
16528
|
+
if (parsed) {
|
|
14632
16529
|
const command = COMMANDS[parsed.command];
|
|
14633
16530
|
if (command) {
|
|
14634
16531
|
parts[textPartIndex].text = command.template.replace(
|
|
14635
16532
|
/\$ARGUMENTS/g,
|
|
14636
|
-
parsed.args ||
|
|
16533
|
+
parsed.args || PROMPTS.CONTINUE
|
|
14637
16534
|
);
|
|
14638
16535
|
}
|
|
14639
16536
|
}
|
|
@@ -14652,7 +16549,7 @@ var OrchestratorPlugin = async (input) => {
|
|
|
14652
16549
|
session.timestamp = now;
|
|
14653
16550
|
session.lastStepTime = now;
|
|
14654
16551
|
const stateSession = state.sessions.get(toolInput.sessionID);
|
|
14655
|
-
if (toolInput.tool ===
|
|
16552
|
+
if (toolInput.tool === TOOL_NAMES.CALL_AGENT && stateSession) {
|
|
14656
16553
|
const sanityResult = checkOutputSanity(toolOutput.output);
|
|
14657
16554
|
if (!sanityResult.isHealthy) {
|
|
14658
16555
|
stateSession.anomalyCount = (stateSession.anomalyCount || 0) + 1;
|
|
@@ -14675,14 +16572,14 @@ Anomaly count: ${stateSession.anomalyCount}
|
|
|
14675
16572
|
}
|
|
14676
16573
|
}
|
|
14677
16574
|
}
|
|
14678
|
-
if (toolInput.tool ===
|
|
16575
|
+
if (toolInput.tool === TOOL_NAMES.CALL_AGENT && toolInput.arguments?.task && stateSession) {
|
|
14679
16576
|
const taskIdMatch = toolInput.arguments.task.match(/\[(TASK-\d+)\]/i);
|
|
14680
16577
|
if (taskIdMatch) {
|
|
14681
16578
|
stateSession.currentTask = taskIdMatch[1].toUpperCase();
|
|
14682
|
-
stateSession.graph?.updateTask(stateSession.currentTask, { status:
|
|
16579
|
+
stateSession.graph?.updateTask(stateSession.currentTask, { status: TASK_STATUS.RUNNING });
|
|
14683
16580
|
}
|
|
14684
16581
|
const agentName = toolInput.arguments.agent;
|
|
14685
|
-
const emoji3 =
|
|
16582
|
+
const emoji3 = AGENT_EMOJI[agentName] || "\u{1F916}";
|
|
14686
16583
|
toolOutput.output = `${emoji3} [${agentName.toUpperCase()}] Working...
|
|
14687
16584
|
|
|
14688
16585
|
` + toolOutput.output;
|
|
@@ -14692,7 +16589,7 @@ Anomaly count: ${stateSession.anomalyCount}
|
|
|
14692
16589
|
state.missionActive = false;
|
|
14693
16590
|
return;
|
|
14694
16591
|
}
|
|
14695
|
-
if (toolOutput.output.includes("[") && toolOutput.output.includes("{") && toolInput.tool ===
|
|
16592
|
+
if (toolOutput.output.includes("[") && toolOutput.output.includes("{") && toolInput.tool === TOOL_NAMES.CALL_AGENT && stateSession) {
|
|
14696
16593
|
const jsonMatch = toolOutput.output.match(/```json\n([\s\S]*?)\n```/) || toolOutput.output.match(/\[\s*\{[\s\S]*?\}\s*\]/);
|
|
14697
16594
|
if (jsonMatch) {
|
|
14698
16595
|
try {
|
|
@@ -14713,7 +16610,7 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14713
16610
|
const taskId = stateSession.currentTask;
|
|
14714
16611
|
if (toolOutput.output.includes("\u2705 PASS") || toolOutput.output.includes("AUDIT RESULT: PASS")) {
|
|
14715
16612
|
if (taskId) {
|
|
14716
|
-
stateSession.graph.updateTask(taskId, { status:
|
|
16613
|
+
stateSession.graph.updateTask(taskId, { status: TASK_STATUS.COMPLETED });
|
|
14717
16614
|
stateSession.taskRetries.clear();
|
|
14718
16615
|
toolOutput.output += `
|
|
14719
16616
|
|
|
@@ -14726,7 +16623,7 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14726
16623
|
const retries = (stateSession.taskRetries.get(taskId) || 0) + 1;
|
|
14727
16624
|
stateSession.taskRetries.set(taskId, retries);
|
|
14728
16625
|
if (retries >= state.maxRetries) {
|
|
14729
|
-
stateSession.graph.updateTask(taskId, { status:
|
|
16626
|
+
stateSession.graph.updateTask(taskId, { status: TASK_STATUS.FAILED });
|
|
14730
16627
|
toolOutput.output += `
|
|
14731
16628
|
|
|
14732
16629
|
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
@@ -14760,7 +16657,7 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14760
16657
|
const session = sessions.get(sessionID);
|
|
14761
16658
|
if (!session?.active) return;
|
|
14762
16659
|
const parts = assistantOutput.parts;
|
|
14763
|
-
const textContent = parts?.filter((p) => p.type ===
|
|
16660
|
+
const textContent = parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text || "").join("\n") || "";
|
|
14764
16661
|
const stateSession = state.sessions.get(sessionID);
|
|
14765
16662
|
const sanityResult = checkOutputSanity(textContent);
|
|
14766
16663
|
if (!sanityResult.isHealthy && stateSession) {
|
|
@@ -14774,7 +16671,7 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14774
16671
|
path: { id: sessionID },
|
|
14775
16672
|
body: {
|
|
14776
16673
|
parts: [{
|
|
14777
|
-
type:
|
|
16674
|
+
type: PART_TYPES.TEXT,
|
|
14778
16675
|
text: `\u26A0\uFE0F ANOMALY #${stateSession.anomalyCount}: ${sanityResult.reason}
|
|
14779
16676
|
|
|
14780
16677
|
` + recoveryText + `
|
|
@@ -14793,16 +16690,26 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14793
16690
|
if (stateSession && stateSession.anomalyCount > 0) {
|
|
14794
16691
|
stateSession.anomalyCount = 0;
|
|
14795
16692
|
}
|
|
14796
|
-
if (textContent.includes(
|
|
16693
|
+
if (textContent.includes(MISSION.COMPLETE) || textContent.includes(MISSION.COMPLETE_TEXT)) {
|
|
14797
16694
|
session.active = false;
|
|
14798
16695
|
state.missionActive = false;
|
|
16696
|
+
emit(MISSION_EVENTS.COMPLETE, {
|
|
16697
|
+
sessionId: sessionID,
|
|
16698
|
+
summary: "Mission completed successfully"
|
|
16699
|
+
});
|
|
16700
|
+
clearSession(sessionID);
|
|
14799
16701
|
sessions.delete(sessionID);
|
|
14800
16702
|
state.sessions.delete(sessionID);
|
|
14801
16703
|
return;
|
|
14802
16704
|
}
|
|
14803
|
-
if (textContent.includes(
|
|
16705
|
+
if (textContent.includes(MISSION.STOP_COMMAND) || textContent.includes(MISSION.CANCEL_COMMAND)) {
|
|
14804
16706
|
session.active = false;
|
|
14805
16707
|
state.missionActive = false;
|
|
16708
|
+
emit(TASK_EVENTS.FAILED, {
|
|
16709
|
+
taskId: sessionID,
|
|
16710
|
+
error: "Cancelled by user"
|
|
16711
|
+
});
|
|
16712
|
+
clearSession(sessionID);
|
|
14806
16713
|
sessions.delete(sessionID);
|
|
14807
16714
|
state.sessions.delete(sessionID);
|
|
14808
16715
|
return;
|
|
@@ -14819,16 +16726,21 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14819
16726
|
state.missionActive = false;
|
|
14820
16727
|
return;
|
|
14821
16728
|
}
|
|
16729
|
+
recordSnapshot(sessionID, {
|
|
16730
|
+
currentStep: session.step,
|
|
16731
|
+
maxSteps: session.maxSteps
|
|
16732
|
+
});
|
|
16733
|
+
const progressInfo = formatCompact2(sessionID);
|
|
14822
16734
|
try {
|
|
14823
16735
|
if (client?.session?.prompt) {
|
|
14824
16736
|
await client.session.prompt({
|
|
14825
16737
|
path: { id: sessionID },
|
|
14826
16738
|
body: {
|
|
14827
16739
|
parts: [{
|
|
14828
|
-
type:
|
|
16740
|
+
type: PART_TYPES.TEXT,
|
|
14829
16741
|
text: CONTINUE_INSTRUCTION + `
|
|
14830
16742
|
|
|
14831
|
-
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`
|
|
16743
|
+
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | ${progressInfo} | This step: ${stepDuration} | Total: ${totalElapsed}`
|
|
14832
16744
|
}]
|
|
14833
16745
|
}
|
|
14834
16746
|
});
|
|
@@ -14839,7 +16751,7 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14839
16751
|
if (client?.session?.prompt) {
|
|
14840
16752
|
await client.session.prompt({
|
|
14841
16753
|
path: { id: sessionID },
|
|
14842
|
-
body: { parts: [{ type:
|
|
16754
|
+
body: { parts: [{ type: PART_TYPES.TEXT, text: PROMPTS.CONTINUE }] }
|
|
14843
16755
|
});
|
|
14844
16756
|
}
|
|
14845
16757
|
} catch {
|
|
@@ -14857,11 +16769,13 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14857
16769
|
manager.handleEvent(event);
|
|
14858
16770
|
} catch {
|
|
14859
16771
|
}
|
|
14860
|
-
if (event.type ===
|
|
16772
|
+
if (event.type === SESSION_EVENTS.DELETED) {
|
|
14861
16773
|
const props = event.properties;
|
|
14862
16774
|
if (props?.info?.id) {
|
|
14863
|
-
|
|
14864
|
-
|
|
16775
|
+
const sessionId = props.info.id;
|
|
16776
|
+
sessions.delete(sessionId);
|
|
16777
|
+
state.sessions.delete(sessionId);
|
|
16778
|
+
clearSession(sessionId);
|
|
14865
16779
|
}
|
|
14866
16780
|
}
|
|
14867
16781
|
}
|
|
@@ -14869,5 +16783,6 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14869
16783
|
};
|
|
14870
16784
|
var index_default = OrchestratorPlugin;
|
|
14871
16785
|
export {
|
|
16786
|
+
OrchestratorPlugin,
|
|
14872
16787
|
index_default as default
|
|
14873
16788
|
};
|