opencode-orchestrator 1.2.15 → 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 +2 -1
- package/dist/core/agents/manager/task-poller.d.ts +2 -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 +1571 -1759
- package/dist/scripts/postinstall.js +125 -29
- package/dist/shared/loop/constants/mission-control.d.ts +2 -2
- 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 +5 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -500,22 +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,
|
|
508
|
+
MAX_CONCURRENCY: 5,
|
|
506
509
|
// Sync polling (for delegate_task sync mode)
|
|
507
|
-
// Optimized:
|
|
508
|
-
SYNC_TIMEOUT_MS:
|
|
509
|
-
|
|
510
|
-
|
|
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)
|
|
511
515
|
MIN_IDLE_TIME_MS: 3 * TIME.SECOND,
|
|
512
|
-
// 5s → 3s (
|
|
513
|
-
MIN_STABILITY_MS:
|
|
514
|
-
//
|
|
516
|
+
// 5s → 3s (quicker detection)
|
|
517
|
+
MIN_STABILITY_MS: 3 * TIME.SECOND,
|
|
518
|
+
// 5s → 3s (faster completion)
|
|
515
519
|
STABLE_POLLS_REQUIRED: 2,
|
|
516
|
-
//
|
|
517
|
-
MAX_POLL_COUNT:
|
|
518
|
-
//
|
|
520
|
+
// Keep at 2 for reliability
|
|
521
|
+
MAX_POLL_COUNT: 600,
|
|
522
|
+
// 150 → 600 (30min = 600 * 3s)
|
|
519
523
|
// Session naming
|
|
520
524
|
SESSION_TITLE_PREFIX: "Parallel",
|
|
521
525
|
// Labels for output
|
|
@@ -637,7 +641,7 @@ var init_mission_control = __esm({
|
|
|
637
641
|
init_limits();
|
|
638
642
|
MISSION_CONTROL = {
|
|
639
643
|
DEFAULT_MAX_ITERATIONS: LIMITS.MAX_ITERATIONS,
|
|
640
|
-
DEFAULT_COUNTDOWN_SECONDS:
|
|
644
|
+
DEFAULT_COUNTDOWN_SECONDS: 10,
|
|
641
645
|
STATE_FILE: "loop-state.json",
|
|
642
646
|
STOP_COMMAND: "/stop",
|
|
643
647
|
CANCEL_COMMAND: "/cancel",
|
|
@@ -1375,6 +1379,20 @@ var init_special_events = __esm({
|
|
|
1375
1379
|
}
|
|
1376
1380
|
});
|
|
1377
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
|
+
|
|
1378
1396
|
// src/shared/session/constants/events/index.ts
|
|
1379
1397
|
var EVENT_TYPES;
|
|
1380
1398
|
var init_events = __esm({
|
|
@@ -1387,6 +1405,7 @@ var init_events = __esm({
|
|
|
1387
1405
|
init_mission_events();
|
|
1388
1406
|
init_message_events();
|
|
1389
1407
|
init_special_events();
|
|
1408
|
+
init_hook_events();
|
|
1390
1409
|
init_task_events();
|
|
1391
1410
|
init_todo_events();
|
|
1392
1411
|
init_session_events();
|
|
@@ -1394,6 +1413,7 @@ var init_events = __esm({
|
|
|
1394
1413
|
init_mission_events();
|
|
1395
1414
|
init_message_events();
|
|
1396
1415
|
init_special_events();
|
|
1416
|
+
init_hook_events();
|
|
1397
1417
|
EVENT_TYPES = {
|
|
1398
1418
|
...TASK_EVENTS,
|
|
1399
1419
|
...TODO_EVENTS,
|
|
@@ -1401,7 +1421,8 @@ var init_events = __esm({
|
|
|
1401
1421
|
...DOCUMENT_EVENTS,
|
|
1402
1422
|
...MISSION_EVENTS,
|
|
1403
1423
|
...MESSAGE_EVENTS,
|
|
1404
|
-
...SPECIAL_EVENTS
|
|
1424
|
+
...SPECIAL_EVENTS,
|
|
1425
|
+
...HOOK_EVENTS
|
|
1405
1426
|
};
|
|
1406
1427
|
}
|
|
1407
1428
|
});
|
|
@@ -5126,13 +5147,13 @@ __export(store_exports, {
|
|
|
5126
5147
|
addDecision: () => addDecision,
|
|
5127
5148
|
addDocument: () => addDocument,
|
|
5128
5149
|
addFinding: () => addFinding,
|
|
5129
|
-
clear: () =>
|
|
5150
|
+
clear: () => clear,
|
|
5130
5151
|
clearAll: () => clearAll,
|
|
5131
5152
|
create: () => create,
|
|
5132
5153
|
get: () => get,
|
|
5133
5154
|
getChildren: () => getChildren,
|
|
5134
5155
|
getMerged: () => getMerged,
|
|
5135
|
-
getStats: () =>
|
|
5156
|
+
getStats: () => getStats
|
|
5136
5157
|
});
|
|
5137
5158
|
function create(sessionId, parentId) {
|
|
5138
5159
|
const context = {
|
|
@@ -5200,7 +5221,7 @@ function addDecision(sessionId, decision) {
|
|
|
5200
5221
|
function getChildren(parentId) {
|
|
5201
5222
|
return Array.from(parentChildMap.get(parentId) || []);
|
|
5202
5223
|
}
|
|
5203
|
-
function
|
|
5224
|
+
function clear(sessionId) {
|
|
5204
5225
|
const context = contexts.get(sessionId);
|
|
5205
5226
|
if (context?.parentId) {
|
|
5206
5227
|
parentChildMap.get(context.parentId)?.delete(sessionId);
|
|
@@ -5211,7 +5232,7 @@ function clearAll() {
|
|
|
5211
5232
|
contexts.clear();
|
|
5212
5233
|
parentChildMap.clear();
|
|
5213
5234
|
}
|
|
5214
|
-
function
|
|
5235
|
+
function getStats() {
|
|
5215
5236
|
let totalDocuments = 0;
|
|
5216
5237
|
let totalFindings = 0;
|
|
5217
5238
|
let totalDecisions = 0;
|
|
@@ -18579,34 +18600,20 @@ var ConcurrencyController = class {
|
|
|
18579
18600
|
}
|
|
18580
18601
|
}
|
|
18581
18602
|
/**
|
|
18582
|
-
* 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.
|
|
18583
18607
|
*/
|
|
18584
18608
|
reportResult(key, success3) {
|
|
18585
18609
|
if (success3) {
|
|
18586
18610
|
const streak = (this.successStreak.get(key) ?? 0) + 1;
|
|
18587
18611
|
this.successStreak.set(key, streak);
|
|
18588
18612
|
this.failureCount.set(key, 0);
|
|
18589
|
-
if (streak >= 3) {
|
|
18590
|
-
const currentLimit = this.getConcurrencyLimit(key);
|
|
18591
|
-
if (currentLimit < PARALLEL_TASK.MAX_CONCURRENCY) {
|
|
18592
|
-
this.setLimit(key, currentLimit + 1);
|
|
18593
|
-
this.successStreak.set(key, 0);
|
|
18594
|
-
log(`[concurrency] Auto-scaling UP for ${key}: ${currentLimit + 1}`);
|
|
18595
|
-
}
|
|
18596
|
-
}
|
|
18597
18613
|
} else {
|
|
18598
18614
|
const failures = (this.failureCount.get(key) ?? 0) + 1;
|
|
18599
18615
|
this.failureCount.set(key, failures);
|
|
18600
18616
|
this.successStreak.set(key, 0);
|
|
18601
|
-
if (failures >= 2) {
|
|
18602
|
-
const currentLimit = this.getConcurrencyLimit(key);
|
|
18603
|
-
const minLimit = 1;
|
|
18604
|
-
if (currentLimit > minLimit) {
|
|
18605
|
-
this.setLimit(key, currentLimit - 1);
|
|
18606
|
-
this.failureCount.set(key, 0);
|
|
18607
|
-
log(`[concurrency] Auto-scaling DOWN for ${key}: ${currentLimit - 1} (due to ${failures} failures)`);
|
|
18608
|
-
}
|
|
18609
|
-
}
|
|
18610
18617
|
}
|
|
18611
18618
|
}
|
|
18612
18619
|
getQueueLength(key) {
|
|
@@ -18809,39 +18816,14 @@ function formatDuration(start, end) {
|
|
|
18809
18816
|
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
18810
18817
|
return `${seconds}s`;
|
|
18811
18818
|
}
|
|
18812
|
-
function buildNotificationMessage(tasks) {
|
|
18813
|
-
const summary = tasks.map((t) => {
|
|
18814
|
-
const status = t.status === TASK_STATUS.COMPLETED ? "\u2705" : "\u274C";
|
|
18815
|
-
return `${status} \`${t.id}\`: ${t.description}`;
|
|
18816
|
-
}).join("\n");
|
|
18817
|
-
return `<system-notification>
|
|
18818
|
-
**All Parallel Tasks Complete**
|
|
18819
|
-
|
|
18820
|
-
${summary}
|
|
18821
|
-
|
|
18822
|
-
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
18823
|
-
</system-notification>`;
|
|
18824
|
-
}
|
|
18825
18819
|
|
|
18826
18820
|
// src/core/session/session-health.ts
|
|
18827
18821
|
var sessionHealth = /* @__PURE__ */ new Map();
|
|
18828
|
-
var STALE_THRESHOLD_MS =
|
|
18829
|
-
var HEALTH_CHECK_INTERVAL_MS =
|
|
18830
|
-
var WARNING_THRESHOLD_MS =
|
|
18822
|
+
var STALE_THRESHOLD_MS = 6e5;
|
|
18823
|
+
var HEALTH_CHECK_INTERVAL_MS = 6e4;
|
|
18824
|
+
var WARNING_THRESHOLD_MS = 3e5;
|
|
18831
18825
|
var healthCheckTimer;
|
|
18832
18826
|
var client;
|
|
18833
|
-
function recordSessionResponse(sessionID) {
|
|
18834
|
-
const health = sessionHealth.get(sessionID);
|
|
18835
|
-
if (health) {
|
|
18836
|
-
health.lastResponseTime = Date.now();
|
|
18837
|
-
health.isStale = false;
|
|
18838
|
-
}
|
|
18839
|
-
}
|
|
18840
|
-
function isSessionStale(sessionID) {
|
|
18841
|
-
const health = sessionHealth.get(sessionID);
|
|
18842
|
-
if (!health) return false;
|
|
18843
|
-
return health.isStale;
|
|
18844
|
-
}
|
|
18845
18827
|
function startHealthCheck(opencodeClient) {
|
|
18846
18828
|
if (healthCheckTimer) {
|
|
18847
18829
|
log("[session-health] Health check already running");
|
|
@@ -18888,292 +18870,6 @@ function performHealthCheck() {
|
|
|
18888
18870
|
});
|
|
18889
18871
|
}
|
|
18890
18872
|
}
|
|
18891
|
-
function cleanupSessionHealth(sessionID) {
|
|
18892
|
-
sessionHealth.delete(sessionID);
|
|
18893
|
-
}
|
|
18894
|
-
|
|
18895
|
-
// src/core/agents/manager/task-launcher.ts
|
|
18896
|
-
init_shared();
|
|
18897
|
-
init_shared();
|
|
18898
|
-
|
|
18899
|
-
// src/core/notification/task-toast-manager.ts
|
|
18900
|
-
init_shared();
|
|
18901
|
-
var TaskToastManager = class {
|
|
18902
|
-
tasks = /* @__PURE__ */ new Map();
|
|
18903
|
-
client = null;
|
|
18904
|
-
concurrency = null;
|
|
18905
|
-
todoSync = null;
|
|
18906
|
-
/**
|
|
18907
|
-
* Initialize the manager with OpenCode client
|
|
18908
|
-
*/
|
|
18909
|
-
init(client2, concurrency) {
|
|
18910
|
-
this.client = client2;
|
|
18911
|
-
this.concurrency = concurrency ?? null;
|
|
18912
|
-
}
|
|
18913
|
-
/**
|
|
18914
|
-
* Set concurrency controller (can be set after init)
|
|
18915
|
-
*/
|
|
18916
|
-
setConcurrencyController(concurrency) {
|
|
18917
|
-
this.concurrency = concurrency;
|
|
18918
|
-
}
|
|
18919
|
-
/**
|
|
18920
|
-
* Set TodoSyncService for TUI status synchronization
|
|
18921
|
-
*/
|
|
18922
|
-
setTodoSync(service) {
|
|
18923
|
-
this.todoSync = service;
|
|
18924
|
-
}
|
|
18925
|
-
/**
|
|
18926
|
-
* Add a new task and show consolidated toast
|
|
18927
|
-
*/
|
|
18928
|
-
addTask(task) {
|
|
18929
|
-
const trackedTask = {
|
|
18930
|
-
id: task.id,
|
|
18931
|
-
description: task.description,
|
|
18932
|
-
agent: task.agent,
|
|
18933
|
-
status: task.status ?? STATUS_LABEL.RUNNING,
|
|
18934
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
18935
|
-
isBackground: task.isBackground,
|
|
18936
|
-
parentSessionID: task.parentSessionID,
|
|
18937
|
-
sessionID: task.sessionID
|
|
18938
|
-
};
|
|
18939
|
-
this.tasks.set(task.id, trackedTask);
|
|
18940
|
-
this.todoSync?.updateTaskStatus(trackedTask);
|
|
18941
|
-
this.showTaskListToast(trackedTask);
|
|
18942
|
-
}
|
|
18943
|
-
/**
|
|
18944
|
-
* Update task status
|
|
18945
|
-
*/
|
|
18946
|
-
updateTask(id, status) {
|
|
18947
|
-
const task = this.tasks.get(id);
|
|
18948
|
-
if (task) {
|
|
18949
|
-
task.status = status;
|
|
18950
|
-
this.todoSync?.updateTaskStatus(task);
|
|
18951
|
-
}
|
|
18952
|
-
}
|
|
18953
|
-
/**
|
|
18954
|
-
* Remove a task
|
|
18955
|
-
*/
|
|
18956
|
-
removeTask(id) {
|
|
18957
|
-
this.tasks.delete(id);
|
|
18958
|
-
this.todoSync?.removeTask(id);
|
|
18959
|
-
}
|
|
18960
|
-
/**
|
|
18961
|
-
* Get all running tasks (newest first)
|
|
18962
|
-
*/
|
|
18963
|
-
getRunningTasks() {
|
|
18964
|
-
return Array.from(this.tasks.values()).filter((t) => t.status === STATUS_LABEL.RUNNING).sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
|
|
18965
|
-
}
|
|
18966
|
-
/**
|
|
18967
|
-
* Get all queued tasks (oldest first - FIFO)
|
|
18968
|
-
*/
|
|
18969
|
-
getQueuedTasks() {
|
|
18970
|
-
return Array.from(this.tasks.values()).filter((t) => t.status === STATUS_LABEL.QUEUED).sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
|
18971
|
-
}
|
|
18972
|
-
/**
|
|
18973
|
-
* Get tasks by parent session
|
|
18974
|
-
*/
|
|
18975
|
-
getTasksByParent(parentSessionID) {
|
|
18976
|
-
return Array.from(this.tasks.values()).filter((t) => t.parentSessionID === parentSessionID);
|
|
18977
|
-
}
|
|
18978
|
-
/**
|
|
18979
|
-
* Format duration since task started
|
|
18980
|
-
*/
|
|
18981
|
-
formatDuration(startedAt) {
|
|
18982
|
-
const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
|
|
18983
|
-
if (seconds < 60) return `${seconds}s`;
|
|
18984
|
-
const minutes = Math.floor(seconds / 60);
|
|
18985
|
-
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
|
18986
|
-
const hours = Math.floor(minutes / 60);
|
|
18987
|
-
return `${hours}h ${minutes % 60}m`;
|
|
18988
|
-
}
|
|
18989
|
-
/**
|
|
18990
|
-
* Get concurrency info string (e.g., " [2/5]")
|
|
18991
|
-
*/
|
|
18992
|
-
/**
|
|
18993
|
-
* Get concurrency info string (e.g., " [2/5]")
|
|
18994
|
-
*/
|
|
18995
|
-
getConcurrencyInfo() {
|
|
18996
|
-
if (!this.concurrency) return "";
|
|
18997
|
-
const running = this.getRunningTasks();
|
|
18998
|
-
const queued = this.getQueuedTasks();
|
|
18999
|
-
const total = running.length;
|
|
19000
|
-
const limit = this.concurrency.getConcurrencyLimit("default");
|
|
19001
|
-
if (limit === Infinity) return "";
|
|
19002
|
-
const filled = TUI_BLOCKS.FILLED.repeat(total);
|
|
19003
|
-
const empty = TUI_BLOCKS.EMPTY.repeat(Math.max(0, limit - total));
|
|
19004
|
-
return ` [${filled}${empty} ${total}/${limit}]`;
|
|
19005
|
-
}
|
|
19006
|
-
/**
|
|
19007
|
-
* Build consolidated task list message
|
|
19008
|
-
*/
|
|
19009
|
-
buildTaskListMessage(newTask) {
|
|
19010
|
-
const running = this.getRunningTasks();
|
|
19011
|
-
const queued = this.getQueuedTasks();
|
|
19012
|
-
const concurrencyInfo = this.getConcurrencyInfo();
|
|
19013
|
-
const lines = [];
|
|
19014
|
-
if (running.length > 0) {
|
|
19015
|
-
lines.push(`${TUI_ICONS.RUNNING} Running (${running.length}) ${concurrencyInfo}`);
|
|
19016
|
-
for (const task of running) {
|
|
19017
|
-
const duration5 = this.formatDuration(task.startedAt);
|
|
19018
|
-
const bgTag = task.isBackground ? TUI_TAGS.BACKGROUND : TUI_TAGS.FOREGROUND;
|
|
19019
|
-
const isNew = newTask && task.id === newTask.id ? TUI_ICONS.NEW : "";
|
|
19020
|
-
lines.push(`${bgTag} ${task.description} (${task.agent}) - ${duration5}${isNew}`);
|
|
19021
|
-
}
|
|
19022
|
-
}
|
|
19023
|
-
if (queued.length > 0) {
|
|
19024
|
-
if (lines.length > 0) lines.push("");
|
|
19025
|
-
lines.push(`${TUI_ICONS.QUEUED} Queued (${queued.length}):`);
|
|
19026
|
-
for (const task of queued) {
|
|
19027
|
-
const bgTag = task.isBackground ? TUI_TAGS.WAITING : TUI_TAGS.PENDING;
|
|
19028
|
-
lines.push(`${bgTag} ${task.description} (${task.agent})`);
|
|
19029
|
-
}
|
|
19030
|
-
}
|
|
19031
|
-
return lines.join("\n");
|
|
19032
|
-
}
|
|
19033
|
-
/**
|
|
19034
|
-
* Show consolidated toast with all running/queued tasks
|
|
19035
|
-
*/
|
|
19036
|
-
showTaskListToast(newTask) {
|
|
19037
|
-
if (!this.client || !this.client.tui) return;
|
|
19038
|
-
const message = this.buildTaskListMessage(newTask);
|
|
19039
|
-
const running = this.getRunningTasks();
|
|
19040
|
-
const queued = this.getQueuedTasks();
|
|
19041
|
-
const title = newTask.isBackground ? `Background Task Started` : `Task Started`;
|
|
19042
|
-
this.client.tui.showToast({
|
|
19043
|
-
body: {
|
|
19044
|
-
title,
|
|
19045
|
-
message: message || `${newTask.description} (${newTask.agent})`,
|
|
19046
|
-
variant: STATUS_LABEL.INFO,
|
|
19047
|
-
duration: running.length + queued.length > 2 ? 5e3 : 3e3
|
|
19048
|
-
}
|
|
19049
|
-
}).catch(() => {
|
|
19050
|
-
});
|
|
19051
|
-
}
|
|
19052
|
-
/**
|
|
19053
|
-
* Show task completion toast
|
|
19054
|
-
*/
|
|
19055
|
-
showCompletionToast(info) {
|
|
19056
|
-
if (!this.client || !this.client.tui) return;
|
|
19057
|
-
this.removeTask(info.id);
|
|
19058
|
-
const remaining = this.getRunningTasks();
|
|
19059
|
-
const queued = this.getQueuedTasks();
|
|
19060
|
-
let message;
|
|
19061
|
-
let title;
|
|
19062
|
-
let variant;
|
|
19063
|
-
if (info.status === STATUS_LABEL.ERROR || info.status === STATUS_LABEL.CANCELLED || info.status === STATUS_LABEL.FAILED) {
|
|
19064
|
-
title = info.status === STATUS_LABEL.ERROR ? "Task Failed" : "Task Cancelled";
|
|
19065
|
-
message = `[FAIL] "${info.description}" ${info.status}
|
|
19066
|
-
${info.error || ""}`;
|
|
19067
|
-
variant = STATUS_LABEL.ERROR;
|
|
19068
|
-
} else {
|
|
19069
|
-
title = "Task Completed";
|
|
19070
|
-
message = `[DONE] "${info.description}" finished in ${info.duration}`;
|
|
19071
|
-
variant = STATUS_LABEL.SUCCESS;
|
|
19072
|
-
}
|
|
19073
|
-
if (remaining.length > 0 || queued.length > 0) {
|
|
19074
|
-
message += `
|
|
19075
|
-
|
|
19076
|
-
Still running: ${remaining.length} | Queued: ${queued.length}`;
|
|
19077
|
-
}
|
|
19078
|
-
this.client.tui.showToast({
|
|
19079
|
-
body: {
|
|
19080
|
-
title,
|
|
19081
|
-
message,
|
|
19082
|
-
variant,
|
|
19083
|
-
duration: 5e3
|
|
19084
|
-
}
|
|
19085
|
-
}).catch(() => {
|
|
19086
|
-
});
|
|
19087
|
-
}
|
|
19088
|
-
/**
|
|
19089
|
-
* Show all-tasks-complete summary toast
|
|
19090
|
-
*/
|
|
19091
|
-
showAllCompleteToast(parentSessionID, completedTasks) {
|
|
19092
|
-
if (!this.client || !this.client.tui) return;
|
|
19093
|
-
const successCount = completedTasks.filter((t) => t.status === STATUS_LABEL.COMPLETED).length;
|
|
19094
|
-
const failCount = completedTasks.filter((t) => t.status === STATUS_LABEL.ERROR || t.status === STATUS_LABEL.CANCELLED || t.status === STATUS_LABEL.FAILED).length;
|
|
19095
|
-
const taskList = completedTasks.map((t) => `- [${t.status === STATUS_LABEL.COMPLETED ? "OK" : "FAIL"}] ${t.description} (${t.duration})`).join("\n");
|
|
19096
|
-
this.client.tui.showToast({
|
|
19097
|
-
body: {
|
|
19098
|
-
title: "All Tasks Completed",
|
|
19099
|
-
message: `${successCount} succeeded, ${failCount} failed
|
|
19100
|
-
|
|
19101
|
-
${taskList}`,
|
|
19102
|
-
variant: failCount > 0 ? STATUS_LABEL.WARNING : STATUS_LABEL.SUCCESS,
|
|
19103
|
-
duration: 7e3
|
|
19104
|
-
}
|
|
19105
|
-
}).catch(() => {
|
|
19106
|
-
});
|
|
19107
|
-
}
|
|
19108
|
-
/**
|
|
19109
|
-
* Show Mission Complete toast (Grand Finale)
|
|
19110
|
-
*/
|
|
19111
|
-
showMissionCompleteToast(title = "Mission Complete", message = "All tasks completed successfully.") {
|
|
19112
|
-
if (!this.client || !this.client.tui) return;
|
|
19113
|
-
const decoratedMessage = `
|
|
19114
|
-
${TUI_ICONS.MISSION_COMPLETE} ${TUI_MESSAGES.MISSION_COMPLETE_TITLE} ${TUI_ICONS.MISSION_COMPLETE}
|
|
19115
|
-
\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
|
|
19116
|
-
${message}
|
|
19117
|
-
\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
|
|
19118
|
-
${TUI_MESSAGES.MISSION_COMPLETE_SUBTITLE}
|
|
19119
|
-
`.trim();
|
|
19120
|
-
this.client.tui.showToast({
|
|
19121
|
-
body: {
|
|
19122
|
-
title: `${TUI_ICONS.SHIELD} ${title}`,
|
|
19123
|
-
message: decoratedMessage,
|
|
19124
|
-
variant: STATUS_LABEL.SUCCESS,
|
|
19125
|
-
duration: 1e4
|
|
19126
|
-
// Longer duration for the finale
|
|
19127
|
-
}
|
|
19128
|
-
}).catch(() => {
|
|
19129
|
-
});
|
|
19130
|
-
}
|
|
19131
|
-
/**
|
|
19132
|
-
* Show progress toast (for long-running tasks)
|
|
19133
|
-
*/
|
|
19134
|
-
showProgressToast(taskId, progress) {
|
|
19135
|
-
if (!this.client || !this.client.tui) return;
|
|
19136
|
-
const task = this.tasks.get(taskId);
|
|
19137
|
-
if (!task) return;
|
|
19138
|
-
const percentage = Math.round(progress.current / progress.total * 100);
|
|
19139
|
-
const progressBar = `[${"#".repeat(Math.floor(percentage / 10))}${"-".repeat(10 - Math.floor(percentage / 10))}]`;
|
|
19140
|
-
this.client.tui.showToast({
|
|
19141
|
-
body: {
|
|
19142
|
-
title: `Task Progress: ${task.description}`,
|
|
19143
|
-
message: `${progressBar} ${percentage}%
|
|
19144
|
-
${progress.message || ""}`,
|
|
19145
|
-
variant: STATUS_LABEL.INFO,
|
|
19146
|
-
duration: 2e3
|
|
19147
|
-
}
|
|
19148
|
-
}).catch(() => {
|
|
19149
|
-
});
|
|
19150
|
-
}
|
|
19151
|
-
/**
|
|
19152
|
-
* Clear all tracked tasks
|
|
19153
|
-
*/
|
|
19154
|
-
clear() {
|
|
19155
|
-
this.tasks.clear();
|
|
19156
|
-
}
|
|
19157
|
-
/**
|
|
19158
|
-
* Get task count stats
|
|
19159
|
-
*/
|
|
19160
|
-
getStats() {
|
|
19161
|
-
const running = this.getRunningTasks().length;
|
|
19162
|
-
const queued = this.getQueuedTasks().length;
|
|
19163
|
-
return { running, queued, total: this.tasks.size };
|
|
19164
|
-
}
|
|
19165
|
-
};
|
|
19166
|
-
var instance = null;
|
|
19167
|
-
function getTaskToastManager() {
|
|
19168
|
-
return instance;
|
|
19169
|
-
}
|
|
19170
|
-
function initTaskToastManager(client2, concurrency) {
|
|
19171
|
-
if (!instance) {
|
|
19172
|
-
instance = new TaskToastManager();
|
|
19173
|
-
}
|
|
19174
|
-
instance.init(client2, concurrency);
|
|
19175
|
-
return instance;
|
|
19176
|
-
}
|
|
19177
18873
|
|
|
19178
18874
|
// src/core/agents/persistence/task-wal.ts
|
|
19179
18875
|
init_shared();
|
|
@@ -19269,130 +18965,1048 @@ var TaskWAL = class {
|
|
|
19269
18965
|
};
|
|
19270
18966
|
var taskWAL = new TaskWAL();
|
|
19271
18967
|
|
|
19272
|
-
// src/core/
|
|
18968
|
+
// src/core/notification/task-toast-manager.ts
|
|
19273
18969
|
init_shared();
|
|
19274
|
-
var
|
|
19275
|
-
|
|
19276
|
-
|
|
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 += `
|
|
19277
19144
|
|
|
19278
|
-
|
|
19279
|
-
|
|
19280
|
-
|
|
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
|
|
19281
19169
|
|
|
19282
|
-
|
|
19283
|
-
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
|
|
19287
|
-
|
|
19288
|
-
|
|
19289
|
-
|
|
19290
|
-
|
|
19291
|
-
|
|
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;
|
|
19292
19346
|
}
|
|
19293
|
-
}
|
|
19294
|
-
//
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
|
|
19299
|
-
|
|
19300
|
-
|
|
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;
|
|
19301
19371
|
}
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
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;
|
|
19310
19400
|
}
|
|
19311
|
-
|
|
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}`);
|
|
19312
19427
|
}
|
|
19313
|
-
}
|
|
19314
|
-
//
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
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);
|
|
19320
19447
|
}
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
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);
|
|
19328
19469
|
}
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
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"));
|
|
19337
19513
|
}
|
|
19338
|
-
return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
|
|
19339
19514
|
}
|
|
19340
|
-
}
|
|
19341
|
-
//
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
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);
|
|
19348
19548
|
}
|
|
19349
|
-
}
|
|
19350
|
-
//
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
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;
|
|
19356
19563
|
}
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
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
|
+
}
|
|
19364
19589
|
}
|
|
19590
|
+
if (recovered > 0) {
|
|
19591
|
+
log(`[UnifiedExecutor] Recovered ${recovered} tasks`);
|
|
19592
|
+
this.startPolling();
|
|
19593
|
+
}
|
|
19594
|
+
return recovered;
|
|
19365
19595
|
}
|
|
19366
|
-
|
|
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
|
+
};
|
|
19367
19644
|
|
|
19368
|
-
// src/core/
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
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");
|
|
19383
19679
|
}
|
|
19384
|
-
|
|
19680
|
+
_SessionPool._instance = new _SessionPool(client2, directory, config3);
|
|
19385
19681
|
}
|
|
19682
|
+
return _SessionPool._instance;
|
|
19386
19683
|
}
|
|
19387
|
-
|
|
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;
|
|
19388
19763
|
return {
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19764
|
+
totalSessions: allSessions.length,
|
|
19765
|
+
sessionsInUse: inUseCount,
|
|
19766
|
+
availableSessions: allSessions.length - inUseCount,
|
|
19767
|
+
reuseHits: this.stats.reuseHits,
|
|
19768
|
+
creationMisses: this.stats.creationMisses,
|
|
19769
|
+
byAgent
|
|
19392
19770
|
};
|
|
19393
19771
|
}
|
|
19394
|
-
|
|
19395
|
-
|
|
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();
|
|
19396
20010
|
|
|
19397
20011
|
// src/core/memory/interfaces.ts
|
|
19398
20012
|
var MemoryLevel = /* @__PURE__ */ ((MemoryLevel2) => {
|
|
@@ -19526,6 +20140,9 @@ var MemoryManager = class _MemoryManager {
|
|
|
19526
20140
|
}
|
|
19527
20141
|
};
|
|
19528
20142
|
|
|
20143
|
+
// src/core/agents/manager.ts
|
|
20144
|
+
init_core2();
|
|
20145
|
+
|
|
19529
20146
|
// src/core/agents/agent-registry.ts
|
|
19530
20147
|
init_shared();
|
|
19531
20148
|
import * as fs4 from "fs/promises";
|
|
@@ -33180,1202 +33797,212 @@ function convertBaseSchema(schema, ctx) {
|
|
|
33180
33797
|
if (typeof schema.maxItems === "number") {
|
|
33181
33798
|
arraySchema = arraySchema.max(schema.maxItems);
|
|
33182
33799
|
}
|
|
33183
|
-
zodSchema = arraySchema;
|
|
33184
|
-
} else {
|
|
33185
|
-
zodSchema = z.array(z.any());
|
|
33186
|
-
}
|
|
33187
|
-
break;
|
|
33188
|
-
}
|
|
33189
|
-
default:
|
|
33190
|
-
throw new Error(`Unsupported type: ${type}`);
|
|
33191
|
-
}
|
|
33192
|
-
if (schema.description) {
|
|
33193
|
-
zodSchema = zodSchema.describe(schema.description);
|
|
33194
|
-
}
|
|
33195
|
-
if (schema.default !== void 0) {
|
|
33196
|
-
zodSchema = zodSchema.default(schema.default);
|
|
33197
|
-
}
|
|
33198
|
-
return zodSchema;
|
|
33199
|
-
}
|
|
33200
|
-
function convertSchema(schema, ctx) {
|
|
33201
|
-
if (typeof schema === "boolean") {
|
|
33202
|
-
return schema ? z.any() : z.never();
|
|
33203
|
-
}
|
|
33204
|
-
let baseSchema = convertBaseSchema(schema, ctx);
|
|
33205
|
-
const hasExplicitType = schema.type || schema.enum !== void 0 || schema.const !== void 0;
|
|
33206
|
-
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
33207
|
-
const options = schema.anyOf.map((s) => convertSchema(s, ctx));
|
|
33208
|
-
const anyOfUnion = z.union(options);
|
|
33209
|
-
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
|
33210
|
-
}
|
|
33211
|
-
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
33212
|
-
const options = schema.oneOf.map((s) => convertSchema(s, ctx));
|
|
33213
|
-
const oneOfUnion = z.xor(options);
|
|
33214
|
-
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
|
33215
|
-
}
|
|
33216
|
-
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
33217
|
-
if (schema.allOf.length === 0) {
|
|
33218
|
-
baseSchema = hasExplicitType ? baseSchema : z.any();
|
|
33219
|
-
} else {
|
|
33220
|
-
let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx);
|
|
33221
|
-
const startIdx = hasExplicitType ? 0 : 1;
|
|
33222
|
-
for (let i = startIdx; i < schema.allOf.length; i++) {
|
|
33223
|
-
result = z.intersection(result, convertSchema(schema.allOf[i], ctx));
|
|
33224
|
-
}
|
|
33225
|
-
baseSchema = result;
|
|
33226
|
-
}
|
|
33227
|
-
}
|
|
33228
|
-
if (schema.nullable === true && ctx.version === "openapi-3.0") {
|
|
33229
|
-
baseSchema = z.nullable(baseSchema);
|
|
33230
|
-
}
|
|
33231
|
-
if (schema.readOnly === true) {
|
|
33232
|
-
baseSchema = z.readonly(baseSchema);
|
|
33233
|
-
}
|
|
33234
|
-
const extraMeta = {};
|
|
33235
|
-
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
33236
|
-
for (const key of coreMetadataKeys) {
|
|
33237
|
-
if (key in schema) {
|
|
33238
|
-
extraMeta[key] = schema[key];
|
|
33239
|
-
}
|
|
33240
|
-
}
|
|
33241
|
-
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
33242
|
-
for (const key of contentMetadataKeys) {
|
|
33243
|
-
if (key in schema) {
|
|
33244
|
-
extraMeta[key] = schema[key];
|
|
33245
|
-
}
|
|
33246
|
-
}
|
|
33247
|
-
for (const key of Object.keys(schema)) {
|
|
33248
|
-
if (!RECOGNIZED_KEYS.has(key)) {
|
|
33249
|
-
extraMeta[key] = schema[key];
|
|
33250
|
-
}
|
|
33251
|
-
}
|
|
33252
|
-
if (Object.keys(extraMeta).length > 0) {
|
|
33253
|
-
ctx.registry.add(baseSchema, extraMeta);
|
|
33254
|
-
}
|
|
33255
|
-
return baseSchema;
|
|
33256
|
-
}
|
|
33257
|
-
function fromJSONSchema(schema, params) {
|
|
33258
|
-
if (typeof schema === "boolean") {
|
|
33259
|
-
return schema ? z.any() : z.never();
|
|
33260
|
-
}
|
|
33261
|
-
const version3 = detectVersion(schema, params?.defaultTarget);
|
|
33262
|
-
const defs = schema.$defs || schema.definitions || {};
|
|
33263
|
-
const ctx = {
|
|
33264
|
-
version: version3,
|
|
33265
|
-
defs,
|
|
33266
|
-
refs: /* @__PURE__ */ new Map(),
|
|
33267
|
-
processing: /* @__PURE__ */ new Set(),
|
|
33268
|
-
rootSchema: schema,
|
|
33269
|
-
registry: params?.registry ?? globalRegistry2
|
|
33270
|
-
};
|
|
33271
|
-
return convertSchema(schema, ctx);
|
|
33272
|
-
}
|
|
33273
|
-
|
|
33274
|
-
// node_modules/zod/v4/classic/coerce.js
|
|
33275
|
-
var coerce_exports2 = {};
|
|
33276
|
-
__export(coerce_exports2, {
|
|
33277
|
-
bigint: () => bigint6,
|
|
33278
|
-
boolean: () => boolean6,
|
|
33279
|
-
date: () => date8,
|
|
33280
|
-
number: () => number6,
|
|
33281
|
-
string: () => string6
|
|
33282
|
-
});
|
|
33283
|
-
function string6(params) {
|
|
33284
|
-
return _coercedString2(ZodString2, params);
|
|
33285
|
-
}
|
|
33286
|
-
function number6(params) {
|
|
33287
|
-
return _coercedNumber2(ZodNumber2, params);
|
|
33288
|
-
}
|
|
33289
|
-
function boolean6(params) {
|
|
33290
|
-
return _coercedBoolean2(ZodBoolean2, params);
|
|
33291
|
-
}
|
|
33292
|
-
function bigint6(params) {
|
|
33293
|
-
return _coercedBigint2(ZodBigInt2, params);
|
|
33294
|
-
}
|
|
33295
|
-
function date8(params) {
|
|
33296
|
-
return _coercedDate2(ZodDate2, params);
|
|
33297
|
-
}
|
|
33298
|
-
|
|
33299
|
-
// node_modules/zod/v4/classic/external.js
|
|
33300
|
-
config2(en_default2());
|
|
33301
|
-
|
|
33302
|
-
// src/core/agents/agent-registry.ts
|
|
33303
|
-
var AgentDefinitionSchema = external_exports2.object({
|
|
33304
|
-
id: external_exports2.string(),
|
|
33305
|
-
// ID is required inside the definition object
|
|
33306
|
-
description: external_exports2.string(),
|
|
33307
|
-
systemPrompt: external_exports2.string(),
|
|
33308
|
-
mode: external_exports2.enum(["primary", "subagent"]).optional(),
|
|
33309
|
-
color: external_exports2.string().optional(),
|
|
33310
|
-
hidden: external_exports2.boolean().optional(),
|
|
33311
|
-
thinking: external_exports2.boolean().optional(),
|
|
33312
|
-
maxTokens: external_exports2.number().optional(),
|
|
33313
|
-
budgetTokens: external_exports2.number().optional(),
|
|
33314
|
-
canWrite: external_exports2.boolean(),
|
|
33315
|
-
// Required per interface
|
|
33316
|
-
canBash: external_exports2.boolean()
|
|
33317
|
-
// Required per interface
|
|
33318
|
-
});
|
|
33319
|
-
var AgentRegistry = class _AgentRegistry {
|
|
33320
|
-
static instance;
|
|
33321
|
-
agents = /* @__PURE__ */ new Map();
|
|
33322
|
-
directory = "";
|
|
33323
|
-
constructor() {
|
|
33324
|
-
for (const [name, def] of Object.entries(AGENTS)) {
|
|
33325
|
-
this.agents.set(name, def);
|
|
33326
|
-
}
|
|
33327
|
-
}
|
|
33328
|
-
static getInstance() {
|
|
33329
|
-
if (!_AgentRegistry.instance) {
|
|
33330
|
-
_AgentRegistry.instance = new _AgentRegistry();
|
|
33331
|
-
}
|
|
33332
|
-
return _AgentRegistry.instance;
|
|
33333
|
-
}
|
|
33334
|
-
setDirectory(dir) {
|
|
33335
|
-
this.directory = dir;
|
|
33336
|
-
this.loadCustomAgents();
|
|
33337
|
-
}
|
|
33338
|
-
/**
|
|
33339
|
-
* Get agent definition by name
|
|
33340
|
-
*/
|
|
33341
|
-
getAgent(name) {
|
|
33342
|
-
return this.agents.get(name);
|
|
33343
|
-
}
|
|
33344
|
-
/**
|
|
33345
|
-
* List all available agent names
|
|
33346
|
-
*/
|
|
33347
|
-
listAgents() {
|
|
33348
|
-
return Array.from(this.agents.keys());
|
|
33349
|
-
}
|
|
33350
|
-
/**
|
|
33351
|
-
* Add or update an agent definition
|
|
33352
|
-
*/
|
|
33353
|
-
registerAgent(name, def) {
|
|
33354
|
-
this.agents.set(name, def);
|
|
33355
|
-
log(`[AgentRegistry] Registered agent: ${name}`);
|
|
33356
|
-
}
|
|
33357
|
-
/**
|
|
33358
|
-
* Load custom agents from .opencode/agents.json
|
|
33359
|
-
*/
|
|
33360
|
-
async loadCustomAgents() {
|
|
33361
|
-
if (!this.directory) return;
|
|
33362
|
-
const agentsConfigPath = path4.join(this.directory, PATHS.AGENTS_CONFIG);
|
|
33363
|
-
try {
|
|
33364
|
-
const content = await fs4.readFile(agentsConfigPath, "utf-8");
|
|
33365
|
-
const customAgents = JSON.parse(content);
|
|
33366
|
-
if (typeof customAgents === "object" && customAgents !== null) {
|
|
33367
|
-
for (const [name, def] of Object.entries(customAgents)) {
|
|
33368
|
-
const result = AgentDefinitionSchema.safeParse(def);
|
|
33369
|
-
if (result.success) {
|
|
33370
|
-
this.registerAgent(name, def);
|
|
33371
|
-
} else {
|
|
33372
|
-
log(`[AgentRegistry] Invalid custom agent definition for: ${name}. Errors: ${result.error.message}`);
|
|
33373
|
-
}
|
|
33374
|
-
}
|
|
33375
|
-
}
|
|
33376
|
-
} catch (error92) {
|
|
33377
|
-
if (error92.code !== "ENOENT") {
|
|
33378
|
-
log(`[AgentRegistry] Error loading custom agents: ${error92}`);
|
|
33379
|
-
}
|
|
33380
|
-
}
|
|
33381
|
-
}
|
|
33382
|
-
};
|
|
33383
|
-
|
|
33384
|
-
// src/core/agents/manager/task-launcher.ts
|
|
33385
|
-
var TaskLauncher = class {
|
|
33386
|
-
constructor(client2, directory, store, concurrency, sessionPool2, onTaskError, startPolling) {
|
|
33387
|
-
this.client = client2;
|
|
33388
|
-
this.directory = directory;
|
|
33389
|
-
this.store = store;
|
|
33390
|
-
this.concurrency = concurrency;
|
|
33391
|
-
this.sessionPool = sessionPool2;
|
|
33392
|
-
this.onTaskError = onTaskError;
|
|
33393
|
-
this.startPolling = startPolling;
|
|
33394
|
-
}
|
|
33395
|
-
/**
|
|
33396
|
-
* Unified launch method - handles both single and multiple tasks efficiently.
|
|
33397
|
-
* All session creations happen in parallel immediately.
|
|
33398
|
-
* Concurrency acquisition and prompt firing happen in the background.
|
|
33399
|
-
*/
|
|
33400
|
-
async launch(inputs) {
|
|
33401
|
-
const isArray = Array.isArray(inputs);
|
|
33402
|
-
const taskInputs = isArray ? inputs : [inputs];
|
|
33403
|
-
if (taskInputs.length === 0) return isArray ? [] : null;
|
|
33404
|
-
const tasks = await Promise.all(taskInputs.map(
|
|
33405
|
-
(input) => this.prepareTask(input).catch(() => null)
|
|
33406
|
-
));
|
|
33407
|
-
const successfulTasks = tasks.filter((t) => t !== null);
|
|
33408
|
-
successfulTasks.forEach((task) => {
|
|
33409
|
-
this.executeBackground(task).catch((error92) => {
|
|
33410
|
-
this.onTaskError(task.id, error92);
|
|
33411
|
-
});
|
|
33412
|
-
});
|
|
33413
|
-
if (successfulTasks.length > 0) {
|
|
33414
|
-
this.startPolling();
|
|
33415
|
-
}
|
|
33416
|
-
return isArray ? successfulTasks : successfulTasks[0] || null;
|
|
33417
|
-
}
|
|
33418
|
-
/**
|
|
33419
|
-
* Prepare task: Create session and registration without blocking on concurrency
|
|
33420
|
-
*/
|
|
33421
|
-
async prepareTask(input) {
|
|
33422
|
-
const currentDepth = input.depth ?? 0;
|
|
33423
|
-
if (currentDepth >= PARALLEL_TASK.MAX_DEPTH) {
|
|
33424
|
-
throw new Error(`Maximum task depth (${PARALLEL_TASK.MAX_DEPTH}) reached. To prevent infinite recursion, no further sub-tasks can be spawned.`);
|
|
33425
|
-
}
|
|
33426
|
-
const session = await this.sessionPool.acquire(
|
|
33427
|
-
input.agent,
|
|
33428
|
-
input.parentSessionID,
|
|
33429
|
-
input.description
|
|
33430
|
-
);
|
|
33431
|
-
const sessionID = session.id;
|
|
33432
|
-
const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
|
|
33433
|
-
const task = {
|
|
33434
|
-
id: taskId,
|
|
33435
|
-
sessionID,
|
|
33436
|
-
parentSessionID: input.parentSessionID,
|
|
33437
|
-
description: input.description,
|
|
33438
|
-
prompt: input.prompt,
|
|
33439
|
-
agent: input.agent,
|
|
33440
|
-
status: TASK_STATUS.PENDING,
|
|
33441
|
-
// Start as PENDING
|
|
33442
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
33443
|
-
concurrencyKey: input.agent,
|
|
33444
|
-
depth: (input.depth ?? 0) + 1,
|
|
33445
|
-
mode: input.mode || "normal",
|
|
33446
|
-
groupID: input.groupID
|
|
33447
|
-
};
|
|
33448
|
-
this.store.set(taskId, task);
|
|
33449
|
-
this.store.trackPending(input.parentSessionID, taskId);
|
|
33450
|
-
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
33451
|
-
});
|
|
33452
|
-
const toastManager = getTaskToastManager();
|
|
33453
|
-
if (toastManager) {
|
|
33454
|
-
toastManager.addTask({
|
|
33455
|
-
id: taskId,
|
|
33456
|
-
description: input.description,
|
|
33457
|
-
agent: input.agent,
|
|
33458
|
-
isBackground: true,
|
|
33459
|
-
parentSessionID: input.parentSessionID,
|
|
33460
|
-
sessionID
|
|
33461
|
-
});
|
|
33462
|
-
}
|
|
33463
|
-
presets_exports.sessionCreated(sessionID, input.agent);
|
|
33464
|
-
return task;
|
|
33465
|
-
}
|
|
33466
|
-
/**
|
|
33467
|
-
* Background execution: Acquire slot and fire prompt with auto-retry
|
|
33468
|
-
*/
|
|
33469
|
-
async executeBackground(task) {
|
|
33470
|
-
let attempt = 1;
|
|
33471
|
-
while (true) {
|
|
33472
|
-
try {
|
|
33473
|
-
await this.concurrency.acquire(task.agent);
|
|
33474
|
-
task.status = TASK_STATUS.RUNNING;
|
|
33475
|
-
task.startedAt = /* @__PURE__ */ new Date();
|
|
33476
|
-
this.store.set(task.id, task);
|
|
33477
|
-
taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
|
|
33478
|
-
});
|
|
33479
|
-
const agentDef = AgentRegistry.getInstance().getAgent(task.agent);
|
|
33480
|
-
let finalPrompt = task.prompt;
|
|
33481
|
-
if (agentDef) {
|
|
33482
|
-
finalPrompt = `### AGENT ROLE: ${agentDef.id}
|
|
33483
|
-
${agentDef.description}
|
|
33484
|
-
|
|
33485
|
-
${agentDef.systemPrompt}
|
|
33486
|
-
|
|
33487
|
-
${finalPrompt}`;
|
|
33488
|
-
}
|
|
33489
|
-
const memory = MemoryManager.getInstance().getContext(finalPrompt);
|
|
33490
|
-
const injectedPrompt = memory ? `${memory}
|
|
33491
|
-
|
|
33492
|
-
${finalPrompt}` : finalPrompt;
|
|
33493
|
-
const wireAgent = Object.values(AGENT_NAMES).includes(task.agent) ? task.agent : AGENT_NAMES.COMMANDER;
|
|
33494
|
-
const promptPromise = this.client.session.prompt({
|
|
33495
|
-
path: { id: task.sessionID },
|
|
33496
|
-
body: {
|
|
33497
|
-
agent: wireAgent,
|
|
33498
|
-
tools: {
|
|
33499
|
-
[TOOL_NAMES.DELEGATE_TASK]: true,
|
|
33500
|
-
[TOOL_NAMES.GET_TASK_RESULT]: true,
|
|
33501
|
-
[TOOL_NAMES.LIST_TASKS]: true,
|
|
33502
|
-
[TOOL_NAMES.CANCEL_TASK]: true,
|
|
33503
|
-
[TOOL_NAMES.SKILL]: true,
|
|
33504
|
-
[TOOL_NAMES.RUN_COMMAND]: true
|
|
33505
|
-
},
|
|
33506
|
-
parts: [{ type: PART_TYPES.TEXT, text: injectedPrompt }]
|
|
33507
|
-
}
|
|
33508
|
-
});
|
|
33509
|
-
await Promise.race([
|
|
33510
|
-
promptPromise,
|
|
33511
|
-
new Promise(
|
|
33512
|
-
(_, reject) => setTimeout(() => reject(new Error("Session prompt execution timed out after 600s")), 6e5)
|
|
33513
|
-
)
|
|
33514
|
-
]);
|
|
33515
|
-
return;
|
|
33516
|
-
} catch (error92) {
|
|
33517
|
-
this.concurrency.release(task.agent);
|
|
33518
|
-
const context = {
|
|
33519
|
-
sessionId: task.sessionID,
|
|
33520
|
-
taskId: task.id,
|
|
33521
|
-
agent: task.agent,
|
|
33522
|
-
error: error92 instanceof Error ? error92 : new Error(String(error92)),
|
|
33523
|
-
attempt,
|
|
33524
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
33525
|
-
};
|
|
33526
|
-
const action = handleError(context);
|
|
33527
|
-
if (action.type === "retry") {
|
|
33528
|
-
log(`[AutoRetry] Task ${task.id} failed (attempt ${attempt}). Retrying in ${action.delay}ms...`);
|
|
33529
|
-
if (action.modifyPrompt) {
|
|
33530
|
-
task.prompt += `
|
|
33531
|
-
|
|
33532
|
-
${action.modifyPrompt}`;
|
|
33533
|
-
}
|
|
33534
|
-
await new Promise((r) => setTimeout(r, action.delay));
|
|
33535
|
-
attempt++;
|
|
33536
|
-
continue;
|
|
33537
|
-
}
|
|
33538
|
-
throw error92;
|
|
33539
|
-
}
|
|
33540
|
-
}
|
|
33541
|
-
}
|
|
33542
|
-
};
|
|
33543
|
-
|
|
33544
|
-
// src/core/agents/manager/task-resumer.ts
|
|
33545
|
-
init_shared();
|
|
33546
|
-
var TaskResumer = class {
|
|
33547
|
-
constructor(client2, store, findBySession, startPolling, notifyParentIfAllComplete) {
|
|
33548
|
-
this.client = client2;
|
|
33549
|
-
this.store = store;
|
|
33550
|
-
this.findBySession = findBySession;
|
|
33551
|
-
this.startPolling = startPolling;
|
|
33552
|
-
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
33553
|
-
}
|
|
33554
|
-
async resume(input) {
|
|
33555
|
-
const existingTask = this.findBySession(input.sessionId);
|
|
33556
|
-
if (!existingTask) {
|
|
33557
|
-
throw new Error(`Task not found for session: ${input.sessionId}`);
|
|
33558
|
-
}
|
|
33559
|
-
existingTask.status = TASK_STATUS.RUNNING;
|
|
33560
|
-
existingTask.completedAt = void 0;
|
|
33561
|
-
existingTask.error = void 0;
|
|
33562
|
-
existingTask.result = void 0;
|
|
33563
|
-
existingTask.parentSessionID = input.parentSessionID;
|
|
33564
|
-
existingTask.startedAt = /* @__PURE__ */ new Date();
|
|
33565
|
-
existingTask.stablePolls = 0;
|
|
33566
|
-
this.store.trackPending(input.parentSessionID, existingTask.id);
|
|
33567
|
-
this.startPolling();
|
|
33568
|
-
taskWAL.log(WAL_ACTIONS.UPDATE, existingTask).catch(() => {
|
|
33569
|
-
});
|
|
33570
|
-
log(`Resuming task ${existingTask.id} in session ${existingTask.sessionID}`);
|
|
33571
|
-
this.client.session.prompt({
|
|
33572
|
-
path: { id: existingTask.sessionID },
|
|
33573
|
-
body: {
|
|
33574
|
-
agent: existingTask.agent,
|
|
33575
|
-
parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
|
|
33576
|
-
}
|
|
33577
|
-
}).catch((error92) => {
|
|
33578
|
-
log(`Resume prompt error for ${existingTask.id}:`, error92);
|
|
33579
|
-
existingTask.status = TASK_STATUS.ERROR;
|
|
33580
|
-
existingTask.error = error92 instanceof Error ? error92.message : String(error92);
|
|
33581
|
-
existingTask.completedAt = /* @__PURE__ */ new Date();
|
|
33582
|
-
this.store.untrackPending(input.parentSessionID, existingTask.id);
|
|
33583
|
-
this.store.queueNotification(existingTask);
|
|
33584
|
-
this.notifyParentIfAllComplete(input.parentSessionID).catch(() => {
|
|
33585
|
-
});
|
|
33586
|
-
taskWAL.log(WAL_ACTIONS.UPDATE, existingTask).catch(() => {
|
|
33587
|
-
});
|
|
33588
|
-
});
|
|
33589
|
-
return existingTask;
|
|
33590
|
-
}
|
|
33591
|
-
};
|
|
33592
|
-
|
|
33593
|
-
// src/core/agents/config.ts
|
|
33594
|
-
init_shared();
|
|
33595
|
-
var CONFIG = {
|
|
33596
|
-
TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
|
|
33597
|
-
CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
|
|
33598
|
-
MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
|
|
33599
|
-
POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
|
|
33600
|
-
};
|
|
33601
|
-
|
|
33602
|
-
// src/core/agents/manager/task-poller.ts
|
|
33603
|
-
init_shared();
|
|
33604
|
-
init_shared();
|
|
33605
|
-
|
|
33606
|
-
// src/core/progress/state-broadcaster.ts
|
|
33607
|
-
var StateBroadcaster = class _StateBroadcaster {
|
|
33608
|
-
static _instance;
|
|
33609
|
-
listeners = /* @__PURE__ */ new Set();
|
|
33610
|
-
currentState = null;
|
|
33611
|
-
constructor() {
|
|
33612
|
-
}
|
|
33613
|
-
static getInstance() {
|
|
33614
|
-
if (!_StateBroadcaster._instance) {
|
|
33615
|
-
_StateBroadcaster._instance = new _StateBroadcaster();
|
|
33616
|
-
}
|
|
33617
|
-
return _StateBroadcaster._instance;
|
|
33618
|
-
}
|
|
33619
|
-
subscribe(listener) {
|
|
33620
|
-
this.listeners.add(listener);
|
|
33621
|
-
if (this.currentState) {
|
|
33622
|
-
listener(this.currentState);
|
|
33623
|
-
}
|
|
33624
|
-
return () => this.listeners.delete(listener);
|
|
33625
|
-
}
|
|
33626
|
-
broadcast(state2) {
|
|
33627
|
-
this.currentState = state2;
|
|
33628
|
-
this.listeners.forEach((listener) => {
|
|
33629
|
-
try {
|
|
33630
|
-
listener(state2);
|
|
33631
|
-
} catch (error92) {
|
|
33632
|
-
}
|
|
33633
|
-
});
|
|
33634
|
-
}
|
|
33635
|
-
getCurrentState() {
|
|
33636
|
-
return this.currentState;
|
|
33637
|
-
}
|
|
33638
|
-
};
|
|
33639
|
-
var stateBroadcaster = StateBroadcaster.getInstance();
|
|
33640
|
-
|
|
33641
|
-
// src/core/progress/progress-notifier.ts
|
|
33642
|
-
init_shared();
|
|
33643
|
-
var ProgressNotifier = class _ProgressNotifier {
|
|
33644
|
-
static _instance;
|
|
33645
|
-
manager = null;
|
|
33646
|
-
constructor() {
|
|
33647
|
-
stateBroadcaster.subscribe(this.handleStateChange.bind(this));
|
|
33648
|
-
}
|
|
33649
|
-
static getInstance() {
|
|
33650
|
-
if (!_ProgressNotifier._instance) {
|
|
33651
|
-
_ProgressNotifier._instance = new _ProgressNotifier();
|
|
33652
|
-
}
|
|
33653
|
-
return _ProgressNotifier._instance;
|
|
33654
|
-
}
|
|
33655
|
-
setManager(manager) {
|
|
33656
|
-
this.manager = manager;
|
|
33657
|
-
}
|
|
33658
|
-
/**
|
|
33659
|
-
* Poll current status from ParallelAgentManager and broadcast it
|
|
33660
|
-
*/
|
|
33661
|
-
update() {
|
|
33662
|
-
if (!this.manager) return;
|
|
33663
|
-
const tasks = this.manager.getAllTasks();
|
|
33664
|
-
const running = tasks.filter((t) => t.status === TASK_STATUS.RUNNING);
|
|
33665
|
-
const completed = tasks.filter((t) => t.status === TASK_STATUS.COMPLETED);
|
|
33666
|
-
const total = tasks.length;
|
|
33667
|
-
const percentage = total > 0 ? Math.round(completed.length / total * 100) : 0;
|
|
33668
|
-
const state2 = {
|
|
33669
|
-
missionId: "current-mission",
|
|
33670
|
-
// Could be dynamic
|
|
33671
|
-
status: percentage === 100 ? "completed" : "executing",
|
|
33672
|
-
progress: {
|
|
33673
|
-
totalTasks: total,
|
|
33674
|
-
completedTasks: completed.length,
|
|
33675
|
-
percentage
|
|
33676
|
-
},
|
|
33677
|
-
activeAgents: running.map((t) => ({
|
|
33678
|
-
id: t.id,
|
|
33679
|
-
type: t.agent,
|
|
33680
|
-
status: t.status,
|
|
33681
|
-
currentTask: t.description
|
|
33682
|
-
})),
|
|
33683
|
-
todo: [],
|
|
33684
|
-
// Need to fetch from TodoEnforcer if possible
|
|
33685
|
-
lastUpdated: /* @__PURE__ */ new Date()
|
|
33686
|
-
};
|
|
33687
|
-
stateBroadcaster.broadcast(state2);
|
|
33688
|
-
}
|
|
33689
|
-
handleStateChange(state2) {
|
|
33690
|
-
if (state2.progress.percentage > 0 && state2.progress.percentage % 25 === 0) {
|
|
33691
|
-
const toastManager = getTaskToastManager();
|
|
33692
|
-
if (toastManager) {
|
|
33693
|
-
}
|
|
33694
|
-
}
|
|
33695
|
-
}
|
|
33696
|
-
};
|
|
33697
|
-
var progressNotifier = ProgressNotifier.getInstance();
|
|
33698
|
-
|
|
33699
|
-
// src/core/agents/manager/task-poller.ts
|
|
33700
|
-
var MAX_TASK_DURATION_MS = 6e5;
|
|
33701
|
-
var TaskPoller = class {
|
|
33702
|
-
constructor(client2, store, concurrency, notifyParentIfAllComplete, scheduleCleanup, pruneExpiredTasks, onTaskComplete, onTaskError) {
|
|
33703
|
-
this.client = client2;
|
|
33704
|
-
this.store = store;
|
|
33705
|
-
this.concurrency = concurrency;
|
|
33706
|
-
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
33707
|
-
this.scheduleCleanup = scheduleCleanup;
|
|
33708
|
-
this.pruneExpiredTasks = pruneExpiredTasks;
|
|
33709
|
-
this.onTaskComplete = onTaskComplete;
|
|
33710
|
-
this.onTaskError = onTaskError;
|
|
33711
|
-
}
|
|
33712
|
-
pollingInterval;
|
|
33713
|
-
messageCache = /* @__PURE__ */ new Map();
|
|
33714
|
-
start() {
|
|
33715
|
-
if (this.pollingInterval) return;
|
|
33716
|
-
log("[task-poller.ts] start() - polling started");
|
|
33717
|
-
this.pollingInterval = setInterval(() => this.poll(), CONFIG.POLL_INTERVAL_MS);
|
|
33718
|
-
this.pollingInterval.unref();
|
|
33719
|
-
}
|
|
33720
|
-
stop() {
|
|
33721
|
-
if (this.pollingInterval) {
|
|
33722
|
-
clearInterval(this.pollingInterval);
|
|
33723
|
-
this.pollingInterval = void 0;
|
|
33724
|
-
}
|
|
33725
|
-
}
|
|
33726
|
-
isRunning() {
|
|
33727
|
-
return !!this.pollingInterval;
|
|
33728
|
-
}
|
|
33729
|
-
async poll() {
|
|
33730
|
-
this.pruneExpiredTasks();
|
|
33731
|
-
const running = this.store.getRunning();
|
|
33732
|
-
if (running.length === 0) {
|
|
33733
|
-
this.stop();
|
|
33734
|
-
return;
|
|
33735
|
-
}
|
|
33736
|
-
log("[task-poller.ts] poll() checking", running.length, "running tasks");
|
|
33737
|
-
try {
|
|
33738
|
-
const statusResult = await this.client.session.status();
|
|
33739
|
-
const allStatuses = statusResult.data ?? {};
|
|
33740
|
-
for (const task of running) {
|
|
33741
|
-
try {
|
|
33742
|
-
const taskDuration = Date.now() - task.startedAt.getTime();
|
|
33743
|
-
if (isSessionStale(task.sessionID)) {
|
|
33744
|
-
log(`[task-poller] Task ${task.id} session is stale. Marking as error.`);
|
|
33745
|
-
this.onTaskError?.(task.id, new Error("Session became stale (no response from agent)"));
|
|
33746
|
-
continue;
|
|
33747
|
-
}
|
|
33748
|
-
if (taskDuration > MAX_TASK_DURATION_MS) {
|
|
33749
|
-
log(`[task-poller] Task ${task.id} exceeded max duration (${MAX_TASK_DURATION_MS}ms). Marking as error.`);
|
|
33750
|
-
this.onTaskError?.(task.id, new Error("Task exceeded maximum execution time"));
|
|
33751
|
-
continue;
|
|
33752
|
-
}
|
|
33753
|
-
if (task.status === TASK_STATUS.PENDING) continue;
|
|
33754
|
-
const sessionStatus = allStatuses[task.sessionID];
|
|
33755
|
-
if (sessionStatus?.type === SESSION_STATUS.IDLE) {
|
|
33756
|
-
const elapsed2 = Date.now() - task.startedAt.getTime();
|
|
33757
|
-
if (elapsed2 < CONFIG.MIN_STABILITY_MS) continue;
|
|
33758
|
-
if (!task.hasStartedOutputting && !await this.validateSessionHasOutput(task.sessionID, task)) continue;
|
|
33759
|
-
await this.completeTask(task);
|
|
33760
|
-
continue;
|
|
33761
|
-
}
|
|
33762
|
-
await this.updateTaskProgress(task);
|
|
33763
|
-
const elapsed = Date.now() - task.startedAt.getTime();
|
|
33764
|
-
if (elapsed >= CONFIG.MIN_STABILITY_MS && task.stablePolls && task.stablePolls >= 3) {
|
|
33765
|
-
if (task.hasStartedOutputting || await this.validateSessionHasOutput(task.sessionID, task)) {
|
|
33766
|
-
log(`Task ${task.id} stable for 3 polls, completing...`);
|
|
33767
|
-
await this.completeTask(task);
|
|
33768
|
-
}
|
|
33769
|
-
}
|
|
33770
|
-
} catch (error92) {
|
|
33771
|
-
log(`Poll error for task ${task.id}:`, error92);
|
|
33772
|
-
}
|
|
33773
|
-
}
|
|
33774
|
-
progressNotifier.update();
|
|
33775
|
-
} catch (error92) {
|
|
33776
|
-
log("Polling error:", error92);
|
|
33777
|
-
}
|
|
33778
|
-
}
|
|
33779
|
-
async validateSessionHasOutput(sessionID, task) {
|
|
33780
|
-
try {
|
|
33781
|
-
const response = await this.client.session.messages({ path: { id: sessionID } });
|
|
33782
|
-
const messages = response.data ?? [];
|
|
33783
|
-
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));
|
|
33784
|
-
if (hasOutput && task) {
|
|
33785
|
-
task.hasStartedOutputting = true;
|
|
33786
|
-
}
|
|
33787
|
-
return hasOutput;
|
|
33788
|
-
} catch {
|
|
33789
|
-
return true;
|
|
33790
|
-
}
|
|
33791
|
-
}
|
|
33792
|
-
async completeTask(task) {
|
|
33793
|
-
log("[task-poller.ts] completeTask() called for", task.id, task.agent);
|
|
33794
|
-
task.status = TASK_STATUS.COMPLETED;
|
|
33795
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
33796
|
-
if (task.concurrencyKey) {
|
|
33797
|
-
this.concurrency.release(task.concurrencyKey);
|
|
33798
|
-
this.concurrency.reportResult(task.concurrencyKey, true);
|
|
33799
|
-
task.concurrencyKey = void 0;
|
|
33800
|
-
}
|
|
33801
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
33802
|
-
this.store.queueNotification(task);
|
|
33803
|
-
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
33804
|
-
this.scheduleCleanup(task.id);
|
|
33805
|
-
taskWAL.log(WAL_ACTIONS.COMPLETE, task).catch(() => {
|
|
33806
|
-
});
|
|
33807
|
-
if (this.onTaskComplete) {
|
|
33808
|
-
Promise.resolve(this.onTaskComplete(task)).catch((err) => log("Error in onTaskComplete callback:", err));
|
|
33809
|
-
}
|
|
33810
|
-
const duration5 = formatDuration(task.startedAt, task.completedAt);
|
|
33811
|
-
presets_exports.sessionCompleted(task.sessionID, duration5);
|
|
33812
|
-
log(`Completed ${task.id} (${duration5})`);
|
|
33813
|
-
progressNotifier.update();
|
|
33814
|
-
}
|
|
33815
|
-
async updateTaskProgress(task) {
|
|
33816
|
-
try {
|
|
33817
|
-
const cached3 = this.messageCache.get(task.sessionID);
|
|
33818
|
-
const statusResult = await this.client.session.status();
|
|
33819
|
-
const sessionInfo = statusResult.data?.[task.sessionID];
|
|
33820
|
-
const currentMsgCount = sessionInfo?.messageCount ?? 0;
|
|
33821
|
-
if (cached3 && cached3.count === currentMsgCount) {
|
|
33822
|
-
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
33823
|
-
return;
|
|
33824
|
-
}
|
|
33825
|
-
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
33826
|
-
this.messageCache.set(task.sessionID, { count: currentMsgCount, lastChecked: /* @__PURE__ */ new Date() });
|
|
33827
|
-
if (result.error) return;
|
|
33828
|
-
const messages = result.data ?? [];
|
|
33829
|
-
const assistantMsgs = messages.filter((m) => m.info?.role === MESSAGE_ROLES.ASSISTANT);
|
|
33830
|
-
let toolCalls = 0;
|
|
33831
|
-
let lastTool;
|
|
33832
|
-
let lastMessage;
|
|
33833
|
-
for (const msg of assistantMsgs) {
|
|
33834
|
-
for (const part of msg.parts ?? []) {
|
|
33835
|
-
if (part.type === PART_TYPES.TOOL_USE || part.tool) {
|
|
33836
|
-
toolCalls++;
|
|
33837
|
-
lastTool = part.tool || part.name;
|
|
33838
|
-
}
|
|
33839
|
-
if (part.type === PART_TYPES.TEXT && part.text) {
|
|
33840
|
-
lastMessage = part.text;
|
|
33841
|
-
}
|
|
33842
|
-
}
|
|
33843
|
-
}
|
|
33844
|
-
task.progress = {
|
|
33845
|
-
toolCalls,
|
|
33846
|
-
lastTool,
|
|
33847
|
-
lastMessage: lastMessage?.slice(0, 100),
|
|
33848
|
-
lastUpdate: /* @__PURE__ */ new Date()
|
|
33849
|
-
};
|
|
33850
|
-
if (task.lastMsgCount === currentMsgCount) {
|
|
33851
|
-
task.stablePolls = 0;
|
|
33800
|
+
zodSchema = arraySchema;
|
|
33852
33801
|
} else {
|
|
33853
|
-
|
|
33802
|
+
zodSchema = z.array(z.any());
|
|
33854
33803
|
}
|
|
33855
|
-
|
|
33856
|
-
} catch {
|
|
33804
|
+
break;
|
|
33857
33805
|
}
|
|
33806
|
+
default:
|
|
33807
|
+
throw new Error(`Unsupported type: ${type}`);
|
|
33858
33808
|
}
|
|
33859
|
-
|
|
33860
|
-
|
|
33861
|
-
// src/core/agents/manager/task-cleaner.ts
|
|
33862
|
-
init_shared();
|
|
33863
|
-
init_store();
|
|
33864
|
-
var TaskCleaner = class {
|
|
33865
|
-
constructor(client2, store, concurrency, sessionPool2) {
|
|
33866
|
-
this.client = client2;
|
|
33867
|
-
this.store = store;
|
|
33868
|
-
this.concurrency = concurrency;
|
|
33869
|
-
this.sessionPool = sessionPool2;
|
|
33809
|
+
if (schema.description) {
|
|
33810
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
33870
33811
|
}
|
|
33871
|
-
|
|
33872
|
-
|
|
33873
|
-
for (const [taskId, task] of this.store.getAll().map((t) => [t.id, t])) {
|
|
33874
|
-
const age = now - task.startedAt.getTime();
|
|
33875
|
-
if (age <= CONFIG.TASK_TTL_MS) continue;
|
|
33876
|
-
log(`Timeout: ${taskId}`);
|
|
33877
|
-
if (task.status === TASK_STATUS.RUNNING) {
|
|
33878
|
-
task.status = TASK_STATUS.TIMEOUT;
|
|
33879
|
-
task.error = "Task exceeded 30 minute time limit";
|
|
33880
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
33881
|
-
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
33882
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
33883
|
-
const toastManager = getTaskToastManager();
|
|
33884
|
-
if (toastManager) {
|
|
33885
|
-
toastManager.showCompletionToast({
|
|
33886
|
-
id: taskId,
|
|
33887
|
-
description: task.description,
|
|
33888
|
-
duration: formatDuration(task.startedAt, task.completedAt),
|
|
33889
|
-
status: TASK_STATUS.ERROR,
|
|
33890
|
-
error: task.error
|
|
33891
|
-
});
|
|
33892
|
-
}
|
|
33893
|
-
}
|
|
33894
|
-
this.sessionPool.release(task.sessionID).catch(() => {
|
|
33895
|
-
});
|
|
33896
|
-
clear2(task.sessionID);
|
|
33897
|
-
this.store.delete(taskId);
|
|
33898
|
-
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
33899
|
-
});
|
|
33900
|
-
}
|
|
33901
|
-
this.store.cleanEmptyNotifications();
|
|
33812
|
+
if (schema.default !== void 0) {
|
|
33813
|
+
zodSchema = zodSchema.default(schema.default);
|
|
33902
33814
|
}
|
|
33903
|
-
|
|
33904
|
-
|
|
33905
|
-
|
|
33906
|
-
|
|
33907
|
-
|
|
33908
|
-
try {
|
|
33909
|
-
await this.sessionPool.release(sessionID);
|
|
33910
|
-
clear2(sessionID);
|
|
33911
|
-
} catch {
|
|
33912
|
-
}
|
|
33913
|
-
}
|
|
33914
|
-
this.store.delete(taskId);
|
|
33915
|
-
if (task) taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
33916
|
-
});
|
|
33917
|
-
log(`Cleaned up ${taskId}`);
|
|
33918
|
-
}, 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();
|
|
33919
33820
|
}
|
|
33920
|
-
|
|
33921
|
-
|
|
33922
|
-
|
|
33923
|
-
|
|
33924
|
-
|
|
33925
|
-
|
|
33926
|
-
|
|
33927
|
-
|
|
33928
|
-
const
|
|
33929
|
-
|
|
33930
|
-
|
|
33931
|
-
|
|
33932
|
-
|
|
33933
|
-
|
|
33934
|
-
|
|
33935
|
-
|
|
33936
|
-
|
|
33937
|
-
|
|
33938
|
-
|
|
33939
|
-
|
|
33940
|
-
toastManager.showAllCompleteToast(parentSessionID, completionInfos);
|
|
33941
|
-
} else if (toastManager) {
|
|
33942
|
-
for (const info of completionInfos) {
|
|
33943
|
-
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));
|
|
33944
33841
|
}
|
|
33842
|
+
baseSchema = result;
|
|
33945
33843
|
}
|
|
33946
|
-
|
|
33947
|
-
|
|
33948
|
-
|
|
33949
|
-
|
|
33950
|
-
|
|
33951
|
-
|
|
33952
|
-
|
|
33953
|
-
|
|
33954
|
-
|
|
33955
|
-
|
|
33956
|
-
|
|
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];
|
|
33957
33856
|
}
|
|
33958
|
-
|
|
33959
|
-
|
|
33960
|
-
|
|
33961
|
-
|
|
33962
|
-
|
|
33963
|
-
noReply: !allComplete,
|
|
33964
|
-
parts: [{ type: PART_TYPES.TEXT, text: message }]
|
|
33965
|
-
}
|
|
33966
|
-
});
|
|
33967
|
-
log(`Notified parent ${parentSessionID} (allComplete=${allComplete}, noReply=${!allComplete})`);
|
|
33968
|
-
} catch (error92) {
|
|
33969
|
-
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];
|
|
33970
33862
|
}
|
|
33971
|
-
this.store.clearNotifications(parentSessionID);
|
|
33972
33863
|
}
|
|
33973
|
-
|
|
33974
|
-
|
|
33975
|
-
|
|
33976
|
-
init_shared();
|
|
33977
|
-
|
|
33978
|
-
// src/core/loop/continuation-lock.ts
|
|
33979
|
-
var locks = /* @__PURE__ */ new Map();
|
|
33980
|
-
var LOCK_TIMEOUT_MS = 3e4;
|
|
33981
|
-
function tryAcquireContinuationLock(sessionID, source) {
|
|
33982
|
-
const now = Date.now();
|
|
33983
|
-
const existing = locks.get(sessionID);
|
|
33984
|
-
if (existing?.acquired) {
|
|
33985
|
-
const elapsed = now - existing.timestamp;
|
|
33986
|
-
if (elapsed < LOCK_TIMEOUT_MS) {
|
|
33987
|
-
log("[continuation-lock] Lock denied - already held", {
|
|
33988
|
-
sessionID: sessionID.slice(0, 8),
|
|
33989
|
-
heldBy: existing.source,
|
|
33990
|
-
requestedBy: source,
|
|
33991
|
-
elapsedMs: elapsed
|
|
33992
|
-
});
|
|
33993
|
-
return false;
|
|
33864
|
+
for (const key of Object.keys(schema)) {
|
|
33865
|
+
if (!RECOGNIZED_KEYS.has(key)) {
|
|
33866
|
+
extraMeta[key] = schema[key];
|
|
33994
33867
|
}
|
|
33995
|
-
log("[continuation-lock] Forcing stale lock release", {
|
|
33996
|
-
sessionID: sessionID.slice(0, 8),
|
|
33997
|
-
staleSource: existing.source,
|
|
33998
|
-
elapsedMs: elapsed
|
|
33999
|
-
});
|
|
34000
33868
|
}
|
|
34001
|
-
|
|
34002
|
-
|
|
34003
|
-
sessionID: sessionID.slice(0, 8),
|
|
34004
|
-
source
|
|
34005
|
-
});
|
|
34006
|
-
return true;
|
|
34007
|
-
}
|
|
34008
|
-
function releaseContinuationLock(sessionID) {
|
|
34009
|
-
const existing = locks.get(sessionID);
|
|
34010
|
-
if (existing?.acquired) {
|
|
34011
|
-
const duration5 = Date.now() - existing.timestamp;
|
|
34012
|
-
log("[continuation-lock] Lock released", {
|
|
34013
|
-
sessionID: sessionID.slice(0, 8),
|
|
34014
|
-
source: existing.source,
|
|
34015
|
-
heldMs: duration5
|
|
34016
|
-
});
|
|
33869
|
+
if (Object.keys(extraMeta).length > 0) {
|
|
33870
|
+
ctx.registry.add(baseSchema, extraMeta);
|
|
34017
33871
|
}
|
|
34018
|
-
|
|
33872
|
+
return baseSchema;
|
|
34019
33873
|
}
|
|
34020
|
-
function
|
|
34021
|
-
|
|
34022
|
-
|
|
34023
|
-
if (Date.now() - lock.timestamp >= LOCK_TIMEOUT_MS) {
|
|
34024
|
-
log("[continuation-lock] Stale lock detected during check", {
|
|
34025
|
-
sessionID: sessionID.slice(0, 8)
|
|
34026
|
-
});
|
|
34027
|
-
locks.delete(sessionID);
|
|
34028
|
-
return false;
|
|
33874
|
+
function fromJSONSchema(schema, params) {
|
|
33875
|
+
if (typeof schema === "boolean") {
|
|
33876
|
+
return schema ? z.any() : z.never();
|
|
34029
33877
|
}
|
|
34030
|
-
|
|
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);
|
|
34031
33889
|
}
|
|
34032
|
-
|
|
34033
|
-
|
|
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);
|
|
34034
33914
|
}
|
|
34035
33915
|
|
|
34036
|
-
//
|
|
34037
|
-
|
|
34038
|
-
constructor(client2, store, concurrency, findBySession, notifyParentIfAllComplete, scheduleCleanup, validateSessionHasOutput2, onTaskComplete) {
|
|
34039
|
-
this.client = client2;
|
|
34040
|
-
this.store = store;
|
|
34041
|
-
this.concurrency = concurrency;
|
|
34042
|
-
this.findBySession = findBySession;
|
|
34043
|
-
this.notifyParentIfAllComplete = notifyParentIfAllComplete;
|
|
34044
|
-
this.scheduleCleanup = scheduleCleanup;
|
|
34045
|
-
this.validateSessionHasOutput = validateSessionHasOutput2;
|
|
34046
|
-
this.onTaskComplete = onTaskComplete;
|
|
34047
|
-
}
|
|
34048
|
-
/**
|
|
34049
|
-
* Handle OpenCode session events for proper resource cleanup.
|
|
34050
|
-
* Call this from your plugin's event hook.
|
|
34051
|
-
*/
|
|
34052
|
-
handle(event) {
|
|
34053
|
-
const props = event.properties;
|
|
34054
|
-
if (event.type === SESSION_EVENTS.IDLE) {
|
|
34055
|
-
const sessionID = props?.sessionID;
|
|
34056
|
-
if (!sessionID) return;
|
|
34057
|
-
recordSessionResponse(sessionID);
|
|
34058
|
-
const task = this.findBySession(sessionID);
|
|
34059
|
-
if (!task || task.status !== TASK_STATUS.RUNNING) return;
|
|
34060
|
-
this.handleSessionIdle(task).catch((err) => {
|
|
34061
|
-
log("Error handling session.idle:", err);
|
|
34062
|
-
});
|
|
34063
|
-
}
|
|
34064
|
-
if (event.type === SESSION_EVENTS.DELETED) {
|
|
34065
|
-
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
34066
|
-
if (!sessionID) return;
|
|
34067
|
-
const task = this.findBySession(sessionID);
|
|
34068
|
-
if (!task) return;
|
|
34069
|
-
this.handleSessionDeleted(task);
|
|
34070
|
-
}
|
|
34071
|
-
}
|
|
34072
|
-
async handleSessionIdle(task) {
|
|
34073
|
-
const elapsed = Date.now() - task.startedAt.getTime();
|
|
34074
|
-
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
34075
|
-
log(`Session idle but too early for ${task.id}, waiting...`);
|
|
34076
|
-
return;
|
|
34077
|
-
}
|
|
34078
|
-
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
34079
|
-
if (!hasOutput) {
|
|
34080
|
-
log(`Session idle but no output for ${task.id}, waiting...`);
|
|
34081
|
-
return;
|
|
34082
|
-
}
|
|
34083
|
-
task.status = TASK_STATUS.COMPLETED;
|
|
34084
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34085
|
-
if (task.concurrencyKey) {
|
|
34086
|
-
this.concurrency.release(task.concurrencyKey);
|
|
34087
|
-
this.concurrency.reportResult(task.concurrencyKey, true);
|
|
34088
|
-
task.concurrencyKey = void 0;
|
|
34089
|
-
}
|
|
34090
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
34091
|
-
this.store.queueNotification(task);
|
|
34092
|
-
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
34093
|
-
this.scheduleCleanup(task.id);
|
|
34094
|
-
taskWAL.log(WAL_ACTIONS.COMPLETE, task).catch(() => {
|
|
34095
|
-
});
|
|
34096
|
-
if (this.onTaskComplete) {
|
|
34097
|
-
Promise.resolve(this.onTaskComplete(task)).catch((err) => log("Error in onTaskComplete callback:", err));
|
|
34098
|
-
}
|
|
34099
|
-
progressNotifier.update();
|
|
34100
|
-
log(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
34101
|
-
}
|
|
34102
|
-
handleSessionDeleted(task) {
|
|
34103
|
-
log(`Session deleted event for task ${task.id}`);
|
|
34104
|
-
if (task.status === TASK_STATUS.RUNNING) {
|
|
34105
|
-
task.status = TASK_STATUS.ERROR;
|
|
34106
|
-
task.error = "Session deleted";
|
|
34107
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34108
|
-
}
|
|
34109
|
-
if (task.concurrencyKey) {
|
|
34110
|
-
this.concurrency.release(task.concurrencyKey);
|
|
34111
|
-
this.concurrency.reportResult(task.concurrencyKey, false);
|
|
34112
|
-
task.concurrencyKey = void 0;
|
|
34113
|
-
}
|
|
34114
|
-
this.store.untrackPending(task.parentSessionID, task.id);
|
|
34115
|
-
this.store.clearNotificationsForTask(task.id);
|
|
34116
|
-
this.store.delete(task.id);
|
|
34117
|
-
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
34118
|
-
});
|
|
34119
|
-
cleanupSessionHealth(task.sessionID);
|
|
34120
|
-
cleanupContinuationLock(task.sessionID);
|
|
34121
|
-
progressNotifier.update();
|
|
34122
|
-
log(`Cleaned up deleted session task: ${task.id}`);
|
|
34123
|
-
}
|
|
34124
|
-
};
|
|
33916
|
+
// node_modules/zod/v4/classic/external.js
|
|
33917
|
+
config2(en_default2());
|
|
34125
33918
|
|
|
34126
|
-
// src/core/agents/
|
|
34127
|
-
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34134
|
-
|
|
34135
|
-
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
//
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
constructor(client2, directory, config3 = {}) {
|
|
34151
|
-
this.client = client2;
|
|
34152
|
-
this.directory = directory;
|
|
34153
|
-
this.config = { ...DEFAULT_CONFIG, ...config3 };
|
|
34154
|
-
this.startHealthCheck();
|
|
34155
|
-
}
|
|
34156
|
-
static getInstance(client2, directory, config3) {
|
|
34157
|
-
if (!_SessionPool._instance) {
|
|
34158
|
-
if (!client2 || !directory) {
|
|
34159
|
-
throw new Error("SessionPool requires client and directory on first call");
|
|
34160
|
-
}
|
|
34161
|
-
_SessionPool._instance = new _SessionPool(client2, directory, config3);
|
|
34162
|
-
}
|
|
34163
|
-
return _SessionPool._instance;
|
|
34164
|
-
}
|
|
34165
|
-
/**
|
|
34166
|
-
* Acquire a session from the pool or create a new one.
|
|
34167
|
-
*/
|
|
34168
|
-
async acquire(agentName, parentSessionID, description) {
|
|
34169
|
-
const poolKey = this.getPoolKey(agentName);
|
|
34170
|
-
const agentPool = this.pool.get(poolKey) || [];
|
|
34171
|
-
const available = agentPool.find((s) => !s.inUse && s.reuseCount < this.config.maxReuseCount);
|
|
34172
|
-
if (available) {
|
|
34173
|
-
available.inUse = true;
|
|
34174
|
-
available.lastUsedAt = /* @__PURE__ */ new Date();
|
|
34175
|
-
available.reuseCount++;
|
|
34176
|
-
this.stats.reuseHits++;
|
|
34177
|
-
log(`[SessionPool] Reusing session ${available.id.slice(0, 8)}... for ${agentName} (reuse #${available.reuseCount})`);
|
|
34178
|
-
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);
|
|
34179
33943
|
}
|
|
34180
|
-
this.stats.creationMisses++;
|
|
34181
|
-
return this.createSession(agentName, parentSessionID, description);
|
|
34182
33944
|
}
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
|
|
34186
|
-
async release(sessionId) {
|
|
34187
|
-
const session = this.sessionsById.get(sessionId);
|
|
34188
|
-
if (!session) {
|
|
34189
|
-
log(`[SessionPool] Session ${sessionId.slice(0, 8)}... not found in pool`);
|
|
34190
|
-
return;
|
|
34191
|
-
}
|
|
34192
|
-
const now = Date.now();
|
|
34193
|
-
const age = now - session.createdAt.getTime();
|
|
34194
|
-
const idle = now - session.lastUsedAt.getTime();
|
|
34195
|
-
if (session.reuseCount >= this.config.maxReuseCount || age > this.config.idleTimeoutMs * 2) {
|
|
34196
|
-
await this.invalidate(sessionId);
|
|
34197
|
-
return;
|
|
34198
|
-
}
|
|
34199
|
-
const poolKey = this.getPoolKey(session.agentName);
|
|
34200
|
-
const agentPool = this.pool.get(poolKey) || [];
|
|
34201
|
-
const availableCount = agentPool.filter((s) => !s.inUse).length;
|
|
34202
|
-
if (availableCount >= this.config.maxPoolSizePerAgent) {
|
|
34203
|
-
const oldest = agentPool.filter((s) => !s.inUse).sort((a, b) => a.lastUsedAt.getTime() - b.lastUsedAt.getTime())[0];
|
|
34204
|
-
if (oldest) {
|
|
34205
|
-
await this.deleteSession(oldest.id);
|
|
34206
|
-
}
|
|
33945
|
+
static getInstance() {
|
|
33946
|
+
if (!_AgentRegistry.instance) {
|
|
33947
|
+
_AgentRegistry.instance = new _AgentRegistry();
|
|
34207
33948
|
}
|
|
34208
|
-
|
|
34209
|
-
session.inUse = false;
|
|
34210
|
-
log(`[SessionPool] Released session ${sessionId.slice(0, 8)}... to pool`);
|
|
33949
|
+
return _AgentRegistry.instance;
|
|
34211
33950
|
}
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
async invalidate(sessionId) {
|
|
34216
|
-
const session = this.sessionsById.get(sessionId);
|
|
34217
|
-
if (!session) return;
|
|
34218
|
-
await this.deleteSession(sessionId);
|
|
34219
|
-
log(`[SessionPool] Invalidated session ${sessionId.slice(0, 8)}...`);
|
|
33951
|
+
setDirectory(dir) {
|
|
33952
|
+
this.directory = dir;
|
|
33953
|
+
this.loadCustomAgents();
|
|
34220
33954
|
}
|
|
34221
33955
|
/**
|
|
34222
|
-
* Get
|
|
33956
|
+
* Get agent definition by name
|
|
34223
33957
|
*/
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
for (const [agentName, sessions] of this.pool.entries()) {
|
|
34227
|
-
const inUse = sessions.filter((s) => s.inUse).length;
|
|
34228
|
-
byAgent[agentName] = {
|
|
34229
|
-
total: sessions.length,
|
|
34230
|
-
inUse,
|
|
34231
|
-
available: sessions.length - inUse
|
|
34232
|
-
};
|
|
34233
|
-
}
|
|
34234
|
-
const allSessions = Array.from(this.sessionsById.values());
|
|
34235
|
-
const inUseCount = allSessions.filter((s) => s.inUse).length;
|
|
34236
|
-
return {
|
|
34237
|
-
totalSessions: allSessions.length,
|
|
34238
|
-
sessionsInUse: inUseCount,
|
|
34239
|
-
availableSessions: allSessions.length - inUseCount,
|
|
34240
|
-
reuseHits: this.stats.reuseHits,
|
|
34241
|
-
creationMisses: this.stats.creationMisses,
|
|
34242
|
-
byAgent
|
|
34243
|
-
};
|
|
33958
|
+
getAgent(name) {
|
|
33959
|
+
return this.agents.get(name);
|
|
34244
33960
|
}
|
|
34245
33961
|
/**
|
|
34246
|
-
*
|
|
33962
|
+
* List all available agent names
|
|
34247
33963
|
*/
|
|
34248
|
-
|
|
34249
|
-
|
|
34250
|
-
let cleanedCount = 0;
|
|
34251
|
-
for (const [sessionId, session] of this.sessionsById.entries()) {
|
|
34252
|
-
if (session.inUse) continue;
|
|
34253
|
-
const idle = now - session.lastUsedAt.getTime();
|
|
34254
|
-
if (idle > this.config.idleTimeoutMs) {
|
|
34255
|
-
await this.deleteSession(sessionId);
|
|
34256
|
-
cleanedCount++;
|
|
34257
|
-
}
|
|
34258
|
-
}
|
|
34259
|
-
if (cleanedCount > 0) {
|
|
34260
|
-
log(`[SessionPool] Cleaned up ${cleanedCount} stale sessions`);
|
|
34261
|
-
}
|
|
34262
|
-
return cleanedCount;
|
|
33964
|
+
listAgents() {
|
|
33965
|
+
return Array.from(this.agents.keys());
|
|
34263
33966
|
}
|
|
34264
33967
|
/**
|
|
34265
|
-
*
|
|
33968
|
+
* Add or update an agent definition
|
|
34266
33969
|
*/
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
clearInterval(this.healthCheckInterval);
|
|
34271
|
-
this.healthCheckInterval = null;
|
|
34272
|
-
}
|
|
34273
|
-
const deletePromises = Array.from(this.sessionsById.keys()).map(
|
|
34274
|
-
(id) => this.deleteSession(id).catch(() => {
|
|
34275
|
-
})
|
|
34276
|
-
);
|
|
34277
|
-
await Promise.all(deletePromises);
|
|
34278
|
-
this.pool.clear();
|
|
34279
|
-
this.sessionsById.clear();
|
|
34280
|
-
log("[SessionPool] Shutdown complete");
|
|
33970
|
+
registerAgent(name, def) {
|
|
33971
|
+
this.agents.set(name, def);
|
|
33972
|
+
log(`[AgentRegistry] Registered agent: ${name}`);
|
|
34281
33973
|
}
|
|
34282
|
-
// =========================================================================
|
|
34283
|
-
// Private Methods
|
|
34284
|
-
// =========================================================================
|
|
34285
33974
|
/**
|
|
34286
|
-
*
|
|
33975
|
+
* Load custom agents from .opencode/agents.json
|
|
34287
33976
|
*/
|
|
34288
|
-
async
|
|
34289
|
-
|
|
34290
|
-
|
|
34291
|
-
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);
|
|
34292
33980
|
try {
|
|
34293
|
-
await
|
|
34294
|
-
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
|
|
34304
|
-
async createSession(agentName, parentSessionID, description) {
|
|
34305
|
-
log(`[SessionPool] Creating new session for ${agentName}`);
|
|
34306
|
-
const result = await Promise.race([
|
|
34307
|
-
this.client.session.create({
|
|
34308
|
-
body: {
|
|
34309
|
-
parentID: parentSessionID,
|
|
34310
|
-
title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${description}`
|
|
34311
|
-
},
|
|
34312
|
-
query: { directory: this.directory }
|
|
34313
|
-
}),
|
|
34314
|
-
new Promise(
|
|
34315
|
-
(_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 60s")), 6e4)
|
|
34316
|
-
)
|
|
34317
|
-
]);
|
|
34318
|
-
if (result.error || !result.data?.id) {
|
|
34319
|
-
throw new Error(`Session creation failed: ${result.error || "No ID"}`);
|
|
34320
|
-
}
|
|
34321
|
-
const session = {
|
|
34322
|
-
id: result.data.id,
|
|
34323
|
-
agentName,
|
|
34324
|
-
projectDirectory: this.directory,
|
|
34325
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
34326
|
-
lastUsedAt: /* @__PURE__ */ new Date(),
|
|
34327
|
-
reuseCount: 0,
|
|
34328
|
-
inUse: true,
|
|
34329
|
-
health: "healthy",
|
|
34330
|
-
lastResetAt: /* @__PURE__ */ new Date()
|
|
34331
|
-
};
|
|
34332
|
-
const poolKey = this.getPoolKey(agentName);
|
|
34333
|
-
const agentPool = this.pool.get(poolKey) || [];
|
|
34334
|
-
agentPool.push(session);
|
|
34335
|
-
this.pool.set(poolKey, agentPool);
|
|
34336
|
-
this.sessionsById.set(session.id, session);
|
|
34337
|
-
return session;
|
|
34338
|
-
}
|
|
34339
|
-
async deleteSession(sessionId) {
|
|
34340
|
-
const session = this.sessionsById.get(sessionId);
|
|
34341
|
-
if (!session) return;
|
|
34342
|
-
this.sessionsById.delete(sessionId);
|
|
34343
|
-
const poolKey = this.getPoolKey(session.agentName);
|
|
34344
|
-
const agentPool = this.pool.get(poolKey);
|
|
34345
|
-
if (agentPool) {
|
|
34346
|
-
const idx = agentPool.findIndex((s) => s.id === sessionId);
|
|
34347
|
-
if (idx !== -1) {
|
|
34348
|
-
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
|
+
}
|
|
34349
33992
|
}
|
|
34350
|
-
|
|
34351
|
-
|
|
33993
|
+
} catch (error92) {
|
|
33994
|
+
if (error92.code !== "ENOENT") {
|
|
33995
|
+
log(`[AgentRegistry] Error loading custom agents: ${error92}`);
|
|
34352
33996
|
}
|
|
34353
33997
|
}
|
|
34354
|
-
try {
|
|
34355
|
-
await this.client.session.delete({ path: { id: sessionId } });
|
|
34356
|
-
} catch {
|
|
34357
|
-
}
|
|
34358
|
-
}
|
|
34359
|
-
startHealthCheck() {
|
|
34360
|
-
this.healthCheckInterval = setInterval(() => {
|
|
34361
|
-
this.cleanup().catch(() => {
|
|
34362
|
-
});
|
|
34363
|
-
}, this.config.healthCheckIntervalMs);
|
|
34364
|
-
this.healthCheckInterval.unref?.();
|
|
34365
33998
|
}
|
|
34366
33999
|
};
|
|
34367
|
-
var sessionPool = {
|
|
34368
|
-
getInstance: SessionPool.getInstance.bind(SessionPool)
|
|
34369
|
-
};
|
|
34370
|
-
|
|
34371
|
-
// src/core/agents/manager.ts
|
|
34372
|
-
init_core2();
|
|
34373
34000
|
|
|
34374
34001
|
// src/core/todo/todo-manager.ts
|
|
34375
34002
|
init_shared();
|
|
34376
34003
|
import * as fs5 from "node:fs";
|
|
34377
34004
|
import * as path5 from "node:path";
|
|
34378
|
-
import * as
|
|
34005
|
+
import * as crypto from "node:crypto";
|
|
34379
34006
|
var TodoManager = class _TodoManager {
|
|
34380
34007
|
static _instance;
|
|
34381
34008
|
directory = "";
|
|
@@ -34445,13 +34072,22 @@ var TodoManager = class _TodoManager {
|
|
|
34445
34072
|
});
|
|
34446
34073
|
}
|
|
34447
34074
|
async _internalUpdate(expectedVersion, updater, author) {
|
|
34448
|
-
const MAX_RETRIES2 =
|
|
34449
|
-
const
|
|
34075
|
+
const MAX_RETRIES2 = 5;
|
|
34076
|
+
const BASE_DELAY_MS = 50;
|
|
34450
34077
|
for (let attempt = 0; attempt < MAX_RETRIES2; attempt++) {
|
|
34451
34078
|
try {
|
|
34452
34079
|
const current = await this.readWithVersion();
|
|
34453
34080
|
if (current.version.version !== expectedVersion) {
|
|
34454
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
|
+
}
|
|
34455
34091
|
return {
|
|
34456
34092
|
success: false,
|
|
34457
34093
|
currentVersion: current.version.version,
|
|
@@ -34474,18 +34110,21 @@ var TodoManager = class _TodoManager {
|
|
|
34474
34110
|
await fs5.promises.rename(tmpPath, this.todoPath);
|
|
34475
34111
|
this.logChange(newVersion, newContent, author).catch(() => {
|
|
34476
34112
|
});
|
|
34477
|
-
log(`[TodoManager] Updated TODO to v${newVersion} by ${author}`);
|
|
34113
|
+
log(`[TodoManager] Updated TODO to v${newVersion} by ${author}${attempt > 0 ? ` (after ${attempt} retries)` : ""}`);
|
|
34478
34114
|
return { success: true, currentVersion: newVersion };
|
|
34479
34115
|
} catch (error92) {
|
|
34480
34116
|
if (attempt === MAX_RETRIES2 - 1) throw error92;
|
|
34481
|
-
|
|
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));
|
|
34482
34120
|
}
|
|
34483
34121
|
}
|
|
34484
|
-
throw new Error("Failed to update TODO");
|
|
34122
|
+
throw new Error("Failed to update TODO after max retries");
|
|
34485
34123
|
}
|
|
34486
34124
|
async updateItem(searchText, newStatus, author = "system") {
|
|
34487
|
-
|
|
34488
|
-
|
|
34125
|
+
const MAX_RETRIES2 = 5;
|
|
34126
|
+
const BASE_DELAY_MS = 50;
|
|
34127
|
+
for (let attempt = 0; attempt < MAX_RETRIES2; attempt++) {
|
|
34489
34128
|
const data = await this.readWithVersion();
|
|
34490
34129
|
const statusMap = {
|
|
34491
34130
|
[TODO_CONSTANTS.STATUS.PENDING]: TODO_CONSTANTS.MARKERS.PENDING,
|
|
@@ -34508,13 +34147,18 @@ var TodoManager = class _TodoManager {
|
|
|
34508
34147
|
}, author);
|
|
34509
34148
|
if (result.success) return true;
|
|
34510
34149
|
if (!result.conflict) return false;
|
|
34511
|
-
|
|
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
|
+
}
|
|
34512
34155
|
}
|
|
34513
34156
|
return false;
|
|
34514
34157
|
}
|
|
34515
34158
|
async addSubTask(parentText, subTaskText, author = "system") {
|
|
34516
|
-
|
|
34517
|
-
|
|
34159
|
+
const MAX_RETRIES2 = 5;
|
|
34160
|
+
const BASE_DELAY_MS = 50;
|
|
34161
|
+
for (let attempt = 0; attempt < MAX_RETRIES2; attempt++) {
|
|
34518
34162
|
const data = await this.readWithVersion();
|
|
34519
34163
|
const result = await this.update(data.version.version, (content) => {
|
|
34520
34164
|
const lines = content.split("\n");
|
|
@@ -34538,7 +34182,11 @@ var TodoManager = class _TodoManager {
|
|
|
34538
34182
|
}, author);
|
|
34539
34183
|
if (result.success) return true;
|
|
34540
34184
|
if (!result.conflict) return false;
|
|
34541
|
-
|
|
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
|
+
}
|
|
34542
34190
|
}
|
|
34543
34191
|
return false;
|
|
34544
34192
|
}
|
|
@@ -34547,7 +34195,7 @@ var TodoManager = class _TodoManager {
|
|
|
34547
34195
|
version: version3,
|
|
34548
34196
|
timestamp: Date.now(),
|
|
34549
34197
|
author,
|
|
34550
|
-
contentHash:
|
|
34198
|
+
contentHash: crypto.createHash("sha256").update(content).digest("hex"),
|
|
34551
34199
|
size: content.length
|
|
34552
34200
|
};
|
|
34553
34201
|
await fs5.promises.appendFile(this.historyPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
@@ -34562,12 +34210,8 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34562
34210
|
directory;
|
|
34563
34211
|
concurrency = new ConcurrencyController();
|
|
34564
34212
|
sessionPool;
|
|
34565
|
-
//
|
|
34566
|
-
|
|
34567
|
-
resumer;
|
|
34568
|
-
poller;
|
|
34569
|
-
cleaner;
|
|
34570
|
-
eventHandler;
|
|
34213
|
+
// Unified executor (replaces 5 separate components)
|
|
34214
|
+
executor;
|
|
34571
34215
|
constructor(client2, directory) {
|
|
34572
34216
|
this.client = client2;
|
|
34573
34217
|
this.directory = directory;
|
|
@@ -34578,42 +34222,12 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34578
34222
|
AgentRegistry.getInstance().setDirectory(directory);
|
|
34579
34223
|
TodoManager.getInstance().setDirectory(directory);
|
|
34580
34224
|
this.sessionPool = SessionPool.getInstance(client2, directory);
|
|
34581
|
-
this.
|
|
34582
|
-
this.poller = new TaskPoller(
|
|
34583
|
-
client2,
|
|
34584
|
-
this.store,
|
|
34585
|
-
this.concurrency,
|
|
34586
|
-
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
34587
|
-
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
34588
|
-
() => this.cleaner.pruneExpiredTasks(),
|
|
34589
|
-
(task) => this.handleTaskComplete(task),
|
|
34590
|
-
(taskId, error92) => this.handleTaskError(taskId, error92)
|
|
34591
|
-
);
|
|
34592
|
-
this.launcher = new TaskLauncher(
|
|
34225
|
+
this.executor = new UnifiedTaskExecutor(
|
|
34593
34226
|
client2,
|
|
34594
34227
|
directory,
|
|
34595
34228
|
this.store,
|
|
34596
34229
|
this.concurrency,
|
|
34597
|
-
this.sessionPool
|
|
34598
|
-
(taskId, error92) => this.handleTaskError(taskId, error92),
|
|
34599
|
-
() => this.poller.start()
|
|
34600
|
-
);
|
|
34601
|
-
this.resumer = new TaskResumer(
|
|
34602
|
-
client2,
|
|
34603
|
-
this.store,
|
|
34604
|
-
(sessionID) => this.findBySession(sessionID),
|
|
34605
|
-
() => this.poller.start(),
|
|
34606
|
-
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID)
|
|
34607
|
-
);
|
|
34608
|
-
this.eventHandler = new EventHandler(
|
|
34609
|
-
client2,
|
|
34610
|
-
this.store,
|
|
34611
|
-
this.concurrency,
|
|
34612
|
-
(sessionID) => this.findBySession(sessionID),
|
|
34613
|
-
(parentSessionID) => this.cleaner.notifyParentIfAllComplete(parentSessionID),
|
|
34614
|
-
(taskId) => this.cleaner.scheduleCleanup(taskId),
|
|
34615
|
-
(sessionID) => this.poller.validateSessionHasOutput(sessionID),
|
|
34616
|
-
(task) => this.handleTaskComplete(task)
|
|
34230
|
+
this.sessionPool
|
|
34617
34231
|
);
|
|
34618
34232
|
progressNotifier.setManager(this);
|
|
34619
34233
|
this.recoverActiveTasks().catch((err) => {
|
|
@@ -34633,13 +34247,22 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34633
34247
|
// Public API
|
|
34634
34248
|
// ========================================================================
|
|
34635
34249
|
async launch(inputs) {
|
|
34636
|
-
|
|
34637
|
-
|
|
34638
|
-
|
|
34639
|
-
|
|
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
|
+
}
|
|
34640
34259
|
}
|
|
34641
34260
|
async resume(input) {
|
|
34642
|
-
|
|
34261
|
+
const task = await this.executor.resume(input);
|
|
34262
|
+
if (!task) {
|
|
34263
|
+
throw new Error(`Task not found: ${input.sessionId}`);
|
|
34264
|
+
}
|
|
34265
|
+
return task;
|
|
34643
34266
|
}
|
|
34644
34267
|
getTask(id) {
|
|
34645
34268
|
return this.store.get(id);
|
|
@@ -34654,25 +34277,11 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34654
34277
|
return this.store.getByParent(parentSessionID);
|
|
34655
34278
|
}
|
|
34656
34279
|
async cancelTask(taskId) {
|
|
34657
|
-
const
|
|
34658
|
-
if (
|
|
34659
|
-
|
|
34660
|
-
task.error = "Cancelled by user";
|
|
34661
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34662
|
-
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
34663
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
34664
|
-
try {
|
|
34665
|
-
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
34666
|
-
log(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
34667
|
-
} catch {
|
|
34668
|
-
log(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
34280
|
+
const result = await this.executor.cancel(taskId);
|
|
34281
|
+
if (result) {
|
|
34282
|
+
progressNotifier.update();
|
|
34669
34283
|
}
|
|
34670
|
-
|
|
34671
|
-
taskWAL.log(WAL_ACTIONS.DELETE, task).catch(() => {
|
|
34672
|
-
});
|
|
34673
|
-
progressNotifier.update();
|
|
34674
|
-
log(`Cancelled ${taskId}`);
|
|
34675
|
-
return true;
|
|
34284
|
+
return result;
|
|
34676
34285
|
}
|
|
34677
34286
|
async getResult(taskId) {
|
|
34678
34287
|
const task = this.store.get(taskId);
|
|
@@ -34703,7 +34312,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34703
34312
|
return this.concurrency;
|
|
34704
34313
|
}
|
|
34705
34314
|
cleanup() {
|
|
34706
|
-
this.
|
|
34315
|
+
this.executor.cleanup();
|
|
34707
34316
|
stopHealthCheck();
|
|
34708
34317
|
this.store.clear();
|
|
34709
34318
|
MemoryManager.getInstance().clearTaskMemory();
|
|
@@ -34715,7 +34324,7 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34715
34324
|
// Event Handling
|
|
34716
34325
|
// ========================================================================
|
|
34717
34326
|
handleEvent(event) {
|
|
34718
|
-
|
|
34327
|
+
log("[ParallelAgentManager] Event received:", event.type);
|
|
34719
34328
|
}
|
|
34720
34329
|
// ========================================================================
|
|
34721
34330
|
// Private Helpers
|
|
@@ -34724,87 +34333,17 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
34724
34333
|
return this.store.getAll().find((t) => t.sessionID === sessionID);
|
|
34725
34334
|
}
|
|
34726
34335
|
handleTaskError(taskId, error92) {
|
|
34727
|
-
|
|
34728
|
-
if (!task) return;
|
|
34729
|
-
task.status = TASK_STATUS.ERROR;
|
|
34730
|
-
task.error = error92 instanceof Error ? error92.message : String(error92);
|
|
34731
|
-
task.completedAt = /* @__PURE__ */ new Date();
|
|
34732
|
-
if (task.concurrencyKey) {
|
|
34733
|
-
this.concurrency.release(task.concurrencyKey);
|
|
34734
|
-
this.concurrency.reportResult(task.concurrencyKey, false);
|
|
34735
|
-
}
|
|
34736
|
-
this.store.untrackPending(task.parentSessionID, taskId);
|
|
34737
|
-
this.cleaner.notifyParentIfAllComplete(task.parentSessionID);
|
|
34738
|
-
this.cleaner.scheduleCleanup(taskId);
|
|
34739
|
-
progressNotifier.update();
|
|
34740
|
-
taskWAL.log(WAL_ACTIONS.UPDATE, task).catch(() => {
|
|
34741
|
-
});
|
|
34336
|
+
log(`[ParallelAgentManager] Delegating error handling to executor for task ${taskId}`);
|
|
34742
34337
|
}
|
|
34743
34338
|
async handleTaskComplete(task) {
|
|
34744
|
-
|
|
34745
|
-
log(`[MSVP] Triggering Unit Review for task ${task.id}`);
|
|
34746
|
-
try {
|
|
34747
|
-
await this.launch({
|
|
34748
|
-
agent: AGENT_NAMES.REVIEWER,
|
|
34749
|
-
description: `Unit Review: ${task.description}`,
|
|
34750
|
-
prompt: `Perform a Unit Review (verification) for the completed task (\`${task.description}\`).
|
|
34751
|
-
Key Checklist:
|
|
34752
|
-
1. Verify if unit test code for the module is written and passes.
|
|
34753
|
-
2. Check for code quality and modularity compliance.
|
|
34754
|
-
3. Instruct immediate correction of found defects or report them.
|
|
34755
|
-
|
|
34756
|
-
This task ensures the completeness of the unit before global integration.`,
|
|
34757
|
-
parentSessionID: task.parentSessionID,
|
|
34758
|
-
depth: task.depth,
|
|
34759
|
-
groupID: task.groupID || task.id
|
|
34760
|
-
// Group reviews with their origins
|
|
34761
|
-
});
|
|
34762
|
-
} catch (error92) {
|
|
34763
|
-
log(`[MSVP] Failed to trigger review for ${task.id}:`, error92);
|
|
34764
|
-
}
|
|
34765
|
-
}
|
|
34339
|
+
log(`[ParallelAgentManager] Task ${task.id} completed`);
|
|
34766
34340
|
progressNotifier.update();
|
|
34767
34341
|
}
|
|
34768
34342
|
async recoverActiveTasks() {
|
|
34769
|
-
const
|
|
34770
|
-
if (tasksMap.size === 0) return;
|
|
34771
|
-
const tasks = Array.from(tasksMap.values());
|
|
34772
|
-
log(`Attempting to recover ${tasks.length} tasks from WAL in parallel...`);
|
|
34773
|
-
let recoveredCount = 0;
|
|
34774
|
-
const chunks = [];
|
|
34775
|
-
const chunkSize = 10;
|
|
34776
|
-
for (let i = 0; i < tasks.length; i += chunkSize) {
|
|
34777
|
-
chunks.push(tasks.slice(i, i + chunkSize));
|
|
34778
|
-
}
|
|
34779
|
-
for (const chunk of chunks) {
|
|
34780
|
-
await Promise.all(chunk.map(async (task) => {
|
|
34781
|
-
if (task.status === TASK_STATUS.RUNNING) {
|
|
34782
|
-
try {
|
|
34783
|
-
const status = await this.client.session.get({ path: { id: task.sessionID } });
|
|
34784
|
-
if (!status.error) {
|
|
34785
|
-
this.store.set(task.id, task);
|
|
34786
|
-
this.store.trackPending(task.parentSessionID, task.id);
|
|
34787
|
-
const toastManager = getTaskToastManager();
|
|
34788
|
-
if (toastManager) {
|
|
34789
|
-
toastManager.addTask({
|
|
34790
|
-
id: task.id,
|
|
34791
|
-
description: task.description,
|
|
34792
|
-
agent: task.agent,
|
|
34793
|
-
isBackground: true,
|
|
34794
|
-
parentSessionID: task.parentSessionID,
|
|
34795
|
-
sessionID: task.sessionID
|
|
34796
|
-
});
|
|
34797
|
-
}
|
|
34798
|
-
recoveredCount++;
|
|
34799
|
-
}
|
|
34800
|
-
} catch {
|
|
34801
|
-
}
|
|
34802
|
-
}
|
|
34803
|
-
}));
|
|
34804
|
-
}
|
|
34343
|
+
const recoveredCount = await this.executor.recoverAll();
|
|
34805
34344
|
if (recoveredCount > 0) {
|
|
34806
|
-
log(`Recovered ${recoveredCount} active tasks.`);
|
|
34807
|
-
|
|
34345
|
+
log(`Recovered ${recoveredCount} active tasks via UnifiedTaskExecutor.`);
|
|
34346
|
+
progressNotifier.update();
|
|
34808
34347
|
}
|
|
34809
34348
|
}
|
|
34810
34349
|
};
|
|
@@ -35498,7 +35037,7 @@ async function list() {
|
|
|
35498
35037
|
expired: new Date(entry.expiresAt) < now
|
|
35499
35038
|
}));
|
|
35500
35039
|
}
|
|
35501
|
-
async function
|
|
35040
|
+
async function clear2() {
|
|
35502
35041
|
const metadata = await readMetadata();
|
|
35503
35042
|
const count = Object.keys(metadata.documents).length;
|
|
35504
35043
|
for (const filename of Object.keys(metadata.documents)) {
|
|
@@ -35984,7 +35523,7 @@ Cached: ${doc.fetchedAt}
|
|
|
35984
35523
|
${doc.content}`;
|
|
35985
35524
|
}
|
|
35986
35525
|
case CACHE_ACTIONS.CLEAR: {
|
|
35987
|
-
const count = await
|
|
35526
|
+
const count = await clear2();
|
|
35988
35527
|
return `Cleared ${count} cached documents`;
|
|
35989
35528
|
}
|
|
35990
35529
|
case CACHE_ACTIONS.STATS: {
|
|
@@ -36252,6 +35791,10 @@ Runs TypeScript compiler and/or ESLint to find issues.
|
|
|
36252
35791
|
// src/index.ts
|
|
36253
35792
|
init_shared();
|
|
36254
35793
|
|
|
35794
|
+
// src/core/notification/toast.ts
|
|
35795
|
+
init_toast_core();
|
|
35796
|
+
init_shared();
|
|
35797
|
+
|
|
36255
35798
|
// src/hooks/constants.ts
|
|
36256
35799
|
init_shared();
|
|
36257
35800
|
var HOOK_ACTIONS = {
|
|
@@ -36907,7 +36450,7 @@ function getLatest(sessionId) {
|
|
|
36907
36450
|
const history = progressHistory.get(sessionId);
|
|
36908
36451
|
return history?.[history.length - 1];
|
|
36909
36452
|
}
|
|
36910
|
-
function
|
|
36453
|
+
function clearSession(sessionId) {
|
|
36911
36454
|
progressHistory.delete(sessionId);
|
|
36912
36455
|
sessionStartTimes.delete(sessionId);
|
|
36913
36456
|
}
|
|
@@ -37238,15 +36781,29 @@ function verifyMissionCompletionSync(directory) {
|
|
|
37238
36781
|
}
|
|
37239
36782
|
if (hasChecklist) {
|
|
37240
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
|
+
}
|
|
37241
36787
|
} else {
|
|
37242
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
|
+
}
|
|
37243
36797
|
}
|
|
37244
36798
|
log("[verification] Mission verification result", {
|
|
37245
36799
|
passed: result.passed,
|
|
37246
36800
|
hasChecklist,
|
|
37247
36801
|
checklistProgress: result.checklistProgress,
|
|
37248
36802
|
todoProgress: result.todoProgress,
|
|
36803
|
+
todoComplete: result.todoComplete,
|
|
37249
36804
|
syncIssuesEmpty: result.syncIssuesEmpty,
|
|
36805
|
+
syncIssuesCount: result.syncIssuesCount,
|
|
36806
|
+
errorCount: result.errors.length,
|
|
37250
36807
|
errors: result.errors.length > 0 ? result.errors : void 0
|
|
37251
36808
|
});
|
|
37252
36809
|
return result;
|
|
@@ -37319,8 +36876,19 @@ async function verifyMissionCompletionAsync(directory) {
|
|
|
37319
36876
|
}
|
|
37320
36877
|
if (hasChecklist) {
|
|
37321
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
|
+
}
|
|
37322
36882
|
} else {
|
|
37323
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
|
+
}
|
|
37324
36892
|
}
|
|
37325
36893
|
lastVerificationResult.set(directory, { result, timestamp: Date.now() });
|
|
37326
36894
|
return result;
|
|
@@ -37985,7 +37553,7 @@ function getNextPending(todos) {
|
|
|
37985
37553
|
pending2.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
37986
37554
|
return pending2[0];
|
|
37987
37555
|
}
|
|
37988
|
-
function
|
|
37556
|
+
function getStats2(todos) {
|
|
37989
37557
|
const stats2 = {
|
|
37990
37558
|
total: todos.length,
|
|
37991
37559
|
pending: todos.filter((t) => t.status === TODO_STATUS2.PENDING).length,
|
|
@@ -38005,7 +37573,7 @@ function getStats3(todos) {
|
|
|
38005
37573
|
// src/core/loop/formatters.ts
|
|
38006
37574
|
init_shared();
|
|
38007
37575
|
function formatProgress(todos) {
|
|
38008
|
-
const stats2 =
|
|
37576
|
+
const stats2 = getStats2(todos);
|
|
38009
37577
|
const done = stats2.completed + stats2.cancelled;
|
|
38010
37578
|
return `${done}/${stats2.total} (${stats2.percentComplete}%)`;
|
|
38011
37579
|
}
|
|
@@ -38072,6 +37640,129 @@ ${LOOP_LABELS.ACTION_DONT_STOP}
|
|
|
38072
37640
|
// src/core/recovery/session-recovery.ts
|
|
38073
37641
|
init_shared();
|
|
38074
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
|
|
38075
37766
|
var recoveryState = /* @__PURE__ */ new Map();
|
|
38076
37767
|
function getState2(sessionID) {
|
|
38077
37768
|
let state2 = recoveryState.get(sessionID);
|
|
@@ -38206,6 +37897,61 @@ function isSessionRecovering(sessionID) {
|
|
|
38206
37897
|
return recoveryState.get(sessionID)?.isRecovering ?? false;
|
|
38207
37898
|
}
|
|
38208
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
|
+
|
|
38209
37955
|
// src/core/loop/todo-continuation.ts
|
|
38210
37956
|
var sessionStates2 = /* @__PURE__ */ new Map();
|
|
38211
37957
|
var COUNTDOWN_SECONDS = 2;
|
|
@@ -38749,6 +38495,11 @@ var TodoSyncService = class {
|
|
|
38749
38495
|
taskTodos = /* @__PURE__ */ new Map();
|
|
38750
38496
|
updateTimeout = null;
|
|
38751
38497
|
watcher = null;
|
|
38498
|
+
// Batching support
|
|
38499
|
+
pendingUpdates = /* @__PURE__ */ new Set();
|
|
38500
|
+
batchTimer = null;
|
|
38501
|
+
BATCH_WINDOW_MS = 100;
|
|
38502
|
+
// 100ms batch window
|
|
38752
38503
|
activeSessions = /* @__PURE__ */ new Set();
|
|
38753
38504
|
constructor(client2, directory) {
|
|
38754
38505
|
this.client = client2;
|
|
@@ -38757,6 +38508,7 @@ var TodoSyncService = class {
|
|
|
38757
38508
|
}
|
|
38758
38509
|
async start() {
|
|
38759
38510
|
await this.reloadFileTodos();
|
|
38511
|
+
this.broadcastUpdate();
|
|
38760
38512
|
if (fs10.existsSync(this.todoPath)) {
|
|
38761
38513
|
let timer;
|
|
38762
38514
|
this.watcher = fs10.watch(this.todoPath, (eventType) => {
|
|
@@ -38815,8 +38567,29 @@ var TodoSyncService = class {
|
|
|
38815
38567
|
}
|
|
38816
38568
|
}
|
|
38817
38569
|
scheduleUpdate(sessionID) {
|
|
38818
|
-
this.
|
|
38819
|
-
|
|
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
|
+
}
|
|
38820
38593
|
}
|
|
38821
38594
|
async sendTodosToSession(sessionID) {
|
|
38822
38595
|
const taskTodosList = Array.from(this.taskTodos.values()).map((t) => {
|
|
@@ -38858,6 +38631,11 @@ var TodoSyncService = class {
|
|
|
38858
38631
|
if (this.watcher) {
|
|
38859
38632
|
this.watcher.close();
|
|
38860
38633
|
}
|
|
38634
|
+
if (this.batchTimer) {
|
|
38635
|
+
clearTimeout(this.batchTimer);
|
|
38636
|
+
this.flushBatchedUpdates().catch(() => {
|
|
38637
|
+
});
|
|
38638
|
+
}
|
|
38861
38639
|
}
|
|
38862
38640
|
};
|
|
38863
38641
|
|
|
@@ -39325,7 +39103,7 @@ function createEventHandler(ctx) {
|
|
|
39325
39103
|
const duration5 = totalTime < 6e4 ? `${Math.round(totalTime / 1e3)}s` : `${Math.round(totalTime / 6e4)}m`;
|
|
39326
39104
|
sessions.delete(sessionID);
|
|
39327
39105
|
state2.sessions.delete(sessionID);
|
|
39328
|
-
|
|
39106
|
+
clearSession(sessionID);
|
|
39329
39107
|
cleanupSessionRecovery(sessionID);
|
|
39330
39108
|
cleanupSession2(sessionID);
|
|
39331
39109
|
cleanupSession3(sessionID);
|
|
@@ -39564,6 +39342,8 @@ Wait for these tasks to complete before concluding the mission.
|
|
|
39564
39342
|
|
|
39565
39343
|
// src/plugin-handlers/system-transform-handler.ts
|
|
39566
39344
|
init_shared();
|
|
39345
|
+
import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
|
|
39346
|
+
import { join as join12 } from "node:path";
|
|
39567
39347
|
function createSystemTransformHandler(ctx) {
|
|
39568
39348
|
const { directory, sessions, state: state2 } = ctx;
|
|
39569
39349
|
return async (input, output) => {
|
|
@@ -39593,6 +39373,17 @@ function createSystemTransformHandler(ctx) {
|
|
|
39593
39373
|
}
|
|
39594
39374
|
} catch {
|
|
39595
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
|
+
}
|
|
39596
39387
|
if (systemAdditions.length > 0) {
|
|
39597
39388
|
output.system.unshift(...systemAdditions);
|
|
39598
39389
|
}
|
|
@@ -39630,6 +39421,27 @@ Use \`get_task_result\` to check completed tasks.
|
|
|
39630
39421
|
Use \`delegate_task\` with background=true for parallel work.
|
|
39631
39422
|
</orchestrator_background_tasks>`;
|
|
39632
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
|
+
}
|
|
39633
39445
|
|
|
39634
39446
|
// src/index.ts
|
|
39635
39447
|
var require2 = createRequire(import.meta.url);
|