better-opencode-async-agents 0.2.1 → 0.4.1
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/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/helpers.d.ts +17 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +676 -35
- package/dist/index.js.map +21 -17
- package/dist/manager/events.d.ts +1 -0
- package/dist/manager/events.d.ts.map +1 -1
- package/dist/manager/index.d.ts +20 -0
- package/dist/manager/index.d.ts.map +1 -1
- package/dist/manager/notifications.d.ts.map +1 -1
- package/dist/manager/polling.d.ts +1 -1
- package/dist/manager/polling.d.ts.map +1 -1
- package/dist/manager/task-lifecycle.d.ts +2 -2
- package/dist/manager/task-lifecycle.d.ts.map +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/server/cors.d.ts +22 -0
- package/dist/server/cors.d.ts.map +1 -0
- package/dist/server/index.d.ts +20 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/routes.d.ts +14 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/sse.d.ts +44 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/types.d.ts +71 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/storage.d.ts +34 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/tools/cancel.d.ts +3 -3
- package/dist/tools/cancel.d.ts.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/output.d.ts +3 -2
- package/dist/tools/output.d.ts.map +1 -1
- package/dist/tools/task.d.ts +1 -0
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -125,7 +125,7 @@ function getHomeDir() {
|
|
|
125
125
|
console.warn("[constants] Could not determine home directory, using /tmp");
|
|
126
126
|
return "/tmp";
|
|
127
127
|
}
|
|
128
|
-
var COMPLETION_DISPLAY_DURATION = 1e4, SPINNER_FRAMES, HOME_DIR, STORAGE_DIR, TASKS_FILE, FORK_MAX_TOKENS = 1e5, FORK_TOOL_RESULT_LIMIT = 1500, FORK_TOOL_PARAMS_LIMIT = 200;
|
|
128
|
+
var COMPLETION_DISPLAY_DURATION = 1e4, SPINNER_FRAMES, HOME_DIR, STORAGE_DIR, TASKS_FILE, FORK_MAX_TOKENS = 1e5, FORK_TOOL_RESULT_LIMIT = 1500, FORK_TOOL_PARAMS_LIMIT = 200, DEFAULT_API_PORT = 5165, DEFAULT_API_HOST = "127.0.0.1", SERVER_INFO_FILENAME = "server.json", MAX_PORT_RETRY = 10, HEARTBEAT_INTERVAL_MS = 30000, MAX_SSE_SUBSCRIBERS = 50, DEFAULT_TASK_LIMIT = 50, MAX_TASK_LIMIT = 200;
|
|
129
129
|
var init_constants = __esm(() => {
|
|
130
130
|
SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
131
131
|
HOME_DIR = getHomeDir();
|
|
@@ -136,11 +136,15 @@ var init_constants = __esm(() => {
|
|
|
136
136
|
// src/storage.ts
|
|
137
137
|
var exports_storage = {};
|
|
138
138
|
__export(exports_storage, {
|
|
139
|
+
writeServerInfo: () => writeServerInfo,
|
|
139
140
|
saveTasks: () => saveTasks,
|
|
140
141
|
saveTask: () => saveTask,
|
|
142
|
+
readServerInfo: () => readServerInfo,
|
|
141
143
|
loadTasks: () => loadTasks,
|
|
144
|
+
loadAllTasks: () => loadAllTasks,
|
|
142
145
|
getPersistedTask: () => getPersistedTask,
|
|
143
146
|
ensureStorageDir: () => ensureStorageDir,
|
|
147
|
+
deleteServerInfo: () => deleteServerInfo,
|
|
144
148
|
deletePersistedTask: () => deletePersistedTask
|
|
145
149
|
});
|
|
146
150
|
async function getFs() {
|
|
@@ -229,9 +233,75 @@ async function deletePersistedTask(sessionID) {
|
|
|
229
233
|
await saveTasks(tasks);
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
|
-
|
|
236
|
+
async function loadAllTasks() {
|
|
237
|
+
const tasks = await loadTasks();
|
|
238
|
+
return Object.entries(tasks).map(([sessionID, task]) => ({ sessionID, ...task }));
|
|
239
|
+
}
|
|
240
|
+
async function writeServerInfo(info) {
|
|
241
|
+
try {
|
|
242
|
+
await ensureStorageDir();
|
|
243
|
+
const content = JSON.stringify(info, null, 2);
|
|
244
|
+
if (hasBun()) {
|
|
245
|
+
await Bun.write(SERVER_INFO_FILE, content);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const fs = await getFs();
|
|
249
|
+
if (fs) {
|
|
250
|
+
await fs.writeFile(SERVER_INFO_FILE, content, "utf-8");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.warn("[storage] No file system API available for writing server info");
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.warn(`[storage] Failed to write server info: ${error}`);
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function readServerInfo() {
|
|
260
|
+
try {
|
|
261
|
+
if (hasBun()) {
|
|
262
|
+
const file = Bun.file(SERVER_INFO_FILE);
|
|
263
|
+
const exists = await file.exists();
|
|
264
|
+
if (!exists) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const content = await file.text();
|
|
268
|
+
return JSON.parse(content);
|
|
269
|
+
}
|
|
270
|
+
const fs = await getFs();
|
|
271
|
+
if (fs) {
|
|
272
|
+
const content = await fs.readFile(SERVER_INFO_FILE, "utf-8");
|
|
273
|
+
return JSON.parse(content);
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const code = error.code;
|
|
278
|
+
if (code !== "ENOENT") {
|
|
279
|
+
console.warn(`[storage] Failed to read server info: ${error}`);
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function deleteServerInfo() {
|
|
285
|
+
try {
|
|
286
|
+
if (hasBun()) {
|
|
287
|
+
const file = Bun.file(SERVER_INFO_FILE);
|
|
288
|
+
const exists = await file.exists();
|
|
289
|
+
if (exists) {
|
|
290
|
+
await file.delete();
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const fs = await getFs();
|
|
295
|
+
if (fs) {
|
|
296
|
+
await fs.unlink(SERVER_INFO_FILE).catch(() => {});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
} catch {}
|
|
300
|
+
}
|
|
301
|
+
var fsPromises = null, SERVER_INFO_FILE;
|
|
233
302
|
var init_storage = __esm(() => {
|
|
234
303
|
init_constants();
|
|
304
|
+
SERVER_INFO_FILE = `${STORAGE_DIR}/${SERVER_INFO_FILENAME}`;
|
|
235
305
|
});
|
|
236
306
|
|
|
237
307
|
// node_modules/tiktoken/lite/tiktoken_bg.wasm
|
|
@@ -801,9 +871,9 @@ Duration: ${duration}
|
|
|
801
871
|
Error fetching messages: ${errMsg}`,
|
|
802
872
|
listHeader: `# Background Tasks
|
|
803
873
|
|
|
804
|
-
| Task ID | Description | Agent | Status | Duration |
|
|
805
|
-
|
|
806
|
-
listSummary: (total, running, completed, errored, cancelled) => `**Total: ${total}** | ⏳ ${running} running | ✓ ${completed} completed | ✗ ${errored} error | ⊘ ${cancelled} cancelled`,
|
|
874
|
+
| Task ID | Description | Agent | Status | Duration | Tools |
|
|
875
|
+
|---------|-------------|-------|--------|----------|-------|`,
|
|
876
|
+
listSummary: (total, running, completed, errored, cancelled, totalToolCalls) => `**Total: ${total}** | ⏳ ${running} running | ✓ ${completed} completed | ✗ ${errored} error | ⊘ ${cancelled} cancelled | \uD83D\uDD27 ${totalToolCalls} tool calls`,
|
|
807
877
|
progressSection: (tools) => `
|
|
808
878
|
| Last tools | ${tools.join(" → ")} |`
|
|
809
879
|
};
|
|
@@ -818,6 +888,7 @@ var PLACEHOLDER_TEXT = {
|
|
|
818
888
|
|
|
819
889
|
// src/helpers.ts
|
|
820
890
|
function setTaskStatus(task, status, options) {
|
|
891
|
+
const previousStatus = task.status;
|
|
821
892
|
task.status = status;
|
|
822
893
|
if (status === "completed" || status === "error" || status === "cancelled") {
|
|
823
894
|
task.completedAt = new Date().toISOString();
|
|
@@ -826,13 +897,35 @@ function setTaskStatus(task, status, options) {
|
|
|
826
897
|
task.error = options.error;
|
|
827
898
|
}
|
|
828
899
|
options?.persistFn?.(task);
|
|
900
|
+
if (options?.emitFn && previousStatus !== status) {
|
|
901
|
+
if (status === "completed") {
|
|
902
|
+
options.emitFn("task.completed", task);
|
|
903
|
+
} else if (status === "error") {
|
|
904
|
+
options.emitFn("task.error", task);
|
|
905
|
+
} else if (status === "cancelled") {
|
|
906
|
+
options.emitFn("task.cancelled", task);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function shortId(sessionId, minLen = 8) {
|
|
911
|
+
if (!sessionId.startsWith("ses_")) {
|
|
912
|
+
return sessionId.slice(0, minLen + 4);
|
|
913
|
+
}
|
|
914
|
+
const suffix = sessionId.slice(4);
|
|
915
|
+
return `ses_${suffix.slice(0, minLen)}`;
|
|
829
916
|
}
|
|
830
|
-
function
|
|
917
|
+
function uniqueShortId(sessionId, siblingIds, minLen = 8) {
|
|
831
918
|
if (!sessionId.startsWith("ses_")) {
|
|
832
|
-
return sessionId.slice(0,
|
|
919
|
+
return sessionId.slice(0, minLen + 4);
|
|
833
920
|
}
|
|
834
921
|
const suffix = sessionId.slice(4);
|
|
835
|
-
|
|
922
|
+
for (let len = minLen;len <= suffix.length; len++) {
|
|
923
|
+
const candidate = `ses_${suffix.slice(0, len)}`;
|
|
924
|
+
const hasClash = siblingIds.some((otherId) => otherId !== sessionId && otherId.startsWith(candidate));
|
|
925
|
+
if (!hasClash)
|
|
926
|
+
return candidate;
|
|
927
|
+
}
|
|
928
|
+
return sessionId;
|
|
836
929
|
}
|
|
837
930
|
function formatDuration(startStr, endStr) {
|
|
838
931
|
const start = new Date(startStr);
|
|
@@ -926,7 +1019,7 @@ async function startEventSubscription(client, handleEvent) {
|
|
|
926
1019
|
}
|
|
927
1020
|
}
|
|
928
1021
|
function handleEvent(event, callbacks) {
|
|
929
|
-
const { clearAllTasks, getTasksArray, notifyParentSession, persistTask } = callbacks;
|
|
1022
|
+
const { clearAllTasks, getTasksArray, notifyParentSession, persistTask, emitTaskEvent } = callbacks;
|
|
930
1023
|
const props = event.properties;
|
|
931
1024
|
if (event.type === "tui.command.execute") {
|
|
932
1025
|
const command = props?.command;
|
|
@@ -954,7 +1047,8 @@ function handleEvent(event, callbacks) {
|
|
|
954
1047
|
if (task.status === "running") {
|
|
955
1048
|
setTaskStatus(task, "cancelled", {
|
|
956
1049
|
error: "Session deleted",
|
|
957
|
-
persistFn: persistTask
|
|
1050
|
+
persistFn: persistTask,
|
|
1051
|
+
emitFn: emitTaskEvent
|
|
958
1052
|
});
|
|
959
1053
|
}
|
|
960
1054
|
return;
|
|
@@ -971,7 +1065,7 @@ function handleEvent(event, callbacks) {
|
|
|
971
1065
|
}
|
|
972
1066
|
if (task.status !== "running")
|
|
973
1067
|
return;
|
|
974
|
-
setTaskStatus(task, "completed", { persistFn: persistTask });
|
|
1068
|
+
setTaskStatus(task, "completed", { persistFn: persistTask, emitFn: emitTaskEvent });
|
|
975
1069
|
notifyParentSession(task);
|
|
976
1070
|
}
|
|
977
1071
|
}
|
|
@@ -1003,6 +1097,14 @@ function showProgressToast(allTasks, animationFrame, client, getTasksArray) {
|
|
|
1003
1097
|
const nextAnimationFrame = (animationFrame + 1) % SPINNER_FRAMES.length;
|
|
1004
1098
|
const spinner = SPINNER_FRAMES[nextAnimationFrame];
|
|
1005
1099
|
const totalToolCalls = batchTasks.reduce((sum, t) => sum + (t.progress?.toolCalls ?? 0), 0);
|
|
1100
|
+
const aggregatedToolCalls = {};
|
|
1101
|
+
for (const t of batchTasks) {
|
|
1102
|
+
if (t.progress?.toolCallsByName) {
|
|
1103
|
+
for (const [toolName, count] of Object.entries(t.progress.toolCallsByName)) {
|
|
1104
|
+
aggregatedToolCalls[toolName] = (aggregatedToolCalls[toolName] ?? 0) + count;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1006
1108
|
const taskLines = [];
|
|
1007
1109
|
const batchRunning = runningTasks.filter((t) => t.batchId === activeBatchId);
|
|
1008
1110
|
for (const task of batchRunning) {
|
|
@@ -1014,7 +1116,9 @@ function showProgressToast(allTasks, animationFrame, client, getTasksArray) {
|
|
|
1014
1116
|
const prevTools = tools.slice(0, -1);
|
|
1015
1117
|
toolsStr = prevTools.length > 0 ? ` - ${prevTools.join(" > ")} > 「${lastTool}」` : ` - 「${lastTool}」`;
|
|
1016
1118
|
}
|
|
1017
|
-
|
|
1119
|
+
const callCount = task.progress?.toolCalls ?? 0;
|
|
1120
|
+
const callsStr = callCount > 0 ? ` [${callCount} calls]` : "";
|
|
1121
|
+
taskLines.push(`${spinner} [${shortId(task.sessionID)}] ${task.agent}: ${task.description} (${duration})${toolsStr}${callsStr}`);
|
|
1018
1122
|
}
|
|
1019
1123
|
const batchCompleted = batchTasks.filter((t) => t.status === "completed" || t.status === "error" || t.status === "cancelled").sort((a, b) => {
|
|
1020
1124
|
const aTime = a.completedAt ? new Date(a.completedAt).getTime() : 0;
|
|
@@ -1026,7 +1130,9 @@ function showProgressToast(allTasks, animationFrame, client, getTasksArray) {
|
|
|
1026
1130
|
for (const task of visibleCompleted) {
|
|
1027
1131
|
const duration = formatDuration2(new Date(task.startedAt), task.completedAt ? new Date(task.completedAt) : undefined);
|
|
1028
1132
|
const icon = task.status === "completed" ? "✓" : task.status === "error" ? "✗" : "⊘";
|
|
1029
|
-
|
|
1133
|
+
const callCount = task.progress?.toolCalls ?? 0;
|
|
1134
|
+
const callsStr = callCount > 0 ? ` [${callCount} calls]` : "";
|
|
1135
|
+
taskLines.push(`${icon} [${shortId(task.sessionID)}] ${task.agent}: ${task.description} (${duration})${callsStr}`);
|
|
1030
1136
|
}
|
|
1031
1137
|
const hiddenCount = batchCompleted.length - visibleCompleted.length;
|
|
1032
1138
|
if (hiddenCount > 0) {
|
|
@@ -1036,7 +1142,14 @@ function showProgressToast(allTasks, animationFrame, client, getTasksArray) {
|
|
|
1036
1142
|
const barLength = 10;
|
|
1037
1143
|
const filledLength = Math.round(finishedCount / Math.max(totalTasks, 1) * barLength);
|
|
1038
1144
|
const progressBar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
1039
|
-
const
|
|
1145
|
+
const sortedTools = Object.entries(aggregatedToolCalls).sort(([, a], [, b]) => b - a);
|
|
1146
|
+
let toolBreakdown = "";
|
|
1147
|
+
if (sortedTools.length > 0) {
|
|
1148
|
+
const top3 = sortedTools.slice(0, 3).map(([name, count]) => `${name}:${count}`).join(" ");
|
|
1149
|
+
const remaining = sortedTools.length - 3;
|
|
1150
|
+
toolBreakdown = remaining > 0 ? ` (${top3} +${remaining} more)` : ` (${top3})`;
|
|
1151
|
+
}
|
|
1152
|
+
const summary = `[${progressBar}] ${finishedCount}/${totalTasks} agents (${progressPercent}%) | ${totalToolCalls} calls${toolBreakdown}`;
|
|
1040
1153
|
const tuiClient = client;
|
|
1041
1154
|
if (!tuiClient.tui?.showToast)
|
|
1042
1155
|
return;
|
|
@@ -1201,7 +1314,7 @@ function stopPolling(pollingInterval) {
|
|
|
1201
1314
|
clearInterval(pollingInterval);
|
|
1202
1315
|
}
|
|
1203
1316
|
}
|
|
1204
|
-
async function pollRunningTasks(client, tasks, originalParentSessionID, updateTaskProgress, notifyParentSession2, clearAllTasks, stopPolling2, showProgressToast2, getTasksArray, getTaskMessages, persistTask) {
|
|
1317
|
+
async function pollRunningTasks(client, tasks, originalParentSessionID, updateTaskProgress, notifyParentSession2, clearAllTasks, stopPolling2, showProgressToast2, getTasksArray, getTaskMessages, persistTask, emitTaskEvent) {
|
|
1205
1318
|
try {
|
|
1206
1319
|
const statusResult = await client.session.status();
|
|
1207
1320
|
const allStatuses = statusResult.data ?? {};
|
|
@@ -1240,7 +1353,7 @@ async function pollRunningTasks(client, tasks, originalParentSessionID, updateTa
|
|
|
1240
1353
|
const messages = await getTaskMessages(task.sessionID);
|
|
1241
1354
|
const hasAssistantResponse = messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text && p.text.length > 0));
|
|
1242
1355
|
if (hasAssistantResponse) {
|
|
1243
|
-
setTaskStatus(task, "completed", { persistFn: persistTask });
|
|
1356
|
+
setTaskStatus(task, "completed", { persistFn: persistTask, emitFn: emitTaskEvent });
|
|
1244
1357
|
notifyParentSession2(task);
|
|
1245
1358
|
continue;
|
|
1246
1359
|
}
|
|
@@ -1250,7 +1363,7 @@ async function pollRunningTasks(client, tasks, originalParentSessionID, updateTa
|
|
|
1250
1363
|
continue;
|
|
1251
1364
|
}
|
|
1252
1365
|
if (sessionStatus.type === "idle") {
|
|
1253
|
-
setTaskStatus(task, "completed", { persistFn: persistTask });
|
|
1366
|
+
setTaskStatus(task, "completed", { persistFn: persistTask, emitFn: emitTaskEvent });
|
|
1254
1367
|
notifyParentSession2(task);
|
|
1255
1368
|
continue;
|
|
1256
1369
|
}
|
|
@@ -1291,6 +1404,7 @@ async function updateTaskProgress(task, client) {
|
|
|
1291
1404
|
return;
|
|
1292
1405
|
const messages = messagesResult.data;
|
|
1293
1406
|
let toolCalls = 0;
|
|
1407
|
+
const toolCallsByName = {};
|
|
1294
1408
|
const allTools = [];
|
|
1295
1409
|
for (const msg of messages) {
|
|
1296
1410
|
const parts = msg.parts ?? msg.content ?? [];
|
|
@@ -1300,6 +1414,7 @@ async function updateTaskProgress(task, client) {
|
|
|
1300
1414
|
for (const part of parts) {
|
|
1301
1415
|
if (part.type === "tool" && part.tool) {
|
|
1302
1416
|
toolCalls++;
|
|
1417
|
+
toolCallsByName[part.tool] = (toolCallsByName[part.tool] ?? 0) + 1;
|
|
1303
1418
|
allTools.push(part.tool);
|
|
1304
1419
|
}
|
|
1305
1420
|
}
|
|
@@ -1307,11 +1422,13 @@ async function updateTaskProgress(task, client) {
|
|
|
1307
1422
|
if (!task.progress) {
|
|
1308
1423
|
task.progress = {
|
|
1309
1424
|
toolCalls: 0,
|
|
1425
|
+
toolCallsByName: {},
|
|
1310
1426
|
lastTools: [],
|
|
1311
1427
|
lastUpdate: new Date().toISOString()
|
|
1312
1428
|
};
|
|
1313
1429
|
}
|
|
1314
1430
|
task.progress.toolCalls = toolCalls;
|
|
1431
|
+
task.progress.toolCallsByName = toolCallsByName;
|
|
1315
1432
|
task.progress.lastTools = allTools.slice(-3);
|
|
1316
1433
|
task.progress.lastUpdate = new Date().toISOString();
|
|
1317
1434
|
} catch {}
|
|
@@ -1436,7 +1553,7 @@ ${part.text}`);
|
|
|
1436
1553
|
|
|
1437
1554
|
// src/manager/task-lifecycle.ts
|
|
1438
1555
|
var FORK_METHOD = "inject";
|
|
1439
|
-
async function launchTask(input, tasks, client, getOrCreateBatchId, setOriginalParentSessionID, startPolling2, notifyParentSession2) {
|
|
1556
|
+
async function launchTask(input, tasks, client, getOrCreateBatchId, setOriginalParentSessionID, startPolling2, notifyParentSession2, emitTaskEvent, persistTask) {
|
|
1440
1557
|
if (!input.agent || input.agent.trim() === "") {
|
|
1441
1558
|
throw new Error("Agent parameter is required");
|
|
1442
1559
|
}
|
|
@@ -1511,12 +1628,16 @@ ${contextText}`;
|
|
|
1511
1628
|
isForked: input.fork ?? false,
|
|
1512
1629
|
progress: {
|
|
1513
1630
|
toolCalls: 0,
|
|
1631
|
+
toolCallsByName: {},
|
|
1514
1632
|
lastTools: [],
|
|
1515
1633
|
lastUpdate: new Date().toISOString()
|
|
1516
1634
|
}
|
|
1517
1635
|
};
|
|
1518
1636
|
setOriginalParentSessionID(input.parentSessionID);
|
|
1519
1637
|
tasks.set(task.sessionID, task);
|
|
1638
|
+
if (persistTask) {
|
|
1639
|
+
await persistTask(task);
|
|
1640
|
+
}
|
|
1520
1641
|
startPolling2();
|
|
1521
1642
|
client.session.promptAsync({
|
|
1522
1643
|
path: { id: sessionID },
|
|
@@ -1542,6 +1663,7 @@ ${contextText}`;
|
|
|
1542
1663
|
existingTask.error = errorMessage;
|
|
1543
1664
|
}
|
|
1544
1665
|
existingTask.completedAt = new Date().toISOString();
|
|
1666
|
+
emitTaskEvent?.("task.error", existingTask);
|
|
1545
1667
|
notifyParentSession2(existingTask);
|
|
1546
1668
|
}
|
|
1547
1669
|
});
|
|
@@ -1568,7 +1690,7 @@ async function getTaskMessages(sessionID, client) {
|
|
|
1568
1690
|
}
|
|
1569
1691
|
return messagesResult.data ?? messagesResult;
|
|
1570
1692
|
}
|
|
1571
|
-
async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skipNotification = false, getTaskMessages2) {
|
|
1693
|
+
async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skipNotification = false, getTaskMessages2, emitTaskEvent) {
|
|
1572
1694
|
if (task.status !== "running") {
|
|
1573
1695
|
return task;
|
|
1574
1696
|
}
|
|
@@ -1579,6 +1701,7 @@ async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skip
|
|
|
1579
1701
|
if (sessionStatus?.type === "idle") {
|
|
1580
1702
|
task.status = "completed";
|
|
1581
1703
|
task.completedAt = new Date().toISOString();
|
|
1704
|
+
emitTaskEvent?.("task.completed", task);
|
|
1582
1705
|
if (!skipNotification) {
|
|
1583
1706
|
notifyParentSession2(task);
|
|
1584
1707
|
}
|
|
@@ -1591,6 +1714,7 @@ async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skip
|
|
|
1591
1714
|
if (hasAssistantResponse) {
|
|
1592
1715
|
task.status = "completed";
|
|
1593
1716
|
task.completedAt = new Date().toISOString();
|
|
1717
|
+
emitTaskEvent?.("task.completed", task);
|
|
1594
1718
|
if (!skipNotification) {
|
|
1595
1719
|
notifyParentSession2(task);
|
|
1596
1720
|
}
|
|
@@ -1627,12 +1751,29 @@ class BackgroundManager {
|
|
|
1627
1751
|
currentBatchId = null;
|
|
1628
1752
|
tasks = new Map;
|
|
1629
1753
|
originalParentSessionID = null;
|
|
1754
|
+
taskEventListeners = [];
|
|
1630
1755
|
constructor(ctx) {
|
|
1631
1756
|
this.client = ctx.client;
|
|
1632
1757
|
this.directory = ctx.directory;
|
|
1633
1758
|
this.startEventSubscription();
|
|
1634
1759
|
this.loadPersistedTasks();
|
|
1635
1760
|
}
|
|
1761
|
+
onTaskEvent(callback) {
|
|
1762
|
+
this.taskEventListeners.push(callback);
|
|
1763
|
+
return () => {
|
|
1764
|
+
const index = this.taskEventListeners.indexOf(callback);
|
|
1765
|
+
if (index > -1) {
|
|
1766
|
+
this.taskEventListeners.splice(index, 1);
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
emitTaskEvent(eventType, task) {
|
|
1771
|
+
for (const listener of this.taskEventListeners) {
|
|
1772
|
+
try {
|
|
1773
|
+
listener(eventType, task);
|
|
1774
|
+
} catch {}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1636
1777
|
async loadPersistedTasks() {
|
|
1637
1778
|
try {
|
|
1638
1779
|
const persisted = await loadTasks();
|
|
@@ -1642,8 +1783,8 @@ class BackgroundManager {
|
|
|
1642
1783
|
async launch(input) {
|
|
1643
1784
|
const task = await launchTask(input, this.tasks, this.client, () => this.getOrCreateBatchId(), (sessionID) => {
|
|
1644
1785
|
this.originalParentSessionID = sessionID;
|
|
1645
|
-
}, () => this.startPolling(), (t) => this.notifyParentSession(t));
|
|
1646
|
-
|
|
1786
|
+
}, () => this.startPolling(), (t) => this.notifyParentSession(t), (eventType, t) => this.emitTaskEvent(eventType, t), (t) => this.persistTask(t));
|
|
1787
|
+
this.emitTaskEvent("task.created", task);
|
|
1647
1788
|
return task;
|
|
1648
1789
|
}
|
|
1649
1790
|
async persistTask(task) {
|
|
@@ -1654,7 +1795,13 @@ class BackgroundManager {
|
|
|
1654
1795
|
createdAt: task.startedAt,
|
|
1655
1796
|
status: task.status,
|
|
1656
1797
|
resumeCount: task.resumeCount,
|
|
1657
|
-
isForked: task.isForked
|
|
1798
|
+
isForked: task.isForked,
|
|
1799
|
+
completedAt: task.completedAt,
|
|
1800
|
+
error: task.error,
|
|
1801
|
+
result: task.result,
|
|
1802
|
+
progress: task.progress,
|
|
1803
|
+
startedAt: task.startedAt,
|
|
1804
|
+
batchId: task.batchId
|
|
1658
1805
|
};
|
|
1659
1806
|
try {
|
|
1660
1807
|
await saveTask(task.sessionID, persisted);
|
|
@@ -1663,7 +1810,11 @@ class BackgroundManager {
|
|
|
1663
1810
|
}
|
|
1664
1811
|
}
|
|
1665
1812
|
async cancelTask(taskId) {
|
|
1666
|
-
|
|
1813
|
+
const task = this.tasks.get(taskId);
|
|
1814
|
+
await cancelTask(taskId, this.tasks, this.client);
|
|
1815
|
+
if (task) {
|
|
1816
|
+
this.emitTaskEvent("task.cancelled", task);
|
|
1817
|
+
}
|
|
1667
1818
|
}
|
|
1668
1819
|
clearAllTasks() {
|
|
1669
1820
|
clearAllTasks(this.tasks, this.client, () => this.stopPolling());
|
|
@@ -1751,12 +1902,15 @@ class BackgroundManager {
|
|
|
1751
1902
|
getAllTasks() {
|
|
1752
1903
|
return Array.from(this.tasks.values());
|
|
1753
1904
|
}
|
|
1905
|
+
getTaskSessionIds() {
|
|
1906
|
+
return Array.from(this.tasks.keys());
|
|
1907
|
+
}
|
|
1754
1908
|
async getTaskMessages(sessionID) {
|
|
1755
1909
|
return getTaskMessages(sessionID, this.client);
|
|
1756
1910
|
}
|
|
1757
1911
|
async checkAndUpdateTaskStatus(task, skipNotification = false) {
|
|
1758
1912
|
const previousStatus = task.status;
|
|
1759
|
-
const updatedTask = await checkAndUpdateTaskStatus(task, this.client, (t) => this.notifyParentSession(t), skipNotification, (sessionID) => this.getTaskMessages(sessionID));
|
|
1913
|
+
const updatedTask = await checkAndUpdateTaskStatus(task, this.client, (t) => this.notifyParentSession(t), skipNotification, (sessionID) => this.getTaskMessages(sessionID), (eventType, t) => this.emitTaskEvent(eventType, t));
|
|
1760
1914
|
if (updatedTask.status !== previousStatus) {
|
|
1761
1915
|
await this.persistTask(updatedTask);
|
|
1762
1916
|
}
|
|
@@ -1903,12 +2057,20 @@ class BackgroundManager {
|
|
|
1903
2057
|
this.pollingInterval = undefined;
|
|
1904
2058
|
}
|
|
1905
2059
|
async pollRunningTasks() {
|
|
1906
|
-
await pollRunningTasks(this.client, this.tasks, this.originalParentSessionID, (task) =>
|
|
2060
|
+
await pollRunningTasks(this.client, this.tasks, this.originalParentSessionID, (task) => this.updateTaskProgressWithEvent(task), (task) => this.notifyParentSession(task), () => this.clearAllTasks(), () => this.stopPolling(), (tasks) => this.showProgressToast(tasks), () => this.getAllTasks(), (sessionID) => this.getTaskMessages(sessionID), (task) => void this.persistTask(task), (eventType, task) => this.emitTaskEvent(eventType, task));
|
|
1907
2061
|
}
|
|
1908
2062
|
showProgressToast(tasks) {
|
|
1909
2063
|
showProgressToast(tasks, this.animationFrame, this.client, () => this.getAllTasks());
|
|
1910
2064
|
this.animationFrame = (this.animationFrame + 1) % 10;
|
|
1911
2065
|
}
|
|
2066
|
+
async updateTaskProgressWithEvent(task) {
|
|
2067
|
+
const previousToolCalls = task.progress?.toolCalls ?? 0;
|
|
2068
|
+
await updateTaskProgress(task, this.client);
|
|
2069
|
+
const currentToolCalls = task.progress?.toolCalls ?? 0;
|
|
2070
|
+
if (currentToolCalls > previousToolCalls) {
|
|
2071
|
+
this.emitTaskEvent("task.updated", task);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
1912
2074
|
notifyParentSession(task) {
|
|
1913
2075
|
notifyParentSession(task, this.client, this.directory, () => this.getAllTasks());
|
|
1914
2076
|
}
|
|
@@ -1917,11 +2079,475 @@ class BackgroundManager {
|
|
|
1917
2079
|
clearAllTasks: () => this.clearAllTasks(),
|
|
1918
2080
|
getTasksArray: () => this.getAllTasks(),
|
|
1919
2081
|
notifyParentSession: (task) => this.notifyParentSession(task),
|
|
1920
|
-
persistTask: (task) => void this.persistTask(task)
|
|
2082
|
+
persistTask: (task) => void this.persistTask(task),
|
|
2083
|
+
emitTaskEvent: (eventType, task) => this.emitTaskEvent(eventType, task)
|
|
1921
2084
|
}));
|
|
1922
2085
|
}
|
|
1923
2086
|
}
|
|
1924
2087
|
|
|
2088
|
+
// src/server/index.ts
|
|
2089
|
+
init_constants();
|
|
2090
|
+
init_storage();
|
|
2091
|
+
|
|
2092
|
+
// src/server/cors.ts
|
|
2093
|
+
var CORS_HEADERS = {
|
|
2094
|
+
"Access-Control-Allow-Origin": "*",
|
|
2095
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
2096
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
2097
|
+
};
|
|
2098
|
+
function withCors(response) {
|
|
2099
|
+
for (const [key, value] of Object.entries(CORS_HEADERS)) {
|
|
2100
|
+
response.headers.set(key, value);
|
|
2101
|
+
}
|
|
2102
|
+
return response;
|
|
2103
|
+
}
|
|
2104
|
+
function jsonResponse(data, status = 200) {
|
|
2105
|
+
return withCors(new Response(JSON.stringify(data), {
|
|
2106
|
+
status,
|
|
2107
|
+
headers: { "Content-Type": "application/json" }
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2110
|
+
function errorResponse(error, status) {
|
|
2111
|
+
return jsonResponse({ error, status }, status);
|
|
2112
|
+
}
|
|
2113
|
+
function preflightResponse() {
|
|
2114
|
+
return withCors(new Response(null, { status: 204 }));
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// src/server/routes.ts
|
|
2118
|
+
init_constants();
|
|
2119
|
+
var startTime = Date.now();
|
|
2120
|
+
function handleHealth(_req, manager) {
|
|
2121
|
+
const tasks = manager.getAllTasks();
|
|
2122
|
+
const body = {
|
|
2123
|
+
status: "ok",
|
|
2124
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
2125
|
+
version: "1.0.0",
|
|
2126
|
+
taskCount: tasks.length
|
|
2127
|
+
};
|
|
2128
|
+
return jsonResponse(body);
|
|
2129
|
+
}
|
|
2130
|
+
function handleStats(_req, manager) {
|
|
2131
|
+
const tasks = manager.getAllTasks();
|
|
2132
|
+
const byStatus = {};
|
|
2133
|
+
const byAgent = {};
|
|
2134
|
+
const toolCallsByName = {};
|
|
2135
|
+
const toolCallsByAgent = {};
|
|
2136
|
+
const durations = [];
|
|
2137
|
+
let activeTasks = 0;
|
|
2138
|
+
for (const task of tasks) {
|
|
2139
|
+
byStatus[task.status] = (byStatus[task.status] ?? 0) + 1;
|
|
2140
|
+
byAgent[task.agent] = (byAgent[task.agent] ?? 0) + 1;
|
|
2141
|
+
if (task.progress?.toolCallsByName) {
|
|
2142
|
+
for (const [toolName, count] of Object.entries(task.progress.toolCallsByName)) {
|
|
2143
|
+
toolCallsByName[toolName] = (toolCallsByName[toolName] ?? 0) + count;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
const agentToolCalls = task.progress?.toolCalls ?? 0;
|
|
2147
|
+
if (agentToolCalls > 0) {
|
|
2148
|
+
toolCallsByAgent[task.agent] = (toolCallsByAgent[task.agent] ?? 0) + agentToolCalls;
|
|
2149
|
+
}
|
|
2150
|
+
if (task.status === "running" || task.status === "resumed") {
|
|
2151
|
+
activeTasks++;
|
|
2152
|
+
}
|
|
2153
|
+
if (task.startedAt && task.completedAt) {
|
|
2154
|
+
const duration = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
|
|
2155
|
+
if (duration > 0)
|
|
2156
|
+
durations.push(duration);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
const body = {
|
|
2160
|
+
byStatus,
|
|
2161
|
+
byAgent,
|
|
2162
|
+
toolCallsByName,
|
|
2163
|
+
toolCallsByAgent,
|
|
2164
|
+
duration: {
|
|
2165
|
+
avg: durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0,
|
|
2166
|
+
max: durations.length > 0 ? Math.max(...durations) : 0,
|
|
2167
|
+
min: durations.length > 0 ? Math.min(...durations) : 0
|
|
2168
|
+
},
|
|
2169
|
+
totalTasks: tasks.length,
|
|
2170
|
+
activeTasks
|
|
2171
|
+
};
|
|
2172
|
+
return jsonResponse(body);
|
|
2173
|
+
}
|
|
2174
|
+
function handleTaskList(req, manager) {
|
|
2175
|
+
const url = new URL(req.url);
|
|
2176
|
+
const status = url.searchParams.get("status");
|
|
2177
|
+
const agent = url.searchParams.get("agent");
|
|
2178
|
+
const search = url.searchParams.get("search");
|
|
2179
|
+
const sortParam = url.searchParams.get("sort") ?? "startedAt:desc";
|
|
2180
|
+
let limit = Number.parseInt(url.searchParams.get("limit") ?? String(DEFAULT_TASK_LIMIT), 10);
|
|
2181
|
+
let offset = Number.parseInt(url.searchParams.get("offset") ?? "0", 10);
|
|
2182
|
+
if (Number.isNaN(limit) || limit < 1)
|
|
2183
|
+
limit = DEFAULT_TASK_LIMIT;
|
|
2184
|
+
if (limit > MAX_TASK_LIMIT)
|
|
2185
|
+
limit = MAX_TASK_LIMIT;
|
|
2186
|
+
if (Number.isNaN(offset) || offset < 0)
|
|
2187
|
+
offset = 0;
|
|
2188
|
+
let tasks = manager.getAllTasks();
|
|
2189
|
+
if (status) {
|
|
2190
|
+
tasks = tasks.filter((t) => t.status === status);
|
|
2191
|
+
}
|
|
2192
|
+
if (agent) {
|
|
2193
|
+
tasks = tasks.filter((t) => t.agent === agent);
|
|
2194
|
+
}
|
|
2195
|
+
if (search) {
|
|
2196
|
+
const searchLower = search.toLowerCase();
|
|
2197
|
+
tasks = tasks.filter((t) => t.description.toLowerCase().includes(searchLower) || t.prompt?.toLowerCase().includes(searchLower));
|
|
2198
|
+
}
|
|
2199
|
+
const [sortField, sortDir] = sortParam.split(":");
|
|
2200
|
+
const direction = sortDir === "asc" ? 1 : -1;
|
|
2201
|
+
const safeSortField = sortField ?? "startedAt";
|
|
2202
|
+
tasks.sort((a, b) => {
|
|
2203
|
+
const aVal = a[safeSortField] ?? "";
|
|
2204
|
+
const bVal = b[safeSortField] ?? "";
|
|
2205
|
+
if (aVal < bVal)
|
|
2206
|
+
return -1 * direction;
|
|
2207
|
+
if (aVal > bVal)
|
|
2208
|
+
return 1 * direction;
|
|
2209
|
+
return 0;
|
|
2210
|
+
});
|
|
2211
|
+
const total = tasks.length;
|
|
2212
|
+
tasks = tasks.slice(offset, offset + limit);
|
|
2213
|
+
const body = { tasks, total, limit, offset };
|
|
2214
|
+
return jsonResponse(body);
|
|
2215
|
+
}
|
|
2216
|
+
function handleTaskDetail(req, manager, id) {
|
|
2217
|
+
const task = manager.getTask(id);
|
|
2218
|
+
if (!task) {
|
|
2219
|
+
return errorResponse("Task not found", 404);
|
|
2220
|
+
}
|
|
2221
|
+
return jsonResponse(task);
|
|
2222
|
+
}
|
|
2223
|
+
async function handleTaskLogs(req, manager, id) {
|
|
2224
|
+
const task = manager.getTask(id);
|
|
2225
|
+
if (!task) {
|
|
2226
|
+
return errorResponse("Task not found", 404);
|
|
2227
|
+
}
|
|
2228
|
+
const messages = await manager.getTaskMessages(id);
|
|
2229
|
+
const body = {
|
|
2230
|
+
messages,
|
|
2231
|
+
taskId: id,
|
|
2232
|
+
retrievedAt: new Date().toISOString()
|
|
2233
|
+
};
|
|
2234
|
+
return jsonResponse(body);
|
|
2235
|
+
}
|
|
2236
|
+
function handleTaskGroup(req, manager, groupId) {
|
|
2237
|
+
const allTasks = manager.getAllTasks();
|
|
2238
|
+
const groupTasks = allTasks.filter((t) => t.batchId === groupId);
|
|
2239
|
+
if (groupTasks.length === 0) {
|
|
2240
|
+
return errorResponse("Task group not found", 404);
|
|
2241
|
+
}
|
|
2242
|
+
let completed = 0;
|
|
2243
|
+
let running = 0;
|
|
2244
|
+
let error = 0;
|
|
2245
|
+
let cancelled = 0;
|
|
2246
|
+
let totalToolCalls = 0;
|
|
2247
|
+
const toolCallsByName = {};
|
|
2248
|
+
const toolCallsByAgent = {};
|
|
2249
|
+
let minStart = Number.POSITIVE_INFINITY;
|
|
2250
|
+
let maxEnd = 0;
|
|
2251
|
+
for (const t of groupTasks) {
|
|
2252
|
+
if (t.status === "completed")
|
|
2253
|
+
completed++;
|
|
2254
|
+
else if (t.status === "running" || t.status === "resumed")
|
|
2255
|
+
running++;
|
|
2256
|
+
else if (t.status === "error")
|
|
2257
|
+
error++;
|
|
2258
|
+
else if (t.status === "cancelled")
|
|
2259
|
+
cancelled++;
|
|
2260
|
+
totalToolCalls += t.progress?.toolCalls || 0;
|
|
2261
|
+
if (t.progress?.toolCallsByName) {
|
|
2262
|
+
for (const [toolName, count] of Object.entries(t.progress.toolCallsByName)) {
|
|
2263
|
+
toolCallsByName[toolName] = (toolCallsByName[toolName] ?? 0) + count;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
const agentToolCalls = t.progress?.toolCalls ?? 0;
|
|
2267
|
+
if (agentToolCalls > 0) {
|
|
2268
|
+
toolCallsByAgent[t.agent] = (toolCallsByAgent[t.agent] ?? 0) + agentToolCalls;
|
|
2269
|
+
}
|
|
2270
|
+
if (t.startedAt) {
|
|
2271
|
+
const s = new Date(t.startedAt).getTime();
|
|
2272
|
+
if (s < minStart)
|
|
2273
|
+
minStart = s;
|
|
2274
|
+
}
|
|
2275
|
+
if (t.completedAt) {
|
|
2276
|
+
const e = new Date(t.completedAt).getTime();
|
|
2277
|
+
if (e > maxEnd)
|
|
2278
|
+
maxEnd = e;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
const total = groupTasks.length;
|
|
2282
|
+
const duration = minStart < Number.POSITIVE_INFINITY && maxEnd > 0 ? maxEnd - minStart : 0;
|
|
2283
|
+
const body = {
|
|
2284
|
+
groupId,
|
|
2285
|
+
tasks: groupTasks,
|
|
2286
|
+
stats: {
|
|
2287
|
+
completed,
|
|
2288
|
+
running,
|
|
2289
|
+
error,
|
|
2290
|
+
cancelled,
|
|
2291
|
+
total,
|
|
2292
|
+
completionRate: total > 0 ? completed / total : 0,
|
|
2293
|
+
totalToolCalls,
|
|
2294
|
+
toolCallsByName,
|
|
2295
|
+
toolCallsByAgent,
|
|
2296
|
+
duration
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
return jsonResponse(body);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// src/server/sse.ts
|
|
2303
|
+
init_constants();
|
|
2304
|
+
class SSEBroadcaster {
|
|
2305
|
+
subscribers = new Set;
|
|
2306
|
+
encoder = new TextEncoder;
|
|
2307
|
+
addSubscriber(controller) {
|
|
2308
|
+
if (this.subscribers.size >= MAX_SSE_SUBSCRIBERS) {
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
this.subscribers.add(controller);
|
|
2312
|
+
return true;
|
|
2313
|
+
}
|
|
2314
|
+
removeSubscriber(controller) {
|
|
2315
|
+
this.subscribers.delete(controller);
|
|
2316
|
+
}
|
|
2317
|
+
broadcast(eventType, data) {
|
|
2318
|
+
const message = this.formatSSE(eventType, data);
|
|
2319
|
+
const encoded = this.encoder.encode(message);
|
|
2320
|
+
for (const controller of this.subscribers) {
|
|
2321
|
+
try {
|
|
2322
|
+
controller.enqueue(encoded);
|
|
2323
|
+
} catch {
|
|
2324
|
+
this.subscribers.delete(controller);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
sendTo(controller, eventType, data) {
|
|
2329
|
+
const message = this.formatSSE(eventType, data);
|
|
2330
|
+
try {
|
|
2331
|
+
controller.enqueue(this.encoder.encode(message));
|
|
2332
|
+
} catch {
|
|
2333
|
+
this.subscribers.delete(controller);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
getSubscriberCount() {
|
|
2337
|
+
return this.subscribers.size;
|
|
2338
|
+
}
|
|
2339
|
+
formatSSE(eventType, data) {
|
|
2340
|
+
return `event: ${eventType}
|
|
2341
|
+
data: ${JSON.stringify(data)}
|
|
2342
|
+
|
|
2343
|
+
`;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
function handleSSERequest(req, broadcaster, dataProvider) {
|
|
2347
|
+
let controller;
|
|
2348
|
+
let heartbeatTimer;
|
|
2349
|
+
const stream = new ReadableStream({
|
|
2350
|
+
start(ctrl) {
|
|
2351
|
+
controller = ctrl;
|
|
2352
|
+
if (!broadcaster.addSubscriber(controller)) {
|
|
2353
|
+
controller.enqueue(new TextEncoder().encode(`event: error
|
|
2354
|
+
data: ${JSON.stringify({ error: "Too many connections" })}
|
|
2355
|
+
|
|
2356
|
+
`));
|
|
2357
|
+
controller.close();
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
const snapshot = {
|
|
2361
|
+
tasks: dataProvider.getAllTasks(),
|
|
2362
|
+
stats: dataProvider.buildStats()
|
|
2363
|
+
};
|
|
2364
|
+
broadcaster.sendTo(controller, "snapshot", snapshot);
|
|
2365
|
+
heartbeatTimer = setInterval(() => {
|
|
2366
|
+
try {
|
|
2367
|
+
broadcaster.sendTo(controller, "heartbeat", { ts: new Date().toISOString() });
|
|
2368
|
+
} catch {
|
|
2369
|
+
clearInterval(heartbeatTimer);
|
|
2370
|
+
broadcaster.removeSubscriber(controller);
|
|
2371
|
+
}
|
|
2372
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
2373
|
+
},
|
|
2374
|
+
cancel() {
|
|
2375
|
+
clearInterval(heartbeatTimer);
|
|
2376
|
+
broadcaster.removeSubscriber(controller);
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
return new Response(stream, {
|
|
2380
|
+
status: 200,
|
|
2381
|
+
headers: {
|
|
2382
|
+
"Content-Type": "text/event-stream",
|
|
2383
|
+
"Cache-Control": "no-cache",
|
|
2384
|
+
Connection: "keep-alive",
|
|
2385
|
+
...CORS_HEADERS
|
|
2386
|
+
}
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
// src/server/index.ts
|
|
2391
|
+
class StatusApiServer {
|
|
2392
|
+
manager;
|
|
2393
|
+
server = null;
|
|
2394
|
+
broadcaster;
|
|
2395
|
+
unsubscribe = null;
|
|
2396
|
+
startedAt;
|
|
2397
|
+
constructor(manager) {
|
|
2398
|
+
this.manager = manager;
|
|
2399
|
+
this.broadcaster = new SSEBroadcaster;
|
|
2400
|
+
this.startedAt = new Date().toISOString();
|
|
2401
|
+
}
|
|
2402
|
+
static async start(manager) {
|
|
2403
|
+
if (process.env.ASYNCAGENTS_API_ENABLED === "false") {
|
|
2404
|
+
return null;
|
|
2405
|
+
}
|
|
2406
|
+
const instance = new StatusApiServer(manager);
|
|
2407
|
+
await instance.bind();
|
|
2408
|
+
return instance;
|
|
2409
|
+
}
|
|
2410
|
+
async bind() {
|
|
2411
|
+
const desiredPort = Number.parseInt(process.env.ASYNCAGENTS_API_PORT ?? String(DEFAULT_API_PORT), 10);
|
|
2412
|
+
const host = process.env.ASYNCAGENTS_API_HOST ?? DEFAULT_API_HOST;
|
|
2413
|
+
const dataProvider = {
|
|
2414
|
+
getAllTasks: () => this.manager.getAllTasks(),
|
|
2415
|
+
buildStats: () => this.buildStatsData()
|
|
2416
|
+
};
|
|
2417
|
+
const broadcaster = this.broadcaster;
|
|
2418
|
+
const manager = this.manager;
|
|
2419
|
+
let lastError = null;
|
|
2420
|
+
for (let attempt = 0;attempt <= MAX_PORT_RETRY; attempt++) {
|
|
2421
|
+
const port2 = attempt < MAX_PORT_RETRY ? desiredPort + attempt : 0;
|
|
2422
|
+
try {
|
|
2423
|
+
this.server = Bun.serve({
|
|
2424
|
+
port: port2,
|
|
2425
|
+
hostname: host,
|
|
2426
|
+
fetch(req) {
|
|
2427
|
+
if (req.method === "OPTIONS") {
|
|
2428
|
+
return preflightResponse();
|
|
2429
|
+
}
|
|
2430
|
+
const url2 = new URL(req.url);
|
|
2431
|
+
const path = url2.pathname;
|
|
2432
|
+
if (path === "/v1/health")
|
|
2433
|
+
return handleHealth(req, manager);
|
|
2434
|
+
if (path === "/v1/stats")
|
|
2435
|
+
return handleStats(req, manager);
|
|
2436
|
+
if (path === "/v1/tasks")
|
|
2437
|
+
return handleTaskList(req, manager);
|
|
2438
|
+
if (path === "/v1/events")
|
|
2439
|
+
return handleSSERequest(req, broadcaster, dataProvider);
|
|
2440
|
+
const logsMatch = path.match(/^\/v1\/tasks\/([^/]+)\/logs$/);
|
|
2441
|
+
const logsId = logsMatch?.[1];
|
|
2442
|
+
if (logsId) {
|
|
2443
|
+
return handleTaskLogs(req, manager, logsId);
|
|
2444
|
+
}
|
|
2445
|
+
const taskMatch = path.match(/^\/v1\/tasks\/([^/]+)$/);
|
|
2446
|
+
const taskId = taskMatch?.[1];
|
|
2447
|
+
if (taskId) {
|
|
2448
|
+
return handleTaskDetail(req, manager, taskId);
|
|
2449
|
+
}
|
|
2450
|
+
const groupMatch = path.match(/^\/v1\/task-groups\/([^/]+)$/);
|
|
2451
|
+
const groupId = groupMatch?.[1];
|
|
2452
|
+
if (groupId) {
|
|
2453
|
+
return handleTaskGroup(req, manager, groupId);
|
|
2454
|
+
}
|
|
2455
|
+
return errorResponse("Not Found", 404);
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
lastError = null;
|
|
2459
|
+
break;
|
|
2460
|
+
} catch (err) {
|
|
2461
|
+
lastError = err;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
if (lastError || !this.server) {
|
|
2465
|
+
throw new Error(`Failed to bind API server after ${MAX_PORT_RETRY + 1} attempts: ${lastError?.message}`);
|
|
2466
|
+
}
|
|
2467
|
+
if (this.manager.onTaskEvent) {
|
|
2468
|
+
this.unsubscribe = this.manager.onTaskEvent((eventType, task) => {
|
|
2469
|
+
this.broadcaster.broadcast(eventType, { task });
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
const port = this.server.port;
|
|
2473
|
+
const url = `http://${host}:${port}`;
|
|
2474
|
+
try {
|
|
2475
|
+
await writeServerInfo({
|
|
2476
|
+
port,
|
|
2477
|
+
pid: process.pid,
|
|
2478
|
+
startedAt: this.startedAt,
|
|
2479
|
+
url,
|
|
2480
|
+
version: "1.0.0"
|
|
2481
|
+
});
|
|
2482
|
+
} catch {}
|
|
2483
|
+
}
|
|
2484
|
+
buildStatsData() {
|
|
2485
|
+
const tasks = this.manager.getAllTasks();
|
|
2486
|
+
const byStatus = {};
|
|
2487
|
+
const byAgent = {};
|
|
2488
|
+
const toolCallsByName = {};
|
|
2489
|
+
const toolCallsByAgent = {};
|
|
2490
|
+
const durations = [];
|
|
2491
|
+
let activeTasks = 0;
|
|
2492
|
+
for (const task of tasks) {
|
|
2493
|
+
byStatus[task.status] = (byStatus[task.status] ?? 0) + 1;
|
|
2494
|
+
byAgent[task.agent] = (byAgent[task.agent] ?? 0) + 1;
|
|
2495
|
+
if (task.progress?.toolCallsByName) {
|
|
2496
|
+
for (const [toolName, count] of Object.entries(task.progress.toolCallsByName)) {
|
|
2497
|
+
toolCallsByName[toolName] = (toolCallsByName[toolName] ?? 0) + count;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
const agentToolCalls = task.progress?.toolCalls ?? 0;
|
|
2501
|
+
if (agentToolCalls > 0) {
|
|
2502
|
+
toolCallsByAgent[task.agent] = (toolCallsByAgent[task.agent] ?? 0) + agentToolCalls;
|
|
2503
|
+
}
|
|
2504
|
+
if (task.status === "running" || task.status === "resumed")
|
|
2505
|
+
activeTasks++;
|
|
2506
|
+
if (task.startedAt && task.completedAt) {
|
|
2507
|
+
const d = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
|
|
2508
|
+
if (d > 0)
|
|
2509
|
+
durations.push(d);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return {
|
|
2513
|
+
byStatus,
|
|
2514
|
+
byAgent,
|
|
2515
|
+
toolCallsByName,
|
|
2516
|
+
toolCallsByAgent,
|
|
2517
|
+
duration: {
|
|
2518
|
+
avg: durations.length ? durations.reduce((a, b) => a + b, 0) / durations.length : 0,
|
|
2519
|
+
max: durations.length ? Math.max(...durations) : 0,
|
|
2520
|
+
min: durations.length ? Math.min(...durations) : 0
|
|
2521
|
+
},
|
|
2522
|
+
totalTasks: tasks.length,
|
|
2523
|
+
activeTasks
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
getPort() {
|
|
2527
|
+
return this.server?.port ?? 0;
|
|
2528
|
+
}
|
|
2529
|
+
getUrl() {
|
|
2530
|
+
const host = process.env.ASYNCAGENTS_API_HOST ?? DEFAULT_API_HOST;
|
|
2531
|
+
return `http://${host}:${this.getPort()}`;
|
|
2532
|
+
}
|
|
2533
|
+
getBroadcaster() {
|
|
2534
|
+
return this.broadcaster;
|
|
2535
|
+
}
|
|
2536
|
+
async stop() {
|
|
2537
|
+
if (this.unsubscribe) {
|
|
2538
|
+
this.unsubscribe();
|
|
2539
|
+
this.unsubscribe = null;
|
|
2540
|
+
}
|
|
2541
|
+
if (this.server) {
|
|
2542
|
+
this.server.stop(true);
|
|
2543
|
+
this.server = null;
|
|
2544
|
+
}
|
|
2545
|
+
try {
|
|
2546
|
+
await deleteServerInfo();
|
|
2547
|
+
} catch {}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
|
|
1925
2551
|
// node_modules/.pnpm/@opencode-ai+plugin@1.1.25/node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
1926
2552
|
var exports_external = {};
|
|
1927
2553
|
__export(exports_external, {
|
|
@@ -14364,7 +14990,9 @@ async function handleLaunchMode(manager, args, toolContext) {
|
|
|
14364
14990
|
parentMessageID: toolContext.messageID,
|
|
14365
14991
|
parentAgent: toolContext.agent
|
|
14366
14992
|
});
|
|
14367
|
-
|
|
14993
|
+
const siblingIds = manager.getTaskSessionIds?.() ?? [];
|
|
14994
|
+
const displayId = uniqueShortId(task.sessionID, siblingIds);
|
|
14995
|
+
return SUCCESS_MESSAGES.taskLaunched(displayId);
|
|
14368
14996
|
} catch (error45) {
|
|
14369
14997
|
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
14370
14998
|
return ERROR_MESSAGES.launchFailed(message);
|
|
@@ -14383,11 +15011,11 @@ function createBackgroundOutput(manager) {
|
|
|
14383
15011
|
},
|
|
14384
15012
|
async execute(args) {
|
|
14385
15013
|
try {
|
|
14386
|
-
const resolvedId = manager.
|
|
15014
|
+
const resolvedId = await manager.resolveTaskIdWithFallback(args.task_id);
|
|
14387
15015
|
if (!resolvedId) {
|
|
14388
15016
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14389
15017
|
}
|
|
14390
|
-
let task = manager.getTask(resolvedId);
|
|
15018
|
+
let task = manager.getTask(resolvedId) ?? await manager.getTaskWithFallback(resolvedId);
|
|
14391
15019
|
if (!task) {
|
|
14392
15020
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14393
15021
|
}
|
|
@@ -14431,11 +15059,11 @@ function createBackgroundCancel(manager) {
|
|
|
14431
15059
|
},
|
|
14432
15060
|
async execute(args) {
|
|
14433
15061
|
try {
|
|
14434
|
-
const resolvedId = manager.
|
|
15062
|
+
const resolvedId = await manager.resolveTaskIdWithFallback(args.task_id);
|
|
14435
15063
|
if (!resolvedId) {
|
|
14436
15064
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14437
15065
|
}
|
|
14438
|
-
const task = manager.
|
|
15066
|
+
const task = await manager.getTaskWithFallback(resolvedId);
|
|
14439
15067
|
if (!task) {
|
|
14440
15068
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14441
15069
|
}
|
|
@@ -14469,6 +15097,7 @@ function createBackgroundList(manager) {
|
|
|
14469
15097
|
}
|
|
14470
15098
|
tasks.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
14471
15099
|
const header = FORMAT_TEMPLATES.listHeader;
|
|
15100
|
+
const allSessionIds = tasks.map((t) => t.sessionID);
|
|
14472
15101
|
const rows = tasks.map((task) => {
|
|
14473
15102
|
const duration3 = formatDuration(task.startedAt, task.completedAt);
|
|
14474
15103
|
const desc = task.description.length > 30 ? `${task.description.slice(0, 27)}...` : task.description;
|
|
@@ -14477,19 +15106,22 @@ function createBackgroundList(manager) {
|
|
|
14477
15106
|
task.isForked ? "(forked)" : "",
|
|
14478
15107
|
task.resumeCount > 0 ? "(resumed)" : ""
|
|
14479
15108
|
].filter(Boolean).join(" ");
|
|
14480
|
-
const
|
|
14481
|
-
|
|
15109
|
+
const shortId2 = uniqueShortId(task.sessionID, allSessionIds);
|
|
15110
|
+
const idWithIndicators = indicators ? `${shortId2} ${indicators}` : shortId2;
|
|
15111
|
+
const toolsInfo = task.progress?.toolCallsByName && Object.keys(task.progress.toolCallsByName).length > 0 ? Object.entries(task.progress.toolCallsByName).sort(([, a], [, b]) => b - a).map(([name, count]) => `${name}:${count}`).join(" ") : task.progress?.toolCalls ? `${task.progress.toolCalls} calls` : "-";
|
|
15112
|
+
return `| \`${idWithIndicators}\` | ${desc} | ${task.agent} | ${icon} ${task.status} | ${duration3} | ${toolsInfo} |`;
|
|
14482
15113
|
}).join(`
|
|
14483
15114
|
`);
|
|
14484
15115
|
const running = tasks.filter((t) => t.status === "running").length;
|
|
14485
15116
|
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
14486
15117
|
const errored = tasks.filter((t) => t.status === "error").length;
|
|
14487
15118
|
const cancelled = tasks.filter((t) => t.status === "cancelled").length;
|
|
15119
|
+
const totalToolCalls = tasks.reduce((sum, t) => sum + (t.progress?.toolCalls ?? 0), 0);
|
|
14488
15120
|
return `${header}
|
|
14489
15121
|
${rows}
|
|
14490
15122
|
|
|
14491
15123
|
---
|
|
14492
|
-
${FORMAT_TEMPLATES.listSummary(tasks.length, running, completed, errored, cancelled)}`;
|
|
15124
|
+
${FORMAT_TEMPLATES.listSummary(tasks.length, running, completed, errored, cancelled, totalToolCalls)}`;
|
|
14493
15125
|
} catch (error45) {
|
|
14494
15126
|
return ERROR_MESSAGES.listFailed(error45 instanceof Error ? error45.message : String(error45));
|
|
14495
15127
|
}
|
|
@@ -14520,6 +15152,15 @@ function createBackgroundClear(manager) {
|
|
|
14520
15152
|
// src/index.ts
|
|
14521
15153
|
async function plugin(ctx) {
|
|
14522
15154
|
const manager = new BackgroundManager(ctx);
|
|
15155
|
+
const server = await StatusApiServer.start(manager);
|
|
15156
|
+
if (server) {
|
|
15157
|
+
const cleanup = () => {
|
|
15158
|
+
server.stop();
|
|
15159
|
+
};
|
|
15160
|
+
process.on("SIGINT", cleanup);
|
|
15161
|
+
process.on("SIGTERM", cleanup);
|
|
15162
|
+
process.on("exit", cleanup);
|
|
15163
|
+
}
|
|
14523
15164
|
return {
|
|
14524
15165
|
tool: {
|
|
14525
15166
|
asyncagents_task: createBackgroundTask(manager),
|
|
@@ -14535,5 +15176,5 @@ export {
|
|
|
14535
15176
|
plugin as default
|
|
14536
15177
|
};
|
|
14537
15178
|
|
|
14538
|
-
//# debugId=
|
|
15179
|
+
//# debugId=A899C06A316CFC2964756E2164756E21
|
|
14539
15180
|
//# sourceMappingURL=index.js.map
|