better-opencode-async-agents 0.2.1 → 0.4.0
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 +652 -33
- 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 +69 -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) {
|
|
@@ -1036,7 +1138,14 @@ function showProgressToast(allTasks, animationFrame, client, getTasksArray) {
|
|
|
1036
1138
|
const barLength = 10;
|
|
1037
1139
|
const filledLength = Math.round(finishedCount / Math.max(totalTasks, 1) * barLength);
|
|
1038
1140
|
const progressBar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
1039
|
-
const
|
|
1141
|
+
const sortedTools = Object.entries(aggregatedToolCalls).sort(([, a], [, b]) => b - a);
|
|
1142
|
+
let toolBreakdown = "";
|
|
1143
|
+
if (sortedTools.length > 0) {
|
|
1144
|
+
const top3 = sortedTools.slice(0, 3).map(([name, count]) => `${name}:${count}`).join(" ");
|
|
1145
|
+
const remaining = sortedTools.length - 3;
|
|
1146
|
+
toolBreakdown = remaining > 0 ? ` (${top3} +${remaining} more)` : ` (${top3})`;
|
|
1147
|
+
}
|
|
1148
|
+
const summary = `[${progressBar}] ${finishedCount}/${totalTasks} agents (${progressPercent}%) | ${totalToolCalls} calls${toolBreakdown}`;
|
|
1040
1149
|
const tuiClient = client;
|
|
1041
1150
|
if (!tuiClient.tui?.showToast)
|
|
1042
1151
|
return;
|
|
@@ -1201,7 +1310,7 @@ function stopPolling(pollingInterval) {
|
|
|
1201
1310
|
clearInterval(pollingInterval);
|
|
1202
1311
|
}
|
|
1203
1312
|
}
|
|
1204
|
-
async function pollRunningTasks(client, tasks, originalParentSessionID, updateTaskProgress, notifyParentSession2, clearAllTasks, stopPolling2, showProgressToast2, getTasksArray, getTaskMessages, persistTask) {
|
|
1313
|
+
async function pollRunningTasks(client, tasks, originalParentSessionID, updateTaskProgress, notifyParentSession2, clearAllTasks, stopPolling2, showProgressToast2, getTasksArray, getTaskMessages, persistTask, emitTaskEvent) {
|
|
1205
1314
|
try {
|
|
1206
1315
|
const statusResult = await client.session.status();
|
|
1207
1316
|
const allStatuses = statusResult.data ?? {};
|
|
@@ -1240,7 +1349,7 @@ async function pollRunningTasks(client, tasks, originalParentSessionID, updateTa
|
|
|
1240
1349
|
const messages = await getTaskMessages(task.sessionID);
|
|
1241
1350
|
const hasAssistantResponse = messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text && p.text.length > 0));
|
|
1242
1351
|
if (hasAssistantResponse) {
|
|
1243
|
-
setTaskStatus(task, "completed", { persistFn: persistTask });
|
|
1352
|
+
setTaskStatus(task, "completed", { persistFn: persistTask, emitFn: emitTaskEvent });
|
|
1244
1353
|
notifyParentSession2(task);
|
|
1245
1354
|
continue;
|
|
1246
1355
|
}
|
|
@@ -1250,7 +1359,7 @@ async function pollRunningTasks(client, tasks, originalParentSessionID, updateTa
|
|
|
1250
1359
|
continue;
|
|
1251
1360
|
}
|
|
1252
1361
|
if (sessionStatus.type === "idle") {
|
|
1253
|
-
setTaskStatus(task, "completed", { persistFn: persistTask });
|
|
1362
|
+
setTaskStatus(task, "completed", { persistFn: persistTask, emitFn: emitTaskEvent });
|
|
1254
1363
|
notifyParentSession2(task);
|
|
1255
1364
|
continue;
|
|
1256
1365
|
}
|
|
@@ -1291,6 +1400,7 @@ async function updateTaskProgress(task, client) {
|
|
|
1291
1400
|
return;
|
|
1292
1401
|
const messages = messagesResult.data;
|
|
1293
1402
|
let toolCalls = 0;
|
|
1403
|
+
const toolCallsByName = {};
|
|
1294
1404
|
const allTools = [];
|
|
1295
1405
|
for (const msg of messages) {
|
|
1296
1406
|
const parts = msg.parts ?? msg.content ?? [];
|
|
@@ -1300,6 +1410,7 @@ async function updateTaskProgress(task, client) {
|
|
|
1300
1410
|
for (const part of parts) {
|
|
1301
1411
|
if (part.type === "tool" && part.tool) {
|
|
1302
1412
|
toolCalls++;
|
|
1413
|
+
toolCallsByName[part.tool] = (toolCallsByName[part.tool] ?? 0) + 1;
|
|
1303
1414
|
allTools.push(part.tool);
|
|
1304
1415
|
}
|
|
1305
1416
|
}
|
|
@@ -1307,11 +1418,13 @@ async function updateTaskProgress(task, client) {
|
|
|
1307
1418
|
if (!task.progress) {
|
|
1308
1419
|
task.progress = {
|
|
1309
1420
|
toolCalls: 0,
|
|
1421
|
+
toolCallsByName: {},
|
|
1310
1422
|
lastTools: [],
|
|
1311
1423
|
lastUpdate: new Date().toISOString()
|
|
1312
1424
|
};
|
|
1313
1425
|
}
|
|
1314
1426
|
task.progress.toolCalls = toolCalls;
|
|
1427
|
+
task.progress.toolCallsByName = toolCallsByName;
|
|
1315
1428
|
task.progress.lastTools = allTools.slice(-3);
|
|
1316
1429
|
task.progress.lastUpdate = new Date().toISOString();
|
|
1317
1430
|
} catch {}
|
|
@@ -1436,7 +1549,7 @@ ${part.text}`);
|
|
|
1436
1549
|
|
|
1437
1550
|
// src/manager/task-lifecycle.ts
|
|
1438
1551
|
var FORK_METHOD = "inject";
|
|
1439
|
-
async function launchTask(input, tasks, client, getOrCreateBatchId, setOriginalParentSessionID, startPolling2, notifyParentSession2) {
|
|
1552
|
+
async function launchTask(input, tasks, client, getOrCreateBatchId, setOriginalParentSessionID, startPolling2, notifyParentSession2, emitTaskEvent, persistTask) {
|
|
1440
1553
|
if (!input.agent || input.agent.trim() === "") {
|
|
1441
1554
|
throw new Error("Agent parameter is required");
|
|
1442
1555
|
}
|
|
@@ -1511,12 +1624,16 @@ ${contextText}`;
|
|
|
1511
1624
|
isForked: input.fork ?? false,
|
|
1512
1625
|
progress: {
|
|
1513
1626
|
toolCalls: 0,
|
|
1627
|
+
toolCallsByName: {},
|
|
1514
1628
|
lastTools: [],
|
|
1515
1629
|
lastUpdate: new Date().toISOString()
|
|
1516
1630
|
}
|
|
1517
1631
|
};
|
|
1518
1632
|
setOriginalParentSessionID(input.parentSessionID);
|
|
1519
1633
|
tasks.set(task.sessionID, task);
|
|
1634
|
+
if (persistTask) {
|
|
1635
|
+
await persistTask(task);
|
|
1636
|
+
}
|
|
1520
1637
|
startPolling2();
|
|
1521
1638
|
client.session.promptAsync({
|
|
1522
1639
|
path: { id: sessionID },
|
|
@@ -1542,6 +1659,7 @@ ${contextText}`;
|
|
|
1542
1659
|
existingTask.error = errorMessage;
|
|
1543
1660
|
}
|
|
1544
1661
|
existingTask.completedAt = new Date().toISOString();
|
|
1662
|
+
emitTaskEvent?.("task.error", existingTask);
|
|
1545
1663
|
notifyParentSession2(existingTask);
|
|
1546
1664
|
}
|
|
1547
1665
|
});
|
|
@@ -1568,7 +1686,7 @@ async function getTaskMessages(sessionID, client) {
|
|
|
1568
1686
|
}
|
|
1569
1687
|
return messagesResult.data ?? messagesResult;
|
|
1570
1688
|
}
|
|
1571
|
-
async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skipNotification = false, getTaskMessages2) {
|
|
1689
|
+
async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skipNotification = false, getTaskMessages2, emitTaskEvent) {
|
|
1572
1690
|
if (task.status !== "running") {
|
|
1573
1691
|
return task;
|
|
1574
1692
|
}
|
|
@@ -1579,6 +1697,7 @@ async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skip
|
|
|
1579
1697
|
if (sessionStatus?.type === "idle") {
|
|
1580
1698
|
task.status = "completed";
|
|
1581
1699
|
task.completedAt = new Date().toISOString();
|
|
1700
|
+
emitTaskEvent?.("task.completed", task);
|
|
1582
1701
|
if (!skipNotification) {
|
|
1583
1702
|
notifyParentSession2(task);
|
|
1584
1703
|
}
|
|
@@ -1591,6 +1710,7 @@ async function checkAndUpdateTaskStatus(task, client, notifyParentSession2, skip
|
|
|
1591
1710
|
if (hasAssistantResponse) {
|
|
1592
1711
|
task.status = "completed";
|
|
1593
1712
|
task.completedAt = new Date().toISOString();
|
|
1713
|
+
emitTaskEvent?.("task.completed", task);
|
|
1594
1714
|
if (!skipNotification) {
|
|
1595
1715
|
notifyParentSession2(task);
|
|
1596
1716
|
}
|
|
@@ -1627,12 +1747,29 @@ class BackgroundManager {
|
|
|
1627
1747
|
currentBatchId = null;
|
|
1628
1748
|
tasks = new Map;
|
|
1629
1749
|
originalParentSessionID = null;
|
|
1750
|
+
taskEventListeners = [];
|
|
1630
1751
|
constructor(ctx) {
|
|
1631
1752
|
this.client = ctx.client;
|
|
1632
1753
|
this.directory = ctx.directory;
|
|
1633
1754
|
this.startEventSubscription();
|
|
1634
1755
|
this.loadPersistedTasks();
|
|
1635
1756
|
}
|
|
1757
|
+
onTaskEvent(callback) {
|
|
1758
|
+
this.taskEventListeners.push(callback);
|
|
1759
|
+
return () => {
|
|
1760
|
+
const index = this.taskEventListeners.indexOf(callback);
|
|
1761
|
+
if (index > -1) {
|
|
1762
|
+
this.taskEventListeners.splice(index, 1);
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
emitTaskEvent(eventType, task) {
|
|
1767
|
+
for (const listener of this.taskEventListeners) {
|
|
1768
|
+
try {
|
|
1769
|
+
listener(eventType, task);
|
|
1770
|
+
} catch {}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1636
1773
|
async loadPersistedTasks() {
|
|
1637
1774
|
try {
|
|
1638
1775
|
const persisted = await loadTasks();
|
|
@@ -1642,8 +1779,8 @@ class BackgroundManager {
|
|
|
1642
1779
|
async launch(input) {
|
|
1643
1780
|
const task = await launchTask(input, this.tasks, this.client, () => this.getOrCreateBatchId(), (sessionID) => {
|
|
1644
1781
|
this.originalParentSessionID = sessionID;
|
|
1645
|
-
}, () => this.startPolling(), (t) => this.notifyParentSession(t));
|
|
1646
|
-
|
|
1782
|
+
}, () => this.startPolling(), (t) => this.notifyParentSession(t), (eventType, t) => this.emitTaskEvent(eventType, t), (t) => this.persistTask(t));
|
|
1783
|
+
this.emitTaskEvent("task.created", task);
|
|
1647
1784
|
return task;
|
|
1648
1785
|
}
|
|
1649
1786
|
async persistTask(task) {
|
|
@@ -1654,7 +1791,13 @@ class BackgroundManager {
|
|
|
1654
1791
|
createdAt: task.startedAt,
|
|
1655
1792
|
status: task.status,
|
|
1656
1793
|
resumeCount: task.resumeCount,
|
|
1657
|
-
isForked: task.isForked
|
|
1794
|
+
isForked: task.isForked,
|
|
1795
|
+
completedAt: task.completedAt,
|
|
1796
|
+
error: task.error,
|
|
1797
|
+
result: task.result,
|
|
1798
|
+
progress: task.progress,
|
|
1799
|
+
startedAt: task.startedAt,
|
|
1800
|
+
batchId: task.batchId
|
|
1658
1801
|
};
|
|
1659
1802
|
try {
|
|
1660
1803
|
await saveTask(task.sessionID, persisted);
|
|
@@ -1663,7 +1806,11 @@ class BackgroundManager {
|
|
|
1663
1806
|
}
|
|
1664
1807
|
}
|
|
1665
1808
|
async cancelTask(taskId) {
|
|
1666
|
-
|
|
1809
|
+
const task = this.tasks.get(taskId);
|
|
1810
|
+
await cancelTask(taskId, this.tasks, this.client);
|
|
1811
|
+
if (task) {
|
|
1812
|
+
this.emitTaskEvent("task.cancelled", task);
|
|
1813
|
+
}
|
|
1667
1814
|
}
|
|
1668
1815
|
clearAllTasks() {
|
|
1669
1816
|
clearAllTasks(this.tasks, this.client, () => this.stopPolling());
|
|
@@ -1751,12 +1898,15 @@ class BackgroundManager {
|
|
|
1751
1898
|
getAllTasks() {
|
|
1752
1899
|
return Array.from(this.tasks.values());
|
|
1753
1900
|
}
|
|
1901
|
+
getTaskSessionIds() {
|
|
1902
|
+
return Array.from(this.tasks.keys());
|
|
1903
|
+
}
|
|
1754
1904
|
async getTaskMessages(sessionID) {
|
|
1755
1905
|
return getTaskMessages(sessionID, this.client);
|
|
1756
1906
|
}
|
|
1757
1907
|
async checkAndUpdateTaskStatus(task, skipNotification = false) {
|
|
1758
1908
|
const previousStatus = task.status;
|
|
1759
|
-
const updatedTask = await checkAndUpdateTaskStatus(task, this.client, (t) => this.notifyParentSession(t), skipNotification, (sessionID) => this.getTaskMessages(sessionID));
|
|
1909
|
+
const updatedTask = await checkAndUpdateTaskStatus(task, this.client, (t) => this.notifyParentSession(t), skipNotification, (sessionID) => this.getTaskMessages(sessionID), (eventType, t) => this.emitTaskEvent(eventType, t));
|
|
1760
1910
|
if (updatedTask.status !== previousStatus) {
|
|
1761
1911
|
await this.persistTask(updatedTask);
|
|
1762
1912
|
}
|
|
@@ -1903,12 +2053,20 @@ class BackgroundManager {
|
|
|
1903
2053
|
this.pollingInterval = undefined;
|
|
1904
2054
|
}
|
|
1905
2055
|
async pollRunningTasks() {
|
|
1906
|
-
await pollRunningTasks(this.client, this.tasks, this.originalParentSessionID, (task) =>
|
|
2056
|
+
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
2057
|
}
|
|
1908
2058
|
showProgressToast(tasks) {
|
|
1909
2059
|
showProgressToast(tasks, this.animationFrame, this.client, () => this.getAllTasks());
|
|
1910
2060
|
this.animationFrame = (this.animationFrame + 1) % 10;
|
|
1911
2061
|
}
|
|
2062
|
+
async updateTaskProgressWithEvent(task) {
|
|
2063
|
+
const previousToolCalls = task.progress?.toolCalls ?? 0;
|
|
2064
|
+
await updateTaskProgress(task, this.client);
|
|
2065
|
+
const currentToolCalls = task.progress?.toolCalls ?? 0;
|
|
2066
|
+
if (currentToolCalls > previousToolCalls) {
|
|
2067
|
+
this.emitTaskEvent("task.updated", task);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
1912
2070
|
notifyParentSession(task) {
|
|
1913
2071
|
notifyParentSession(task, this.client, this.directory, () => this.getAllTasks());
|
|
1914
2072
|
}
|
|
@@ -1917,11 +2075,457 @@ class BackgroundManager {
|
|
|
1917
2075
|
clearAllTasks: () => this.clearAllTasks(),
|
|
1918
2076
|
getTasksArray: () => this.getAllTasks(),
|
|
1919
2077
|
notifyParentSession: (task) => this.notifyParentSession(task),
|
|
1920
|
-
persistTask: (task) => void this.persistTask(task)
|
|
2078
|
+
persistTask: (task) => void this.persistTask(task),
|
|
2079
|
+
emitTaskEvent: (eventType, task) => this.emitTaskEvent(eventType, task)
|
|
1921
2080
|
}));
|
|
1922
2081
|
}
|
|
1923
2082
|
}
|
|
1924
2083
|
|
|
2084
|
+
// src/server/index.ts
|
|
2085
|
+
init_constants();
|
|
2086
|
+
init_storage();
|
|
2087
|
+
|
|
2088
|
+
// src/server/cors.ts
|
|
2089
|
+
var CORS_HEADERS = {
|
|
2090
|
+
"Access-Control-Allow-Origin": "*",
|
|
2091
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
2092
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
2093
|
+
};
|
|
2094
|
+
function withCors(response) {
|
|
2095
|
+
for (const [key, value] of Object.entries(CORS_HEADERS)) {
|
|
2096
|
+
response.headers.set(key, value);
|
|
2097
|
+
}
|
|
2098
|
+
return response;
|
|
2099
|
+
}
|
|
2100
|
+
function jsonResponse(data, status = 200) {
|
|
2101
|
+
return withCors(new Response(JSON.stringify(data), {
|
|
2102
|
+
status,
|
|
2103
|
+
headers: { "Content-Type": "application/json" }
|
|
2104
|
+
}));
|
|
2105
|
+
}
|
|
2106
|
+
function errorResponse(error, status) {
|
|
2107
|
+
return jsonResponse({ error, status }, status);
|
|
2108
|
+
}
|
|
2109
|
+
function preflightResponse() {
|
|
2110
|
+
return withCors(new Response(null, { status: 204 }));
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// src/server/routes.ts
|
|
2114
|
+
init_constants();
|
|
2115
|
+
var startTime = Date.now();
|
|
2116
|
+
function handleHealth(_req, manager) {
|
|
2117
|
+
const tasks = manager.getAllTasks();
|
|
2118
|
+
const body = {
|
|
2119
|
+
status: "ok",
|
|
2120
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
2121
|
+
version: "1.0.0",
|
|
2122
|
+
taskCount: tasks.length
|
|
2123
|
+
};
|
|
2124
|
+
return jsonResponse(body);
|
|
2125
|
+
}
|
|
2126
|
+
function handleStats(_req, manager) {
|
|
2127
|
+
const tasks = manager.getAllTasks();
|
|
2128
|
+
const byStatus = {};
|
|
2129
|
+
const byAgent = {};
|
|
2130
|
+
const toolCallsByName = {};
|
|
2131
|
+
const durations = [];
|
|
2132
|
+
let activeTasks = 0;
|
|
2133
|
+
for (const task of tasks) {
|
|
2134
|
+
byStatus[task.status] = (byStatus[task.status] ?? 0) + 1;
|
|
2135
|
+
byAgent[task.agent] = (byAgent[task.agent] ?? 0) + 1;
|
|
2136
|
+
if (task.progress?.toolCallsByName) {
|
|
2137
|
+
for (const [toolName, count] of Object.entries(task.progress.toolCallsByName)) {
|
|
2138
|
+
toolCallsByName[toolName] = (toolCallsByName[toolName] ?? 0) + count;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
if (task.status === "running" || task.status === "resumed") {
|
|
2142
|
+
activeTasks++;
|
|
2143
|
+
}
|
|
2144
|
+
if (task.startedAt && task.completedAt) {
|
|
2145
|
+
const duration = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
|
|
2146
|
+
if (duration > 0)
|
|
2147
|
+
durations.push(duration);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
const body = {
|
|
2151
|
+
byStatus,
|
|
2152
|
+
byAgent,
|
|
2153
|
+
toolCallsByName,
|
|
2154
|
+
duration: {
|
|
2155
|
+
avg: durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0,
|
|
2156
|
+
max: durations.length > 0 ? Math.max(...durations) : 0,
|
|
2157
|
+
min: durations.length > 0 ? Math.min(...durations) : 0
|
|
2158
|
+
},
|
|
2159
|
+
totalTasks: tasks.length,
|
|
2160
|
+
activeTasks
|
|
2161
|
+
};
|
|
2162
|
+
return jsonResponse(body);
|
|
2163
|
+
}
|
|
2164
|
+
function handleTaskList(req, manager) {
|
|
2165
|
+
const url = new URL(req.url);
|
|
2166
|
+
const status = url.searchParams.get("status");
|
|
2167
|
+
const agent = url.searchParams.get("agent");
|
|
2168
|
+
const search = url.searchParams.get("search");
|
|
2169
|
+
const sortParam = url.searchParams.get("sort") ?? "startedAt:desc";
|
|
2170
|
+
let limit = Number.parseInt(url.searchParams.get("limit") ?? String(DEFAULT_TASK_LIMIT), 10);
|
|
2171
|
+
let offset = Number.parseInt(url.searchParams.get("offset") ?? "0", 10);
|
|
2172
|
+
if (Number.isNaN(limit) || limit < 1)
|
|
2173
|
+
limit = DEFAULT_TASK_LIMIT;
|
|
2174
|
+
if (limit > MAX_TASK_LIMIT)
|
|
2175
|
+
limit = MAX_TASK_LIMIT;
|
|
2176
|
+
if (Number.isNaN(offset) || offset < 0)
|
|
2177
|
+
offset = 0;
|
|
2178
|
+
let tasks = manager.getAllTasks();
|
|
2179
|
+
if (status) {
|
|
2180
|
+
tasks = tasks.filter((t) => t.status === status);
|
|
2181
|
+
}
|
|
2182
|
+
if (agent) {
|
|
2183
|
+
tasks = tasks.filter((t) => t.agent === agent);
|
|
2184
|
+
}
|
|
2185
|
+
if (search) {
|
|
2186
|
+
const searchLower = search.toLowerCase();
|
|
2187
|
+
tasks = tasks.filter((t) => t.description.toLowerCase().includes(searchLower) || t.prompt?.toLowerCase().includes(searchLower));
|
|
2188
|
+
}
|
|
2189
|
+
const [sortField, sortDir] = sortParam.split(":");
|
|
2190
|
+
const direction = sortDir === "asc" ? 1 : -1;
|
|
2191
|
+
const safeSortField = sortField ?? "startedAt";
|
|
2192
|
+
tasks.sort((a, b) => {
|
|
2193
|
+
const aVal = a[safeSortField] ?? "";
|
|
2194
|
+
const bVal = b[safeSortField] ?? "";
|
|
2195
|
+
if (aVal < bVal)
|
|
2196
|
+
return -1 * direction;
|
|
2197
|
+
if (aVal > bVal)
|
|
2198
|
+
return 1 * direction;
|
|
2199
|
+
return 0;
|
|
2200
|
+
});
|
|
2201
|
+
const total = tasks.length;
|
|
2202
|
+
tasks = tasks.slice(offset, offset + limit);
|
|
2203
|
+
const body = { tasks, total, limit, offset };
|
|
2204
|
+
return jsonResponse(body);
|
|
2205
|
+
}
|
|
2206
|
+
function handleTaskDetail(req, manager, id) {
|
|
2207
|
+
const task = manager.getTask(id);
|
|
2208
|
+
if (!task) {
|
|
2209
|
+
return errorResponse("Task not found", 404);
|
|
2210
|
+
}
|
|
2211
|
+
return jsonResponse(task);
|
|
2212
|
+
}
|
|
2213
|
+
async function handleTaskLogs(req, manager, id) {
|
|
2214
|
+
const task = manager.getTask(id);
|
|
2215
|
+
if (!task) {
|
|
2216
|
+
return errorResponse("Task not found", 404);
|
|
2217
|
+
}
|
|
2218
|
+
const messages = await manager.getTaskMessages(id);
|
|
2219
|
+
const body = {
|
|
2220
|
+
messages,
|
|
2221
|
+
taskId: id,
|
|
2222
|
+
retrievedAt: new Date().toISOString()
|
|
2223
|
+
};
|
|
2224
|
+
return jsonResponse(body);
|
|
2225
|
+
}
|
|
2226
|
+
function handleTaskGroup(req, manager, groupId) {
|
|
2227
|
+
const allTasks = manager.getAllTasks();
|
|
2228
|
+
const groupTasks = allTasks.filter((t) => t.batchId === groupId);
|
|
2229
|
+
if (groupTasks.length === 0) {
|
|
2230
|
+
return errorResponse("Task group not found", 404);
|
|
2231
|
+
}
|
|
2232
|
+
let completed = 0;
|
|
2233
|
+
let running = 0;
|
|
2234
|
+
let error = 0;
|
|
2235
|
+
let cancelled = 0;
|
|
2236
|
+
let totalToolCalls = 0;
|
|
2237
|
+
const toolCallsByName = {};
|
|
2238
|
+
let minStart = Number.POSITIVE_INFINITY;
|
|
2239
|
+
let maxEnd = 0;
|
|
2240
|
+
for (const t of groupTasks) {
|
|
2241
|
+
if (t.status === "completed")
|
|
2242
|
+
completed++;
|
|
2243
|
+
else if (t.status === "running" || t.status === "resumed")
|
|
2244
|
+
running++;
|
|
2245
|
+
else if (t.status === "error")
|
|
2246
|
+
error++;
|
|
2247
|
+
else if (t.status === "cancelled")
|
|
2248
|
+
cancelled++;
|
|
2249
|
+
totalToolCalls += t.progress?.toolCalls || 0;
|
|
2250
|
+
if (t.progress?.toolCallsByName) {
|
|
2251
|
+
for (const [toolName, count] of Object.entries(t.progress.toolCallsByName)) {
|
|
2252
|
+
toolCallsByName[toolName] = (toolCallsByName[toolName] ?? 0) + count;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (t.startedAt) {
|
|
2256
|
+
const s = new Date(t.startedAt).getTime();
|
|
2257
|
+
if (s < minStart)
|
|
2258
|
+
minStart = s;
|
|
2259
|
+
}
|
|
2260
|
+
if (t.completedAt) {
|
|
2261
|
+
const e = new Date(t.completedAt).getTime();
|
|
2262
|
+
if (e > maxEnd)
|
|
2263
|
+
maxEnd = e;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
const total = groupTasks.length;
|
|
2267
|
+
const duration = minStart < Number.POSITIVE_INFINITY && maxEnd > 0 ? maxEnd - minStart : 0;
|
|
2268
|
+
const body = {
|
|
2269
|
+
groupId,
|
|
2270
|
+
tasks: groupTasks,
|
|
2271
|
+
stats: {
|
|
2272
|
+
completed,
|
|
2273
|
+
running,
|
|
2274
|
+
error,
|
|
2275
|
+
cancelled,
|
|
2276
|
+
total,
|
|
2277
|
+
completionRate: total > 0 ? completed / total : 0,
|
|
2278
|
+
totalToolCalls,
|
|
2279
|
+
toolCallsByName,
|
|
2280
|
+
duration
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
return jsonResponse(body);
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// src/server/sse.ts
|
|
2287
|
+
init_constants();
|
|
2288
|
+
class SSEBroadcaster {
|
|
2289
|
+
subscribers = new Set;
|
|
2290
|
+
encoder = new TextEncoder;
|
|
2291
|
+
addSubscriber(controller) {
|
|
2292
|
+
if (this.subscribers.size >= MAX_SSE_SUBSCRIBERS) {
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
this.subscribers.add(controller);
|
|
2296
|
+
return true;
|
|
2297
|
+
}
|
|
2298
|
+
removeSubscriber(controller) {
|
|
2299
|
+
this.subscribers.delete(controller);
|
|
2300
|
+
}
|
|
2301
|
+
broadcast(eventType, data) {
|
|
2302
|
+
const message = this.formatSSE(eventType, data);
|
|
2303
|
+
const encoded = this.encoder.encode(message);
|
|
2304
|
+
for (const controller of this.subscribers) {
|
|
2305
|
+
try {
|
|
2306
|
+
controller.enqueue(encoded);
|
|
2307
|
+
} catch {
|
|
2308
|
+
this.subscribers.delete(controller);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
sendTo(controller, eventType, data) {
|
|
2313
|
+
const message = this.formatSSE(eventType, data);
|
|
2314
|
+
try {
|
|
2315
|
+
controller.enqueue(this.encoder.encode(message));
|
|
2316
|
+
} catch {
|
|
2317
|
+
this.subscribers.delete(controller);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
getSubscriberCount() {
|
|
2321
|
+
return this.subscribers.size;
|
|
2322
|
+
}
|
|
2323
|
+
formatSSE(eventType, data) {
|
|
2324
|
+
return `event: ${eventType}
|
|
2325
|
+
data: ${JSON.stringify(data)}
|
|
2326
|
+
|
|
2327
|
+
`;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
function handleSSERequest(req, broadcaster, dataProvider) {
|
|
2331
|
+
let controller;
|
|
2332
|
+
let heartbeatTimer;
|
|
2333
|
+
const stream = new ReadableStream({
|
|
2334
|
+
start(ctrl) {
|
|
2335
|
+
controller = ctrl;
|
|
2336
|
+
if (!broadcaster.addSubscriber(controller)) {
|
|
2337
|
+
controller.enqueue(new TextEncoder().encode(`event: error
|
|
2338
|
+
data: ${JSON.stringify({ error: "Too many connections" })}
|
|
2339
|
+
|
|
2340
|
+
`));
|
|
2341
|
+
controller.close();
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const snapshot = {
|
|
2345
|
+
tasks: dataProvider.getAllTasks(),
|
|
2346
|
+
stats: dataProvider.buildStats()
|
|
2347
|
+
};
|
|
2348
|
+
broadcaster.sendTo(controller, "snapshot", snapshot);
|
|
2349
|
+
heartbeatTimer = setInterval(() => {
|
|
2350
|
+
try {
|
|
2351
|
+
broadcaster.sendTo(controller, "heartbeat", { ts: new Date().toISOString() });
|
|
2352
|
+
} catch {
|
|
2353
|
+
clearInterval(heartbeatTimer);
|
|
2354
|
+
broadcaster.removeSubscriber(controller);
|
|
2355
|
+
}
|
|
2356
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
2357
|
+
},
|
|
2358
|
+
cancel() {
|
|
2359
|
+
clearInterval(heartbeatTimer);
|
|
2360
|
+
broadcaster.removeSubscriber(controller);
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
return new Response(stream, {
|
|
2364
|
+
status: 200,
|
|
2365
|
+
headers: {
|
|
2366
|
+
"Content-Type": "text/event-stream",
|
|
2367
|
+
"Cache-Control": "no-cache",
|
|
2368
|
+
Connection: "keep-alive",
|
|
2369
|
+
...CORS_HEADERS
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// src/server/index.ts
|
|
2375
|
+
class StatusApiServer {
|
|
2376
|
+
manager;
|
|
2377
|
+
server = null;
|
|
2378
|
+
broadcaster;
|
|
2379
|
+
unsubscribe = null;
|
|
2380
|
+
startedAt;
|
|
2381
|
+
constructor(manager) {
|
|
2382
|
+
this.manager = manager;
|
|
2383
|
+
this.broadcaster = new SSEBroadcaster;
|
|
2384
|
+
this.startedAt = new Date().toISOString();
|
|
2385
|
+
}
|
|
2386
|
+
static async start(manager) {
|
|
2387
|
+
if (process.env.ASYNCAGENTS_API_ENABLED === "false") {
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
const instance = new StatusApiServer(manager);
|
|
2391
|
+
await instance.bind();
|
|
2392
|
+
return instance;
|
|
2393
|
+
}
|
|
2394
|
+
async bind() {
|
|
2395
|
+
const desiredPort = Number.parseInt(process.env.ASYNCAGENTS_API_PORT ?? String(DEFAULT_API_PORT), 10);
|
|
2396
|
+
const host = process.env.ASYNCAGENTS_API_HOST ?? DEFAULT_API_HOST;
|
|
2397
|
+
const dataProvider = {
|
|
2398
|
+
getAllTasks: () => this.manager.getAllTasks(),
|
|
2399
|
+
buildStats: () => this.buildStatsData()
|
|
2400
|
+
};
|
|
2401
|
+
const broadcaster = this.broadcaster;
|
|
2402
|
+
const manager = this.manager;
|
|
2403
|
+
let lastError = null;
|
|
2404
|
+
for (let attempt = 0;attempt <= MAX_PORT_RETRY; attempt++) {
|
|
2405
|
+
const port2 = attempt < MAX_PORT_RETRY ? desiredPort + attempt : 0;
|
|
2406
|
+
try {
|
|
2407
|
+
this.server = Bun.serve({
|
|
2408
|
+
port: port2,
|
|
2409
|
+
hostname: host,
|
|
2410
|
+
fetch(req) {
|
|
2411
|
+
if (req.method === "OPTIONS") {
|
|
2412
|
+
return preflightResponse();
|
|
2413
|
+
}
|
|
2414
|
+
const url2 = new URL(req.url);
|
|
2415
|
+
const path = url2.pathname;
|
|
2416
|
+
if (path === "/v1/health")
|
|
2417
|
+
return handleHealth(req, manager);
|
|
2418
|
+
if (path === "/v1/stats")
|
|
2419
|
+
return handleStats(req, manager);
|
|
2420
|
+
if (path === "/v1/tasks")
|
|
2421
|
+
return handleTaskList(req, manager);
|
|
2422
|
+
if (path === "/v1/events")
|
|
2423
|
+
return handleSSERequest(req, broadcaster, dataProvider);
|
|
2424
|
+
const logsMatch = path.match(/^\/v1\/tasks\/([^/]+)\/logs$/);
|
|
2425
|
+
const logsId = logsMatch?.[1];
|
|
2426
|
+
if (logsId) {
|
|
2427
|
+
return handleTaskLogs(req, manager, logsId);
|
|
2428
|
+
}
|
|
2429
|
+
const taskMatch = path.match(/^\/v1\/tasks\/([^/]+)$/);
|
|
2430
|
+
const taskId = taskMatch?.[1];
|
|
2431
|
+
if (taskId) {
|
|
2432
|
+
return handleTaskDetail(req, manager, taskId);
|
|
2433
|
+
}
|
|
2434
|
+
const groupMatch = path.match(/^\/v1\/task-groups\/([^/]+)$/);
|
|
2435
|
+
const groupId = groupMatch?.[1];
|
|
2436
|
+
if (groupId) {
|
|
2437
|
+
return handleTaskGroup(req, manager, groupId);
|
|
2438
|
+
}
|
|
2439
|
+
return errorResponse("Not Found", 404);
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
lastError = null;
|
|
2443
|
+
break;
|
|
2444
|
+
} catch (err) {
|
|
2445
|
+
lastError = err;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
if (lastError || !this.server) {
|
|
2449
|
+
throw new Error(`Failed to bind API server after ${MAX_PORT_RETRY + 1} attempts: ${lastError?.message}`);
|
|
2450
|
+
}
|
|
2451
|
+
if (this.manager.onTaskEvent) {
|
|
2452
|
+
this.unsubscribe = this.manager.onTaskEvent((eventType, task) => {
|
|
2453
|
+
this.broadcaster.broadcast(eventType, { task });
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
const port = this.server.port;
|
|
2457
|
+
const url = `http://${host}:${port}`;
|
|
2458
|
+
try {
|
|
2459
|
+
await writeServerInfo({
|
|
2460
|
+
port,
|
|
2461
|
+
pid: process.pid,
|
|
2462
|
+
startedAt: this.startedAt,
|
|
2463
|
+
url,
|
|
2464
|
+
version: "1.0.0"
|
|
2465
|
+
});
|
|
2466
|
+
} catch {}
|
|
2467
|
+
}
|
|
2468
|
+
buildStatsData() {
|
|
2469
|
+
const tasks = this.manager.getAllTasks();
|
|
2470
|
+
const byStatus = {};
|
|
2471
|
+
const byAgent = {};
|
|
2472
|
+
const toolCallsByName = {};
|
|
2473
|
+
const durations = [];
|
|
2474
|
+
let activeTasks = 0;
|
|
2475
|
+
for (const task of tasks) {
|
|
2476
|
+
byStatus[task.status] = (byStatus[task.status] ?? 0) + 1;
|
|
2477
|
+
byAgent[task.agent] = (byAgent[task.agent] ?? 0) + 1;
|
|
2478
|
+
if (task.progress?.toolCallsByName) {
|
|
2479
|
+
for (const [toolName, count] of Object.entries(task.progress.toolCallsByName)) {
|
|
2480
|
+
toolCallsByName[toolName] = (toolCallsByName[toolName] ?? 0) + count;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
if (task.status === "running" || task.status === "resumed")
|
|
2484
|
+
activeTasks++;
|
|
2485
|
+
if (task.startedAt && task.completedAt) {
|
|
2486
|
+
const d = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
|
|
2487
|
+
if (d > 0)
|
|
2488
|
+
durations.push(d);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
return {
|
|
2492
|
+
byStatus,
|
|
2493
|
+
byAgent,
|
|
2494
|
+
toolCallsByName,
|
|
2495
|
+
duration: {
|
|
2496
|
+
avg: durations.length ? durations.reduce((a, b) => a + b, 0) / durations.length : 0,
|
|
2497
|
+
max: durations.length ? Math.max(...durations) : 0,
|
|
2498
|
+
min: durations.length ? Math.min(...durations) : 0
|
|
2499
|
+
},
|
|
2500
|
+
totalTasks: tasks.length,
|
|
2501
|
+
activeTasks
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
getPort() {
|
|
2505
|
+
return this.server?.port ?? 0;
|
|
2506
|
+
}
|
|
2507
|
+
getUrl() {
|
|
2508
|
+
const host = process.env.ASYNCAGENTS_API_HOST ?? DEFAULT_API_HOST;
|
|
2509
|
+
return `http://${host}:${this.getPort()}`;
|
|
2510
|
+
}
|
|
2511
|
+
getBroadcaster() {
|
|
2512
|
+
return this.broadcaster;
|
|
2513
|
+
}
|
|
2514
|
+
async stop() {
|
|
2515
|
+
if (this.unsubscribe) {
|
|
2516
|
+
this.unsubscribe();
|
|
2517
|
+
this.unsubscribe = null;
|
|
2518
|
+
}
|
|
2519
|
+
if (this.server) {
|
|
2520
|
+
this.server.stop(true);
|
|
2521
|
+
this.server = null;
|
|
2522
|
+
}
|
|
2523
|
+
try {
|
|
2524
|
+
await deleteServerInfo();
|
|
2525
|
+
} catch {}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
1925
2529
|
// node_modules/.pnpm/@opencode-ai+plugin@1.1.25/node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
1926
2530
|
var exports_external = {};
|
|
1927
2531
|
__export(exports_external, {
|
|
@@ -14364,7 +14968,9 @@ async function handleLaunchMode(manager, args, toolContext) {
|
|
|
14364
14968
|
parentMessageID: toolContext.messageID,
|
|
14365
14969
|
parentAgent: toolContext.agent
|
|
14366
14970
|
});
|
|
14367
|
-
|
|
14971
|
+
const siblingIds = manager.getTaskSessionIds?.() ?? [];
|
|
14972
|
+
const displayId = uniqueShortId(task.sessionID, siblingIds);
|
|
14973
|
+
return SUCCESS_MESSAGES.taskLaunched(displayId);
|
|
14368
14974
|
} catch (error45) {
|
|
14369
14975
|
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
14370
14976
|
return ERROR_MESSAGES.launchFailed(message);
|
|
@@ -14383,11 +14989,11 @@ function createBackgroundOutput(manager) {
|
|
|
14383
14989
|
},
|
|
14384
14990
|
async execute(args) {
|
|
14385
14991
|
try {
|
|
14386
|
-
const resolvedId = manager.
|
|
14992
|
+
const resolvedId = await manager.resolveTaskIdWithFallback(args.task_id);
|
|
14387
14993
|
if (!resolvedId) {
|
|
14388
14994
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14389
14995
|
}
|
|
14390
|
-
let task = manager.getTask(resolvedId);
|
|
14996
|
+
let task = manager.getTask(resolvedId) ?? await manager.getTaskWithFallback(resolvedId);
|
|
14391
14997
|
if (!task) {
|
|
14392
14998
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14393
14999
|
}
|
|
@@ -14431,11 +15037,11 @@ function createBackgroundCancel(manager) {
|
|
|
14431
15037
|
},
|
|
14432
15038
|
async execute(args) {
|
|
14433
15039
|
try {
|
|
14434
|
-
const resolvedId = manager.
|
|
15040
|
+
const resolvedId = await manager.resolveTaskIdWithFallback(args.task_id);
|
|
14435
15041
|
if (!resolvedId) {
|
|
14436
15042
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14437
15043
|
}
|
|
14438
|
-
const task = manager.
|
|
15044
|
+
const task = await manager.getTaskWithFallback(resolvedId);
|
|
14439
15045
|
if (!task) {
|
|
14440
15046
|
return ERROR_MESSAGES.taskNotFound(args.task_id);
|
|
14441
15047
|
}
|
|
@@ -14469,6 +15075,7 @@ function createBackgroundList(manager) {
|
|
|
14469
15075
|
}
|
|
14470
15076
|
tasks.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
14471
15077
|
const header = FORMAT_TEMPLATES.listHeader;
|
|
15078
|
+
const allSessionIds = tasks.map((t) => t.sessionID);
|
|
14472
15079
|
const rows = tasks.map((task) => {
|
|
14473
15080
|
const duration3 = formatDuration(task.startedAt, task.completedAt);
|
|
14474
15081
|
const desc = task.description.length > 30 ? `${task.description.slice(0, 27)}...` : task.description;
|
|
@@ -14477,19 +15084,22 @@ function createBackgroundList(manager) {
|
|
|
14477
15084
|
task.isForked ? "(forked)" : "",
|
|
14478
15085
|
task.resumeCount > 0 ? "(resumed)" : ""
|
|
14479
15086
|
].filter(Boolean).join(" ");
|
|
14480
|
-
const
|
|
14481
|
-
|
|
15087
|
+
const shortId2 = uniqueShortId(task.sessionID, allSessionIds);
|
|
15088
|
+
const idWithIndicators = indicators ? `${shortId2} ${indicators}` : shortId2;
|
|
15089
|
+
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` : "-";
|
|
15090
|
+
return `| \`${idWithIndicators}\` | ${desc} | ${task.agent} | ${icon} ${task.status} | ${duration3} | ${toolsInfo} |`;
|
|
14482
15091
|
}).join(`
|
|
14483
15092
|
`);
|
|
14484
15093
|
const running = tasks.filter((t) => t.status === "running").length;
|
|
14485
15094
|
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
14486
15095
|
const errored = tasks.filter((t) => t.status === "error").length;
|
|
14487
15096
|
const cancelled = tasks.filter((t) => t.status === "cancelled").length;
|
|
15097
|
+
const totalToolCalls = tasks.reduce((sum, t) => sum + (t.progress?.toolCalls ?? 0), 0);
|
|
14488
15098
|
return `${header}
|
|
14489
15099
|
${rows}
|
|
14490
15100
|
|
|
14491
15101
|
---
|
|
14492
|
-
${FORMAT_TEMPLATES.listSummary(tasks.length, running, completed, errored, cancelled)}`;
|
|
15102
|
+
${FORMAT_TEMPLATES.listSummary(tasks.length, running, completed, errored, cancelled, totalToolCalls)}`;
|
|
14493
15103
|
} catch (error45) {
|
|
14494
15104
|
return ERROR_MESSAGES.listFailed(error45 instanceof Error ? error45.message : String(error45));
|
|
14495
15105
|
}
|
|
@@ -14520,6 +15130,15 @@ function createBackgroundClear(manager) {
|
|
|
14520
15130
|
// src/index.ts
|
|
14521
15131
|
async function plugin(ctx) {
|
|
14522
15132
|
const manager = new BackgroundManager(ctx);
|
|
15133
|
+
const server = await StatusApiServer.start(manager);
|
|
15134
|
+
if (server) {
|
|
15135
|
+
const cleanup = () => {
|
|
15136
|
+
server.stop();
|
|
15137
|
+
};
|
|
15138
|
+
process.on("SIGINT", cleanup);
|
|
15139
|
+
process.on("SIGTERM", cleanup);
|
|
15140
|
+
process.on("exit", cleanup);
|
|
15141
|
+
}
|
|
14523
15142
|
return {
|
|
14524
15143
|
tool: {
|
|
14525
15144
|
asyncagents_task: createBackgroundTask(manager),
|
|
@@ -14535,5 +15154,5 @@ export {
|
|
|
14535
15154
|
plugin as default
|
|
14536
15155
|
};
|
|
14537
15156
|
|
|
14538
|
-
//# debugId=
|
|
15157
|
+
//# debugId=C3296E4B18C7938B64756E2164756E21
|
|
14539
15158
|
//# sourceMappingURL=index.js.map
|