opencode-orchestrator 1.2.17 → 1.2.21
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/dist/core/agents/concurrency.d.ts +4 -1
- package/dist/core/agents/config.d.ts +1 -1
- package/dist/core/agents/manager.d.ts +1 -5
- package/dist/core/agents/session-pool.d.ts +5 -0
- package/dist/core/agents/unified-task-executor.d.ts +109 -0
- package/dist/core/sync/todo-sync-service.d.ts +7 -0
- package/dist/hooks/types.d.ts +1 -1
- package/dist/index.js +1569 -1763
- package/dist/scripts/postinstall.js +125 -29
- package/dist/shared/session/constants/events/hook-events.d.ts +9 -0
- package/dist/shared/session/constants/events/index.d.ts +5 -0
- package/dist/shared/task/constants/parallel-task.d.ts +4 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -500,24 +500,26 @@ var init_parallel_task = __esm({
|
|
|
500
500
|
* Worker and Reviewer are terminal nodes by design.
|
|
501
501
|
*/
|
|
502
502
|
TERMINAL_DEPTH: 2,
|
|
503
|
-
// Concurrency limits (
|
|
504
|
-
|
|
505
|
-
//
|
|
503
|
+
// Concurrency limits (FIXED at 5 for stability - no auto-scaling)
|
|
504
|
+
CONCURRENCY: 5,
|
|
505
|
+
// Fixed concurrency (removed DEFAULT/MAX split)
|
|
506
|
+
// Legacy support (deprecated - use CONCURRENCY instead)
|
|
507
|
+
DEFAULT_CONCURRENCY: 5,
|
|
506
508
|
MAX_CONCURRENCY: 5,
|
|
507
|
-
// Reduced from 50
|
|
508
509
|
// Sync polling (for delegate_task sync mode)
|
|
509
|
-
// Optimized:
|
|
510
|
-
SYNC_TIMEOUT_MS:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
510
|
+
// Optimized: 3s polling for faster response while maintaining stability
|
|
511
|
+
SYNC_TIMEOUT_MS: 30 * TIME.MINUTE,
|
|
512
|
+
// 5min → 30min (longer tasks)
|
|
513
|
+
POLL_INTERVAL_MS: 3e3,
|
|
514
|
+
// 5000ms → 3000ms (faster polling)
|
|
515
|
+
MIN_IDLE_TIME_MS: 3 * TIME.SECOND,
|
|
516
|
+
// 5s → 3s (quicker detection)
|
|
517
|
+
MIN_STABILITY_MS: 3 * TIME.SECOND,
|
|
518
|
+
// 5s → 3s (faster completion)
|
|
517
519
|
STABLE_POLLS_REQUIRED: 2,
|
|
518
|
-
//
|
|
519
|
-
MAX_POLL_COUNT:
|
|
520
|
-
//
|
|
520
|
+
// Keep at 2 for reliability
|
|
521
|
+
MAX_POLL_COUNT: 600,
|
|
522
|
+
// 150 → 600 (30min = 600 * 3s)
|
|
521
523
|
// Session naming
|
|
522
524
|
SESSION_TITLE_PREFIX: "Parallel",
|
|
523
525
|
// Labels for output
|
|
@@ -1377,6 +1379,20 @@ var init_special_events = __esm({
|
|
|
1377
1379
|
}
|
|
1378
1380
|
});
|
|
1379
1381
|
|
|
1382
|
+
// src/shared/session/constants/events/hook-events.ts
|
|
1383
|
+
var HOOK_EVENTS;
|
|
1384
|
+
var init_hook_events = __esm({
|
|
1385
|
+
"src/shared/session/constants/events/hook-events.ts"() {
|
|
1386
|
+
"use strict";
|
|
1387
|
+
HOOK_EVENTS = {
|
|
1388
|
+
PRE_TOOL: "hook.pre_tool",
|
|
1389
|
+
POST_TOOL: "hook.post_tool",
|
|
1390
|
+
CHAT: "hook.chat",
|
|
1391
|
+
DONE: "hook.done"
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1380
1396
|
// src/shared/session/constants/events/index.ts
|
|
1381
1397
|
var EVENT_TYPES;
|
|
1382
1398
|
var init_events = __esm({
|
|
@@ -1389,6 +1405,7 @@ var init_events = __esm({
|
|
|
1389
1405
|
init_mission_events();
|
|
1390
1406
|
init_message_events();
|
|
1391
1407
|
init_special_events();
|
|
1408
|
+
init_hook_events();
|
|
1392
1409
|
init_task_events();
|
|
1393
1410
|
init_todo_events();
|
|
1394
1411
|
init_session_events();
|
|
@@ -1396,6 +1413,7 @@ var init_events = __esm({
|
|
|
1396
1413
|
init_mission_events();
|
|
1397
1414
|
init_message_events();
|
|
1398
1415
|
init_special_events();
|
|
1416
|
+
init_hook_events();
|
|
1399
1417
|
EVENT_TYPES = {
|
|
1400
1418
|
...TASK_EVENTS,
|
|
1401
1419
|
...TODO_EVENTS,
|
|
@@ -1403,7 +1421,8 @@ var init_events = __esm({
|
|
|
1403
1421
|
...DOCUMENT_EVENTS,
|
|
1404
1422
|
...MISSION_EVENTS,
|
|
1405
1423
|
...MESSAGE_EVENTS,
|
|
1406
|
-
...SPECIAL_EVENTS
|
|
1424
|
+
...SPECIAL_EVENTS,
|
|
1425
|
+
...HOOK_EVENTS
|
|
1407
1426
|
};
|
|
1408
1427
|
}
|
|
1409
1428
|
});
|
|
@@ -5128,13 +5147,13 @@ __export(store_exports, {
|
|
|
5128
5147
|
addDecision: () => addDecision,
|
|
5129
5148
|
addDocument: () => addDocument,
|
|
5130
5149
|
addFinding: () => addFinding,
|
|
5131
|
-
clear: () =>
|
|
5150
|
+
clear: () => clear,
|
|
5132
5151
|
clearAll: () => clearAll,
|
|
5133
5152
|
create: () => create,
|
|
5134
5153
|
get: () => get,
|
|
5135
5154
|
getChildren: () => getChildren,
|
|
5136
5155
|
getMerged: () => getMerged,
|
|
5137
|
-
getStats: () =>
|
|
5156
|
+
getStats: () => getStats
|
|
5138
5157
|
});
|
|
5139
5158
|
function create(sessionId, parentId) {
|
|
5140
5159
|
const context = {
|
|
@@ -5202,7 +5221,7 @@ function addDecision(sessionId, decision) {
|
|
|
5202
5221
|
function getChildren(parentId) {
|
|
5203
5222
|
return Array.from(parentChildMap.get(parentId) || []);
|
|
5204
5223
|
}
|
|
5205
|
-
function
|
|
5224
|
+
function clear(sessionId) {
|
|
5206
5225
|
const context = contexts.get(sessionId);
|
|
5207
5226
|
if (context?.parentId) {
|
|
5208
5227
|
parentChildMap.get(context.parentId)?.delete(sessionId);
|
|
@@ -5213,7 +5232,7 @@ function clearAll() {
|
|
|
5213
5232
|
contexts.clear();
|
|
5214
5233
|
parentChildMap.clear();
|
|
5215
5234
|
}
|
|
5216
|
-
function
|
|
5235
|
+
function getStats() {
|
|
5217
5236
|
let totalDocuments = 0;
|
|
5218
5237
|
let totalFindings = 0;
|
|
5219
5238
|
let totalDecisions = 0;
|
|
@@ -18581,34 +18600,20 @@ var ConcurrencyController = class {
|
|
|
18581
18600
|
}
|
|
18582
18601
|
}
|
|
18583
18602
|
/**
|
|
18584
|
-
* Report success/failure
|
|
18603
|
+
* Report success/failure (for metrics only - auto-scaling DISABLED)
|
|
18604
|
+
*
|
|
18605
|
+
* Auto-scaling has been removed for stability. Concurrency is now fixed at 5.
|
|
18606
|
+
* This method is kept for backwards compatibility and metrics tracking.
|
|
18585
18607
|
*/
|
|
18586
18608
|
reportResult(key, success3) {
|
|
18587
18609
|
if (success3) {
|
|
18588
18610
|
const streak = (this.successStreak.get(key) ?? 0) + 1;
|
|
18589
18611
|
this.successStreak.set(key, streak);
|
|
18590
18612
|
this.failureCount.set(key, 0);
|
|
18591
|
-
if (streak >= 5) {
|
|
18592
|
-
const currentLimit = this.getConcurrencyLimit(key);
|
|
18593
|
-
if (currentLimit < PARALLEL_TASK.MAX_CONCURRENCY) {
|
|
18594
|
-
this.setLimit(key, currentLimit + 1);
|
|
18595
|
-
this.successStreak.set(key, 0);
|
|
18596
|
-
log(`[concurrency] Auto-scaling UP for ${key}: ${currentLimit + 1}`);
|
|
18597
|
-
}
|
|
18598
|
-
}
|
|
18599
18613
|
} else {
|
|
18600
18614
|
const failures = (this.failureCount.get(key) ?? 0) + 1;
|
|
18601
18615
|
this.failureCount.set(key, failures);
|
|
18602
18616
|
this.successStreak.set(key, 0);
|
|
18603
|
-
if (failures >= 2) {
|
|
18604
|
-
const currentLimit = this.getConcurrencyLimit(key);
|
|
18605
|
-
const minLimit = 1;
|
|
18606
|
-
if (currentLimit > minLimit) {
|
|
18607
|
-
this.setLimit(key, currentLimit - 1);
|
|
18608
|
-
this.failureCount.set(key, 0);
|
|
18609
|
-
log(`[concurrency] Auto-scaling DOWN for ${key}: ${currentLimit - 1} (due to ${failures} failures)`);
|
|
18610
|
-
}
|
|
18611
|
-
}
|
|
18612
18617
|
}
|
|
18613
18618
|
}
|
|
18614
18619
|
getQueueLength(key) {
|
|
@@ -18811,19 +18816,6 @@ function formatDuration(start, end) {
|
|
|
18811
18816
|
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
18812
18817
|
return `${seconds}s`;
|
|
18813
18818
|
}
|
|
18814
|
-
function buildNotificationMessage(tasks) {
|
|
18815
|
-
const summary = tasks.map((t) => {
|
|
18816
|
-
const status = t.status === TASK_STATUS.COMPLETED ? "\u2705" : "\u274C";
|
|
18817
|
-
return `${status} \`${t.id}\`: ${t.description}`;
|
|
18818
|
-
}).join("\n");
|
|
18819
|
-
return `<system-notification>
|
|
18820
|
-
**All Parallel Tasks Complete**
|
|
18821
|
-
|
|
18822
|
-
${summary}
|
|
18823
|
-
|
|
18824
|
-
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
18825
|
-
</system-notification>`;
|
|
18826
|
-
}
|
|
18827
18819
|
|
|
18828
18820
|
// src/core/session/session-health.ts
|
|
18829
18821
|
var sessionHealth = /* @__PURE__ */ new Map();
|
|
@@ -18832,13 +18824,6 @@ var HEALTH_CHECK_INTERVAL_MS = 6e4;
|
|
|
18832
18824
|
var WARNING_THRESHOLD_MS = 3e5;
|
|
18833
18825
|
var healthCheckTimer;
|
|
18834
18826
|
var client;
|
|
18835
|
-
function recordSessionResponse(sessionID) {
|
|
18836
|
-
const health = sessionHealth.get(sessionID);
|
|
18837
|
-
if (health) {
|
|
18838
|
-
health.lastResponseTime = Date.now();
|
|
18839
|
-
health.isStale = false;
|
|
18840
|
-
}
|
|
18841
|
-
}
|
|
18842
18827
|
function startHealthCheck(opencodeClient) {
|
|
18843
18828
|
if (healthCheckTimer) {
|
|
18844
18829
|
log("[session-health] Health check already running");
|
|
@@ -18885,292 +18870,6 @@ function performHealthCheck() {
|
|
|
18885
18870
|
});
|
|
18886
18871
|
}
|
|
18887
18872
|
}
|
|
18888
|
-
function cleanupSessionHealth(sessionID) {
|
|
18889
|
-
sessionHealth.delete(sessionID);
|
|
18890
|
-
}
|
|
18891
|
-
|
|
18892
|
-
// src/core/agents/manager/task-launcher.ts
|
|
18893
|
-
init_shared();
|
|
18894
|
-
init_shared();
|
|
18895
|
-
|
|
18896
|
-
// src/core/notification/task-toast-manager.ts
|
|
18897
|
-
init_shared();
|
|
18898
|
-
var TaskToastManager = class {
|
|
18899
|
-
tasks = /* @__PURE__ */ new Map();
|
|
18900
|
-
client = null;
|
|
18901
|
-
concurrency = null;
|
|
18902
|
-
todoSync = null;
|
|
18903
|
-
/**
|
|
18904
|
-
* Initialize the manager with OpenCode client
|
|
18905
|
-
*/
|
|
18906
|
-
init(client2, concurrency) {
|
|
18907
|
-
this.client = client2;
|
|
18908
|
-
this.concurrency = concurrency ?? null;
|
|
18909
|
-
}
|
|
18910
|
-
/**
|
|
18911
|
-
* Set concurrency controller (can be set after init)
|
|
18912
|
-
*/
|
|
18913
|
-
setConcurrencyController(concurrency) {
|
|
18914
|
-
this.concurrency = concurrency;
|
|
18915
|
-
}
|
|
18916
|
-
/**
|
|
18917
|
-
* Set TodoSyncService for TUI status synchronization
|
|
18918
|
-
*/
|
|
18919
|
-
setTodoSync(service) {
|
|
18920
|
-
this.todoSync = service;
|
|
18921
|
-
}
|
|
18922
|
-
/**
|
|
18923
|
-
* Add a new task and show consolidated toast
|
|
18924
|
-
*/
|
|
18925
|
-
addTask(task) {
|
|
18926
|
-
const trackedTask = {
|
|
18927
|
-
id: task.id,
|
|
18928
|
-
description: task.description,
|
|
18929
|
-
agent: task.agent,
|
|
18930
|
-
status: task.status ?? STATUS_LABEL.RUNNING,
|
|
18931
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
18932
|
-
isBackground: task.isBackground,
|
|
18933
|
-
parentSessionID: task.parentSessionID,
|
|
18934
|
-
sessionID: task.sessionID
|
|
18935
|
-
};
|
|
18936
|
-
this.tasks.set(task.id, trackedTask);
|
|
18937
|
-
this.todoSync?.updateTaskStatus(trackedTask);
|
|
18938
|
-
this.showTaskListToast(trackedTask);
|
|
18939
|
-
}
|
|
18940
|
-
/**
|
|
18941
|
-
* Update task status
|
|
18942
|
-
*/
|
|
18943
|
-
updateTask(id, status) {
|
|
18944
|
-
const task = this.tasks.get(id);
|
|
18945
|
-
if (task) {
|
|
18946
|
-
task.status = status;
|
|
18947
|
-
this.todoSync?.updateTaskStatus(task);
|
|
18948
|
-
}
|
|
18949
|
-
}
|
|
18950
|
-
/**
|
|
18951
|
-
* Remove a task
|
|
18952
|
-
*/
|
|
18953
|
-
removeTask(id) {
|
|
18954
|
-
this.tasks.delete(id);
|
|
18955
|
-
this.todoSync?.removeTask(id);
|
|
18956
|
-
}
|
|
18957
|
-
/**
|
|
18958
|
-
* Get all running tasks (newest first)
|
|
18959
|
-
*/
|
|
18960
|
-
getRunningTasks() {
|
|
18961
|
-
return Array.from(this.tasks.values()).filter((t) => t.status === STATUS_LABEL.RUNNING).sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
18962
|
-
}
|
|
18963
|
-
/**
|
|
18964
|
-
* Get all queued tasks (oldest first - FIFO)
|
|
18965
|
-
*/
|
|
18966
|
-
getQueuedTasks() {
|
|
18967
|
-
return Array.from(this.tasks.values()).filter((t) => t.status === STATUS_LABEL.QUEUED).sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
|
18968
|
-
}
|
|
18969
|
-
/**
|
|
18970
|
-
* Get tasks by parent session
|
|
18971
|
-
*/
|
|
18972
|
-
getTasksByParent(parentSessionID) {
|
|
18973
|
-
return Array.from(this.tasks.values()).filter((t) => t.parentSessionID === parentSessionID);
|
|
18974
|
-
}
|
|
18975
|
-
/**
|
|
18976
|
-
* Format duration since task started
|
|
18977
|
-
*/
|
|
18978
|
-
formatDuration(startedAt) {
|
|
18979
|
-
const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
|
|
18980
|
-
if (seconds < 60) return `${seconds}s`;
|
|
18981
|
-
const minutes = Math.floor(seconds / 60);
|
|
18982
|
-
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
|
18983
|
-
const hours = Math.floor(minutes / 60);
|
|
18984
|
-
return `${hours}h ${minutes % 60}m`;
|
|
18985
|
-
}
|
|
18986
|
-
/**
|
|
18987
|
-
* Get concurrency info string (e.g., " [2/5]")
|
|
18988
|
-
*/
|
|
18989
|
-
/**
|
|
18990
|
-
* Get concurrency info string (e.g., " [2/5]")
|
|
18991
|
-
*/
|
|
18992
|
-
getConcurrencyInfo() {
|
|
18993
|
-
if (!this.concurrency) return "";
|
|
18994
|
-
const running = this.getRunningTasks();
|
|
18995
|
-
const queued = this.getQueuedTasks();
|
|
18996
|
-
const total = running.length;
|
|
18997
|
-
const limit = this.concurrency.getConcurrencyLimit("default");
|
|
18998
|
-
if (limit === Infinity) return "";
|
|
18999
|
-
const filled = TUI_BLOCKS.FILLED.repeat(total);
|
|
19000
|
-
const empty = TUI_BLOCKS.EMPTY.repeat(Math.max(0, limit - total));
|
|
19001
|
-
return ` [${filled}${empty} ${total}/${limit}]`;
|
|
19002
|
-
}
|
|
19003
|
-
/**
|
|
19004
|
-
* Build consolidated task list message
|
|
19005
|
-
*/
|
|
19006
|
-
buildTaskListMessage(newTask) {
|
|
19007
|
-
const running = this.getRunningTasks();
|
|
19008
|
-
const queued = this.getQueuedTasks();
|
|
19009
|
-
const concurrencyInfo = this.getConcurrencyInfo();
|
|
19010
|
-
const lines = [];
|
|
19011
|
-
if (running.length > 0) {
|
|
19012
|
-
lines.push(`${TUI_ICONS.RUNNING} Running (${running.length}) ${concurrencyInfo}`);
|
|
19013
|
-
for (const task of running) {
|
|
19014
|
-
const duration5 = this.formatDuration(task.startedAt);
|
|
19015
|
-
const bgTag = task.isBackground ? TUI_TAGS.BACKGROUND : TUI_TAGS.FOREGROUND;
|
|
19016
|
-
const isNew = newTask && task.id === newTask.id ? TUI_ICONS.NEW : "";
|
|
19017
|
-
lines.push(`${bgTag} ${task.description} (${task.agent}) - ${duration5}${isNew}`);
|
|
19018
|
-
}
|
|
19019
|
-
}
|
|
19020
|
-
if (queued.length > 0) {
|
|
19021
|
-
if (lines.length > 0) lines.push("");
|
|
19022
|
-
lines.push(`${TUI_ICONS.QUEUED} Queued (${queued.length}):`);
|
|
19023
|
-
for (const task of queued) {
|
|
19024
|
-
const bgTag = task.isBackground ? TUI_TAGS.WAITING : TUI_TAGS.PENDING;
|
|
19025
|
-
lines.push(`${bgTag} ${task.description} (${task.agent})`);
|
|
19026
|
-
}
|
|
19027
|
-
}
|
|
19028
|
-
return lines.join("\n");
|
|
19029
|
-
}
|
|
19030
|
-
/**
|
|
19031
|
-
* Show consolidated toast with all running/queued tasks
|
|
19032
|
-
*/
|
|
19033
|
-
showTaskListToast(newTask) {
|
|
19034
|
-
if (!this.client || !this.client.tui) return;
|
|
19035
|
-
const message = this.buildTaskListMessage(newTask);
|
|
19036
|
-
const running = this.getRunningTasks();
|
|
19037
|
-
const queued = this.getQueuedTasks();
|
|
19038
|
-
const title = newTask.isBackground ? `Background Task Started` : `Task Started`;
|
|
19039
|
-
this.client.tui.showToast({
|
|
19040
|
-
body: {
|
|
19041
|
-
title,
|
|
19042
|
-
message: message || `${newTask.description} (${newTask.agent})`,
|
|
19043
|
-
variant: STATUS_LABEL.INFO,
|
|
19044
|
-
duration: running.length + queued.length > 2 ? 5e3 : 3e3
|
|
19045
|
-
}
|
|
19046
|
-
}).catch(() => {
|
|
19047
|
-
});
|
|
19048
|
-
}
|
|
19049
|
-
/**
|
|
19050
|
-
* Show task completion toast
|
|
19051
|
-
*/
|
|
19052
|
-
showCompletionToast(info) {
|
|
19053
|
-
if (!this.client || !this.client.tui) return;
|
|
19054
|
-
this.removeTask(info.id);
|
|
19055
|
-
const remaining = this.getRunningTasks();
|
|
19056
|
-
const queued = this.getQueuedTasks();
|
|
19057
|
-
let message;
|
|
19058
|
-
let title;
|
|
19059
|
-
let variant;
|
|
19060
|
-
if (info.status === STATUS_LABEL.ERROR || info.status === STATUS_LABEL.CANCELLED || info.status === STATUS_LABEL.FAILED) {
|
|
19061
|
-
title = info.status === STATUS_LABEL.ERROR ? "Task Failed" : "Task Cancelled";
|
|
19062
|
-
message = `[FAIL] "${info.description}" ${info.status}
|
|
19063
|
-
${info.error || ""}`;
|
|
19064
|
-
variant = STATUS_LABEL.ERROR;
|
|
19065
|
-
} else {
|
|
19066
|
-
title = "Task Completed";
|
|
19067
|
-
message = `[DONE] "${info.description}" finished in ${info.duration}`;
|
|
19068
|
-
variant = STATUS_LABEL.SUCCESS;
|
|
19069
|
-
}
|
|
19070
|
-
if (remaining.length > 0 || queued.length > 0) {
|
|
19071
|
-
message += `
|
|
19072
|
-
|
|
19073
|
-
Still running: ${remaining.length} | Queued: ${queued.length}`;
|
|
19074
|
-
}
|
|
19075
|
-
this.client.tui.showToast({
|
|
19076
|
-
body: {
|
|
19077
|
-
title,
|
|
19078
|
-
message,
|
|
19079
|
-
variant,
|
|
19080
|
-
duration: 5e3
|
|
19081
|
-
}
|
|
19082
|
-
}).catch(() => {
|
|
19083
|
-
});
|
|
19084
|
-
}
|
|
19085
|
-
/**
|
|
19086
|
-
* Show all-tasks-complete summary toast
|
|
19087
|
-
*/
|
|
19088
|
-
showAllCompleteToast(parentSessionID, completedTasks) {
|
|
19089
|
-
if (!this.client || !this.client.tui) return;
|
|
19090
|
-
const successCount = completedTasks.filter((t) => t.status === STATUS_LABEL.COMPLETED).length;
|
|
19091
|
-
const failCount = completedTasks.filter((t) => t.status === STATUS_LABEL.ERROR || t.status === STATUS_LABEL.CANCELLED || t.status === STATUS_LABEL.FAILED).length;
|
|
19092
|
-
const taskList = completedTasks.map((t) => `- [${t.status === STATUS_LABEL.COMPLETED ? "OK" : "FAIL"}] ${t.description} (${t.duration})`).join("\n");
|
|
19093
|
-
this.client.tui.showToast({
|
|
19094
|
-
body: {
|
|
19095
|
-
title: "All Tasks Completed",
|
|
19096
|
-
message: `${successCount} succeeded, ${failCount} failed
|
|
19097
|
-
|
|
19098
|
-
${taskList}`,
|
|
19099
|
-
variant: failCount > 0 ? STATUS_LABEL.WARNING : STATUS_LABEL.SUCCESS,
|
|
19100
|
-
duration: 7e3
|
|
19101
|
-
}
|
|
19102
|
-
}).catch(() => {
|
|
19103
|
-
});
|
|
19104
|
-
}
|
|
19105
|
-
/**
|
|
19106
|
-
* Show Mission Complete toast (Grand Finale)
|
|
19107
|
-
*/
|
|
19108
|
-
showMissionCompleteToast(title = "Mission Complete", message = "All tasks completed successfully.") {
|
|
19109
|
-
if (!this.client || !this.client.tui) return;
|
|
19110
|
-
const decoratedMessage = `
|
|
19111
|
-
${TUI_ICONS.MISSION_COMPLETE} ${TUI_MESSAGES.MISSION_COMPLETE_TITLE} ${TUI_ICONS.MISSION_COMPLETE}
|
|
19112
|
-
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
19113
|
-
${message}
|
|
19114
|
-
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
19115
|
-
${TUI_MESSAGES.MISSION_COMPLETE_SUBTITLE}
|
|
19116
|
-
`.trim();
|
|
19117
|
-
this.client.tui.showToast({
|
|
19118
|
-
body: {
|
|
19119
|
-
title: `${TUI_ICONS.SHIELD} ${title}`,
|
|
19120
|
-
message: decoratedMessage,
|
|
19121
|
-
variant: STATUS_LABEL.SUCCESS,
|
|
19122
|
-
duration: 1e4
|
|
19123
|
-
// Longer duration for the finale
|
|
19124
|
-
}
|
|
19125
|
-
}).catch(() => {
|
|
19126
|
-
});
|
|
19127
|
-
}
|
|
19128
|
-
/**
|
|
19129
|
-
* Show progress toast (for long-running tasks)
|
|
19130
|
-
*/
|
|
19131
|
-
showProgressToast(taskId, progress) {
|
|
19132
|
-
if (!this.client || !this.client.tui) return;
|
|
19133
|
-
const task = this.tasks.get(taskId);
|
|
19134
|
-
if (!task) return;
|
|
19135
|
-
const percentage = Math.round(progress.current / progress.total * 100);
|
|
19136
|
-
const progressBar = `[${"#".repeat(Math.floor(percentage / 10))}${"-".repeat(10 - Math.floor(percentage / 10))}]`;
|
|
19137
|
-
this.client.tui.showToast({
|
|
19138
|
-
body: {
|
|
19139
|
-
title: `Task Progress: ${task.description}`,
|
|
19140
|
-
message: `${progressBar} ${percentage}%
|
|
19141
|
-
${progress.message || ""}`,
|
|
19142
|
-
variant: STATUS_LABEL.INFO,
|
|
19143
|
-
duration: 2e3
|
|
19144
|
-
}
|
|
19145
|
-
}).catch(() => {
|
|
19146
|
-
});
|
|
19147
|
-
}
|
|
19148
|
-
/**
|
|
19149
|
-
* Clear all tracked tasks
|
|
19150
|
-
*/
|
|
19151
|
-
clear() {
|
|
19152
|
-
this.tasks.clear();
|
|
19153
|
-
}
|
|
19154
|
-
/**
|
|
19155
|
-
* Get task count stats
|
|
19156
|
-
*/
|
|
19157
|
-
getStats() {
|
|
19158
|
-
const running = this.getRunningTasks().length;
|
|
19159
|
-
const queued = this.getQueuedTasks().length;
|
|
19160
|
-
return { running, queued, total: this.tasks.size };
|
|
19161
|
-
}
|
|
19162
|
-
};
|
|
19163
|
-
var instance = null;
|
|
19164
|
-
function getTaskToastManager() {
|
|
19165
|
-
return instance;
|
|
19166
|
-
}
|
|
19167
|
-
function initTaskToastManager(client2, concurrency) {
|
|
19168
|
-
if (!instance) {
|
|
19169
|
-
instance = new TaskToastManager();
|
|
19170
|
-
}
|
|
19171
|
-
instance.init(client2, concurrency);
|
|
19172
|
-
return instance;
|
|
19173
|
-
}
|
|
19174
18873
|
|
|
19175
18874
|
// src/core/agents/persistence/task-wal.ts
|
|
19176
18875
|
init_shared();
|
|
@@ -19266,130 +18965,1048 @@ var TaskWAL = class {
|
|
|
19266
18965
|
};
|
|
19267
18966
|
var taskWAL = new TaskWAL();
|
|
19268
18967
|
|
|
19269
|
-
// src/core/
|
|
18968
|
+
// src/core/notification/task-toast-manager.ts
|
|
19270
18969
|
init_shared();
|
|
19271
|
-
var
|
|
19272
|
-
|
|
19273
|
-
|
|
18970
|
+
var TaskToastManager = class {
|
|
18971
|
+
tasks = /* @__PURE__ */ new Map();
|
|
18972
|
+
client = null;
|
|
18973
|
+
concurrency = null;
|
|
18974
|
+
todoSync = null;
|
|
18975
|
+
/**
|
|
18976
|
+
* Initialize the manager with OpenCode client
|
|
18977
|
+
*/
|
|
18978
|
+
init(client2, concurrency) {
|
|
18979
|
+
this.client = client2;
|
|
18980
|
+
this.concurrency = concurrency ?? null;
|
|
18981
|
+
}
|
|
18982
|
+
/**
|
|
18983
|
+
* Set concurrency controller (can be set after init)
|
|
18984
|
+
*/
|
|
18985
|
+
setConcurrencyController(concurrency) {
|
|
18986
|
+
this.concurrency = concurrency;
|
|
18987
|
+
}
|
|
18988
|
+
/**
|
|
18989
|
+
* Set TodoSyncService for TUI status synchronization
|
|
18990
|
+
*/
|
|
18991
|
+
setTodoSync(service) {
|
|
18992
|
+
this.todoSync = service;
|
|
18993
|
+
}
|
|
18994
|
+
/**
|
|
18995
|
+
* Add a new task and show consolidated toast
|
|
18996
|
+
*/
|
|
18997
|
+
addTask(task) {
|
|
18998
|
+
const trackedTask = {
|
|
18999
|
+
id: task.id,
|
|
19000
|
+
description: task.description,
|
|
19001
|
+
agent: task.agent,
|
|
19002
|
+
status: task.status ?? STATUS_LABEL.RUNNING,
|
|
19003
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
19004
|
+
isBackground: task.isBackground,
|
|
19005
|
+
parentSessionID: task.parentSessionID,
|
|
19006
|
+
sessionID: task.sessionID
|
|
19007
|
+
};
|
|
19008
|
+
this.tasks.set(task.id, trackedTask);
|
|
19009
|
+
this.todoSync?.updateTaskStatus(trackedTask);
|
|
19010
|
+
this.showTaskListToast(trackedTask);
|
|
19011
|
+
}
|
|
19012
|
+
/**
|
|
19013
|
+
* Update task status
|
|
19014
|
+
*/
|
|
19015
|
+
updateTask(id, status) {
|
|
19016
|
+
const task = this.tasks.get(id);
|
|
19017
|
+
if (task) {
|
|
19018
|
+
task.status = status;
|
|
19019
|
+
this.todoSync?.updateTaskStatus(task);
|
|
19020
|
+
}
|
|
19021
|
+
}
|
|
19022
|
+
/**
|
|
19023
|
+
* Remove a task
|
|
19024
|
+
*/
|
|
19025
|
+
removeTask(id) {
|
|
19026
|
+
this.tasks.delete(id);
|
|
19027
|
+
this.todoSync?.removeTask(id);
|
|
19028
|
+
}
|
|
19029
|
+
/**
|
|
19030
|
+
* Get all running tasks (newest first)
|
|
19031
|
+
*/
|
|
19032
|
+
getRunningTasks() {
|
|
19033
|
+
return Array.from(this.tasks.values()).filter((t) => t.status === STATUS_LABEL.RUNNING).sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
19034
|
+
}
|
|
19035
|
+
/**
|
|
19036
|
+
* Get all queued tasks (oldest first - FIFO)
|
|
19037
|
+
*/
|
|
19038
|
+
getQueuedTasks() {
|
|
19039
|
+
return Array.from(this.tasks.values()).filter((t) => t.status === STATUS_LABEL.QUEUED).sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
|
19040
|
+
}
|
|
19041
|
+
/**
|
|
19042
|
+
* Get tasks by parent session
|
|
19043
|
+
*/
|
|
19044
|
+
getTasksByParent(parentSessionID) {
|
|
19045
|
+
return Array.from(this.tasks.values()).filter((t) => t.parentSessionID === parentSessionID);
|
|
19046
|
+
}
|
|
19047
|
+
/**
|
|
19048
|
+
* Format duration since task started
|
|
19049
|
+
*/
|
|
19050
|
+
formatDuration(startedAt) {
|
|
19051
|
+
const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
|
|
19052
|
+
if (seconds < 60) return `${seconds}s`;
|
|
19053
|
+
const minutes = Math.floor(seconds / 60);
|
|
19054
|
+
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
|
19055
|
+
const hours = Math.floor(minutes / 60);
|
|
19056
|
+
return `${hours}h ${minutes % 60}m`;
|
|
19057
|
+
}
|
|
19058
|
+
/**
|
|
19059
|
+
* Get concurrency info string (e.g., " [2/5]")
|
|
19060
|
+
*/
|
|
19061
|
+
/**
|
|
19062
|
+
* Get concurrency info string (e.g., " [2/5]")
|
|
19063
|
+
*/
|
|
19064
|
+
getConcurrencyInfo() {
|
|
19065
|
+
if (!this.concurrency) return "";
|
|
19066
|
+
const running = this.getRunningTasks();
|
|
19067
|
+
const queued = this.getQueuedTasks();
|
|
19068
|
+
const total = running.length;
|
|
19069
|
+
const limit = this.concurrency.getConcurrencyLimit("default");
|
|
19070
|
+
if (limit === Infinity) return "";
|
|
19071
|
+
const filled = TUI_BLOCKS.FILLED.repeat(total);
|
|
19072
|
+
const empty = TUI_BLOCKS.EMPTY.repeat(Math.max(0, limit - total));
|
|
19073
|
+
return ` [${filled}${empty} ${total}/${limit}]`;
|
|
19074
|
+
}
|
|
19075
|
+
/**
|
|
19076
|
+
* Build consolidated task list message
|
|
19077
|
+
*/
|
|
19078
|
+
buildTaskListMessage(newTask) {
|
|
19079
|
+
const running = this.getRunningTasks();
|
|
19080
|
+
const queued = this.getQueuedTasks();
|
|
19081
|
+
const concurrencyInfo = this.getConcurrencyInfo();
|
|
19082
|
+
const lines = [];
|
|
19083
|
+
if (running.length > 0) {
|
|
19084
|
+
lines.push(`${TUI_ICONS.RUNNING} Running (${running.length}) ${concurrencyInfo}`);
|
|
19085
|
+
for (const task of running) {
|
|
19086
|
+
const duration5 = this.formatDuration(task.startedAt);
|
|
19087
|
+
const bgTag = task.isBackground ? TUI_TAGS.BACKGROUND : TUI_TAGS.FOREGROUND;
|
|
19088
|
+
const isNew = newTask && task.id === newTask.id ? TUI_ICONS.NEW : "";
|
|
19089
|
+
lines.push(`${bgTag} ${task.description} (${task.agent}) - ${duration5}${isNew}`);
|
|
19090
|
+
}
|
|
19091
|
+
}
|
|
19092
|
+
if (queued.length > 0) {
|
|
19093
|
+
if (lines.length > 0) lines.push("");
|
|
19094
|
+
lines.push(`${TUI_ICONS.QUEUED} Queued (${queued.length}):`);
|
|
19095
|
+
for (const task of queued) {
|
|
19096
|
+
const bgTag = task.isBackground ? TUI_TAGS.WAITING : TUI_TAGS.PENDING;
|
|
19097
|
+
lines.push(`${bgTag} ${task.description} (${task.agent})`);
|
|
19098
|
+
}
|
|
19099
|
+
}
|
|
19100
|
+
return lines.join("\n");
|
|
19101
|
+
}
|
|
19102
|
+
/**
|
|
19103
|
+
* Show consolidated toast with all running/queued tasks
|
|
19104
|
+
*/
|
|
19105
|
+
showTaskListToast(newTask) {
|
|
19106
|
+
if (!this.client || !this.client.tui) return;
|
|
19107
|
+
const message = this.buildTaskListMessage(newTask);
|
|
19108
|
+
const running = this.getRunningTasks();
|
|
19109
|
+
const queued = this.getQueuedTasks();
|
|
19110
|
+
const title = newTask.isBackground ? `Background Task Started` : `Task Started`;
|
|
19111
|
+
this.client.tui.showToast({
|
|
19112
|
+
body: {
|
|
19113
|
+
title,
|
|
19114
|
+
message: message || `${newTask.description} (${newTask.agent})`,
|
|
19115
|
+
variant: STATUS_LABEL.INFO,
|
|
19116
|
+
duration: running.length + queued.length > 2 ? 5e3 : 3e3
|
|
19117
|
+
}
|
|
19118
|
+
}).catch(() => {
|
|
19119
|
+
});
|
|
19120
|
+
}
|
|
19121
|
+
/**
|
|
19122
|
+
* Show task completion toast
|
|
19123
|
+
*/
|
|
19124
|
+
showCompletionToast(info) {
|
|
19125
|
+
if (!this.client || !this.client.tui) return;
|
|
19126
|
+
this.removeTask(info.id);
|
|
19127
|
+
const remaining = this.getRunningTasks();
|
|
19128
|
+
const queued = this.getQueuedTasks();
|
|
19129
|
+
let message;
|
|
19130
|
+
let title;
|
|
19131
|
+
let variant;
|
|
19132
|
+
if (info.status === STATUS_LABEL.ERROR || info.status === STATUS_LABEL.CANCELLED || info.status === STATUS_LABEL.FAILED) {
|
|
19133
|
+
title = info.status === STATUS_LABEL.ERROR ? "Task Failed" : "Task Cancelled";
|
|
19134
|
+
message = `[FAIL] "${info.description}" ${info.status}
|
|
19135
|
+
${info.error || ""}`;
|
|
19136
|
+
variant = STATUS_LABEL.ERROR;
|
|
19137
|
+
} else {
|
|
19138
|
+
title = "Task Completed";
|
|
19139
|
+
message = `[DONE] "${info.description}" finished in ${info.duration}`;
|
|
19140
|
+
variant = STATUS_LABEL.SUCCESS;
|
|
19141
|
+
}
|
|
19142
|
+
if (remaining.length > 0 || queued.length > 0) {
|
|
19143
|
+
message += `
|
|
19274
19144
|
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
|
|
19145
|
+
Still running: ${remaining.length} | Queued: ${queued.length}`;
|
|
19146
|
+
}
|
|
19147
|
+
this.client.tui.showToast({
|
|
19148
|
+
body: {
|
|
19149
|
+
title,
|
|
19150
|
+
message,
|
|
19151
|
+
variant,
|
|
19152
|
+
duration: 5e3
|
|
19153
|
+
}
|
|
19154
|
+
}).catch(() => {
|
|
19155
|
+
});
|
|
19156
|
+
}
|
|
19157
|
+
/**
|
|
19158
|
+
* Show all-tasks-complete summary toast
|
|
19159
|
+
*/
|
|
19160
|
+
showAllCompleteToast(parentSessionID, completedTasks) {
|
|
19161
|
+
if (!this.client || !this.client.tui) return;
|
|
19162
|
+
const successCount = completedTasks.filter((t) => t.status === STATUS_LABEL.COMPLETED).length;
|
|
19163
|
+
const failCount = completedTasks.filter((t) => t.status === STATUS_LABEL.ERROR || t.status === STATUS_LABEL.CANCELLED || t.status === STATUS_LABEL.FAILED).length;
|
|
19164
|
+
const taskList = completedTasks.map((t) => `- [${t.status === STATUS_LABEL.COMPLETED ? "OK" : "FAIL"}] ${t.description} (${t.duration})`).join("\n");
|
|
19165
|
+
this.client.tui.showToast({
|
|
19166
|
+
body: {
|
|
19167
|
+
title: "All Tasks Completed",
|
|
19168
|
+
message: `${successCount} succeeded, ${failCount} failed
|
|
19278
19169
|
|
|
19279
|
-
|
|
19280
|
-
|
|
19281
|
-
|
|
19282
|
-
|
|
19283
|
-
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
|
|
19287
|
-
|
|
19288
|
-
|
|
19170
|
+
${taskList}`,
|
|
19171
|
+
variant: failCount > 0 ? STATUS_LABEL.WARNING : STATUS_LABEL.SUCCESS,
|
|
19172
|
+
duration: 7e3
|
|
19173
|
+
}
|
|
19174
|
+
}).catch(() => {
|
|
19175
|
+
});
|
|
19176
|
+
}
|
|
19177
|
+
/**
|
|
19178
|
+
* Show Mission Complete toast (Grand Finale)
|
|
19179
|
+
*/
|
|
19180
|
+
showMissionCompleteToast(title = "Mission Complete", message = "All tasks completed successfully.") {
|
|
19181
|
+
if (!this.client || !this.client.tui) return;
|
|
19182
|
+
const decoratedMessage = `
|
|
19183
|
+
${TUI_ICONS.MISSION_COMPLETE} ${TUI_MESSAGES.MISSION_COMPLETE_TITLE} ${TUI_ICONS.MISSION_COMPLETE}
|
|
19184
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
19185
|
+
${message}
|
|
19186
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
19187
|
+
${TUI_MESSAGES.MISSION_COMPLETE_SUBTITLE}
|
|
19188
|
+
`.trim();
|
|
19189
|
+
this.client.tui.showToast({
|
|
19190
|
+
body: {
|
|
19191
|
+
title: `${TUI_ICONS.SHIELD} ${title}`,
|
|
19192
|
+
message: decoratedMessage,
|
|
19193
|
+
variant: STATUS_LABEL.SUCCESS,
|
|
19194
|
+
duration: 1e4
|
|
19195
|
+
// Longer duration for the finale
|
|
19196
|
+
}
|
|
19197
|
+
}).catch(() => {
|
|
19198
|
+
});
|
|
19199
|
+
}
|
|
19200
|
+
/**
|
|
19201
|
+
* Show progress toast (for long-running tasks)
|
|
19202
|
+
*/
|
|
19203
|
+
showProgressToast(taskId, progress) {
|
|
19204
|
+
if (!this.client || !this.client.tui) return;
|
|
19205
|
+
const task = this.tasks.get(taskId);
|
|
19206
|
+
if (!task) return;
|
|
19207
|
+
const percentage = Math.round(progress.current / progress.total * 100);
|
|
19208
|
+
const progressBar = `[${"#".repeat(Math.floor(percentage / 10))}${"-".repeat(10 - Math.floor(percentage / 10))}]`;
|
|
19209
|
+
this.client.tui.showToast({
|
|
19210
|
+
body: {
|
|
19211
|
+
title: `Task Progress: ${task.description}`,
|
|
19212
|
+
message: `${progressBar} ${percentage}%
|
|
19213
|
+
${progress.message || ""}`,
|
|
19214
|
+
variant: STATUS_LABEL.INFO,
|
|
19215
|
+
duration: 2e3
|
|
19216
|
+
}
|
|
19217
|
+
}).catch(() => {
|
|
19218
|
+
});
|
|
19219
|
+
}
|
|
19220
|
+
/**
|
|
19221
|
+
* Clear all tracked tasks
|
|
19222
|
+
*/
|
|
19223
|
+
clear() {
|
|
19224
|
+
this.tasks.clear();
|
|
19225
|
+
}
|
|
19226
|
+
/**
|
|
19227
|
+
* Get task count stats
|
|
19228
|
+
*/
|
|
19229
|
+
getStats() {
|
|
19230
|
+
const running = this.getRunningTasks().length;
|
|
19231
|
+
const queued = this.getQueuedTasks().length;
|
|
19232
|
+
return { running, queued, total: this.tasks.size };
|
|
19233
|
+
}
|
|
19234
|
+
};
|
|
19235
|
+
var instance = null;
|
|
19236
|
+
function getTaskToastManager() {
|
|
19237
|
+
return instance;
|
|
19238
|
+
}
|
|
19239
|
+
function initTaskToastManager(client2, concurrency) {
|
|
19240
|
+
if (!instance) {
|
|
19241
|
+
instance = new TaskToastManager();
|
|
19242
|
+
}
|
|
19243
|
+
instance.init(client2, concurrency);
|
|
19244
|
+
return instance;
|
|
19245
|
+
}
|
|
19246
|
+
|
|
19247
|
+
// src/core/agents/unified-task-executor.ts
|
|
19248
|
+
init_shared();
|
|
19249
|
+
var UnifiedTaskExecutor = class {
|
|
19250
|
+
client;
|
|
19251
|
+
directory;
|
|
19252
|
+
store;
|
|
19253
|
+
concurrency;
|
|
19254
|
+
sessionPool;
|
|
19255
|
+
// Polling state
|
|
19256
|
+
pollInterval = null;
|
|
19257
|
+
isPolling = false;
|
|
19258
|
+
// Cleanup state
|
|
19259
|
+
cleanupTimers = /* @__PURE__ */ new Map();
|
|
19260
|
+
constructor(client2, directory, store, concurrency, sessionPool2) {
|
|
19261
|
+
this.client = client2;
|
|
19262
|
+
this.directory = directory;
|
|
19263
|
+
this.store = store;
|
|
19264
|
+
this.concurrency = concurrency;
|
|
19265
|
+
this.sessionPool = sessionPool2;
|
|
19266
|
+
}
|
|
19267
|
+
// ========================================================================
|
|
19268
|
+
// LAUNCH: Task Creation and Execution
|
|
19269
|
+
// ========================================================================
|
|
19270
|
+
/**
|
|
19271
|
+
* Launch a new parallel task
|
|
19272
|
+
*/
|
|
19273
|
+
async launch(input) {
|
|
19274
|
+
const depth = input.depth ?? 1;
|
|
19275
|
+
if (depth >= PARALLEL_TASK.MAX_DEPTH) {
|
|
19276
|
+
throw new Error(`Max depth ${PARALLEL_TASK.MAX_DEPTH} exceeded (current: ${depth})`);
|
|
19277
|
+
}
|
|
19278
|
+
const taskId = `task_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
19279
|
+
const concurrencyKey = input.agent;
|
|
19280
|
+
await this.concurrency.acquire(concurrencyKey);
|
|
19281
|
+
let sessionID;
|
|
19282
|
+
try {
|
|
19283
|
+
const session = await this.sessionPool.acquire(
|
|
19284
|
+
input.agent,
|
|
19285
|
+
input.parentSessionID,
|
|
19286
|
+
input.description
|
|
19287
|
+
);
|
|
19288
|
+
sessionID = session.id;
|
|
19289
|
+
const task = {
|
|
19290
|
+
id: taskId,
|
|
19291
|
+
sessionID,
|
|
19292
|
+
parentSessionID: input.parentSessionID,
|
|
19293
|
+
description: input.description,
|
|
19294
|
+
prompt: input.prompt,
|
|
19295
|
+
agent: input.agent,
|
|
19296
|
+
status: TASK_STATUS.RUNNING,
|
|
19297
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
19298
|
+
depth,
|
|
19299
|
+
concurrencyKey,
|
|
19300
|
+
mode: input.mode,
|
|
19301
|
+
groupID: input.groupID,
|
|
19302
|
+
progress: {
|
|
19303
|
+
toolCalls: 0,
|
|
19304
|
+
lastTool: void 0,
|
|
19305
|
+
lastUpdate: /* @__PURE__ */ new Date()
|
|
19306
|
+
}
|
|
19307
|
+
};
|
|
19308
|
+
this.store.set(taskId, task);
|
|
19309
|
+
this.store.trackPending(input.parentSessionID, taskId);
|
|
19310
|
+
await taskWAL.log(WAL_ACTIONS.LAUNCH, task);
|
|
19311
|
+
log(`[UnifiedExecutor] Task launched: ${taskId} (agent: ${input.agent}, depth: ${input.depth})`);
|
|
19312
|
+
const toastManager = getTaskToastManager();
|
|
19313
|
+
if (toastManager) {
|
|
19314
|
+
toastManager.addTask({
|
|
19315
|
+
id: taskId,
|
|
19316
|
+
description: input.description,
|
|
19317
|
+
agent: input.agent,
|
|
19318
|
+
isBackground: false,
|
|
19319
|
+
parentSessionID: input.parentSessionID,
|
|
19320
|
+
sessionID,
|
|
19321
|
+
status: TASK_STATUS.RUNNING
|
|
19322
|
+
});
|
|
19323
|
+
}
|
|
19324
|
+
await this.client.session.prompt({
|
|
19325
|
+
path: { id: sessionID },
|
|
19326
|
+
body: {
|
|
19327
|
+
agent: input.agent,
|
|
19328
|
+
tools: {},
|
|
19329
|
+
parts: [
|
|
19330
|
+
{
|
|
19331
|
+
type: PART_TYPES.TEXT,
|
|
19332
|
+
text: input.prompt
|
|
19333
|
+
}
|
|
19334
|
+
]
|
|
19335
|
+
}
|
|
19336
|
+
});
|
|
19337
|
+
this.startPolling();
|
|
19338
|
+
return task;
|
|
19339
|
+
} catch (error92) {
|
|
19340
|
+
if (sessionID) {
|
|
19341
|
+
await this.sessionPool.release(sessionID);
|
|
19342
|
+
}
|
|
19343
|
+
this.concurrency.release(concurrencyKey);
|
|
19344
|
+
log(`[UnifiedExecutor] Launch failed: ${error92}`);
|
|
19345
|
+
throw error92;
|
|
19289
19346
|
}
|
|
19290
|
-
}
|
|
19291
|
-
//
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19347
|
+
}
|
|
19348
|
+
// ========================================================================
|
|
19349
|
+
// POLLING: Monitor Task Execution
|
|
19350
|
+
// ========================================================================
|
|
19351
|
+
/**
|
|
19352
|
+
* Start polling for task updates
|
|
19353
|
+
*/
|
|
19354
|
+
startPolling() {
|
|
19355
|
+
if (this.isPolling) return;
|
|
19356
|
+
this.isPolling = true;
|
|
19357
|
+
this.pollInterval = setInterval(() => {
|
|
19358
|
+
this.pollTasks().catch((err) => {
|
|
19359
|
+
log(`[UnifiedExecutor] Poll error: ${err}`);
|
|
19360
|
+
});
|
|
19361
|
+
}, PARALLEL_TASK.POLL_INTERVAL_MS);
|
|
19362
|
+
this.pollInterval.unref?.();
|
|
19363
|
+
}
|
|
19364
|
+
/**
|
|
19365
|
+
* Stop polling
|
|
19366
|
+
*/
|
|
19367
|
+
stopPolling() {
|
|
19368
|
+
if (this.pollInterval) {
|
|
19369
|
+
clearInterval(this.pollInterval);
|
|
19370
|
+
this.pollInterval = null;
|
|
19298
19371
|
}
|
|
19299
|
-
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19372
|
+
this.isPolling = false;
|
|
19373
|
+
}
|
|
19374
|
+
/**
|
|
19375
|
+
* Poll all running tasks
|
|
19376
|
+
*/
|
|
19377
|
+
async pollTasks() {
|
|
19378
|
+
const runningTasks = this.store.getRunning();
|
|
19379
|
+
if (runningTasks.length === 0) {
|
|
19380
|
+
this.stopPolling();
|
|
19381
|
+
return;
|
|
19382
|
+
}
|
|
19383
|
+
for (const task of runningTasks) {
|
|
19384
|
+
await this.pollTask(task);
|
|
19385
|
+
}
|
|
19386
|
+
this.pruneExpiredTasks();
|
|
19387
|
+
}
|
|
19388
|
+
/**
|
|
19389
|
+
* Poll a single task
|
|
19390
|
+
*/
|
|
19391
|
+
async pollTask(task) {
|
|
19392
|
+
try {
|
|
19393
|
+
const result = await this.client.session.get({
|
|
19394
|
+
path: { id: task.sessionID }
|
|
19395
|
+
});
|
|
19396
|
+
if ("error" in result && result.error) {
|
|
19397
|
+
const errorMessage = "Session error";
|
|
19398
|
+
await this.handleTaskError(task.id, new Error(errorMessage));
|
|
19399
|
+
return;
|
|
19307
19400
|
}
|
|
19308
|
-
|
|
19401
|
+
const session = result.data;
|
|
19402
|
+
if (!session) {
|
|
19403
|
+
await this.handleTaskError(task.id, new Error("Session not found"));
|
|
19404
|
+
return;
|
|
19405
|
+
}
|
|
19406
|
+
const messageCount = session.messages?.length || 0;
|
|
19407
|
+
if (task.progress) {
|
|
19408
|
+
task.progress.lastUpdate = /* @__PURE__ */ new Date();
|
|
19409
|
+
}
|
|
19410
|
+
const isBusy = session.status === "busy" || session.busy;
|
|
19411
|
+
const hasOutput = messageCount > 1;
|
|
19412
|
+
if (!isBusy && hasOutput) {
|
|
19413
|
+
if (task.lastMsgCount === messageCount) {
|
|
19414
|
+
task.stablePolls = (task.stablePolls || 0) + 1;
|
|
19415
|
+
} else {
|
|
19416
|
+
task.stablePolls = 0;
|
|
19417
|
+
task.lastMsgCount = messageCount;
|
|
19418
|
+
}
|
|
19419
|
+
if (task.stablePolls >= PARALLEL_TASK.STABLE_POLLS_REQUIRED) {
|
|
19420
|
+
await this.handleTaskComplete(task);
|
|
19421
|
+
}
|
|
19422
|
+
} else {
|
|
19423
|
+
task.stablePolls = 0;
|
|
19424
|
+
}
|
|
19425
|
+
} catch (error92) {
|
|
19426
|
+
log(`[UnifiedExecutor] Poll task ${task.id} error: ${error92}`);
|
|
19309
19427
|
}
|
|
19310
|
-
}
|
|
19311
|
-
//
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19428
|
+
}
|
|
19429
|
+
// ========================================================================
|
|
19430
|
+
// COMPLETION: Handle Task Completion
|
|
19431
|
+
// ========================================================================
|
|
19432
|
+
/**
|
|
19433
|
+
* Handle task completion
|
|
19434
|
+
*/
|
|
19435
|
+
async handleTaskComplete(task) {
|
|
19436
|
+
log(`[UnifiedExecutor] Task complete: ${task.id}`);
|
|
19437
|
+
task.status = TASK_STATUS.COMPLETED;
|
|
19438
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
19439
|
+
await taskWAL.log(WAL_ACTIONS.UPDATE, task);
|
|
19440
|
+
await this.sessionPool.release(task.sessionID);
|
|
19441
|
+
this.concurrency.release(task.concurrencyKey || task.agent);
|
|
19442
|
+
this.concurrency.reportResult(task.concurrencyKey || task.agent, true);
|
|
19443
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
19444
|
+
const toastManager = getTaskToastManager();
|
|
19445
|
+
if (toastManager) {
|
|
19446
|
+
toastManager.updateTask(task.id, TASK_STATUS.COMPLETED);
|
|
19317
19447
|
}
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19448
|
+
this.scheduleCleanup(task.id);
|
|
19449
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
19450
|
+
}
|
|
19451
|
+
/**
|
|
19452
|
+
* Handle task error
|
|
19453
|
+
*/
|
|
19454
|
+
async handleTaskError(taskId, error92) {
|
|
19455
|
+
const task = this.store.get(taskId);
|
|
19456
|
+
if (!task) return;
|
|
19457
|
+
log(`[UnifiedExecutor] Task error: ${taskId} - ${error92.message}`);
|
|
19458
|
+
task.status = TASK_STATUS.ERROR;
|
|
19459
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
19460
|
+
task.error = error92.message;
|
|
19461
|
+
await taskWAL.log(WAL_ACTIONS.UPDATE, task);
|
|
19462
|
+
await this.sessionPool.release(task.sessionID);
|
|
19463
|
+
this.concurrency.release(task.concurrencyKey || task.agent);
|
|
19464
|
+
this.concurrency.reportResult(task.concurrencyKey || task.agent, false);
|
|
19465
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
19466
|
+
const toastManager = getTaskToastManager();
|
|
19467
|
+
if (toastManager) {
|
|
19468
|
+
toastManager.updateTask(task.id, TASK_STATUS.ERROR);
|
|
19325
19469
|
}
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19470
|
+
this.scheduleCleanup(task.id);
|
|
19471
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
19472
|
+
}
|
|
19473
|
+
// ========================================================================
|
|
19474
|
+
// CLEANUP: Task Cleanup and GC
|
|
19475
|
+
// ========================================================================
|
|
19476
|
+
/**
|
|
19477
|
+
* Schedule cleanup for a task
|
|
19478
|
+
*/
|
|
19479
|
+
scheduleCleanup(taskId) {
|
|
19480
|
+
const timer = setTimeout(() => {
|
|
19481
|
+
this.cleanupTask(taskId);
|
|
19482
|
+
this.cleanupTimers.delete(taskId);
|
|
19483
|
+
}, PARALLEL_TASK.CLEANUP_DELAY_MS);
|
|
19484
|
+
this.cleanupTimers.set(taskId, timer);
|
|
19485
|
+
}
|
|
19486
|
+
/**
|
|
19487
|
+
* Cleanup a task
|
|
19488
|
+
*/
|
|
19489
|
+
async cleanupTask(taskId) {
|
|
19490
|
+
const task = this.store.get(taskId);
|
|
19491
|
+
if (!task) return;
|
|
19492
|
+
log(`[UnifiedExecutor] Cleaning up task: ${taskId}`);
|
|
19493
|
+
this.store.delete(taskId);
|
|
19494
|
+
if (task) {
|
|
19495
|
+
await taskWAL.log(WAL_ACTIONS.DELETE, task);
|
|
19496
|
+
}
|
|
19497
|
+
const toastManager = getTaskToastManager();
|
|
19498
|
+
if (toastManager) {
|
|
19499
|
+
toastManager.removeTask(taskId);
|
|
19500
|
+
}
|
|
19501
|
+
}
|
|
19502
|
+
/**
|
|
19503
|
+
* Prune expired tasks
|
|
19504
|
+
*/
|
|
19505
|
+
pruneExpiredTasks() {
|
|
19506
|
+
const now = Date.now();
|
|
19507
|
+
const tasks = this.store.getAll();
|
|
19508
|
+
for (const task of tasks) {
|
|
19509
|
+
const age = now - task.startedAt.getTime();
|
|
19510
|
+
if (age > PARALLEL_TASK.TTL_MS) {
|
|
19511
|
+
log(`[UnifiedExecutor] Pruning expired task: ${task.id} (age: ${age}ms)`);
|
|
19512
|
+
this.handleTaskError(task.id, new Error("Task timeout"));
|
|
19334
19513
|
}
|
|
19335
|
-
return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
|
|
19336
19514
|
}
|
|
19337
|
-
}
|
|
19338
|
-
//
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19515
|
+
}
|
|
19516
|
+
// ========================================================================
|
|
19517
|
+
// NOTIFICATION: Parent Notification
|
|
19518
|
+
// ========================================================================
|
|
19519
|
+
/**
|
|
19520
|
+
* Notify parent if all tasks complete
|
|
19521
|
+
*/
|
|
19522
|
+
async notifyParentIfAllComplete(parentSessionID) {
|
|
19523
|
+
const pendingCount = this.store.getPendingCount(parentSessionID);
|
|
19524
|
+
if (pendingCount === 0) {
|
|
19525
|
+
log(`[UnifiedExecutor] All tasks complete for parent: ${parentSessionID}`);
|
|
19526
|
+
const tasks = this.store.getByParent(parentSessionID);
|
|
19527
|
+
const summary = tasks.map(
|
|
19528
|
+
(t) => `- ${t.status === TASK_STATUS.COMPLETED ? "\u2705" : "\u274C"} ${t.description}`
|
|
19529
|
+
).join("\n");
|
|
19530
|
+
try {
|
|
19531
|
+
await this.client.session.prompt({
|
|
19532
|
+
path: { id: parentSessionID },
|
|
19533
|
+
body: {
|
|
19534
|
+
agent: "",
|
|
19535
|
+
tools: {},
|
|
19536
|
+
parts: [{
|
|
19537
|
+
type: PART_TYPES.TEXT,
|
|
19538
|
+
text: `All delegated tasks complete:
|
|
19539
|
+
|
|
19540
|
+
${summary}`
|
|
19541
|
+
}]
|
|
19542
|
+
}
|
|
19543
|
+
});
|
|
19544
|
+
} catch (error92) {
|
|
19545
|
+
log(`[UnifiedExecutor] Failed to notify parent: ${error92}`);
|
|
19546
|
+
}
|
|
19547
|
+
this.store.clearNotifications(parentSessionID);
|
|
19345
19548
|
}
|
|
19346
|
-
}
|
|
19347
|
-
//
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19549
|
+
}
|
|
19550
|
+
// ========================================================================
|
|
19551
|
+
// RESUME: Task Recovery
|
|
19552
|
+
// ========================================================================
|
|
19553
|
+
/**
|
|
19554
|
+
* Resume a task from disk
|
|
19555
|
+
*/
|
|
19556
|
+
async resume(input) {
|
|
19557
|
+
log(`[UnifiedExecutor] Resuming task: ${input.sessionId}`);
|
|
19558
|
+
const tasks = await taskWAL.readAll();
|
|
19559
|
+
const task = tasks.get(input.sessionId);
|
|
19560
|
+
if (!task) {
|
|
19561
|
+
log(`[UnifiedExecutor] No task found to resume: ${input.sessionId}`);
|
|
19562
|
+
return null;
|
|
19353
19563
|
}
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19564
|
+
this.store.set(task.id, task);
|
|
19565
|
+
this.store.trackPending(task.parentSessionID, task.id);
|
|
19566
|
+
this.startPolling();
|
|
19567
|
+
log(`[UnifiedExecutor] Task resumed: ${task.id}`);
|
|
19568
|
+
return task;
|
|
19569
|
+
}
|
|
19570
|
+
/**
|
|
19571
|
+
* Recover all active tasks from disk
|
|
19572
|
+
*/
|
|
19573
|
+
async recoverAll() {
|
|
19574
|
+
log("[UnifiedExecutor] Recovering tasks from WAL...");
|
|
19575
|
+
const tasks = await taskWAL.readAll();
|
|
19576
|
+
let recovered = 0;
|
|
19577
|
+
for (const [sessionID, task] of tasks) {
|
|
19578
|
+
if (task.status === TASK_STATUS.RUNNING || task.status === TASK_STATUS.PENDING) {
|
|
19579
|
+
try {
|
|
19580
|
+
const result = await this.client.session.get({ path: { id: sessionID } });
|
|
19581
|
+
if (result.data) {
|
|
19582
|
+
this.store.set(task.id, task);
|
|
19583
|
+
this.store.trackPending(task.parentSessionID, task.id);
|
|
19584
|
+
recovered++;
|
|
19585
|
+
}
|
|
19586
|
+
} catch {
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19361
19589
|
}
|
|
19590
|
+
if (recovered > 0) {
|
|
19591
|
+
log(`[UnifiedExecutor] Recovered ${recovered} tasks`);
|
|
19592
|
+
this.startPolling();
|
|
19593
|
+
}
|
|
19594
|
+
return recovered;
|
|
19362
19595
|
}
|
|
19363
|
-
|
|
19596
|
+
// ========================================================================
|
|
19597
|
+
// UTILITIES
|
|
19598
|
+
// ========================================================================
|
|
19599
|
+
/**
|
|
19600
|
+
* Cancel a task
|
|
19601
|
+
*/
|
|
19602
|
+
async cancel(taskId) {
|
|
19603
|
+
const task = this.store.get(taskId);
|
|
19604
|
+
if (!task) return false;
|
|
19605
|
+
log(`[UnifiedExecutor] Cancelling task: ${taskId}`);
|
|
19606
|
+
try {
|
|
19607
|
+
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
19608
|
+
} catch {
|
|
19609
|
+
}
|
|
19610
|
+
await this.handleTaskError(taskId, new Error("Cancelled by user"));
|
|
19611
|
+
return true;
|
|
19612
|
+
}
|
|
19613
|
+
/**
|
|
19614
|
+
* Get task by ID
|
|
19615
|
+
*/
|
|
19616
|
+
getTask(taskId) {
|
|
19617
|
+
return this.store.get(taskId);
|
|
19618
|
+
}
|
|
19619
|
+
/**
|
|
19620
|
+
* Get all tasks
|
|
19621
|
+
*/
|
|
19622
|
+
getAllTasks() {
|
|
19623
|
+
return this.store.getAll();
|
|
19624
|
+
}
|
|
19625
|
+
/**
|
|
19626
|
+
* Get tasks by parent
|
|
19627
|
+
*/
|
|
19628
|
+
getTasksByParent(parentSessionID) {
|
|
19629
|
+
return this.store.getByParent(parentSessionID);
|
|
19630
|
+
}
|
|
19631
|
+
/**
|
|
19632
|
+
* Cleanup all resources
|
|
19633
|
+
*/
|
|
19634
|
+
cleanup() {
|
|
19635
|
+
log("[UnifiedExecutor] Cleaning up...");
|
|
19636
|
+
this.stopPolling();
|
|
19637
|
+
for (const timer of this.cleanupTimers.values()) {
|
|
19638
|
+
clearTimeout(timer);
|
|
19639
|
+
}
|
|
19640
|
+
this.cleanupTimers.clear();
|
|
19641
|
+
this.pruneExpiredTasks();
|
|
19642
|
+
}
|
|
19643
|
+
};
|
|
19364
19644
|
|
|
19365
|
-
// src/core/
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19645
|
+
// src/core/agents/session-pool.ts
|
|
19646
|
+
init_shared();
|
|
19647
|
+
var DEFAULT_CONFIG = {
|
|
19648
|
+
maxPoolSizePerAgent: 5,
|
|
19649
|
+
idleTimeoutMs: 3e5,
|
|
19650
|
+
// 5 minutes
|
|
19651
|
+
maxReuseCount: 10,
|
|
19652
|
+
healthCheckIntervalMs: 6e4
|
|
19653
|
+
// 1 minute
|
|
19654
|
+
};
|
|
19655
|
+
var SessionPool = class _SessionPool {
|
|
19656
|
+
static _instance;
|
|
19657
|
+
pool = /* @__PURE__ */ new Map();
|
|
19658
|
+
// key: agentName
|
|
19659
|
+
sessionsById = /* @__PURE__ */ new Map();
|
|
19660
|
+
config;
|
|
19661
|
+
client;
|
|
19662
|
+
directory;
|
|
19663
|
+
healthCheckInterval = null;
|
|
19664
|
+
// Statistics
|
|
19665
|
+
stats = {
|
|
19666
|
+
reuseHits: 0,
|
|
19667
|
+
creationMisses: 0
|
|
19668
|
+
};
|
|
19669
|
+
constructor(client2, directory, config3 = {}) {
|
|
19670
|
+
this.client = client2;
|
|
19671
|
+
this.directory = directory;
|
|
19672
|
+
this.config = { ...DEFAULT_CONFIG, ...config3 };
|
|
19673
|
+
this.startHealthCheck();
|
|
19674
|
+
}
|
|
19675
|
+
static getInstance(client2, directory, config3) {
|
|
19676
|
+
if (!_SessionPool._instance) {
|
|
19677
|
+
if (!client2 || !directory) {
|
|
19678
|
+
throw new Error("SessionPool requires client and directory on first call");
|
|
19380
19679
|
}
|
|
19381
|
-
|
|
19680
|
+
_SessionPool._instance = new _SessionPool(client2, directory, config3);
|
|
19382
19681
|
}
|
|
19682
|
+
return _SessionPool._instance;
|
|
19383
19683
|
}
|
|
19384
|
-
|
|
19684
|
+
/**
|
|
19685
|
+
* Acquire a session from the pool or create a new one.
|
|
19686
|
+
* Sessions are validated before reuse to ensure health.
|
|
19687
|
+
*/
|
|
19688
|
+
async acquire(agentName, parentSessionID, description) {
|
|
19689
|
+
const poolKey = this.getPoolKey(agentName);
|
|
19690
|
+
const agentPool = this.pool.get(poolKey) || [];
|
|
19691
|
+
const candidates = agentPool.filter((s) => !s.inUse && s.reuseCount < this.config.maxReuseCount);
|
|
19692
|
+
for (const candidate of candidates) {
|
|
19693
|
+
const isHealthy = await this.validateSessionHealth(candidate.id);
|
|
19694
|
+
if (isHealthy) {
|
|
19695
|
+
candidate.inUse = true;
|
|
19696
|
+
candidate.lastUsedAt = /* @__PURE__ */ new Date();
|
|
19697
|
+
candidate.reuseCount++;
|
|
19698
|
+
candidate.health = "healthy";
|
|
19699
|
+
this.stats.reuseHits++;
|
|
19700
|
+
log(`[SessionPool] Reusing session ${candidate.id.slice(0, 8)}... for ${agentName} (reuse #${candidate.reuseCount})`);
|
|
19701
|
+
return candidate;
|
|
19702
|
+
} else {
|
|
19703
|
+
log(`[SessionPool] Session ${candidate.id.slice(0, 8)}... failed health check, removing`);
|
|
19704
|
+
await this.deleteSession(candidate.id);
|
|
19705
|
+
}
|
|
19706
|
+
}
|
|
19707
|
+
this.stats.creationMisses++;
|
|
19708
|
+
return this.createSession(agentName, parentSessionID, description);
|
|
19709
|
+
}
|
|
19710
|
+
/**
|
|
19711
|
+
* Release a session back to the pool for reuse.
|
|
19712
|
+
*/
|
|
19713
|
+
async release(sessionId) {
|
|
19714
|
+
const session = this.sessionsById.get(sessionId);
|
|
19715
|
+
if (!session) {
|
|
19716
|
+
log(`[SessionPool] Session ${sessionId.slice(0, 8)}... not found in pool`);
|
|
19717
|
+
return;
|
|
19718
|
+
}
|
|
19719
|
+
const now = Date.now();
|
|
19720
|
+
const age = now - session.createdAt.getTime();
|
|
19721
|
+
const idle = now - session.lastUsedAt.getTime();
|
|
19722
|
+
if (session.reuseCount >= this.config.maxReuseCount || age > this.config.idleTimeoutMs * 2) {
|
|
19723
|
+
await this.invalidate(sessionId);
|
|
19724
|
+
return;
|
|
19725
|
+
}
|
|
19726
|
+
const poolKey = this.getPoolKey(session.agentName);
|
|
19727
|
+
const agentPool = this.pool.get(poolKey) || [];
|
|
19728
|
+
const availableCount = agentPool.filter((s) => !s.inUse).length;
|
|
19729
|
+
if (availableCount >= this.config.maxPoolSizePerAgent) {
|
|
19730
|
+
const oldest = agentPool.filter((s) => !s.inUse).sort((a, b) => a.lastUsedAt.getTime() - b.lastUsedAt.getTime())[0];
|
|
19731
|
+
if (oldest) {
|
|
19732
|
+
await this.deleteSession(oldest.id);
|
|
19733
|
+
}
|
|
19734
|
+
}
|
|
19735
|
+
await this.resetSession(sessionId);
|
|
19736
|
+
session.inUse = false;
|
|
19737
|
+
log(`[SessionPool] Released session ${sessionId.slice(0, 8)}... to pool`);
|
|
19738
|
+
}
|
|
19739
|
+
/**
|
|
19740
|
+
* Invalidate a session (remove from pool and delete).
|
|
19741
|
+
*/
|
|
19742
|
+
async invalidate(sessionId) {
|
|
19743
|
+
const session = this.sessionsById.get(sessionId);
|
|
19744
|
+
if (!session) return;
|
|
19745
|
+
await this.deleteSession(sessionId);
|
|
19746
|
+
log(`[SessionPool] Invalidated session ${sessionId.slice(0, 8)}...`);
|
|
19747
|
+
}
|
|
19748
|
+
/**
|
|
19749
|
+
* Get current pool statistics.
|
|
19750
|
+
*/
|
|
19751
|
+
getStats() {
|
|
19752
|
+
const byAgent = {};
|
|
19753
|
+
for (const [agentName, sessions] of this.pool.entries()) {
|
|
19754
|
+
const inUse = sessions.filter((s) => s.inUse).length;
|
|
19755
|
+
byAgent[agentName] = {
|
|
19756
|
+
total: sessions.length,
|
|
19757
|
+
inUse,
|
|
19758
|
+
available: sessions.length - inUse
|
|
19759
|
+
};
|
|
19760
|
+
}
|
|
19761
|
+
const allSessions = Array.from(this.sessionsById.values());
|
|
19762
|
+
const inUseCount = allSessions.filter((s) => s.inUse).length;
|
|
19385
19763
|
return {
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19764
|
+
totalSessions: allSessions.length,
|
|
19765
|
+
sessionsInUse: inUseCount,
|
|
19766
|
+
availableSessions: allSessions.length - inUseCount,
|
|
19767
|
+
reuseHits: this.stats.reuseHits,
|
|
19768
|
+
creationMisses: this.stats.creationMisses,
|
|
19769
|
+
byAgent
|
|
19389
19770
|
};
|
|
19390
19771
|
}
|
|
19391
|
-
|
|
19392
|
-
|
|
19772
|
+
/**
|
|
19773
|
+
* Cleanup stale sessions.
|
|
19774
|
+
*/
|
|
19775
|
+
async cleanup() {
|
|
19776
|
+
const now = Date.now();
|
|
19777
|
+
let cleanedCount = 0;
|
|
19778
|
+
for (const [sessionId, session] of this.sessionsById.entries()) {
|
|
19779
|
+
if (session.inUse) continue;
|
|
19780
|
+
const idle = now - session.lastUsedAt.getTime();
|
|
19781
|
+
if (idle > this.config.idleTimeoutMs) {
|
|
19782
|
+
await this.deleteSession(sessionId);
|
|
19783
|
+
cleanedCount++;
|
|
19784
|
+
}
|
|
19785
|
+
}
|
|
19786
|
+
if (cleanedCount > 0) {
|
|
19787
|
+
log(`[SessionPool] Cleaned up ${cleanedCount} stale sessions`);
|
|
19788
|
+
}
|
|
19789
|
+
return cleanedCount;
|
|
19790
|
+
}
|
|
19791
|
+
/**
|
|
19792
|
+
* Shutdown the pool.
|
|
19793
|
+
*/
|
|
19794
|
+
async shutdown() {
|
|
19795
|
+
log("[SessionPool] Shutting down...");
|
|
19796
|
+
if (this.healthCheckInterval) {
|
|
19797
|
+
clearInterval(this.healthCheckInterval);
|
|
19798
|
+
this.healthCheckInterval = null;
|
|
19799
|
+
}
|
|
19800
|
+
const deletePromises = Array.from(this.sessionsById.keys()).map(
|
|
19801
|
+
(id) => this.deleteSession(id).catch(() => {
|
|
19802
|
+
})
|
|
19803
|
+
);
|
|
19804
|
+
await Promise.all(deletePromises);
|
|
19805
|
+
this.pool.clear();
|
|
19806
|
+
this.sessionsById.clear();
|
|
19807
|
+
log("[SessionPool] Shutdown complete");
|
|
19808
|
+
}
|
|
19809
|
+
// =========================================================================
|
|
19810
|
+
// Private Methods
|
|
19811
|
+
// =========================================================================
|
|
19812
|
+
/**
|
|
19813
|
+
* Reset/Compact a session to clear context for next reuse.
|
|
19814
|
+
*/
|
|
19815
|
+
async resetSession(sessionId) {
|
|
19816
|
+
const session = this.sessionsById.get(sessionId);
|
|
19817
|
+
if (!session) return;
|
|
19818
|
+
log(`[SessionPool] Resetting session ${sessionId.slice(0, 8)}...`);
|
|
19819
|
+
try {
|
|
19820
|
+
await this.client.session.compact?.({ path: { id: sessionId } });
|
|
19821
|
+
session.lastResetAt = /* @__PURE__ */ new Date();
|
|
19822
|
+
session.health = "healthy";
|
|
19823
|
+
} catch (error92) {
|
|
19824
|
+
log(`[SessionPool] Failed to reset session ${sessionId.slice(0, 8)}: ${error92}`);
|
|
19825
|
+
session.health = "degraded";
|
|
19826
|
+
}
|
|
19827
|
+
}
|
|
19828
|
+
getPoolKey(agentName) {
|
|
19829
|
+
return agentName;
|
|
19830
|
+
}
|
|
19831
|
+
async createSession(agentName, parentSessionID, description) {
|
|
19832
|
+
log(`[SessionPool] Creating new session for ${agentName}`);
|
|
19833
|
+
const result = await Promise.race([
|
|
19834
|
+
this.client.session.create({
|
|
19835
|
+
body: {
|
|
19836
|
+
parentID: parentSessionID,
|
|
19837
|
+
title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${description}`
|
|
19838
|
+
},
|
|
19839
|
+
query: { directory: this.directory }
|
|
19840
|
+
}),
|
|
19841
|
+
new Promise(
|
|
19842
|
+
(_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 60s")), 6e4)
|
|
19843
|
+
)
|
|
19844
|
+
]);
|
|
19845
|
+
if (result.error || !result.data?.id) {
|
|
19846
|
+
throw new Error(`Session creation failed: ${result.error || "No ID"}`);
|
|
19847
|
+
}
|
|
19848
|
+
const session = {
|
|
19849
|
+
id: result.data.id,
|
|
19850
|
+
agentName,
|
|
19851
|
+
projectDirectory: this.directory,
|
|
19852
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
19853
|
+
lastUsedAt: /* @__PURE__ */ new Date(),
|
|
19854
|
+
reuseCount: 0,
|
|
19855
|
+
inUse: true,
|
|
19856
|
+
health: "healthy",
|
|
19857
|
+
lastResetAt: /* @__PURE__ */ new Date()
|
|
19858
|
+
};
|
|
19859
|
+
const poolKey = this.getPoolKey(agentName);
|
|
19860
|
+
const agentPool = this.pool.get(poolKey) || [];
|
|
19861
|
+
agentPool.push(session);
|
|
19862
|
+
this.pool.set(poolKey, agentPool);
|
|
19863
|
+
this.sessionsById.set(session.id, session);
|
|
19864
|
+
return session;
|
|
19865
|
+
}
|
|
19866
|
+
async deleteSession(sessionId) {
|
|
19867
|
+
const session = this.sessionsById.get(sessionId);
|
|
19868
|
+
if (!session) return;
|
|
19869
|
+
this.sessionsById.delete(sessionId);
|
|
19870
|
+
const poolKey = this.getPoolKey(session.agentName);
|
|
19871
|
+
const agentPool = this.pool.get(poolKey);
|
|
19872
|
+
if (agentPool) {
|
|
19873
|
+
const idx = agentPool.findIndex((s) => s.id === sessionId);
|
|
19874
|
+
if (idx !== -1) {
|
|
19875
|
+
agentPool.splice(idx, 1);
|
|
19876
|
+
}
|
|
19877
|
+
if (agentPool.length === 0) {
|
|
19878
|
+
this.pool.delete(poolKey);
|
|
19879
|
+
}
|
|
19880
|
+
}
|
|
19881
|
+
try {
|
|
19882
|
+
await this.client.session.delete({ path: { id: sessionId } });
|
|
19883
|
+
} catch {
|
|
19884
|
+
}
|
|
19885
|
+
}
|
|
19886
|
+
/**
|
|
19887
|
+
* Validate session health by checking if it exists on the server.
|
|
19888
|
+
*/
|
|
19889
|
+
async validateSessionHealth(sessionId) {
|
|
19890
|
+
try {
|
|
19891
|
+
const result = await Promise.race([
|
|
19892
|
+
this.client.session.get({ path: { id: sessionId } }),
|
|
19893
|
+
new Promise(
|
|
19894
|
+
(_, reject) => setTimeout(() => reject(new Error("Health check timeout")), 5e3)
|
|
19895
|
+
)
|
|
19896
|
+
]);
|
|
19897
|
+
if (result.error || !result.data) {
|
|
19898
|
+
return false;
|
|
19899
|
+
}
|
|
19900
|
+
return true;
|
|
19901
|
+
} catch (error92) {
|
|
19902
|
+
log(`[SessionPool] Health check failed for session ${sessionId.slice(0, 8)}: ${error92}`);
|
|
19903
|
+
return false;
|
|
19904
|
+
}
|
|
19905
|
+
}
|
|
19906
|
+
startHealthCheck() {
|
|
19907
|
+
this.healthCheckInterval = setInterval(() => {
|
|
19908
|
+
this.cleanup().catch(() => {
|
|
19909
|
+
});
|
|
19910
|
+
}, this.config.healthCheckIntervalMs);
|
|
19911
|
+
this.healthCheckInterval.unref?.();
|
|
19912
|
+
}
|
|
19913
|
+
};
|
|
19914
|
+
var sessionPool = {
|
|
19915
|
+
getInstance: SessionPool.getInstance.bind(SessionPool)
|
|
19916
|
+
};
|
|
19917
|
+
|
|
19918
|
+
// src/core/progress/state-broadcaster.ts
|
|
19919
|
+
var StateBroadcaster = class _StateBroadcaster {
|
|
19920
|
+
static _instance;
|
|
19921
|
+
listeners = /* @__PURE__ */ new Set();
|
|
19922
|
+
currentState = null;
|
|
19923
|
+
constructor() {
|
|
19924
|
+
}
|
|
19925
|
+
static getInstance() {
|
|
19926
|
+
if (!_StateBroadcaster._instance) {
|
|
19927
|
+
_StateBroadcaster._instance = new _StateBroadcaster();
|
|
19928
|
+
}
|
|
19929
|
+
return _StateBroadcaster._instance;
|
|
19930
|
+
}
|
|
19931
|
+
subscribe(listener) {
|
|
19932
|
+
this.listeners.add(listener);
|
|
19933
|
+
if (this.currentState) {
|
|
19934
|
+
listener(this.currentState);
|
|
19935
|
+
}
|
|
19936
|
+
return () => this.listeners.delete(listener);
|
|
19937
|
+
}
|
|
19938
|
+
broadcast(state2) {
|
|
19939
|
+
this.currentState = state2;
|
|
19940
|
+
this.listeners.forEach((listener) => {
|
|
19941
|
+
try {
|
|
19942
|
+
listener(state2);
|
|
19943
|
+
} catch (error92) {
|
|
19944
|
+
}
|
|
19945
|
+
});
|
|
19946
|
+
}
|
|
19947
|
+
getCurrentState() {
|
|
19948
|
+
return this.currentState;
|
|
19949
|
+
}
|
|
19950
|
+
};
|
|
19951
|
+
var stateBroadcaster = StateBroadcaster.getInstance();
|
|
19952
|
+
|
|
19953
|
+
// src/core/progress/progress-notifier.ts
|
|
19954
|
+
init_shared();
|
|
19955
|
+
var ProgressNotifier = class _ProgressNotifier {
|
|
19956
|
+
static _instance;
|
|
19957
|
+
manager = null;
|
|
19958
|
+
constructor() {
|
|
19959
|
+
stateBroadcaster.subscribe(this.handleStateChange.bind(this));
|
|
19960
|
+
}
|
|
19961
|
+
static getInstance() {
|
|
19962
|
+
if (!_ProgressNotifier._instance) {
|
|
19963
|
+
_ProgressNotifier._instance = new _ProgressNotifier();
|
|
19964
|
+
}
|
|
19965
|
+
return _ProgressNotifier._instance;
|
|
19966
|
+
}
|
|
19967
|
+
setManager(manager) {
|
|
19968
|
+
this.manager = manager;
|
|
19969
|
+
}
|
|
19970
|
+
/**
|
|
19971
|
+
* Poll current status from ParallelAgentManager and broadcast it
|
|
19972
|
+
*/
|
|
19973
|
+
update() {
|
|
19974
|
+
if (!this.manager) return;
|
|
19975
|
+
const tasks = this.manager.getAllTasks();
|
|
19976
|
+
const running = tasks.filter((t) => t.status === TASK_STATUS.RUNNING);
|
|
19977
|
+
const completed = tasks.filter((t) => t.status === TASK_STATUS.COMPLETED);
|
|
19978
|
+
const total = tasks.length;
|
|
19979
|
+
const percentage = total > 0 ? Math.round(completed.length / total * 100) : 0;
|
|
19980
|
+
const state2 = {
|
|
19981
|
+
missionId: "current-mission",
|
|
19982
|
+
// Could be dynamic
|
|
19983
|
+
status: percentage === 100 ? "completed" : "executing",
|
|
19984
|
+
progress: {
|
|
19985
|
+
totalTasks: total,
|
|
19986
|
+
completedTasks: completed.length,
|
|
19987
|
+
percentage
|
|
19988
|
+
},
|
|
19989
|
+
activeAgents: running.map((t) => ({
|
|
19990
|
+
id: t.id,
|
|
19991
|
+
type: t.agent,
|
|
19992
|
+
status: t.status,
|
|
19993
|
+
currentTask: t.description
|
|
19994
|
+
})),
|
|
19995
|
+
todo: [],
|
|
19996
|
+
// Need to fetch from TodoEnforcer if possible
|
|
19997
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
19998
|
+
};
|
|
19999
|
+
stateBroadcaster.broadcast(state2);
|
|
20000
|
+
}
|
|
20001
|
+
handleStateChange(state2) {
|
|
20002
|
+
if (state2.progress.percentage > 0 && state2.progress.percentage % 25 === 0) {
|
|
20003
|
+
const toastManager = getTaskToastManager();
|
|
20004
|
+
if (toastManager) {
|
|
20005
|
+
}
|
|
20006
|
+
}
|
|
20007
|
+
}
|
|
20008
|
+
};
|
|
20009
|
+
var progressNotifier = ProgressNotifier.getInstance();
|
|
19393
20010
|
|
|
19394
20011
|
// src/core/memory/interfaces.ts
|
|
19395
20012
|
var MemoryLevel = /* @__PURE__ */ ((MemoryLevel2) => {
|
|
@@ -19523,6 +20140,9 @@ var MemoryManager = class _MemoryManager {
|
|
|
19523
20140
|
}
|
|
19524
20141
|
};
|
|
19525
20142
|
|
|
20143
|
+
// src/core/agents/manager.ts
|
|
20144
|
+
init_core2();
|
|
20145
|
+
|
|
19526
20146
|
// src/core/agents/agent-registry.ts
|
|
19527
20147
|
init_shared();
|
|
19528
20148
|
import * as fs4 from "fs/promises";
|
|
@@ -33175,1213 +33795,214 @@ function convertBaseSchema(schema, ctx) {
|
|
|
33175
33795
|
arraySchema = arraySchema.min(schema.minItems);
|
|
33176
33796
|
}
|
|
33177
33797
|
if (typeof schema.maxItems === "number") {
|
|
33178
|
-
arraySchema = arraySchema.max(schema.maxItems);
|
|
33179
|
-
}
|
|
33180
|
-
zodSchema = arraySchema;
|
|
33181
|
-
} else {
|
|
33182
|
-
zodSchema = z.array(z.any());
|
|
33183
|
-
}
|
|
33184
|
-
break;
|
|
33185
|
-
}
|
|
33186
|
-
default:
|
|
33187
|
-
throw new Error(`Unsupported type: ${type}`);
|
|
33188
|
-
}
|
|
33189
|
-
if (schema.description) {
|
|
33190
|
-
zodSchema = zodSchema.describe(schema.description);
|
|
33191
|
-
}
|
|
33192
|
-
if (schema.default !== void 0) {
|
|
33193
|
-
zodSchema = zodSchema.default(schema.default);
|
|
33194
|
-
}
|
|
33195
|
-
return zodSchema;
|
|
33196
|
-
}
|
|
33197
|
-
function convertSchema(schema, ctx) {
|
|
33198
|
-
if (typeof schema === "boolean") {
|
|
33199
|
-
return schema ? z.any() : z.never();
|
|
33200
|
-
}
|
|
33201
|
-
let baseSchema = convertBaseSchema(schema, ctx);
|
|
33202
|
-
const hasExplicitType = schema.type || schema.enum !== void 0 || schema.const !== void 0;
|
|
33203
|
-
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
33204
|
-
const options = schema.anyOf.map((s) => convertSchema(s, ctx));
|
|
33205
|
-
const anyOfUnion = z.union(options);
|
|
33206
|
-
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
|
33207
|
-
}
|
|
33208
|
-
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
33209
|
-
const options = schema.oneOf.map((s) => convertSchema(s, ctx));
|
|
33210
|
-
const oneOfUnion = z.xor(options);
|
|
33211
|
-
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
|
33212
|
-
}
|
|
33213
|
-
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
33214
|
-
if (schema.allOf.length === 0) {
|
|
33215
|
-
baseSchema = hasExplicitType ? baseSchema : z.any();
|
|
33216
|
-
} else {
|
|
33217
|
-
let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx);
|
|
33218
|
-
const startIdx = hasExplicitType ? 0 : 1;
|
|
33219
|
-
for (let i = startIdx; i < schema.allOf.length; i++) {
|
|
33220
|
-
result = z.intersection(result, convertSchema(schema.allOf[i], ctx));
|
|
33221
|
-
}
|
|
33222
|
-
baseSchema = result;
|
|
33223
|
-
}
|
|
33224
|
-
}
|
|
33225
|
-
if (schema.nullable === true && ctx.version === "openapi-3.0") {
|
|
33226
|
-
baseSchema = z.nullable(baseSchema);
|
|
33227
|
-
}
|
|
33228
|
-
if (schema.readOnly === true) {
|
|
33229
|
-
baseSchema = z.readonly(baseSchema);
|
|
33230
|
-
}
|
|
33231
|
-
const extraMeta = {};
|
|
33232
|
-
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
33233
|
-
for (const key of coreMetadataKeys) {
|
|
33234
|
-
if (key in schema) {
|
|
33235
|
-
extraMeta[key] = schema[key];
|
|
33236
|
-
}
|
|
33237
|
-
}
|
|
33238
|
-
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
33239
|
-
for (const key of contentMetadataKeys) {
|
|
33240
|
-
if (key in schema) {
|
|
33241
|
-
extraMeta[key] = schema[key];
|
|
33242
|
-
}
|
|
33243
|
-
}
|
|
33244
|
-
for (const key of Object.keys(schema)) {
|
|
33245
|
-
if (!RECOGNIZED_KEYS.has(key)) {
|
|
33246
|
-
extraMeta[key] = schema[key];
|
|
33247
|
-
}
|
|
33248
|
-
}
|
|
33249
|
-
if (Object.keys(extraMeta).length > 0) {
|
|
33250
|
-
ctx.registry.add(baseSchema, extraMeta);
|
|
33251
|
-
}
|
|
33252
|
-
return baseSchema;
|
|
33253
|
-
}
|
|
33254
|
-
function fromJSONSchema(schema, params) {
|
|
33255
|
-
if (typeof schema === "boolean") {
|
|
33256
|
-
return schema ? z.any() : z.never();
|
|
33257
|
-
}
|
|
33258
|
-
const version3 = detectVersion(schema, params?.defaultTarget);
|
|
33259
|
-
const defs = schema.$defs || schema.definitions || {};
|
|
33260
|
-
const ctx = {
|
|
33261
|
-
version: version3,
|
|
33262
|
-
defs,
|
|
33263
|
-
refs: /* @__PURE__ */ new Map(),
|
|
33264
|
-
processing: /* @__PURE__ */ new Set(),
|
|
33265
|
-
rootSchema: schema,
|
|
33266
|
-
registry: params?.registry ?? globalRegistry2
|
|
33267
|
-
};
|
|
33268
|
-
return convertSchema(schema, ctx);
|
|
33269
|
-
}
|
|
33270
|
-
|
|
33271
|
-
// node_modules/zod/v4/classic/coerce.js
|
|
33272
|
-
var coerce_exports2 = {};
|
|
33273
|
-
__export(coerce_exports2, {
|
|
33274
|
-
bigint: () => bigint6,
|
|
33275
|
-
boolean: () => boolean6,
|
|
33276
|
-
date: () => date8,
|
|
33277
|
-
number: () => number6,
|
|
33278
|
-
string: () => string6
|
|
33279
|
-
});
|
|
33280
|
-
function string6(params) {
|
|
33281
|
-
return _coercedString2(ZodString2, params);
|
|
33282
|
-
}
|
|
33283
|
-
function number6(params) {
|
|
33284
|
-
return _coercedNumber2(ZodNumber2, params);
|
|
33285
|
-
}
|
|
33286
|
-
function boolean6(params) {
|
|
33287
|
-
return _coercedBoolean2(ZodBoolean2, params);
|
|
33288
|
-
}
|
|
33289
|
-
function bigint6(params) {
|
|
33290
|
-
return _coercedBigint2(ZodBigInt2, params);
|
|
33291
|
-
}
|
|
33292
|
-
function date8(params) {
|
|
33293
|
-
return _coercedDate2(ZodDate2, params);
|
|
33294
|
-
}
|
|
33295
|
-
|
|
33296
|
-
// node_modules/zod/v4/classic/external.js
|
|
33297
|
-
config2(en_default2());
|
|
33298
|
-
|
|
33299
|
-
// src/core/agents/agent-registry.ts
|
|
33300
|
-
var AgentDefinitionSchema = external_exports2.object({
|
|
33301
|
-
id: external_exports2.string(),
|
|
33302
|
-
// ID is required inside the definition object
|
|
33303
|
-
description: external_exports2.string(),
|
|
33304
|
-
systemPrompt: external_exports2.string(),
|
|
33305
|
-
mode: external_exports2.enum(["primary", "subagent"]).optional(),
|
|
33306
|
-
color: external_exports2.string().optional(),
|
|
33307
|
-
hidden: external_exports2.boolean().optional(),
|
|
33308
|
-
thinking: external_exports2.boolean().optional(),
|
|
33309
|
-
maxTokens: external_exports2.number().optional(),
|
|
33310
|
-
budgetTokens: external_exports2.number().optional(),
|
|
33311
|
-
canWrite: external_exports2.boolean(),
|
|
33312
|
-
// Required per interface
|
|
33313
|
-
canBash: external_exports2.boolean()
|
|
33314
|
-
// Required per interface
|
|
33315
|
-
});
|
|
33316
|
-
var AgentRegistry = class _AgentRegistry {
|
|
33317
|
-
static instance;
|
|
33318
|
-
agents = /* @__PURE__ */ new Map();
|
|
33319
|
-
directory = "";
|
|
33320
|
-
constructor() {
|
|
33321
|
-
for (const [name, def] of Object.entries(AGENTS)) {
|
|
33322
|
-
this.agents.set(name, def);
|
|
33323
|
-
}
|
|
33324
|
-
}
|
|
33325
|
-
static getInstance() {
|
|
33326
|
-
if (!_AgentRegistry.instance) {
|
|
33327
|
-
_AgentRegistry.instance = new _AgentRegistry();
|
|
33328
|
-
}
|
|
33329
|
-
return _AgentRegistry.instance;
|
|
33330
|
-
}
|
|
33331
|
-
setDirectory(dir) {
|
|
33332
|
-
this.directory = dir;
|
|
33333
|
-
this.loadCustomAgents();
|
|
33334
|
-
}
|
|
33335
|
-
/**
|
|
33336
|
-
* Get agent definition by name
|
|
33337
|
-
*/
|
|
33338
|
-
getAgent(name) {
|
|
33339
|
-
return this.agents.get(name);
|
|
33340
|
-
}
|
|
33341
|
-
/**
|
|
33342
|
-
* List all available agent names
|
|
33343
|
-
*/
|
|
33344
|
-
listAgents() {
|
|
33345
|
-
return Array.from(this.agents.keys());
|
|
33346
|
-
}
|
|
33347
|
-
/**
|
|
33348
|
-
* Add or update an agent definition
|
|
33349
|
-
*/
|
|
33350
|
-
registerAgent(name, def) {
|
|
33351
|
-
this.agents.set(name, def);
|
|
33352
|
-
log(`[AgentRegistry] Registered agent: ${name}`);
|
|
33353
|
-
}
|
|
33354
|
-
/**
|
|
33355
|
-
* Load custom agents from .opencode/agents.json
|
|
33356
|
-
*/
|
|
33357
|
-
async loadCustomAgents() {
|
|
33358
|
-
if (!this.directory) return;
|
|
33359
|
-
const agentsConfigPath = path4.join(this.directory, PATHS.AGENTS_CONFIG);
|
|
33360
|
-
try {
|
|
33361
|
-
const content = await fs4.readFile(agentsConfigPath, "utf-8");
|
|
33362
|
-
const customAgents = JSON.parse(content);
|
|
33363
|
-
if (typeof customAgents === "object" && customAgents !== null) {
|
|
33364
|
-
for (const [name, def] of Object.entries(customAgents)) {
|
|
33365
|
-
const result = AgentDefinitionSchema.safeParse(def);
|
|
33366
|
-
if (result.success) {
|
|
33367
|
-
this.registerAgent(name, def);
|
|
33368
|
-
} else {
|
|
33369
|
-
log(`[AgentRegistry] Invalid custom agent definition for: ${name}. Errors: ${result.error.message}`);
|
|
33370
|
-
}
|
|
33371
|
-
}
|
|
33372
|
-
}
|
|
33373
|
-
} catch (error92) {
|
|
33374
|
-
if (error92.code !== "ENOENT") {
|
|
33375
|
-
log(`[AgentRegistry] Error loading custom agents: ${error92}`);
|
|
33376
|
-
}
|
|
33377
|
-
}
|
|
33378
|
-
}
|
|
33379
|
-
};
|
|
33380
|
-
|
|
33381
|
-
// src/core/agents/manager/task-launcher.ts
|
|
33382
|
-
var TaskLauncher = class {
|
|
33383
|
-
constructor(client2, directory, store, concurrency, sessionPool2, onTaskError, startPolling) {
|
|
33384
|
-
this.client = client2;
|
|
33385
|
-
this.directory = directory;
|
|
33386
|
-
this.store = store;
|
|
33387
|
-
this.concurrency = concurrency;
|
|
33388
|
-
this.sessionPool = sessionPool2;
|
|
33389
|
-
this.onTaskError = onTaskError;
|
|
33390
|
-
this.startPolling = startPolling;
|
|
33391
|
-
}
|
|
33392
|
-
/**
|
|
33393
|
-
* Unified launch method - handles both single and multiple tasks efficiently.
|
|
33394
|
-
* All session creations happen in parallel immediately.
|
|
33395
|
-
* Concurrency acquisition and prompt firing happen in the background.
|
|
33396
|
-
*/
|
|
33397
|
-
async launch(inputs) {
|
|
33398
|
-
const isArray = Array.isArray(inputs);
|
|
33399
|
-
const taskInputs = isArray ? inputs : [inputs];
|
|
33400
|
-
if (taskInputs.length === 0) return isArray ? [] : null;
|
|
33401
|
-
const tasks = await Promise.all(taskInputs.map(
|
|
33402
|
-
(input) => this.prepareTask(input).catch(() => null)
|
|
33403
|
-
));
|
|
33404
|
-
const successfulTasks = tasks.filter((t) => t !== null);
|
|
33405
|
-
successfulTasks.forEach((task) => {
|
|
33406
|
-
this.executeBackground(task).catch((error92) => {
|
|
33407
|
-
this.onTaskError(task.id, error92);
|
|
33408
|
-
});
|
|
33409
|
-
});
|
|
33410
|
-
if (successfulTasks.length > 0) {
|
|
33411
|
-
this.startPolling();
|
|
33412
|
-
}
|
|
33413
|
-
return isArray ? successfulTasks : successfulTasks[0] || null;
|
|
33414
|
-
}
|
|
33415
|
-
/**
|
|
33416
|
-
* Prepare task: Create session and registration without blocking on concurrency
|
|
33417
|
-
*/
|
|
33418
|
-
async prepareTask(input) {
|
|
33419
|
-
const currentDepth = input.depth ?? 0;
|
|
33420
|
-
if (currentDepth >= PARALLEL_TASK.MAX_DEPTH) {
|
|
33421
|
-
throw new Error(`Maximum task depth (${PARALLEL_TASK.MAX_DEPTH}) reached. To prevent infinite recursion, no further sub-tasks can be spawned.`);
|
|
33422
|
-
}
|
|
33423
|
-
const session = await this.sessionPool.acquire(
|
|
33424
|
-
input.agent,
|
|
33425
|
-
input.parentSessionID,
|
|
33426
|
-
input.description
|
|
33427
|
-
);
|
|
33428
|
-
const sessionID = session.id;
|
|
33429
|
-
const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
|
|
33430
|
-
const task = {
|
|
33431
|
-
id: taskId,
|
|
33432
|
-
sessionID,
|
|
33433
|
-
parentSessionID: input.parentSessionID,
|
|
33434
|
-
description: input.description,
|
|
33435
|
-
prompt: input.prompt,
|
|
33436
|
-
agent: input.agent,
|
|
33437
|
-
status: TASK_STATUS.PENDING,
|
|
33438
|
-
// Start as PENDING
|
|
33439
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
33440
|
-
concurrencyKey: input.agent,
|
|
33441
|
-
depth: (input.depth ?? 0) + 1,
|
|
33442
|
-
mode: input.mode || "normal",
|
|
33443
|
-
groupID: input.groupID
|
|
33444
|
-
};
|
|
33445
|
-
this.store.set(taskId, task);
|
|
33446
|
-
this.store.trackPending(input.parentSessionID, taskId);
|
|
33447
|
-
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
33448
|
-
});
|
|
33449
|
-
const toastManager = getTaskToastManager();
|
|
33450
|
-
if (toastManager) {
|
|
33451
|
-
toastManager.addTask({
|
|
33452
|
-
id: taskId,
|
|
33453
|
-
description: input.description,
|
|
33454
|
-
agent: input.agent,
|
|
33455
|
-
isBackground: true,
|
|
33456
|
-
parentSessionID: input.parentSessionID,
|
|
33457
|
-
sessionID
|
|
33458
|
-
});
|
|
33459
|
-
}
|
|
33460
|
-
presets_exports.sessionCreated(sessionID, input.agent);
|
|
33461
|
-
return task;
|
|
33462
|
-
}
|
|
33463
|
-
/**
|
|
33464
|
-
* Background execution: Acquire slot and fire prompt with auto-retry
|
|
33465
|
-
*/
|
|
33466
|
-
async executeBackground(task) {
|
|
33467
|
-
let attempt = 1;
|
|
33468
|
-
while (true) {
|
|
33469
|
-
try {
|
|
33470
|
-
await this.concurrency.acquire(task.agent);
|
|
33471
|
-
task.status = TASK_STATUS.RUNNING;
|
|
33472
|
-
task.startedAt = /* @__PURE__ */ new Date();
|
|
33473
|
-
this.store.set(task.id, task);
|
|
33474
|
-
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
33475
|
-
});
|
|
33476
|
-
const agentDef = AgentRegistry.getInstance().getAgent(task.agent);
|
|
33477
|
-
let finalPrompt = task.prompt;
|
|
33478
|
-
if (agentDef) {
|
|
33479
|
-
finalPrompt = `### AGENT ROLE: ${agentDef.id}
|
|
33480
|
-
${agentDef.description}
|
|
33481
|
-
|
|
33482
|
-
${agentDef.systemPrompt}
|
|
33483
|
-
|
|
33484
|
-
${finalPrompt}`;
|
|
33485
|
-
}
|
|
33486
|
-
const memory = MemoryManager.getInstance().getContext(finalPrompt);
|
|
33487
|
-
const injectedPrompt = memory ? `${memory}
|
|
33488
|
-
|
|
33489
|
-
${finalPrompt}` : finalPrompt;
|
|
33490
|
-
const wireAgent = Object.values(AGENT_NAMES).includes(task.agent) ? task.agent : AGENT_NAMES.COMMANDER;
|
|
33491
|
-
const promptPromise = this.client.session.prompt({
|
|
33492
|
-
path: { id: task.sessionID },
|
|
33493
|
-
body: {
|
|
33494
|
-
agent: wireAgent,
|
|
33495
|
-
tools: {
|
|
33496
|
-
[TOOL_NAMES.DELEGATE_TASK]: true,
|
|
33497
|
-
[TOOL_NAMES.GET_TASK_RESULT]: true,
|
|
33498
|
-
[TOOL_NAMES.LIST_TASKS]: true,
|
|
33499
|
-
[TOOL_NAMES.CANCEL_TASK]: true,
|
|
33500
|
-
[TOOL_NAMES.SKILL]: true,
|
|
33501
|
-
[TOOL_NAMES.RUN_COMMAND]: true
|
|
33502
|
-
},
|
|
33503
|
-
parts: [{ type: PART_TYPES.TEXT, text: injectedPrompt }]
|
|
33504
|
-
}
|
|
33505
|
-
});
|
|
33506
|
-
await Promise.race([
|
|
33507
|
-
promptPromise,
|
|
33508
|
-
new Promise(
|
|
33509
|
-
(_, reject) => setTimeout(() => reject(new Error("Session prompt execution timed out after 600s")), 6e5)
|
|
33510
|
-
)
|
|
33511
|
-
]);
|
|
33512
|
-
return;
|
|
33513
|
-
} catch (error92) {
|
|
33514
|
-
this.concurrency.release(task.agent);
|
|
33515
|
-
const context = {
|
|
33516
|
-
sessionId: task.sessionID,
|
|
33517
|
-
taskId: task.id,
|
|
33518
|
-
agent: task.agent,
|
|
33519
|
-
error: error92 instanceof Error ? error92 : new Error(String(error92)),
|
|
33520
|
-
attempt,
|
|
33521
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
33522
|
-
};
|
|
33523
|
-
const action = handleError(context);
|
|
33524
|
-
if (action.type === "retry") {
|
|
33525
|
-
log(`[AutoRetry] Task ${task.id} failed (attempt ${attempt}). Retrying in ${action.delay}ms...`);
|
|
33526
|
-
if (action.modifyPrompt) {
|
|
33527
|
-
task.prompt += `
|
|
33528
|
-
|
|
33529
|
-
${action.modifyPrompt}`;
|
|
33530
|
-
}
|
|
33531
|
-
await new Promise((r) => setTimeout(r, action.delay));
|
|
33532
|
-
attempt++;
|
|
33533
|
-
continue;
|
|
33534
|
-
}
|
|
33535
|
-
throw error92;
|
|
33536
|
-
}
|
|
33537
|
-
}
|
|
33538
|
-
}
|
|
33539
|
-
};
|
|
33540
|
-
|
|
33541
|
-
// src/core/agents/manager/task-resumer.ts
|
|
33542
|
-
init_shared();
|
|
33543
|
-
var TaskResumer = class {
|
|
33544
|
-
constructor(client2, store, findBySession, startPolling, notifyParentIfAllComplete) {
|
|
33545
|
-
this.client = client2;
|
|
33546
|
-
this.store = store;
|
|
33547
|
-
this.findBySession = findBySession;
|
|
33548
|
-
this.startPolling = startPolling;
|
|
33549
|
-
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
33550
|
-
}
|
|
33551
|
-
async resume(input) {
|
|
33552
|
-
const existingTask = this.findBySession(input.sessionId);
|
|
33553
|
-
if (!existingTask) {
|
|
33554
|
-
throw new Error(`Task not found for session: ${input.sessionId}`);
|
|
33555
|
-
}
|
|
33556
|
-
existingTask.status = TASK_STATUS.RUNNING;
|
|
33557
|
-
existingTask.completedAt = void 0;
|
|
33558
|
-
existingTask.error = void 0;
|
|
33559
|
-
existingTask.result = void 0;
|
|
33560
|
-
existingTask.parentSessionID = input.parentSessionID;
|
|
33561
|
-
existingTask.startedAt = /* @__PURE__ */ new Date();
|
|
33562
|
-
existingTask.stablePolls = 0;
|
|
33563
|
-
this.store.trackPending(input.parentSessionID, existingTask.id);
|
|
33564
|
-
this.startPolling();
|
|
33565
|
-
taskWAL.log(WAL_ACTIONS.UPDATE, existingTask).catch(() => {
|
|
33566
|
-
});
|
|
33567
|
-
log(`Resuming task ${existingTask.id} in session ${existingTask.sessionID}`);
|
|
33568
|
-
this.client.session.prompt({
|
|
33569
|
-
path: { id: existingTask.sessionID },
|
|
33570
|
-
body: {
|
|
33571
|
-
agent: existingTask.agent,
|
|
33572
|
-
parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
|
|
33573
|
-
}
|
|
33574
|
-
}).catch((error92) => {
|
|
33575
|
-
log(`Resume prompt error for ${existingTask.id}:`, error92);
|
|
33576
|
-
existingTask.status = TASK_STATUS.ERROR;
|
|
33577
|
-
existingTask.error = error92 instanceof Error ? error92.message : String(error92);
|
|
33578
|
-
existingTask.completedAt = /* @__PURE__ */ new Date();
|
|
33579
|
-
this.store.untrackPending(input.parentSessionID, existingTask.id);
|
|
33580
|
-
this.store.queueNotification(existingTask);
|
|
33581
|
-
this.notifyParentIfAllComplete(input.parentSessionID).catch(() => {
|
|
33582
|
-
});
|
|
33583
|
-
taskWAL.log(WAL_ACTIONS.UPDATE, existingTask).catch(() => {
|
|
33584
|
-
});
|
|
33585
|
-
});
|
|
33586
|
-
return existingTask;
|
|
33587
|
-
}
|
|
33588
|
-
};
|
|
33589
|
-
|
|
33590
|
-
// src/core/agents/config.ts
|
|
33591
|
-
init_shared();
|
|
33592
|
-
var CONFIG = {
|
|
33593
|
-
TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
|
|
33594
|
-
CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
|
|
33595
|
-
MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
|
|
33596
|
-
POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS,
|
|
33597
|
-
STABLE_POLLS_REQUIRED: PARALLEL_TASK.STABLE_POLLS_REQUIRED
|
|
33598
|
-
};
|
|
33599
|
-
|
|
33600
|
-
// src/core/agents/manager/task-poller.ts
|
|
33601
|
-
init_shared();
|
|
33602
|
-
init_shared();
|
|
33603
|
-
|
|
33604
|
-
// src/core/progress/state-broadcaster.ts
|
|
33605
|
-
var StateBroadcaster = class _StateBroadcaster {
|
|
33606
|
-
static _instance;
|
|
33607
|
-
listeners = /* @__PURE__ */ new Set();
|
|
33608
|
-
currentState = null;
|
|
33609
|
-
constructor() {
|
|
33610
|
-
}
|
|
33611
|
-
static getInstance() {
|
|
33612
|
-
if (!_StateBroadcaster._instance) {
|
|
33613
|
-
_StateBroadcaster._instance = new _StateBroadcaster();
|
|
33614
|
-
}
|
|
33615
|
-
return _StateBroadcaster._instance;
|
|
33616
|
-
}
|
|
33617
|
-
subscribe(listener) {
|
|
33618
|
-
this.listeners.add(listener);
|
|
33619
|
-
if (this.currentState) {
|
|
33620
|
-
listener(this.currentState);
|
|
33621
|
-
}
|
|
33622
|
-
return () => this.listeners.delete(listener);
|
|
33623
|
-
}
|
|
33624
|
-
broadcast(state2) {
|
|
33625
|
-
this.currentState = state2;
|
|
33626
|
-
this.listeners.forEach((listener) => {
|
|
33627
|
-
try {
|
|
33628
|
-
listener(state2);
|
|
33629
|
-
} catch (error92) {
|
|
33630
|
-
}
|
|
33631
|
-
});
|
|
33632
|
-
}
|
|
33633
|
-
getCurrentState() {
|
|
33634
|
-
return this.currentState;
|
|
33635
|
-
}
|
|
33636
|
-
};
|
|
33637
|
-
var stateBroadcaster = StateBroadcaster.getInstance();
|
|
33638
|
-
|
|
33639
|
-
// src/core/progress/progress-notifier.ts
|
|
33640
|
-
init_shared();
|
|
33641
|
-
var ProgressNotifier = class _ProgressNotifier {
|
|
33642
|
-
static _instance;
|
|
33643
|
-
manager = null;
|
|
33644
|
-
constructor() {
|
|
33645
|
-
stateBroadcaster.subscribe(this.handleStateChange.bind(this));
|
|
33646
|
-
}
|
|
33647
|
-
static getInstance() {
|
|
33648
|
-
if (!_ProgressNotifier._instance) {
|
|
33649
|
-
_ProgressNotifier._instance = new _ProgressNotifier();
|
|
33650
|
-
}
|
|
33651
|
-
return _ProgressNotifier._instance;
|
|
33652
|
-
}
|
|
33653
|
-
setManager(manager) {
|
|
33654
|
-
this.manager = manager;
|
|
33655
|
-
}
|
|
33656
|
-
/**
|
|
33657
|
-
* Poll current status from ParallelAgentManager and broadcast it
|
|
33658
|
-
*/
|
|
33659
|
-
update() {
|
|
33660
|
-
if (!this.manager) return;
|
|
33661
|
-
const tasks = this.manager.getAllTasks();
|
|
33662
|
-
const running = tasks.filter((t) => t.status === TASK_STATUS.RUNNING);
|
|
33663
|
-
const completed = tasks.filter((t) => t.status === TASK_STATUS.COMPLETED);
|
|
33664
|
-
const total = tasks.length;
|
|
33665
|
-
const percentage = total > 0 ? Math.round(completed.length / total * 100) : 0;
|
|
33666
|
-
const state2 = {
|
|
33667
|
-
missionId: "current-mission",
|
|
33668
|
-
// Could be dynamic
|
|
33669
|
-
status: percentage === 100 ? "completed" : "executing",
|
|
33670
|
-
progress: {
|
|
33671
|
-
totalTasks: total,
|
|
33672
|
-
completedTasks: completed.length,
|
|
33673
|
-
percentage
|
|
33674
|
-
},
|
|
33675
|
-
activeAgents: running.map((t) => ({
|
|
33676
|
-
id: t.id,
|
|
33677
|
-
type: t.agent,
|
|
33678
|
-
status: t.status,
|
|
33679
|
-
currentTask: t.description
|
|
33680
|
-
})),
|
|
33681
|
-
todo: [],
|
|
33682
|
-
// Need to fetch from TodoEnforcer if possible
|
|
33683
|
-
lastUpdated: /* @__PURE__ */ new Date()
|
|
33684
|
-
};
|
|
33685
|
-
stateBroadcaster.broadcast(state2);
|
|
33686
|
-
}
|
|
33687
|
-
handleStateChange(state2) {
|
|
33688
|
-
if (state2.progress.percentage > 0 && state2.progress.percentage % 25 === 0) {
|
|
33689
|
-
const toastManager = getTaskToastManager();
|
|
33690
|
-
if (toastManager) {
|
|
33691
|
-
}
|
|
33692
|
-
}
|
|
33693
|
-
}
|
|
33694
|
-
};
|
|
33695
|
-
var progressNotifier = ProgressNotifier.getInstance();
|
|
33696
|
-
|
|
33697
|
-
// src/core/agents/manager/task-poller.ts
|
|
33698
|
-
var MAX_TASK_DURATION_MS = 18e5;
|
|
33699
|
-
var TaskPoller = class {
|
|
33700
|
-
constructor(client2, store, concurrency, notifyParentIfAllComplete, scheduleCleanup, pruneExpiredTasks, onTaskComplete, onTaskError) {
|
|
33701
|
-
this.client = client2;
|
|
33702
|
-
this.store = store;
|
|
33703
|
-
this.concurrency = concurrency;
|
|
33704
|
-
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
33705
|
-
this.scheduleCleanup = scheduleCleanup;
|
|
33706
|
-
this.pruneExpiredTasks = pruneExpiredTasks;
|
|
33707
|
-
this.onTaskComplete = onTaskComplete;
|
|
33708
|
-
this.onTaskError = onTaskError;
|
|
33709
|
-
}
|
|
33710
|
-
pollTimeout;
|
|
33711
|
-
messageCache = /* @__PURE__ */ new Map();
|
|
33712
|
-
start() {
|
|
33713
|
-
if (this.pollTimeout) return;
|
|
33714
|
-
log("[task-poller.ts] start() - polling started");
|
|
33715
|
-
this.scheduleNextPoll();
|
|
33716
|
-
}
|
|
33717
|
-
stop() {
|
|
33718
|
-
if (this.pollTimeout) {
|
|
33719
|
-
clearTimeout(this.pollTimeout);
|
|
33720
|
-
this.pollTimeout = void 0;
|
|
33721
|
-
}
|
|
33722
|
-
}
|
|
33723
|
-
isRunning() {
|
|
33724
|
-
return !!this.pollTimeout;
|
|
33725
|
-
}
|
|
33726
|
-
scheduleNextPoll() {
|
|
33727
|
-
if (this.pollTimeout) clearTimeout(this.pollTimeout);
|
|
33728
|
-
this.pollTimeout = setTimeout(() => this.poll(), CONFIG.POLL_INTERVAL_MS);
|
|
33729
|
-
this.pollTimeout.unref();
|
|
33730
|
-
}
|
|
33731
|
-
async poll() {
|
|
33732
|
-
this.pruneExpiredTasks();
|
|
33733
|
-
const running = this.store.getRunning();
|
|
33734
|
-
if (running.length === 0) {
|
|
33735
|
-
this.stop();
|
|
33736
|
-
return;
|
|
33737
|
-
}
|
|
33738
|
-
log("[task-poller.ts] poll() checking", running.length, "running tasks");
|
|
33739
|
-
try {
|
|
33740
|
-
const statusResult = await this.client.session.status();
|
|
33741
|
-
const allStatuses = statusResult.data ?? {};
|
|
33742
|
-
for (const task of running) {
|
|
33743
|
-
try {
|
|
33744
|
-
const taskDuration = Date.now() - task.startedAt.getTime();
|
|
33745
|
-
if (taskDuration > MAX_TASK_DURATION_MS) {
|
|
33746
|
-
log(`[task-poller] Task ${task.id} exceeded max duration (${MAX_TASK_DURATION_MS}ms). Marking as error.`);
|
|
33747
|
-
this.onTaskError?.(task.id, new Error("Task exceeded maximum execution time"));
|
|
33748
|
-
continue;
|
|
33749
|
-
}
|
|
33750
|
-
if (task.status === TASK_STATUS.PENDING) continue;
|
|
33751
|
-
const sessionStatus = allStatuses[task.sessionID];
|
|
33752
|
-
if (sessionStatus?.type === SESSION_STATUS.IDLE) {
|
|
33753
|
-
const elapsed2 = Date.now() - task.startedAt.getTime();
|
|
33754
|
-
if (elapsed2 < CONFIG.MIN_STABILITY_MS) continue;
|
|
33755
|
-
if (!task.hasStartedOutputting && !await this.validateSessionHasOutput(task.sessionID, task)) continue;
|
|
33756
|
-
await this.completeTask(task);
|
|
33757
|
-
continue;
|
|
33758
|
-
}
|
|
33759
|
-
await this.updateTaskProgress(task, sessionStatus);
|
|
33760
|
-
const elapsed = Date.now() - task.startedAt.getTime();
|
|
33761
|
-
if (elapsed >= CONFIG.MIN_STABILITY_MS && task.stablePolls && task.stablePolls >= CONFIG.STABLE_POLLS_REQUIRED) {
|
|
33762
|
-
if (task.hasStartedOutputting || await this.validateSessionHasOutput(task.sessionID, task)) {
|
|
33763
|
-
log(`Task ${task.id} stable for ${task.stablePolls} polls, completing...`);
|
|
33764
|
-
await this.completeTask(task);
|
|
33765
|
-
}
|
|
33766
|
-
}
|
|
33767
|
-
} catch (error92) {
|
|
33768
|
-
log(`Poll error for task ${task.id}:`, error92);
|
|
33769
|
-
}
|
|
33770
|
-
}
|
|
33771
|
-
progressNotifier.update();
|
|
33772
|
-
} catch (error92) {
|
|
33773
|
-
log("Polling error:", error92);
|
|
33774
|
-
} finally {
|
|
33775
|
-
if (this.store.getRunning().length > 0) {
|
|
33776
|
-
this.scheduleNextPoll();
|
|
33777
|
-
} else {
|
|
33778
|
-
this.stop();
|
|
33779
|
-
}
|
|
33780
|
-
}
|
|
33781
|
-
}
|
|
33782
|
-
async validateSessionHasOutput(sessionID, task) {
|
|
33783
|
-
try {
|
|
33784
|
-
const response = await this.client.session.messages({ path: { id: sessionID } });
|
|
33785
|
-
const messages = response.data ?? [];
|
|
33786
|
-
const hasOutput = messages.some((m) => m.info?.role === MESSAGE_ROLES.ASSISTANT && m.parts?.some((p) => p.type === PART_TYPES.TEXT && p.text?.trim() || p.type === PART_TYPES.TOOL));
|
|
33787
|
-
if (hasOutput && task) {
|
|
33788
|
-
task.hasStartedOutputting = true;
|
|
33789
|
-
}
|
|
33790
|
-
return hasOutput;
|
|
33791
|
-
} catch {
|
|
33792
|
-
return true;
|
|
33793
|
-
}
|
|
33794
|
-
}
|
|
33795
|
-
async completeTask(task) {
|
|
33796
|
-
log("[task-poller.ts] completeTask() called for", task.id, task.agent);
|
|
33797
|
-
task.status = TASK_STATUS.COMPLETED;
|
|
33798
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
33799
|
-
if (task.concurrencyKey) {
|
|
33800
|
-
this.concurrency.release(task.concurrencyKey);
|
|
33801
|
-
this.concurrency.reportResult(task.concurrencyKey, true);
|
|
33802
|
-
task.concurrencyKey = void 0;
|
|
33803
|
-
}
|
|
33804
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
33805
|
-
this.store.queueNotification(task);
|
|
33806
|
-
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
33807
|
-
this.scheduleCleanup(task.id);
|
|
33808
|
-
taskWAL.log(WAL_ACTIONS.COMPLETE, task).catch(() => {
|
|
33809
|
-
});
|
|
33810
|
-
if (this.onTaskComplete) {
|
|
33811
|
-
Promise.resolve(this.onTaskComplete(task)).catch((err) => log("Error in onTaskComplete callback:", err));
|
|
33812
|
-
}
|
|
33813
|
-
const duration5 = formatDuration(task.startedAt, task.completedAt);
|
|
33814
|
-
presets_exports.sessionCompleted(task.sessionID, duration5);
|
|
33815
|
-
log(`Completed ${task.id} (${duration5})`);
|
|
33816
|
-
progressNotifier.update();
|
|
33817
|
-
}
|
|
33818
|
-
async updateTaskProgress(task, sessionStatus) {
|
|
33819
|
-
try {
|
|
33820
|
-
const cached3 = this.messageCache.get(task.sessionID);
|
|
33821
|
-
let currentMsgCount = sessionStatus?.messageCount;
|
|
33822
|
-
if (currentMsgCount === void 0) {
|
|
33823
|
-
const statusResult = await this.client.session.status();
|
|
33824
|
-
const sessionInfo = statusResult.data?.[task.sessionID];
|
|
33825
|
-
currentMsgCount = sessionInfo?.messageCount ?? 0;
|
|
33826
|
-
}
|
|
33827
|
-
if (cached3 && cached3.count === currentMsgCount) {
|
|
33828
|
-
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
33829
|
-
return;
|
|
33830
|
-
}
|
|
33831
|
-
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
33832
|
-
this.messageCache.set(task.sessionID, { count: currentMsgCount, lastChecked: /* @__PURE__ */ new Date() });
|
|
33833
|
-
if (result.error) return;
|
|
33834
|
-
const messages = result.data ?? [];
|
|
33835
|
-
const assistantMsgs = messages.filter((m) => m.info?.role === MESSAGE_ROLES.ASSISTANT);
|
|
33836
|
-
let toolCalls = 0;
|
|
33837
|
-
let lastTool;
|
|
33838
|
-
let lastMessage;
|
|
33839
|
-
for (const msg of assistantMsgs) {
|
|
33840
|
-
for (const part of msg.parts ?? []) {
|
|
33841
|
-
if (part.type === PART_TYPES.TOOL_USE || part.tool) {
|
|
33842
|
-
toolCalls++;
|
|
33843
|
-
lastTool = part.tool || part.name;
|
|
33844
|
-
}
|
|
33845
|
-
if (part.type === PART_TYPES.TEXT && part.text) {
|
|
33846
|
-
lastMessage = part.text;
|
|
33847
|
-
}
|
|
33848
|
-
}
|
|
33849
|
-
}
|
|
33850
|
-
task.progress = {
|
|
33851
|
-
toolCalls,
|
|
33852
|
-
lastTool,
|
|
33853
|
-
lastMessage: lastMessage?.slice(0, 100),
|
|
33854
|
-
lastUpdate: /* @__PURE__ */ new Date()
|
|
33855
|
-
};
|
|
33856
|
-
if (task.lastMsgCount === currentMsgCount) {
|
|
33857
|
-
task.stablePolls = 0;
|
|
33798
|
+
arraySchema = arraySchema.max(schema.maxItems);
|
|
33799
|
+
}
|
|
33800
|
+
zodSchema = arraySchema;
|
|
33858
33801
|
} else {
|
|
33859
|
-
|
|
33802
|
+
zodSchema = z.array(z.any());
|
|
33860
33803
|
}
|
|
33861
|
-
|
|
33862
|
-
} catch {
|
|
33804
|
+
break;
|
|
33863
33805
|
}
|
|
33806
|
+
default:
|
|
33807
|
+
throw new Error(`Unsupported type: ${type}`);
|
|
33864
33808
|
}
|
|
33865
|
-
|
|
33866
|
-
|
|
33867
|
-
// src/core/agents/manager/task-cleaner.ts
|
|
33868
|
-
init_shared();
|
|
33869
|
-
init_store();
|
|
33870
|
-
var TaskCleaner = class {
|
|
33871
|
-
constructor(client2, store, concurrency, sessionPool2) {
|
|
33872
|
-
this.client = client2;
|
|
33873
|
-
this.store = store;
|
|
33874
|
-
this.concurrency = concurrency;
|
|
33875
|
-
this.sessionPool = sessionPool2;
|
|
33809
|
+
if (schema.description) {
|
|
33810
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
33876
33811
|
}
|
|
33877
|
-
|
|
33878
|
-
|
|
33879
|
-
for (const [taskId, task] of this.store.getAll().map((t) => [t.id, t])) {
|
|
33880
|
-
const age = now - task.startedAt.getTime();
|
|
33881
|
-
if (age <= CONFIG.TASK_TTL_MS) continue;
|
|
33882
|
-
log(`Timeout: ${taskId}`);
|
|
33883
|
-
if (task.status === TASK_STATUS.RUNNING) {
|
|
33884
|
-
task.status = TASK_STATUS.TIMEOUT;
|
|
33885
|
-
task.error = "Task exceeded 30 minute time limit";
|
|
33886
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
33887
|
-
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
33888
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
33889
|
-
const toastManager = getTaskToastManager();
|
|
33890
|
-
if (toastManager) {
|
|
33891
|
-
toastManager.showCompletionToast({
|
|
33892
|
-
id: taskId,
|
|
33893
|
-
description: task.description,
|
|
33894
|
-
duration: formatDuration(task.startedAt, task.completedAt),
|
|
33895
|
-
status: TASK_STATUS.ERROR,
|
|
33896
|
-
error: task.error
|
|
33897
|
-
});
|
|
33898
|
-
}
|
|
33899
|
-
}
|
|
33900
|
-
this.sessionPool.release(task.sessionID).catch(() => {
|
|
33901
|
-
});
|
|
33902
|
-
clear2(task.sessionID);
|
|
33903
|
-
this.store.delete(taskId);
|
|
33904
|
-
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
33905
|
-
});
|
|
33906
|
-
}
|
|
33907
|
-
this.store.cleanEmptyNotifications();
|
|
33812
|
+
if (schema.default !== void 0) {
|
|
33813
|
+
zodSchema = zodSchema.default(schema.default);
|
|
33908
33814
|
}
|
|
33909
|
-
|
|
33910
|
-
|
|
33911
|
-
|
|
33912
|
-
|
|
33913
|
-
|
|
33914
|
-
try {
|
|
33915
|
-
await this.sessionPool.release(sessionID);
|
|
33916
|
-
clear2(sessionID);
|
|
33917
|
-
} catch {
|
|
33918
|
-
}
|
|
33919
|
-
}
|
|
33920
|
-
this.store.delete(taskId);
|
|
33921
|
-
if (task) taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
33922
|
-
});
|
|
33923
|
-
log(`Cleaned up ${taskId}`);
|
|
33924
|
-
}, CONFIG.CLEANUP_DELAY_MS);
|
|
33815
|
+
return zodSchema;
|
|
33816
|
+
}
|
|
33817
|
+
function convertSchema(schema, ctx) {
|
|
33818
|
+
if (typeof schema === "boolean") {
|
|
33819
|
+
return schema ? z.any() : z.never();
|
|
33925
33820
|
}
|
|
33926
|
-
|
|
33927
|
-
|
|
33928
|
-
|
|
33929
|
-
|
|
33930
|
-
|
|
33931
|
-
|
|
33932
|
-
|
|
33933
|
-
|
|
33934
|
-
const
|
|
33935
|
-
|
|
33936
|
-
|
|
33937
|
-
|
|
33938
|
-
|
|
33939
|
-
|
|
33940
|
-
|
|
33941
|
-
|
|
33942
|
-
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
toastManager.showAllCompleteToast(parentSessionID, completionInfos);
|
|
33947
|
-
} else if (toastManager) {
|
|
33948
|
-
for (const info of completionInfos) {
|
|
33949
|
-
toastManager.showCompletionToast(info);
|
|
33821
|
+
let baseSchema = convertBaseSchema(schema, ctx);
|
|
33822
|
+
const hasExplicitType = schema.type || schema.enum !== void 0 || schema.const !== void 0;
|
|
33823
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
33824
|
+
const options = schema.anyOf.map((s) => convertSchema(s, ctx));
|
|
33825
|
+
const anyOfUnion = z.union(options);
|
|
33826
|
+
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
|
33827
|
+
}
|
|
33828
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
33829
|
+
const options = schema.oneOf.map((s) => convertSchema(s, ctx));
|
|
33830
|
+
const oneOfUnion = z.xor(options);
|
|
33831
|
+
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
|
33832
|
+
}
|
|
33833
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
33834
|
+
if (schema.allOf.length === 0) {
|
|
33835
|
+
baseSchema = hasExplicitType ? baseSchema : z.any();
|
|
33836
|
+
} else {
|
|
33837
|
+
let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx);
|
|
33838
|
+
const startIdx = hasExplicitType ? 0 : 1;
|
|
33839
|
+
for (let i = startIdx; i < schema.allOf.length; i++) {
|
|
33840
|
+
result = z.intersection(result, convertSchema(schema.allOf[i], ctx));
|
|
33950
33841
|
}
|
|
33842
|
+
baseSchema = result;
|
|
33951
33843
|
}
|
|
33952
|
-
|
|
33953
|
-
|
|
33954
|
-
|
|
33955
|
-
|
|
33956
|
-
|
|
33957
|
-
|
|
33958
|
-
|
|
33959
|
-
|
|
33960
|
-
|
|
33961
|
-
|
|
33962
|
-
|
|
33844
|
+
}
|
|
33845
|
+
if (schema.nullable === true && ctx.version === "openapi-3.0") {
|
|
33846
|
+
baseSchema = z.nullable(baseSchema);
|
|
33847
|
+
}
|
|
33848
|
+
if (schema.readOnly === true) {
|
|
33849
|
+
baseSchema = z.readonly(baseSchema);
|
|
33850
|
+
}
|
|
33851
|
+
const extraMeta = {};
|
|
33852
|
+
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
33853
|
+
for (const key of coreMetadataKeys) {
|
|
33854
|
+
if (key in schema) {
|
|
33855
|
+
extraMeta[key] = schema[key];
|
|
33963
33856
|
}
|
|
33964
|
-
|
|
33965
|
-
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
noReply: !allComplete,
|
|
33970
|
-
parts: [{ type: PART_TYPES.TEXT, text: message }]
|
|
33971
|
-
}
|
|
33972
|
-
});
|
|
33973
|
-
log(`Notified parent ${parentSessionID} (allComplete=${allComplete}, noReply=${!allComplete})`);
|
|
33974
|
-
} catch (error92) {
|
|
33975
|
-
log("Notification error:", error92);
|
|
33857
|
+
}
|
|
33858
|
+
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
33859
|
+
for (const key of contentMetadataKeys) {
|
|
33860
|
+
if (key in schema) {
|
|
33861
|
+
extraMeta[key] = schema[key];
|
|
33976
33862
|
}
|
|
33977
|
-
this.store.clearNotifications(parentSessionID);
|
|
33978
33863
|
}
|
|
33979
|
-
|
|
33980
|
-
|
|
33981
|
-
|
|
33982
|
-
init_shared();
|
|
33983
|
-
|
|
33984
|
-
// src/core/loop/continuation-lock.ts
|
|
33985
|
-
var locks = /* @__PURE__ */ new Map();
|
|
33986
|
-
var LOCK_TIMEOUT_MS = 12e4;
|
|
33987
|
-
function tryAcquireContinuationLock(sessionID, source) {
|
|
33988
|
-
const now = Date.now();
|
|
33989
|
-
const existing = locks.get(sessionID);
|
|
33990
|
-
if (existing?.acquired) {
|
|
33991
|
-
const elapsed = now - existing.timestamp;
|
|
33992
|
-
if (elapsed < LOCK_TIMEOUT_MS) {
|
|
33993
|
-
log("[continuation-lock] Lock denied - already held", {
|
|
33994
|
-
sessionID: sessionID.slice(0, 8),
|
|
33995
|
-
heldBy: existing.source,
|
|
33996
|
-
requestedBy: source,
|
|
33997
|
-
elapsedMs: elapsed
|
|
33998
|
-
});
|
|
33999
|
-
return false;
|
|
33864
|
+
for (const key of Object.keys(schema)) {
|
|
33865
|
+
if (!RECOGNIZED_KEYS.has(key)) {
|
|
33866
|
+
extraMeta[key] = schema[key];
|
|
34000
33867
|
}
|
|
34001
|
-
log("[continuation-lock] Forcing stale lock release", {
|
|
34002
|
-
sessionID: sessionID.slice(0, 8),
|
|
34003
|
-
staleSource: existing.source,
|
|
34004
|
-
elapsedMs: elapsed
|
|
34005
|
-
});
|
|
34006
33868
|
}
|
|
34007
|
-
|
|
34008
|
-
|
|
34009
|
-
sessionID: sessionID.slice(0, 8),
|
|
34010
|
-
source
|
|
34011
|
-
});
|
|
34012
|
-
return true;
|
|
34013
|
-
}
|
|
34014
|
-
function releaseContinuationLock(sessionID) {
|
|
34015
|
-
const existing = locks.get(sessionID);
|
|
34016
|
-
if (existing?.acquired) {
|
|
34017
|
-
const duration5 = Date.now() - existing.timestamp;
|
|
34018
|
-
log("[continuation-lock] Lock released", {
|
|
34019
|
-
sessionID: sessionID.slice(0, 8),
|
|
34020
|
-
source: existing.source,
|
|
34021
|
-
heldMs: duration5
|
|
34022
|
-
});
|
|
33869
|
+
if (Object.keys(extraMeta).length > 0) {
|
|
33870
|
+
ctx.registry.add(baseSchema, extraMeta);
|
|
34023
33871
|
}
|
|
34024
|
-
|
|
33872
|
+
return baseSchema;
|
|
34025
33873
|
}
|
|
34026
|
-
function
|
|
34027
|
-
|
|
34028
|
-
|
|
34029
|
-
if (Date.now() - lock.timestamp >= LOCK_TIMEOUT_MS) {
|
|
34030
|
-
log("[continuation-lock] Stale lock detected during check", {
|
|
34031
|
-
sessionID: sessionID.slice(0, 8)
|
|
34032
|
-
});
|
|
34033
|
-
locks.delete(sessionID);
|
|
34034
|
-
return false;
|
|
33874
|
+
function fromJSONSchema(schema, params) {
|
|
33875
|
+
if (typeof schema === "boolean") {
|
|
33876
|
+
return schema ? z.any() : z.never();
|
|
34035
33877
|
}
|
|
34036
|
-
|
|
33878
|
+
const version3 = detectVersion(schema, params?.defaultTarget);
|
|
33879
|
+
const defs = schema.$defs || schema.definitions || {};
|
|
33880
|
+
const ctx = {
|
|
33881
|
+
version: version3,
|
|
33882
|
+
defs,
|
|
33883
|
+
refs: /* @__PURE__ */ new Map(),
|
|
33884
|
+
processing: /* @__PURE__ */ new Set(),
|
|
33885
|
+
rootSchema: schema,
|
|
33886
|
+
registry: params?.registry ?? globalRegistry2
|
|
33887
|
+
};
|
|
33888
|
+
return convertSchema(schema, ctx);
|
|
34037
33889
|
}
|
|
34038
|
-
|
|
34039
|
-
|
|
33890
|
+
|
|
33891
|
+
// node_modules/zod/v4/classic/coerce.js
|
|
33892
|
+
var coerce_exports2 = {};
|
|
33893
|
+
__export(coerce_exports2, {
|
|
33894
|
+
bigint: () => bigint6,
|
|
33895
|
+
boolean: () => boolean6,
|
|
33896
|
+
date: () => date8,
|
|
33897
|
+
number: () => number6,
|
|
33898
|
+
string: () => string6
|
|
33899
|
+
});
|
|
33900
|
+
function string6(params) {
|
|
33901
|
+
return _coercedString2(ZodString2, params);
|
|
33902
|
+
}
|
|
33903
|
+
function number6(params) {
|
|
33904
|
+
return _coercedNumber2(ZodNumber2, params);
|
|
33905
|
+
}
|
|
33906
|
+
function boolean6(params) {
|
|
33907
|
+
return _coercedBoolean2(ZodBoolean2, params);
|
|
33908
|
+
}
|
|
33909
|
+
function bigint6(params) {
|
|
33910
|
+
return _coercedBigint2(ZodBigInt2, params);
|
|
33911
|
+
}
|
|
33912
|
+
function date8(params) {
|
|
33913
|
+
return _coercedDate2(ZodDate2, params);
|
|
34040
33914
|
}
|
|
34041
33915
|
|
|
34042
|
-
//
|
|
34043
|
-
|
|
34044
|
-
constructor(client2, store, concurrency, findBySession, notifyParentIfAllComplete, scheduleCleanup, validateSessionHasOutput2, onTaskComplete) {
|
|
34045
|
-
this.client = client2;
|
|
34046
|
-
this.store = store;
|
|
34047
|
-
this.concurrency = concurrency;
|
|
34048
|
-
this.findBySession = findBySession;
|
|
34049
|
-
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
34050
|
-
this.scheduleCleanup = scheduleCleanup;
|
|
34051
|
-
this.validateSessionHasOutput = validateSessionHasOutput2;
|
|
34052
|
-
this.onTaskComplete = onTaskComplete;
|
|
34053
|
-
}
|
|
34054
|
-
/**
|
|
34055
|
-
* Handle OpenCode session events for proper resource cleanup.
|
|
34056
|
-
* Call this from your plugin's event hook.
|
|
34057
|
-
*/
|
|
34058
|
-
handle(event) {
|
|
34059
|
-
const props = event.properties;
|
|
34060
|
-
if (event.type === SESSION_EVENTS.IDLE) {
|
|
34061
|
-
const sessionID = props?.sessionID;
|
|
34062
|
-
if (!sessionID) return;
|
|
34063
|
-
recordSessionResponse(sessionID);
|
|
34064
|
-
const task = this.findBySession(sessionID);
|
|
34065
|
-
if (!task || task.status !== TASK_STATUS.RUNNING) return;
|
|
34066
|
-
this.handleSessionIdle(task).catch((err) => {
|
|
34067
|
-
log("Error handling session.idle:", err);
|
|
34068
|
-
});
|
|
34069
|
-
}
|
|
34070
|
-
if (event.type === SESSION_EVENTS.DELETED) {
|
|
34071
|
-
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
34072
|
-
if (!sessionID) return;
|
|
34073
|
-
const task = this.findBySession(sessionID);
|
|
34074
|
-
if (!task) return;
|
|
34075
|
-
this.handleSessionDeleted(task);
|
|
34076
|
-
}
|
|
34077
|
-
}
|
|
34078
|
-
async handleSessionIdle(task) {
|
|
34079
|
-
const elapsed = Date.now() - task.startedAt.getTime();
|
|
34080
|
-
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
34081
|
-
log(`Session idle but too early for ${task.id}, waiting...`);
|
|
34082
|
-
return;
|
|
34083
|
-
}
|
|
34084
|
-
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
34085
|
-
if (!hasOutput) {
|
|
34086
|
-
log(`Session idle but no output for ${task.id}, waiting...`);
|
|
34087
|
-
return;
|
|
34088
|
-
}
|
|
34089
|
-
task.status = TASK_STATUS.COMPLETED;
|
|
34090
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34091
|
-
if (task.concurrencyKey) {
|
|
34092
|
-
this.concurrency.release(task.concurrencyKey);
|
|
34093
|
-
this.concurrency.reportResult(task.concurrencyKey, true);
|
|
34094
|
-
task.concurrencyKey = void 0;
|
|
34095
|
-
}
|
|
34096
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
34097
|
-
this.store.queueNotification(task);
|
|
34098
|
-
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
34099
|
-
this.scheduleCleanup(task.id);
|
|
34100
|
-
taskWAL.log(WAL_ACTIONS.COMPLETE, task).catch(() => {
|
|
34101
|
-
});
|
|
34102
|
-
if (this.onTaskComplete) {
|
|
34103
|
-
Promise.resolve(this.onTaskComplete(task)).catch((err) => log("Error in onTaskComplete callback:", err));
|
|
34104
|
-
}
|
|
34105
|
-
progressNotifier.update();
|
|
34106
|
-
log(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
34107
|
-
}
|
|
34108
|
-
handleSessionDeleted(task) {
|
|
34109
|
-
log(`Session deleted event for task ${task.id}`);
|
|
34110
|
-
if (task.status === TASK_STATUS.RUNNING) {
|
|
34111
|
-
task.status = TASK_STATUS.ERROR;
|
|
34112
|
-
task.error = "Session deleted";
|
|
34113
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34114
|
-
}
|
|
34115
|
-
if (task.concurrencyKey) {
|
|
34116
|
-
this.concurrency.release(task.concurrencyKey);
|
|
34117
|
-
this.concurrency.reportResult(task.concurrencyKey, false);
|
|
34118
|
-
task.concurrencyKey = void 0;
|
|
34119
|
-
}
|
|
34120
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
34121
|
-
this.store.clearNotificationsForTask(task.id);
|
|
34122
|
-
this.store.delete(task.id);
|
|
34123
|
-
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
34124
|
-
});
|
|
34125
|
-
cleanupSessionHealth(task.sessionID);
|
|
34126
|
-
cleanupContinuationLock(task.sessionID);
|
|
34127
|
-
progressNotifier.update();
|
|
34128
|
-
log(`Cleaned up deleted session task: ${task.id}`);
|
|
34129
|
-
}
|
|
34130
|
-
};
|
|
33916
|
+
// node_modules/zod/v4/classic/external.js
|
|
33917
|
+
config2(en_default2());
|
|
34131
33918
|
|
|
34132
|
-
// src/core/agents/
|
|
34133
|
-
|
|
34134
|
-
|
|
34135
|
-
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34145
|
-
//
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
constructor(client2, directory, config3 = {}) {
|
|
34157
|
-
this.client = client2;
|
|
34158
|
-
this.directory = directory;
|
|
34159
|
-
this.config = { ...DEFAULT_CONFIG, ...config3 };
|
|
34160
|
-
this.startHealthCheck();
|
|
34161
|
-
}
|
|
34162
|
-
static getInstance(client2, directory, config3) {
|
|
34163
|
-
if (!_SessionPool._instance) {
|
|
34164
|
-
if (!client2 || !directory) {
|
|
34165
|
-
throw new Error("SessionPool requires client and directory on first call");
|
|
34166
|
-
}
|
|
34167
|
-
_SessionPool._instance = new _SessionPool(client2, directory, config3);
|
|
34168
|
-
}
|
|
34169
|
-
return _SessionPool._instance;
|
|
34170
|
-
}
|
|
34171
|
-
/**
|
|
34172
|
-
* Acquire a session from the pool or create a new one.
|
|
34173
|
-
*/
|
|
34174
|
-
async acquire(agentName, parentSessionID, description) {
|
|
34175
|
-
const poolKey = this.getPoolKey(agentName);
|
|
34176
|
-
const agentPool = this.pool.get(poolKey) || [];
|
|
34177
|
-
const available = agentPool.find((s) => !s.inUse && s.reuseCount < this.config.maxReuseCount);
|
|
34178
|
-
if (available) {
|
|
34179
|
-
available.inUse = true;
|
|
34180
|
-
available.lastUsedAt = /* @__PURE__ */ new Date();
|
|
34181
|
-
available.reuseCount++;
|
|
34182
|
-
this.stats.reuseHits++;
|
|
34183
|
-
log(`[SessionPool] Reusing session ${available.id.slice(0, 8)}... for ${agentName} (reuse #${available.reuseCount})`);
|
|
34184
|
-
return available;
|
|
33919
|
+
// src/core/agents/agent-registry.ts
|
|
33920
|
+
var AgentDefinitionSchema = external_exports2.object({
|
|
33921
|
+
id: external_exports2.string(),
|
|
33922
|
+
// ID is required inside the definition object
|
|
33923
|
+
description: external_exports2.string(),
|
|
33924
|
+
systemPrompt: external_exports2.string(),
|
|
33925
|
+
mode: external_exports2.enum(["primary", "subagent"]).optional(),
|
|
33926
|
+
color: external_exports2.string().optional(),
|
|
33927
|
+
hidden: external_exports2.boolean().optional(),
|
|
33928
|
+
thinking: external_exports2.boolean().optional(),
|
|
33929
|
+
maxTokens: external_exports2.number().optional(),
|
|
33930
|
+
budgetTokens: external_exports2.number().optional(),
|
|
33931
|
+
canWrite: external_exports2.boolean(),
|
|
33932
|
+
// Required per interface
|
|
33933
|
+
canBash: external_exports2.boolean()
|
|
33934
|
+
// Required per interface
|
|
33935
|
+
});
|
|
33936
|
+
var AgentRegistry = class _AgentRegistry {
|
|
33937
|
+
static instance;
|
|
33938
|
+
agents = /* @__PURE__ */ new Map();
|
|
33939
|
+
directory = "";
|
|
33940
|
+
constructor() {
|
|
33941
|
+
for (const [name, def] of Object.entries(AGENTS)) {
|
|
33942
|
+
this.agents.set(name, def);
|
|
34185
33943
|
}
|
|
34186
|
-
this.stats.creationMisses++;
|
|
34187
|
-
return this.createSession(agentName, parentSessionID, description);
|
|
34188
33944
|
}
|
|
34189
|
-
|
|
34190
|
-
|
|
34191
|
-
|
|
34192
|
-
async release(sessionId) {
|
|
34193
|
-
const session = this.sessionsById.get(sessionId);
|
|
34194
|
-
if (!session) {
|
|
34195
|
-
log(`[SessionPool] Session ${sessionId.slice(0, 8)}... not found in pool`);
|
|
34196
|
-
return;
|
|
34197
|
-
}
|
|
34198
|
-
const now = Date.now();
|
|
34199
|
-
const age = now - session.createdAt.getTime();
|
|
34200
|
-
const idle = now - session.lastUsedAt.getTime();
|
|
34201
|
-
if (session.reuseCount >= this.config.maxReuseCount || age > this.config.idleTimeoutMs * 2) {
|
|
34202
|
-
await this.invalidate(sessionId);
|
|
34203
|
-
return;
|
|
34204
|
-
}
|
|
34205
|
-
const poolKey = this.getPoolKey(session.agentName);
|
|
34206
|
-
const agentPool = this.pool.get(poolKey) || [];
|
|
34207
|
-
const availableCount = agentPool.filter((s) => !s.inUse).length;
|
|
34208
|
-
if (availableCount >= this.config.maxPoolSizePerAgent) {
|
|
34209
|
-
const oldest = agentPool.filter((s) => !s.inUse).sort((a, b) => a.lastUsedAt.getTime() - b.lastUsedAt.getTime())[0];
|
|
34210
|
-
if (oldest) {
|
|
34211
|
-
await this.deleteSession(oldest.id);
|
|
34212
|
-
}
|
|
33945
|
+
static getInstance() {
|
|
33946
|
+
if (!_AgentRegistry.instance) {
|
|
33947
|
+
_AgentRegistry.instance = new _AgentRegistry();
|
|
34213
33948
|
}
|
|
34214
|
-
|
|
34215
|
-
session.inUse = false;
|
|
34216
|
-
log(`[SessionPool] Released session ${sessionId.slice(0, 8)}... to pool`);
|
|
33949
|
+
return _AgentRegistry.instance;
|
|
34217
33950
|
}
|
|
34218
|
-
|
|
34219
|
-
|
|
34220
|
-
|
|
34221
|
-
async invalidate(sessionId) {
|
|
34222
|
-
const session = this.sessionsById.get(sessionId);
|
|
34223
|
-
if (!session) return;
|
|
34224
|
-
await this.deleteSession(sessionId);
|
|
34225
|
-
log(`[SessionPool] Invalidated session ${sessionId.slice(0, 8)}...`);
|
|
33951
|
+
setDirectory(dir) {
|
|
33952
|
+
this.directory = dir;
|
|
33953
|
+
this.loadCustomAgents();
|
|
34226
33954
|
}
|
|
34227
33955
|
/**
|
|
34228
|
-
* Get
|
|
33956
|
+
* Get agent definition by name
|
|
34229
33957
|
*/
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
for (const [agentName, sessions] of this.pool.entries()) {
|
|
34233
|
-
const inUse = sessions.filter((s) => s.inUse).length;
|
|
34234
|
-
byAgent[agentName] = {
|
|
34235
|
-
total: sessions.length,
|
|
34236
|
-
inUse,
|
|
34237
|
-
available: sessions.length - inUse
|
|
34238
|
-
};
|
|
34239
|
-
}
|
|
34240
|
-
const allSessions = Array.from(this.sessionsById.values());
|
|
34241
|
-
const inUseCount = allSessions.filter((s) => s.inUse).length;
|
|
34242
|
-
return {
|
|
34243
|
-
totalSessions: allSessions.length,
|
|
34244
|
-
sessionsInUse: inUseCount,
|
|
34245
|
-
availableSessions: allSessions.length - inUseCount,
|
|
34246
|
-
reuseHits: this.stats.reuseHits,
|
|
34247
|
-
creationMisses: this.stats.creationMisses,
|
|
34248
|
-
byAgent
|
|
34249
|
-
};
|
|
33958
|
+
getAgent(name) {
|
|
33959
|
+
return this.agents.get(name);
|
|
34250
33960
|
}
|
|
34251
33961
|
/**
|
|
34252
|
-
*
|
|
33962
|
+
* List all available agent names
|
|
34253
33963
|
*/
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
let cleanedCount = 0;
|
|
34257
|
-
for (const [sessionId, session] of this.sessionsById.entries()) {
|
|
34258
|
-
if (session.inUse) continue;
|
|
34259
|
-
const idle = now - session.lastUsedAt.getTime();
|
|
34260
|
-
if (idle > this.config.idleTimeoutMs) {
|
|
34261
|
-
await this.deleteSession(sessionId);
|
|
34262
|
-
cleanedCount++;
|
|
34263
|
-
}
|
|
34264
|
-
}
|
|
34265
|
-
if (cleanedCount > 0) {
|
|
34266
|
-
log(`[SessionPool] Cleaned up ${cleanedCount} stale sessions`);
|
|
34267
|
-
}
|
|
34268
|
-
return cleanedCount;
|
|
33964
|
+
listAgents() {
|
|
33965
|
+
return Array.from(this.agents.keys());
|
|
34269
33966
|
}
|
|
34270
33967
|
/**
|
|
34271
|
-
*
|
|
33968
|
+
* Add or update an agent definition
|
|
34272
33969
|
*/
|
|
34273
|
-
|
|
34274
|
-
|
|
34275
|
-
|
|
34276
|
-
clearInterval(this.healthCheckInterval);
|
|
34277
|
-
this.healthCheckInterval = null;
|
|
34278
|
-
}
|
|
34279
|
-
const deletePromises = Array.from(this.sessionsById.keys()).map(
|
|
34280
|
-
(id) => this.deleteSession(id).catch(() => {
|
|
34281
|
-
})
|
|
34282
|
-
);
|
|
34283
|
-
await Promise.all(deletePromises);
|
|
34284
|
-
this.pool.clear();
|
|
34285
|
-
this.sessionsById.clear();
|
|
34286
|
-
log("[SessionPool] Shutdown complete");
|
|
33970
|
+
registerAgent(name, def) {
|
|
33971
|
+
this.agents.set(name, def);
|
|
33972
|
+
log(`[AgentRegistry] Registered agent: ${name}`);
|
|
34287
33973
|
}
|
|
34288
|
-
// =========================================================================
|
|
34289
|
-
// Private Methods
|
|
34290
|
-
// =========================================================================
|
|
34291
33974
|
/**
|
|
34292
|
-
*
|
|
33975
|
+
* Load custom agents from .opencode/agents.json
|
|
34293
33976
|
*/
|
|
34294
|
-
async
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
log(`[SessionPool] Resetting session ${sessionId.slice(0, 8)}...`);
|
|
33977
|
+
async loadCustomAgents() {
|
|
33978
|
+
if (!this.directory) return;
|
|
33979
|
+
const agentsConfigPath = path4.join(this.directory, PATHS.AGENTS_CONFIG);
|
|
34298
33980
|
try {
|
|
34299
|
-
await
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
|
|
34304
|
-
|
|
34305
|
-
|
|
34306
|
-
|
|
34307
|
-
|
|
34308
|
-
|
|
34309
|
-
|
|
34310
|
-
async createSession(agentName, parentSessionID, description) {
|
|
34311
|
-
log(`[SessionPool] Creating new session for ${agentName}`);
|
|
34312
|
-
const result = await Promise.race([
|
|
34313
|
-
this.client.session.create({
|
|
34314
|
-
body: {
|
|
34315
|
-
parentID: parentSessionID,
|
|
34316
|
-
title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${description}`
|
|
34317
|
-
},
|
|
34318
|
-
query: { directory: this.directory }
|
|
34319
|
-
}),
|
|
34320
|
-
new Promise(
|
|
34321
|
-
(_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 60s")), 6e4)
|
|
34322
|
-
)
|
|
34323
|
-
]);
|
|
34324
|
-
if (result.error || !result.data?.id) {
|
|
34325
|
-
throw new Error(`Session creation failed: ${result.error || "No ID"}`);
|
|
34326
|
-
}
|
|
34327
|
-
const session = {
|
|
34328
|
-
id: result.data.id,
|
|
34329
|
-
agentName,
|
|
34330
|
-
projectDirectory: this.directory,
|
|
34331
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
34332
|
-
lastUsedAt: /* @__PURE__ */ new Date(),
|
|
34333
|
-
reuseCount: 0,
|
|
34334
|
-
inUse: true,
|
|
34335
|
-
health: "healthy",
|
|
34336
|
-
lastResetAt: /* @__PURE__ */ new Date()
|
|
34337
|
-
};
|
|
34338
|
-
const poolKey = this.getPoolKey(agentName);
|
|
34339
|
-
const agentPool = this.pool.get(poolKey) || [];
|
|
34340
|
-
agentPool.push(session);
|
|
34341
|
-
this.pool.set(poolKey, agentPool);
|
|
34342
|
-
this.sessionsById.set(session.id, session);
|
|
34343
|
-
return session;
|
|
34344
|
-
}
|
|
34345
|
-
async deleteSession(sessionId) {
|
|
34346
|
-
const session = this.sessionsById.get(sessionId);
|
|
34347
|
-
if (!session) return;
|
|
34348
|
-
this.sessionsById.delete(sessionId);
|
|
34349
|
-
const poolKey = this.getPoolKey(session.agentName);
|
|
34350
|
-
const agentPool = this.pool.get(poolKey);
|
|
34351
|
-
if (agentPool) {
|
|
34352
|
-
const idx = agentPool.findIndex((s) => s.id === sessionId);
|
|
34353
|
-
if (idx !== -1) {
|
|
34354
|
-
agentPool.splice(idx, 1);
|
|
33981
|
+
const content = await fs4.readFile(agentsConfigPath, "utf-8");
|
|
33982
|
+
const customAgents = JSON.parse(content);
|
|
33983
|
+
if (typeof customAgents === "object" && customAgents !== null) {
|
|
33984
|
+
for (const [name, def] of Object.entries(customAgents)) {
|
|
33985
|
+
const result = AgentDefinitionSchema.safeParse(def);
|
|
33986
|
+
if (result.success) {
|
|
33987
|
+
this.registerAgent(name, def);
|
|
33988
|
+
} else {
|
|
33989
|
+
log(`[AgentRegistry] Invalid custom agent definition for: ${name}. Errors: ${result.error.message}`);
|
|
33990
|
+
}
|
|
33991
|
+
}
|
|
34355
33992
|
}
|
|
34356
|
-
|
|
34357
|
-
|
|
33993
|
+
} catch (error92) {
|
|
33994
|
+
if (error92.code !== "ENOENT") {
|
|
33995
|
+
log(`[AgentRegistry] Error loading custom agents: ${error92}`);
|
|
34358
33996
|
}
|
|
34359
33997
|
}
|
|
34360
|
-
try {
|
|
34361
|
-
await this.client.session.delete({ path: { id: sessionId } });
|
|
34362
|
-
} catch {
|
|
34363
|
-
}
|
|
34364
|
-
}
|
|
34365
|
-
startHealthCheck() {
|
|
34366
|
-
this.healthCheckInterval = setInterval(() => {
|
|
34367
|
-
this.cleanup().catch(() => {
|
|
34368
|
-
});
|
|
34369
|
-
}, this.config.healthCheckIntervalMs);
|
|
34370
|
-
this.healthCheckInterval.unref?.();
|
|
34371
33998
|
}
|
|
34372
33999
|
};
|
|
34373
|
-
var sessionPool = {
|
|
34374
|
-
getInstance: SessionPool.getInstance.bind(SessionPool)
|
|
34375
|
-
};
|
|
34376
|
-
|
|
34377
|
-
// src/core/agents/manager.ts
|
|
34378
|
-
init_core2();
|
|
34379
34000
|
|
|
34380
34001
|
// src/core/todo/todo-manager.ts
|
|
34381
34002
|
init_shared();
|
|
34382
34003
|
import * as fs5 from "node:fs";
|
|
34383
34004
|
import * as path5 from "node:path";
|
|
34384
|
-
import * as
|
|
34005
|
+
import * as crypto from "node:crypto";
|
|
34385
34006
|
var TodoManager = class _TodoManager {
|
|
34386
34007
|
static _instance;
|
|
34387
34008
|
directory = "";
|
|
@@ -34451,13 +34072,22 @@ var TodoManager = class _TodoManager {
|
|
|
34451
34072
|
});
|
|
34452
34073
|
}
|
|
34453
34074
|
async _internalUpdate(expectedVersion, updater, author) {
|
|
34454
|
-
const MAX_RETRIES2 =
|
|
34455
|
-
const
|
|
34075
|
+
const MAX_RETRIES2 = 5;
|
|
34076
|
+
const BASE_DELAY_MS = 50;
|
|
34456
34077
|
for (let attempt = 0; attempt < MAX_RETRIES2; attempt++) {
|
|
34457
34078
|
try {
|
|
34458
34079
|
const current = await this.readWithVersion();
|
|
34459
34080
|
if (current.version.version !== expectedVersion) {
|
|
34460
34081
|
log(`[TodoManager] Conflict: expected v${expectedVersion}, found v${current.version.version}`);
|
|
34082
|
+
if (attempt < MAX_RETRIES2 - 1) {
|
|
34083
|
+
const backoffDelay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
34084
|
+
const jitter = Math.random() * 50;
|
|
34085
|
+
const totalDelay = backoffDelay + jitter;
|
|
34086
|
+
log(`[TodoManager] Retrying in ${Math.round(totalDelay)}ms (attempt ${attempt + 1}/${MAX_RETRIES2})`);
|
|
34087
|
+
await new Promise((r) => setTimeout(r, totalDelay));
|
|
34088
|
+
expectedVersion = current.version.version;
|
|
34089
|
+
continue;
|
|
34090
|
+
}
|
|
34461
34091
|
return {
|
|
34462
34092
|
success: false,
|
|
34463
34093
|
currentVersion: current.version.version,
|
|
@@ -34480,18 +34110,21 @@ var TodoManager = class _TodoManager {
|
|
|
34480
34110
|
await fs5.promises.rename(tmpPath, this.todoPath);
|
|
34481
34111
|
this.logChange(newVersion, newContent, author).catch(() => {
|
|
34482
34112
|
});
|
|
34483
|
-
log(`[TodoManager] Updated TODO to v${newVersion} by ${author}`);
|
|
34113
|
+
log(`[TodoManager] Updated TODO to v${newVersion} by ${author}${attempt > 0 ? ` (after ${attempt} retries)` : ""}`);
|
|
34484
34114
|
return { success: true, currentVersion: newVersion };
|
|
34485
34115
|
} catch (error92) {
|
|
34486
34116
|
if (attempt === MAX_RETRIES2 - 1) throw error92;
|
|
34487
|
-
|
|
34117
|
+
const backoffDelay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
34118
|
+
const jitter = Math.random() * 50;
|
|
34119
|
+
await new Promise((r) => setTimeout(r, backoffDelay + jitter));
|
|
34488
34120
|
}
|
|
34489
34121
|
}
|
|
34490
|
-
throw new Error("Failed to update TODO");
|
|
34122
|
+
throw new Error("Failed to update TODO after max retries");
|
|
34491
34123
|
}
|
|
34492
34124
|
async updateItem(searchText, newStatus, author = "system") {
|
|
34493
|
-
|
|
34494
|
-
|
|
34125
|
+
const MAX_RETRIES2 = 5;
|
|
34126
|
+
const BASE_DELAY_MS = 50;
|
|
34127
|
+
for (let attempt = 0; attempt < MAX_RETRIES2; attempt++) {
|
|
34495
34128
|
const data = await this.readWithVersion();
|
|
34496
34129
|
const statusMap = {
|
|
34497
34130
|
[TODO_CONSTANTS.STATUS.PENDING]: TODO_CONSTANTS.MARKERS.PENDING,
|
|
@@ -34514,13 +34147,18 @@ var TodoManager = class _TodoManager {
|
|
|
34514
34147
|
}, author);
|
|
34515
34148
|
if (result.success) return true;
|
|
34516
34149
|
if (!result.conflict) return false;
|
|
34517
|
-
|
|
34150
|
+
if (attempt < MAX_RETRIES2 - 1) {
|
|
34151
|
+
const backoffDelay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
34152
|
+
const jitter = Math.random() * 50;
|
|
34153
|
+
await new Promise((r) => setTimeout(r, backoffDelay + jitter));
|
|
34154
|
+
}
|
|
34518
34155
|
}
|
|
34519
34156
|
return false;
|
|
34520
34157
|
}
|
|
34521
34158
|
async addSubTask(parentText, subTaskText, author = "system") {
|
|
34522
|
-
|
|
34523
|
-
|
|
34159
|
+
const MAX_RETRIES2 = 5;
|
|
34160
|
+
const BASE_DELAY_MS = 50;
|
|
34161
|
+
for (let attempt = 0; attempt < MAX_RETRIES2; attempt++) {
|
|
34524
34162
|
const data = await this.readWithVersion();
|
|
34525
34163
|
const result = await this.update(data.version.version, (content) => {
|
|
34526
34164
|
const lines = content.split("\n");
|
|
@@ -34544,7 +34182,11 @@ var TodoManager = class _TodoManager {
|
|
|
34544
34182
|
}, author);
|
|
34545
34183
|
if (result.success) return true;
|
|
34546
34184
|
if (!result.conflict) return false;
|
|
34547
|
-
|
|
34185
|
+
if (attempt < MAX_RETRIES2 - 1) {
|
|
34186
|
+
const backoffDelay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
34187
|
+
const jitter = Math.random() * 50;
|
|
34188
|
+
await new Promise((r) => setTimeout(r, backoffDelay + jitter));
|
|
34189
|
+
}
|
|
34548
34190
|
}
|
|
34549
34191
|
return false;
|
|
34550
34192
|
}
|
|
@@ -34553,7 +34195,7 @@ var TodoManager = class _TodoManager {
|
|
|
34553
34195
|
version: version3,
|
|
34554
34196
|
timestamp: Date.now(),
|
|
34555
34197
|
author,
|
|
34556
|
-
contentHash:
|
|
34198
|
+
contentHash: crypto.createHash("sha256").update(content).digest("hex"),
|
|
34557
34199
|
size: content.length
|
|
34558
34200
|
};
|
|
34559
34201
|
await fs5.promises.appendFile(this.historyPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
@@ -34568,12 +34210,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34568
34210
|
directory;
|
|
34569
34211
|
concurrency = new ConcurrencyController();
|
|
34570
34212
|
sessionPool;
|
|
34571
|
-
//
|
|
34572
|
-
|
|
34573
|
-
resumer;
|
|
34574
|
-
poller;
|
|
34575
|
-
cleaner;
|
|
34576
|
-
eventHandler;
|
|
34213
|
+
// Unified executor (replaces 5 separate components)
|
|
34214
|
+
executor;
|
|
34577
34215
|
constructor(client2, directory) {
|
|
34578
34216
|
this.client = client2;
|
|
34579
34217
|
this.directory = directory;
|
|
@@ -34584,42 +34222,12 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34584
34222
|
AgentRegistry.getInstance().setDirectory(directory);
|
|
34585
34223
|
TodoManager.getInstance().setDirectory(directory);
|
|
34586
34224
|
this.sessionPool = SessionPool.getInstance(client2, directory);
|
|
34587
|
-
this.
|
|
34588
|
-
this.poller = new TaskPoller(
|
|
34589
|
-
client2,
|
|
34590
|
-
this.store,
|
|
34591
|
-
this.concurrency,
|
|
34592
|
-
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
34593
|
-
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
34594
|
-
() => this.cleaner.pruneExpiredTasks(),
|
|
34595
|
-
(task) => this.handleTaskComplete(task),
|
|
34596
|
-
(taskId, error92) => this.handleTaskError(taskId, error92)
|
|
34597
|
-
);
|
|
34598
|
-
this.launcher = new TaskLauncher(
|
|
34225
|
+
this.executor = new UnifiedTaskExecutor(
|
|
34599
34226
|
client2,
|
|
34600
34227
|
directory,
|
|
34601
34228
|
this.store,
|
|
34602
34229
|
this.concurrency,
|
|
34603
|
-
this.sessionPool
|
|
34604
|
-
(taskId, error92) => this.handleTaskError(taskId, error92),
|
|
34605
|
-
() => this.poller.start()
|
|
34606
|
-
);
|
|
34607
|
-
this.resumer = new TaskResumer(
|
|
34608
|
-
client2,
|
|
34609
|
-
this.store,
|
|
34610
|
-
(sessionID) => this.findBySession(sessionID),
|
|
34611
|
-
() => this.poller.start(),
|
|
34612
|
-
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID)
|
|
34613
|
-
);
|
|
34614
|
-
this.eventHandler = new EventHandler(
|
|
34615
|
-
client2,
|
|
34616
|
-
this.store,
|
|
34617
|
-
this.concurrency,
|
|
34618
|
-
(sessionID) => this.findBySession(sessionID),
|
|
34619
|
-
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
34620
|
-
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
34621
|
-
(sessionID) => this.poller.validateSessionHasOutput(sessionID),
|
|
34622
|
-
(task) => this.handleTaskComplete(task)
|
|
34230
|
+
this.sessionPool
|
|
34623
34231
|
);
|
|
34624
34232
|
progressNotifier.setManager(this);
|
|
34625
34233
|
this.recoverActiveTasks().catch((err) => {
|
|
@@ -34639,13 +34247,22 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34639
34247
|
// Public API
|
|
34640
34248
|
// ========================================================================
|
|
34641
34249
|
async launch(inputs) {
|
|
34642
|
-
|
|
34643
|
-
|
|
34644
|
-
|
|
34645
|
-
|
|
34250
|
+
if (Array.isArray(inputs)) {
|
|
34251
|
+
const results = await Promise.all(inputs.map((input) => this.executor.launch(input)));
|
|
34252
|
+
progressNotifier.update();
|
|
34253
|
+
return results;
|
|
34254
|
+
} else {
|
|
34255
|
+
const result = await this.executor.launch(inputs);
|
|
34256
|
+
progressNotifier.update();
|
|
34257
|
+
return result;
|
|
34258
|
+
}
|
|
34646
34259
|
}
|
|
34647
34260
|
async resume(input) {
|
|
34648
|
-
|
|
34261
|
+
const task = await this.executor.resume(input);
|
|
34262
|
+
if (!task) {
|
|
34263
|
+
throw new Error(`Task not found: ${input.sessionId}`);
|
|
34264
|
+
}
|
|
34265
|
+
return task;
|
|
34649
34266
|
}
|
|
34650
34267
|
getTask(id) {
|
|
34651
34268
|
return this.store.get(id);
|
|
@@ -34660,25 +34277,11 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34660
34277
|
return this.store.getByParent(parentSessionID);
|
|
34661
34278
|
}
|
|
34662
34279
|
async cancelTask(taskId) {
|
|
34663
|
-
const
|
|
34664
|
-
if (
|
|
34665
|
-
|
|
34666
|
-
task.error = "Cancelled by user";
|
|
34667
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34668
|
-
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
34669
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
34670
|
-
try {
|
|
34671
|
-
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
34672
|
-
log(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
34673
|
-
} catch {
|
|
34674
|
-
log(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
34280
|
+
const result = await this.executor.cancel(taskId);
|
|
34281
|
+
if (result) {
|
|
34282
|
+
progressNotifier.update();
|
|
34675
34283
|
}
|
|
34676
|
-
|
|
34677
|
-
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
34678
|
-
});
|
|
34679
|
-
progressNotifier.update();
|
|
34680
|
-
log(`Cancelled ${taskId}`);
|
|
34681
|
-
return true;
|
|
34284
|
+
return result;
|
|
34682
34285
|
}
|
|
34683
34286
|
async getResult(taskId) {
|
|
34684
34287
|
const task = this.store.get(taskId);
|
|
@@ -34709,7 +34312,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34709
34312
|
return this.concurrency;
|
|
34710
34313
|
}
|
|
34711
34314
|
cleanup() {
|
|
34712
|
-
this.
|
|
34315
|
+
this.executor.cleanup();
|
|
34713
34316
|
stopHealthCheck();
|
|
34714
34317
|
this.store.clear();
|
|
34715
34318
|
MemoryManager.getInstance().clearTaskMemory();
|
|
@@ -34721,7 +34324,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34721
34324
|
// Event Handling
|
|
34722
34325
|
// ========================================================================
|
|
34723
34326
|
handleEvent(event) {
|
|
34724
|
-
|
|
34327
|
+
log("[ParallelAgentManager] Event received:", event.type);
|
|
34725
34328
|
}
|
|
34726
34329
|
// ========================================================================
|
|
34727
34330
|
// Private Helpers
|
|
@@ -34730,87 +34333,17 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34730
34333
|
return this.store.getAll().find((t) => t.sessionID === sessionID);
|
|
34731
34334
|
}
|
|
34732
34335
|
handleTaskError(taskId, error92) {
|
|
34733
|
-
|
|
34734
|
-
if (!task) return;
|
|
34735
|
-
task.status = TASK_STATUS.ERROR;
|
|
34736
|
-
task.error = error92 instanceof Error ? error92.message : String(error92);
|
|
34737
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34738
|
-
if (task.concurrencyKey) {
|
|
34739
|
-
this.concurrency.release(task.concurrencyKey);
|
|
34740
|
-
this.concurrency.reportResult(task.concurrencyKey, false);
|
|
34741
|
-
}
|
|
34742
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
34743
|
-
this.cleaner.notifyParentIfAllComplete(task.parentSessionID);
|
|
34744
|
-
this.cleaner.scheduleCleanup(taskId);
|
|
34745
|
-
progressNotifier.update();
|
|
34746
|
-
taskWAL.log(WAL_ACTIONS.UPDATE, task).catch(() => {
|
|
34747
|
-
});
|
|
34336
|
+
log(`[ParallelAgentManager] Delegating error handling to executor for task ${taskId}`);
|
|
34748
34337
|
}
|
|
34749
34338
|
async handleTaskComplete(task) {
|
|
34750
|
-
|
|
34751
|
-
log(`[MSVP] Triggering Unit Review for task ${task.id}`);
|
|
34752
|
-
try {
|
|
34753
|
-
await this.launch({
|
|
34754
|
-
agent: AGENT_NAMES.REVIEWER,
|
|
34755
|
-
description: `Unit Review: ${task.description}`,
|
|
34756
|
-
prompt: `Perform a Unit Review (verification) for the completed task (\`${task.description}\`).
|
|
34757
|
-
Key Checklist:
|
|
34758
|
-
1. Verify if unit test code for the module is written and passes.
|
|
34759
|
-
2. Check for code quality and modularity compliance.
|
|
34760
|
-
3. Instruct immediate correction of found defects or report them.
|
|
34761
|
-
|
|
34762
|
-
This task ensures the completeness of the unit before global integration.`,
|
|
34763
|
-
parentSessionID: task.parentSessionID,
|
|
34764
|
-
depth: task.depth,
|
|
34765
|
-
groupID: task.groupID || task.id
|
|
34766
|
-
// Group reviews with their origins
|
|
34767
|
-
});
|
|
34768
|
-
} catch (error92) {
|
|
34769
|
-
log(`[MSVP] Failed to trigger review for ${task.id}:`, error92);
|
|
34770
|
-
}
|
|
34771
|
-
}
|
|
34339
|
+
log(`[ParallelAgentManager] Task ${task.id} completed`);
|
|
34772
34340
|
progressNotifier.update();
|
|
34773
34341
|
}
|
|
34774
34342
|
async recoverActiveTasks() {
|
|
34775
|
-
const
|
|
34776
|
-
if (tasksMap.size === 0) return;
|
|
34777
|
-
const tasks = Array.from(tasksMap.values());
|
|
34778
|
-
log(`Attempting to recover ${tasks.length} tasks from WAL in parallel...`);
|
|
34779
|
-
let recoveredCount = 0;
|
|
34780
|
-
const chunks = [];
|
|
34781
|
-
const chunkSize = 10;
|
|
34782
|
-
for (let i = 0; i < tasks.length; i += chunkSize) {
|
|
34783
|
-
chunks.push(tasks.slice(i, i + chunkSize));
|
|
34784
|
-
}
|
|
34785
|
-
for (const chunk of chunks) {
|
|
34786
|
-
await Promise.all(chunk.map(async (task) => {
|
|
34787
|
-
if (task.status === TASK_STATUS.RUNNING) {
|
|
34788
|
-
try {
|
|
34789
|
-
const status = await this.client.session.get({ path: { id: task.sessionID } });
|
|
34790
|
-
if (!status.error) {
|
|
34791
|
-
this.store.set(task.id, task);
|
|
34792
|
-
this.store.trackPending(task.parentSessionID, task.id);
|
|
34793
|
-
const toastManager = getTaskToastManager();
|
|
34794
|
-
if (toastManager) {
|
|
34795
|
-
toastManager.addTask({
|
|
34796
|
-
id: task.id,
|
|
34797
|
-
description: task.description,
|
|
34798
|
-
agent: task.agent,
|
|
34799
|
-
isBackground: true,
|
|
34800
|
-
parentSessionID: task.parentSessionID,
|
|
34801
|
-
sessionID: task.sessionID
|
|
34802
|
-
});
|
|
34803
|
-
}
|
|
34804
|
-
recoveredCount++;
|
|
34805
|
-
}
|
|
34806
|
-
} catch {
|
|
34807
|
-
}
|
|
34808
|
-
}
|
|
34809
|
-
}));
|
|
34810
|
-
}
|
|
34343
|
+
const recoveredCount = await this.executor.recoverAll();
|
|
34811
34344
|
if (recoveredCount > 0) {
|
|
34812
|
-
log(`Recovered ${recoveredCount} active tasks.`);
|
|
34813
|
-
|
|
34345
|
+
log(`Recovered ${recoveredCount} active tasks via UnifiedTaskExecutor.`);
|
|
34346
|
+
progressNotifier.update();
|
|
34814
34347
|
}
|
|
34815
34348
|
}
|
|
34816
34349
|
};
|
|
@@ -35504,7 +35037,7 @@ async function list() {
|
|
|
35504
35037
|
expired: new Date(entry.expiresAt) < now
|
|
35505
35038
|
}));
|
|
35506
35039
|
}
|
|
35507
|
-
async function
|
|
35040
|
+
async function clear2() {
|
|
35508
35041
|
const metadata = await readMetadata();
|
|
35509
35042
|
const count = Object.keys(metadata.documents).length;
|
|
35510
35043
|
for (const filename of Object.keys(metadata.documents)) {
|
|
@@ -35990,7 +35523,7 @@ Cached: ${doc.fetchedAt}
|
|
|
35990
35523
|
${doc.content}`;
|
|
35991
35524
|
}
|
|
35992
35525
|
case CACHE_ACTIONS.CLEAR: {
|
|
35993
|
-
const count = await
|
|
35526
|
+
const count = await clear2();
|
|
35994
35527
|
return `Cleared ${count} cached documents`;
|
|
35995
35528
|
}
|
|
35996
35529
|
case CACHE_ACTIONS.STATS: {
|
|
@@ -36258,6 +35791,10 @@ Runs TypeScript compiler and/or ESLint to find issues.
|
|
|
36258
35791
|
// src/index.ts
|
|
36259
35792
|
init_shared();
|
|
36260
35793
|
|
|
35794
|
+
// src/core/notification/toast.ts
|
|
35795
|
+
init_toast_core();
|
|
35796
|
+
init_shared();
|
|
35797
|
+
|
|
36261
35798
|
// src/hooks/constants.ts
|
|
36262
35799
|
init_shared();
|
|
36263
35800
|
var HOOK_ACTIONS = {
|
|
@@ -36913,7 +36450,7 @@ function getLatest(sessionId) {
|
|
|
36913
36450
|
const history = progressHistory.get(sessionId);
|
|
36914
36451
|
return history?.[history.length - 1];
|
|
36915
36452
|
}
|
|
36916
|
-
function
|
|
36453
|
+
function clearSession(sessionId) {
|
|
36917
36454
|
progressHistory.delete(sessionId);
|
|
36918
36455
|
sessionStartTimes.delete(sessionId);
|
|
36919
36456
|
}
|
|
@@ -37244,15 +36781,29 @@ function verifyMissionCompletionSync(directory) {
|
|
|
37244
36781
|
}
|
|
37245
36782
|
if (hasChecklist) {
|
|
37246
36783
|
result.passed = result.checklistComplete && result.syncIssuesEmpty;
|
|
36784
|
+
if (!result.passed && result.errors.length === 0) {
|
|
36785
|
+
result.errors.push("Checklist verification failed - complete all items");
|
|
36786
|
+
}
|
|
37247
36787
|
} else {
|
|
37248
36788
|
result.passed = result.todoComplete && result.syncIssuesEmpty;
|
|
36789
|
+
if (!result.passed && result.errors.length === 0) {
|
|
36790
|
+
if (!result.todoComplete) {
|
|
36791
|
+
result.errors.push("TODO verification failed - complete all items");
|
|
36792
|
+
}
|
|
36793
|
+
if (!result.syncIssuesEmpty) {
|
|
36794
|
+
result.errors.push("Sync issues must be resolved");
|
|
36795
|
+
}
|
|
36796
|
+
}
|
|
37249
36797
|
}
|
|
37250
36798
|
log("[verification] Mission verification result", {
|
|
37251
36799
|
passed: result.passed,
|
|
37252
36800
|
hasChecklist,
|
|
37253
36801
|
checklistProgress: result.checklistProgress,
|
|
37254
36802
|
todoProgress: result.todoProgress,
|
|
36803
|
+
todoComplete: result.todoComplete,
|
|
37255
36804
|
syncIssuesEmpty: result.syncIssuesEmpty,
|
|
36805
|
+
syncIssuesCount: result.syncIssuesCount,
|
|
36806
|
+
errorCount: result.errors.length,
|
|
37256
36807
|
errors: result.errors.length > 0 ? result.errors : void 0
|
|
37257
36808
|
});
|
|
37258
36809
|
return result;
|
|
@@ -37325,8 +36876,19 @@ async function verifyMissionCompletionAsync(directory) {
|
|
|
37325
36876
|
}
|
|
37326
36877
|
if (hasChecklist) {
|
|
37327
36878
|
result.passed = result.checklistComplete && result.syncIssuesEmpty;
|
|
36879
|
+
if (!result.passed && result.errors.length === 0) {
|
|
36880
|
+
result.errors.push("Checklist verification failed - complete all items");
|
|
36881
|
+
}
|
|
37328
36882
|
} else {
|
|
37329
36883
|
result.passed = result.todoComplete && result.syncIssuesEmpty;
|
|
36884
|
+
if (!result.passed && result.errors.length === 0) {
|
|
36885
|
+
if (!result.todoComplete) {
|
|
36886
|
+
result.errors.push("TODO verification failed - complete all items");
|
|
36887
|
+
}
|
|
36888
|
+
if (!result.syncIssuesEmpty) {
|
|
36889
|
+
result.errors.push("Sync issues must be resolved");
|
|
36890
|
+
}
|
|
36891
|
+
}
|
|
37330
36892
|
}
|
|
37331
36893
|
lastVerificationResult.set(directory, { result, timestamp: Date.now() });
|
|
37332
36894
|
return result;
|
|
@@ -37991,7 +37553,7 @@ function getNextPending(todos) {
|
|
|
37991
37553
|
pending2.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
37992
37554
|
return pending2[0];
|
|
37993
37555
|
}
|
|
37994
|
-
function
|
|
37556
|
+
function getStats2(todos) {
|
|
37995
37557
|
const stats2 = {
|
|
37996
37558
|
total: todos.length,
|
|
37997
37559
|
pending: todos.filter((t) => t.status === TODO_STATUS2.PENDING).length,
|
|
@@ -38011,7 +37573,7 @@ function getStats3(todos) {
|
|
|
38011
37573
|
// src/core/loop/formatters.ts
|
|
38012
37574
|
init_shared();
|
|
38013
37575
|
function formatProgress(todos) {
|
|
38014
|
-
const stats2 =
|
|
37576
|
+
const stats2 = getStats2(todos);
|
|
38015
37577
|
const done = stats2.completed + stats2.cancelled;
|
|
38016
37578
|
return `${done}/${stats2.total} (${stats2.percentComplete}%)`;
|
|
38017
37579
|
}
|
|
@@ -38078,6 +37640,129 @@ ${LOOP_LABELS.ACTION_DONT_STOP}
|
|
|
38078
37640
|
// src/core/recovery/session-recovery.ts
|
|
38079
37641
|
init_shared();
|
|
38080
37642
|
init_shared();
|
|
37643
|
+
|
|
37644
|
+
// src/core/recovery/constants.ts
|
|
37645
|
+
init_shared();
|
|
37646
|
+
var MAX_RETRIES = RECOVERY.MAX_ATTEMPTS;
|
|
37647
|
+
var BASE_DELAY = RECOVERY.BASE_DELAY_MS;
|
|
37648
|
+
var MAX_HISTORY = HISTORY.MAX_RECOVERY;
|
|
37649
|
+
|
|
37650
|
+
// src/core/recovery/patterns.ts
|
|
37651
|
+
var errorPatterns = [
|
|
37652
|
+
// Rate limiting
|
|
37653
|
+
{
|
|
37654
|
+
pattern: /rate.?limit|too.?many.?requests|429/i,
|
|
37655
|
+
category: "rate_limit",
|
|
37656
|
+
handler: (ctx) => {
|
|
37657
|
+
const delay = BASE_DELAY * Math.pow(2, ctx.attempt);
|
|
37658
|
+
presets_exports.warningRateLimited();
|
|
37659
|
+
return { type: "retry", delay, attempt: ctx.attempt + 1 };
|
|
37660
|
+
}
|
|
37661
|
+
},
|
|
37662
|
+
// Context overflow
|
|
37663
|
+
{
|
|
37664
|
+
pattern: /context.?length|token.?limit|maximum.?context/i,
|
|
37665
|
+
category: "context_overflow",
|
|
37666
|
+
handler: () => {
|
|
37667
|
+
presets_exports.errorRecovery("Compacting context");
|
|
37668
|
+
return { type: "compact", reason: "Context limit reached" };
|
|
37669
|
+
}
|
|
37670
|
+
},
|
|
37671
|
+
// Network errors
|
|
37672
|
+
{
|
|
37673
|
+
pattern: /ECONNREFUSED|ETIMEDOUT|network|fetch.?failed/i,
|
|
37674
|
+
category: "network",
|
|
37675
|
+
handler: (ctx) => {
|
|
37676
|
+
if (ctx.attempt >= MAX_RETRIES) {
|
|
37677
|
+
return { type: "abort", reason: "Network unavailable after retries" };
|
|
37678
|
+
}
|
|
37679
|
+
return { type: "retry", delay: BASE_DELAY * (ctx.attempt + 1), attempt: ctx.attempt + 1 };
|
|
37680
|
+
}
|
|
37681
|
+
},
|
|
37682
|
+
// Session errors
|
|
37683
|
+
{
|
|
37684
|
+
pattern: /session.?not.?found|session.?expired/i,
|
|
37685
|
+
category: "session",
|
|
37686
|
+
handler: () => {
|
|
37687
|
+
return { type: "abort", reason: "Session no longer available" };
|
|
37688
|
+
}
|
|
37689
|
+
},
|
|
37690
|
+
// Tool errors
|
|
37691
|
+
{
|
|
37692
|
+
pattern: /tool.?not.?found|unknown.?tool/i,
|
|
37693
|
+
category: "tool",
|
|
37694
|
+
handler: (ctx) => {
|
|
37695
|
+
return { type: "escalate", to: "Reviewer", reason: `Unknown tool used by ${ctx.agent}` };
|
|
37696
|
+
}
|
|
37697
|
+
},
|
|
37698
|
+
// Parse errors
|
|
37699
|
+
{
|
|
37700
|
+
pattern: /parse.?error|invalid.?json|syntax.?error/i,
|
|
37701
|
+
category: "parse",
|
|
37702
|
+
handler: (ctx) => {
|
|
37703
|
+
if (ctx.attempt >= 2) {
|
|
37704
|
+
return { type: "skip", reason: "Persistent parse error" };
|
|
37705
|
+
}
|
|
37706
|
+
return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
|
|
37707
|
+
}
|
|
37708
|
+
},
|
|
37709
|
+
// Gibberish / hallucination
|
|
37710
|
+
{
|
|
37711
|
+
pattern: /gibberish|hallucination|mixed.?language/i,
|
|
37712
|
+
category: "gibberish",
|
|
37713
|
+
handler: () => {
|
|
37714
|
+
presets_exports.errorRecovery("Retrying with clean context");
|
|
37715
|
+
return { type: "retry", delay: 1e3, attempt: 1 };
|
|
37716
|
+
}
|
|
37717
|
+
},
|
|
37718
|
+
// LSP specific errors
|
|
37719
|
+
{
|
|
37720
|
+
pattern: /lsp.?diagnostics|tsc.?error|eslint.?error/i,
|
|
37721
|
+
category: "lsp",
|
|
37722
|
+
handler: (ctx) => {
|
|
37723
|
+
return { type: "retry", delay: 2e3, attempt: ctx.attempt + 1, modifyPrompt: "Note: Previous attempt had LSP issues. Ensure code quality and consider simpler implementation." };
|
|
37724
|
+
}
|
|
37725
|
+
},
|
|
37726
|
+
// execution timeout
|
|
37727
|
+
{
|
|
37728
|
+
pattern: /execution.?timed.?out|timeout/i,
|
|
37729
|
+
category: "timeout",
|
|
37730
|
+
handler: (ctx) => {
|
|
37731
|
+
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." };
|
|
37732
|
+
}
|
|
37733
|
+
}
|
|
37734
|
+
];
|
|
37735
|
+
|
|
37736
|
+
// src/core/recovery/handler.ts
|
|
37737
|
+
var recoveryHistory = [];
|
|
37738
|
+
function handleError(context) {
|
|
37739
|
+
const errorMessage = context.error.message || String(context.error);
|
|
37740
|
+
for (const pattern of errorPatterns) {
|
|
37741
|
+
const matches = typeof pattern.pattern === "string" ? errorMessage.includes(pattern.pattern) : pattern.pattern.test(errorMessage);
|
|
37742
|
+
if (matches) {
|
|
37743
|
+
const action = pattern.handler(context);
|
|
37744
|
+
recoveryHistory.push({
|
|
37745
|
+
context,
|
|
37746
|
+
action,
|
|
37747
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
37748
|
+
});
|
|
37749
|
+
if (recoveryHistory.length > MAX_HISTORY) {
|
|
37750
|
+
recoveryHistory.shift();
|
|
37751
|
+
}
|
|
37752
|
+
return action;
|
|
37753
|
+
}
|
|
37754
|
+
}
|
|
37755
|
+
if (context.attempt < MAX_RETRIES) {
|
|
37756
|
+
return {
|
|
37757
|
+
type: "retry",
|
|
37758
|
+
delay: BASE_DELAY * Math.pow(2, context.attempt),
|
|
37759
|
+
attempt: context.attempt + 1
|
|
37760
|
+
};
|
|
37761
|
+
}
|
|
37762
|
+
return { type: "abort", reason: `Unknown error after ${MAX_RETRIES} retries` };
|
|
37763
|
+
}
|
|
37764
|
+
|
|
37765
|
+
// src/core/recovery/session-recovery.ts
|
|
38081
37766
|
var recoveryState = /* @__PURE__ */ new Map();
|
|
38082
37767
|
function getState2(sessionID) {
|
|
38083
37768
|
let state2 = recoveryState.get(sessionID);
|
|
@@ -38212,6 +37897,61 @@ function isSessionRecovering(sessionID) {
|
|
|
38212
37897
|
return recoveryState.get(sessionID)?.isRecovering ?? false;
|
|
38213
37898
|
}
|
|
38214
37899
|
|
|
37900
|
+
// src/core/loop/continuation-lock.ts
|
|
37901
|
+
var locks = /* @__PURE__ */ new Map();
|
|
37902
|
+
var LOCK_TIMEOUT_MS = 12e4;
|
|
37903
|
+
function tryAcquireContinuationLock(sessionID, source) {
|
|
37904
|
+
const now = Date.now();
|
|
37905
|
+
const existing = locks.get(sessionID);
|
|
37906
|
+
if (existing?.acquired) {
|
|
37907
|
+
const elapsed = now - existing.timestamp;
|
|
37908
|
+
if (elapsed < LOCK_TIMEOUT_MS) {
|
|
37909
|
+
log("[continuation-lock] Lock denied - already held", {
|
|
37910
|
+
sessionID: sessionID.slice(0, 8),
|
|
37911
|
+
heldBy: existing.source,
|
|
37912
|
+
requestedBy: source,
|
|
37913
|
+
elapsedMs: elapsed
|
|
37914
|
+
});
|
|
37915
|
+
return false;
|
|
37916
|
+
}
|
|
37917
|
+
log("[continuation-lock] Forcing stale lock release", {
|
|
37918
|
+
sessionID: sessionID.slice(0, 8),
|
|
37919
|
+
staleSource: existing.source,
|
|
37920
|
+
elapsedMs: elapsed
|
|
37921
|
+
});
|
|
37922
|
+
}
|
|
37923
|
+
locks.set(sessionID, { acquired: true, timestamp: now, source });
|
|
37924
|
+
log("[continuation-lock] Lock acquired", {
|
|
37925
|
+
sessionID: sessionID.slice(0, 8),
|
|
37926
|
+
source
|
|
37927
|
+
});
|
|
37928
|
+
return true;
|
|
37929
|
+
}
|
|
37930
|
+
function releaseContinuationLock(sessionID) {
|
|
37931
|
+
const existing = locks.get(sessionID);
|
|
37932
|
+
if (existing?.acquired) {
|
|
37933
|
+
const duration5 = Date.now() - existing.timestamp;
|
|
37934
|
+
log("[continuation-lock] Lock released", {
|
|
37935
|
+
sessionID: sessionID.slice(0, 8),
|
|
37936
|
+
source: existing.source,
|
|
37937
|
+
heldMs: duration5
|
|
37938
|
+
});
|
|
37939
|
+
}
|
|
37940
|
+
locks.delete(sessionID);
|
|
37941
|
+
}
|
|
37942
|
+
function hasContinuationLock(sessionID) {
|
|
37943
|
+
const lock = locks.get(sessionID);
|
|
37944
|
+
if (!lock?.acquired) return false;
|
|
37945
|
+
if (Date.now() - lock.timestamp >= LOCK_TIMEOUT_MS) {
|
|
37946
|
+
log("[continuation-lock] Stale lock detected during check", {
|
|
37947
|
+
sessionID: sessionID.slice(0, 8)
|
|
37948
|
+
});
|
|
37949
|
+
locks.delete(sessionID);
|
|
37950
|
+
return false;
|
|
37951
|
+
}
|
|
37952
|
+
return true;
|
|
37953
|
+
}
|
|
37954
|
+
|
|
38215
37955
|
// src/core/loop/todo-continuation.ts
|
|
38216
37956
|
var sessionStates2 = /* @__PURE__ */ new Map();
|
|
38217
37957
|
var COUNTDOWN_SECONDS = 2;
|
|
@@ -38755,6 +38495,11 @@ var TodoSyncService = class {
|
|
|
38755
38495
|
taskTodos = /* @__PURE__ */ new Map();
|
|
38756
38496
|
updateTimeout = null;
|
|
38757
38497
|
watcher = null;
|
|
38498
|
+
// Batching support
|
|
38499
|
+
pendingUpdates = /* @__PURE__ */ new Set();
|
|
38500
|
+
batchTimer = null;
|
|
38501
|
+
BATCH_WINDOW_MS = 100;
|
|
38502
|
+
// 100ms batch window
|
|
38758
38503
|
activeSessions = /* @__PURE__ */ new Set();
|
|
38759
38504
|
constructor(client2, directory) {
|
|
38760
38505
|
this.client = client2;
|
|
@@ -38763,6 +38508,7 @@ var TodoSyncService = class {
|
|
|
38763
38508
|
}
|
|
38764
38509
|
async start() {
|
|
38765
38510
|
await this.reloadFileTodos();
|
|
38511
|
+
this.broadcastUpdate();
|
|
38766
38512
|
if (fs10.existsSync(this.todoPath)) {
|
|
38767
38513
|
let timer;
|
|
38768
38514
|
this.watcher = fs10.watch(this.todoPath, (eventType) => {
|
|
@@ -38821,8 +38567,29 @@ var TodoSyncService = class {
|
|
|
38821
38567
|
}
|
|
38822
38568
|
}
|
|
38823
38569
|
scheduleUpdate(sessionID) {
|
|
38824
|
-
this.
|
|
38825
|
-
|
|
38570
|
+
this.pendingUpdates.add(sessionID);
|
|
38571
|
+
if (!this.batchTimer) {
|
|
38572
|
+
this.batchTimer = setTimeout(() => {
|
|
38573
|
+
this.flushBatchedUpdates();
|
|
38574
|
+
}, this.BATCH_WINDOW_MS);
|
|
38575
|
+
}
|
|
38576
|
+
}
|
|
38577
|
+
/**
|
|
38578
|
+
* Flush all pending updates in parallel batches
|
|
38579
|
+
*/
|
|
38580
|
+
async flushBatchedUpdates() {
|
|
38581
|
+
const sessions = Array.from(this.pendingUpdates);
|
|
38582
|
+
this.pendingUpdates.clear();
|
|
38583
|
+
this.batchTimer = null;
|
|
38584
|
+
if (sessions.length === 0) return;
|
|
38585
|
+
log(`[TodoSync] Flushing ${sessions.length} batched updates`);
|
|
38586
|
+
const CHUNK_SIZE = 5;
|
|
38587
|
+
for (let i = 0; i < sessions.length; i += CHUNK_SIZE) {
|
|
38588
|
+
const chunk = sessions.slice(i, i + CHUNK_SIZE);
|
|
38589
|
+
await Promise.allSettled(
|
|
38590
|
+
chunk.map((sessionID) => this.sendTodosToSession(sessionID))
|
|
38591
|
+
);
|
|
38592
|
+
}
|
|
38826
38593
|
}
|
|
38827
38594
|
async sendTodosToSession(sessionID) {
|
|
38828
38595
|
const taskTodosList = Array.from(this.taskTodos.values()).map((t) => {
|
|
@@ -38864,6 +38631,11 @@ var TodoSyncService = class {
|
|
|
38864
38631
|
if (this.watcher) {
|
|
38865
38632
|
this.watcher.close();
|
|
38866
38633
|
}
|
|
38634
|
+
if (this.batchTimer) {
|
|
38635
|
+
clearTimeout(this.batchTimer);
|
|
38636
|
+
this.flushBatchedUpdates().catch(() => {
|
|
38637
|
+
});
|
|
38638
|
+
}
|
|
38867
38639
|
}
|
|
38868
38640
|
};
|
|
38869
38641
|
|
|
@@ -39331,7 +39103,7 @@ function createEventHandler(ctx) {
|
|
|
39331
39103
|
const duration5 = totalTime < 6e4 ? `${Math.round(totalTime / 1e3)}s` : `${Math.round(totalTime / 6e4)}m`;
|
|
39332
39104
|
sessions.delete(sessionID);
|
|
39333
39105
|
state2.sessions.delete(sessionID);
|
|
39334
|
-
|
|
39106
|
+
clearSession(sessionID);
|
|
39335
39107
|
cleanupSessionRecovery(sessionID);
|
|
39336
39108
|
cleanupSession2(sessionID);
|
|
39337
39109
|
cleanupSession3(sessionID);
|
|
@@ -39570,6 +39342,8 @@ Wait for these tasks to complete before concluding the mission.
|
|
|
39570
39342
|
|
|
39571
39343
|
// src/plugin-handlers/system-transform-handler.ts
|
|
39572
39344
|
init_shared();
|
|
39345
|
+
import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
|
|
39346
|
+
import { join as join12 } from "node:path";
|
|
39573
39347
|
function createSystemTransformHandler(ctx) {
|
|
39574
39348
|
const { directory, sessions, state: state2 } = ctx;
|
|
39575
39349
|
return async (input, output) => {
|
|
@@ -39599,6 +39373,17 @@ function createSystemTransformHandler(ctx) {
|
|
|
39599
39373
|
}
|
|
39600
39374
|
} catch {
|
|
39601
39375
|
}
|
|
39376
|
+
const todoPath = join12(directory, PATHS.TODO);
|
|
39377
|
+
if (existsSync9(todoPath)) {
|
|
39378
|
+
try {
|
|
39379
|
+
const todoContent = readFileSync3(todoPath, "utf-8");
|
|
39380
|
+
const todos = parseTodoMd(todoContent);
|
|
39381
|
+
if (todos.length > 0) {
|
|
39382
|
+
systemAdditions.push(buildTodoPrompt(todos));
|
|
39383
|
+
}
|
|
39384
|
+
} catch {
|
|
39385
|
+
}
|
|
39386
|
+
}
|
|
39602
39387
|
if (systemAdditions.length > 0) {
|
|
39603
39388
|
output.system.unshift(...systemAdditions);
|
|
39604
39389
|
}
|
|
@@ -39636,6 +39421,27 @@ Use \`get_task_result\` to check completed tasks.
|
|
|
39636
39421
|
Use \`delegate_task\` with background=true for parallel work.
|
|
39637
39422
|
</orchestrator_background_tasks>`;
|
|
39638
39423
|
}
|
|
39424
|
+
function buildTodoPrompt(todos) {
|
|
39425
|
+
const incompleteTodos = todos.filter((t) => t.status !== "completed");
|
|
39426
|
+
const totalCount = todos.length;
|
|
39427
|
+
const completeCount = totalCount - incompleteTodos.length;
|
|
39428
|
+
if (incompleteTodos.length === 0) {
|
|
39429
|
+
return `<orchestrator_todos>
|
|
39430
|
+
\u2705 All TODOs Complete (${completeCount}/${totalCount})
|
|
39431
|
+
|
|
39432
|
+
All tasks in .opencode/todo.md are finished.
|
|
39433
|
+
</orchestrator_todos>`;
|
|
39434
|
+
}
|
|
39435
|
+
const todoList = incompleteTodos.map((t) => ` - [${t.status === "in_progress" ? "~" : " "}] ${t.content} (${t.priority})`).join("\n");
|
|
39436
|
+
return `<orchestrator_todos>
|
|
39437
|
+
\u{1F4DD} TODO List (${completeCount}/${totalCount} complete):
|
|
39438
|
+
|
|
39439
|
+
${todoList}
|
|
39440
|
+
|
|
39441
|
+
Use the built-in \`todowrite\` tool to update TODO status as you complete tasks.
|
|
39442
|
+
When you finish a task, mark it as completed in the TODO list.
|
|
39443
|
+
</orchestrator_todos>`;
|
|
39444
|
+
}
|
|
39639
39445
|
|
|
39640
39446
|
// src/index.ts
|
|
39641
39447
|
var require2 = createRequire(import.meta.url);
|