opencode-orchestrator 1.0.65 → 1.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -104
- package/dist/core/agents/agent-registry.d.ts +29 -0
- package/dist/core/agents/interfaces/session-pool.interface.d.ts +79 -0
- package/dist/core/agents/manager/task-cleaner.d.ts +3 -1
- package/dist/core/agents/manager/task-launcher.d.ts +4 -2
- package/dist/core/agents/manager.d.ts +2 -0
- package/dist/core/agents/session-pool.d.ts +58 -0
- package/dist/core/loop/todo-manager.d.ts +18 -0
- package/dist/core/memory/interfaces.d.ts +33 -0
- package/dist/core/memory/memory-manager.d.ts +40 -0
- package/dist/core/metrics/collector.d.ts +27 -0
- package/dist/core/orchestrator/session-manager.d.ts +0 -1
- package/dist/core/plugins/interfaces.d.ts +30 -0
- package/dist/core/plugins/plugin-manager.d.ts +21 -0
- package/dist/core/progress/progress-notifier.d.ts +14 -0
- package/dist/core/progress/state-broadcaster.d.ts +29 -0
- package/dist/core/progress/terminal-monitor.d.ts +13 -0
- package/dist/core/recovery/interfaces/recovery-action.d.ts +1 -0
- package/dist/hooks/constants.d.ts +4 -1
- package/dist/hooks/custom/memory-gate.d.ts +21 -0
- package/dist/hooks/custom/metrics.d.ts +14 -0
- package/dist/index.js +1621 -469
- package/dist/shared/core/constants/index.d.ts +1 -0
- package/dist/shared/core/constants/memory-hooks.d.ts +66 -0
- package/dist/shared/core/constants/paths.d.ts +2 -0
- package/dist/shared/tool/constants/tool-names.d.ts +3 -0
- package/dist/tools/lsp/diagnostics-cache.d.ts +14 -0
- package/dist/tools/parallel/list-agents.d.ts +10 -0
- package/dist/tools/parallel/show-metrics.d.ts +10 -0
- package/dist/tools/parallel/update-todo.d.ts +28 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,13 +14,13 @@ __export(store_exports, {
|
|
|
14
14
|
addDecision: () => addDecision,
|
|
15
15
|
addDocument: () => addDocument,
|
|
16
16
|
addFinding: () => addFinding,
|
|
17
|
-
clear: () =>
|
|
17
|
+
clear: () => clear2,
|
|
18
18
|
clearAll: () => clearAll,
|
|
19
19
|
create: () => create,
|
|
20
20
|
get: () => get,
|
|
21
21
|
getChildren: () => getChildren,
|
|
22
22
|
getMerged: () => getMerged,
|
|
23
|
-
getStats: () =>
|
|
23
|
+
getStats: () => getStats2
|
|
24
24
|
});
|
|
25
25
|
function create(sessionId, parentId) {
|
|
26
26
|
const context = {
|
|
@@ -88,7 +88,7 @@ function addDecision(sessionId, decision) {
|
|
|
88
88
|
function getChildren(parentId) {
|
|
89
89
|
return Array.from(parentChildMap.get(parentId) || []);
|
|
90
90
|
}
|
|
91
|
-
function
|
|
91
|
+
function clear2(sessionId) {
|
|
92
92
|
const context = contexts.get(sessionId);
|
|
93
93
|
if (context?.parentId) {
|
|
94
94
|
parentChildMap.get(context.parentId)?.delete(sessionId);
|
|
@@ -99,7 +99,7 @@ function clearAll() {
|
|
|
99
99
|
contexts.clear();
|
|
100
100
|
parentChildMap.clear();
|
|
101
101
|
}
|
|
102
|
-
function
|
|
102
|
+
function getStats2() {
|
|
103
103
|
let totalDocuments = 0;
|
|
104
104
|
let totalFindings = 0;
|
|
105
105
|
let totalDecisions = 0;
|
|
@@ -133,6 +133,7 @@ var AGENT_NAMES = {
|
|
|
133
133
|
PLANNER: "Planner",
|
|
134
134
|
WORKER: "Worker",
|
|
135
135
|
REVIEWER: "Reviewer"
|
|
136
|
+
// Unit Review & Final Quality Gate (Conceptual Master)
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
// src/shared/agent/constants/agent-tokens.ts
|
|
@@ -194,7 +195,10 @@ var PATHS = {
|
|
|
194
195
|
SYNC_ISSUES: ".opencode/sync-issues.md",
|
|
195
196
|
INTEGRATION_STATUS: ".opencode/integration-status.md",
|
|
196
197
|
// Progress tracking
|
|
197
|
-
STATUS: ".opencode/status.md"
|
|
198
|
+
STATUS: ".opencode/status.md",
|
|
199
|
+
// Configuration
|
|
200
|
+
AGENTS_CONFIG: ".opencode/agents.json",
|
|
201
|
+
PLUGINS: ".opencode/plugins"
|
|
198
202
|
};
|
|
199
203
|
|
|
200
204
|
// src/shared/core/constants/memory-limits.ts
|
|
@@ -314,6 +318,132 @@ var PHASES = {
|
|
|
314
318
|
}
|
|
315
319
|
};
|
|
316
320
|
|
|
321
|
+
// src/shared/tool/constants/tool-names.ts
|
|
322
|
+
var TOOL_NAMES = {
|
|
323
|
+
// Parallel task tools
|
|
324
|
+
DELEGATE_TASK: "delegate_task",
|
|
325
|
+
GET_TASK_RESULT: "get_task_result",
|
|
326
|
+
LIST_TASKS: "list_tasks",
|
|
327
|
+
CANCEL_TASK: "cancel_task",
|
|
328
|
+
LIST_AGENTS: "list_agents",
|
|
329
|
+
SHOW_METRICS: "show_metrics",
|
|
330
|
+
UPDATE_TODO: "update_todo",
|
|
331
|
+
// Background command tools
|
|
332
|
+
RUN_BACKGROUND: "run_background",
|
|
333
|
+
CHECK_BACKGROUND: "check_background",
|
|
334
|
+
LIST_BACKGROUND: "list_background",
|
|
335
|
+
KILL_BACKGROUND: "kill_background",
|
|
336
|
+
// Command tools
|
|
337
|
+
RUN_COMMAND: "run_command",
|
|
338
|
+
// Search tools
|
|
339
|
+
GREP_SEARCH: "grep_search",
|
|
340
|
+
GLOB_SEARCH: "glob_search",
|
|
341
|
+
MGREP: "mgrep",
|
|
342
|
+
SED_REPLACE: "sed_replace",
|
|
343
|
+
// Diff & Compare tools
|
|
344
|
+
DIFF: "diff",
|
|
345
|
+
// JSON tools
|
|
346
|
+
JQ: "jq",
|
|
347
|
+
// HTTP tools
|
|
348
|
+
HTTP: "http",
|
|
349
|
+
// File tools
|
|
350
|
+
FILE_STATS: "file_stats",
|
|
351
|
+
// Git tools
|
|
352
|
+
GIT_DIFF: "git_diff",
|
|
353
|
+
GIT_STATUS: "git_status",
|
|
354
|
+
// Web tools
|
|
355
|
+
WEBFETCH: "webfetch",
|
|
356
|
+
WEBSEARCH: "websearch",
|
|
357
|
+
CODESEARCH: "codesearch",
|
|
358
|
+
CACHE_DOCS: "cache_docs",
|
|
359
|
+
// LSP tools
|
|
360
|
+
LSP_DIAGNOSTICS: "lsp_diagnostics",
|
|
361
|
+
// AST tools
|
|
362
|
+
AST_SEARCH: "ast_search",
|
|
363
|
+
AST_REPLACE: "ast_replace",
|
|
364
|
+
// Other tools
|
|
365
|
+
CALL_AGENT: "call_agent",
|
|
366
|
+
SLASHCOMMAND: "slashcommand",
|
|
367
|
+
SKILL: "skill"
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/shared/core/constants/memory-hooks.ts
|
|
371
|
+
var MEMORY_CONSTANTS = {
|
|
372
|
+
ID_PREFIX: "mem_",
|
|
373
|
+
LEVELS: {
|
|
374
|
+
SYSTEM: "system",
|
|
375
|
+
PROJECT: "project",
|
|
376
|
+
MISSION: "mission",
|
|
377
|
+
TASK: "task"
|
|
378
|
+
},
|
|
379
|
+
IMPORTANCE: {
|
|
380
|
+
LOW: 0.3,
|
|
381
|
+
NORMAL: 0.5,
|
|
382
|
+
HIGH: 0.7,
|
|
383
|
+
CRITICAL: 0.9
|
|
384
|
+
},
|
|
385
|
+
// Tools that produce high volume or irrelevant output for memory
|
|
386
|
+
NOISY_TOOLS: [
|
|
387
|
+
TOOL_NAMES.LIST_TASKS,
|
|
388
|
+
TOOL_NAMES.GET_TASK_RESULT,
|
|
389
|
+
TOOL_NAMES.LIST_BACKGROUND,
|
|
390
|
+
TOOL_NAMES.CHECK_BACKGROUND,
|
|
391
|
+
TOOL_NAMES.LIST_AGENTS,
|
|
392
|
+
TOOL_NAMES.SHOW_METRICS
|
|
393
|
+
],
|
|
394
|
+
// Significant keywords for memory promotion
|
|
395
|
+
KEYWORDS: {
|
|
396
|
+
DONE: "DONE",
|
|
397
|
+
SUCCESS: "SUCCESS",
|
|
398
|
+
ERROR: "ERROR",
|
|
399
|
+
FAIL: "FAIL"
|
|
400
|
+
},
|
|
401
|
+
MAX_CONTENT_LENGTH: 1e3
|
|
402
|
+
};
|
|
403
|
+
var HOOK_NAMES = {
|
|
404
|
+
MEMORY_GATE: "MemoryGate",
|
|
405
|
+
METRICS_TELEMETRY: "MetricsTelemetry",
|
|
406
|
+
SANITY_CHECK: "SanityCheck",
|
|
407
|
+
MISSION_LOOP: "MissionLoop",
|
|
408
|
+
MISSION_CONTROL: "MissionControl",
|
|
409
|
+
STRICT_ROLE_GUARD: "StrictRoleGuard",
|
|
410
|
+
SECRET_SCANNER: "SecretScanner",
|
|
411
|
+
AGENT_UI: "AgentUI",
|
|
412
|
+
RESOURCE_CONTROL: "ResourceControl",
|
|
413
|
+
USER_ACTIVITY: "UserActivity",
|
|
414
|
+
SLASH_COMMAND: "SlashCommandDispatcher"
|
|
415
|
+
};
|
|
416
|
+
var TODO_CONSTANTS = {
|
|
417
|
+
MARKERS: {
|
|
418
|
+
PENDING: "[ ]",
|
|
419
|
+
COMPLETED: "[x]",
|
|
420
|
+
PROGRESS: "[/]",
|
|
421
|
+
FAILED: "[-]"
|
|
422
|
+
},
|
|
423
|
+
STATUS: {
|
|
424
|
+
PENDING: "pending",
|
|
425
|
+
COMPLETED: "completed",
|
|
426
|
+
PROGRESS: "progress",
|
|
427
|
+
FAILED: "failed"
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
var TUI_CONSTANTS = {
|
|
431
|
+
BAR_WIDTH: 30,
|
|
432
|
+
COLORS: {
|
|
433
|
+
PROGRESS: "\x1B[36m",
|
|
434
|
+
AGENT: "\x1B[32m",
|
|
435
|
+
RESET: "\x1B[0m",
|
|
436
|
+
BOLD: "\x1B[1m",
|
|
437
|
+
DIM: "\x1B[2m"
|
|
438
|
+
},
|
|
439
|
+
LABELS: {
|
|
440
|
+
IDLE: "Idle",
|
|
441
|
+
WAITING: "Waiting for tasks...",
|
|
442
|
+
PROGRESS_TITLE: "MISSION PROGRESS",
|
|
443
|
+
AGENT_TITLE: "ACTIVE AGENTS"
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
317
447
|
// src/shared/core/constants/cli.ts
|
|
318
448
|
var CLI_NAME = {
|
|
319
449
|
NPX: "npx",
|
|
@@ -835,52 +965,6 @@ var EVENT_TYPES = {
|
|
|
835
965
|
...SPECIAL_EVENTS
|
|
836
966
|
};
|
|
837
967
|
|
|
838
|
-
// src/shared/tool/constants/tool-names.ts
|
|
839
|
-
var TOOL_NAMES = {
|
|
840
|
-
// Parallel task tools
|
|
841
|
-
DELEGATE_TASK: "delegate_task",
|
|
842
|
-
GET_TASK_RESULT: "get_task_result",
|
|
843
|
-
LIST_TASKS: "list_tasks",
|
|
844
|
-
CANCEL_TASK: "cancel_task",
|
|
845
|
-
// Background command tools
|
|
846
|
-
RUN_BACKGROUND: "run_background",
|
|
847
|
-
CHECK_BACKGROUND: "check_background",
|
|
848
|
-
LIST_BACKGROUND: "list_background",
|
|
849
|
-
KILL_BACKGROUND: "kill_background",
|
|
850
|
-
// Command tools
|
|
851
|
-
RUN_COMMAND: "run_command",
|
|
852
|
-
// Search tools
|
|
853
|
-
GREP_SEARCH: "grep_search",
|
|
854
|
-
GLOB_SEARCH: "glob_search",
|
|
855
|
-
MGREP: "mgrep",
|
|
856
|
-
SED_REPLACE: "sed_replace",
|
|
857
|
-
// Diff & Compare tools
|
|
858
|
-
DIFF: "diff",
|
|
859
|
-
// JSON tools
|
|
860
|
-
JQ: "jq",
|
|
861
|
-
// HTTP tools
|
|
862
|
-
HTTP: "http",
|
|
863
|
-
// File tools
|
|
864
|
-
FILE_STATS: "file_stats",
|
|
865
|
-
// Git tools
|
|
866
|
-
GIT_DIFF: "git_diff",
|
|
867
|
-
GIT_STATUS: "git_status",
|
|
868
|
-
// Web tools
|
|
869
|
-
WEBFETCH: "webfetch",
|
|
870
|
-
WEBSEARCH: "websearch",
|
|
871
|
-
CODESEARCH: "codesearch",
|
|
872
|
-
CACHE_DOCS: "cache_docs",
|
|
873
|
-
// LSP tools
|
|
874
|
-
LSP_DIAGNOSTICS: "lsp_diagnostics",
|
|
875
|
-
// AST tools
|
|
876
|
-
AST_SEARCH: "ast_search",
|
|
877
|
-
AST_REPLACE: "ast_replace",
|
|
878
|
-
// Other tools
|
|
879
|
-
CALL_AGENT: "call_agent",
|
|
880
|
-
SLASHCOMMAND: "slashcommand",
|
|
881
|
-
SKILL: "skill"
|
|
882
|
-
};
|
|
883
|
-
|
|
884
968
|
// src/shared/tool/constants/lsp/lsp-severity.ts
|
|
885
969
|
var LSP_SEVERITY = {
|
|
886
970
|
ERROR: 1,
|
|
@@ -2142,10 +2226,10 @@ function mergeDefs(...defs) {
|
|
|
2142
2226
|
function cloneDef(schema) {
|
|
2143
2227
|
return mergeDefs(schema._zod.def);
|
|
2144
2228
|
}
|
|
2145
|
-
function getElementAtPath(obj,
|
|
2146
|
-
if (!
|
|
2229
|
+
function getElementAtPath(obj, path9) {
|
|
2230
|
+
if (!path9)
|
|
2147
2231
|
return obj;
|
|
2148
|
-
return
|
|
2232
|
+
return path9.reduce((acc, key) => acc?.[key], obj);
|
|
2149
2233
|
}
|
|
2150
2234
|
function promiseAllObject(promisesObj) {
|
|
2151
2235
|
const keys = Object.keys(promisesObj);
|
|
@@ -2506,11 +2590,11 @@ function aborted(x, startIndex = 0) {
|
|
|
2506
2590
|
}
|
|
2507
2591
|
return false;
|
|
2508
2592
|
}
|
|
2509
|
-
function prefixIssues(
|
|
2593
|
+
function prefixIssues(path9, issues) {
|
|
2510
2594
|
return issues.map((iss) => {
|
|
2511
2595
|
var _a;
|
|
2512
2596
|
(_a = iss).path ?? (_a.path = []);
|
|
2513
|
-
iss.path.unshift(
|
|
2597
|
+
iss.path.unshift(path9);
|
|
2514
2598
|
return iss;
|
|
2515
2599
|
});
|
|
2516
2600
|
}
|
|
@@ -2678,7 +2762,7 @@ function treeifyError(error45, _mapper) {
|
|
|
2678
2762
|
return issue2.message;
|
|
2679
2763
|
};
|
|
2680
2764
|
const result = { errors: [] };
|
|
2681
|
-
const processError = (error46,
|
|
2765
|
+
const processError = (error46, path9 = []) => {
|
|
2682
2766
|
var _a, _b;
|
|
2683
2767
|
for (const issue2 of error46.issues) {
|
|
2684
2768
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -2688,7 +2772,7 @@ function treeifyError(error45, _mapper) {
|
|
|
2688
2772
|
} else if (issue2.code === "invalid_element") {
|
|
2689
2773
|
processError({ issues: issue2.issues }, issue2.path);
|
|
2690
2774
|
} else {
|
|
2691
|
-
const fullpath = [...
|
|
2775
|
+
const fullpath = [...path9, ...issue2.path];
|
|
2692
2776
|
if (fullpath.length === 0) {
|
|
2693
2777
|
result.errors.push(mapper(issue2));
|
|
2694
2778
|
continue;
|
|
@@ -2720,8 +2804,8 @@ function treeifyError(error45, _mapper) {
|
|
|
2720
2804
|
}
|
|
2721
2805
|
function toDotPath(_path) {
|
|
2722
2806
|
const segs = [];
|
|
2723
|
-
const
|
|
2724
|
-
for (const seg of
|
|
2807
|
+
const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2808
|
+
for (const seg of path9) {
|
|
2725
2809
|
if (typeof seg === "number")
|
|
2726
2810
|
segs.push(`[${seg}]`);
|
|
2727
2811
|
else if (typeof seg === "symbol")
|
|
@@ -17332,141 +17416,490 @@ var TaskWAL = class {
|
|
|
17332
17416
|
};
|
|
17333
17417
|
var taskWAL = new TaskWAL();
|
|
17334
17418
|
|
|
17335
|
-
// src/core/
|
|
17336
|
-
var
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
async launch(inputs) {
|
|
17351
|
-
const isArray = Array.isArray(inputs);
|
|
17352
|
-
const taskInputs = isArray ? inputs : [inputs];
|
|
17353
|
-
if (taskInputs.length === 0) return isArray ? [] : null;
|
|
17354
|
-
const tasks = await Promise.all(taskInputs.map(
|
|
17355
|
-
(input) => this.prepareTask(input).catch(() => null)
|
|
17356
|
-
));
|
|
17357
|
-
const successfulTasks = tasks.filter((t) => t !== null);
|
|
17358
|
-
successfulTasks.forEach((task) => {
|
|
17359
|
-
this.executeBackground(task).catch((error45) => {
|
|
17360
|
-
this.onTaskError(task.id, error45);
|
|
17361
|
-
});
|
|
17362
|
-
});
|
|
17363
|
-
if (successfulTasks.length > 0) {
|
|
17364
|
-
this.startPolling();
|
|
17419
|
+
// src/core/recovery/constants.ts
|
|
17420
|
+
var MAX_RETRIES = RECOVERY.MAX_ATTEMPTS;
|
|
17421
|
+
var BASE_DELAY = RECOVERY.BASE_DELAY_MS;
|
|
17422
|
+
var MAX_HISTORY = HISTORY.MAX_RECOVERY;
|
|
17423
|
+
|
|
17424
|
+
// src/core/recovery/patterns.ts
|
|
17425
|
+
var errorPatterns = [
|
|
17426
|
+
// Rate limiting
|
|
17427
|
+
{
|
|
17428
|
+
pattern: /rate.?limit|too.?many.?requests|429/i,
|
|
17429
|
+
category: "rate_limit",
|
|
17430
|
+
handler: (ctx) => {
|
|
17431
|
+
const delay = BASE_DELAY * Math.pow(2, ctx.attempt);
|
|
17432
|
+
presets_exports.warningRateLimited();
|
|
17433
|
+
return { type: "retry", delay, attempt: ctx.attempt + 1 };
|
|
17365
17434
|
}
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
throw new Error(`Maximum task depth (${PARALLEL_TASK.MAX_DEPTH}) reached. To prevent infinite recursion, no further sub-tasks can be spawned.`);
|
|
17435
|
+
},
|
|
17436
|
+
// Context overflow
|
|
17437
|
+
{
|
|
17438
|
+
pattern: /context.?length|token.?limit|maximum.?context/i,
|
|
17439
|
+
category: "context_overflow",
|
|
17440
|
+
handler: () => {
|
|
17441
|
+
presets_exports.errorRecovery("Compacting context");
|
|
17442
|
+
return { type: "compact", reason: "Context limit reached" };
|
|
17375
17443
|
}
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
(_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 60s")), 6e4)
|
|
17387
|
-
)
|
|
17388
|
-
]);
|
|
17389
|
-
if (createResult.error || !createResult.data?.id) {
|
|
17390
|
-
throw new Error(`Session creation failed: ${createResult.error || "No ID"}`);
|
|
17444
|
+
},
|
|
17445
|
+
// Network errors
|
|
17446
|
+
{
|
|
17447
|
+
pattern: /ECONNREFUSED|ETIMEDOUT|network|fetch.?failed/i,
|
|
17448
|
+
category: "network",
|
|
17449
|
+
handler: (ctx) => {
|
|
17450
|
+
if (ctx.attempt >= MAX_RETRIES) {
|
|
17451
|
+
return { type: "abort", reason: "Network unavailable after retries" };
|
|
17452
|
+
}
|
|
17453
|
+
return { type: "retry", delay: BASE_DELAY * (ctx.attempt + 1), attempt: ctx.attempt + 1 };
|
|
17391
17454
|
}
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
prompt: input.prompt,
|
|
17400
|
-
agent: input.agent,
|
|
17401
|
-
status: TASK_STATUS.PENDING,
|
|
17402
|
-
// Start as PENDING
|
|
17403
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
17404
|
-
concurrencyKey: input.agent,
|
|
17405
|
-
depth: (input.depth ?? 0) + 1,
|
|
17406
|
-
mode: input.mode || "normal",
|
|
17407
|
-
groupID: input.groupID
|
|
17408
|
-
};
|
|
17409
|
-
this.store.set(taskId, task);
|
|
17410
|
-
this.store.trackPending(input.parentSessionID, taskId);
|
|
17411
|
-
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
17412
|
-
});
|
|
17413
|
-
const toastManager = getTaskToastManager();
|
|
17414
|
-
if (toastManager) {
|
|
17415
|
-
toastManager.addTask({
|
|
17416
|
-
id: taskId,
|
|
17417
|
-
description: input.description,
|
|
17418
|
-
agent: input.agent,
|
|
17419
|
-
isBackground: true,
|
|
17420
|
-
parentSessionID: input.parentSessionID,
|
|
17421
|
-
sessionID
|
|
17422
|
-
});
|
|
17455
|
+
},
|
|
17456
|
+
// Session errors
|
|
17457
|
+
{
|
|
17458
|
+
pattern: /session.?not.?found|session.?expired/i,
|
|
17459
|
+
category: "session",
|
|
17460
|
+
handler: () => {
|
|
17461
|
+
return { type: "abort", reason: "Session no longer available" };
|
|
17423
17462
|
}
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
try {
|
|
17432
|
-
await this.concurrency.acquire(task.agent);
|
|
17433
|
-
task.status = TASK_STATUS.RUNNING;
|
|
17434
|
-
task.startedAt = /* @__PURE__ */ new Date();
|
|
17435
|
-
this.store.set(task.id, task);
|
|
17436
|
-
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
17437
|
-
});
|
|
17438
|
-
const promptPromise = this.client.session.prompt({
|
|
17439
|
-
path: { id: task.sessionID },
|
|
17440
|
-
body: {
|
|
17441
|
-
agent: task.agent,
|
|
17442
|
-
tools: {
|
|
17443
|
-
// HPFA: Allow agents to delegate sub-tasks (Fractal Spawning)
|
|
17444
|
-
delegate_task: true,
|
|
17445
|
-
get_task_result: true,
|
|
17446
|
-
list_tasks: true,
|
|
17447
|
-
cancel_task: true,
|
|
17448
|
-
[TOOL_NAMES.SKILL]: true,
|
|
17449
|
-
[TOOL_NAMES.RUN_COMMAND]: true
|
|
17450
|
-
},
|
|
17451
|
-
parts: [{ type: PART_TYPES.TEXT, text: task.prompt }]
|
|
17452
|
-
}
|
|
17453
|
-
});
|
|
17454
|
-
await Promise.race([
|
|
17455
|
-
promptPromise,
|
|
17456
|
-
new Promise(
|
|
17457
|
-
(_, reject) => setTimeout(() => reject(new Error("Session prompt execution timed out after 600s")), 6e5)
|
|
17458
|
-
)
|
|
17459
|
-
]);
|
|
17460
|
-
} catch (error45) {
|
|
17461
|
-
this.concurrency.release(task.agent);
|
|
17462
|
-
throw error45;
|
|
17463
|
+
},
|
|
17464
|
+
// Tool errors
|
|
17465
|
+
{
|
|
17466
|
+
pattern: /tool.?not.?found|unknown.?tool/i,
|
|
17467
|
+
category: "tool",
|
|
17468
|
+
handler: (ctx) => {
|
|
17469
|
+
return { type: "escalate", to: "Reviewer", reason: `Unknown tool used by ${ctx.agent}` };
|
|
17463
17470
|
}
|
|
17464
|
-
}
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17471
|
+
},
|
|
17472
|
+
// Parse errors
|
|
17473
|
+
{
|
|
17474
|
+
pattern: /parse.?error|invalid.?json|syntax.?error/i,
|
|
17475
|
+
category: "parse",
|
|
17476
|
+
handler: (ctx) => {
|
|
17477
|
+
if (ctx.attempt >= 2) {
|
|
17478
|
+
return { type: "skip", reason: "Persistent parse error" };
|
|
17479
|
+
}
|
|
17480
|
+
return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
|
|
17481
|
+
}
|
|
17482
|
+
},
|
|
17483
|
+
// Gibberish / hallucination
|
|
17484
|
+
{
|
|
17485
|
+
pattern: /gibberish|hallucination|mixed.?language/i,
|
|
17486
|
+
category: "gibberish",
|
|
17487
|
+
handler: () => {
|
|
17488
|
+
presets_exports.errorRecovery("Retrying with clean context");
|
|
17489
|
+
return { type: "retry", delay: 1e3, attempt: 1 };
|
|
17490
|
+
}
|
|
17491
|
+
},
|
|
17492
|
+
// LSP specific errors
|
|
17493
|
+
{
|
|
17494
|
+
pattern: /lsp.?diagnostics|tsc.?error|eslint.?error/i,
|
|
17495
|
+
category: "lsp",
|
|
17496
|
+
handler: (ctx) => {
|
|
17497
|
+
return { type: "retry", delay: 2e3, attempt: ctx.attempt + 1, modifyPrompt: "Note: Previous attempt had LSP issues. Ensure code quality and consider simpler implementation." };
|
|
17498
|
+
}
|
|
17499
|
+
},
|
|
17500
|
+
// execution timeout
|
|
17501
|
+
{
|
|
17502
|
+
pattern: /execution.?timed.?out|timeout/i,
|
|
17503
|
+
category: "timeout",
|
|
17504
|
+
handler: (ctx) => {
|
|
17505
|
+
return { type: "retry", delay: 5e3, attempt: ctx.attempt + 1, modifyPrompt: "Note: Previous attempt timed out. Break down the task into smaller sub-tasks if necessary." };
|
|
17506
|
+
}
|
|
17507
|
+
}
|
|
17508
|
+
];
|
|
17509
|
+
|
|
17510
|
+
// src/core/recovery/handler.ts
|
|
17511
|
+
var recoveryHistory = [];
|
|
17512
|
+
function handleError(context) {
|
|
17513
|
+
const errorMessage = context.error.message || String(context.error);
|
|
17514
|
+
for (const pattern of errorPatterns) {
|
|
17515
|
+
const matches = typeof pattern.pattern === "string" ? errorMessage.includes(pattern.pattern) : pattern.pattern.test(errorMessage);
|
|
17516
|
+
if (matches) {
|
|
17517
|
+
const action = pattern.handler(context);
|
|
17518
|
+
recoveryHistory.push({
|
|
17519
|
+
context,
|
|
17520
|
+
action,
|
|
17521
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
17522
|
+
});
|
|
17523
|
+
if (recoveryHistory.length > MAX_HISTORY) {
|
|
17524
|
+
recoveryHistory.shift();
|
|
17525
|
+
}
|
|
17526
|
+
return action;
|
|
17527
|
+
}
|
|
17528
|
+
}
|
|
17529
|
+
if (context.attempt < MAX_RETRIES) {
|
|
17530
|
+
return {
|
|
17531
|
+
type: "retry",
|
|
17532
|
+
delay: BASE_DELAY * Math.pow(2, context.attempt),
|
|
17533
|
+
attempt: context.attempt + 1
|
|
17534
|
+
};
|
|
17535
|
+
}
|
|
17536
|
+
return { type: "abort", reason: `Unknown error after ${MAX_RETRIES} retries` };
|
|
17537
|
+
}
|
|
17538
|
+
|
|
17539
|
+
// src/core/memory/interfaces.ts
|
|
17540
|
+
var MemoryLevel = /* @__PURE__ */ ((MemoryLevel2) => {
|
|
17541
|
+
MemoryLevel2["SYSTEM"] = "system";
|
|
17542
|
+
MemoryLevel2["PROJECT"] = "project";
|
|
17543
|
+
MemoryLevel2["MISSION"] = "mission";
|
|
17544
|
+
MemoryLevel2["TASK"] = "task";
|
|
17545
|
+
return MemoryLevel2;
|
|
17546
|
+
})(MemoryLevel || {});
|
|
17547
|
+
|
|
17548
|
+
// src/core/memory/memory-manager.ts
|
|
17549
|
+
var MemoryManager = class _MemoryManager {
|
|
17550
|
+
static instance;
|
|
17551
|
+
memories = /* @__PURE__ */ new Map();
|
|
17552
|
+
// id -> level mapping
|
|
17553
|
+
storage = {
|
|
17554
|
+
["system" /* SYSTEM */]: [],
|
|
17555
|
+
["project" /* PROJECT */]: [],
|
|
17556
|
+
["mission" /* MISSION */]: [],
|
|
17557
|
+
["task" /* TASK */]: []
|
|
17558
|
+
};
|
|
17559
|
+
config = {
|
|
17560
|
+
tokenBudgets: {
|
|
17561
|
+
["system" /* SYSTEM */]: 2e3,
|
|
17562
|
+
["project" /* PROJECT */]: 5e3,
|
|
17563
|
+
["mission" /* MISSION */]: 1e4,
|
|
17564
|
+
["task" /* TASK */]: 2e4
|
|
17565
|
+
},
|
|
17566
|
+
enableRelevanceFiltering: true
|
|
17567
|
+
};
|
|
17568
|
+
constructor() {
|
|
17569
|
+
}
|
|
17570
|
+
static getInstance() {
|
|
17571
|
+
if (!_MemoryManager.instance) {
|
|
17572
|
+
_MemoryManager.instance = new _MemoryManager();
|
|
17573
|
+
}
|
|
17574
|
+
return _MemoryManager.instance;
|
|
17575
|
+
}
|
|
17576
|
+
/**
|
|
17577
|
+
* Add a memory entry
|
|
17578
|
+
*/
|
|
17579
|
+
add(level, content, importance = MEMORY_CONSTANTS.IMPORTANCE.NORMAL, metadata) {
|
|
17580
|
+
const id = `${MEMORY_CONSTANTS.ID_PREFIX}${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
17581
|
+
const entry = {
|
|
17582
|
+
id,
|
|
17583
|
+
level,
|
|
17584
|
+
content: content.trim(),
|
|
17585
|
+
timestamp: Date.now(),
|
|
17586
|
+
importance,
|
|
17587
|
+
metadata
|
|
17588
|
+
};
|
|
17589
|
+
this.storage[level].push(entry);
|
|
17590
|
+
this.memories.set(id, level);
|
|
17591
|
+
this.storage[level].sort((a, b) => b.importance - a.importance || b.timestamp - a.timestamp);
|
|
17592
|
+
log(`[MemoryManager] Added ${level} memory: ${id.slice(0, 10)}...`);
|
|
17593
|
+
this.prune(level);
|
|
17594
|
+
return id;
|
|
17595
|
+
}
|
|
17596
|
+
/**
|
|
17597
|
+
* Retrieve memory for prompt construction
|
|
17598
|
+
*/
|
|
17599
|
+
getContext(query) {
|
|
17600
|
+
let context = "";
|
|
17601
|
+
for (const level of ["system" /* SYSTEM */, "project" /* PROJECT */, "mission" /* MISSION */, "task" /* TASK */]) {
|
|
17602
|
+
const entries = this.storage[level];
|
|
17603
|
+
if (entries.length === 0) continue;
|
|
17604
|
+
context += `
|
|
17605
|
+
### ${level.toUpperCase()} MEMORY
|
|
17606
|
+
`;
|
|
17607
|
+
const filteredEntries = query && this.config.enableRelevanceFiltering ? entries.filter((e) => this.isRelevant(e, query)) : entries;
|
|
17608
|
+
for (const entry of filteredEntries) {
|
|
17609
|
+
context += `- [${new Date(entry.timestamp).toISOString()}] ${entry.content}
|
|
17610
|
+
`;
|
|
17611
|
+
}
|
|
17612
|
+
}
|
|
17613
|
+
return context.trim();
|
|
17614
|
+
}
|
|
17615
|
+
/**
|
|
17616
|
+
* Simple keyword-based relevance check
|
|
17617
|
+
*/
|
|
17618
|
+
isRelevant(entry, query) {
|
|
17619
|
+
if (entry.importance > 0.8) return true;
|
|
17620
|
+
const q = query.toLowerCase();
|
|
17621
|
+
const content = entry.content.toLowerCase();
|
|
17622
|
+
const keywords = q.split(/\s+/).filter((k) => k.length > 3);
|
|
17623
|
+
if (keywords.length === 0) return true;
|
|
17624
|
+
return keywords.some((k) => content.includes(k));
|
|
17625
|
+
}
|
|
17626
|
+
/**
|
|
17627
|
+
* Prune memory based on token budget (simplified)
|
|
17628
|
+
*/
|
|
17629
|
+
prune(level) {
|
|
17630
|
+
const budget = this.config.tokenBudgets[level];
|
|
17631
|
+
let currentSize = this.storage[level].reduce((acc, e) => acc + e.content.length / 4, 0);
|
|
17632
|
+
while (currentSize > budget && this.storage[level].length > 0) {
|
|
17633
|
+
const removed = this.storage[level].pop();
|
|
17634
|
+
if (removed) {
|
|
17635
|
+
this.memories.delete(removed.id);
|
|
17636
|
+
currentSize -= removed.content.length / 4;
|
|
17637
|
+
}
|
|
17638
|
+
}
|
|
17639
|
+
}
|
|
17640
|
+
/**
|
|
17641
|
+
* Clear task memory (Short-term)
|
|
17642
|
+
*/
|
|
17643
|
+
clearTaskMemory() {
|
|
17644
|
+
for (const entry of this.storage["task" /* TASK */]) {
|
|
17645
|
+
this.memories.delete(entry.id);
|
|
17646
|
+
}
|
|
17647
|
+
this.storage["task" /* TASK */] = [];
|
|
17648
|
+
log("[MemoryManager] Task memory cleared.");
|
|
17649
|
+
}
|
|
17650
|
+
/**
|
|
17651
|
+
* Export full memory state (for persistence)
|
|
17652
|
+
*/
|
|
17653
|
+
export() {
|
|
17654
|
+
return { ...this.storage };
|
|
17655
|
+
}
|
|
17656
|
+
/**
|
|
17657
|
+
* Import memory state
|
|
17658
|
+
*/
|
|
17659
|
+
import(snapshot) {
|
|
17660
|
+
this.storage = snapshot;
|
|
17661
|
+
this.memories.clear();
|
|
17662
|
+
for (const level of Object.values(MemoryLevel)) {
|
|
17663
|
+
for (const entry of this.storage[level]) {
|
|
17664
|
+
this.memories.set(entry.id, level);
|
|
17665
|
+
}
|
|
17666
|
+
}
|
|
17667
|
+
}
|
|
17668
|
+
};
|
|
17669
|
+
|
|
17670
|
+
// src/core/agents/agent-registry.ts
|
|
17671
|
+
import * as fs4 from "fs/promises";
|
|
17672
|
+
import * as path4 from "path";
|
|
17673
|
+
var AgentRegistry = class _AgentRegistry {
|
|
17674
|
+
static instance;
|
|
17675
|
+
agents = /* @__PURE__ */ new Map();
|
|
17676
|
+
directory = "";
|
|
17677
|
+
constructor() {
|
|
17678
|
+
for (const [name, def] of Object.entries(AGENTS)) {
|
|
17679
|
+
this.agents.set(name, def);
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
static getInstance() {
|
|
17683
|
+
if (!_AgentRegistry.instance) {
|
|
17684
|
+
_AgentRegistry.instance = new _AgentRegistry();
|
|
17685
|
+
}
|
|
17686
|
+
return _AgentRegistry.instance;
|
|
17687
|
+
}
|
|
17688
|
+
setDirectory(dir) {
|
|
17689
|
+
this.directory = dir;
|
|
17690
|
+
this.loadCustomAgents();
|
|
17691
|
+
}
|
|
17692
|
+
/**
|
|
17693
|
+
* Get agent definition by name
|
|
17694
|
+
*/
|
|
17695
|
+
getAgent(name) {
|
|
17696
|
+
return this.agents.get(name);
|
|
17697
|
+
}
|
|
17698
|
+
/**
|
|
17699
|
+
* List all available agent names
|
|
17700
|
+
*/
|
|
17701
|
+
listAgents() {
|
|
17702
|
+
return Array.from(this.agents.keys());
|
|
17703
|
+
}
|
|
17704
|
+
/**
|
|
17705
|
+
* Add or update an agent definition
|
|
17706
|
+
*/
|
|
17707
|
+
registerAgent(name, def) {
|
|
17708
|
+
this.agents.set(name, def);
|
|
17709
|
+
log(`[AgentRegistry] Registered agent: ${name}`);
|
|
17710
|
+
}
|
|
17711
|
+
/**
|
|
17712
|
+
* Load custom agents from .opencode/agents.json
|
|
17713
|
+
*/
|
|
17714
|
+
async loadCustomAgents() {
|
|
17715
|
+
if (!this.directory) return;
|
|
17716
|
+
const agentsConfigPath = path4.join(this.directory, PATHS.AGENTS_CONFIG);
|
|
17717
|
+
try {
|
|
17718
|
+
const content = await fs4.readFile(agentsConfigPath, "utf-8");
|
|
17719
|
+
const customAgents = JSON.parse(content);
|
|
17720
|
+
if (typeof customAgents === "object" && customAgents !== null) {
|
|
17721
|
+
for (const [name, def] of Object.entries(customAgents)) {
|
|
17722
|
+
if (this.isValidAgentDefinition(def)) {
|
|
17723
|
+
this.registerAgent(name, def);
|
|
17724
|
+
} else {
|
|
17725
|
+
log(`[AgentRegistry] Invalid custom agent definition for: ${name}`);
|
|
17726
|
+
}
|
|
17727
|
+
}
|
|
17728
|
+
}
|
|
17729
|
+
} catch (error45) {
|
|
17730
|
+
if (error45.code !== "ENOENT") {
|
|
17731
|
+
log(`[AgentRegistry] Error loading custom agents: ${error45}`);
|
|
17732
|
+
}
|
|
17733
|
+
}
|
|
17734
|
+
}
|
|
17735
|
+
isValidAgentDefinition(def) {
|
|
17736
|
+
return typeof def.id === "string" && typeof def.description === "string" && typeof def.systemPrompt === "string" && typeof def.canWrite === "boolean" && typeof def.canBash === "boolean";
|
|
17737
|
+
}
|
|
17738
|
+
};
|
|
17739
|
+
|
|
17740
|
+
// src/core/agents/manager/task-launcher.ts
|
|
17741
|
+
var TaskLauncher = class {
|
|
17742
|
+
constructor(client, directory, store, concurrency, sessionPool2, onTaskError, startPolling) {
|
|
17743
|
+
this.client = client;
|
|
17744
|
+
this.directory = directory;
|
|
17745
|
+
this.store = store;
|
|
17746
|
+
this.concurrency = concurrency;
|
|
17747
|
+
this.sessionPool = sessionPool2;
|
|
17748
|
+
this.onTaskError = onTaskError;
|
|
17749
|
+
this.startPolling = startPolling;
|
|
17750
|
+
}
|
|
17751
|
+
/**
|
|
17752
|
+
* Unified launch method - handles both single and multiple tasks efficiently.
|
|
17753
|
+
* All session creations happen in parallel immediately.
|
|
17754
|
+
* Concurrency acquisition and prompt firing happen in the background.
|
|
17755
|
+
*/
|
|
17756
|
+
async launch(inputs) {
|
|
17757
|
+
const isArray = Array.isArray(inputs);
|
|
17758
|
+
const taskInputs = isArray ? inputs : [inputs];
|
|
17759
|
+
if (taskInputs.length === 0) return isArray ? [] : null;
|
|
17760
|
+
const tasks = await Promise.all(taskInputs.map(
|
|
17761
|
+
(input) => this.prepareTask(input).catch(() => null)
|
|
17762
|
+
));
|
|
17763
|
+
const successfulTasks = tasks.filter((t) => t !== null);
|
|
17764
|
+
successfulTasks.forEach((task) => {
|
|
17765
|
+
this.executeBackground(task).catch((error45) => {
|
|
17766
|
+
this.onTaskError(task.id, error45);
|
|
17767
|
+
});
|
|
17768
|
+
});
|
|
17769
|
+
if (successfulTasks.length > 0) {
|
|
17770
|
+
this.startPolling();
|
|
17771
|
+
}
|
|
17772
|
+
return isArray ? successfulTasks : successfulTasks[0] || null;
|
|
17773
|
+
}
|
|
17774
|
+
/**
|
|
17775
|
+
* Prepare task: Create session and registration without blocking on concurrency
|
|
17776
|
+
*/
|
|
17777
|
+
async prepareTask(input) {
|
|
17778
|
+
const currentDepth = input.depth ?? 0;
|
|
17779
|
+
if (currentDepth >= PARALLEL_TASK.MAX_DEPTH) {
|
|
17780
|
+
throw new Error(`Maximum task depth (${PARALLEL_TASK.MAX_DEPTH}) reached. To prevent infinite recursion, no further sub-tasks can be spawned.`);
|
|
17781
|
+
}
|
|
17782
|
+
const session = await this.sessionPool.acquire(
|
|
17783
|
+
input.agent,
|
|
17784
|
+
input.parentSessionID,
|
|
17785
|
+
input.description
|
|
17786
|
+
);
|
|
17787
|
+
const sessionID = session.id;
|
|
17788
|
+
const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
|
|
17789
|
+
const task = {
|
|
17790
|
+
id: taskId,
|
|
17791
|
+
sessionID,
|
|
17792
|
+
parentSessionID: input.parentSessionID,
|
|
17793
|
+
description: input.description,
|
|
17794
|
+
prompt: input.prompt,
|
|
17795
|
+
agent: input.agent,
|
|
17796
|
+
status: TASK_STATUS.PENDING,
|
|
17797
|
+
// Start as PENDING
|
|
17798
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
17799
|
+
concurrencyKey: input.agent,
|
|
17800
|
+
depth: (input.depth ?? 0) + 1,
|
|
17801
|
+
mode: input.mode || "normal",
|
|
17802
|
+
groupID: input.groupID
|
|
17803
|
+
};
|
|
17804
|
+
this.store.set(taskId, task);
|
|
17805
|
+
this.store.trackPending(input.parentSessionID, taskId);
|
|
17806
|
+
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
17807
|
+
});
|
|
17808
|
+
const toastManager = getTaskToastManager();
|
|
17809
|
+
if (toastManager) {
|
|
17810
|
+
toastManager.addTask({
|
|
17811
|
+
id: taskId,
|
|
17812
|
+
description: input.description,
|
|
17813
|
+
agent: input.agent,
|
|
17814
|
+
isBackground: true,
|
|
17815
|
+
parentSessionID: input.parentSessionID,
|
|
17816
|
+
sessionID
|
|
17817
|
+
});
|
|
17818
|
+
}
|
|
17819
|
+
presets_exports.sessionCreated(sessionID, input.agent);
|
|
17820
|
+
return task;
|
|
17821
|
+
}
|
|
17822
|
+
/**
|
|
17823
|
+
* Background execution: Acquire slot and fire prompt with auto-retry
|
|
17824
|
+
*/
|
|
17825
|
+
async executeBackground(task) {
|
|
17826
|
+
let attempt = 1;
|
|
17827
|
+
while (true) {
|
|
17828
|
+
try {
|
|
17829
|
+
await this.concurrency.acquire(task.agent);
|
|
17830
|
+
task.status = TASK_STATUS.RUNNING;
|
|
17831
|
+
task.startedAt = /* @__PURE__ */ new Date();
|
|
17832
|
+
this.store.set(task.id, task);
|
|
17833
|
+
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
17834
|
+
});
|
|
17835
|
+
const agentDef = AgentRegistry.getInstance().getAgent(task.agent);
|
|
17836
|
+
let finalPrompt = task.prompt;
|
|
17837
|
+
if (agentDef) {
|
|
17838
|
+
finalPrompt = `### AGENT ROLE: ${agentDef.id}
|
|
17839
|
+
${agentDef.description}
|
|
17840
|
+
|
|
17841
|
+
${agentDef.systemPrompt}
|
|
17842
|
+
|
|
17843
|
+
${finalPrompt}`;
|
|
17844
|
+
}
|
|
17845
|
+
const memory = MemoryManager.getInstance().getContext(finalPrompt);
|
|
17846
|
+
const injectedPrompt = memory ? `${memory}
|
|
17847
|
+
|
|
17848
|
+
${finalPrompt}` : finalPrompt;
|
|
17849
|
+
const wireAgent = Object.values(AGENT_NAMES).includes(task.agent) ? task.agent : AGENT_NAMES.COMMANDER;
|
|
17850
|
+
const promptPromise = this.client.session.prompt({
|
|
17851
|
+
path: { id: task.sessionID },
|
|
17852
|
+
body: {
|
|
17853
|
+
agent: wireAgent,
|
|
17854
|
+
tools: {
|
|
17855
|
+
[TOOL_NAMES.DELEGATE_TASK]: true,
|
|
17856
|
+
[TOOL_NAMES.GET_TASK_RESULT]: true,
|
|
17857
|
+
[TOOL_NAMES.LIST_TASKS]: true,
|
|
17858
|
+
[TOOL_NAMES.CANCEL_TASK]: true,
|
|
17859
|
+
[TOOL_NAMES.SKILL]: true,
|
|
17860
|
+
[TOOL_NAMES.RUN_COMMAND]: true
|
|
17861
|
+
},
|
|
17862
|
+
parts: [{ type: PART_TYPES.TEXT, text: injectedPrompt }]
|
|
17863
|
+
}
|
|
17864
|
+
});
|
|
17865
|
+
await Promise.race([
|
|
17866
|
+
promptPromise,
|
|
17867
|
+
new Promise(
|
|
17868
|
+
(_, reject) => setTimeout(() => reject(new Error("Session prompt execution timed out after 600s")), 6e5)
|
|
17869
|
+
)
|
|
17870
|
+
]);
|
|
17871
|
+
return;
|
|
17872
|
+
} catch (error45) {
|
|
17873
|
+
this.concurrency.release(task.agent);
|
|
17874
|
+
const context = {
|
|
17875
|
+
sessionId: task.sessionID,
|
|
17876
|
+
taskId: task.id,
|
|
17877
|
+
agent: task.agent,
|
|
17878
|
+
error: error45 instanceof Error ? error45 : new Error(String(error45)),
|
|
17879
|
+
attempt,
|
|
17880
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
17881
|
+
};
|
|
17882
|
+
const action = handleError(context);
|
|
17883
|
+
if (action.type === "retry") {
|
|
17884
|
+
log(`[AutoRetry] Task ${task.id} failed (attempt ${attempt}). Retrying in ${action.delay}ms...`);
|
|
17885
|
+
if (action.modifyPrompt) {
|
|
17886
|
+
task.prompt += `
|
|
17887
|
+
|
|
17888
|
+
${action.modifyPrompt}`;
|
|
17889
|
+
}
|
|
17890
|
+
await new Promise((r) => setTimeout(r, action.delay));
|
|
17891
|
+
attempt++;
|
|
17892
|
+
continue;
|
|
17893
|
+
}
|
|
17894
|
+
throw error45;
|
|
17895
|
+
}
|
|
17896
|
+
}
|
|
17897
|
+
}
|
|
17898
|
+
};
|
|
17899
|
+
|
|
17900
|
+
// src/core/agents/manager/task-resumer.ts
|
|
17901
|
+
var TaskResumer = class {
|
|
17902
|
+
constructor(client, store, findBySession, startPolling, notifyParentIfAllComplete) {
|
|
17470
17903
|
this.client = client;
|
|
17471
17904
|
this.store = store;
|
|
17472
17905
|
this.findBySession = findBySession;
|
|
@@ -17520,6 +17953,98 @@ var CONFIG = {
|
|
|
17520
17953
|
POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
|
|
17521
17954
|
};
|
|
17522
17955
|
|
|
17956
|
+
// src/core/progress/state-broadcaster.ts
|
|
17957
|
+
var StateBroadcaster = class _StateBroadcaster {
|
|
17958
|
+
static _instance;
|
|
17959
|
+
listeners = /* @__PURE__ */ new Set();
|
|
17960
|
+
currentState = null;
|
|
17961
|
+
constructor() {
|
|
17962
|
+
}
|
|
17963
|
+
static getInstance() {
|
|
17964
|
+
if (!_StateBroadcaster._instance) {
|
|
17965
|
+
_StateBroadcaster._instance = new _StateBroadcaster();
|
|
17966
|
+
}
|
|
17967
|
+
return _StateBroadcaster._instance;
|
|
17968
|
+
}
|
|
17969
|
+
subscribe(listener) {
|
|
17970
|
+
this.listeners.add(listener);
|
|
17971
|
+
if (this.currentState) {
|
|
17972
|
+
listener(this.currentState);
|
|
17973
|
+
}
|
|
17974
|
+
return () => this.listeners.delete(listener);
|
|
17975
|
+
}
|
|
17976
|
+
broadcast(state2) {
|
|
17977
|
+
this.currentState = state2;
|
|
17978
|
+
this.listeners.forEach((listener) => {
|
|
17979
|
+
try {
|
|
17980
|
+
listener(state2);
|
|
17981
|
+
} catch (error45) {
|
|
17982
|
+
}
|
|
17983
|
+
});
|
|
17984
|
+
}
|
|
17985
|
+
getCurrentState() {
|
|
17986
|
+
return this.currentState;
|
|
17987
|
+
}
|
|
17988
|
+
};
|
|
17989
|
+
var stateBroadcaster = StateBroadcaster.getInstance();
|
|
17990
|
+
|
|
17991
|
+
// src/core/progress/progress-notifier.ts
|
|
17992
|
+
var ProgressNotifier = class _ProgressNotifier {
|
|
17993
|
+
static _instance;
|
|
17994
|
+
manager = null;
|
|
17995
|
+
constructor() {
|
|
17996
|
+
stateBroadcaster.subscribe(this.handleStateChange.bind(this));
|
|
17997
|
+
}
|
|
17998
|
+
static getInstance() {
|
|
17999
|
+
if (!_ProgressNotifier._instance) {
|
|
18000
|
+
_ProgressNotifier._instance = new _ProgressNotifier();
|
|
18001
|
+
}
|
|
18002
|
+
return _ProgressNotifier._instance;
|
|
18003
|
+
}
|
|
18004
|
+
setManager(manager) {
|
|
18005
|
+
this.manager = manager;
|
|
18006
|
+
}
|
|
18007
|
+
/**
|
|
18008
|
+
* Poll current status from ParallelAgentManager and broadcast it
|
|
18009
|
+
*/
|
|
18010
|
+
update() {
|
|
18011
|
+
if (!this.manager) return;
|
|
18012
|
+
const tasks = this.manager.getAllTasks();
|
|
18013
|
+
const running = tasks.filter((t) => t.status === TASK_STATUS.RUNNING);
|
|
18014
|
+
const completed = tasks.filter((t) => t.status === TASK_STATUS.COMPLETED);
|
|
18015
|
+
const total = tasks.length;
|
|
18016
|
+
const percentage = total > 0 ? Math.round(completed.length / total * 100) : 0;
|
|
18017
|
+
const state2 = {
|
|
18018
|
+
missionId: "current-mission",
|
|
18019
|
+
// Could be dynamic
|
|
18020
|
+
status: percentage === 100 ? "completed" : "executing",
|
|
18021
|
+
progress: {
|
|
18022
|
+
totalTasks: total,
|
|
18023
|
+
completedTasks: completed.length,
|
|
18024
|
+
percentage
|
|
18025
|
+
},
|
|
18026
|
+
activeAgents: running.map((t) => ({
|
|
18027
|
+
id: t.id,
|
|
18028
|
+
type: t.agent,
|
|
18029
|
+
status: t.status,
|
|
18030
|
+
currentTask: t.description
|
|
18031
|
+
})),
|
|
18032
|
+
todo: [],
|
|
18033
|
+
// Need to fetch from TodoEnforcer if possible
|
|
18034
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
18035
|
+
};
|
|
18036
|
+
stateBroadcaster.broadcast(state2);
|
|
18037
|
+
}
|
|
18038
|
+
handleStateChange(state2) {
|
|
18039
|
+
if (state2.progress.percentage > 0 && state2.progress.percentage % 25 === 0) {
|
|
18040
|
+
const toastManager = getTaskToastManager();
|
|
18041
|
+
if (toastManager) {
|
|
18042
|
+
}
|
|
18043
|
+
}
|
|
18044
|
+
}
|
|
18045
|
+
};
|
|
18046
|
+
var progressNotifier = ProgressNotifier.getInstance();
|
|
18047
|
+
|
|
17523
18048
|
// src/core/agents/manager/task-poller.ts
|
|
17524
18049
|
var TaskPoller = class {
|
|
17525
18050
|
constructor(client, store, concurrency, notifyParentIfAllComplete, scheduleCleanup, pruneExpiredTasks, onTaskComplete) {
|
|
@@ -17581,6 +18106,7 @@ var TaskPoller = class {
|
|
|
17581
18106
|
log(`Poll error for task ${task.id}:`, error45);
|
|
17582
18107
|
}
|
|
17583
18108
|
}
|
|
18109
|
+
progressNotifier.update();
|
|
17584
18110
|
} catch (error45) {
|
|
17585
18111
|
log("Polling error:", error45);
|
|
17586
18112
|
}
|
|
@@ -17619,6 +18145,7 @@ var TaskPoller = class {
|
|
|
17619
18145
|
const duration3 = formatDuration(task.startedAt, task.completedAt);
|
|
17620
18146
|
presets_exports.sessionCompleted(task.sessionID, duration3);
|
|
17621
18147
|
log(`Completed ${task.id} (${duration3})`);
|
|
18148
|
+
progressNotifier.update();
|
|
17622
18149
|
}
|
|
17623
18150
|
async updateTaskProgress(task) {
|
|
17624
18151
|
try {
|
|
@@ -17661,10 +18188,11 @@ var TaskPoller = class {
|
|
|
17661
18188
|
// src/core/agents/manager/task-cleaner.ts
|
|
17662
18189
|
init_store();
|
|
17663
18190
|
var TaskCleaner = class {
|
|
17664
|
-
constructor(client, store, concurrency) {
|
|
18191
|
+
constructor(client, store, concurrency, sessionPool2) {
|
|
17665
18192
|
this.client = client;
|
|
17666
18193
|
this.store = store;
|
|
17667
18194
|
this.concurrency = concurrency;
|
|
18195
|
+
this.sessionPool = sessionPool2;
|
|
17668
18196
|
}
|
|
17669
18197
|
pruneExpiredTasks() {
|
|
17670
18198
|
const now = Date.now();
|
|
@@ -17689,9 +18217,9 @@ var TaskCleaner = class {
|
|
|
17689
18217
|
});
|
|
17690
18218
|
}
|
|
17691
18219
|
}
|
|
17692
|
-
this.
|
|
18220
|
+
this.sessionPool.release(task.sessionID).catch(() => {
|
|
17693
18221
|
});
|
|
17694
|
-
|
|
18222
|
+
clear2(task.sessionID);
|
|
17695
18223
|
this.store.delete(taskId);
|
|
17696
18224
|
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
17697
18225
|
});
|
|
@@ -17704,8 +18232,8 @@ var TaskCleaner = class {
|
|
|
17704
18232
|
setTimeout(async () => {
|
|
17705
18233
|
if (sessionID) {
|
|
17706
18234
|
try {
|
|
17707
|
-
await this.
|
|
17708
|
-
|
|
18235
|
+
await this.sessionPool.release(sessionID);
|
|
18236
|
+
clear2(sessionID);
|
|
17709
18237
|
} catch {
|
|
17710
18238
|
}
|
|
17711
18239
|
}
|
|
@@ -17832,6 +18360,7 @@ var EventHandler = class {
|
|
|
17832
18360
|
if (this.onTaskComplete) {
|
|
17833
18361
|
Promise.resolve(this.onTaskComplete(task)).catch((err) => log("Error in onTaskComplete callback:", err));
|
|
17834
18362
|
}
|
|
18363
|
+
progressNotifier.update();
|
|
17835
18364
|
log(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
17836
18365
|
}
|
|
17837
18366
|
handleSessionDeleted(task) {
|
|
@@ -17851,10 +18380,378 @@ var EventHandler = class {
|
|
|
17851
18380
|
this.store.delete(task.id);
|
|
17852
18381
|
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
17853
18382
|
});
|
|
18383
|
+
progressNotifier.update();
|
|
17854
18384
|
log(`Cleaned up deleted session task: ${task.id}`);
|
|
17855
18385
|
}
|
|
17856
18386
|
};
|
|
17857
18387
|
|
|
18388
|
+
// src/core/agents/session-pool.ts
|
|
18389
|
+
var DEFAULT_CONFIG = {
|
|
18390
|
+
maxPoolSizePerAgent: 5,
|
|
18391
|
+
idleTimeoutMs: 3e5,
|
|
18392
|
+
// 5 minutes
|
|
18393
|
+
maxReuseCount: 10,
|
|
18394
|
+
healthCheckIntervalMs: 6e4
|
|
18395
|
+
// 1 minute
|
|
18396
|
+
};
|
|
18397
|
+
var SessionPool = class _SessionPool {
|
|
18398
|
+
static _instance;
|
|
18399
|
+
pool = /* @__PURE__ */ new Map();
|
|
18400
|
+
// key: agentName
|
|
18401
|
+
sessionsById = /* @__PURE__ */ new Map();
|
|
18402
|
+
config;
|
|
18403
|
+
client;
|
|
18404
|
+
directory;
|
|
18405
|
+
healthCheckInterval = null;
|
|
18406
|
+
// Statistics
|
|
18407
|
+
stats = {
|
|
18408
|
+
reuseHits: 0,
|
|
18409
|
+
creationMisses: 0
|
|
18410
|
+
};
|
|
18411
|
+
constructor(client, directory, config2 = {}) {
|
|
18412
|
+
this.client = client;
|
|
18413
|
+
this.directory = directory;
|
|
18414
|
+
this.config = { ...DEFAULT_CONFIG, ...config2 };
|
|
18415
|
+
this.startHealthCheck();
|
|
18416
|
+
}
|
|
18417
|
+
static getInstance(client, directory, config2) {
|
|
18418
|
+
if (!_SessionPool._instance) {
|
|
18419
|
+
if (!client || !directory) {
|
|
18420
|
+
throw new Error("SessionPool requires client and directory on first call");
|
|
18421
|
+
}
|
|
18422
|
+
_SessionPool._instance = new _SessionPool(client, directory, config2);
|
|
18423
|
+
}
|
|
18424
|
+
return _SessionPool._instance;
|
|
18425
|
+
}
|
|
18426
|
+
/**
|
|
18427
|
+
* Acquire a session from the pool or create a new one.
|
|
18428
|
+
*/
|
|
18429
|
+
async acquire(agentName, parentSessionID, description) {
|
|
18430
|
+
const poolKey = this.getPoolKey(agentName);
|
|
18431
|
+
const agentPool = this.pool.get(poolKey) || [];
|
|
18432
|
+
const available = agentPool.find((s) => !s.inUse && s.reuseCount < this.config.maxReuseCount);
|
|
18433
|
+
if (available) {
|
|
18434
|
+
available.inUse = true;
|
|
18435
|
+
available.lastUsedAt = /* @__PURE__ */ new Date();
|
|
18436
|
+
available.reuseCount++;
|
|
18437
|
+
this.stats.reuseHits++;
|
|
18438
|
+
log(`[SessionPool] Reusing session ${available.id.slice(0, 8)}... for ${agentName} (reuse #${available.reuseCount})`);
|
|
18439
|
+
return available;
|
|
18440
|
+
}
|
|
18441
|
+
this.stats.creationMisses++;
|
|
18442
|
+
return this.createSession(agentName, parentSessionID, description);
|
|
18443
|
+
}
|
|
18444
|
+
/**
|
|
18445
|
+
* Release a session back to the pool for reuse.
|
|
18446
|
+
*/
|
|
18447
|
+
async release(sessionId) {
|
|
18448
|
+
const session = this.sessionsById.get(sessionId);
|
|
18449
|
+
if (!session) {
|
|
18450
|
+
log(`[SessionPool] Session ${sessionId.slice(0, 8)}... not found in pool`);
|
|
18451
|
+
return;
|
|
18452
|
+
}
|
|
18453
|
+
const now = Date.now();
|
|
18454
|
+
const age = now - session.createdAt.getTime();
|
|
18455
|
+
const idle = now - session.lastUsedAt.getTime();
|
|
18456
|
+
if (session.reuseCount >= this.config.maxReuseCount || age > this.config.idleTimeoutMs * 2) {
|
|
18457
|
+
await this.invalidate(sessionId);
|
|
18458
|
+
return;
|
|
18459
|
+
}
|
|
18460
|
+
const poolKey = this.getPoolKey(session.agentName);
|
|
18461
|
+
const agentPool = this.pool.get(poolKey) || [];
|
|
18462
|
+
const availableCount = agentPool.filter((s) => !s.inUse).length;
|
|
18463
|
+
if (availableCount >= this.config.maxPoolSizePerAgent) {
|
|
18464
|
+
const oldest = agentPool.filter((s) => !s.inUse).sort((a, b) => a.lastUsedAt.getTime() - b.lastUsedAt.getTime())[0];
|
|
18465
|
+
if (oldest) {
|
|
18466
|
+
await this.deleteSession(oldest.id);
|
|
18467
|
+
}
|
|
18468
|
+
}
|
|
18469
|
+
session.inUse = false;
|
|
18470
|
+
log(`[SessionPool] Released session ${sessionId.slice(0, 8)}... to pool`);
|
|
18471
|
+
}
|
|
18472
|
+
/**
|
|
18473
|
+
* Invalidate a session (remove from pool and delete).
|
|
18474
|
+
*/
|
|
18475
|
+
async invalidate(sessionId) {
|
|
18476
|
+
const session = this.sessionsById.get(sessionId);
|
|
18477
|
+
if (!session) return;
|
|
18478
|
+
await this.deleteSession(sessionId);
|
|
18479
|
+
log(`[SessionPool] Invalidated session ${sessionId.slice(0, 8)}...`);
|
|
18480
|
+
}
|
|
18481
|
+
/**
|
|
18482
|
+
* Get current pool statistics.
|
|
18483
|
+
*/
|
|
18484
|
+
getStats() {
|
|
18485
|
+
const byAgent = {};
|
|
18486
|
+
for (const [agentName, sessions] of this.pool.entries()) {
|
|
18487
|
+
const inUse = sessions.filter((s) => s.inUse).length;
|
|
18488
|
+
byAgent[agentName] = {
|
|
18489
|
+
total: sessions.length,
|
|
18490
|
+
inUse,
|
|
18491
|
+
available: sessions.length - inUse
|
|
18492
|
+
};
|
|
18493
|
+
}
|
|
18494
|
+
const allSessions = Array.from(this.sessionsById.values());
|
|
18495
|
+
const inUseCount = allSessions.filter((s) => s.inUse).length;
|
|
18496
|
+
return {
|
|
18497
|
+
totalSessions: allSessions.length,
|
|
18498
|
+
sessionsInUse: inUseCount,
|
|
18499
|
+
availableSessions: allSessions.length - inUseCount,
|
|
18500
|
+
reuseHits: this.stats.reuseHits,
|
|
18501
|
+
creationMisses: this.stats.creationMisses,
|
|
18502
|
+
byAgent
|
|
18503
|
+
};
|
|
18504
|
+
}
|
|
18505
|
+
/**
|
|
18506
|
+
* Cleanup stale sessions.
|
|
18507
|
+
*/
|
|
18508
|
+
async cleanup() {
|
|
18509
|
+
const now = Date.now();
|
|
18510
|
+
let cleanedCount = 0;
|
|
18511
|
+
for (const [sessionId, session] of this.sessionsById.entries()) {
|
|
18512
|
+
if (session.inUse) continue;
|
|
18513
|
+
const idle = now - session.lastUsedAt.getTime();
|
|
18514
|
+
if (idle > this.config.idleTimeoutMs) {
|
|
18515
|
+
await this.deleteSession(sessionId);
|
|
18516
|
+
cleanedCount++;
|
|
18517
|
+
}
|
|
18518
|
+
}
|
|
18519
|
+
if (cleanedCount > 0) {
|
|
18520
|
+
log(`[SessionPool] Cleaned up ${cleanedCount} stale sessions`);
|
|
18521
|
+
}
|
|
18522
|
+
return cleanedCount;
|
|
18523
|
+
}
|
|
18524
|
+
/**
|
|
18525
|
+
* Shutdown the pool.
|
|
18526
|
+
*/
|
|
18527
|
+
async shutdown() {
|
|
18528
|
+
log("[SessionPool] Shutting down...");
|
|
18529
|
+
if (this.healthCheckInterval) {
|
|
18530
|
+
clearInterval(this.healthCheckInterval);
|
|
18531
|
+
this.healthCheckInterval = null;
|
|
18532
|
+
}
|
|
18533
|
+
const deletePromises = Array.from(this.sessionsById.keys()).map(
|
|
18534
|
+
(id) => this.deleteSession(id).catch(() => {
|
|
18535
|
+
})
|
|
18536
|
+
);
|
|
18537
|
+
await Promise.all(deletePromises);
|
|
18538
|
+
this.pool.clear();
|
|
18539
|
+
this.sessionsById.clear();
|
|
18540
|
+
log("[SessionPool] Shutdown complete");
|
|
18541
|
+
}
|
|
18542
|
+
// =========================================================================
|
|
18543
|
+
// Private Methods
|
|
18544
|
+
// =========================================================================
|
|
18545
|
+
getPoolKey(agentName) {
|
|
18546
|
+
return agentName;
|
|
18547
|
+
}
|
|
18548
|
+
async createSession(agentName, parentSessionID, description) {
|
|
18549
|
+
log(`[SessionPool] Creating new session for ${agentName}`);
|
|
18550
|
+
const result = await Promise.race([
|
|
18551
|
+
this.client.session.create({
|
|
18552
|
+
body: {
|
|
18553
|
+
parentID: parentSessionID,
|
|
18554
|
+
title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${description}`
|
|
18555
|
+
},
|
|
18556
|
+
query: { directory: this.directory }
|
|
18557
|
+
}),
|
|
18558
|
+
new Promise(
|
|
18559
|
+
(_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 60s")), 6e4)
|
|
18560
|
+
)
|
|
18561
|
+
]);
|
|
18562
|
+
if (result.error || !result.data?.id) {
|
|
18563
|
+
throw new Error(`Session creation failed: ${result.error || "No ID"}`);
|
|
18564
|
+
}
|
|
18565
|
+
const session = {
|
|
18566
|
+
id: result.data.id,
|
|
18567
|
+
agentName,
|
|
18568
|
+
projectDirectory: this.directory,
|
|
18569
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
18570
|
+
lastUsedAt: /* @__PURE__ */ new Date(),
|
|
18571
|
+
reuseCount: 0,
|
|
18572
|
+
inUse: true
|
|
18573
|
+
};
|
|
18574
|
+
const poolKey = this.getPoolKey(agentName);
|
|
18575
|
+
const agentPool = this.pool.get(poolKey) || [];
|
|
18576
|
+
agentPool.push(session);
|
|
18577
|
+
this.pool.set(poolKey, agentPool);
|
|
18578
|
+
this.sessionsById.set(session.id, session);
|
|
18579
|
+
return session;
|
|
18580
|
+
}
|
|
18581
|
+
async deleteSession(sessionId) {
|
|
18582
|
+
const session = this.sessionsById.get(sessionId);
|
|
18583
|
+
if (!session) return;
|
|
18584
|
+
this.sessionsById.delete(sessionId);
|
|
18585
|
+
const poolKey = this.getPoolKey(session.agentName);
|
|
18586
|
+
const agentPool = this.pool.get(poolKey);
|
|
18587
|
+
if (agentPool) {
|
|
18588
|
+
const idx = agentPool.findIndex((s) => s.id === sessionId);
|
|
18589
|
+
if (idx !== -1) {
|
|
18590
|
+
agentPool.splice(idx, 1);
|
|
18591
|
+
}
|
|
18592
|
+
if (agentPool.length === 0) {
|
|
18593
|
+
this.pool.delete(poolKey);
|
|
18594
|
+
}
|
|
18595
|
+
}
|
|
18596
|
+
try {
|
|
18597
|
+
await this.client.session.delete({ path: { id: sessionId } });
|
|
18598
|
+
} catch {
|
|
18599
|
+
}
|
|
18600
|
+
}
|
|
18601
|
+
startHealthCheck() {
|
|
18602
|
+
this.healthCheckInterval = setInterval(() => {
|
|
18603
|
+
this.cleanup().catch(() => {
|
|
18604
|
+
});
|
|
18605
|
+
}, this.config.healthCheckIntervalMs);
|
|
18606
|
+
this.healthCheckInterval.unref?.();
|
|
18607
|
+
}
|
|
18608
|
+
};
|
|
18609
|
+
var sessionPool = {
|
|
18610
|
+
getInstance: SessionPool.getInstance.bind(SessionPool)
|
|
18611
|
+
};
|
|
18612
|
+
|
|
18613
|
+
// src/core/progress/terminal-monitor.ts
|
|
18614
|
+
var TerminalMonitor = class _TerminalMonitor {
|
|
18615
|
+
static instance;
|
|
18616
|
+
lastLineCount = 0;
|
|
18617
|
+
active = false;
|
|
18618
|
+
constructor() {
|
|
18619
|
+
stateBroadcaster.subscribe(this.render.bind(this));
|
|
18620
|
+
}
|
|
18621
|
+
static getInstance() {
|
|
18622
|
+
if (!_TerminalMonitor.instance) {
|
|
18623
|
+
_TerminalMonitor.instance = new _TerminalMonitor();
|
|
18624
|
+
}
|
|
18625
|
+
return _TerminalMonitor.instance;
|
|
18626
|
+
}
|
|
18627
|
+
start() {
|
|
18628
|
+
this.active = true;
|
|
18629
|
+
}
|
|
18630
|
+
stop() {
|
|
18631
|
+
this.active = false;
|
|
18632
|
+
process.stdout.write("\n");
|
|
18633
|
+
}
|
|
18634
|
+
render(state2) {
|
|
18635
|
+
if (!this.active) return;
|
|
18636
|
+
const { COLORS, BAR_WIDTH, LABELS } = TUI_CONSTANTS;
|
|
18637
|
+
if (this.lastLineCount > 0) {
|
|
18638
|
+
process.stdout.write(`\x1B[${this.lastLineCount}A`);
|
|
18639
|
+
}
|
|
18640
|
+
let output = "";
|
|
18641
|
+
const filledWidth = Math.round(state2.progress.percentage / 100 * BAR_WIDTH);
|
|
18642
|
+
const bar = "\u2588".repeat(filledWidth) + "\u2591".repeat(BAR_WIDTH - filledWidth);
|
|
18643
|
+
output += `${COLORS.BOLD}${COLORS.PROGRESS}${LABELS.PROGRESS_TITLE}: [${bar}] ${state2.progress.percentage}% ${COLORS.RESET}
|
|
18644
|
+
`;
|
|
18645
|
+
output += `Tasks: ${state2.progress.completedTasks}/${state2.progress.totalTasks} completed
|
|
18646
|
+
|
|
18647
|
+
`;
|
|
18648
|
+
if (state2.activeAgents.length > 0) {
|
|
18649
|
+
output += `${COLORS.BOLD}${LABELS.AGENT_TITLE}:${COLORS.RESET}
|
|
18650
|
+
`;
|
|
18651
|
+
state2.activeAgents.forEach((agent) => {
|
|
18652
|
+
const desc = agent.currentTask && agent.currentTask.length > 50 ? agent.currentTask.substring(0, 47) + "..." : agent.currentTask || LABELS.IDLE;
|
|
18653
|
+
output += ` ${COLORS.AGENT}\u25CF${COLORS.RESET} ${COLORS.BOLD}${agent.type}${COLORS.RESET}: ${desc}
|
|
18654
|
+
`;
|
|
18655
|
+
});
|
|
18656
|
+
output += "\n";
|
|
18657
|
+
} else {
|
|
18658
|
+
output += `${COLORS.DIM}${LABELS.WAITING}${COLORS.RESET}
|
|
18659
|
+
|
|
18660
|
+
`;
|
|
18661
|
+
}
|
|
18662
|
+
const lines = output.split("\n");
|
|
18663
|
+
this.lastLineCount = lines.length - 1;
|
|
18664
|
+
process.stdout.write(output);
|
|
18665
|
+
}
|
|
18666
|
+
};
|
|
18667
|
+
|
|
18668
|
+
// src/core/loop/todo-manager.ts
|
|
18669
|
+
import { readFileSync, writeFileSync, existsSync as existsSync3 } from "node:fs";
|
|
18670
|
+
import { join as join5 } from "node:path";
|
|
18671
|
+
var TodoManager = class _TodoManager {
|
|
18672
|
+
static instance;
|
|
18673
|
+
directory = "";
|
|
18674
|
+
constructor() {
|
|
18675
|
+
}
|
|
18676
|
+
static getInstance() {
|
|
18677
|
+
if (!_TodoManager.instance) {
|
|
18678
|
+
_TodoManager.instance = new _TodoManager();
|
|
18679
|
+
}
|
|
18680
|
+
return _TodoManager.instance;
|
|
18681
|
+
}
|
|
18682
|
+
setDirectory(dir) {
|
|
18683
|
+
this.directory = dir;
|
|
18684
|
+
}
|
|
18685
|
+
/**
|
|
18686
|
+
* Update a specific TODO item by its text content
|
|
18687
|
+
*/
|
|
18688
|
+
updateItem(searchText, newStatus) {
|
|
18689
|
+
const todoPath = join5(this.directory, PATHS.TODO);
|
|
18690
|
+
if (!existsSync3(todoPath)) return false;
|
|
18691
|
+
try {
|
|
18692
|
+
const content = readFileSync(todoPath, "utf-8");
|
|
18693
|
+
const lines = content.split("\n");
|
|
18694
|
+
const statusMap = {
|
|
18695
|
+
[TODO_CONSTANTS.STATUS.PENDING]: TODO_CONSTANTS.MARKERS.PENDING,
|
|
18696
|
+
[TODO_CONSTANTS.STATUS.COMPLETED]: TODO_CONSTANTS.MARKERS.COMPLETED,
|
|
18697
|
+
[TODO_CONSTANTS.STATUS.PROGRESS]: TODO_CONSTANTS.MARKERS.PROGRESS,
|
|
18698
|
+
[TODO_CONSTANTS.STATUS.FAILED]: TODO_CONSTANTS.MARKERS.FAILED
|
|
18699
|
+
};
|
|
18700
|
+
const marker = statusMap[newStatus] || TODO_CONSTANTS.MARKERS.PENDING;
|
|
18701
|
+
let updated = false;
|
|
18702
|
+
const newLines = lines.map((line) => {
|
|
18703
|
+
if (line.includes(searchText) && (line.includes(TODO_CONSTANTS.MARKERS.PENDING) || line.includes(TODO_CONSTANTS.MARKERS.PROGRESS) || line.includes(TODO_CONSTANTS.MARKERS.COMPLETED) || line.includes(TODO_CONSTANTS.MARKERS.FAILED))) {
|
|
18704
|
+
updated = true;
|
|
18705
|
+
return line.replace(/\[[ x\/\-]\]/, marker);
|
|
18706
|
+
}
|
|
18707
|
+
return line;
|
|
18708
|
+
});
|
|
18709
|
+
if (updated) {
|
|
18710
|
+
writeFileSync(todoPath, newLines.join("\n"), "utf-8");
|
|
18711
|
+
log(`[TodoManager] Updated item: "${searchText}" -> ${newStatus}`);
|
|
18712
|
+
return true;
|
|
18713
|
+
}
|
|
18714
|
+
return false;
|
|
18715
|
+
} catch (error45) {
|
|
18716
|
+
log(`[TodoManager] Error updating TODO: ${error45}`);
|
|
18717
|
+
return false;
|
|
18718
|
+
}
|
|
18719
|
+
}
|
|
18720
|
+
/**
|
|
18721
|
+
* Add a new sub-task under a parent task
|
|
18722
|
+
*/
|
|
18723
|
+
addSubTask(parentText, subTaskText) {
|
|
18724
|
+
const todoPath = join5(this.directory, PATHS.TODO);
|
|
18725
|
+
if (!existsSync3(todoPath)) return false;
|
|
18726
|
+
try {
|
|
18727
|
+
const content = readFileSync(todoPath, "utf-8");
|
|
18728
|
+
const lines = content.split("\n");
|
|
18729
|
+
let parentIndex = -1;
|
|
18730
|
+
let parentIndent = "";
|
|
18731
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18732
|
+
if (lines[i].includes(parentText)) {
|
|
18733
|
+
parentIndex = i;
|
|
18734
|
+
const match = lines[i].match(/^(\s*)/);
|
|
18735
|
+
parentIndent = match ? match[1] : "";
|
|
18736
|
+
break;
|
|
18737
|
+
}
|
|
18738
|
+
}
|
|
18739
|
+
if (parentIndex !== -1) {
|
|
18740
|
+
const subTaskIndent = parentIndent + " ";
|
|
18741
|
+
const newLine = `${subTaskIndent}- ${TODO_CONSTANTS.MARKERS.PENDING} ${subTaskText}`;
|
|
18742
|
+
lines.splice(parentIndex + 1, 0, newLine);
|
|
18743
|
+
writeFileSync(todoPath, lines.join("\n"), "utf-8");
|
|
18744
|
+
log(`[TodoManager] Added sub-task: "${subTaskText}" under "${parentText}"`);
|
|
18745
|
+
return true;
|
|
18746
|
+
}
|
|
18747
|
+
return false;
|
|
18748
|
+
} catch (error45) {
|
|
18749
|
+
log(`[TodoManager] Error adding sub-task: ${error45}`);
|
|
18750
|
+
return false;
|
|
18751
|
+
}
|
|
18752
|
+
}
|
|
18753
|
+
};
|
|
18754
|
+
|
|
17858
18755
|
// src/core/agents/manager.ts
|
|
17859
18756
|
var ParallelAgentManager = class _ParallelAgentManager {
|
|
17860
18757
|
static _instance;
|
|
@@ -17862,6 +18759,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17862
18759
|
client;
|
|
17863
18760
|
directory;
|
|
17864
18761
|
concurrency = new ConcurrencyController();
|
|
18762
|
+
sessionPool;
|
|
17865
18763
|
// Composed components
|
|
17866
18764
|
launcher;
|
|
17867
18765
|
resumer;
|
|
@@ -17871,7 +18769,13 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17871
18769
|
constructor(client, directory) {
|
|
17872
18770
|
this.client = client;
|
|
17873
18771
|
this.directory = directory;
|
|
17874
|
-
|
|
18772
|
+
const memory = MemoryManager.getInstance();
|
|
18773
|
+
memory.add("system" /* SYSTEM */, CORE_PHILOSOPHY, 1);
|
|
18774
|
+
memory.add("project" /* PROJECT */, `Working directory: ${directory}`, 0.9);
|
|
18775
|
+
AgentRegistry.getInstance().setDirectory(directory);
|
|
18776
|
+
TodoManager.getInstance().setDirectory(directory);
|
|
18777
|
+
this.sessionPool = SessionPool.getInstance(client, directory);
|
|
18778
|
+
this.cleaner = new TaskCleaner(client, this.store, this.concurrency, this.sessionPool);
|
|
17875
18779
|
this.poller = new TaskPoller(
|
|
17876
18780
|
client,
|
|
17877
18781
|
this.store,
|
|
@@ -17886,6 +18790,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17886
18790
|
directory,
|
|
17887
18791
|
this.store,
|
|
17888
18792
|
this.concurrency,
|
|
18793
|
+
this.sessionPool,
|
|
17889
18794
|
(taskId, error45) => this.handleTaskError(taskId, error45),
|
|
17890
18795
|
() => this.poller.start()
|
|
17891
18796
|
);
|
|
@@ -17906,6 +18811,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17906
18811
|
(sessionID) => this.poller.validateSessionHasOutput(sessionID),
|
|
17907
18812
|
(task) => this.handleTaskComplete(task)
|
|
17908
18813
|
);
|
|
18814
|
+
progressNotifier.setManager(this);
|
|
18815
|
+
TerminalMonitor.getInstance().start();
|
|
17909
18816
|
this.recoverActiveTasks().catch((err) => {
|
|
17910
18817
|
log("Recovery error:", err);
|
|
17911
18818
|
});
|
|
@@ -17924,7 +18831,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17924
18831
|
// ========================================================================
|
|
17925
18832
|
async launch(inputs) {
|
|
17926
18833
|
this.cleaner.pruneExpiredTasks();
|
|
17927
|
-
|
|
18834
|
+
const result = await this.launcher.launch(inputs);
|
|
18835
|
+
progressNotifier.update();
|
|
18836
|
+
return result;
|
|
17928
18837
|
}
|
|
17929
18838
|
async resume(input) {
|
|
17930
18839
|
return this.resumer.resume(input);
|
|
@@ -17958,6 +18867,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17958
18867
|
this.cleaner.scheduleCleanup(taskId);
|
|
17959
18868
|
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
17960
18869
|
});
|
|
18870
|
+
progressNotifier.update();
|
|
17961
18871
|
log(`Cancelled ${taskId}`);
|
|
17962
18872
|
return true;
|
|
17963
18873
|
}
|
|
@@ -17992,6 +18902,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17992
18902
|
cleanup() {
|
|
17993
18903
|
this.poller.stop();
|
|
17994
18904
|
this.store.clear();
|
|
18905
|
+
MemoryManager.getInstance().clearTaskMemory();
|
|
18906
|
+
TerminalMonitor.getInstance().stop();
|
|
17995
18907
|
Promise.resolve().then(() => (init_store(), store_exports)).then((store) => store.clearAll()).catch(() => {
|
|
17996
18908
|
});
|
|
17997
18909
|
}
|
|
@@ -18021,6 +18933,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
18021
18933
|
this.store.untrackPending(task.parentSessionID, taskId);
|
|
18022
18934
|
this.cleaner.notifyParentIfAllComplete(task.parentSessionID);
|
|
18023
18935
|
this.cleaner.scheduleCleanup(taskId);
|
|
18936
|
+
progressNotifier.update();
|
|
18024
18937
|
taskWAL.log(WAL_ACTIONS.UPDATE, task).catch(() => {
|
|
18025
18938
|
});
|
|
18026
18939
|
}
|
|
@@ -18047,6 +18960,7 @@ This task ensures the completeness of the unit before global integration.`,
|
|
|
18047
18960
|
log(`[MSVP] Failed to trigger review for ${task.id}:`, error45);
|
|
18048
18961
|
}
|
|
18049
18962
|
}
|
|
18963
|
+
progressNotifier.update();
|
|
18050
18964
|
}
|
|
18051
18965
|
async recoverActiveTasks() {
|
|
18052
18966
|
const tasks = await taskWAL.readAll();
|
|
@@ -18085,7 +18999,13 @@ This task ensures the completeness of the unit before global integration.`,
|
|
|
18085
18999
|
}
|
|
18086
19000
|
};
|
|
18087
19001
|
var parallelAgentManager = {
|
|
18088
|
-
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
|
|
19002
|
+
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager),
|
|
19003
|
+
cleanup: () => {
|
|
19004
|
+
try {
|
|
19005
|
+
ParallelAgentManager.getInstance().cleanup();
|
|
19006
|
+
} catch {
|
|
19007
|
+
}
|
|
19008
|
+
}
|
|
18089
19009
|
};
|
|
18090
19010
|
|
|
18091
19011
|
// src/tools/parallel/delegate-task.ts
|
|
@@ -18221,6 +19141,7 @@ ${PROMPT_TAGS.RESUME.close}
|
|
|
18221
19141
|
${PROMPT_TAGS.SAFETY.open}
|
|
18222
19142
|
- Max 10 tasks per agent type (configurable)
|
|
18223
19143
|
- Auto-timeout: 60 minutes
|
|
19144
|
+
- Use \`${TOOL_NAMES.LIST_AGENTS}\` to see all available agents (including custom ones).
|
|
18224
19145
|
${PROMPT_TAGS.SAFETY.close}`,
|
|
18225
19146
|
args: {
|
|
18226
19147
|
[PARALLEL_PARAMS.AGENT]: tool.schema.string().describe("Agent name"),
|
|
@@ -18352,7 +19273,7 @@ Session: \`${task.sessionID}\` (save for resume)`;
|
|
|
18352
19273
|
if (pollResult.timedOut) {
|
|
18353
19274
|
log(`${PARALLEL_LOG.DELEGATE_TASK} Sync: timed out`, pollResult);
|
|
18354
19275
|
return `${OUTPUT_LABEL.TIMEOUT} after ${Math.floor(pollResult.elapsedMs / 1e3)}s (${pollResult.pollCount} polls)
|
|
18355
|
-
Session: \`${sessionID}\` - Use
|
|
19276
|
+
Session: \`${sessionID}\` - Use ${TOOL_NAMES.GET_TASK_RESULT} or resume later.`;
|
|
18356
19277
|
}
|
|
18357
19278
|
const text = await extractSessionResult(session, sessionID);
|
|
18358
19279
|
log(`${PARALLEL_LOG.DELEGATE_TASK} Sync: completed`, { sessionID, elapsedMs: pollResult.elapsedMs });
|
|
@@ -18383,59 +19304,201 @@ var createGetTaskResultTool = (manager) => tool({
|
|
|
18383
19304
|
if (task.status === STATUS_LABEL.ERROR || task.status === STATUS_LABEL.TIMEOUT) {
|
|
18384
19305
|
return `[${task.status.toUpperCase()}] ${task.error}`;
|
|
18385
19306
|
}
|
|
18386
|
-
return `${OUTPUT_LABEL.DONE} Completed in ${duration3}
|
|
19307
|
+
return `${OUTPUT_LABEL.DONE} Completed in ${duration3}
|
|
19308
|
+
|
|
19309
|
+
${result || "(No output)"}`;
|
|
19310
|
+
}
|
|
19311
|
+
});
|
|
19312
|
+
|
|
19313
|
+
// src/tools/parallel/list-tasks.ts
|
|
19314
|
+
var createListTasksTool = (manager) => tool({
|
|
19315
|
+
description: `List all background tasks.`,
|
|
19316
|
+
args: {
|
|
19317
|
+
[PARALLEL_PARAMS.STATUS]: tool.schema.string().optional().describe("Filter: all, running, completed, error")
|
|
19318
|
+
},
|
|
19319
|
+
async execute(args) {
|
|
19320
|
+
const status = args[PARALLEL_PARAMS.STATUS] || STATUS_LABEL.ALL;
|
|
19321
|
+
let tasks;
|
|
19322
|
+
switch (status) {
|
|
19323
|
+
case STATUS_LABEL.RUNNING:
|
|
19324
|
+
tasks = manager.getRunningTasks();
|
|
19325
|
+
break;
|
|
19326
|
+
case STATUS_LABEL.COMPLETED:
|
|
19327
|
+
tasks = manager.getAllTasks().filter((t) => t.status === TASK_STATUS.COMPLETED);
|
|
19328
|
+
break;
|
|
19329
|
+
case STATUS_LABEL.ERROR:
|
|
19330
|
+
tasks = manager.getAllTasks().filter((t) => t.status === TASK_STATUS.ERROR || t.status === TASK_STATUS.TIMEOUT);
|
|
19331
|
+
break;
|
|
19332
|
+
default:
|
|
19333
|
+
tasks = manager.getAllTasks();
|
|
19334
|
+
}
|
|
19335
|
+
if (tasks.length === 0) return `No tasks found.`;
|
|
19336
|
+
const rows = tasks.map((t) => {
|
|
19337
|
+
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
|
|
19338
|
+
return `| \`${t.id}\` | [${t.status.toUpperCase()}] | ${t.agent} | ${elapsed}s |`;
|
|
19339
|
+
}).join("\n");
|
|
19340
|
+
return `**Tasks List**
|
|
19341
|
+
|
|
19342
|
+
| ID | ${LOOP_LABELS.STATUS_TITLE || "Status"} | ${PARALLEL_PARAMS.AGENT.toUpperCase()} | Time |
|
|
19343
|
+
|----|--------|-------|------|
|
|
19344
|
+
${rows}`;
|
|
19345
|
+
}
|
|
19346
|
+
});
|
|
19347
|
+
|
|
19348
|
+
// src/tools/parallel/cancel-task.ts
|
|
19349
|
+
var createCancelTaskTool = (manager) => tool({
|
|
19350
|
+
description: `Cancel a running task.`,
|
|
19351
|
+
args: {
|
|
19352
|
+
taskId: tool.schema.string().describe("Task ID to cancel")
|
|
19353
|
+
},
|
|
19354
|
+
async execute(args) {
|
|
19355
|
+
const cancelled = await manager.cancelTask(args.taskId);
|
|
19356
|
+
if (cancelled) return `${OUTPUT_LABEL.CANCELLED} task: \`${args.taskId}\``;
|
|
19357
|
+
const task = manager.getTask(args.taskId);
|
|
19358
|
+
if (task) return `${OUTPUT_LABEL.WARNING} Cannot cancel: Task is ${task.status}`;
|
|
19359
|
+
return `${OUTPUT_LABEL.ERROR} Not found: \`${args.taskId}\``;
|
|
19360
|
+
}
|
|
19361
|
+
});
|
|
19362
|
+
|
|
19363
|
+
// src/tools/parallel/list-agents.ts
|
|
19364
|
+
var createListAgentsTool = () => tool({
|
|
19365
|
+
description: "List all available agents for delegation, including built-in and custom templates.",
|
|
19366
|
+
args: {},
|
|
19367
|
+
async execute() {
|
|
19368
|
+
const registry2 = AgentRegistry.getInstance();
|
|
19369
|
+
const agentNames = registry2.listAgents();
|
|
19370
|
+
if (agentNames.length === 0) {
|
|
19371
|
+
return `${OUTPUT_LABEL.INFO} No agents registered.`;
|
|
19372
|
+
}
|
|
19373
|
+
let output = `${OUTPUT_LABEL.INFO} Available Agents:
|
|
19374
|
+
|
|
19375
|
+
`;
|
|
19376
|
+
for (const name of agentNames) {
|
|
19377
|
+
const def = registry2.getAgent(name);
|
|
19378
|
+
if (def) {
|
|
19379
|
+
output += `- **${name}**: ${def.description}
|
|
19380
|
+
`;
|
|
19381
|
+
}
|
|
19382
|
+
}
|
|
19383
|
+
return output;
|
|
19384
|
+
}
|
|
19385
|
+
});
|
|
19386
|
+
|
|
19387
|
+
// src/core/metrics/collector.ts
|
|
19388
|
+
var MetricsCollector = class _MetricsCollector {
|
|
19389
|
+
static instance;
|
|
19390
|
+
agentLatencies = /* @__PURE__ */ new Map();
|
|
19391
|
+
toolLatencies = /* @__PURE__ */ new Map();
|
|
19392
|
+
tokenUsage = 0;
|
|
19393
|
+
lineCount = 0;
|
|
19394
|
+
tasks = [];
|
|
19395
|
+
constructor() {
|
|
19396
|
+
}
|
|
19397
|
+
static getInstance() {
|
|
19398
|
+
if (!_MetricsCollector.instance) {
|
|
19399
|
+
_MetricsCollector.instance = new _MetricsCollector();
|
|
19400
|
+
}
|
|
19401
|
+
return _MetricsCollector.instance;
|
|
19402
|
+
}
|
|
19403
|
+
recordAgentExecution(agent, duration3) {
|
|
19404
|
+
const latencies = this.agentLatencies.get(agent) || [];
|
|
19405
|
+
latencies.push(duration3);
|
|
19406
|
+
this.agentLatencies.set(agent, latencies);
|
|
19407
|
+
}
|
|
19408
|
+
recordToolExecution(tool2, duration3) {
|
|
19409
|
+
const latencies = this.toolLatencies.get(tool2) || [];
|
|
19410
|
+
latencies.push(duration3);
|
|
19411
|
+
this.toolLatencies.set(tool2, latencies);
|
|
19412
|
+
}
|
|
19413
|
+
recordTokenUsage(tokens) {
|
|
19414
|
+
this.tokenUsage += tokens;
|
|
19415
|
+
}
|
|
19416
|
+
recordTaskResult(id, success2) {
|
|
19417
|
+
this.tasks.push({ id, success: success2 });
|
|
19418
|
+
}
|
|
19419
|
+
recordLinesProduced(lines) {
|
|
19420
|
+
this.lineCount += lines;
|
|
19421
|
+
}
|
|
19422
|
+
getStats() {
|
|
19423
|
+
const avgAgentLatency = {};
|
|
19424
|
+
for (const [agent, lats] of this.agentLatencies.entries()) {
|
|
19425
|
+
avgAgentLatency[agent] = Math.round(lats.reduce((a, b) => a + b, 0) / lats.length);
|
|
19426
|
+
}
|
|
19427
|
+
const avgToolLatency = {};
|
|
19428
|
+
for (const [tool2, lats] of this.toolLatencies.entries()) {
|
|
19429
|
+
avgToolLatency[tool2] = Math.round(lats.reduce((a, b) => a + b, 0) / lats.length);
|
|
19430
|
+
}
|
|
19431
|
+
const successfulTasks = this.tasks.filter((t) => t.success).length;
|
|
19432
|
+
return {
|
|
19433
|
+
avgAgentLatency,
|
|
19434
|
+
avgToolLatency,
|
|
19435
|
+
tokenUsage: this.tokenUsage,
|
|
19436
|
+
efficiency: this.lineCount > 0 ? this.tokenUsage / this.lineCount : 0,
|
|
19437
|
+
totalTasks: this.tasks.length,
|
|
19438
|
+
successRate: this.tasks.length > 0 ? successfulTasks / this.tasks.length : 0
|
|
19439
|
+
};
|
|
19440
|
+
}
|
|
19441
|
+
};
|
|
19442
|
+
|
|
19443
|
+
// src/tools/parallel/show-metrics.ts
|
|
19444
|
+
var createShowMetricsTool = () => tool({
|
|
19445
|
+
description: "Display a performance dashboard for the current mission, showing tool/agent latency and token usage.",
|
|
19446
|
+
args: {},
|
|
19447
|
+
async execute() {
|
|
19448
|
+
const stats2 = MetricsCollector.getInstance().getStats();
|
|
19449
|
+
let output = `${OUTPUT_LABEL.INFO} **Performance Dashboard**
|
|
19450
|
+
|
|
19451
|
+
`;
|
|
19452
|
+
output += `### \u23F1\uFE0F Latency (Average)
|
|
19453
|
+
|
|
19454
|
+
`;
|
|
19455
|
+
output += `**Tools:**
|
|
19456
|
+
`;
|
|
19457
|
+
for (const [tool2, lat] of Object.entries(stats2.avgToolLatency)) {
|
|
19458
|
+
output += `- \`${tool2}\`: ${lat}ms
|
|
19459
|
+
`;
|
|
19460
|
+
}
|
|
19461
|
+
output += `
|
|
19462
|
+
**Agents:**
|
|
19463
|
+
`;
|
|
19464
|
+
for (const [agent, lat] of Object.entries(stats2.avgAgentLatency)) {
|
|
19465
|
+
output += `- \`${agent}\`: ${lat}ms
|
|
19466
|
+
`;
|
|
19467
|
+
}
|
|
19468
|
+
output += `
|
|
19469
|
+
### \u{1FA99} Resource Usage
|
|
18387
19470
|
|
|
18388
|
-
|
|
19471
|
+
`;
|
|
19472
|
+
output += `- **Total Tokens (Est.)**: ${Math.round(stats2.tokenUsage).toLocaleString()}
|
|
19473
|
+
`;
|
|
19474
|
+
output += `- **Mission Success Rate**: ${Math.round(stats2.successRate * 100)}%
|
|
19475
|
+
`;
|
|
19476
|
+
output += `- **Total Sub-tasks**: ${stats2.totalTasks}
|
|
19477
|
+
`;
|
|
19478
|
+
return output;
|
|
18389
19479
|
}
|
|
18390
19480
|
});
|
|
18391
19481
|
|
|
18392
|
-
// src/tools/parallel/
|
|
18393
|
-
var
|
|
18394
|
-
description:
|
|
19482
|
+
// src/tools/parallel/update-todo.ts
|
|
19483
|
+
var createUpdateTodoTool = () => tool({
|
|
19484
|
+
description: "Update the status of a task in .opencode/todo.md or add a sub-task. Use this for incremental updates instead of rewriting the whole file.",
|
|
18395
19485
|
args: {
|
|
18396
|
-
|
|
19486
|
+
action: tool.schema.enum(["update", "add"]).describe("Action to perform"),
|
|
19487
|
+
task: tool.schema.string().describe("Text content of the task to update or the parent task to add under"),
|
|
19488
|
+
status: tool.schema.enum(Object.values(TODO_CONSTANTS.STATUS)).optional().describe("New status (for 'update' action)"),
|
|
19489
|
+
subtask: tool.schema.string().optional().describe("Description of the new sub-task (for 'add' action)")
|
|
18397
19490
|
},
|
|
18398
19491
|
async execute(args) {
|
|
18399
|
-
const
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
case STATUS_LABEL.ERROR:
|
|
18409
|
-
tasks = manager.getAllTasks().filter((t) => t.status === TASK_STATUS.ERROR || t.status === TASK_STATUS.TIMEOUT);
|
|
18410
|
-
break;
|
|
18411
|
-
default:
|
|
18412
|
-
tasks = manager.getAllTasks();
|
|
19492
|
+
const manager = TodoManager.getInstance();
|
|
19493
|
+
if (args.action === "update") {
|
|
19494
|
+
if (!args.status) return `${OUTPUT_LABEL.ERROR} 'status' is required for update action.`;
|
|
19495
|
+
const success2 = manager.updateItem(args.task, args.status);
|
|
19496
|
+
return success2 ? `${OUTPUT_LABEL.DONE} Updated task status: "${args.task}" -> ${args.status}` : `${OUTPUT_LABEL.ERROR} Task not found: "${args.task}"`;
|
|
19497
|
+
} else {
|
|
19498
|
+
if (!args.subtask) return `${OUTPUT_LABEL.ERROR} 'subtask' is required for add action.`;
|
|
19499
|
+
const success2 = manager.addSubTask(args.task, args.subtask);
|
|
19500
|
+
return success2 ? `${OUTPUT_LABEL.DONE} Added sub-task: "${args.subtask}" under "${args.task}"` : `${OUTPUT_LABEL.ERROR} Parent task not found: "${args.task}"`;
|
|
18413
19501
|
}
|
|
18414
|
-
if (tasks.length === 0) return `No tasks found.`;
|
|
18415
|
-
const rows = tasks.map((t) => {
|
|
18416
|
-
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
|
|
18417
|
-
return `| \`${t.id}\` | [${t.status.toUpperCase()}] | ${t.agent} | ${elapsed}s |`;
|
|
18418
|
-
}).join("\n");
|
|
18419
|
-
return `**Tasks List**
|
|
18420
|
-
|
|
18421
|
-
| ID | ${LOOP_LABELS.STATUS_TITLE || "Status"} | ${PARALLEL_PARAMS.AGENT.toUpperCase()} | Time |
|
|
18422
|
-
|----|--------|-------|------|
|
|
18423
|
-
${rows}`;
|
|
18424
|
-
}
|
|
18425
|
-
});
|
|
18426
|
-
|
|
18427
|
-
// src/tools/parallel/cancel-task.ts
|
|
18428
|
-
var createCancelTaskTool = (manager) => tool({
|
|
18429
|
-
description: `Cancel a running task.`,
|
|
18430
|
-
args: {
|
|
18431
|
-
taskId: tool.schema.string().describe("Task ID to cancel")
|
|
18432
|
-
},
|
|
18433
|
-
async execute(args) {
|
|
18434
|
-
const cancelled = await manager.cancelTask(args.taskId);
|
|
18435
|
-
if (cancelled) return `${OUTPUT_LABEL.CANCELLED} task: \`${args.taskId}\``;
|
|
18436
|
-
const task = manager.getTask(args.taskId);
|
|
18437
|
-
if (task) return `${OUTPUT_LABEL.WARNING} Cannot cancel: Task is ${task.status}`;
|
|
18438
|
-
return `${OUTPUT_LABEL.ERROR} Not found: \`${args.taskId}\``;
|
|
18439
19502
|
}
|
|
18440
19503
|
});
|
|
18441
19504
|
|
|
@@ -18445,7 +19508,10 @@ function createAsyncAgentTools(manager, client) {
|
|
|
18445
19508
|
[TOOL_NAMES.DELEGATE_TASK]: createDelegateTaskTool(manager, client),
|
|
18446
19509
|
[TOOL_NAMES.GET_TASK_RESULT]: createGetTaskResultTool(manager),
|
|
18447
19510
|
[TOOL_NAMES.LIST_TASKS]: createListTasksTool(manager),
|
|
18448
|
-
[TOOL_NAMES.CANCEL_TASK]: createCancelTaskTool(manager)
|
|
19511
|
+
[TOOL_NAMES.CANCEL_TASK]: createCancelTaskTool(manager),
|
|
19512
|
+
[TOOL_NAMES.LIST_AGENTS]: createListAgentsTool(),
|
|
19513
|
+
[TOOL_NAMES.SHOW_METRICS]: createShowMetricsTool(),
|
|
19514
|
+
[TOOL_NAMES.UPDATE_TODO]: createUpdateTodoTool()
|
|
18449
19515
|
};
|
|
18450
19516
|
}
|
|
18451
19517
|
|
|
@@ -18499,15 +19565,15 @@ var METADATA_FILE = PATHS.DOC_METADATA;
|
|
|
18499
19565
|
var DEFAULT_TTL_MS = CACHE.DEFAULT_TTL_MS;
|
|
18500
19566
|
|
|
18501
19567
|
// src/core/cache/operations.ts
|
|
18502
|
-
import * as
|
|
18503
|
-
import * as
|
|
19568
|
+
import * as fs6 from "node:fs/promises";
|
|
19569
|
+
import * as path5 from "node:path";
|
|
18504
19570
|
|
|
18505
19571
|
// src/core/cache/utils.ts
|
|
18506
|
-
import * as
|
|
18507
|
-
import { existsSync as
|
|
19572
|
+
import * as fs5 from "node:fs/promises";
|
|
19573
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
18508
19574
|
async function ensureCacheDir() {
|
|
18509
|
-
if (!
|
|
18510
|
-
await
|
|
19575
|
+
if (!existsSync4(CACHE_DIR)) {
|
|
19576
|
+
await fs5.mkdir(CACHE_DIR, { recursive: true });
|
|
18511
19577
|
}
|
|
18512
19578
|
}
|
|
18513
19579
|
function urlToFilename(url2) {
|
|
@@ -18522,7 +19588,7 @@ function urlToFilename(url2) {
|
|
|
18522
19588
|
}
|
|
18523
19589
|
async function readMetadata() {
|
|
18524
19590
|
try {
|
|
18525
|
-
const content = await
|
|
19591
|
+
const content = await fs5.readFile(METADATA_FILE, "utf-8");
|
|
18526
19592
|
return JSON.parse(content);
|
|
18527
19593
|
} catch {
|
|
18528
19594
|
return { documents: {}, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -18531,7 +19597,7 @@ async function readMetadata() {
|
|
|
18531
19597
|
async function writeMetadata(metadata) {
|
|
18532
19598
|
await ensureCacheDir();
|
|
18533
19599
|
metadata.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
18534
|
-
await
|
|
19600
|
+
await fs5.writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
|
|
18535
19601
|
}
|
|
18536
19602
|
|
|
18537
19603
|
// src/core/cache/operations.ts
|
|
@@ -18545,8 +19611,8 @@ async function get2(url2) {
|
|
|
18545
19611
|
return null;
|
|
18546
19612
|
}
|
|
18547
19613
|
try {
|
|
18548
|
-
const filepath =
|
|
18549
|
-
const content = await
|
|
19614
|
+
const filepath = path5.join(CACHE_DIR, filename);
|
|
19615
|
+
const content = await fs6.readFile(filepath, "utf-8");
|
|
18550
19616
|
return { ...entry, content };
|
|
18551
19617
|
} catch {
|
|
18552
19618
|
return null;
|
|
@@ -18557,8 +19623,8 @@ async function getByFilename(filename) {
|
|
|
18557
19623
|
const entry = metadata.documents[filename];
|
|
18558
19624
|
if (!entry) return null;
|
|
18559
19625
|
try {
|
|
18560
|
-
const filepath =
|
|
18561
|
-
const content = await
|
|
19626
|
+
const filepath = path5.join(CACHE_DIR, filename);
|
|
19627
|
+
const content = await fs6.readFile(filepath, "utf-8");
|
|
18562
19628
|
return { ...entry, content };
|
|
18563
19629
|
} catch {
|
|
18564
19630
|
return null;
|
|
@@ -18567,7 +19633,7 @@ async function getByFilename(filename) {
|
|
|
18567
19633
|
async function set2(url2, content, title, ttlMs = DEFAULT_TTL_MS) {
|
|
18568
19634
|
await ensureCacheDir();
|
|
18569
19635
|
const filename = urlToFilename(url2);
|
|
18570
|
-
const filepath =
|
|
19636
|
+
const filepath = path5.join(CACHE_DIR, filename);
|
|
18571
19637
|
const now = /* @__PURE__ */ new Date();
|
|
18572
19638
|
const header = `# ${title}
|
|
18573
19639
|
|
|
@@ -18578,7 +19644,7 @@ async function set2(url2, content, title, ttlMs = DEFAULT_TTL_MS) {
|
|
|
18578
19644
|
|
|
18579
19645
|
`;
|
|
18580
19646
|
const fullContent = header + content;
|
|
18581
|
-
await
|
|
19647
|
+
await fs6.writeFile(filepath, fullContent);
|
|
18582
19648
|
const metadata = await readMetadata();
|
|
18583
19649
|
metadata.documents[filename] = {
|
|
18584
19650
|
url: url2,
|
|
@@ -18592,9 +19658,9 @@ async function set2(url2, content, title, ttlMs = DEFAULT_TTL_MS) {
|
|
|
18592
19658
|
}
|
|
18593
19659
|
async function remove(url2) {
|
|
18594
19660
|
const filename = urlToFilename(url2);
|
|
18595
|
-
const filepath =
|
|
19661
|
+
const filepath = path5.join(CACHE_DIR, filename);
|
|
18596
19662
|
try {
|
|
18597
|
-
await
|
|
19663
|
+
await fs6.unlink(filepath);
|
|
18598
19664
|
const metadata = await readMetadata();
|
|
18599
19665
|
delete metadata.documents[filename];
|
|
18600
19666
|
await writeMetadata(metadata);
|
|
@@ -18612,13 +19678,13 @@ async function list() {
|
|
|
18612
19678
|
expired: new Date(entry.expiresAt) < now
|
|
18613
19679
|
}));
|
|
18614
19680
|
}
|
|
18615
|
-
async function
|
|
19681
|
+
async function clear3() {
|
|
18616
19682
|
const metadata = await readMetadata();
|
|
18617
19683
|
const count = Object.keys(metadata.documents).length;
|
|
18618
19684
|
for (const filename of Object.keys(metadata.documents)) {
|
|
18619
|
-
const filepath =
|
|
19685
|
+
const filepath = path5.join(CACHE_DIR, filename);
|
|
18620
19686
|
try {
|
|
18621
|
-
await
|
|
19687
|
+
await fs6.unlink(filepath);
|
|
18622
19688
|
} catch {
|
|
18623
19689
|
}
|
|
18624
19690
|
}
|
|
@@ -19096,7 +20162,7 @@ Cached: ${doc.fetchedAt}
|
|
|
19096
20162
|
${doc.content}`;
|
|
19097
20163
|
}
|
|
19098
20164
|
case CACHE_ACTIONS.CLEAR: {
|
|
19099
|
-
const count = await
|
|
20165
|
+
const count = await clear3();
|
|
19100
20166
|
return `Cleared ${count} cached documents`;
|
|
19101
20167
|
}
|
|
19102
20168
|
case CACHE_ACTIONS.STATS: {
|
|
@@ -19276,6 +20342,60 @@ ${r.content}
|
|
|
19276
20342
|
}
|
|
19277
20343
|
});
|
|
19278
20344
|
|
|
20345
|
+
// src/tools/lsp/diagnostics-cache.ts
|
|
20346
|
+
import fs7 from "fs/promises";
|
|
20347
|
+
import path6 from "path";
|
|
20348
|
+
var DiagnosticsCache = class {
|
|
20349
|
+
cache = /* @__PURE__ */ new Map();
|
|
20350
|
+
defaultTTL = 3e4;
|
|
20351
|
+
// 30 seconds
|
|
20352
|
+
async get(directory, file2) {
|
|
20353
|
+
const key = this.getCacheKey(directory, file2);
|
|
20354
|
+
const cached2 = this.cache.get(key);
|
|
20355
|
+
if (!cached2) return null;
|
|
20356
|
+
if (Date.now() - cached2.timestamp > this.defaultTTL) {
|
|
20357
|
+
this.cache.delete(key);
|
|
20358
|
+
return null;
|
|
20359
|
+
}
|
|
20360
|
+
try {
|
|
20361
|
+
const currentMtime = await this.getFilesMtime(directory, file2);
|
|
20362
|
+
if (currentMtime > cached2.filesMtime) {
|
|
20363
|
+
this.cache.delete(key);
|
|
20364
|
+
return null;
|
|
20365
|
+
}
|
|
20366
|
+
} catch (error45) {
|
|
20367
|
+
this.cache.delete(key);
|
|
20368
|
+
return null;
|
|
20369
|
+
}
|
|
20370
|
+
log(`[DiagnosticsCache] Cache hit for ${file2 || "all files"}`);
|
|
20371
|
+
return cached2.diagnostics;
|
|
20372
|
+
}
|
|
20373
|
+
async set(directory, file2, diagnostics) {
|
|
20374
|
+
try {
|
|
20375
|
+
const key = this.getCacheKey(directory, file2);
|
|
20376
|
+
const filesMtime = await this.getFilesMtime(directory, file2);
|
|
20377
|
+
this.cache.set(key, {
|
|
20378
|
+
diagnostics,
|
|
20379
|
+
timestamp: Date.now(),
|
|
20380
|
+
filesMtime
|
|
20381
|
+
});
|
|
20382
|
+
} catch (error45) {
|
|
20383
|
+
}
|
|
20384
|
+
}
|
|
20385
|
+
getCacheKey(directory, file2) {
|
|
20386
|
+
return file2 ? `${directory}:${file2}` : directory;
|
|
20387
|
+
}
|
|
20388
|
+
async getFilesMtime(directory, file2) {
|
|
20389
|
+
if (file2 && file2 !== "*") {
|
|
20390
|
+
const stats3 = await fs7.stat(path6.join(directory, file2));
|
|
20391
|
+
return stats3.mtimeMs;
|
|
20392
|
+
}
|
|
20393
|
+
const stats2 = await fs7.stat(directory);
|
|
20394
|
+
return stats2.mtimeMs;
|
|
20395
|
+
}
|
|
20396
|
+
};
|
|
20397
|
+
var diagnosticsCache = new DiagnosticsCache();
|
|
20398
|
+
|
|
19279
20399
|
// src/tools/lsp/index.ts
|
|
19280
20400
|
var lspDiagnosticsTool = (directory) => tool({
|
|
19281
20401
|
description: `Get LSP diagnostics (errors/warnings) for files.
|
|
@@ -19293,11 +20413,17 @@ Runs TypeScript compiler and/or ESLint to find issues.
|
|
|
19293
20413
|
include_warnings: tool.schema.boolean().optional().describe("Include warnings (default: true)")
|
|
19294
20414
|
},
|
|
19295
20415
|
async execute(args) {
|
|
19296
|
-
|
|
20416
|
+
const cached2 = await diagnosticsCache.get(directory, args.file);
|
|
20417
|
+
if (cached2) return cached2;
|
|
20418
|
+
const result = await callRustTool("lsp_diagnostics", {
|
|
19297
20419
|
directory,
|
|
19298
20420
|
file: args.file,
|
|
19299
20421
|
include_warnings: args.include_warnings
|
|
19300
20422
|
});
|
|
20423
|
+
if (result) {
|
|
20424
|
+
await diagnosticsCache.set(directory, args.file, result);
|
|
20425
|
+
}
|
|
20426
|
+
return result;
|
|
19301
20427
|
}
|
|
19302
20428
|
});
|
|
19303
20429
|
|
|
@@ -19316,16 +20442,7 @@ var HOOK_ACTIONS = {
|
|
|
19316
20442
|
PROCESS: "process",
|
|
19317
20443
|
INTERCEPT: "intercept"
|
|
19318
20444
|
};
|
|
19319
|
-
var
|
|
19320
|
-
SANITY_CHECK: "SanityCheck",
|
|
19321
|
-
MISSION_LOOP: "MissionLoop",
|
|
19322
|
-
STRICT_ROLE_GUARD: "StrictRoleGuard",
|
|
19323
|
-
SECRET_SCANNER: "SecretScanner",
|
|
19324
|
-
AGENT_UI: "AgentUI",
|
|
19325
|
-
RESOURCE_CONTROL: "ResourceControl",
|
|
19326
|
-
SLASH_COMMAND: "SlashCommandDispatcher",
|
|
19327
|
-
USER_ACTIVITY: "UserActivity"
|
|
19328
|
-
};
|
|
20445
|
+
var HOOK_NAMES2 = HOOK_NAMES;
|
|
19329
20446
|
|
|
19330
20447
|
// src/hooks/registry.ts
|
|
19331
20448
|
var HookRegistry = class _HookRegistry {
|
|
@@ -19548,23 +20665,25 @@ function ensureSessionInitialized(sessions, sessionID) {
|
|
|
19548
20665
|
}
|
|
19549
20666
|
return sessions.get(sessionID);
|
|
19550
20667
|
}
|
|
19551
|
-
function
|
|
20668
|
+
function ensureGlobalState(sessionID) {
|
|
19552
20669
|
let stateSession = state.sessions.get(sessionID);
|
|
19553
20670
|
if (!stateSession) {
|
|
19554
|
-
|
|
20671
|
+
const newState = {
|
|
19555
20672
|
enabled: true,
|
|
19556
20673
|
iterations: 0,
|
|
19557
20674
|
taskRetries: /* @__PURE__ */ new Map(),
|
|
19558
20675
|
currentTask: "",
|
|
19559
20676
|
anomalyCount: 0
|
|
19560
|
-
}
|
|
19561
|
-
|
|
19562
|
-
|
|
19563
|
-
}
|
|
19564
|
-
if (stateSession) {
|
|
19565
|
-
stateSession.enabled = true;
|
|
19566
|
-
stateSession.anomalyCount = 0;
|
|
20677
|
+
};
|
|
20678
|
+
state.sessions.set(sessionID, newState);
|
|
20679
|
+
return newState;
|
|
19567
20680
|
}
|
|
20681
|
+
return stateSession;
|
|
20682
|
+
}
|
|
20683
|
+
function activateMissionState(sessionID) {
|
|
20684
|
+
const stateSession = ensureGlobalState(sessionID);
|
|
20685
|
+
stateSession.enabled = true;
|
|
20686
|
+
stateSession.anomalyCount = 0;
|
|
19568
20687
|
state.missionActive = true;
|
|
19569
20688
|
log(`[SessionManager] Mission Activated: ${sessionID}`);
|
|
19570
20689
|
}
|
|
@@ -19587,18 +20706,16 @@ function updateSessionTokens(sessions, sessionID, inputLen, outputLen) {
|
|
|
19587
20706
|
session.tokens.estimatedCost = Number(cost.toFixed(4));
|
|
19588
20707
|
}
|
|
19589
20708
|
function recordAnomaly(sessionID) {
|
|
19590
|
-
const session =
|
|
20709
|
+
const session = ensureGlobalState(sessionID);
|
|
19591
20710
|
session.anomalyCount = (session.anomalyCount || 0) + 1;
|
|
19592
20711
|
return session.anomalyCount;
|
|
19593
20712
|
}
|
|
19594
20713
|
function resetAnomaly(sessionID) {
|
|
19595
|
-
const session =
|
|
19596
|
-
|
|
19597
|
-
session.anomalyCount = 0;
|
|
19598
|
-
}
|
|
20714
|
+
const session = ensureGlobalState(sessionID);
|
|
20715
|
+
session.anomalyCount = 0;
|
|
19599
20716
|
}
|
|
19600
20717
|
function updateCurrentTask(sessionID, taskID) {
|
|
19601
|
-
const session =
|
|
20718
|
+
const session = ensureGlobalState(sessionID);
|
|
19602
20719
|
session.currentTask = taskID;
|
|
19603
20720
|
}
|
|
19604
20721
|
|
|
@@ -19673,7 +20790,7 @@ var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
|
19673
20790
|
|
|
19674
20791
|
// src/hooks/features/sanity-check.ts
|
|
19675
20792
|
var SanityCheckHook = class {
|
|
19676
|
-
name =
|
|
20793
|
+
name = HOOK_NAMES2.SANITY_CHECK;
|
|
19677
20794
|
async execute(ctx, toolOrText, input, output) {
|
|
19678
20795
|
if (output) {
|
|
19679
20796
|
if (toolOrText === TOOL_NAMES.CALL_AGENT) {
|
|
@@ -19713,20 +20830,20 @@ var SanityCheckHook = class {
|
|
|
19713
20830
|
};
|
|
19714
20831
|
|
|
19715
20832
|
// src/core/loop/mission-loop.ts
|
|
19716
|
-
import { existsSync as
|
|
19717
|
-
import { join as
|
|
20833
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync } from "node:fs";
|
|
20834
|
+
import { join as join7 } from "node:path";
|
|
19718
20835
|
var STATE_FILE = MISSION_CONTROL.STATE_FILE;
|
|
19719
20836
|
var DEFAULT_MAX_ITERATIONS = MISSION_CONTROL.DEFAULT_MAX_ITERATIONS;
|
|
19720
20837
|
function getStateFilePath(directory) {
|
|
19721
|
-
return
|
|
20838
|
+
return join7(directory, PATHS.OPENCODE, STATE_FILE);
|
|
19722
20839
|
}
|
|
19723
20840
|
function readLoopState(directory) {
|
|
19724
20841
|
const filePath = getStateFilePath(directory);
|
|
19725
|
-
if (!
|
|
20842
|
+
if (!existsSync5(filePath)) {
|
|
19726
20843
|
return null;
|
|
19727
20844
|
}
|
|
19728
20845
|
try {
|
|
19729
|
-
const content =
|
|
20846
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
19730
20847
|
return JSON.parse(content);
|
|
19731
20848
|
} catch (error45) {
|
|
19732
20849
|
log(`[${MISSION_CONTROL.LOG_SOURCE}] Failed to read state: ${error45}`);
|
|
@@ -19735,12 +20852,12 @@ function readLoopState(directory) {
|
|
|
19735
20852
|
}
|
|
19736
20853
|
function writeLoopState(directory, state2) {
|
|
19737
20854
|
const filePath = getStateFilePath(directory);
|
|
19738
|
-
const dirPath =
|
|
20855
|
+
const dirPath = join7(directory, PATHS.OPENCODE);
|
|
19739
20856
|
try {
|
|
19740
|
-
if (!
|
|
20857
|
+
if (!existsSync5(dirPath)) {
|
|
19741
20858
|
mkdirSync(dirPath, { recursive: true });
|
|
19742
20859
|
}
|
|
19743
|
-
|
|
20860
|
+
writeFileSync2(filePath, JSON.stringify(state2, null, 2), "utf-8");
|
|
19744
20861
|
return true;
|
|
19745
20862
|
} catch (error45) {
|
|
19746
20863
|
log(`[${MISSION_CONTROL.LOG_SOURCE}] Failed to write state: ${error45}`);
|
|
@@ -19749,7 +20866,7 @@ function writeLoopState(directory, state2) {
|
|
|
19749
20866
|
}
|
|
19750
20867
|
function clearLoopState(directory) {
|
|
19751
20868
|
const filePath = getStateFilePath(directory);
|
|
19752
|
-
if (!
|
|
20869
|
+
if (!existsSync5(filePath)) {
|
|
19753
20870
|
return false;
|
|
19754
20871
|
}
|
|
19755
20872
|
try {
|
|
@@ -19781,6 +20898,7 @@ function startMissionLoop(directory, sessionID, prompt, options = {}) {
|
|
|
19781
20898
|
};
|
|
19782
20899
|
const success2 = writeLoopState(directory, state2);
|
|
19783
20900
|
if (success2) {
|
|
20901
|
+
TerminalMonitor.getInstance().start();
|
|
19784
20902
|
log(`[${MISSION_CONTROL.LOG_SOURCE}] Loop started`, {
|
|
19785
20903
|
sessionID,
|
|
19786
20904
|
maxIterations: state2.maxIterations
|
|
@@ -19807,29 +20925,12 @@ function generateMissionContinuationPrompt(state2, verificationSummary) {
|
|
|
19807
20925
|
const summaryHeader = verificationSummary ? `
|
|
19808
20926
|
[Verification Status]: ${verificationSummary}
|
|
19809
20927
|
` : "";
|
|
19810
|
-
return
|
|
20928
|
+
return `${CONTINUE_INSTRUCTION}
|
|
20929
|
+
|
|
20930
|
+
<mission_loop iteration="${state2.iteration}" max="${state2.maxIterations}">
|
|
19811
20931
|
\u26A0\uFE0F **MISSION NOT COMPLETE** - Iteration ${state2.iteration}/${state2.maxIterations}
|
|
19812
20932
|
${summaryHeader}
|
|
19813
20933
|
|
|
19814
|
-
The mission is INCOMPLETE. You MUST continue working NOW.
|
|
19815
|
-
|
|
19816
|
-
**HIERARCHICAL TODO MANDATE**:
|
|
19817
|
-
Your work is strictly governed by the hierarchy in \`.opencode/todo.md\`.
|
|
19818
|
-
1\uFE0F\u20E3 **Milestones (Grade 1)**: Large phases of the mission.
|
|
19819
|
-
2\uFE0F\u20E3 **Tasks (Grade 2)**: Sub-tasks within milestones.
|
|
19820
|
-
3\uFE0F\u20E3 **Sub-tasks (Grade 3)**: Atomic actions.
|
|
19821
|
-
|
|
19822
|
-
**CONCLUDING RULES**:
|
|
19823
|
-
\u274C Do NOT stop if there are ANY \`[ ]\` items remaining.
|
|
19824
|
-
\u274C Do NOT ask for permission to continue.
|
|
19825
|
-
\u274C Do NOT declare completion without verifying EVERY leaf node.
|
|
19826
|
-
|
|
19827
|
-
**REQUIRED SEQUENCE**:
|
|
19828
|
-
1. Read \`.opencode/todo.md\` to identify the next \`[ ]\` item.
|
|
19829
|
-
2. If the plan is too abstract, breakdown the next task into smaller sub-tasks.
|
|
19830
|
-
3. Execute and mark as \`[x]\` ONLY after verification.
|
|
19831
|
-
4. Move to the next item immediately.
|
|
19832
|
-
|
|
19833
20934
|
**Your Original Task**:
|
|
19834
20935
|
${state2.prompt}
|
|
19835
20936
|
|
|
@@ -19848,7 +20949,7 @@ function getLatest(sessionId) {
|
|
|
19848
20949
|
const history = progressHistory.get(sessionId);
|
|
19849
20950
|
return history?.[history.length - 1];
|
|
19850
20951
|
}
|
|
19851
|
-
function
|
|
20952
|
+
function clearSession2(sessionId) {
|
|
19852
20953
|
progressHistory.delete(sessionId);
|
|
19853
20954
|
sessionStartTimes.delete(sessionId);
|
|
19854
20955
|
}
|
|
@@ -19913,8 +21014,8 @@ function formatElapsedTime(startMs, endMs = Date.now()) {
|
|
|
19913
21014
|
}
|
|
19914
21015
|
|
|
19915
21016
|
// src/core/loop/verification.ts
|
|
19916
|
-
import { existsSync as
|
|
19917
|
-
import { join as
|
|
21017
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
21018
|
+
import { join as join8 } from "node:path";
|
|
19918
21019
|
var CHECKLIST_FILE = CHECKLIST.FILE;
|
|
19919
21020
|
function parseChecklistLine(line, currentCategory) {
|
|
19920
21021
|
const trimmedLine = line.trim();
|
|
@@ -19979,12 +21080,12 @@ function parseChecklist(content) {
|
|
|
19979
21080
|
return items;
|
|
19980
21081
|
}
|
|
19981
21082
|
function readChecklist(directory) {
|
|
19982
|
-
const filePath =
|
|
19983
|
-
if (!
|
|
21083
|
+
const filePath = join8(directory, CHECKLIST_FILE);
|
|
21084
|
+
if (!existsSync6(filePath)) {
|
|
19984
21085
|
return [];
|
|
19985
21086
|
}
|
|
19986
21087
|
try {
|
|
19987
|
-
const content =
|
|
21088
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
19988
21089
|
return parseChecklist(content);
|
|
19989
21090
|
} catch (error45) {
|
|
19990
21091
|
log(`[checklist] Failed to read checklist: ${error45}`);
|
|
@@ -20001,8 +21102,8 @@ function verifyChecklist(directory) {
|
|
|
20001
21102
|
incompleteList: [],
|
|
20002
21103
|
errors: []
|
|
20003
21104
|
};
|
|
20004
|
-
const filePath =
|
|
20005
|
-
if (!
|
|
21105
|
+
const filePath = join8(directory, CHECKLIST_FILE);
|
|
21106
|
+
if (!existsSync6(filePath)) {
|
|
20006
21107
|
result.errors.push(`Verification checklist not found at ${CHECKLIST_FILE}`);
|
|
20007
21108
|
result.errors.push("Create checklist with at least: build, tests, and any environment-specific checks");
|
|
20008
21109
|
return result;
|
|
@@ -20078,10 +21179,10 @@ function verifyMissionCompletion(directory) {
|
|
|
20078
21179
|
result.errors.push(` ... and ${checklistResult.incompleteList.length - 5} more`);
|
|
20079
21180
|
}
|
|
20080
21181
|
}
|
|
20081
|
-
const todoPath =
|
|
20082
|
-
if (
|
|
21182
|
+
const todoPath = join8(directory, PATHS.TODO);
|
|
21183
|
+
if (existsSync6(todoPath)) {
|
|
20083
21184
|
try {
|
|
20084
|
-
const content =
|
|
21185
|
+
const content = readFileSync3(todoPath, "utf-8");
|
|
20085
21186
|
const incompleteCount = countMatches(content, TODO_INCOMPLETE_PATTERN);
|
|
20086
21187
|
const completeCount = countMatches(content, TODO_COMPLETE_PATTERN);
|
|
20087
21188
|
const total = incompleteCount + completeCount;
|
|
@@ -20103,10 +21204,10 @@ function verifyMissionCompletion(directory) {
|
|
|
20103
21204
|
} else if (!hasChecklist) {
|
|
20104
21205
|
result.errors.push(`TODO file not found at ${PATHS.TODO}`);
|
|
20105
21206
|
}
|
|
20106
|
-
const syncPath =
|
|
20107
|
-
if (
|
|
21207
|
+
const syncPath = join8(directory, PATHS.SYNC_ISSUES);
|
|
21208
|
+
if (existsSync6(syncPath)) {
|
|
20108
21209
|
try {
|
|
20109
|
-
const content =
|
|
21210
|
+
const content = readFileSync3(syncPath, "utf-8");
|
|
20110
21211
|
result.syncIssuesEmpty = !hasRealSyncIssues(content);
|
|
20111
21212
|
if (!result.syncIssuesEmpty) {
|
|
20112
21213
|
const issueLines = content.split("\n").filter(
|
|
@@ -20241,10 +21342,10 @@ async function resolveCommandPath(key, commandName) {
|
|
|
20241
21342
|
const currentPending = pending.get(key);
|
|
20242
21343
|
if (currentPending) return currentPending;
|
|
20243
21344
|
const promise2 = (async () => {
|
|
20244
|
-
const
|
|
20245
|
-
cache[key] =
|
|
21345
|
+
const path9 = await findCommand(commandName);
|
|
21346
|
+
cache[key] = path9;
|
|
20246
21347
|
pending.delete(key);
|
|
20247
|
-
return
|
|
21348
|
+
return path9;
|
|
20248
21349
|
})();
|
|
20249
21350
|
pending.set(key, promise2);
|
|
20250
21351
|
return promise2;
|
|
@@ -20253,21 +21354,21 @@ async function resolveCommandPath(key, commandName) {
|
|
|
20253
21354
|
// src/core/notification/os-notify/notifier.ts
|
|
20254
21355
|
var execAsync2 = promisify2(exec2);
|
|
20255
21356
|
async function notifyDarwin(title, message) {
|
|
20256
|
-
const
|
|
21357
|
+
const path9 = await resolveCommandPath(
|
|
20257
21358
|
NOTIFICATION_COMMAND_KEYS.OSASCRIPT,
|
|
20258
21359
|
NOTIFICATION_COMMANDS.OSASCRIPT
|
|
20259
21360
|
);
|
|
20260
|
-
if (!
|
|
21361
|
+
if (!path9) return;
|
|
20261
21362
|
const escT = title.replace(/"/g, '\\"');
|
|
20262
21363
|
const escM = message.replace(/"/g, '\\"');
|
|
20263
|
-
await execAsync2(`${
|
|
21364
|
+
await execAsync2(`${path9} -e 'display notification "${escM}" with title "${escT}" sound name "Glass"'`);
|
|
20264
21365
|
}
|
|
20265
21366
|
async function notifyLinux(title, message) {
|
|
20266
|
-
const
|
|
21367
|
+
const path9 = await resolveCommandPath(
|
|
20267
21368
|
NOTIFICATION_COMMAND_KEYS.NOTIFY_SEND,
|
|
20268
21369
|
NOTIFICATION_COMMANDS.NOTIFY_SEND
|
|
20269
21370
|
);
|
|
20270
|
-
if (
|
|
21371
|
+
if (path9) await execAsync2(`${path9} "${title}" "${message}" 2>/dev/null`);
|
|
20271
21372
|
}
|
|
20272
21373
|
async function notifyWindows(title, message) {
|
|
20273
21374
|
const ps = await resolveCommandPath(
|
|
@@ -20313,11 +21414,11 @@ import { exec as exec3 } from "node:child_process";
|
|
|
20313
21414
|
async function playDarwin(soundPath) {
|
|
20314
21415
|
if (!soundPath) return;
|
|
20315
21416
|
try {
|
|
20316
|
-
const
|
|
21417
|
+
const path9 = await resolveCommandPath(
|
|
20317
21418
|
NOTIFICATION_COMMAND_KEYS.AFPLAY,
|
|
20318
21419
|
NOTIFICATION_COMMANDS.AFPLAY
|
|
20319
21420
|
);
|
|
20320
|
-
if (
|
|
21421
|
+
if (path9) exec3(`"${path9}" "${soundPath}"`);
|
|
20321
21422
|
} catch (err) {
|
|
20322
21423
|
log(`[session-notify] Error playing sound (Darwin): ${err}`);
|
|
20323
21424
|
}
|
|
@@ -20387,7 +21488,7 @@ function getDefaultSoundPath(p) {
|
|
|
20387
21488
|
|
|
20388
21489
|
// src/hooks/features/mission-loop.ts
|
|
20389
21490
|
var MissionControlHook = class {
|
|
20390
|
-
name =
|
|
21491
|
+
name = HOOK_NAMES2.MISSION_LOOP;
|
|
20391
21492
|
async execute(ctx, text) {
|
|
20392
21493
|
const chatResult = await this.handleChatCommand(ctx, text);
|
|
20393
21494
|
if (chatResult) return chatResult;
|
|
@@ -20473,6 +21574,7 @@ var MissionControlHook = class {
|
|
|
20473
21574
|
async handleMissionComplete(directory, verification) {
|
|
20474
21575
|
log(MISSION_MESSAGES.COMPLETE_LOG + " " + buildVerificationSummary(verification));
|
|
20475
21576
|
const cleared = clearLoopState(directory);
|
|
21577
|
+
parallelAgentManager.cleanup();
|
|
20476
21578
|
if (cleared) {
|
|
20477
21579
|
const toastManager = getTaskToastManager();
|
|
20478
21580
|
if (toastManager) {
|
|
@@ -20535,7 +21637,7 @@ var UI_PATTERNS = {
|
|
|
20535
21637
|
|
|
20536
21638
|
// src/hooks/custom/strict-role-guard.ts
|
|
20537
21639
|
var StrictRoleGuardHook = class {
|
|
20538
|
-
name =
|
|
21640
|
+
name = HOOK_NAMES2.STRICT_ROLE_GUARD;
|
|
20539
21641
|
async execute(ctx, tool2, args) {
|
|
20540
21642
|
if (tool2 === TOOL_NAMES.RUN_COMMAND || tool2 === TOOL_NAMES.RUN_BACKGROUND) {
|
|
20541
21643
|
const cmd = args?.command;
|
|
@@ -20554,7 +21656,7 @@ var StrictRoleGuardHook = class {
|
|
|
20554
21656
|
|
|
20555
21657
|
// src/hooks/custom/secret-scanner.ts
|
|
20556
21658
|
var SecretScannerHook = class {
|
|
20557
|
-
name =
|
|
21659
|
+
name = HOOK_NAMES2.SECRET_SCANNER;
|
|
20558
21660
|
async execute(ctx, tool2, input, output) {
|
|
20559
21661
|
let content = output.output;
|
|
20560
21662
|
let modified = false;
|
|
@@ -20573,7 +21675,7 @@ var SecretScannerHook = class {
|
|
|
20573
21675
|
|
|
20574
21676
|
// src/hooks/custom/agent-ui.ts
|
|
20575
21677
|
var AgentUIHook = class {
|
|
20576
|
-
name =
|
|
21678
|
+
name = HOOK_NAMES2.AGENT_UI;
|
|
20577
21679
|
async execute(ctx, tool2, input, output) {
|
|
20578
21680
|
if (tool2 !== TOOL_NAMES.CALL_AGENT) return {};
|
|
20579
21681
|
if (input?.task) {
|
|
@@ -20693,7 +21795,7 @@ function cleanupSession(sessionID) {
|
|
|
20693
21795
|
var COMPACTION_THRESHOLD = CONTEXT_THRESHOLDS.WARNING;
|
|
20694
21796
|
var COOLDOWN_MS = 10 * 60 * 1e3;
|
|
20695
21797
|
var ResourceControlHook = class {
|
|
20696
|
-
name =
|
|
21798
|
+
name = HOOK_NAMES2.RESOURCE_CONTROL;
|
|
20697
21799
|
lastCompactionTime = /* @__PURE__ */ new Map();
|
|
20698
21800
|
async execute(ctx, toolOrText, input, output) {
|
|
20699
21801
|
let inputLen = 0;
|
|
@@ -20758,7 +21860,7 @@ function getNextPending(todos) {
|
|
|
20758
21860
|
pending2.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
20759
21861
|
return pending2[0];
|
|
20760
21862
|
}
|
|
20761
|
-
function
|
|
21863
|
+
function getStats3(todos) {
|
|
20762
21864
|
const stats2 = {
|
|
20763
21865
|
total: todos.length,
|
|
20764
21866
|
pending: todos.filter((t) => t.status === TODO_STATUS2.PENDING).length,
|
|
@@ -20777,7 +21879,7 @@ function getStats2(todos) {
|
|
|
20777
21879
|
|
|
20778
21880
|
// src/core/loop/formatters.ts
|
|
20779
21881
|
function formatProgress(todos) {
|
|
20780
|
-
const stats2 =
|
|
21882
|
+
const stats2 = getStats3(todos);
|
|
20781
21883
|
const done = stats2.completed + stats2.cancelled;
|
|
20782
21884
|
return `${done}/${stats2.total} (${stats2.percentComplete}%)`;
|
|
20783
21885
|
}
|
|
@@ -20841,110 +21943,6 @@ ${LOOP_LABELS.ACTION_DONT_STOP}
|
|
|
20841
21943
|
return prompt;
|
|
20842
21944
|
}
|
|
20843
21945
|
|
|
20844
|
-
// src/core/recovery/constants.ts
|
|
20845
|
-
var MAX_RETRIES = RECOVERY.MAX_ATTEMPTS;
|
|
20846
|
-
var BASE_DELAY = RECOVERY.BASE_DELAY_MS;
|
|
20847
|
-
var MAX_HISTORY = HISTORY.MAX_RECOVERY;
|
|
20848
|
-
|
|
20849
|
-
// src/core/recovery/patterns.ts
|
|
20850
|
-
var errorPatterns = [
|
|
20851
|
-
// Rate limiting
|
|
20852
|
-
{
|
|
20853
|
-
pattern: /rate.?limit|too.?many.?requests|429/i,
|
|
20854
|
-
category: "rate_limit",
|
|
20855
|
-
handler: (ctx) => {
|
|
20856
|
-
const delay = BASE_DELAY * Math.pow(2, ctx.attempt);
|
|
20857
|
-
presets_exports.warningRateLimited();
|
|
20858
|
-
return { type: "retry", delay, attempt: ctx.attempt + 1 };
|
|
20859
|
-
}
|
|
20860
|
-
},
|
|
20861
|
-
// Context overflow
|
|
20862
|
-
{
|
|
20863
|
-
pattern: /context.?length|token.?limit|maximum.?context/i,
|
|
20864
|
-
category: "context_overflow",
|
|
20865
|
-
handler: () => {
|
|
20866
|
-
presets_exports.errorRecovery("Compacting context");
|
|
20867
|
-
return { type: "compact", reason: "Context limit reached" };
|
|
20868
|
-
}
|
|
20869
|
-
},
|
|
20870
|
-
// Network errors
|
|
20871
|
-
{
|
|
20872
|
-
pattern: /ECONNREFUSED|ETIMEDOUT|network|fetch.?failed/i,
|
|
20873
|
-
category: "network",
|
|
20874
|
-
handler: (ctx) => {
|
|
20875
|
-
if (ctx.attempt >= MAX_RETRIES) {
|
|
20876
|
-
return { type: "abort", reason: "Network unavailable after retries" };
|
|
20877
|
-
}
|
|
20878
|
-
return { type: "retry", delay: BASE_DELAY * (ctx.attempt + 1), attempt: ctx.attempt + 1 };
|
|
20879
|
-
}
|
|
20880
|
-
},
|
|
20881
|
-
// Session errors
|
|
20882
|
-
{
|
|
20883
|
-
pattern: /session.?not.?found|session.?expired/i,
|
|
20884
|
-
category: "session",
|
|
20885
|
-
handler: () => {
|
|
20886
|
-
return { type: "abort", reason: "Session no longer available" };
|
|
20887
|
-
}
|
|
20888
|
-
},
|
|
20889
|
-
// Tool errors
|
|
20890
|
-
{
|
|
20891
|
-
pattern: /tool.?not.?found|unknown.?tool/i,
|
|
20892
|
-
category: "tool",
|
|
20893
|
-
handler: (ctx) => {
|
|
20894
|
-
return { type: "escalate", to: "Reviewer", reason: `Unknown tool used by ${ctx.agent}` };
|
|
20895
|
-
}
|
|
20896
|
-
},
|
|
20897
|
-
// Parse errors
|
|
20898
|
-
{
|
|
20899
|
-
pattern: /parse.?error|invalid.?json|syntax.?error/i,
|
|
20900
|
-
category: "parse",
|
|
20901
|
-
handler: (ctx) => {
|
|
20902
|
-
if (ctx.attempt >= 2) {
|
|
20903
|
-
return { type: "skip", reason: "Persistent parse error" };
|
|
20904
|
-
}
|
|
20905
|
-
return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
|
|
20906
|
-
}
|
|
20907
|
-
},
|
|
20908
|
-
// Gibberish / hallucination
|
|
20909
|
-
{
|
|
20910
|
-
pattern: /gibberish|hallucination|mixed.?language/i,
|
|
20911
|
-
category: "gibberish",
|
|
20912
|
-
handler: () => {
|
|
20913
|
-
presets_exports.errorRecovery("Retrying with clean context");
|
|
20914
|
-
return { type: "retry", delay: 1e3, attempt: 1 };
|
|
20915
|
-
}
|
|
20916
|
-
}
|
|
20917
|
-
];
|
|
20918
|
-
|
|
20919
|
-
// src/core/recovery/handler.ts
|
|
20920
|
-
var recoveryHistory = [];
|
|
20921
|
-
function handleError(context) {
|
|
20922
|
-
const errorMessage = context.error.message || String(context.error);
|
|
20923
|
-
for (const pattern of errorPatterns) {
|
|
20924
|
-
const matches = typeof pattern.pattern === "string" ? errorMessage.includes(pattern.pattern) : pattern.pattern.test(errorMessage);
|
|
20925
|
-
if (matches) {
|
|
20926
|
-
const action = pattern.handler(context);
|
|
20927
|
-
recoveryHistory.push({
|
|
20928
|
-
context,
|
|
20929
|
-
action,
|
|
20930
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
20931
|
-
});
|
|
20932
|
-
if (recoveryHistory.length > MAX_HISTORY) {
|
|
20933
|
-
recoveryHistory.shift();
|
|
20934
|
-
}
|
|
20935
|
-
return action;
|
|
20936
|
-
}
|
|
20937
|
-
}
|
|
20938
|
-
if (context.attempt < MAX_RETRIES) {
|
|
20939
|
-
return {
|
|
20940
|
-
type: "retry",
|
|
20941
|
-
delay: BASE_DELAY * Math.pow(2, context.attempt),
|
|
20942
|
-
attempt: context.attempt + 1
|
|
20943
|
-
};
|
|
20944
|
-
}
|
|
20945
|
-
return { type: "abort", reason: `Unknown error after ${MAX_RETRIES} retries` };
|
|
20946
|
-
}
|
|
20947
|
-
|
|
20948
21946
|
// src/core/recovery/session-recovery.ts
|
|
20949
21947
|
var recoveryState = /* @__PURE__ */ new Map();
|
|
20950
21948
|
function getState2(sessionID) {
|
|
@@ -21292,7 +22290,7 @@ function cleanupSession2(sessionID) {
|
|
|
21292
22290
|
|
|
21293
22291
|
// src/hooks/custom/user-activity.ts
|
|
21294
22292
|
var UserActivityHook = class {
|
|
21295
|
-
name =
|
|
22293
|
+
name = HOOK_NAMES2.USER_ACTIVITY;
|
|
21296
22294
|
async execute(ctx, message) {
|
|
21297
22295
|
if (ctx.sessionID) {
|
|
21298
22296
|
handleUserMessage(ctx.sessionID);
|
|
@@ -21301,6 +22299,70 @@ var UserActivityHook = class {
|
|
|
21301
22299
|
}
|
|
21302
22300
|
};
|
|
21303
22301
|
|
|
22302
|
+
// src/hooks/custom/memory-gate.ts
|
|
22303
|
+
var MemoryGateHook = class {
|
|
22304
|
+
name = HOOK_NAMES.MEMORY_GATE;
|
|
22305
|
+
memoryManager = MemoryManager.getInstance();
|
|
22306
|
+
async execute(context, toolOrText, input, output) {
|
|
22307
|
+
if (output) {
|
|
22308
|
+
return this.handlePostTool(context, toolOrText, input, output);
|
|
22309
|
+
} else {
|
|
22310
|
+
return this.handleAssistantDone(context, toolOrText);
|
|
22311
|
+
}
|
|
22312
|
+
}
|
|
22313
|
+
/**
|
|
22314
|
+
* Post-Tool: Capture tool outputs to TASK memory
|
|
22315
|
+
*/
|
|
22316
|
+
async handlePostTool(context, tool2, input, output) {
|
|
22317
|
+
if (MEMORY_CONSTANTS.NOISY_TOOLS.includes(tool2)) return {};
|
|
22318
|
+
const maxContentLength = MEMORY_CONSTANTS.MAX_CONTENT_LENGTH;
|
|
22319
|
+
let content = output.output;
|
|
22320
|
+
if (content.length > maxContentLength) {
|
|
22321
|
+
content = content.substring(0, maxContentLength) + "... [truncated]";
|
|
22322
|
+
}
|
|
22323
|
+
const memoryText = `Tool [${tool2}] result for input ${JSON.stringify(input)}: ${content}`;
|
|
22324
|
+
this.memoryManager.add("task" /* TASK */, memoryText, MEMORY_CONSTANTS.IMPORTANCE.LOW);
|
|
22325
|
+
return {};
|
|
22326
|
+
}
|
|
22327
|
+
/**
|
|
22328
|
+
* Assistant Done: Capture turn summary to MISSION memory if important
|
|
22329
|
+
*/
|
|
22330
|
+
async handleAssistantDone(context, finalText) {
|
|
22331
|
+
const { KEYWORDS, IMPORTANCE } = MEMORY_CONSTANTS;
|
|
22332
|
+
if (finalText.includes(KEYWORDS.DONE) || finalText.includes(KEYWORDS.SUCCESS) || finalText.includes(KEYWORDS.ERROR)) {
|
|
22333
|
+
const importance = finalText.includes(KEYWORDS.ERROR) ? IMPORTANCE.CRITICAL : IMPORTANCE.HIGH;
|
|
22334
|
+
const summary = finalText.length > 500 ? finalText.substring(0, 500) + "..." : finalText;
|
|
22335
|
+
this.memoryManager.add("mission" /* MISSION */, `Agent [${context.agent}] turn summary: ${summary}`, importance);
|
|
22336
|
+
}
|
|
22337
|
+
return { action: HOOK_ACTIONS.CONTINUE };
|
|
22338
|
+
}
|
|
22339
|
+
};
|
|
22340
|
+
|
|
22341
|
+
// src/hooks/custom/metrics.ts
|
|
22342
|
+
var MetricsHook = class {
|
|
22343
|
+
name = HOOK_NAMES.METRICS_TELEMETRY;
|
|
22344
|
+
startTimes = /* @__PURE__ */ new Map();
|
|
22345
|
+
metrics = MetricsCollector.getInstance();
|
|
22346
|
+
async execute(context, toolOrText, input, output) {
|
|
22347
|
+
if (!output && input) {
|
|
22348
|
+
this.startTimes.set(`tool_${toolOrText}_${context.sessionID}`, Date.now());
|
|
22349
|
+
return { action: HOOK_ACTIONS.ALLOW };
|
|
22350
|
+
} else if (output) {
|
|
22351
|
+
const startTime = this.startTimes.get(`tool_${toolOrText}_${context.sessionID}`);
|
|
22352
|
+
if (startTime) {
|
|
22353
|
+
const duration3 = Date.now() - startTime;
|
|
22354
|
+
this.metrics.recordToolExecution(toolOrText, duration3);
|
|
22355
|
+
this.startTimes.delete(`tool_${toolOrText}_${context.sessionID}`);
|
|
22356
|
+
}
|
|
22357
|
+
this.metrics.recordTokenUsage(output.output.length / 4);
|
|
22358
|
+
return {};
|
|
22359
|
+
} else {
|
|
22360
|
+
this.metrics.recordTokenUsage(toolOrText.length / 4);
|
|
22361
|
+
return { action: HOOK_ACTIONS.CONTINUE };
|
|
22362
|
+
}
|
|
22363
|
+
}
|
|
22364
|
+
};
|
|
22365
|
+
|
|
21304
22366
|
// src/hooks/index.ts
|
|
21305
22367
|
function initializeHooks() {
|
|
21306
22368
|
const registry2 = HookRegistry.getInstance();
|
|
@@ -21311,18 +22373,103 @@ function initializeHooks() {
|
|
|
21311
22373
|
const agentUI = new AgentUIHook();
|
|
21312
22374
|
const resourceControl = new ResourceControlHook();
|
|
21313
22375
|
const userActivity = new UserActivityHook();
|
|
22376
|
+
const memoryGate = new MemoryGateHook();
|
|
22377
|
+
const metricsHook = new MetricsHook();
|
|
21314
22378
|
registry2.registerChat(userActivity);
|
|
21315
22379
|
registry2.registerChat(missionControl);
|
|
21316
22380
|
registry2.registerPostTool(sanityCheck);
|
|
21317
22381
|
registry2.registerPostTool(secretScanner);
|
|
21318
22382
|
registry2.registerPostTool(agentUI);
|
|
21319
22383
|
registry2.registerPostTool(resourceControl);
|
|
22384
|
+
registry2.registerPostTool(memoryGate);
|
|
22385
|
+
registry2.registerPostTool(metricsHook);
|
|
21320
22386
|
registry2.registerPreTool(roleGuard);
|
|
22387
|
+
registry2.registerPreTool(metricsHook);
|
|
21321
22388
|
registry2.registerDone(sanityCheck);
|
|
21322
22389
|
registry2.registerDone(missionControl);
|
|
21323
22390
|
registry2.registerDone(resourceControl);
|
|
22391
|
+
registry2.registerDone(memoryGate);
|
|
22392
|
+
registry2.registerDone(metricsHook);
|
|
21324
22393
|
}
|
|
21325
22394
|
|
|
22395
|
+
// src/core/plugins/plugin-manager.ts
|
|
22396
|
+
import * as fs8 from "fs/promises";
|
|
22397
|
+
import * as path7 from "path";
|
|
22398
|
+
var PluginManager = class _PluginManager {
|
|
22399
|
+
static instance;
|
|
22400
|
+
plugins = /* @__PURE__ */ new Map();
|
|
22401
|
+
directory = "";
|
|
22402
|
+
dynamicTools = {};
|
|
22403
|
+
constructor() {
|
|
22404
|
+
}
|
|
22405
|
+
static getInstance() {
|
|
22406
|
+
if (!_PluginManager.instance) {
|
|
22407
|
+
_PluginManager.instance = new _PluginManager();
|
|
22408
|
+
}
|
|
22409
|
+
return _PluginManager.instance;
|
|
22410
|
+
}
|
|
22411
|
+
async initialize(directory) {
|
|
22412
|
+
this.directory = directory;
|
|
22413
|
+
await this.loadPlugins();
|
|
22414
|
+
}
|
|
22415
|
+
/**
|
|
22416
|
+
* Load plugins from .opencode/plugins/*.js
|
|
22417
|
+
*/
|
|
22418
|
+
async loadPlugins() {
|
|
22419
|
+
if (!this.directory) return;
|
|
22420
|
+
const pluginsDir = path7.join(this.directory, PATHS.PLUGINS);
|
|
22421
|
+
try {
|
|
22422
|
+
await fs8.mkdir(pluginsDir, { recursive: true });
|
|
22423
|
+
const files = await fs8.readdir(pluginsDir);
|
|
22424
|
+
for (const file2 of files) {
|
|
22425
|
+
if (file2.endsWith(".js") || file2.endsWith(".mjs")) {
|
|
22426
|
+
await this.loadPlugin(path7.join(pluginsDir, file2));
|
|
22427
|
+
}
|
|
22428
|
+
}
|
|
22429
|
+
} catch (error45) {
|
|
22430
|
+
log(`[PluginManager] Error reading plugins directory: ${error45}`);
|
|
22431
|
+
}
|
|
22432
|
+
}
|
|
22433
|
+
async loadPlugin(pluginPath) {
|
|
22434
|
+
try {
|
|
22435
|
+
const module = await import(`file://${pluginPath}`);
|
|
22436
|
+
const plugin = module.default || module;
|
|
22437
|
+
if (!plugin.name) {
|
|
22438
|
+
log(`[PluginManager] Plugin at ${pluginPath} missing name, skipping.`);
|
|
22439
|
+
return;
|
|
22440
|
+
}
|
|
22441
|
+
log(`[PluginManager] Loading plugin: ${plugin.name} (v${plugin.version})`);
|
|
22442
|
+
const context = { directory: this.directory };
|
|
22443
|
+
if (plugin.init) {
|
|
22444
|
+
await plugin.init(context);
|
|
22445
|
+
}
|
|
22446
|
+
if (plugin.tools) {
|
|
22447
|
+
for (const [name, tool2] of Object.entries(plugin.tools)) {
|
|
22448
|
+
this.dynamicTools[name] = tool2;
|
|
22449
|
+
log(`[PluginManager] Registered tool: ${name} from plugin ${plugin.name}`);
|
|
22450
|
+
}
|
|
22451
|
+
}
|
|
22452
|
+
if (plugin.hooks) {
|
|
22453
|
+
const registry2 = HookRegistry.getInstance();
|
|
22454
|
+
if (plugin.hooks.preTool) registry2.registerPreTool(plugin.hooks.preTool);
|
|
22455
|
+
if (plugin.hooks.postTool) registry2.registerPostTool(plugin.hooks.postTool);
|
|
22456
|
+
if (plugin.hooks.chat) registry2.registerChat(plugin.hooks.chat);
|
|
22457
|
+
if (plugin.hooks.done) registry2.registerDone(plugin.hooks.done);
|
|
22458
|
+
log(`[PluginManager] Registered hooks from plugin ${plugin.name}`);
|
|
22459
|
+
}
|
|
22460
|
+
this.plugins.set(plugin.name, plugin);
|
|
22461
|
+
} catch (error45) {
|
|
22462
|
+
log(`[PluginManager] Failed to load plugin ${pluginPath}: ${error45}`);
|
|
22463
|
+
}
|
|
22464
|
+
}
|
|
22465
|
+
/**
|
|
22466
|
+
* Get all dynamically registered tools
|
|
22467
|
+
*/
|
|
22468
|
+
getDynamicTools() {
|
|
22469
|
+
return this.dynamicTools;
|
|
22470
|
+
}
|
|
22471
|
+
};
|
|
22472
|
+
|
|
21326
22473
|
// src/plugin-handlers/tool-execute-pre-handler.ts
|
|
21327
22474
|
function createToolExecuteBeforeHandler(ctx) {
|
|
21328
22475
|
const { sessions, directory } = ctx;
|
|
@@ -21381,17 +22528,17 @@ function createChatMessageHandler(ctx) {
|
|
|
21381
22528
|
}
|
|
21382
22529
|
|
|
21383
22530
|
// src/utils/compatibility/claude.ts
|
|
21384
|
-
import
|
|
21385
|
-
import
|
|
22531
|
+
import fs9 from "fs";
|
|
22532
|
+
import path8 from "path";
|
|
21386
22533
|
function findClaudeRules(startDir = process.cwd()) {
|
|
21387
22534
|
try {
|
|
21388
22535
|
let currentDir = startDir;
|
|
21389
|
-
const root =
|
|
22536
|
+
const root = path8.parse(startDir).root;
|
|
21390
22537
|
while (true) {
|
|
21391
|
-
const claudeMdPath =
|
|
21392
|
-
if (
|
|
22538
|
+
const claudeMdPath = path8.join(currentDir, "CLAUDE.md");
|
|
22539
|
+
if (fs9.existsSync(claudeMdPath)) {
|
|
21393
22540
|
try {
|
|
21394
|
-
const content =
|
|
22541
|
+
const content = fs9.readFileSync(claudeMdPath, "utf-8");
|
|
21395
22542
|
log(`[compatibility] Loaded CLAUDE.md from ${claudeMdPath}`);
|
|
21396
22543
|
return formatRules("CLAUDE.md", content);
|
|
21397
22544
|
} catch (e) {
|
|
@@ -21399,11 +22546,11 @@ function findClaudeRules(startDir = process.cwd()) {
|
|
|
21399
22546
|
}
|
|
21400
22547
|
}
|
|
21401
22548
|
if (currentDir === root) break;
|
|
21402
|
-
currentDir =
|
|
22549
|
+
currentDir = path8.dirname(currentDir);
|
|
21403
22550
|
}
|
|
21404
|
-
const copilotPath =
|
|
21405
|
-
if (
|
|
21406
|
-
return formatRules("Copilot Instructions",
|
|
22551
|
+
const copilotPath = path8.join(startDir, ".github", "copilot-instructions.md");
|
|
22552
|
+
if (fs9.existsSync(copilotPath)) {
|
|
22553
|
+
return formatRules("Copilot Instructions", fs9.readFileSync(copilotPath, "utf-8"));
|
|
21407
22554
|
}
|
|
21408
22555
|
return null;
|
|
21409
22556
|
} catch (error45) {
|
|
@@ -21686,7 +22833,7 @@ function createEventHandler(ctx) {
|
|
|
21686
22833
|
const duration3 = totalTime < 6e4 ? `${Math.round(totalTime / 1e3)}s` : `${Math.round(totalTime / 6e4)}m`;
|
|
21687
22834
|
sessions.delete(sessionID);
|
|
21688
22835
|
state2.sessions.delete(sessionID);
|
|
21689
|
-
|
|
22836
|
+
clearSession2(sessionID);
|
|
21690
22837
|
cleanupSessionRecovery(sessionID);
|
|
21691
22838
|
cleanupSession2(sessionID);
|
|
21692
22839
|
cleanupSession3(sessionID);
|
|
@@ -21998,6 +23145,9 @@ var OrchestratorPlugin = async (input) => {
|
|
|
21998
23145
|
const sessions = /* @__PURE__ */ new Map();
|
|
21999
23146
|
const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
|
|
22000
23147
|
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
|
|
23148
|
+
const pluginManager = PluginManager.getInstance();
|
|
23149
|
+
await pluginManager.initialize(directory);
|
|
23150
|
+
const dynamicTools = pluginManager.getDynamicTools();
|
|
22001
23151
|
taskToastManager.setConcurrencyController(parallelAgentManager2.getConcurrency());
|
|
22002
23152
|
const handlerContext = {
|
|
22003
23153
|
client,
|
|
@@ -22044,7 +23194,9 @@ var OrchestratorPlugin = async (input) => {
|
|
|
22044
23194
|
[TOOL_NAMES.AST_SEARCH]: astSearchTool(directory),
|
|
22045
23195
|
[TOOL_NAMES.AST_REPLACE]: astReplaceTool(directory),
|
|
22046
23196
|
// Async agent tools
|
|
22047
|
-
...asyncAgentTools
|
|
23197
|
+
...asyncAgentTools,
|
|
23198
|
+
// Dynamic tools from plugins
|
|
23199
|
+
...dynamicTools
|
|
22048
23200
|
},
|
|
22049
23201
|
// -----------------------------------------------------------------
|
|
22050
23202
|
// Config hook - registers our commands and agents with OpenCode
|