@sema-agent/core 1.450.0 → 1.452.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/agents/observer.js +3 -0
- package/dist/agents/subagent.js +51 -12
- package/dist/core/ask-question.js +5 -5
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/background-agent-store.js +5 -1
- package/dist/core/exec-output-tail.d.ts +18 -1
- package/dist/core/exec-output-tail.js +38 -5
- package/dist/core/lsp-session.d.ts +1 -0
- package/dist/core/lsp-session.js +24 -6
- package/dist/core/lsp.d.ts +10 -0
- package/dist/core/lsp.js +63 -6
- package/dist/core/mailbox-store.d.ts +3 -2
- package/dist/core/mailbox-store.js +19 -4
- package/dist/core/memory.js +1 -1
- package/dist/core/runner/prepare-task.js +11 -1
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/session-reconcile.js +5 -2
- package/dist/core/task-notification.d.ts +1 -0
- package/dist/core/task-registry.d.ts +15 -9
- package/dist/core/task-registry.js +290 -53
- package/dist/core/tool-result-store.js +2 -2
- package/dist/core/tools.d.ts +5 -0
- package/dist/core/tools.js +3 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/workflow-journal-store.d.ts +2 -0
- package/dist/core/workflow-journal-store.js +14 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +1 -0
- package/dist/engine/execution-env/node-execution-env.js +130 -20
- package/dist/engine/lsp/node-lsp-manager.d.ts +3 -1
- package/dist/engine/lsp/node-lsp-manager.js +22 -5
- package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
- package/dist/engine/lsp/stdio-lsp-transport.js +17 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -0
- package/dist/orchestration/run-workflow-tool.js +35 -6
- package/dist/orchestration/workflow.d.ts +9 -0
- package/dist/orchestration/workflow.js +80 -5
- package/dist/stores/cc/mailbox-store.js +6 -1
- package/dist/stores/file/background-agent-store.js +3 -2
- package/dist/stores/file/mailbox-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.js +9 -7
- package/dist/tools/fs/encoding.d.ts +5 -0
- package/dist/tools/fs/encoding.js +6 -0
- package/dist/tools/fs/index.js +184 -120
- package/dist/tools/fs/notebook.d.ts +43 -0
- package/dist/tools/fs/notebook.js +141 -0
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/search.js +141 -12
- package/dist/tools/gitea-issue.js +4 -2
- package/dist/tools/monitor.js +12 -8
- package/dist/tools/scheduler-tools.js +16 -16
- package/dist/tools/task-list.js +34 -12
- package/dist/tools/web.d.ts +2 -0
- package/dist/tools/web.js +105 -19
- package/dist/tools/worktree.js +14 -14
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
3
|
+
import { uuidv7 } from "../internal/harness.js";
|
|
4
|
+
import { defineTool, errorResult } from "./tools.js";
|
|
4
5
|
import { hasBackgroundShell } from "./background-shell.js";
|
|
5
6
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
6
7
|
import { summarizeWorkflowRun } from "./workflow-run-store.js";
|
|
@@ -8,6 +9,16 @@ import { canAccessAgentRecord, BackgroundAgentStoreError, } from "./background-a
|
|
|
8
9
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
9
10
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
10
11
|
import { clipWithFilePointer } from "./tool-errors.js";
|
|
12
|
+
function mintCompletionId(target) {
|
|
13
|
+
if (target.completionId === undefined)
|
|
14
|
+
target.completionId = uuidv7();
|
|
15
|
+
return target.completionId;
|
|
16
|
+
}
|
|
17
|
+
function commitCompletionIdIfEmpty(target, value) {
|
|
18
|
+
if (target.completionId === undefined)
|
|
19
|
+
target.completionId = value;
|
|
20
|
+
return target.completionId;
|
|
21
|
+
}
|
|
11
22
|
const defaultMonitorTimers = {
|
|
12
23
|
setInterval: (fn, ms) => {
|
|
13
24
|
const h = setInterval(() => void fn(), ms);
|
|
@@ -49,6 +60,9 @@ export const MONITOR_DEFAULT_TIMEOUT_MS = 300_000;
|
|
|
49
60
|
export const MONITOR_MAX_TIMEOUT_MS = 3_600_000;
|
|
50
61
|
export const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
|
|
51
62
|
const MONITOR_EVENT_RESULT_CAP = 2_000;
|
|
63
|
+
function clipMonitorEvent(s) {
|
|
64
|
+
return s.length <= MONITOR_EVENT_RESULT_CAP ? s : `${s.slice(0, MONITOR_EVENT_RESULT_CAP)}\n…[+${s.length - MONITOR_EVENT_RESULT_CAP} chars truncated]`;
|
|
65
|
+
}
|
|
52
66
|
const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
|
|
53
67
|
const TASK_OUTPUT_MAX_CHARS = 160_000;
|
|
54
68
|
const TASK_OUTPUT_ALIASES = ["BashOutput", "AgentOutputTool", "BashOutputTool", "AgentOutput", "WorkflowStatus"];
|
|
@@ -79,6 +93,7 @@ function noTask(taskId) {
|
|
|
79
93
|
return {
|
|
80
94
|
content: JSON.stringify({ error: "task not found" }),
|
|
81
95
|
details: { task_id: taskId, type: "unknown", status: "not_found", retrieval_status: "not_ready", error: "not_found" },
|
|
96
|
+
isError: true,
|
|
82
97
|
};
|
|
83
98
|
}
|
|
84
99
|
export function normalizeAgentName(s) {
|
|
@@ -133,6 +148,7 @@ function noTaskForStop(taskId, enrich) {
|
|
|
133
148
|
return {
|
|
134
149
|
content,
|
|
135
150
|
details: { task_id: taskId, type: "unknown", status: "not_found", retrieval_status: "not_ready", error: "not_found" },
|
|
151
|
+
isError: true,
|
|
136
152
|
};
|
|
137
153
|
}
|
|
138
154
|
function bashTerminalResult(spool) {
|
|
@@ -163,6 +179,31 @@ function bashMirrorGapNote(handle) {
|
|
|
163
179
|
? " [!] The output file is INCOMPLETE (one or more writes to it failed) — trust the result text / TaskOutput over the file."
|
|
164
180
|
: "";
|
|
165
181
|
}
|
|
182
|
+
function accountDroppedBytes(spool, poll) {
|
|
183
|
+
const d = poll.bytesDroppedBeforeCursor ?? 0;
|
|
184
|
+
if (d > 0)
|
|
185
|
+
spool.droppedBytes = (spool.droppedBytes ?? 0) + d;
|
|
186
|
+
else if (poll.truncated === true)
|
|
187
|
+
spool.dropUnknown = true;
|
|
188
|
+
}
|
|
189
|
+
function droppedGapNote(spool) {
|
|
190
|
+
if ((spool.droppedBytes ?? 0) > 0)
|
|
191
|
+
return ` [!] ${spool.droppedBytes} earlier byte(s) of output were permanently dropped before this window (execution-environment tail buffer evicted) — the event stream and this spool are INCOMPLETE.`;
|
|
192
|
+
if (spool.dropUnknown === true)
|
|
193
|
+
return " [!] An unknown amount of earlier output was permanently dropped before this window (execution-environment tail buffer evicted) — the event stream and this spool are INCOMPLETE.";
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
function firstDropNote(spool) {
|
|
197
|
+
if (spool.dropNotified === true)
|
|
198
|
+
return "";
|
|
199
|
+
const note = droppedGapNote(spool);
|
|
200
|
+
if (note !== "")
|
|
201
|
+
spool.dropNotified = true;
|
|
202
|
+
return note;
|
|
203
|
+
}
|
|
204
|
+
function alreadyTerminalStopNote(id, status) {
|
|
205
|
+
return `${id} already ${status} — nothing to stop; poll TaskOutput for its final state.`;
|
|
206
|
+
}
|
|
166
207
|
function monitorInTimeoutWindow(handle) {
|
|
167
208
|
return handle.status === "running" && handle.deadlineAt !== undefined && handle.timers.now() < handle.deadlineAt;
|
|
168
209
|
}
|
|
@@ -238,6 +279,19 @@ function formatWorkflowRun(run) {
|
|
|
238
279
|
...(overCap ? { agent_runs_note: `showing ${agentRows.length} of ${run.agents.length} agent rows (every non-completed row kept; completed rows trimmed).` } : {}),
|
|
239
280
|
startedAt: run.startedAt,
|
|
240
281
|
...(run.endedAt !== undefined ? { endedAt: run.endedAt, elapsedMs: run.endedAt - run.startedAt } : {}),
|
|
282
|
+
...(run.resume !== undefined
|
|
283
|
+
? {
|
|
284
|
+
resume: {
|
|
285
|
+
from_run_id: run.resume.fromRunId,
|
|
286
|
+
journal_entries: run.resume.journalEntries,
|
|
287
|
+
replayed: run.resume.replayed,
|
|
288
|
+
...(run.resume.divergedAtOrdinal !== undefined
|
|
289
|
+
? { replay_stopped_at: run.resume.divergedAtOrdinal, replay_stopped_reason: run.resume.divergedReason }
|
|
290
|
+
: {}),
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
: {}),
|
|
294
|
+
...(run.journalSkips !== undefined && run.journalSkips > 0 ? { journal_skipped: run.journalSkips } : {}),
|
|
241
295
|
...(run.status === "failed" && run.error ? { error: delimitUntrusted("workflow error", boundedRedactedSummary(run.error, 500)) } : {}),
|
|
242
296
|
...(run.status === "completed" && run.result !== undefined
|
|
243
297
|
? { result: delimitUntrusted("workflow result", run.resultFull ?? run.result) }
|
|
@@ -268,6 +322,7 @@ function formatWorkflowRun(run) {
|
|
|
268
322
|
type: "workflow",
|
|
269
323
|
status: run.status,
|
|
270
324
|
retrieval_status: "success",
|
|
325
|
+
...(run.completionId !== undefined ? { completionId: run.completionId } : {}),
|
|
271
326
|
details: summary,
|
|
272
327
|
},
|
|
273
328
|
};
|
|
@@ -762,6 +817,9 @@ export class TaskRegistry {
|
|
|
762
817
|
return undefined;
|
|
763
818
|
return handle.stoppedBy ?? handle.stopSource ?? "system";
|
|
764
819
|
}
|
|
820
|
+
getCompletionId(id) {
|
|
821
|
+
return this.handles.get(id)?.completionId;
|
|
822
|
+
}
|
|
765
823
|
parkBackgroundAgent(id, park) {
|
|
766
824
|
const handle = this.handles.get(id);
|
|
767
825
|
if (!handle || handle.type !== "background_agent")
|
|
@@ -819,8 +877,9 @@ export class TaskRegistry {
|
|
|
819
877
|
handle.parkedCheckpointToken = undefined;
|
|
820
878
|
handle.resolveParkedStop = undefined;
|
|
821
879
|
handle.updatedAt = now;
|
|
880
|
+
mintCompletionId(handle);
|
|
822
881
|
this.pokeBgQuiescence(handle.owner);
|
|
823
|
-
this.durableAgentWrite(handle, { status: "failed", error: handle.error, settledAt: now, stoppedBy: "system" }, ["parkedCheckpointToken", "parkClaimId", "parkedAt"]);
|
|
882
|
+
this.durableAgentWrite(handle, { status: "failed", error: handle.error, settledAt: now, stoppedBy: "system", completionId: handle.completionId }, ["parkedCheckpointToken", "parkClaimId", "parkedAt"]);
|
|
824
883
|
liveFailed++;
|
|
825
884
|
}
|
|
826
885
|
const { reconcileParkedAgents: storeReconcile } = await import("./background-agent-store.js");
|
|
@@ -895,6 +954,7 @@ export class TaskRegistry {
|
|
|
895
954
|
return "retry";
|
|
896
955
|
}
|
|
897
956
|
const now = Date.now();
|
|
957
|
+
const rollbackCompletionIdCandidate = uuidv7();
|
|
898
958
|
const settleLocalParkedHandle = (error) => {
|
|
899
959
|
const h = this.handles.get(ticket.handle);
|
|
900
960
|
if (h !== undefined && h.type === "background_agent" && h.status === "parked") {
|
|
@@ -903,6 +963,7 @@ export class TaskRegistry {
|
|
|
903
963
|
h.parkedCheckpointToken = undefined;
|
|
904
964
|
h.resolveParkedStop = undefined;
|
|
905
965
|
h.updatedAt = now;
|
|
966
|
+
commitCompletionIdIfEmpty(h, rollbackCompletionIdCandidate);
|
|
906
967
|
this.pokeBgQuiescence(h.owner);
|
|
907
968
|
}
|
|
908
969
|
};
|
|
@@ -948,6 +1009,7 @@ export class TaskRegistry {
|
|
|
948
1009
|
term.stoppedBy = "system";
|
|
949
1010
|
term.settledAt = now;
|
|
950
1011
|
term.updatedAt = now;
|
|
1012
|
+
term.completionId = rollbackCompletionIdCandidate;
|
|
951
1013
|
delete term.parkedCheckpointToken;
|
|
952
1014
|
delete term.parkClaimId;
|
|
953
1015
|
delete term.parkedAt;
|
|
@@ -977,6 +1039,7 @@ export class TaskRegistry {
|
|
|
977
1039
|
failed.stoppedBy = "system";
|
|
978
1040
|
failed.settledAt = now;
|
|
979
1041
|
failed.updatedAt = now;
|
|
1042
|
+
failed.completionId = rollbackCompletionIdCandidate;
|
|
980
1043
|
delete failed.parkedCheckpointToken;
|
|
981
1044
|
delete failed.parkClaimId;
|
|
982
1045
|
delete failed.parkedAt;
|
|
@@ -1053,6 +1116,7 @@ export class TaskRegistry {
|
|
|
1053
1116
|
handle.parkedCheckpointToken = undefined;
|
|
1054
1117
|
handle.resolveParkedStop = undefined;
|
|
1055
1118
|
handle.updatedAt = Date.now();
|
|
1119
|
+
handle.completionId = undefined;
|
|
1056
1120
|
if (flipped.seq !== undefined)
|
|
1057
1121
|
handle.cycleSeq = flipped.seq;
|
|
1058
1122
|
return true;
|
|
@@ -1069,6 +1133,7 @@ export class TaskRegistry {
|
|
|
1069
1133
|
return undefined;
|
|
1070
1134
|
if (handle.status !== "running") {
|
|
1071
1135
|
if (handle.status === "killed") {
|
|
1136
|
+
mintCompletionId(handle);
|
|
1072
1137
|
let backfilled = false;
|
|
1073
1138
|
if (handle.result === undefined && outcome.result !== undefined && outcome.result !== "") {
|
|
1074
1139
|
handle.result = outcome.result;
|
|
@@ -1088,12 +1153,14 @@ export class TaskRegistry {
|
|
|
1088
1153
|
...(handle.resultFull !== undefined ? { finalOutputFull: handle.resultFull } : {}),
|
|
1089
1154
|
...(handle.resultIsPartial ? { resultIsPartial: true } : {}),
|
|
1090
1155
|
...(handle.error !== undefined ? { error: handle.error } : {}),
|
|
1156
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1091
1157
|
});
|
|
1092
1158
|
}
|
|
1093
1159
|
}
|
|
1094
1160
|
return handle.status === "completed" || handle.status === "failed" || handle.status === "killed" ? handle.status : undefined;
|
|
1095
1161
|
}
|
|
1096
1162
|
handle.status = outcome.status;
|
|
1163
|
+
mintCompletionId(handle);
|
|
1097
1164
|
handle.notify = undefined;
|
|
1098
1165
|
for (const [, , qResolve] of handle.preAttachQueue ?? [])
|
|
1099
1166
|
qResolve?.({ ok: false, reason: "not_running" });
|
|
@@ -1123,6 +1190,7 @@ export class TaskRegistry {
|
|
|
1123
1190
|
...(outcome.seq !== undefined ? { seq: outcome.seq } : {}),
|
|
1124
1191
|
settledAt: Date.now(),
|
|
1125
1192
|
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
1193
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1126
1194
|
...(handle.result !== undefined ? { finalOutput: handle.result } : {}),
|
|
1127
1195
|
...(handle.resultFull !== undefined ? { finalOutputFull: handle.resultFull } : {}),
|
|
1128
1196
|
...(handle.resultIsPartial ? { resultIsPartial: true } : {}),
|
|
@@ -1195,6 +1263,7 @@ export class TaskRegistry {
|
|
|
1195
1263
|
handle.status = "killed";
|
|
1196
1264
|
handle.stoppedBy = handle.stopSource ?? source;
|
|
1197
1265
|
handle.updatedAt = Date.now();
|
|
1266
|
+
mintCompletionId(handle);
|
|
1198
1267
|
if (handle.outputFile !== undefined) {
|
|
1199
1268
|
try {
|
|
1200
1269
|
const drained = await handle.env.pollBackground(handle.shellId);
|
|
@@ -1221,6 +1290,7 @@ export class TaskRegistry {
|
|
|
1221
1290
|
stoppedBy: handle.stoppedBy,
|
|
1222
1291
|
summary: `${(handle.description ?? "background command").slice(0, 200)} — killed before completion (${clause(handle.stoppedBy)})${killFailed ? ` — kill attempt reported ${kill.ok ? "" : kill.error.code}: the process may still be terminating; the environment teardown retries` : ""}${bashMirrorGapNote(handle)}`,
|
|
1223
1292
|
...(resultText.length > 0 ? { result: resultText, partial: true } : {}),
|
|
1293
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1224
1294
|
}));
|
|
1225
1295
|
}
|
|
1226
1296
|
else {
|
|
@@ -1235,6 +1305,7 @@ export class TaskRegistry {
|
|
|
1235
1305
|
handle.status = "killed";
|
|
1236
1306
|
handle.stoppedBy = handle.stopSource ?? source;
|
|
1237
1307
|
handle.updatedAt = Date.now();
|
|
1308
|
+
mintCompletionId(handle);
|
|
1238
1309
|
const lastLines = handle.lineBuf !== "" ? [handle.lineBuf] : [];
|
|
1239
1310
|
handle.lineBuf = "";
|
|
1240
1311
|
this.notifyTerminalOnce(handle, () => handle.onEvent?.({
|
|
@@ -1245,6 +1316,7 @@ export class TaskRegistry {
|
|
|
1245
1316
|
stoppedBy: handle.stoppedBy,
|
|
1246
1317
|
summary: `${(handle.description ?? "monitor").slice(0, 200)} — killed before completion (${clause(handle.stoppedBy)}); watch ended.${killFailed ? ` Kill attempt reported ${kill.ok ? "" : kill.error.code}: the process may still be terminating; the environment teardown retries.` : ""}`,
|
|
1247
1318
|
...(lastLines.length > 0 ? { lines: lastLines, result: lastLines.join("\n"), partial: true } : {}),
|
|
1319
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1248
1320
|
}));
|
|
1249
1321
|
}
|
|
1250
1322
|
settled++;
|
|
@@ -1270,6 +1342,7 @@ export class TaskRegistry {
|
|
|
1270
1342
|
handle.status = "killed";
|
|
1271
1343
|
handle.stoppedBy = handle.stopSource ?? "system";
|
|
1272
1344
|
handle.updatedAt = Date.now();
|
|
1345
|
+
mintCompletionId(handle);
|
|
1273
1346
|
reaped++;
|
|
1274
1347
|
continue;
|
|
1275
1348
|
}
|
|
@@ -1286,6 +1359,7 @@ export class TaskRegistry {
|
|
|
1286
1359
|
handle.status = "killed";
|
|
1287
1360
|
handle.stoppedBy = handle.stopSource ?? "system";
|
|
1288
1361
|
handle.updatedAt = Date.now();
|
|
1362
|
+
mintCompletionId(handle);
|
|
1289
1363
|
reaped++;
|
|
1290
1364
|
continue;
|
|
1291
1365
|
}
|
|
@@ -1442,6 +1516,7 @@ export class TaskRegistry {
|
|
|
1442
1516
|
return;
|
|
1443
1517
|
handle.status = "failed";
|
|
1444
1518
|
handle.updatedAt = Date.now();
|
|
1519
|
+
mintCompletionId(handle);
|
|
1445
1520
|
stop();
|
|
1446
1521
|
const lostResult = bashTerminalResult(handle.spool);
|
|
1447
1522
|
this.notifyTerminalOnce(handle, () => onTerminal({
|
|
@@ -1452,11 +1527,13 @@ export class TaskRegistry {
|
|
|
1452
1527
|
status: "failed",
|
|
1453
1528
|
summary: `${(handle.description ?? "background command").slice(0, 200)} — lost its background process (${r.error.message}); no further output will arrive.${bashMirrorGapNote(handle)}`,
|
|
1454
1529
|
...(lostResult.length > 0 ? { result: lostResult } : {}),
|
|
1530
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1455
1531
|
}));
|
|
1456
1532
|
return;
|
|
1457
1533
|
}
|
|
1458
1534
|
pollFailures = 0;
|
|
1459
1535
|
const spool = handle.spool;
|
|
1536
|
+
accountDroppedBytes(spool, r.value);
|
|
1460
1537
|
spool.stdout = roll(spool.stdout + r.value.stdout);
|
|
1461
1538
|
spool.stderr = roll(spool.stderr + r.value.stderr);
|
|
1462
1539
|
if (handle.outputFile !== undefined && (r.value.stdout.length > 0 || r.value.stderr.length > 0)) {
|
|
@@ -1470,6 +1547,7 @@ export class TaskRegistry {
|
|
|
1470
1547
|
}
|
|
1471
1548
|
handle.updatedAt = Date.now();
|
|
1472
1549
|
if (r.value.status !== "running") {
|
|
1550
|
+
mintCompletionId(handle);
|
|
1473
1551
|
spool.exitCode = r.value.exitCode;
|
|
1474
1552
|
if (r.value.timedOut === true) {
|
|
1475
1553
|
spool.timedOut = true;
|
|
@@ -1496,9 +1574,12 @@ export class TaskRegistry {
|
|
|
1496
1574
|
? r.value.timeoutSec !== undefined
|
|
1497
1575
|
? `${desc} — timed out after ${r.value.timeoutSec}s (its own background time budget); the process was killed`
|
|
1498
1576
|
: `${desc} — timed out (its own background time budget); the process was killed`
|
|
1499
|
-
: `${desc} — ${r.value.status}`) +
|
|
1577
|
+
: `${desc} — ${r.value.status}`) +
|
|
1578
|
+
bashMirrorGapNote(handle) +
|
|
1579
|
+
droppedGapNote(spool),
|
|
1500
1580
|
...(resultText.length > 0 ? { result: resultText } : {}),
|
|
1501
1581
|
...(handle.status === "killed" && resultText.length > 0 ? { partial: true } : {}),
|
|
1582
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1502
1583
|
}));
|
|
1503
1584
|
}
|
|
1504
1585
|
}
|
|
@@ -1509,6 +1590,7 @@ export class TaskRegistry {
|
|
|
1509
1590
|
return;
|
|
1510
1591
|
handle.status = "failed";
|
|
1511
1592
|
handle.updatedAt = Date.now();
|
|
1593
|
+
mintCompletionId(handle);
|
|
1512
1594
|
stop();
|
|
1513
1595
|
const crashedResult = bashTerminalResult(handle.spool);
|
|
1514
1596
|
this.notifyTerminalOnce(handle, () => onTerminal({
|
|
@@ -1519,6 +1601,7 @@ export class TaskRegistry {
|
|
|
1519
1601
|
status: "failed",
|
|
1520
1602
|
summary: `${(handle.description ?? "background command").slice(0, 200)} — lost its background process (poll failed: ${e instanceof Error ? e.message : String(e)}); no further output will arrive.${bashMirrorGapNote(handle)}`,
|
|
1521
1603
|
...(crashedResult.length > 0 ? { result: crashedResult } : {}),
|
|
1604
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1522
1605
|
}));
|
|
1523
1606
|
}
|
|
1524
1607
|
finally {
|
|
@@ -1592,19 +1675,29 @@ export class TaskRegistry {
|
|
|
1592
1675
|
this.startMonitorWatcher(id);
|
|
1593
1676
|
return id;
|
|
1594
1677
|
}
|
|
1678
|
+
absorbMonitorPoll(handle, v) {
|
|
1679
|
+
const spool = handle.spool;
|
|
1680
|
+
accountDroppedBytes(spool, v);
|
|
1681
|
+
const ROLL_CAP = 2 * TASK_OUTPUT_MAX_CHARS;
|
|
1682
|
+
spool.stdout = rollSpoolText(spool, spool.stdout + v.stdout, ROLL_CAP);
|
|
1683
|
+
spool.stderr = rollSpoolText(spool, spool.stderr + v.stderr, ROLL_CAP);
|
|
1684
|
+
handle.lineBuf += v.stdout;
|
|
1685
|
+
const parts = handle.lineBuf.split(/\r?\n/);
|
|
1686
|
+
handle.lineBuf = parts.pop() ?? "";
|
|
1687
|
+
return parts.filter((l) => l !== "");
|
|
1688
|
+
}
|
|
1689
|
+
emitMonitorEvent(handle, n) {
|
|
1690
|
+
try {
|
|
1691
|
+
handle.onEvent?.(n);
|
|
1692
|
+
}
|
|
1693
|
+
catch {
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1595
1696
|
startMonitorWatcher(id) {
|
|
1596
1697
|
const handle = this.handles.get(id);
|
|
1597
1698
|
if (!handle || handle.type !== "monitor")
|
|
1598
1699
|
return;
|
|
1599
1700
|
const timers = handle.timers;
|
|
1600
|
-
const ROLL_CAP = 2 * TASK_OUTPUT_MAX_CHARS;
|
|
1601
|
-
const roll = (s) => {
|
|
1602
|
-
if (s.length <= ROLL_CAP)
|
|
1603
|
-
return s;
|
|
1604
|
-
const half = Math.floor(ROLL_CAP / 2);
|
|
1605
|
-
handle.spool.rolledChars += s.length - 2 * half;
|
|
1606
|
-
return s.slice(0, half) + s.slice(s.length - half);
|
|
1607
|
-
};
|
|
1608
1701
|
let ticking = false;
|
|
1609
1702
|
const MAX_RETRYABLE_POLL_FAILURES = 3;
|
|
1610
1703
|
let pollFailures = 0;
|
|
@@ -1614,14 +1707,7 @@ export class TaskRegistry {
|
|
|
1614
1707
|
handle.watcher = undefined;
|
|
1615
1708
|
}
|
|
1616
1709
|
};
|
|
1617
|
-
const emit = (n) =>
|
|
1618
|
-
try {
|
|
1619
|
-
handle.onEvent?.(n);
|
|
1620
|
-
}
|
|
1621
|
-
catch {
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
const clipEvent = (s) => s.length <= MONITOR_EVENT_RESULT_CAP ? s : `${s.slice(0, MONITOR_EVENT_RESULT_CAP)}\n…[+${s.length - MONITOR_EVENT_RESULT_CAP} chars truncated]`;
|
|
1710
|
+
const emit = (n) => this.emitMonitorEvent(handle, n);
|
|
1625
1711
|
const pendingFinalLines = (lines) => {
|
|
1626
1712
|
const out = handle.lineBuf !== "" ? [...lines, handle.lineBuf] : lines;
|
|
1627
1713
|
handle.lineBuf = "";
|
|
@@ -1629,6 +1715,7 @@ export class TaskRegistry {
|
|
|
1629
1715
|
};
|
|
1630
1716
|
const terminal = (status, summary, finalLines) => {
|
|
1631
1717
|
handle.updatedAt = Date.now();
|
|
1718
|
+
mintCompletionId(handle);
|
|
1632
1719
|
stop();
|
|
1633
1720
|
this.notifyTerminalOnce(handle, () => emit({
|
|
1634
1721
|
task_id: id,
|
|
@@ -1636,14 +1723,16 @@ export class TaskRegistry {
|
|
|
1636
1723
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
1637
1724
|
status,
|
|
1638
1725
|
...(status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
1639
|
-
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${summary}`,
|
|
1640
|
-
...(finalLines.length > 0 ? { lines: finalLines, result:
|
|
1726
|
+
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${summary}${droppedGapNote(handle.spool)}`,
|
|
1727
|
+
...(finalLines.length > 0 ? { lines: finalLines, result: clipMonitorEvent(finalLines.join("\n")), ...(status === "killed" ? { partial: true } : {}) } : {}),
|
|
1728
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1641
1729
|
}));
|
|
1642
1730
|
};
|
|
1643
1731
|
const tick = async () => {
|
|
1644
1732
|
if (ticking)
|
|
1645
1733
|
return;
|
|
1646
1734
|
ticking = true;
|
|
1735
|
+
handle.tickInFlight = true;
|
|
1647
1736
|
try {
|
|
1648
1737
|
if (this.handles.get(id) !== handle)
|
|
1649
1738
|
return stop();
|
|
@@ -1660,20 +1749,22 @@ export class TaskRegistry {
|
|
|
1660
1749
|
}
|
|
1661
1750
|
pollFailures = 0;
|
|
1662
1751
|
const spool = handle.spool;
|
|
1663
|
-
|
|
1664
|
-
spool.stderr = roll(spool.stderr + r.value.stderr);
|
|
1665
|
-
handle.lineBuf += r.value.stdout;
|
|
1666
|
-
const parts = handle.lineBuf.split(/\r?\n/);
|
|
1667
|
-
handle.lineBuf = parts.pop() ?? "";
|
|
1668
|
-
const lines = parts.filter((l) => l !== "");
|
|
1752
|
+
const lines = this.absorbMonitorPoll(handle, r.value);
|
|
1669
1753
|
handle.status = statusFromBackground(r.value.status, r.value.exitCode);
|
|
1670
1754
|
if (r.value.status !== "running") {
|
|
1671
1755
|
spool.exitCode = r.value.exitCode;
|
|
1672
1756
|
if (handle.status === "killed" && handle.stoppedBy === undefined)
|
|
1673
|
-
handle.stoppedBy = handle.stopSource ?? "system";
|
|
1757
|
+
handle.stoppedBy = handle.stopSource ?? (r.value.timedOut === true ? "timeout" : "system");
|
|
1758
|
+
if (r.value.timedOut === true) {
|
|
1759
|
+
spool.timedOut = true;
|
|
1760
|
+
if (r.value.timeoutSec !== undefined)
|
|
1761
|
+
spool.timeoutSec = r.value.timeoutSec;
|
|
1762
|
+
}
|
|
1674
1763
|
const label = r.value.status === "exited"
|
|
1675
1764
|
? `exited(code ${r.value.exitCode}); watch ended.`
|
|
1676
|
-
:
|
|
1765
|
+
: r.value.timedOut === true
|
|
1766
|
+
? `timed out after ${r.value.timeoutSec !== undefined ? `${r.value.timeoutSec}s` : "its background time budget"} (the execution environment's background time budget); the process was killed and the watch ended.`
|
|
1767
|
+
: `${r.value.status}; watch ended.`;
|
|
1677
1768
|
return terminal(handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed", label, pendingFinalLines(lines));
|
|
1678
1769
|
}
|
|
1679
1770
|
const now = timers.now();
|
|
@@ -1721,8 +1812,8 @@ export class TaskRegistry {
|
|
|
1721
1812
|
status: "event",
|
|
1722
1813
|
seq: handle.seq,
|
|
1723
1814
|
lines,
|
|
1724
|
-
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${lines.length} new output line(s)`,
|
|
1725
|
-
result:
|
|
1815
|
+
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${lines.length} new output line(s)${firstDropNote(spool)}`,
|
|
1816
|
+
result: clipMonitorEvent(lines.join("\n")),
|
|
1726
1817
|
});
|
|
1727
1818
|
}
|
|
1728
1819
|
catch (e) {
|
|
@@ -1735,6 +1826,7 @@ export class TaskRegistry {
|
|
|
1735
1826
|
}
|
|
1736
1827
|
finally {
|
|
1737
1828
|
ticking = false;
|
|
1829
|
+
handle.tickInFlight = undefined;
|
|
1738
1830
|
}
|
|
1739
1831
|
};
|
|
1740
1832
|
handle.watcher = timers.setInterval(tick, handle.batchWindowMs);
|
|
@@ -1769,11 +1861,18 @@ export class TaskRegistry {
|
|
|
1769
1861
|
const r = run?.resultFull ?? run?.result;
|
|
1770
1862
|
if (typeof r === "string")
|
|
1771
1863
|
handle.result = r;
|
|
1864
|
+
if (run?.completionId !== undefined)
|
|
1865
|
+
handle.completionId = run.completionId;
|
|
1772
1866
|
handle.updatedAt = Date.now();
|
|
1773
1867
|
}, (err) => {
|
|
1774
1868
|
if (handle.status !== "cancelled")
|
|
1775
1869
|
handle.status = "failed";
|
|
1776
1870
|
handle.error = boundedRedactedSummary(err instanceof Error ? err.message : String(err), 500);
|
|
1871
|
+
const rejectedRun = err !== null && (typeof err === "object" || typeof err === "function")
|
|
1872
|
+
? err.workflowRun
|
|
1873
|
+
: undefined;
|
|
1874
|
+
if (rejectedRun?.completionId !== undefined)
|
|
1875
|
+
handle.completionId = rejectedRun.completionId;
|
|
1777
1876
|
handle.updatedAt = Date.now();
|
|
1778
1877
|
});
|
|
1779
1878
|
return id;
|
|
@@ -1825,6 +1924,7 @@ export class TaskRegistry {
|
|
|
1825
1924
|
The durable record is still marked running (last update ${new Date(row.updatedAt).toISOString()}), but this engine instance is not executing it — the outcome is unknown here. If its host process is gone, a retention sweep (staleRunningMaxAgeMs) settles the record as interrupted.`;
|
|
1826
1925
|
return {
|
|
1827
1926
|
content: delimitUntrusted(`TaskOutput ${row.handle}`, body),
|
|
1927
|
+
isError: true,
|
|
1828
1928
|
details: {
|
|
1829
1929
|
task_id: row.handle,
|
|
1830
1930
|
type: "background_agent",
|
|
@@ -1867,6 +1967,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1867
1967
|
...(row.status === "killed" && row.stoppedBy !== undefined ? { stoppedBy: row.stoppedBy } : {}),
|
|
1868
1968
|
...(row.seq !== undefined ? { seq: row.seq } : {}),
|
|
1869
1969
|
...(row.resultIsPartial ? { partial_result: true } : {}),
|
|
1970
|
+
...(row.completionId !== undefined ? { completionId: row.completionId } : {}),
|
|
1870
1971
|
},
|
|
1871
1972
|
};
|
|
1872
1973
|
}
|
|
@@ -1917,7 +2018,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1917
2018
|
? `${row.handle} is not attached to this engine instance; cannot stop it here. If its host process is gone, a retention sweep (staleRunningMaxAgeMs) settles it as interrupted.`
|
|
1918
2019
|
: row.status === "parked"
|
|
1919
2020
|
? `${row.handle} is parked on a pending approval — stop it by deciding (deny/expire) the pending approval in the durable approval inbox.`
|
|
1920
|
-
:
|
|
2021
|
+
: alreadyTerminalStopNote(row.handle, row.status),
|
|
1921
2022
|
details: {
|
|
1922
2023
|
task_id: row.handle,
|
|
1923
2024
|
type: "background_agent",
|
|
@@ -1929,13 +2030,14 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1929
2030
|
};
|
|
1930
2031
|
}
|
|
1931
2032
|
}
|
|
1932
|
-
const byName = this.resolveBackgroundAgentByName(taskId, access);
|
|
2033
|
+
const byName = this.resolveBackgroundAgentByName(taskId, access, { preferRunning: true });
|
|
1933
2034
|
if (byName.status === "found")
|
|
1934
2035
|
return this.stopBackgroundAgent(byName.handle);
|
|
1935
2036
|
if (byName.status === "ambiguous") {
|
|
1936
2037
|
return {
|
|
1937
2038
|
content: byName.message,
|
|
1938
2039
|
details: { task_id: taskId, retrieval_status: "not_ready", error: "ambiguous" },
|
|
2040
|
+
isError: true,
|
|
1939
2041
|
};
|
|
1940
2042
|
}
|
|
1941
2043
|
return noTaskForStop(taskId, {
|
|
@@ -1943,7 +2045,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1943
2045
|
runningAgents: this.runningBackgroundAgentLabels(access),
|
|
1944
2046
|
});
|
|
1945
2047
|
}
|
|
1946
|
-
resolveBackgroundAgentByName(name, access) {
|
|
2048
|
+
resolveBackgroundAgentByName(name, access, opts) {
|
|
1947
2049
|
const key = normalizeAgentName(name);
|
|
1948
2050
|
if (key === "")
|
|
1949
2051
|
return { status: "not_found" };
|
|
@@ -1961,6 +2063,17 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1961
2063
|
if (h.name === name || normalizeAgentName(h.name) === key)
|
|
1962
2064
|
named.push(h);
|
|
1963
2065
|
}
|
|
2066
|
+
if (opts?.preferRunning === true) {
|
|
2067
|
+
const namedRunning = named.filter((h) => h.status === "running");
|
|
2068
|
+
if (namedRunning.length > 1) {
|
|
2069
|
+
return {
|
|
2070
|
+
status: "ambiguous",
|
|
2071
|
+
message: `Multiple RUNNING background agents match "${name}": ${namedRunning.map((h) => `${h.id} (${h.name ?? h.description ?? "unnamed"})`).join(", ")}. Use the task ID.`,
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
if (namedRunning.length === 1)
|
|
2075
|
+
return { status: "found", handle: namedRunning[0] };
|
|
2076
|
+
}
|
|
1964
2077
|
const namedWinner = latestOf(named);
|
|
1965
2078
|
if (namedWinner !== undefined)
|
|
1966
2079
|
return { status: "found", handle: namedWinner };
|
|
@@ -2038,10 +2151,11 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2038
2151
|
handle.error = undefined;
|
|
2039
2152
|
handle.resultIsPartial = undefined;
|
|
2040
2153
|
handle.stopSource = undefined;
|
|
2154
|
+
handle.completionId = undefined;
|
|
2041
2155
|
handle.reviveCycle = (handle.reviveCycle ?? 0) + 1;
|
|
2042
2156
|
handle.cycleSeq = (handle.cycleSeq ?? 1) + 1;
|
|
2043
2157
|
handle.updatedAt = Date.now();
|
|
2044
|
-
this.durableAgentWrite(handle, { status: "running" }, ["settledAt", "stoppedBy", "finalOutput", "finalOutputFull", "error", "resultIsPartial", "summary", "recentSteps", "editedFiles", "usage"]);
|
|
2158
|
+
this.durableAgentWrite(handle, { status: "running" }, ["settledAt", "stoppedBy", "finalOutput", "finalOutputFull", "error", "resultIsPartial", "completionId", "summary", "recentSteps", "editedFiles", "usage"]);
|
|
2045
2159
|
return { ok: true, cycle: handle.reviveCycle };
|
|
2046
2160
|
}
|
|
2047
2161
|
settleRevivedAgent(id, cycle, outcome) {
|
|
@@ -2277,6 +2391,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2277
2391
|
return {
|
|
2278
2392
|
content: `Error (TaskOutput): invalid filter regex: ${filter}`,
|
|
2279
2393
|
details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status: "not_ready" },
|
|
2394
|
+
isError: true,
|
|
2280
2395
|
};
|
|
2281
2396
|
}
|
|
2282
2397
|
}
|
|
@@ -2293,7 +2408,13 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2293
2408
|
const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
|
|
2294
2409
|
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : running ? "running" : handle.status) + timedOutClause;
|
|
2295
2410
|
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)\n` : "";
|
|
2296
|
-
const
|
|
2411
|
+
const droppedNote = (spool.droppedBytes ?? 0) > 0
|
|
2412
|
+
? `[!] ${spool.droppedBytes} earlier byte(s) permanently dropped before this window (8MB buffer evicted)\n`
|
|
2413
|
+
: spool.dropUnknown === true
|
|
2414
|
+
? "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n"
|
|
2415
|
+
: "";
|
|
2416
|
+
const stdoutHeading = droppedNote !== "" ? "stdout (re-readable spool — INCOMPLETE, see note above)" : "stdout (full, re-readable)";
|
|
2417
|
+
const body = `status: ${displayStatus}\n${rolledNote}${droppedNote}--- ${stdoutHeading} ---\n${shape(spool.stdout) || "(none)"}\n` +
|
|
2297
2418
|
`--- stderr ---\n${shape(spool.stderr) || "(none)"}`;
|
|
2298
2419
|
return {
|
|
2299
2420
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
@@ -2303,9 +2424,11 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2303
2424
|
status: handle.status,
|
|
2304
2425
|
retrieval_status: retrieval,
|
|
2305
2426
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2427
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2306
2428
|
details: {
|
|
2307
2429
|
status: running ? "running" : handle.status,
|
|
2308
2430
|
exitCode: spool.exitCode,
|
|
2431
|
+
...(spool.droppedBytes !== undefined ? { bytesDroppedBeforeCursor: spool.droppedBytes } : {}),
|
|
2309
2432
|
...(spool.timedOut === true ? { timedOut: true, ...(spool.timeoutSec !== undefined ? { timeoutSec: spool.timeoutSec } : {}) } : {}),
|
|
2310
2433
|
},
|
|
2311
2434
|
},
|
|
@@ -2335,6 +2458,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2335
2458
|
return {
|
|
2336
2459
|
content: `Error (TaskOutput): ${r.error.message}${salvaged}`,
|
|
2337
2460
|
details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status, error: r.error.code },
|
|
2461
|
+
isError: true,
|
|
2338
2462
|
};
|
|
2339
2463
|
}
|
|
2340
2464
|
stdout = roll(stdout + r.value.stdout);
|
|
@@ -2345,6 +2469,8 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2345
2469
|
handle.stoppedBy = handle.stopSource ?? (r.value.timedOut === true ? "timeout" : "system");
|
|
2346
2470
|
handle.updatedAt = Date.now();
|
|
2347
2471
|
const running = r.value.status === "running";
|
|
2472
|
+
if (!running)
|
|
2473
|
+
mintCompletionId(handle);
|
|
2348
2474
|
if (!running || deadline === undefined || Date.now() >= deadline || signal?.aborted) {
|
|
2349
2475
|
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
2350
2476
|
const shape = (text) => clipTaskOutput(rx ? text.split(/\r?\n/).filter((l) => rx.test(l)).join("\n") : text, handle.outputFile);
|
|
@@ -2362,6 +2488,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2362
2488
|
status: handle.status,
|
|
2363
2489
|
retrieval_status: retrieval,
|
|
2364
2490
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2491
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2365
2492
|
details: {
|
|
2366
2493
|
status: r.value.status,
|
|
2367
2494
|
exitCode: r.value.exitCode,
|
|
@@ -2435,10 +2562,12 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2435
2562
|
type: "workflow",
|
|
2436
2563
|
status: handle.status,
|
|
2437
2564
|
retrieval_status: handle.status === "running" && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success",
|
|
2565
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2438
2566
|
},
|
|
2439
2567
|
};
|
|
2440
2568
|
}
|
|
2441
2569
|
async stopBackgroundBash(handle) {
|
|
2570
|
+
const wasRunning = handle.status === "running";
|
|
2442
2571
|
if (handle.watcher !== undefined) {
|
|
2443
2572
|
clearInterval(handle.watcher);
|
|
2444
2573
|
handle.watcher = undefined;
|
|
@@ -2447,8 +2576,15 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2447
2576
|
const r = await handle.env.killBackground(handle.shellId);
|
|
2448
2577
|
if (handle.status !== "running") {
|
|
2449
2578
|
return {
|
|
2450
|
-
content: `Terminated ${handle.id}
|
|
2451
|
-
details: {
|
|
2579
|
+
content: wasRunning ? `Terminated ${handle.id}.` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
2580
|
+
details: {
|
|
2581
|
+
task_id: handle.id,
|
|
2582
|
+
type: "background_bash",
|
|
2583
|
+
status: handle.status,
|
|
2584
|
+
retrieval_status: "success",
|
|
2585
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2586
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2587
|
+
},
|
|
2452
2588
|
};
|
|
2453
2589
|
}
|
|
2454
2590
|
if (!r.ok) {
|
|
@@ -2457,10 +2593,18 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2457
2593
|
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2458
2594
|
handle.status = "killed";
|
|
2459
2595
|
handle.updatedAt = Date.now();
|
|
2596
|
+
mintCompletionId(handle);
|
|
2460
2597
|
this.pokeBgQuiescence(handle.owner);
|
|
2461
2598
|
return {
|
|
2462
2599
|
content: `Terminated ${handle.id} (the process was already gone).`,
|
|
2463
|
-
details: {
|
|
2600
|
+
details: {
|
|
2601
|
+
task_id: handle.id,
|
|
2602
|
+
type: "background_bash",
|
|
2603
|
+
status: "killed",
|
|
2604
|
+
retrieval_status: "success",
|
|
2605
|
+
stoppedBy: handle.stoppedBy,
|
|
2606
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2607
|
+
},
|
|
2464
2608
|
};
|
|
2465
2609
|
}
|
|
2466
2610
|
const restored = handle.onTerminal !== undefined;
|
|
@@ -2471,16 +2615,25 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2471
2615
|
content: `Error (TaskStop): ${r.error.message} — the kill did not land; the process may still be running.` +
|
|
2472
2616
|
(restored ? " Its watcher keeps running: TaskOutput still works and the completion notification will still fire." : " TaskOutput still reflects its live status."),
|
|
2473
2617
|
details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status, error: r.error.code },
|
|
2618
|
+
isError: true,
|
|
2474
2619
|
};
|
|
2475
2620
|
}
|
|
2476
2621
|
if (handle.stoppedBy === undefined)
|
|
2477
2622
|
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2478
2623
|
handle.status = "killed";
|
|
2479
2624
|
handle.updatedAt = Date.now();
|
|
2625
|
+
mintCompletionId(handle);
|
|
2480
2626
|
this.pokeBgQuiescence(handle.owner);
|
|
2481
2627
|
return {
|
|
2482
2628
|
content: `Terminated ${handle.id}.`,
|
|
2483
|
-
details: {
|
|
2629
|
+
details: {
|
|
2630
|
+
task_id: handle.id,
|
|
2631
|
+
type: "background_bash",
|
|
2632
|
+
status: "killed",
|
|
2633
|
+
retrieval_status: "success",
|
|
2634
|
+
stoppedBy: handle.stoppedBy,
|
|
2635
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2636
|
+
},
|
|
2484
2637
|
};
|
|
2485
2638
|
}
|
|
2486
2639
|
async pollMonitor(handle, filter, deadline, signal) {
|
|
@@ -2493,6 +2646,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2493
2646
|
return {
|
|
2494
2647
|
content: `Error (TaskOutput): invalid filter regex: ${filter}`,
|
|
2495
2648
|
details: { task_id: handle.id, type: "monitor", status: handle.status, retrieval_status: "not_ready" },
|
|
2649
|
+
isError: true,
|
|
2496
2650
|
};
|
|
2497
2651
|
}
|
|
2498
2652
|
}
|
|
@@ -2503,9 +2657,17 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2503
2657
|
const shape = (text) => clipTaskOutput(rx ? text.split(/\r?\n/).filter((l) => rx.test(l)).join("\n") : text, handle.outputFile);
|
|
2504
2658
|
const running = handle.status === "running";
|
|
2505
2659
|
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
2506
|
-
const
|
|
2660
|
+
const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
|
|
2661
|
+
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : running ? "running" : handle.status) + timedOutClause;
|
|
2507
2662
|
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)\n` : "";
|
|
2508
|
-
const
|
|
2663
|
+
const droppedNote = (spool.droppedBytes ?? 0) > 0
|
|
2664
|
+
? `[!] ${spool.droppedBytes} earlier byte(s) permanently dropped before this window (8MB buffer evicted)\n`
|
|
2665
|
+
: spool.dropUnknown === true
|
|
2666
|
+
? "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n"
|
|
2667
|
+
: "";
|
|
2668
|
+
const drainNote = spool.drainFailed === true ? "[!] the final output drain at stop failed — the tail of the output may be missing\n" : "";
|
|
2669
|
+
const stdoutHeading = droppedNote !== "" || drainNote !== "" ? "stdout (re-readable spool — INCOMPLETE, see note above)" : "stdout (full, re-readable)";
|
|
2670
|
+
const body = `status: ${displayStatus}\n${rolledNote}${droppedNote}${drainNote}--- ${stdoutHeading} ---\n${shape(spool.stdout) || "(none)"}\n` +
|
|
2509
2671
|
`--- stderr ---\n${shape(spool.stderr) || "(none)"}`;
|
|
2510
2672
|
return {
|
|
2511
2673
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
@@ -2515,21 +2677,66 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2515
2677
|
status: handle.status,
|
|
2516
2678
|
retrieval_status: retrieval,
|
|
2517
2679
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2518
|
-
|
|
2680
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2681
|
+
details: {
|
|
2682
|
+
status: running ? "running" : handle.status,
|
|
2683
|
+
exitCode: spool.exitCode,
|
|
2684
|
+
...(spool.droppedBytes !== undefined ? { bytesDroppedBeforeCursor: spool.droppedBytes } : {}),
|
|
2685
|
+
...(spool.timedOut === true ? { timedOut: true, ...(spool.timeoutSec !== undefined ? { timeoutSec: spool.timeoutSec } : {}) } : {}),
|
|
2686
|
+
},
|
|
2519
2687
|
},
|
|
2520
2688
|
};
|
|
2521
2689
|
}
|
|
2522
2690
|
async stopMonitor(handle) {
|
|
2691
|
+
const wasRunning = handle.status === "running";
|
|
2523
2692
|
if (handle.watcher !== undefined) {
|
|
2524
2693
|
handle.timers.clearInterval(handle.watcher);
|
|
2525
2694
|
handle.watcher = undefined;
|
|
2526
2695
|
}
|
|
2696
|
+
if (wasRunning && handle.tickInFlight !== true) {
|
|
2697
|
+
try {
|
|
2698
|
+
const drained = await handle.env.pollBackground(handle.shellId);
|
|
2699
|
+
if (drained.ok) {
|
|
2700
|
+
const lines = this.absorbMonitorPoll(handle, drained.value);
|
|
2701
|
+
const finalLines = handle.lineBuf !== "" ? [...lines, handle.lineBuf] : lines;
|
|
2702
|
+
handle.lineBuf = "";
|
|
2703
|
+
if (finalLines.length > 0 && handle.status === "running") {
|
|
2704
|
+
handle.seq += 1;
|
|
2705
|
+
handle.updatedAt = Date.now();
|
|
2706
|
+
this.emitMonitorEvent(handle, {
|
|
2707
|
+
task_id: handle.id,
|
|
2708
|
+
task_type: "monitor",
|
|
2709
|
+
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
2710
|
+
status: "event",
|
|
2711
|
+
seq: handle.seq,
|
|
2712
|
+
lines: finalLines,
|
|
2713
|
+
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${finalLines.length} final output line(s) drained at stop${firstDropNote(handle.spool)}`,
|
|
2714
|
+
result: clipMonitorEvent(finalLines.join("\n")),
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
else {
|
|
2719
|
+
handle.spool.drainFailed = true;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
catch {
|
|
2723
|
+
handle.spool.drainFailed = true;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
const drainNote = handle.spool.drainFailed === true ? " (the final output drain failed — TaskOutput may be missing the last window)" : "";
|
|
2527
2727
|
this.markStopSource(handle.id, "parent");
|
|
2528
2728
|
const r = await handle.env.killBackground(handle.shellId);
|
|
2529
2729
|
if (handle.status !== "running") {
|
|
2530
2730
|
return {
|
|
2531
|
-
content: `Terminated ${handle.id}
|
|
2532
|
-
details: {
|
|
2731
|
+
content: wasRunning ? `Terminated ${handle.id}.${drainNote}` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
2732
|
+
details: {
|
|
2733
|
+
task_id: handle.id,
|
|
2734
|
+
type: "monitor",
|
|
2735
|
+
status: handle.status,
|
|
2736
|
+
retrieval_status: "success",
|
|
2737
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2738
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2739
|
+
},
|
|
2533
2740
|
};
|
|
2534
2741
|
}
|
|
2535
2742
|
if (!r.ok) {
|
|
@@ -2538,10 +2745,18 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2538
2745
|
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2539
2746
|
handle.status = "killed";
|
|
2540
2747
|
handle.updatedAt = Date.now();
|
|
2748
|
+
mintCompletionId(handle);
|
|
2541
2749
|
this.pokeBgQuiescence(handle.owner);
|
|
2542
2750
|
return {
|
|
2543
|
-
content: `Terminated ${handle.id} (the process was already gone)
|
|
2544
|
-
details: {
|
|
2751
|
+
content: `Terminated ${handle.id} (the process was already gone).${drainNote}`,
|
|
2752
|
+
details: {
|
|
2753
|
+
task_id: handle.id,
|
|
2754
|
+
type: "monitor",
|
|
2755
|
+
status: "killed",
|
|
2756
|
+
retrieval_status: "success",
|
|
2757
|
+
stoppedBy: handle.stoppedBy,
|
|
2758
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2759
|
+
},
|
|
2545
2760
|
};
|
|
2546
2761
|
}
|
|
2547
2762
|
if (handle.watcher === undefined)
|
|
@@ -2549,6 +2764,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2549
2764
|
const retrieval_status = r.error.code === "timeout" ? "timeout" : "not_ready";
|
|
2550
2765
|
return {
|
|
2551
2766
|
content: `Error (TaskStop): ${r.error.message} — the kill did not land; the process may still be running. The watch keeps running: events and the terminal notification still fire.`,
|
|
2767
|
+
isError: true,
|
|
2552
2768
|
details: { task_id: handle.id, type: "monitor", status: handle.status, retrieval_status, error: r.error.code },
|
|
2553
2769
|
};
|
|
2554
2770
|
}
|
|
@@ -2557,16 +2773,18 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2557
2773
|
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2558
2774
|
handle.status = "killed";
|
|
2559
2775
|
handle.updatedAt = Date.now();
|
|
2776
|
+
mintCompletionId(handle);
|
|
2560
2777
|
this.pokeBgQuiescence(handle.owner);
|
|
2561
2778
|
}
|
|
2562
2779
|
return {
|
|
2563
|
-
content: `Terminated ${handle.id}
|
|
2780
|
+
content: `Terminated ${handle.id}.${drainNote}`,
|
|
2564
2781
|
details: {
|
|
2565
2782
|
task_id: handle.id,
|
|
2566
2783
|
type: "monitor",
|
|
2567
2784
|
status: handle.status,
|
|
2568
2785
|
retrieval_status: "success",
|
|
2569
2786
|
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2787
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2570
2788
|
},
|
|
2571
2789
|
};
|
|
2572
2790
|
}
|
|
@@ -2606,10 +2824,12 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2606
2824
|
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2607
2825
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2608
2826
|
...(handle.resultIsPartial ? { partial_result: true } : {}),
|
|
2827
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2609
2828
|
},
|
|
2610
2829
|
};
|
|
2611
2830
|
}
|
|
2612
2831
|
async stopBackgroundAgent(handle) {
|
|
2832
|
+
const entryStatus = handle.status;
|
|
2613
2833
|
if (handle.status === "parked") {
|
|
2614
2834
|
const arbiter = handle.resolveParkedStop;
|
|
2615
2835
|
if (arbiter === undefined) {
|
|
@@ -2632,6 +2852,7 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2632
2852
|
handle.status = "killed";
|
|
2633
2853
|
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2634
2854
|
handle.updatedAt = Date.now();
|
|
2855
|
+
mintCompletionId(handle);
|
|
2635
2856
|
handle.parkedCheckpointToken = undefined;
|
|
2636
2857
|
handle.resolveParkedStop = undefined;
|
|
2637
2858
|
this.pokeBgQuiescence(handle.owner);
|
|
@@ -2657,7 +2878,12 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2657
2878
|
}
|
|
2658
2879
|
}
|
|
2659
2880
|
}
|
|
2660
|
-
this.durableAgentWrite(handle, {
|
|
2881
|
+
this.durableAgentWrite(handle, {
|
|
2882
|
+
status: "killed",
|
|
2883
|
+
settledAt: Date.now(),
|
|
2884
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2885
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2886
|
+
}, ["parkedCheckpointToken", "parkClaimId"]);
|
|
2661
2887
|
return {
|
|
2662
2888
|
content: `Terminated ${handle.id} (pending approval expired).`,
|
|
2663
2889
|
details: {
|
|
@@ -2667,6 +2893,7 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2667
2893
|
retrieval_status: "success",
|
|
2668
2894
|
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2669
2895
|
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2896
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2670
2897
|
},
|
|
2671
2898
|
};
|
|
2672
2899
|
}
|
|
@@ -2674,11 +2901,13 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2674
2901
|
return {
|
|
2675
2902
|
content: `${handle.id} was not stopped: the approval arbitration store was unreachable — the row stays parked; retry the stop.`,
|
|
2676
2903
|
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_arbiter_unreachable" },
|
|
2904
|
+
isError: true,
|
|
2677
2905
|
};
|
|
2678
2906
|
}
|
|
2679
2907
|
return {
|
|
2680
2908
|
content: `${handle.id} was not stopped: its pending approval was already consumed (a resume won the arbitration).`,
|
|
2681
2909
|
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_resume_won" },
|
|
2910
|
+
isError: true,
|
|
2682
2911
|
};
|
|
2683
2912
|
}
|
|
2684
2913
|
if (handle.status === "running") {
|
|
@@ -2687,6 +2916,7 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2687
2916
|
handle.status = "killed";
|
|
2688
2917
|
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2689
2918
|
handle.updatedAt = Date.now();
|
|
2919
|
+
mintCompletionId(handle);
|
|
2690
2920
|
handle.notify = undefined;
|
|
2691
2921
|
for (const [, , qResolve] of handle.preAttachQueue ?? [])
|
|
2692
2922
|
qResolve?.({ ok: false, reason: "not_running" });
|
|
@@ -2699,10 +2929,11 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2699
2929
|
status: "killed",
|
|
2700
2930
|
settledAt: Date.now(),
|
|
2701
2931
|
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2932
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2702
2933
|
});
|
|
2703
2934
|
}
|
|
2704
2935
|
return {
|
|
2705
|
-
content: `Terminated ${handle.id}
|
|
2936
|
+
content: entryStatus === "running" ? `Terminated ${handle.id}.` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
2706
2937
|
details: {
|
|
2707
2938
|
task_id: handle.id,
|
|
2708
2939
|
type: "background_agent",
|
|
@@ -2710,6 +2941,7 @@ ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no
|
|
|
2710
2941
|
retrieval_status: "success",
|
|
2711
2942
|
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2712
2943
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2944
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2713
2945
|
},
|
|
2714
2946
|
};
|
|
2715
2947
|
}
|
|
@@ -2765,7 +2997,7 @@ export function createTaskOutputTool(opts) {
|
|
|
2765
2997
|
const args = rawArgs;
|
|
2766
2998
|
const id = firstString(args.task_id, args.bash_id, args.runId);
|
|
2767
2999
|
if (!id)
|
|
2768
|
-
return "Error (TaskOutput): Missing required parameter: task_id";
|
|
3000
|
+
return errorResult("Error (TaskOutput): Missing required parameter: task_id");
|
|
2769
3001
|
const requestedMs = typeof args.timeout === "number" ? args.timeout : undefined;
|
|
2770
3002
|
let effectiveTimeoutMs = requestedMs;
|
|
2771
3003
|
let waitClampNote = "";
|
|
@@ -2792,7 +3024,11 @@ export function createTaskOutputTool(opts) {
|
|
|
2792
3024
|
signal: ctx.signal,
|
|
2793
3025
|
});
|
|
2794
3026
|
const { type: taskType, ...rest } = r.details;
|
|
2795
|
-
return {
|
|
3027
|
+
return {
|
|
3028
|
+
content: r.content + waitClampNote,
|
|
3029
|
+
details: { type: "task-output", ...(taskType !== undefined ? { taskType } : {}), ...(waitClampNote !== "" ? { waitClamped: true } : {}), ...rest },
|
|
3030
|
+
...(r.isError ? { isError: true } : {}),
|
|
3031
|
+
};
|
|
2796
3032
|
},
|
|
2797
3033
|
});
|
|
2798
3034
|
}
|
|
@@ -2815,12 +3051,13 @@ export function createTaskStopTool(opts) {
|
|
|
2815
3051
|
const args = rawArgs;
|
|
2816
3052
|
const id = firstString(args.task_id, args.shell_id, args.runId);
|
|
2817
3053
|
if (!id)
|
|
2818
|
-
return "Missing required parameter: task_id";
|
|
3054
|
+
return errorResult("Missing required parameter: task_id");
|
|
2819
3055
|
const r = await opts.registry.stopTask(id, { owner: ctx.taskId ?? opts.owner, scope: ctx.principal ?? opts.scope, ...((ctx.sessionId ?? opts.sessionId) !== undefined ? { sessionId: ctx.sessionId ?? opts.sessionId } : {}) }, { workflowStore: opts.workflowStore, agentStore: opts.agentStore });
|
|
2820
3056
|
const { type: taskType, ...rest } = r.details;
|
|
2821
3057
|
return {
|
|
2822
3058
|
content: r.content,
|
|
2823
3059
|
details: { type: "task-stop", message: r.content, task_type: taskType ?? "unknown", ...rest },
|
|
3060
|
+
...(r.isError ? { isError: true } : {}),
|
|
2824
3061
|
};
|
|
2825
3062
|
},
|
|
2826
3063
|
});
|