@sema-agent/core 1.452.0 → 2.0.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/agent-transcript-tool.d.ts +15 -0
- package/dist/agents/agent-transcript-tool.js +122 -0
- package/dist/agents/cascade.js +5 -8
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/repair-loop.js +1 -1
- package/dist/agents/retain-ledger.d.ts +60 -0
- package/dist/agents/retain-ledger.js +225 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/roster-store.js +9 -0
- package/dist/agents/send-message-tool.d.ts +41 -0
- package/dist/agents/send-message-tool.js +469 -0
- package/dist/agents/subagent.d.ts +33 -108
- package/dist/agents/subagent.js +22 -834
- package/dist/agents/teacher.d.ts +0 -1
- package/dist/agents/teacher.js +0 -9
- package/dist/bin/sema-tb.js +3 -3
- package/dist/brain/anthropic.js +15 -40
- package/dist/brain/openai.js +15 -40
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/stream-engine.js +2 -2
- package/dist/brain/stream-shared.d.ts +17 -0
- package/dist/brain/stream-shared.js +50 -0
- package/dist/brain/timeout.d.ts +1 -1
- package/dist/brain/timeout.js +1 -1
- package/dist/config/catalog.js +1 -3
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +6 -0
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-store.d.ts +0 -1
- package/dist/core/exec-gate.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +1 -0
- package/dist/core/exec-output-tail.js +6 -0
- package/dist/core/file-snapshot-store.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/git-worktree-env.d.ts +1 -1
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/image-downsample.d.ts +44 -0
- package/dist/core/image-downsample.js +97 -0
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp.d.ts +3 -45
- package/dist/core/mcp.js +2 -97
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-recall.d.ts +0 -12
- package/dist/core/memory-recall.js +0 -222
- package/dist/core/memory.d.ts +0 -6
- package/dist/core/memory.js +0 -88
- package/dist/core/message-utils.d.ts +1 -1
- package/dist/core/oracle-isolation.d.ts +1 -1
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/roles.d.ts +1 -1
- package/dist/core/runner/active-skill-scope.d.ts +1 -1
- package/dist/core/runner/assemble-result.d.ts +0 -2
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-memory.d.ts +16 -0
- package/dist/core/runner/prepare-memory.js +191 -0
- package/dist/core/runner/prepare-task.d.ts +4 -28
- package/dist/core/runner/prepare-task.js +19 -207
- package/dist/core/runner/prompt-suggestions.d.ts +1 -1
- package/dist/core/runner/runtask.d.ts +2 -3
- package/dist/core/runner/runtask.js +337 -529
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +5 -5
- package/dist/core/runner/synthetic-tools.js +4 -4
- package/dist/core/runner/tool-disclosure.d.ts +3 -3
- package/dist/core/runner/tool-disclosure.js +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +6 -0
- package/dist/core/runner/tool-output-projection.js +81 -0
- package/dist/core/runtime.d.ts +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-reconcile.d.ts +1 -1
- package/dist/core/session-store.d.ts +2 -1
- package/dist/core/session-store.js +2 -1
- package/dist/core/session.d.ts +0 -1
- package/dist/core/task-registry-agent.d.ts +115 -0
- package/dist/core/task-registry-agent.js +1163 -0
- package/dist/core/task-registry-monitor.d.ts +13 -0
- package/dist/core/task-registry-monitor.js +347 -0
- package/dist/core/task-registry-shared.d.ts +294 -0
- package/dist/core/task-registry-shared.js +271 -0
- package/dist/core/task-registry-workflow.d.ts +5 -0
- package/dist/core/task-registry-workflow.js +143 -0
- package/dist/core/task-registry.d.ts +51 -216
- package/dist/core/task-registry.js +127 -1940
- package/dist/core/task-tool-shape.d.ts +32 -0
- package/dist/core/task-tool-shape.js +83 -0
- package/dist/core/tool-policy.d.ts +1 -2
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tool-result-store.js +1 -1
- package/dist/core/tools.d.ts +2 -3
- package/dist/core/tools.js +0 -8
- package/dist/core/trace.d.ts +2 -2
- package/dist/core/types.d.ts +0 -29
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +19 -19
- package/dist/engine/execution-env/node-execution-env.d.ts +13 -0
- package/dist/engine/execution-env/node-execution-env.js +55 -25
- package/dist/engine/lsp/frame-decoder.d.ts +1 -1
- package/dist/engine/lsp/frame-decoder.js +1 -1
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/lsp/stdio-lsp-transport.js +2 -2
- package/dist/index.d.ts +16 -14
- package/dist/index.js +13 -11
- package/dist/internal/harness-types.d.ts +7 -0
- package/dist/internal/harness-types.js +1 -0
- package/dist/internal/harness.d.ts +1 -7
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/workflow-types.d.ts +193 -0
- package/dist/orchestration/workflow-types.js +54 -0
- package/dist/orchestration/workflow.d.ts +3 -193
- package/dist/orchestration/workflow.js +7 -59
- package/dist/prompt-assembly/assemble.d.ts +3 -3
- package/dist/prompt-assembly/assemble.js +0 -16
- package/dist/prompt-assembly/explain.d.ts +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompts/default.d.ts +1 -6
- package/dist/prompts/default.js +3 -11
- package/dist/scenarios/env.d.ts +1 -1
- package/dist/scenarios/full-body.d.ts +0 -3
- package/dist/scenarios/full-body.js +0 -2
- package/dist/stores/file/background-agent-store.d.ts +2 -15
- package/dist/stores/file/background-agent-store.js +30 -100
- package/dist/stores/file/checkpoint-store.d.ts +1 -14
- package/dist/stores/file/checkpoint-store.js +47 -119
- package/dist/stores/file/file-snapshot-store.d.ts +1 -1
- package/dist/stores/file/shared-ledger.d.ts +35 -0
- package/dist/stores/file/shared-ledger.js +94 -0
- package/dist/stores/file/workflow-run-store.d.ts +1 -14
- package/dist/stores/file/workflow-run-store.js +24 -93
- package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
- package/dist/tools/fs/bash-readonly-classifier.js +166 -0
- package/dist/tools/fs/fs-bash.d.ts +31 -0
- package/dist/tools/fs/fs-bash.js +672 -0
- package/dist/tools/fs/fs-pdf.d.ts +22 -0
- package/dist/tools/fs/fs-pdf.js +236 -0
- package/dist/tools/fs/fs-read.d.ts +8 -0
- package/dist/tools/fs/fs-read.js +286 -0
- package/dist/tools/fs/fs-search-tools.d.ts +5 -0
- package/dist/tools/fs/fs-search-tools.js +173 -0
- package/dist/tools/fs/fs-shared.d.ts +55 -0
- package/dist/tools/fs/fs-shared.js +144 -0
- package/dist/tools/fs/fs-write.d.ts +8 -0
- package/dist/tools/fs/fs-write.js +423 -0
- package/dist/tools/fs/index.d.ts +12 -65
- package/dist/tools/fs/index.js +26 -2090
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/repo-map.d.ts +3 -3
- package/dist/tools/fs/repo-map.js +1 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/search.d.ts +1 -1
- package/dist/tools/monitor.d.ts +1 -1
- package/dist/tools/scheduler-tools.d.ts +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/dist/tools/web.js +1 -1
- package/dist/tools/worktree.d.ts +1 -1
- package/package.json +1 -1
- package/dist/core/dynamic-recall.d.ts +0 -32
- package/dist/core/dynamic-recall.js +0 -76
|
@@ -1,94 +1,29 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { Type } from "typebox";
|
|
3
|
-
import { uuidv7 } from "../internal/harness.js";
|
|
4
2
|
import { defineTool, errorResult } from "./tools.js";
|
|
5
3
|
import { hasBackgroundShell } from "./background-shell.js";
|
|
6
4
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
7
|
-
import {
|
|
8
|
-
import { canAccessAgentRecord, BackgroundAgentStoreError, } from "./background-agent-store.js";
|
|
5
|
+
import { canAccessAgentRecord, } from "./background-agent-store.js";
|
|
9
6
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
function commitCompletionIdIfEmpty(target, value) {
|
|
18
|
-
if (target.completionId === undefined)
|
|
19
|
-
target.completionId = value;
|
|
20
|
-
return target.completionId;
|
|
21
|
-
}
|
|
22
|
-
const defaultMonitorTimers = {
|
|
23
|
-
setInterval: (fn, ms) => {
|
|
24
|
-
const h = setInterval(() => void fn(), ms);
|
|
25
|
-
h.unref?.();
|
|
26
|
-
return h;
|
|
27
|
-
},
|
|
28
|
-
clearInterval: (h) => clearInterval(h),
|
|
29
|
-
now: () => Date.now(),
|
|
30
|
-
};
|
|
7
|
+
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
8
|
+
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
9
|
+
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, droppedGapNote, alreadyTerminalStopNote, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
10
|
+
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
11
|
+
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_ALIASES, TASK_STOP_ALIASES, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
12
|
+
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
13
|
+
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
31
14
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
32
15
|
const BLOCK_MAX_TIMEOUT_MS = 600_000;
|
|
33
|
-
const BLOCK_POLL_INTERVAL_MS = 400;
|
|
34
16
|
function blockDeadline(timeoutMs, now) {
|
|
35
17
|
const t = Math.min(BLOCK_MAX_TIMEOUT_MS, Math.max(1, Math.floor(timeoutMs ?? BLOCK_DEFAULT_TIMEOUT_MS)));
|
|
36
18
|
return now + t;
|
|
37
19
|
}
|
|
38
|
-
const sleepPollStep = (deadline, signal) => sleepMs(deadline === undefined ? BLOCK_POLL_INTERVAL_MS : Math.max(1, Math.min(BLOCK_POLL_INTERVAL_MS, deadline - Date.now())), signal);
|
|
39
|
-
const sleepMs = (ms, signal) => new Promise((resolve) => {
|
|
40
|
-
const t = setTimeout(done, ms);
|
|
41
|
-
function done() {
|
|
42
|
-
clearTimeout(t);
|
|
43
|
-
signal?.removeEventListener("abort", done);
|
|
44
|
-
resolve();
|
|
45
|
-
}
|
|
46
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
47
|
-
});
|
|
48
20
|
const BLOCK_WAIT_WRITEOUT_RESERVE_MS = 5_000;
|
|
49
|
-
export const DURABLE_AGENT_HEARTBEAT_MS = 60_000;
|
|
50
|
-
export const DURABLE_AGENT_HANDLE_RE = /^a[0-9a-f]{6,32}$/;
|
|
51
21
|
const TASK_PREFIX = {
|
|
52
22
|
background_bash: "b",
|
|
53
23
|
workflow: "w",
|
|
54
24
|
background_agent: "a",
|
|
55
25
|
monitor: "m",
|
|
56
26
|
};
|
|
57
|
-
export const BG_AGENT_REAP_STOP_ERROR = "stopped (parent task ended or TaskStop)";
|
|
58
|
-
export const MONITOR_BATCH_WINDOW_MS = 200;
|
|
59
|
-
export const MONITOR_DEFAULT_TIMEOUT_MS = 300_000;
|
|
60
|
-
export const MONITOR_MAX_TIMEOUT_MS = 3_600_000;
|
|
61
|
-
export const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
|
|
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
|
-
}
|
|
66
|
-
const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
|
|
67
|
-
const TASK_OUTPUT_MAX_CHARS = 160_000;
|
|
68
|
-
const TASK_OUTPUT_ALIASES = ["BashOutput", "AgentOutputTool", "BashOutputTool", "AgentOutput", "WorkflowStatus"];
|
|
69
|
-
const TASK_STOP_ALIASES = ["KillShell", "KillBash"];
|
|
70
|
-
const TASK_OUTPUT_MIN_CHARS = 512;
|
|
71
|
-
function taskMaxOutputChars() {
|
|
72
|
-
const raw = process.env.TASK_MAX_OUTPUT_LENGTH;
|
|
73
|
-
if (!raw)
|
|
74
|
-
return TASK_OUTPUT_DEFAULT_CHARS;
|
|
75
|
-
if (!/^\d+$/.test(raw.trim()))
|
|
76
|
-
return TASK_OUTPUT_DEFAULT_CHARS;
|
|
77
|
-
const n = parseInt(raw.trim(), 10);
|
|
78
|
-
if (!Number.isFinite(n) || n <= 0)
|
|
79
|
-
return TASK_OUTPUT_DEFAULT_CHARS;
|
|
80
|
-
return Math.min(Math.max(n, TASK_OUTPUT_MIN_CHARS), TASK_OUTPUT_MAX_CHARS);
|
|
81
|
-
}
|
|
82
|
-
export function clipTaskOutput(s, fullOutputPath) {
|
|
83
|
-
return clipWithFilePointer(s, taskMaxOutputChars(), fullOutputPath);
|
|
84
|
-
}
|
|
85
|
-
function firstString(...values) {
|
|
86
|
-
for (const value of values) {
|
|
87
|
-
if (typeof value === "string" && value.trim() !== "")
|
|
88
|
-
return value.trim();
|
|
89
|
-
}
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
27
|
function noTask(taskId) {
|
|
93
28
|
return {
|
|
94
29
|
content: JSON.stringify({ error: "task not found" }),
|
|
@@ -96,49 +31,6 @@ function noTask(taskId) {
|
|
|
96
31
|
isError: true,
|
|
97
32
|
};
|
|
98
33
|
}
|
|
99
|
-
export function normalizeAgentName(s) {
|
|
100
|
-
return s
|
|
101
|
-
.normalize("NFKC")
|
|
102
|
-
.replace(/[\p{Cc}\p{Cf}]/gu, (t) => (/\s/.test(t) ? t : ""))
|
|
103
|
-
.trim()
|
|
104
|
-
.toLowerCase()
|
|
105
|
-
.replace(/\s+/g, "-");
|
|
106
|
-
}
|
|
107
|
-
function editDistance(a, b) {
|
|
108
|
-
if (a === b)
|
|
109
|
-
return 0;
|
|
110
|
-
const m = a.length;
|
|
111
|
-
const n = b.length;
|
|
112
|
-
const d = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
|
|
113
|
-
for (let i = 1; i <= m; i++) {
|
|
114
|
-
for (let j = 1; j <= n; j++) {
|
|
115
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
116
|
-
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
117
|
-
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
118
|
-
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return d[m][n];
|
|
123
|
-
}
|
|
124
|
-
function closestName(query, candidates) {
|
|
125
|
-
let best;
|
|
126
|
-
let bestDist = 3;
|
|
127
|
-
const seen = new Set();
|
|
128
|
-
for (const c of candidates) {
|
|
129
|
-
if (seen.has(c))
|
|
130
|
-
continue;
|
|
131
|
-
seen.add(c);
|
|
132
|
-
if (Math.abs(c.length - query.length) > 2)
|
|
133
|
-
continue;
|
|
134
|
-
const dist = editDistance(query, c);
|
|
135
|
-
if (dist <= 2 && dist < bestDist) {
|
|
136
|
-
bestDist = dist;
|
|
137
|
-
best = c;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return best;
|
|
141
|
-
}
|
|
142
34
|
function noTaskForStop(taskId, enrich) {
|
|
143
35
|
let content = `No task found with ID: ${taskId}`;
|
|
144
36
|
if (enrich?.suggestion !== undefined)
|
|
@@ -156,427 +48,120 @@ function bashTerminalResult(spool) {
|
|
|
156
48
|
const err = spool.stderr.slice(-2_000);
|
|
157
49
|
return err.length > 0 ? `${out}${out.length > 0 ? "\n" : ""}--- stderr (tail) ---\n${err}` : out;
|
|
158
50
|
}
|
|
159
|
-
function statusFromBackground(status, exitCode) {
|
|
160
|
-
if (status === "running")
|
|
161
|
-
return "running";
|
|
162
|
-
if (status === "killed")
|
|
163
|
-
return "killed";
|
|
164
|
-
if (status === "failed")
|
|
165
|
-
return "failed";
|
|
166
|
-
if (status === "exited")
|
|
167
|
-
return exitCode === 0 ? "completed" : "failed";
|
|
168
|
-
return "failed";
|
|
169
|
-
}
|
|
170
|
-
function rollSpoolText(spool, s, cap) {
|
|
171
|
-
if (s.length <= cap)
|
|
172
|
-
return s;
|
|
173
|
-
const half = Math.floor(cap / 2);
|
|
174
|
-
spool.rolledChars += s.length - 2 * half;
|
|
175
|
-
return s.slice(0, half) + s.slice(s.length - half);
|
|
176
|
-
}
|
|
177
51
|
function bashMirrorGapNote(handle) {
|
|
178
52
|
return handle.outputFile !== undefined && handle.spool?.mirrorFailed === true
|
|
179
53
|
? " [!] The output file is INCOMPLETE (one or more writes to it failed) — trust the result text / TaskOutput over the file."
|
|
180
54
|
: "";
|
|
181
55
|
}
|
|
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
|
-
}
|
|
207
56
|
function monitorInTimeoutWindow(handle) {
|
|
208
57
|
return handle.status === "running" && handle.deadlineAt !== undefined && handle.timers.now() < handle.deadlineAt;
|
|
209
58
|
}
|
|
210
|
-
function canAccess(handle, access) {
|
|
211
|
-
if (handle.scope === undefined || access.scope === undefined || access.scope !== handle.scope)
|
|
212
|
-
return false;
|
|
213
|
-
if (handle.owner === undefined)
|
|
214
|
-
return false;
|
|
215
|
-
if (access.owner === handle.owner)
|
|
216
|
-
return true;
|
|
217
|
-
if (handle.sessionScoped && access.sessionId !== undefined && access.sessionId === handle.owner)
|
|
218
|
-
return true;
|
|
219
|
-
if (handle.type === "workflow" && access.sessionId !== undefined && handle.originatingSessionId === access.sessionId)
|
|
220
|
-
return true;
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
function assertOwnership(input, site) {
|
|
224
|
-
if (input.owner === undefined || input.scope === undefined) {
|
|
225
|
-
throw new Error(`TaskRegistry.${site}: owner AND scope are both required (access is default-deny on both axes; use scope "default" for a single-tenant deployment)`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
export function canAccessWorkflowRun(run, access) {
|
|
229
|
-
if (access.scope === undefined || run.scope !== access.scope)
|
|
230
|
-
return false;
|
|
231
|
-
if (access.owner !== undefined && run.sourceTaskId !== undefined && run.sourceTaskId === access.owner)
|
|
232
|
-
return true;
|
|
233
|
-
if (access.sessionId !== undefined && run.originatingSessionId !== undefined && run.originatingSessionId === access.sessionId)
|
|
234
|
-
return true;
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
const WORKFLOW_PARTIAL_RESULTS_MAX = 5;
|
|
238
|
-
const WORKFLOW_AGENT_ROWS_MAX = 60;
|
|
239
|
-
function workflowAgentRow(a, ordinal) {
|
|
240
|
-
return {
|
|
241
|
-
ordinal,
|
|
242
|
-
label: a.label,
|
|
243
|
-
...(a.phase !== undefined ? { phase: a.phase } : {}),
|
|
244
|
-
status: a.status,
|
|
245
|
-
...(a.errorCode !== undefined ? { errorCode: a.errorCode } : {}),
|
|
246
|
-
...(a.errorMessage !== undefined
|
|
247
|
-
? { error: delimitUntrusted(`workflow agent ${a.label} error`, boundedRedactedSummary(a.errorMessage, 200)) }
|
|
248
|
-
: {}),
|
|
249
|
-
...(a.startedAt !== undefined && a.endedAt !== undefined ? { elapsedMs: a.endedAt - a.startedAt } : {}),
|
|
250
|
-
...(a.replayed === true ? { replayed: true } : {}),
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
function formatWorkflowRun(run) {
|
|
254
|
-
const summary = summarizeWorkflowRun(run);
|
|
255
|
-
const done = run.agents.filter((a) => a.status === "completed" || a.status === "failed").length;
|
|
256
|
-
const failed = run.agents.filter((a) => a.status === "failed").length;
|
|
257
|
-
const allRows = run.agents.map((a, i) => ({ a, i }));
|
|
258
|
-
const overCap = allRows.length > WORKFLOW_AGENT_ROWS_MAX;
|
|
259
|
-
const keptRows = overCap
|
|
260
|
-
? (() => {
|
|
261
|
-
const urgent = allRows.filter((r) => r.a.status !== "completed");
|
|
262
|
-
const budget = Math.max(0, WORKFLOW_AGENT_ROWS_MAX - urgent.length);
|
|
263
|
-
const fill = allRows.filter((r) => r.a.status === "completed").slice(0, budget);
|
|
264
|
-
return [...urgent, ...fill].sort((x, y) => x.i - y.i);
|
|
265
|
-
})()
|
|
266
|
-
: allRows;
|
|
267
|
-
const agentRows = keptRows.map((r) => workflowAgentRow(r.a, r.i));
|
|
268
|
-
const finishedAgents = run.status === "failed" ? run.agents.filter((a) => a.status === "completed" && a.output !== undefined && a.output !== "") : [];
|
|
269
|
-
return {
|
|
270
|
-
content: JSON.stringify({
|
|
271
|
-
task_id: run.id,
|
|
272
|
-
runId: run.id,
|
|
273
|
-
type: "workflow",
|
|
274
|
-
status: run.status,
|
|
275
|
-
...(summary.name ? { name: summary.name } : {}),
|
|
276
|
-
...(summary.currentPhase ? { currentPhase: summary.currentPhase } : {}),
|
|
277
|
-
agents: { done, total: run.agents.length, failed },
|
|
278
|
-
agent_runs: agentRows,
|
|
279
|
-
...(overCap ? { agent_runs_note: `showing ${agentRows.length} of ${run.agents.length} agent rows (every non-completed row kept; completed rows trimmed).` } : {}),
|
|
280
|
-
startedAt: run.startedAt,
|
|
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 } : {}),
|
|
295
|
-
...(run.status === "failed" && run.error ? { error: delimitUntrusted("workflow error", boundedRedactedSummary(run.error, 500)) } : {}),
|
|
296
|
-
...(run.status === "completed" && run.result !== undefined
|
|
297
|
-
? { result: delimitUntrusted("workflow result", run.resultFull ?? run.result) }
|
|
298
|
-
: {}),
|
|
299
|
-
...(finishedAgents.length > 0
|
|
300
|
-
? {
|
|
301
|
-
partial_results: finishedAgents.slice(0, WORKFLOW_PARTIAL_RESULTS_MAX).map((a) => ({
|
|
302
|
-
agent: a.label,
|
|
303
|
-
output: delimitUntrusted(`workflow agent ${a.label} output`, a.output),
|
|
304
|
-
})),
|
|
305
|
-
...(finishedAgents.length > WORKFLOW_PARTIAL_RESULTS_MAX
|
|
306
|
-
? { partial_results_note: `showing ${WORKFLOW_PARTIAL_RESULTS_MAX} of ${finishedAgents.length} completed agent outputs; the full set remains retrievable from the workflow run record (getWorkflowRun / the workflow journal).` }
|
|
307
|
-
: {}),
|
|
308
|
-
}
|
|
309
|
-
: {}),
|
|
310
|
-
note: run.status === "running"
|
|
311
|
-
? "still running — poll again shortly."
|
|
312
|
-
: run.status === "completed"
|
|
313
|
-
? run.result !== undefined
|
|
314
|
-
? "completed — `result` above is the workflow's return value."
|
|
315
|
-
: "completed — this run predates result persistence; its result was delivered on the completion notification."
|
|
316
|
-
: finishedAgents.length > 0
|
|
317
|
-
? `failed — see error. ${finishedAgents.length} agent(s) had already completed before the run ended; their outputs are inlined under \`partial_results\`.`
|
|
318
|
-
: "failed — see error.",
|
|
319
|
-
}),
|
|
320
|
-
details: {
|
|
321
|
-
task_id: run.id,
|
|
322
|
-
type: "workflow",
|
|
323
|
-
status: run.status,
|
|
324
|
-
retrieval_status: "success",
|
|
325
|
-
...(run.completionId !== undefined ? { completionId: run.completionId } : {}),
|
|
326
|
-
details: summary,
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
59
|
export class TaskRegistry {
|
|
331
60
|
handles = new Map();
|
|
332
61
|
sessionReapHooks = new Set();
|
|
333
62
|
bgQuiescenceWatchers = new Map();
|
|
334
63
|
legacyToTaskId = new Map();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
liveLanes++;
|
|
352
|
-
this.durableAgentWrite(bh, {});
|
|
353
|
-
}
|
|
354
|
-
else if (bh.durable.flushFailed) {
|
|
355
|
-
if ((bh.durable.redrives = (bh.durable.redrives ?? 0) + 1) > 60) {
|
|
356
|
-
bh.durable.poisoned = true;
|
|
357
|
-
process.emitWarning(`sema durable-agents: terminal flush for ${bh.durable.record.handle} unrecoverable after 60 heartbeat re-drives — durable writes for this row stop here`);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
liveLanes++;
|
|
361
|
-
this.durableAgentWrite(bh, {});
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (liveLanes === 0) {
|
|
365
|
-
clearInterval(t);
|
|
366
|
-
this.durableHeartbeatTimer = undefined;
|
|
367
|
-
}
|
|
368
|
-
}, DURABLE_AGENT_HEARTBEAT_MS);
|
|
369
|
-
t.unref?.();
|
|
370
|
-
this.durableHeartbeatTimer = t;
|
|
371
|
-
}
|
|
372
|
-
durableAgentWrite(handle, patch, clear) {
|
|
373
|
-
const lane = handle.durable;
|
|
374
|
-
if (!lane || lane.poisoned)
|
|
375
|
-
return;
|
|
376
|
-
lane.chain = lane.chain.then(async () => {
|
|
377
|
-
if (lane.poisoned)
|
|
378
|
-
return;
|
|
379
|
-
Object.assign(lane.record, patch);
|
|
380
|
-
if (clear !== undefined)
|
|
381
|
-
for (const k of clear)
|
|
382
|
-
delete lane.record[k];
|
|
383
|
-
lane.record.updatedAt = Date.now();
|
|
384
|
-
const poison = (why) => {
|
|
385
|
-
lane.poisoned = true;
|
|
386
|
-
process.emitWarning(`sema durable-agents: ${why} for ${lane.record.handle} — durable writes for this row stop here`);
|
|
387
|
-
};
|
|
388
|
-
const adoptOwnRow = async () => {
|
|
389
|
-
try {
|
|
390
|
-
const live = await lane.store.get(lane.record.handle, lane.record.scope);
|
|
391
|
-
if (!live)
|
|
392
|
-
return "gone";
|
|
393
|
-
if (live.writerId !== lane.record.writerId)
|
|
394
|
-
return "foreign";
|
|
395
|
-
if (live.writerEpoch !== lane.record.writerEpoch)
|
|
396
|
-
return "foreign";
|
|
397
|
-
lane.record.rev = live.rev;
|
|
398
|
-
lane.written = true;
|
|
399
|
-
return "adopted";
|
|
400
|
-
}
|
|
401
|
-
catch {
|
|
402
|
-
return "unreadable";
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
406
|
-
if (lane.poisoned)
|
|
407
|
-
return;
|
|
408
|
-
if (attempt > 0) {
|
|
409
|
-
await new Promise((r) => {
|
|
410
|
-
const t = setTimeout(r, 50 * attempt);
|
|
411
|
-
t.unref?.();
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
try {
|
|
415
|
-
if (!lane.written) {
|
|
416
|
-
await lane.store.put(structuredClone(lane.record));
|
|
417
|
-
lane.written = true;
|
|
418
|
-
lane.flushFailed = false;
|
|
419
|
-
lane.redrives = 0;
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const ok = await lane.store.update(lane.record.handle, lane.record.scope, structuredClone(lane.record), {
|
|
423
|
-
rev: lane.record.rev,
|
|
424
|
-
});
|
|
425
|
-
if (ok) {
|
|
426
|
-
lane.record.rev += 1;
|
|
427
|
-
lane.flushFailed = false;
|
|
428
|
-
lane.redrives = 0;
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
const verdict = await adoptOwnRow();
|
|
432
|
-
if (lane.poisoned)
|
|
433
|
-
return;
|
|
434
|
-
if (verdict === "adopted")
|
|
435
|
-
continue;
|
|
436
|
-
if (verdict === "unreadable")
|
|
437
|
-
continue;
|
|
438
|
-
poison(verdict === "gone" ? "row reaped mid-write" : "CAS lost to a foreign writer");
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
catch (e) {
|
|
442
|
-
if (e instanceof BackgroundAgentStoreError && e.code === "agent_record.already_exists") {
|
|
443
|
-
const verdict = await adoptOwnRow();
|
|
444
|
-
if (lane.poisoned)
|
|
445
|
-
return;
|
|
446
|
-
if (verdict === "adopted")
|
|
447
|
-
continue;
|
|
448
|
-
if (verdict === "unreadable")
|
|
449
|
-
continue;
|
|
450
|
-
if (verdict === "gone")
|
|
451
|
-
continue;
|
|
452
|
-
poison("handle collision (foreign durable row left untouched)");
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
shutdownDebug(`durable-agents write failed for ${lane.record.handle} (attempt ${attempt + 1}/3)`, { error: String(e) });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
lane.flushFailed = true;
|
|
459
|
-
});
|
|
460
|
-
}
|
|
64
|
+
core = (() => {
|
|
65
|
+
const self = this;
|
|
66
|
+
return {
|
|
67
|
+
handles: self.handles,
|
|
68
|
+
mintTaskId: (type) => self.mintTaskId(type),
|
|
69
|
+
markStopSource: (id, source) => self.markStopSource(id, source),
|
|
70
|
+
notifyTerminalOnce: (handle, send) => self.notifyTerminalOnce(handle, send),
|
|
71
|
+
pokeBgQuiescence: (owner) => self.pokeBgQuiescence(owner),
|
|
72
|
+
get writerId() { return self.writerId; },
|
|
73
|
+
get reapingHandles() { return self.reapingHandles; },
|
|
74
|
+
get claimingHandles() { return self.claimingHandles; },
|
|
75
|
+
get durableHeartbeatTimer() { return self.durableHeartbeatTimer; },
|
|
76
|
+
set durableHeartbeatTimer(v) { self.durableHeartbeatTimer = v; },
|
|
77
|
+
get deliverConfirmTimeoutMs() { return self.deliverConfirmTimeoutMs; },
|
|
78
|
+
};
|
|
79
|
+
})();
|
|
461
80
|
durableAgentArmed(id) {
|
|
462
|
-
|
|
463
|
-
if (!h || h.type !== "background_agent")
|
|
464
|
-
return false;
|
|
465
|
-
const lane = h.durable;
|
|
466
|
-
return lane !== undefined && !lane.poisoned;
|
|
81
|
+
return durableAgentArmedLane(this.core, id);
|
|
467
82
|
}
|
|
468
83
|
durableAgentRowProbe(id) {
|
|
469
|
-
|
|
470
|
-
if (!h || h.type !== "background_agent")
|
|
471
|
-
return undefined;
|
|
472
|
-
const lane = h.durable;
|
|
473
|
-
if (!lane)
|
|
474
|
-
return undefined;
|
|
475
|
-
return async () => {
|
|
476
|
-
await lane.chain.catch(() => undefined);
|
|
477
|
-
return lane.written && !lane.poisoned && !lane.flushFailed;
|
|
478
|
-
};
|
|
84
|
+
return durableAgentRowProbeLane(this.core, id);
|
|
479
85
|
}
|
|
480
86
|
beginDurableClaim(id) {
|
|
481
|
-
|
|
482
|
-
if (live !== undefined && (live.status === "running" || live.status === "pending"))
|
|
483
|
-
return false;
|
|
484
|
-
if (this.reapingHandles.has(id) || this.claimingHandles.has(id))
|
|
485
|
-
return false;
|
|
486
|
-
this.claimingHandles.add(id);
|
|
487
|
-
return true;
|
|
87
|
+
return beginDurableClaimLane(this.core, id);
|
|
488
88
|
}
|
|
489
89
|
endDurableClaim(id) {
|
|
490
|
-
this.
|
|
90
|
+
return endDurableClaimLane(this.core, id);
|
|
491
91
|
}
|
|
492
92
|
async reapDurableAgents(scope, deps, policy) {
|
|
493
|
-
|
|
494
|
-
if (policy.staleRunningMaxAgeMs !== undefined) {
|
|
495
|
-
await deps.store.reap(scope, now, { staleRunningMaxAgeMs: policy.staleRunningMaxAgeMs });
|
|
496
|
-
}
|
|
497
|
-
if (policy.maxAgeMs === undefined && policy.keep === undefined)
|
|
498
|
-
return { rowsReaped: 0, sessionsReleased: 0, skippedNoSessions: 0 };
|
|
499
|
-
const terminal = (await deps.store.listByScope(scope)).filter((r) => r.status !== "running" && r.status !== "parked");
|
|
500
|
-
terminal.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
501
|
-
const doomed = new Map();
|
|
502
|
-
if (policy.maxAgeMs !== undefined) {
|
|
503
|
-
for (const r of terminal) {
|
|
504
|
-
if ((r.settledAt ?? r.spawnedAt) < now - policy.maxAgeMs)
|
|
505
|
-
doomed.set(r.handle, r);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
if (policy.keep !== undefined) {
|
|
509
|
-
for (const r of terminal.slice(Math.max(0, policy.keep)))
|
|
510
|
-
doomed.set(r.handle, r);
|
|
511
|
-
}
|
|
512
|
-
let rowsReaped = 0;
|
|
513
|
-
let sessionsReleased = 0;
|
|
514
|
-
let skippedNoSessions = 0;
|
|
515
|
-
for (const r of doomed.values()) {
|
|
516
|
-
if (this.claimingHandles.has(r.handle))
|
|
517
|
-
continue;
|
|
518
|
-
this.reapingHandles.add(r.handle);
|
|
519
|
-
try {
|
|
520
|
-
let live;
|
|
521
|
-
try {
|
|
522
|
-
live = await deps.store.get(r.handle, scope);
|
|
523
|
-
}
|
|
524
|
-
catch {
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
if (!live || live.status === "running" || live.status === "parked")
|
|
528
|
-
continue;
|
|
529
|
-
const inProc = this.handles.get(r.handle);
|
|
530
|
-
if (inProc !== undefined && (inProc.status === "running" || inProc.status === "pending" || inProc.status === "parked"))
|
|
531
|
-
continue;
|
|
532
|
-
if (live.sessionId !== undefined && deps.sessions === undefined) {
|
|
533
|
-
skippedNoSessions++;
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
try {
|
|
537
|
-
if (!(await deps.store.delete(r.handle, scope, { rev: live.rev })))
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
catch {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
rowsReaped++;
|
|
544
|
-
if (deps.mailbox !== undefined) {
|
|
545
|
-
try {
|
|
546
|
-
await deps.mailbox.drop(scope, r.handle);
|
|
547
|
-
}
|
|
548
|
-
catch {
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
if (live.sessionId !== undefined && deps.sessions !== undefined) {
|
|
552
|
-
try {
|
|
553
|
-
await deps.sessions.unpin?.(live.sessionId);
|
|
554
|
-
await deps.sessions.release(live.sessionId);
|
|
555
|
-
sessionsReleased++;
|
|
556
|
-
}
|
|
557
|
-
catch {
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
finally {
|
|
562
|
-
this.reapingHandles.delete(r.handle);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
return { rowsReaped, sessionsReleased, skippedNoSessions };
|
|
93
|
+
return reapDurableAgentsLane(this.core, scope, deps, policy);
|
|
566
94
|
}
|
|
567
95
|
releaseDurableTranscriptAnchor(id) {
|
|
568
|
-
|
|
569
|
-
if (!handle || handle.type !== "background_agent")
|
|
570
|
-
return;
|
|
571
|
-
this.durableAgentWrite(handle, {}, ["sessionId"]);
|
|
96
|
+
return releaseDurableTranscriptAnchorLane(this.core, id);
|
|
572
97
|
}
|
|
573
98
|
bindBackgroundAgentSession(id, sessionId) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
99
|
+
return bindBackgroundAgentSessionLane(this.core, id, sessionId);
|
|
100
|
+
}
|
|
101
|
+
registerBackgroundAgent(input) {
|
|
102
|
+
return registerBackgroundAgentLane(this.core, input);
|
|
103
|
+
}
|
|
104
|
+
parkBackgroundAgent(id, park) {
|
|
105
|
+
return parkBackgroundAgentLane(this.core, id, park);
|
|
106
|
+
}
|
|
107
|
+
async reconcileParkedAgents(stores, scope, now, opts) {
|
|
108
|
+
return reconcileParkedAgentsLane(this.core, stores, scope, now, opts);
|
|
109
|
+
}
|
|
110
|
+
async claimParkedAgent(stores, handle, access) {
|
|
111
|
+
return claimParkedAgentLane(stores, handle, access);
|
|
112
|
+
}
|
|
113
|
+
async rollbackParkedClaim(stores, ticket) {
|
|
114
|
+
return rollbackParkedClaimLane(this.core, stores, ticket);
|
|
115
|
+
}
|
|
116
|
+
async consumeParkedFlip(id, stores, ticket) {
|
|
117
|
+
return consumeParkedFlipLane(this.core, id, stores, ticket);
|
|
118
|
+
}
|
|
119
|
+
finalizeParkedResume(id) {
|
|
120
|
+
return finalizeParkedResumeLane(this.core, id);
|
|
121
|
+
}
|
|
122
|
+
settleBackgroundAgent(id, outcome) {
|
|
123
|
+
return settleBackgroundAgentLane(this.core, id, outcome);
|
|
124
|
+
}
|
|
125
|
+
abortBackgroundAgentsForOwner(access, opts) {
|
|
126
|
+
return abortBackgroundAgentsForOwnerLane(this.core, access, opts);
|
|
127
|
+
}
|
|
128
|
+
serveDurableAgentRow(row) {
|
|
129
|
+
return serveDurableAgentRowLane(row);
|
|
130
|
+
}
|
|
131
|
+
resolveBackgroundAgentByName(name, access, opts) {
|
|
132
|
+
return resolveBackgroundAgentByNameLane(this.core, name, access, opts);
|
|
133
|
+
}
|
|
134
|
+
markRetainedContinuation(id) {
|
|
135
|
+
return markRetainedContinuationLane(this.core, id);
|
|
579
136
|
}
|
|
137
|
+
reviveBackgroundAgent(id, access, abort) {
|
|
138
|
+
return reviveBackgroundAgentLane(this.core, id, access, abort);
|
|
139
|
+
}
|
|
140
|
+
settleRevivedAgent(id, cycle, outcome) {
|
|
141
|
+
return settleRevivedAgentLane(this.core, id, cycle, outcome);
|
|
142
|
+
}
|
|
143
|
+
unmarkRetainedContinuation(id) {
|
|
144
|
+
return unmarkRetainedContinuationLane(this.core, id);
|
|
145
|
+
}
|
|
146
|
+
attachAgentNotify(id, notify, cycle) {
|
|
147
|
+
return attachAgentNotifyLane(this.core, id, notify, cycle);
|
|
148
|
+
}
|
|
149
|
+
async deliverToRunningAgent(id, access, notification, opts) {
|
|
150
|
+
return deliverToRunningAgentLane(this.core, id, access, notification, opts);
|
|
151
|
+
}
|
|
152
|
+
runningBackgroundAgentLabels(access) {
|
|
153
|
+
return runningBackgroundAgentLabelsLane(this.core, access);
|
|
154
|
+
}
|
|
155
|
+
async pollBackgroundAgent(handle, deadline, signal) {
|
|
156
|
+
return pollBackgroundAgentLane(handle, deadline, signal);
|
|
157
|
+
}
|
|
158
|
+
async stopBackgroundAgent(handle) {
|
|
159
|
+
return stopBackgroundAgentLane(this.core, handle);
|
|
160
|
+
}
|
|
161
|
+
writerId = randomBytes(8).toString("hex");
|
|
162
|
+
reapingHandles = new Set();
|
|
163
|
+
claimingHandles = new Set();
|
|
164
|
+
durableHeartbeatTimer;
|
|
580
165
|
mintTaskId(type) {
|
|
581
166
|
const prefix = TASK_PREFIX[type];
|
|
582
167
|
for (let i = 0; i < 32; i++) {
|
|
@@ -626,99 +211,6 @@ export class TaskRegistry {
|
|
|
626
211
|
}
|
|
627
212
|
return false;
|
|
628
213
|
}
|
|
629
|
-
registerBackgroundAgent(input) {
|
|
630
|
-
assertOwnership(input, "registerBackgroundAgent");
|
|
631
|
-
if (input.id !== undefined && !DURABLE_AGENT_HANDLE_RE.test(input.id)) {
|
|
632
|
-
throw new Error(`TaskRegistry: background-agent id ${JSON.stringify(input.id)} does not match the a<hex> handle form`);
|
|
633
|
-
}
|
|
634
|
-
const id = input.id ?? this.mintTaskId("background_agent");
|
|
635
|
-
if (input.durableSeed !== undefined) {
|
|
636
|
-
const prior = this.handles.get(id);
|
|
637
|
-
if (prior !== undefined) {
|
|
638
|
-
const parkedHandoff = prior.status === "parked" && input.initialStatus === "parked";
|
|
639
|
-
if (prior.type !== "background_agent" || prior.status === "running" || prior.status === "pending" || (prior.status === "parked" && !parkedHandoff)) {
|
|
640
|
-
throw new Error(`TaskRegistry: task id already registered: ${id}`);
|
|
641
|
-
}
|
|
642
|
-
const priorLane = prior.durable;
|
|
643
|
-
if (priorLane !== undefined)
|
|
644
|
-
priorLane.poisoned = true;
|
|
645
|
-
this.handles.delete(id);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
if (this.handles.has(id))
|
|
649
|
-
throw new Error(`TaskRegistry: task id already registered: ${id}`);
|
|
650
|
-
const now = input.now ?? Date.now();
|
|
651
|
-
this.handles.set(id, {
|
|
652
|
-
id,
|
|
653
|
-
type: "background_agent",
|
|
654
|
-
status: input.initialStatus ?? "running",
|
|
655
|
-
...(input.parkedCheckpointToken !== undefined ? { parkedCheckpointToken: input.parkedCheckpointToken } : {}),
|
|
656
|
-
...(input.resolveParkedStop !== undefined ? { resolveParkedStop: input.resolveParkedStop } : {}),
|
|
657
|
-
...(input.sessionScoped ? { sessionScoped: true } : {}),
|
|
658
|
-
...(input.description !== undefined ? { description: input.description } : {}),
|
|
659
|
-
...(input.name !== undefined ? { name: input.name } : {}),
|
|
660
|
-
...(input.agentType !== undefined ? { agentType: input.agentType } : {}),
|
|
661
|
-
...(input.parentTaskId !== undefined ? { parentTaskId: input.parentTaskId } : {}),
|
|
662
|
-
...(input.parentSessionId !== undefined ? { parentSessionId: input.parentSessionId } : {}),
|
|
663
|
-
...(input.rootSessionId !== undefined ? { rootSessionId: input.rootSessionId } : {}),
|
|
664
|
-
...(input.deliveryChannel !== undefined ? { channelState: input.deliveryChannel } : {}),
|
|
665
|
-
...(input.retainedContinuation !== undefined ? { retainedContinuation: input.retainedContinuation } : {}),
|
|
666
|
-
...(input.owner !== undefined ? { owner: input.owner } : {}),
|
|
667
|
-
...(input.scope !== undefined ? { scope: input.scope } : {}),
|
|
668
|
-
...(input.toolUseId !== undefined ? { toolUseId: input.toolUseId } : {}),
|
|
669
|
-
cycleSeq: input.durableSeed !== undefined ? (input.durableSeed.record.seq ?? 1) : 1,
|
|
670
|
-
createdAt: now,
|
|
671
|
-
updatedAt: now,
|
|
672
|
-
abort: input.abort,
|
|
673
|
-
});
|
|
674
|
-
if (input.store) {
|
|
675
|
-
const handle = this.handles.get(id);
|
|
676
|
-
if (input.durableSeed !== undefined) {
|
|
677
|
-
const seeded = structuredClone(input.durableSeed.record);
|
|
678
|
-
seeded.rev = input.durableSeed.rev;
|
|
679
|
-
handle.durable = {
|
|
680
|
-
store: input.store,
|
|
681
|
-
record: seeded,
|
|
682
|
-
chain: Promise.resolve(),
|
|
683
|
-
written: true,
|
|
684
|
-
poisoned: false,
|
|
685
|
-
flushFailed: false,
|
|
686
|
-
};
|
|
687
|
-
this.ensureDurableHeartbeat();
|
|
688
|
-
return id;
|
|
689
|
-
}
|
|
690
|
-
handle.durable = {
|
|
691
|
-
store: input.store,
|
|
692
|
-
record: {
|
|
693
|
-
handle: id,
|
|
694
|
-
scope: input.scope,
|
|
695
|
-
owner: input.owner,
|
|
696
|
-
sessionScoped: input.sessionScoped === true,
|
|
697
|
-
...(input.parentSessionId !== undefined ? { parentSessionId: input.parentSessionId } : {}),
|
|
698
|
-
...(input.rootSessionId !== undefined ? { rootSessionId: input.rootSessionId } : {}),
|
|
699
|
-
...(input.parentTaskId !== undefined ? { parentTaskId: input.parentTaskId } : {}),
|
|
700
|
-
...(input.name !== undefined ? { name: input.name } : {}),
|
|
701
|
-
...(input.agentType !== undefined ? { agentType: input.agentType } : {}),
|
|
702
|
-
...(input.description !== undefined ? { description: input.description } : {}),
|
|
703
|
-
...(input.model !== undefined ? { model: input.model } : {}),
|
|
704
|
-
...(input.teamName !== undefined ? { teamName: input.teamName } : {}),
|
|
705
|
-
writerId: this.writerId,
|
|
706
|
-
writerEpoch: 1,
|
|
707
|
-
spawnedAt: now,
|
|
708
|
-
updatedAt: now,
|
|
709
|
-
status: "running",
|
|
710
|
-
rev: 0,
|
|
711
|
-
},
|
|
712
|
-
chain: Promise.resolve(),
|
|
713
|
-
written: false,
|
|
714
|
-
poisoned: false,
|
|
715
|
-
flushFailed: false,
|
|
716
|
-
};
|
|
717
|
-
this.durableAgentWrite(handle, {});
|
|
718
|
-
this.ensureDurableHeartbeat();
|
|
719
|
-
}
|
|
720
|
-
return id;
|
|
721
|
-
}
|
|
722
214
|
getAccessibleTask(taskId, access) {
|
|
723
215
|
const h = this.handles.get(taskId);
|
|
724
216
|
if (!h || !canAccess(h, access))
|
|
@@ -820,399 +312,6 @@ export class TaskRegistry {
|
|
|
820
312
|
getCompletionId(id) {
|
|
821
313
|
return this.handles.get(id)?.completionId;
|
|
822
314
|
}
|
|
823
|
-
parkBackgroundAgent(id, park) {
|
|
824
|
-
const handle = this.handles.get(id);
|
|
825
|
-
if (!handle || handle.type !== "background_agent")
|
|
826
|
-
return undefined;
|
|
827
|
-
if (handle.status !== "running")
|
|
828
|
-
return undefined;
|
|
829
|
-
handle.status = "parked";
|
|
830
|
-
handle.parkedCheckpointToken = park.checkpointToken;
|
|
831
|
-
handle.resolveParkedStop = park.resolveStop;
|
|
832
|
-
handle.notify = undefined;
|
|
833
|
-
for (const [, , qResolve] of handle.preAttachQueue ?? [])
|
|
834
|
-
qResolve?.({ ok: false, reason: "not_running" });
|
|
835
|
-
handle.preAttachQueue = undefined;
|
|
836
|
-
for (const r of [...(handle.inFlightDirect ?? [])])
|
|
837
|
-
r({ ok: false, reason: "not_running" });
|
|
838
|
-
handle.inFlightDirect = undefined;
|
|
839
|
-
handle.updatedAt = Date.now();
|
|
840
|
-
if (park.seq !== undefined)
|
|
841
|
-
handle.cycleSeq = park.seq;
|
|
842
|
-
this.pokeBgQuiescence(handle.owner);
|
|
843
|
-
this.durableAgentWrite(handle, {
|
|
844
|
-
status: "parked",
|
|
845
|
-
parkedCheckpointToken: park.checkpointToken,
|
|
846
|
-
parkedAt: Date.now(),
|
|
847
|
-
...(park.seq !== undefined ? { seq: park.seq } : {}),
|
|
848
|
-
}, ["parkClaimId"]);
|
|
849
|
-
return "parked";
|
|
850
|
-
}
|
|
851
|
-
async reconcileParkedAgents(stores, scope, now, opts) {
|
|
852
|
-
let liveFailed = 0;
|
|
853
|
-
for (const handle of this.handles.values()) {
|
|
854
|
-
if (handle.type !== "background_agent" || handle.status !== "parked")
|
|
855
|
-
continue;
|
|
856
|
-
if (handle.scope !== undefined && handle.scope !== scope)
|
|
857
|
-
continue;
|
|
858
|
-
const token = handle.parkedCheckpointToken;
|
|
859
|
-
let state;
|
|
860
|
-
if (token === undefined)
|
|
861
|
-
state = "missing";
|
|
862
|
-
else {
|
|
863
|
-
try {
|
|
864
|
-
const cp = await stores.checkpointStore.get(token);
|
|
865
|
-
state = cp === null ? "missing" : cp.status;
|
|
866
|
-
}
|
|
867
|
-
catch {
|
|
868
|
-
state = "unreadable";
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
if (state !== "expired" && state !== "missing")
|
|
872
|
-
continue;
|
|
873
|
-
if (handle.status !== "parked")
|
|
874
|
-
continue;
|
|
875
|
-
handle.status = "failed";
|
|
876
|
-
handle.error = `approval checkpoint ${state} while parked (reconciled)`;
|
|
877
|
-
handle.parkedCheckpointToken = undefined;
|
|
878
|
-
handle.resolveParkedStop = undefined;
|
|
879
|
-
handle.updatedAt = now;
|
|
880
|
-
mintCompletionId(handle);
|
|
881
|
-
this.pokeBgQuiescence(handle.owner);
|
|
882
|
-
this.durableAgentWrite(handle, { status: "failed", error: handle.error, settledAt: now, stoppedBy: "system", completionId: handle.completionId }, ["parkedCheckpointToken", "parkClaimId", "parkedAt"]);
|
|
883
|
-
liveFailed++;
|
|
884
|
-
}
|
|
885
|
-
const { reconcileParkedAgents: storeReconcile } = await import("./background-agent-store.js");
|
|
886
|
-
const liveHandles = new Set();
|
|
887
|
-
for (const h of this.handles.values())
|
|
888
|
-
if (h.type === "background_agent")
|
|
889
|
-
liveHandles.add(h.id);
|
|
890
|
-
const storeHalf = await storeReconcile(stores, scope, now, {
|
|
891
|
-
...(opts?.staleClaimMaxAgeMs !== undefined ? { staleClaimMaxAgeMs: opts.staleClaimMaxAgeMs } : {}),
|
|
892
|
-
excludeHandles: liveHandles,
|
|
893
|
-
});
|
|
894
|
-
return { failed: storeHalf.failed + liveFailed, rolledBack: storeHalf.rolledBack };
|
|
895
|
-
}
|
|
896
|
-
async claimParkedAgent(stores, handle, access) {
|
|
897
|
-
if (access.scope === undefined)
|
|
898
|
-
return { ok: false, reason: "not_found" };
|
|
899
|
-
let row;
|
|
900
|
-
try {
|
|
901
|
-
row = await stores.agentStore.get(handle, access.scope);
|
|
902
|
-
}
|
|
903
|
-
catch {
|
|
904
|
-
return { ok: false, reason: "store_unreachable" };
|
|
905
|
-
}
|
|
906
|
-
if (!row || !canAccessAgentRecord(row, access))
|
|
907
|
-
return { ok: false, reason: "not_found" };
|
|
908
|
-
if (row.status !== "parked")
|
|
909
|
-
return { ok: false, reason: "not_parked" };
|
|
910
|
-
const token = row.parkedCheckpointToken;
|
|
911
|
-
if (token === undefined || row.sessionId === undefined)
|
|
912
|
-
return { ok: false, reason: "binding_broken" };
|
|
913
|
-
let cp;
|
|
914
|
-
try {
|
|
915
|
-
cp = (await stores.checkpointStore.get(token));
|
|
916
|
-
}
|
|
917
|
-
catch {
|
|
918
|
-
return { ok: false, reason: "store_unreachable" };
|
|
919
|
-
}
|
|
920
|
-
if (cp === null || cp.sessionId !== row.sessionId)
|
|
921
|
-
return { ok: false, reason: "binding_broken" };
|
|
922
|
-
if (cp.status !== "pending")
|
|
923
|
-
return { ok: false, reason: "checkpoint_not_pending" };
|
|
924
|
-
const claimId = randomBytes(8).toString("hex");
|
|
925
|
-
const reserved = structuredClone(row);
|
|
926
|
-
reserved.parkClaimId = claimId;
|
|
927
|
-
reserved.updatedAt = Date.now();
|
|
928
|
-
let won;
|
|
929
|
-
try {
|
|
930
|
-
won = await stores.agentStore.updateIf(handle, access.scope, reserved, {
|
|
931
|
-
rev: row.rev,
|
|
932
|
-
status: "parked",
|
|
933
|
-
parkedCheckpointToken: token,
|
|
934
|
-
parkClaimId: null,
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
catch {
|
|
938
|
-
return { ok: false, reason: "store_unreachable" };
|
|
939
|
-
}
|
|
940
|
-
if (!won)
|
|
941
|
-
return { ok: false, reason: "claim_lost" };
|
|
942
|
-
return {
|
|
943
|
-
ok: true,
|
|
944
|
-
ticket: { handle, scope: access.scope, token, claimId, reservedRev: row.rev + 1, row: structuredClone(row) },
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
async rollbackParkedClaim(stores, ticket) {
|
|
948
|
-
let cpStatus;
|
|
949
|
-
try {
|
|
950
|
-
const cp = (await stores.checkpointStore.get(ticket.token));
|
|
951
|
-
cpStatus = cp === null ? "missing" : cp.status;
|
|
952
|
-
}
|
|
953
|
-
catch {
|
|
954
|
-
return "retry";
|
|
955
|
-
}
|
|
956
|
-
const now = Date.now();
|
|
957
|
-
const rollbackCompletionIdCandidate = uuidv7();
|
|
958
|
-
const settleLocalParkedHandle = (error) => {
|
|
959
|
-
const h = this.handles.get(ticket.handle);
|
|
960
|
-
if (h !== undefined && h.type === "background_agent" && h.status === "parked") {
|
|
961
|
-
h.status = "failed";
|
|
962
|
-
h.error = error;
|
|
963
|
-
h.parkedCheckpointToken = undefined;
|
|
964
|
-
h.resolveParkedStop = undefined;
|
|
965
|
-
h.updatedAt = now;
|
|
966
|
-
commitCompletionIdIfEmpty(h, rollbackCompletionIdCandidate);
|
|
967
|
-
this.pokeBgQuiescence(h.owner);
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
let liveRow;
|
|
971
|
-
try {
|
|
972
|
-
liveRow = await stores.agentStore.get(ticket.handle, ticket.scope);
|
|
973
|
-
}
|
|
974
|
-
catch {
|
|
975
|
-
return "retry";
|
|
976
|
-
}
|
|
977
|
-
if (!liveRow || liveRow.parkClaimId !== ticket.claimId)
|
|
978
|
-
return "lost";
|
|
979
|
-
if (cpStatus === "pending") {
|
|
980
|
-
const restored = structuredClone(liveRow);
|
|
981
|
-
delete restored.parkClaimId;
|
|
982
|
-
restored.updatedAt = now;
|
|
983
|
-
restored.writerEpoch = (liveRow.writerEpoch ?? 0) + 1;
|
|
984
|
-
const liveHandle = this.handles.get(ticket.handle);
|
|
985
|
-
if (liveHandle?.type === "background_agent" &&
|
|
986
|
-
liveHandle.durable !== undefined &&
|
|
987
|
-
liveHandle.durable.record.writerId === liveRow.writerId &&
|
|
988
|
-
(liveHandle.durable.record.writerEpoch ?? 0) === (liveRow.writerEpoch ?? 0)) {
|
|
989
|
-
liveHandle.durable.record.writerEpoch = restored.writerEpoch;
|
|
990
|
-
}
|
|
991
|
-
try {
|
|
992
|
-
const won = await stores.agentStore.updateIf(ticket.handle, ticket.scope, restored, {
|
|
993
|
-
rev: liveRow.rev,
|
|
994
|
-
status: "parked",
|
|
995
|
-
parkedCheckpointToken: ticket.token,
|
|
996
|
-
parkClaimId: ticket.claimId,
|
|
997
|
-
});
|
|
998
|
-
if (!won)
|
|
999
|
-
return "lost";
|
|
1000
|
-
try {
|
|
1001
|
-
const cp2 = (await stores.checkpointStore.get(ticket.token));
|
|
1002
|
-
if (cp2 !== null && cp2.status === "pending")
|
|
1003
|
-
return "rolled_back";
|
|
1004
|
-
const post = await stores.agentStore.get(ticket.handle, ticket.scope);
|
|
1005
|
-
if (post && post.status === "parked") {
|
|
1006
|
-
const term = structuredClone(post);
|
|
1007
|
-
term.status = "failed";
|
|
1008
|
-
term.error = `approval checkpoint ${cp2 === null ? "missing" : cp2.status} under the resume claim (claim rollback)`;
|
|
1009
|
-
term.stoppedBy = "system";
|
|
1010
|
-
term.settledAt = now;
|
|
1011
|
-
term.updatedAt = now;
|
|
1012
|
-
term.completionId = rollbackCompletionIdCandidate;
|
|
1013
|
-
delete term.parkedCheckpointToken;
|
|
1014
|
-
delete term.parkClaimId;
|
|
1015
|
-
delete term.parkedAt;
|
|
1016
|
-
if (await stores.agentStore.updateIf(ticket.handle, ticket.scope, term, { rev: post.rev, status: "parked" })) {
|
|
1017
|
-
settleLocalParkedHandle(term.error);
|
|
1018
|
-
return "failed";
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
return "lost";
|
|
1022
|
-
}
|
|
1023
|
-
catch {
|
|
1024
|
-
return "rolled_back";
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
catch {
|
|
1028
|
-
return "retry";
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
const error = cpStatus === "resolved"
|
|
1032
|
-
? "approval consumed but the resume could not proceed — outcome unknown (claim rollback)"
|
|
1033
|
-
: `approval checkpoint ${cpStatus} under the resume claim (claim rollback)`;
|
|
1034
|
-
if (liveRow.status !== "parked")
|
|
1035
|
-
return "lost";
|
|
1036
|
-
const failed = structuredClone(liveRow);
|
|
1037
|
-
failed.status = "failed";
|
|
1038
|
-
failed.error = error;
|
|
1039
|
-
failed.stoppedBy = "system";
|
|
1040
|
-
failed.settledAt = now;
|
|
1041
|
-
failed.updatedAt = now;
|
|
1042
|
-
failed.completionId = rollbackCompletionIdCandidate;
|
|
1043
|
-
delete failed.parkedCheckpointToken;
|
|
1044
|
-
delete failed.parkClaimId;
|
|
1045
|
-
delete failed.parkedAt;
|
|
1046
|
-
try {
|
|
1047
|
-
const won = await stores.agentStore.updateIf(ticket.handle, ticket.scope, failed, {
|
|
1048
|
-
rev: liveRow.rev,
|
|
1049
|
-
status: "parked",
|
|
1050
|
-
parkClaimId: ticket.claimId,
|
|
1051
|
-
});
|
|
1052
|
-
if (won)
|
|
1053
|
-
settleLocalParkedHandle(error);
|
|
1054
|
-
return won ? "failed" : "lost";
|
|
1055
|
-
}
|
|
1056
|
-
catch {
|
|
1057
|
-
return "retry";
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
async consumeParkedFlip(id, stores, ticket) {
|
|
1061
|
-
const handle = this.handles.get(id);
|
|
1062
|
-
if (!handle || handle.type !== "background_agent" || handle.status !== "parked")
|
|
1063
|
-
return false;
|
|
1064
|
-
const lane = handle.durable;
|
|
1065
|
-
const doFlip = async () => {
|
|
1066
|
-
let live;
|
|
1067
|
-
try {
|
|
1068
|
-
live = await stores.agentStore.get(ticket.handle, ticket.scope);
|
|
1069
|
-
}
|
|
1070
|
-
catch {
|
|
1071
|
-
return undefined;
|
|
1072
|
-
}
|
|
1073
|
-
if (!live || live.status !== "parked" || live.parkClaimId !== ticket.claimId || live.parkedCheckpointToken !== ticket.token)
|
|
1074
|
-
return undefined;
|
|
1075
|
-
const flipped = structuredClone(live);
|
|
1076
|
-
flipped.status = "running";
|
|
1077
|
-
flipped.writerId = this.writerId;
|
|
1078
|
-
flipped.writerEpoch = (live.writerEpoch ?? 0) + 1;
|
|
1079
|
-
flipped.updatedAt = Date.now();
|
|
1080
|
-
flipped.seq = (live.seq ?? 1) + 1;
|
|
1081
|
-
delete flipped.parkedCheckpointToken;
|
|
1082
|
-
delete flipped.parkedAt;
|
|
1083
|
-
try {
|
|
1084
|
-
const won = await stores.agentStore.updateIf(ticket.handle, ticket.scope, flipped, {
|
|
1085
|
-
rev: live.rev,
|
|
1086
|
-
status: "parked",
|
|
1087
|
-
parkedCheckpointToken: ticket.token,
|
|
1088
|
-
parkClaimId: ticket.claimId,
|
|
1089
|
-
});
|
|
1090
|
-
if (!won)
|
|
1091
|
-
return undefined;
|
|
1092
|
-
}
|
|
1093
|
-
catch {
|
|
1094
|
-
return undefined;
|
|
1095
|
-
}
|
|
1096
|
-
flipped.rev = live.rev + 1;
|
|
1097
|
-
return flipped;
|
|
1098
|
-
};
|
|
1099
|
-
let flipped;
|
|
1100
|
-
if (lane !== undefined && !lane.poisoned) {
|
|
1101
|
-
const p = lane.chain.then(doFlip, doFlip);
|
|
1102
|
-
lane.chain = p.then((f) => {
|
|
1103
|
-
if (f !== undefined) {
|
|
1104
|
-
lane.record = structuredClone(f);
|
|
1105
|
-
lane.written = true;
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
flipped = await p;
|
|
1109
|
-
}
|
|
1110
|
-
else {
|
|
1111
|
-
flipped = await doFlip();
|
|
1112
|
-
}
|
|
1113
|
-
if (flipped === undefined)
|
|
1114
|
-
return false;
|
|
1115
|
-
handle.status = "running";
|
|
1116
|
-
handle.parkedCheckpointToken = undefined;
|
|
1117
|
-
handle.resolveParkedStop = undefined;
|
|
1118
|
-
handle.updatedAt = Date.now();
|
|
1119
|
-
handle.completionId = undefined;
|
|
1120
|
-
if (flipped.seq !== undefined)
|
|
1121
|
-
handle.cycleSeq = flipped.seq;
|
|
1122
|
-
return true;
|
|
1123
|
-
}
|
|
1124
|
-
finalizeParkedResume(id) {
|
|
1125
|
-
const handle = this.handles.get(id);
|
|
1126
|
-
if (!handle || handle.type !== "background_agent")
|
|
1127
|
-
return;
|
|
1128
|
-
this.durableAgentWrite(handle, {}, ["parkClaimId"]);
|
|
1129
|
-
}
|
|
1130
|
-
settleBackgroundAgent(id, outcome) {
|
|
1131
|
-
const handle = this.handles.get(id);
|
|
1132
|
-
if (!handle || handle.type !== "background_agent")
|
|
1133
|
-
return undefined;
|
|
1134
|
-
if (handle.status !== "running") {
|
|
1135
|
-
if (handle.status === "killed") {
|
|
1136
|
-
mintCompletionId(handle);
|
|
1137
|
-
let backfilled = false;
|
|
1138
|
-
if (handle.result === undefined && outcome.result !== undefined && outcome.result !== "") {
|
|
1139
|
-
handle.result = outcome.result;
|
|
1140
|
-
handle.resultIsPartial = true;
|
|
1141
|
-
backfilled = true;
|
|
1142
|
-
if (outcome.resultFull !== undefined)
|
|
1143
|
-
handle.resultFull = outcome.resultFull;
|
|
1144
|
-
}
|
|
1145
|
-
if (handle.error === undefined && outcome.error !== undefined && outcome.error !== BG_AGENT_REAP_STOP_ERROR) {
|
|
1146
|
-
handle.error = outcome.error;
|
|
1147
|
-
backfilled = true;
|
|
1148
|
-
}
|
|
1149
|
-
if (backfilled) {
|
|
1150
|
-
handle.updatedAt = Date.now();
|
|
1151
|
-
this.durableAgentWrite(handle, {
|
|
1152
|
-
...(handle.result !== undefined ? { finalOutput: handle.result } : {}),
|
|
1153
|
-
...(handle.resultFull !== undefined ? { finalOutputFull: handle.resultFull } : {}),
|
|
1154
|
-
...(handle.resultIsPartial ? { resultIsPartial: true } : {}),
|
|
1155
|
-
...(handle.error !== undefined ? { error: handle.error } : {}),
|
|
1156
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
return handle.status === "completed" || handle.status === "failed" || handle.status === "killed" ? handle.status : undefined;
|
|
1161
|
-
}
|
|
1162
|
-
handle.status = outcome.status;
|
|
1163
|
-
mintCompletionId(handle);
|
|
1164
|
-
handle.notify = undefined;
|
|
1165
|
-
for (const [, , qResolve] of handle.preAttachQueue ?? [])
|
|
1166
|
-
qResolve?.({ ok: false, reason: "not_running" });
|
|
1167
|
-
handle.preAttachQueue = undefined;
|
|
1168
|
-
for (const r of [...(handle.inFlightDirect ?? [])])
|
|
1169
|
-
r({ ok: false, reason: "not_running" });
|
|
1170
|
-
handle.inFlightDirect = undefined;
|
|
1171
|
-
if (outcome.status === "killed" && handle.stoppedBy === undefined) {
|
|
1172
|
-
handle.stoppedBy = outcome.stoppedBy ?? handle.stopSource ?? "system";
|
|
1173
|
-
}
|
|
1174
|
-
if (outcome.result !== undefined)
|
|
1175
|
-
handle.result = outcome.result;
|
|
1176
|
-
if (outcome.resultFull !== undefined)
|
|
1177
|
-
handle.resultFull = outcome.resultFull;
|
|
1178
|
-
if (outcome.status === "killed" && outcome.result !== undefined && outcome.result !== "") {
|
|
1179
|
-
handle.resultIsPartial = true;
|
|
1180
|
-
}
|
|
1181
|
-
if (outcome.error !== undefined && !(outcome.status === "killed" && outcome.error === BG_AGENT_REAP_STOP_ERROR)) {
|
|
1182
|
-
handle.error = outcome.error;
|
|
1183
|
-
}
|
|
1184
|
-
handle.updatedAt = Date.now();
|
|
1185
|
-
if (outcome.seq !== undefined)
|
|
1186
|
-
handle.cycleSeq = outcome.seq;
|
|
1187
|
-
this.pokeBgQuiescence(handle.owner);
|
|
1188
|
-
this.durableAgentWrite(handle, {
|
|
1189
|
-
status: outcome.status,
|
|
1190
|
-
...(outcome.seq !== undefined ? { seq: outcome.seq } : {}),
|
|
1191
|
-
settledAt: Date.now(),
|
|
1192
|
-
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
1193
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1194
|
-
...(handle.result !== undefined ? { finalOutput: handle.result } : {}),
|
|
1195
|
-
...(handle.resultFull !== undefined ? { finalOutputFull: handle.resultFull } : {}),
|
|
1196
|
-
...(handle.resultIsPartial ? { resultIsPartial: true } : {}),
|
|
1197
|
-
...(handle.error !== undefined ? { error: handle.error } : {}),
|
|
1198
|
-
}, ["parkedCheckpointToken", "parkClaimId", "parkedAt"]);
|
|
1199
|
-
return outcome.status;
|
|
1200
|
-
}
|
|
1201
|
-
abortBackgroundAgentsForOwner(access, opts) {
|
|
1202
|
-
let aborted = 0;
|
|
1203
|
-
for (const handle of this.handles.values()) {
|
|
1204
|
-
if (handle.type !== "background_agent" || handle.status !== "running" || !canAccess(handle, access))
|
|
1205
|
-
continue;
|
|
1206
|
-
if (opts?.skipSessionScoped && handle.sessionScoped)
|
|
1207
|
-
continue;
|
|
1208
|
-
if (opts?.sessionScopedOnly && !handle.sessionScoped)
|
|
1209
|
-
continue;
|
|
1210
|
-
this.markStopSource(handle.id, "parent");
|
|
1211
|
-
handle.abort.abort();
|
|
1212
|
-
aborted++;
|
|
1213
|
-
}
|
|
1214
|
-
return aborted;
|
|
1215
|
-
}
|
|
1216
315
|
markStopSourceForOwner(access, source, opts) {
|
|
1217
316
|
let marked = 0;
|
|
1218
317
|
for (const handle of this.handles.values()) {
|
|
@@ -1639,243 +738,10 @@ export class TaskRegistry {
|
|
|
1639
738
|
return out;
|
|
1640
739
|
}
|
|
1641
740
|
registerMonitor(input) {
|
|
1642
|
-
|
|
1643
|
-
const id = this.mintTaskId("monitor");
|
|
1644
|
-
const now = input.now ?? Date.now();
|
|
1645
|
-
const timers = input.timers ?? defaultMonitorTimers;
|
|
1646
|
-
const persistent = input.persistent === true;
|
|
1647
|
-
const timeoutMs = persistent
|
|
1648
|
-
? undefined
|
|
1649
|
-
: Math.min(MONITOR_MAX_TIMEOUT_MS, Math.max(1, Math.floor(input.timeoutMs ?? MONITOR_DEFAULT_TIMEOUT_MS)));
|
|
1650
|
-
const handle = {
|
|
1651
|
-
id,
|
|
1652
|
-
type: "monitor",
|
|
1653
|
-
status: "running",
|
|
1654
|
-
...(input.description !== undefined ? { description: input.description } : {}),
|
|
1655
|
-
...(input.owner !== undefined ? { owner: input.owner } : {}),
|
|
1656
|
-
...(input.scope !== undefined ? { scope: input.scope } : {}),
|
|
1657
|
-
...(input.toolUseId !== undefined ? { toolUseId: input.toolUseId } : {}),
|
|
1658
|
-
...(persistent ? { persistent: true } : {}),
|
|
1659
|
-
...(input.sessionScoped ? { sessionScoped: true } : {}),
|
|
1660
|
-
createdAt: now,
|
|
1661
|
-
updatedAt: now,
|
|
1662
|
-
env: input.env,
|
|
1663
|
-
shellId: input.shellId,
|
|
1664
|
-
spool: { stdout: "", stderr: "", rolledChars: 0 },
|
|
1665
|
-
lineBuf: "",
|
|
1666
|
-
seq: 0,
|
|
1667
|
-
batchTimes: [],
|
|
1668
|
-
batchWindowMs: Math.max(1, Math.floor(input.batchWindowMs ?? MONITOR_BATCH_WINDOW_MS)),
|
|
1669
|
-
maxBatchesPerMinute: Math.max(1, Math.floor(input.maxBatchesPerMinute ?? MONITOR_MAX_BATCHES_PER_MINUTE)),
|
|
1670
|
-
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt: timers.now() + timeoutMs } : {}),
|
|
1671
|
-
timers,
|
|
1672
|
-
...(input.onEvent !== undefined ? { onEvent: input.onEvent } : {}),
|
|
1673
|
-
};
|
|
1674
|
-
this.handles.set(id, handle);
|
|
1675
|
-
this.startMonitorWatcher(id);
|
|
1676
|
-
return id;
|
|
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
|
-
}
|
|
1696
|
-
startMonitorWatcher(id) {
|
|
1697
|
-
const handle = this.handles.get(id);
|
|
1698
|
-
if (!handle || handle.type !== "monitor")
|
|
1699
|
-
return;
|
|
1700
|
-
const timers = handle.timers;
|
|
1701
|
-
let ticking = false;
|
|
1702
|
-
const MAX_RETRYABLE_POLL_FAILURES = 3;
|
|
1703
|
-
let pollFailures = 0;
|
|
1704
|
-
const stop = () => {
|
|
1705
|
-
if (handle.watcher !== undefined) {
|
|
1706
|
-
timers.clearInterval(handle.watcher);
|
|
1707
|
-
handle.watcher = undefined;
|
|
1708
|
-
}
|
|
1709
|
-
};
|
|
1710
|
-
const emit = (n) => this.emitMonitorEvent(handle, n);
|
|
1711
|
-
const pendingFinalLines = (lines) => {
|
|
1712
|
-
const out = handle.lineBuf !== "" ? [...lines, handle.lineBuf] : lines;
|
|
1713
|
-
handle.lineBuf = "";
|
|
1714
|
-
return out;
|
|
1715
|
-
};
|
|
1716
|
-
const terminal = (status, summary, finalLines) => {
|
|
1717
|
-
handle.updatedAt = Date.now();
|
|
1718
|
-
mintCompletionId(handle);
|
|
1719
|
-
stop();
|
|
1720
|
-
this.notifyTerminalOnce(handle, () => emit({
|
|
1721
|
-
task_id: id,
|
|
1722
|
-
task_type: "monitor",
|
|
1723
|
-
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
1724
|
-
status,
|
|
1725
|
-
...(status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
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 } : {}),
|
|
1729
|
-
}));
|
|
1730
|
-
};
|
|
1731
|
-
const tick = async () => {
|
|
1732
|
-
if (ticking)
|
|
1733
|
-
return;
|
|
1734
|
-
ticking = true;
|
|
1735
|
-
handle.tickInFlight = true;
|
|
1736
|
-
try {
|
|
1737
|
-
if (this.handles.get(id) !== handle)
|
|
1738
|
-
return stop();
|
|
1739
|
-
if (handle.status !== "running")
|
|
1740
|
-
return stop();
|
|
1741
|
-
const r = await handle.env.pollBackground(handle.shellId);
|
|
1742
|
-
if (this.handles.get(id) !== handle || handle.status !== "running")
|
|
1743
|
-
return stop();
|
|
1744
|
-
if (!r.ok) {
|
|
1745
|
-
if ((r.error.code === "timeout" || r.error.code === "io") && ++pollFailures <= MAX_RETRYABLE_POLL_FAILURES)
|
|
1746
|
-
return;
|
|
1747
|
-
handle.status = "failed";
|
|
1748
|
-
return terminal("failed", `monitor lost its process (${r.error.message}); watch ended.`, pendingFinalLines([]));
|
|
1749
|
-
}
|
|
1750
|
-
pollFailures = 0;
|
|
1751
|
-
const spool = handle.spool;
|
|
1752
|
-
const lines = this.absorbMonitorPoll(handle, r.value);
|
|
1753
|
-
handle.status = statusFromBackground(r.value.status, r.value.exitCode);
|
|
1754
|
-
if (r.value.status !== "running") {
|
|
1755
|
-
spool.exitCode = r.value.exitCode;
|
|
1756
|
-
if (handle.status === "killed" && handle.stoppedBy === undefined)
|
|
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
|
-
}
|
|
1763
|
-
const label = r.value.status === "exited"
|
|
1764
|
-
? `exited(code ${r.value.exitCode}); watch ended.`
|
|
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.`;
|
|
1768
|
-
return terminal(handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed", label, pendingFinalLines(lines));
|
|
1769
|
-
}
|
|
1770
|
-
const now = timers.now();
|
|
1771
|
-
if (handle.deadlineAt !== undefined && now >= handle.deadlineAt) {
|
|
1772
|
-
this.markStopSource(id, "timeout");
|
|
1773
|
-
const k = await handle.env.killBackground(handle.shellId);
|
|
1774
|
-
if (this.handles.get(id) !== handle || handle.status !== "running")
|
|
1775
|
-
return stop();
|
|
1776
|
-
if (!k.ok && k.error.code !== "not_found") {
|
|
1777
|
-
handle.status = "failed";
|
|
1778
|
-
return terminal("failed", `timed out after ${handle.timeoutMs}ms, but the kill failed (${k.error.message}); the process may still be running. Watch ended.`, pendingFinalLines(lines));
|
|
1779
|
-
}
|
|
1780
|
-
handle.status = "killed";
|
|
1781
|
-
handle.stoppedBy = handle.stopSource ?? "timeout";
|
|
1782
|
-
return terminal("killed", `timed out after ${handle.timeoutMs}ms; the process was killed and the watch ended.`, pendingFinalLines(lines));
|
|
1783
|
-
}
|
|
1784
|
-
if (lines.length === 0) {
|
|
1785
|
-
if (r.value.stdout.length > 0 || r.value.stderr.length > 0)
|
|
1786
|
-
handle.updatedAt = Date.now();
|
|
1787
|
-
return;
|
|
1788
|
-
}
|
|
1789
|
-
const cutoff = now - 60_000;
|
|
1790
|
-
handle.batchTimes = handle.batchTimes.filter((t) => t > cutoff);
|
|
1791
|
-
handle.batchTimes.push(now);
|
|
1792
|
-
if (handle.batchTimes.length > handle.maxBatchesPerMinute) {
|
|
1793
|
-
this.markStopSource(id, "system");
|
|
1794
|
-
const k = await handle.env.killBackground(handle.shellId);
|
|
1795
|
-
if (this.handles.get(id) !== handle || handle.status !== "running")
|
|
1796
|
-
return stop();
|
|
1797
|
-
if (!k.ok && k.error.code !== "not_found") {
|
|
1798
|
-
handle.status = "failed";
|
|
1799
|
-
return terminal("failed", `auto-stop triggered (more than ${handle.maxBatchesPerMinute} notification batches within a minute), but the kill failed (${k.error.message}); the process may still be running. Watch ended.`, pendingFinalLines(lines));
|
|
1800
|
-
}
|
|
1801
|
-
handle.status = "killed";
|
|
1802
|
-
handle.stoppedBy = handle.stopSource ?? "system";
|
|
1803
|
-
return terminal("killed", `auto-stopped: it produced too many events (more than ${handle.maxBatchesPerMinute} notification batches within a minute). ` +
|
|
1804
|
-
`The process was killed; its full output remains readable via TaskOutput. Restart it with a quieter command (filter its output) if you still need the watch.`, pendingFinalLines(lines));
|
|
1805
|
-
}
|
|
1806
|
-
handle.seq += 1;
|
|
1807
|
-
handle.updatedAt = Date.now();
|
|
1808
|
-
emit({
|
|
1809
|
-
task_id: id,
|
|
1810
|
-
task_type: "monitor",
|
|
1811
|
-
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
1812
|
-
status: "event",
|
|
1813
|
-
seq: handle.seq,
|
|
1814
|
-
lines,
|
|
1815
|
-
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${lines.length} new output line(s)${firstDropNote(spool)}`,
|
|
1816
|
-
result: clipMonitorEvent(lines.join("\n")),
|
|
1817
|
-
});
|
|
1818
|
-
}
|
|
1819
|
-
catch (e) {
|
|
1820
|
-
if (this.handles.get(id) !== handle || handle.status !== "running")
|
|
1821
|
-
return stop();
|
|
1822
|
-
if (++pollFailures <= MAX_RETRYABLE_POLL_FAILURES)
|
|
1823
|
-
return;
|
|
1824
|
-
handle.status = "failed";
|
|
1825
|
-
terminal("failed", `monitor watcher crashed (${e instanceof Error ? e.message : String(e)}); watch ended.`, pendingFinalLines([]));
|
|
1826
|
-
}
|
|
1827
|
-
finally {
|
|
1828
|
-
ticking = false;
|
|
1829
|
-
handle.tickInFlight = undefined;
|
|
1830
|
-
}
|
|
1831
|
-
};
|
|
1832
|
-
handle.watcher = timers.setInterval(tick, handle.batchWindowMs);
|
|
741
|
+
return registerMonitorLane(this.core, input);
|
|
1833
742
|
}
|
|
1834
743
|
registerWorkflow(input) {
|
|
1835
|
-
|
|
1836
|
-
const id = input.runId;
|
|
1837
|
-
if (this.handles.has(id))
|
|
1838
|
-
throw new Error(`TaskRegistry: task id already registered: ${id}`);
|
|
1839
|
-
const now = input.now ?? Date.now();
|
|
1840
|
-
const originatingSessionId = input.originatingSessionId;
|
|
1841
|
-
const handle = {
|
|
1842
|
-
id,
|
|
1843
|
-
type: "workflow",
|
|
1844
|
-
status: "running",
|
|
1845
|
-
...(input.description !== undefined ? { description: input.description } : {}),
|
|
1846
|
-
...(input.owner !== undefined ? { owner: input.owner } : {}),
|
|
1847
|
-
...(input.scope !== undefined ? { scope: input.scope } : {}),
|
|
1848
|
-
...(input.toolUseId !== undefined ? { toolUseId: input.toolUseId } : {}),
|
|
1849
|
-
createdAt: now,
|
|
1850
|
-
updatedAt: now,
|
|
1851
|
-
runId: id,
|
|
1852
|
-
handle: input.handle,
|
|
1853
|
-
...(input.store !== undefined ? { store: input.store } : {}),
|
|
1854
|
-
...(originatingSessionId !== undefined ? { originatingSessionId, sessionScoped: true } : {}),
|
|
1855
|
-
...(input.onServedTerminal !== undefined ? { onServedTerminal: input.onServedTerminal } : {}),
|
|
1856
|
-
};
|
|
1857
|
-
this.handles.set(id, handle);
|
|
1858
|
-
void input.handle.done.then((res) => {
|
|
1859
|
-
handle.status = "completed";
|
|
1860
|
-
const run = res?.run;
|
|
1861
|
-
const r = run?.resultFull ?? run?.result;
|
|
1862
|
-
if (typeof r === "string")
|
|
1863
|
-
handle.result = r;
|
|
1864
|
-
if (run?.completionId !== undefined)
|
|
1865
|
-
handle.completionId = run.completionId;
|
|
1866
|
-
handle.updatedAt = Date.now();
|
|
1867
|
-
}, (err) => {
|
|
1868
|
-
if (handle.status !== "cancelled")
|
|
1869
|
-
handle.status = "failed";
|
|
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;
|
|
1876
|
-
handle.updatedAt = Date.now();
|
|
1877
|
-
});
|
|
1878
|
-
return id;
|
|
744
|
+
return registerWorkflowLane(this.core, input);
|
|
1879
745
|
}
|
|
1880
746
|
async pollTask(id, access, opts = {}) {
|
|
1881
747
|
const taskId = id.trim();
|
|
@@ -1939,38 +805,6 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1939
805
|
}
|
|
1940
806
|
return noTask(taskId);
|
|
1941
807
|
}
|
|
1942
|
-
serveDurableAgentRow(row) {
|
|
1943
|
-
if (row.status === "parked") {
|
|
1944
|
-
return {
|
|
1945
|
-
content: delimitUntrusted(`TaskOutput ${row.handle}`, `status: parked
|
|
1946
|
-
The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
|
|
1947
|
-
details: {
|
|
1948
|
-
task_id: row.handle,
|
|
1949
|
-
type: "background_agent",
|
|
1950
|
-
status: "parked",
|
|
1951
|
-
retrieval_status: "success",
|
|
1952
|
-
...(row.seq !== undefined ? { seq: row.seq } : {}),
|
|
1953
|
-
},
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
const body = `status: ${row.status}
|
|
1957
|
-
${row.error ? `error: ${row.error}
|
|
1958
|
-
` : ""}${row.finalOutput ? `--- result${row.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
|
|
1959
|
-
${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}`;
|
|
1960
|
-
return {
|
|
1961
|
-
content: delimitUntrusted(`TaskOutput ${row.handle}`, body),
|
|
1962
|
-
details: {
|
|
1963
|
-
task_id: row.handle,
|
|
1964
|
-
type: "background_agent",
|
|
1965
|
-
status: row.status,
|
|
1966
|
-
retrieval_status: "success",
|
|
1967
|
-
...(row.status === "killed" && row.stoppedBy !== undefined ? { stoppedBy: row.stoppedBy } : {}),
|
|
1968
|
-
...(row.seq !== undefined ? { seq: row.seq } : {}),
|
|
1969
|
-
...(row.resultIsPartial ? { partial_result: true } : {}),
|
|
1970
|
-
...(row.completionId !== undefined ? { completionId: row.completionId } : {}),
|
|
1971
|
-
},
|
|
1972
|
-
};
|
|
1973
|
-
}
|
|
1974
808
|
async stopTask(id, access, opts = {}) {
|
|
1975
809
|
const taskId = id.trim();
|
|
1976
810
|
const handle = this.resolveInternal(taskId, access);
|
|
@@ -2026,265 +860,24 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2026
860
|
retrieval_status: row.status === "parked" ? "not_ready" : "success",
|
|
2027
861
|
...(row.status === "running" ? { error: "not_local" } : {}),
|
|
2028
862
|
...(row.status === "parked" ? { error: "parked_pending_approval" } : {}),
|
|
2029
|
-
},
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
const byName = this.resolveBackgroundAgentByName(taskId, access, { preferRunning: true });
|
|
2034
|
-
if (byName.status === "found")
|
|
2035
|
-
return this.stopBackgroundAgent(byName.handle);
|
|
2036
|
-
if (byName.status === "ambiguous") {
|
|
2037
|
-
return {
|
|
2038
|
-
content: byName.message,
|
|
2039
|
-
details: { task_id: taskId, retrieval_status: "not_ready", error: "ambiguous" },
|
|
2040
|
-
isError: true,
|
|
2041
|
-
};
|
|
2042
|
-
}
|
|
2043
|
-
return noTaskForStop(taskId, {
|
|
2044
|
-
...(byName.suggestion !== undefined ? { suggestion: byName.suggestion } : {}),
|
|
2045
|
-
runningAgents: this.runningBackgroundAgentLabels(access),
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
resolveBackgroundAgentByName(name, access, opts) {
|
|
2049
|
-
const key = normalizeAgentName(name);
|
|
2050
|
-
if (key === "")
|
|
2051
|
-
return { status: "not_found" };
|
|
2052
|
-
const latestOf = (pool) => {
|
|
2053
|
-
let best;
|
|
2054
|
-
for (const h of pool)
|
|
2055
|
-
if (best === undefined || h.createdAt >= best.createdAt)
|
|
2056
|
-
best = h;
|
|
2057
|
-
return best;
|
|
2058
|
-
};
|
|
2059
|
-
const named = [];
|
|
2060
|
-
for (const h of this.handles.values()) {
|
|
2061
|
-
if (h.type !== "background_agent" || h.name === undefined || !canAccess(h, access))
|
|
2062
|
-
continue;
|
|
2063
|
-
if (h.name === name || normalizeAgentName(h.name) === key)
|
|
2064
|
-
named.push(h);
|
|
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
|
-
}
|
|
2077
|
-
const namedWinner = latestOf(named);
|
|
2078
|
-
if (namedWinner !== undefined)
|
|
2079
|
-
return { status: "found", handle: namedWinner };
|
|
2080
|
-
const exact = [];
|
|
2081
|
-
for (const h of this.handles.values()) {
|
|
2082
|
-
if (h.type !== "background_agent" || !canAccess(h, access))
|
|
2083
|
-
continue;
|
|
2084
|
-
if (h.description === name)
|
|
2085
|
-
exact.push(h);
|
|
2086
|
-
}
|
|
2087
|
-
const exactRunning = exact.filter((h) => h.status === "running");
|
|
2088
|
-
const exactPool = exactRunning.length > 0 ? exactRunning : exact;
|
|
2089
|
-
if (exactPool.length === 1)
|
|
2090
|
-
return { status: "found", handle: exactPool[0] };
|
|
2091
|
-
if (exactPool.length > 1) {
|
|
2092
|
-
return {
|
|
2093
|
-
status: "ambiguous",
|
|
2094
|
-
message: `Multiple background agents match "${name}": ${exactPool.map((h) => (h.description ? `${h.id} (${h.description})` : h.id)).join(", ")}. Use the task ID.`,
|
|
2095
|
-
};
|
|
2096
|
-
}
|
|
2097
|
-
const matches = [];
|
|
2098
|
-
for (const h of this.handles.values()) {
|
|
2099
|
-
if (h.type !== "background_agent" || !canAccess(h, access))
|
|
2100
|
-
continue;
|
|
2101
|
-
if (h.description !== undefined && normalizeAgentName(h.description) === key)
|
|
2102
|
-
matches.push(h);
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
}
|
|
2103
866
|
}
|
|
2104
|
-
const
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
if (pool.length > 1) {
|
|
867
|
+
const byName = this.resolveBackgroundAgentByName(taskId, access, { preferRunning: true });
|
|
868
|
+
if (byName.status === "found")
|
|
869
|
+
return this.stopBackgroundAgent(byName.handle);
|
|
870
|
+
if (byName.status === "ambiguous") {
|
|
2109
871
|
return {
|
|
2110
|
-
|
|
2111
|
-
|
|
872
|
+
content: byName.message,
|
|
873
|
+
details: { task_id: taskId, retrieval_status: "not_ready", error: "ambiguous" },
|
|
874
|
+
isError: true,
|
|
2112
875
|
};
|
|
2113
876
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
continue;
|
|
2118
|
-
for (const label of [h.name, h.description]) {
|
|
2119
|
-
if (label === undefined)
|
|
2120
|
-
continue;
|
|
2121
|
-
const k = normalizeAgentName(label);
|
|
2122
|
-
if (k !== "" && !byKey.has(k))
|
|
2123
|
-
byKey.set(k, label);
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
const closest = closestName(key, byKey.keys());
|
|
2127
|
-
return { status: "not_found", ...(closest !== undefined ? { suggestion: byKey.get(closest) } : {}) };
|
|
2128
|
-
}
|
|
2129
|
-
markRetainedContinuation(id) {
|
|
2130
|
-
const handle = this.handles.get(id);
|
|
2131
|
-
if (handle && handle.type === "background_agent")
|
|
2132
|
-
handle.retainedContinuation = true;
|
|
2133
|
-
}
|
|
2134
|
-
reviveBackgroundAgent(id, access, abort) {
|
|
2135
|
-
if (this.reapingHandles.has(id) || this.claimingHandles.has(id))
|
|
2136
|
-
return { ok: false, reason: "not_found" };
|
|
2137
|
-
const handle = this.handles.get(id);
|
|
2138
|
-
if (!handle || handle.type !== "background_agent" || !canAccess(handle, access))
|
|
2139
|
-
return { ok: false, reason: "not_found" };
|
|
2140
|
-
if (handle.status === "running" || handle.status === "pending")
|
|
2141
|
-
return { ok: false, reason: "still_running" };
|
|
2142
|
-
if (handle.status === "parked")
|
|
2143
|
-
return { ok: false, reason: "still_running" };
|
|
2144
|
-
handle.status = "running";
|
|
2145
|
-
handle.channelState = "attaching";
|
|
2146
|
-
handle.notify = undefined;
|
|
2147
|
-
if (abort !== undefined)
|
|
2148
|
-
handle.abort = abort;
|
|
2149
|
-
handle.result = undefined;
|
|
2150
|
-
handle.resultFull = undefined;
|
|
2151
|
-
handle.error = undefined;
|
|
2152
|
-
handle.resultIsPartial = undefined;
|
|
2153
|
-
handle.stopSource = undefined;
|
|
2154
|
-
handle.completionId = undefined;
|
|
2155
|
-
handle.reviveCycle = (handle.reviveCycle ?? 0) + 1;
|
|
2156
|
-
handle.cycleSeq = (handle.cycleSeq ?? 1) + 1;
|
|
2157
|
-
handle.updatedAt = Date.now();
|
|
2158
|
-
this.durableAgentWrite(handle, { status: "running" }, ["settledAt", "stoppedBy", "finalOutput", "finalOutputFull", "error", "resultIsPartial", "completionId", "summary", "recentSteps", "editedFiles", "usage"]);
|
|
2159
|
-
return { ok: true, cycle: handle.reviveCycle };
|
|
2160
|
-
}
|
|
2161
|
-
settleRevivedAgent(id, cycle, outcome) {
|
|
2162
|
-
const handle = this.handles.get(id);
|
|
2163
|
-
if (!handle || handle.type !== "background_agent")
|
|
2164
|
-
return undefined;
|
|
2165
|
-
if ((handle.reviveCycle ?? 0) !== cycle)
|
|
2166
|
-
return undefined;
|
|
2167
|
-
return this.settleBackgroundAgent(id, outcome);
|
|
2168
|
-
}
|
|
2169
|
-
unmarkRetainedContinuation(id) {
|
|
2170
|
-
const handle = this.handles.get(id);
|
|
2171
|
-
if (handle && handle.type === "background_agent")
|
|
2172
|
-
handle.retainedContinuation = false;
|
|
2173
|
-
}
|
|
2174
|
-
attachAgentNotify(id, notify, cycle) {
|
|
2175
|
-
const handle = this.handles.get(id);
|
|
2176
|
-
if (!handle || handle.type !== "background_agent" || handle.status !== "running")
|
|
2177
|
-
return;
|
|
2178
|
-
if (cycle !== undefined && (handle.reviveCycle ?? 0) !== cycle)
|
|
2179
|
-
return;
|
|
2180
|
-
handle.notify = notify;
|
|
2181
|
-
handle.channelState = "attaching";
|
|
2182
|
-
const live = handle.preAttachQueue;
|
|
2183
|
-
if (live !== undefined && live.length > 0) {
|
|
2184
|
-
void (async () => {
|
|
2185
|
-
while (handle.preAttachQueue === live && live.length > 0) {
|
|
2186
|
-
const [qPayload, qOpts, qResolve] = live[0];
|
|
2187
|
-
let result;
|
|
2188
|
-
try {
|
|
2189
|
-
const d = await notify(qPayload, qOpts);
|
|
2190
|
-
result =
|
|
2191
|
-
d === "parked" && handle.retainedContinuation !== true
|
|
2192
|
-
? { ok: false, reason: "not_running" }
|
|
2193
|
-
: d === "parked"
|
|
2194
|
-
? { ok: true, disposition: "parked" }
|
|
2195
|
-
: { ok: true, disposition: "queued" };
|
|
2196
|
-
}
|
|
2197
|
-
catch {
|
|
2198
|
-
result = { ok: false, reason: "not_running" };
|
|
2199
|
-
}
|
|
2200
|
-
if (handle.preAttachQueue !== live)
|
|
2201
|
-
return;
|
|
2202
|
-
live.shift();
|
|
2203
|
-
qResolve?.(result);
|
|
2204
|
-
}
|
|
2205
|
-
if (handle.preAttachQueue === live) {
|
|
2206
|
-
handle.preAttachQueue = undefined;
|
|
2207
|
-
handle.channelState = "attached";
|
|
2208
|
-
}
|
|
2209
|
-
})();
|
|
2210
|
-
}
|
|
2211
|
-
else {
|
|
2212
|
-
handle.preAttachQueue = undefined;
|
|
2213
|
-
handle.channelState = "attached";
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
async deliverToRunningAgent(id, access, notification, opts) {
|
|
2217
|
-
const handle = this.handles.get(id);
|
|
2218
|
-
if (!handle || handle.type !== "background_agent" || !canAccess(handle, access))
|
|
2219
|
-
return { ok: false, reason: "not_found" };
|
|
2220
|
-
if (handle.status !== "running")
|
|
2221
|
-
return { ok: false, reason: "not_running" };
|
|
2222
|
-
const bounded = (p, fallback, onTimeout) => new Promise((resolve) => {
|
|
2223
|
-
const t = setTimeout(() => {
|
|
2224
|
-
try {
|
|
2225
|
-
onTimeout?.();
|
|
2226
|
-
}
|
|
2227
|
-
catch {
|
|
2228
|
-
}
|
|
2229
|
-
resolve(fallback);
|
|
2230
|
-
}, this.deliverConfirmTimeoutMs);
|
|
2231
|
-
t.unref?.();
|
|
2232
|
-
void p.then((v) => {
|
|
2233
|
-
clearTimeout(t);
|
|
2234
|
-
resolve(v);
|
|
2235
|
-
}, () => {
|
|
2236
|
-
clearTimeout(t);
|
|
2237
|
-
resolve(fallback);
|
|
2238
|
-
});
|
|
877
|
+
return noTaskForStop(taskId, {
|
|
878
|
+
...(byName.suggestion !== undefined ? { suggestion: byName.suggestion } : {}),
|
|
879
|
+
runningAgents: this.runningBackgroundAgentLabels(access),
|
|
2239
880
|
});
|
|
2240
|
-
if (handle.notify === undefined || handle.channelState === "attaching") {
|
|
2241
|
-
if (handle.channelState !== "attaching")
|
|
2242
|
-
return { ok: false, reason: "no_channel" };
|
|
2243
|
-
const q = (handle.preAttachQueue ??= []);
|
|
2244
|
-
if (q.length >= 8)
|
|
2245
|
-
return { ok: false, reason: "no_channel" };
|
|
2246
|
-
return bounded(new Promise((resolve) => {
|
|
2247
|
-
q.push([notification, opts, resolve]);
|
|
2248
|
-
}), { ok: true, disposition: "pending" });
|
|
2249
|
-
}
|
|
2250
|
-
let detachOnTimeout;
|
|
2251
|
-
return await bounded(new Promise((resolve) => {
|
|
2252
|
-
const inflight = (handle.inFlightDirect ??= new Set());
|
|
2253
|
-
const settleOnce = (r) => {
|
|
2254
|
-
if (!inflight.has(settleOnce))
|
|
2255
|
-
return;
|
|
2256
|
-
inflight.delete(settleOnce);
|
|
2257
|
-
resolve(r);
|
|
2258
|
-
};
|
|
2259
|
-
inflight.add(settleOnce);
|
|
2260
|
-
detachOnTimeout = () => inflight.delete(settleOnce);
|
|
2261
|
-
void (async () => {
|
|
2262
|
-
let result;
|
|
2263
|
-
try {
|
|
2264
|
-
const d = await handle.notify?.(notification, opts);
|
|
2265
|
-
if (d === "parked") {
|
|
2266
|
-
result = handle.retainedContinuation === true ? { ok: true, disposition: "parked" } : { ok: false, reason: "not_running" };
|
|
2267
|
-
}
|
|
2268
|
-
else {
|
|
2269
|
-
result = { ok: true, disposition: "queued" };
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
catch {
|
|
2273
|
-
result = { ok: false, reason: "not_running" };
|
|
2274
|
-
}
|
|
2275
|
-
settleOnce(result);
|
|
2276
|
-
})();
|
|
2277
|
-
}), { ok: true, disposition: "pending" }, () => detachOnTimeout?.());
|
|
2278
|
-
}
|
|
2279
|
-
runningBackgroundAgentLabels(access) {
|
|
2280
|
-
const out = [];
|
|
2281
|
-
for (const h of this.handles.values()) {
|
|
2282
|
-
if (h.type !== "background_agent" || h.status !== "running" || !canAccess(h, access))
|
|
2283
|
-
continue;
|
|
2284
|
-
const label = h.name ?? h.description;
|
|
2285
|
-
out.push(label ? `${h.id} (${label})` : h.id);
|
|
2286
|
-
}
|
|
2287
|
-
return out;
|
|
2288
881
|
}
|
|
2289
882
|
deliverConfirmTimeoutMs = 10_000;
|
|
2290
883
|
lastGcAt = 0;
|
|
@@ -2501,70 +1094,8 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2501
1094
|
await sleepPollStep(deadline, signal);
|
|
2502
1095
|
}
|
|
2503
1096
|
}
|
|
2504
|
-
fireServedTerminal(handle) {
|
|
2505
|
-
const cb = handle.onServedTerminal;
|
|
2506
|
-
if (!cb)
|
|
2507
|
-
return;
|
|
2508
|
-
handle.onServedTerminal = undefined;
|
|
2509
|
-
try {
|
|
2510
|
-
cb();
|
|
2511
|
-
}
|
|
2512
|
-
catch {
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
1097
|
async pollWorkflow(handle, access, deadline, signal) {
|
|
2516
|
-
|
|
2517
|
-
if (store) {
|
|
2518
|
-
for (;;) {
|
|
2519
|
-
const run = await store.get(handle.runId);
|
|
2520
|
-
if (!run || !canAccessWorkflowRun(run, access))
|
|
2521
|
-
break;
|
|
2522
|
-
if (run.status !== "running" || deadline === undefined || Date.now() >= deadline || signal?.aborted) {
|
|
2523
|
-
const out = formatWorkflowRun(run);
|
|
2524
|
-
if (run.status === "running" && deadline !== undefined && Date.now() >= deadline)
|
|
2525
|
-
out.details.retrieval_status = "timeout";
|
|
2526
|
-
else if (run.status !== "running")
|
|
2527
|
-
this.fireServedTerminal(handle);
|
|
2528
|
-
return out;
|
|
2529
|
-
}
|
|
2530
|
-
await sleepPollStep(deadline, signal);
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
|
|
2534
|
-
await sleepPollStep(deadline, signal);
|
|
2535
|
-
}
|
|
2536
|
-
if (handle.status !== "running")
|
|
2537
|
-
this.fireServedTerminal(handle);
|
|
2538
|
-
return {
|
|
2539
|
-
content: JSON.stringify({
|
|
2540
|
-
task_id: handle.id,
|
|
2541
|
-
runId: handle.id,
|
|
2542
|
-
type: "workflow",
|
|
2543
|
-
status: handle.status,
|
|
2544
|
-
...(handle.status === "completed" && handle.result !== undefined
|
|
2545
|
-
? { result: delimitUntrusted("workflow result", handle.result) }
|
|
2546
|
-
: {}),
|
|
2547
|
-
...(handle.status === "failed" && handle.error !== undefined
|
|
2548
|
-
? { error: delimitUntrusted("workflow error", handle.error) }
|
|
2549
|
-
: {}),
|
|
2550
|
-
note: handle.status === "running"
|
|
2551
|
-
? "still running — poll again shortly."
|
|
2552
|
-
: handle.status === "completed"
|
|
2553
|
-
? handle.result !== undefined
|
|
2554
|
-
? "completed — `result` above is the workflow's return value."
|
|
2555
|
-
: "completed — its result was delivered on the completion notification."
|
|
2556
|
-
: handle.status === "failed" && handle.error !== undefined
|
|
2557
|
-
? "failed — see error."
|
|
2558
|
-
: "workflow reached a terminal state.",
|
|
2559
|
-
}),
|
|
2560
|
-
details: {
|
|
2561
|
-
task_id: handle.id,
|
|
2562
|
-
type: "workflow",
|
|
2563
|
-
status: handle.status,
|
|
2564
|
-
retrieval_status: handle.status === "running" && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success",
|
|
2565
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2566
|
-
},
|
|
2567
|
-
};
|
|
1098
|
+
return pollWorkflowLane(handle, access, deadline, signal);
|
|
2568
1099
|
}
|
|
2569
1100
|
async stopBackgroundBash(handle) {
|
|
2570
1101
|
const wasRunning = handle.status === "running";
|
|
@@ -2637,367 +1168,30 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
2637
1168
|
};
|
|
2638
1169
|
}
|
|
2639
1170
|
async pollMonitor(handle, filter, deadline, signal) {
|
|
2640
|
-
|
|
2641
|
-
if (filter !== undefined) {
|
|
2642
|
-
try {
|
|
2643
|
-
rx = new RegExp(filter);
|
|
2644
|
-
}
|
|
2645
|
-
catch {
|
|
2646
|
-
return {
|
|
2647
|
-
content: `Error (TaskOutput): invalid filter regex: ${filter}`,
|
|
2648
|
-
details: { task_id: handle.id, type: "monitor", status: handle.status, retrieval_status: "not_ready" },
|
|
2649
|
-
isError: true,
|
|
2650
|
-
};
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
|
|
2654
|
-
await sleepPollStep(deadline, signal);
|
|
2655
|
-
}
|
|
2656
|
-
const spool = handle.spool;
|
|
2657
|
-
const shape = (text) => clipTaskOutput(rx ? text.split(/\r?\n/).filter((l) => rx.test(l)).join("\n") : text, handle.outputFile);
|
|
2658
|
-
const running = handle.status === "running";
|
|
2659
|
-
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
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;
|
|
2662
|
-
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)\n` : "";
|
|
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` +
|
|
2671
|
-
`--- stderr ---\n${shape(spool.stderr) || "(none)"}`;
|
|
2672
|
-
return {
|
|
2673
|
-
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
2674
|
-
details: {
|
|
2675
|
-
task_id: handle.id,
|
|
2676
|
-
type: "monitor",
|
|
2677
|
-
status: handle.status,
|
|
2678
|
-
retrieval_status: retrieval,
|
|
2679
|
-
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
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
|
-
},
|
|
2687
|
-
},
|
|
2688
|
-
};
|
|
1171
|
+
return pollMonitorLane(handle, filter, deadline, signal);
|
|
2689
1172
|
}
|
|
2690
1173
|
async stopMonitor(handle) {
|
|
2691
|
-
|
|
2692
|
-
if (handle.watcher !== undefined) {
|
|
2693
|
-
handle.timers.clearInterval(handle.watcher);
|
|
2694
|
-
handle.watcher = undefined;
|
|
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)" : "";
|
|
2727
|
-
this.markStopSource(handle.id, "parent");
|
|
2728
|
-
const r = await handle.env.killBackground(handle.shellId);
|
|
2729
|
-
if (handle.status !== "running") {
|
|
2730
|
-
return {
|
|
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
|
-
},
|
|
2740
|
-
};
|
|
2741
|
-
}
|
|
2742
|
-
if (!r.ok) {
|
|
2743
|
-
if (r.error.code === "not_found") {
|
|
2744
|
-
if (handle.stoppedBy === undefined)
|
|
2745
|
-
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2746
|
-
handle.status = "killed";
|
|
2747
|
-
handle.updatedAt = Date.now();
|
|
2748
|
-
mintCompletionId(handle);
|
|
2749
|
-
this.pokeBgQuiescence(handle.owner);
|
|
2750
|
-
return {
|
|
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
|
-
},
|
|
2760
|
-
};
|
|
2761
|
-
}
|
|
2762
|
-
if (handle.watcher === undefined)
|
|
2763
|
-
this.startMonitorWatcher(handle.id);
|
|
2764
|
-
const retrieval_status = r.error.code === "timeout" ? "timeout" : "not_ready";
|
|
2765
|
-
return {
|
|
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,
|
|
2768
|
-
details: { task_id: handle.id, type: "monitor", status: handle.status, retrieval_status, error: r.error.code },
|
|
2769
|
-
};
|
|
2770
|
-
}
|
|
2771
|
-
if (handle.status === "running") {
|
|
2772
|
-
if (handle.stoppedBy === undefined)
|
|
2773
|
-
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2774
|
-
handle.status = "killed";
|
|
2775
|
-
handle.updatedAt = Date.now();
|
|
2776
|
-
mintCompletionId(handle);
|
|
2777
|
-
this.pokeBgQuiescence(handle.owner);
|
|
2778
|
-
}
|
|
2779
|
-
return {
|
|
2780
|
-
content: `Terminated ${handle.id}.${drainNote}`,
|
|
2781
|
-
details: {
|
|
2782
|
-
task_id: handle.id,
|
|
2783
|
-
type: "monitor",
|
|
2784
|
-
status: handle.status,
|
|
2785
|
-
retrieval_status: "success",
|
|
2786
|
-
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2787
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2788
|
-
},
|
|
2789
|
-
};
|
|
2790
|
-
}
|
|
2791
|
-
async pollBackgroundAgent(handle, deadline, signal) {
|
|
2792
|
-
while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
|
|
2793
|
-
await sleepPollStep(deadline, signal);
|
|
2794
|
-
}
|
|
2795
|
-
const running = handle.status === "running";
|
|
2796
|
-
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
2797
|
-
if (handle.status === "parked") {
|
|
2798
|
-
return {
|
|
2799
|
-
content: delimitUntrusted(`TaskOutput ${handle.id}`, `status: parked
|
|
2800
|
-
The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
|
|
2801
|
-
details: {
|
|
2802
|
-
task_id: handle.id,
|
|
2803
|
-
type: "background_agent",
|
|
2804
|
-
status: "parked",
|
|
2805
|
-
retrieval_status: "success",
|
|
2806
|
-
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2807
|
-
},
|
|
2808
|
-
};
|
|
2809
|
-
}
|
|
2810
|
-
const body = running
|
|
2811
|
-
? `status: running
|
|
2812
|
-
The agent is still working — you will be notified when it completes.`
|
|
2813
|
-
: `status: ${handle.status}
|
|
2814
|
-
${handle.error ? `error: ${handle.error}
|
|
2815
|
-
` : ""}${handle.result ? `--- result${handle.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
|
|
2816
|
-
${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no result text)"}`;
|
|
2817
|
-
return {
|
|
2818
|
-
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
2819
|
-
details: {
|
|
2820
|
-
task_id: handle.id,
|
|
2821
|
-
type: "background_agent",
|
|
2822
|
-
status: handle.status,
|
|
2823
|
-
retrieval_status: retrieval,
|
|
2824
|
-
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2825
|
-
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2826
|
-
...(handle.resultIsPartial ? { partial_result: true } : {}),
|
|
2827
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2828
|
-
},
|
|
2829
|
-
};
|
|
2830
|
-
}
|
|
2831
|
-
async stopBackgroundAgent(handle) {
|
|
2832
|
-
const entryStatus = handle.status;
|
|
2833
|
-
if (handle.status === "parked") {
|
|
2834
|
-
const arbiter = handle.resolveParkedStop;
|
|
2835
|
-
if (arbiter === undefined) {
|
|
2836
|
-
return {
|
|
2837
|
-
content: `${handle.id} is parked on a pending approval — stop it by deciding (deny/expire) the pending approval in the durable approval inbox.`,
|
|
2838
|
-
details: { task_id: handle.id, type: "background_agent", status: "parked", retrieval_status: "not_ready", error: "parked_pending_approval" },
|
|
2839
|
-
};
|
|
2840
|
-
}
|
|
2841
|
-
let stopWon = false;
|
|
2842
|
-
let arbiterUnreachable = false;
|
|
2843
|
-
try {
|
|
2844
|
-
stopWon = await arbiter();
|
|
2845
|
-
}
|
|
2846
|
-
catch {
|
|
2847
|
-
stopWon = false;
|
|
2848
|
-
arbiterUnreachable = true;
|
|
2849
|
-
}
|
|
2850
|
-
if (stopWon && handle.status === "parked") {
|
|
2851
|
-
this.markStopSource(handle.id, "parent");
|
|
2852
|
-
handle.status = "killed";
|
|
2853
|
-
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2854
|
-
handle.updatedAt = Date.now();
|
|
2855
|
-
mintCompletionId(handle);
|
|
2856
|
-
handle.parkedCheckpointToken = undefined;
|
|
2857
|
-
handle.resolveParkedStop = undefined;
|
|
2858
|
-
this.pokeBgQuiescence(handle.owner);
|
|
2859
|
-
{
|
|
2860
|
-
const lane = handle.durable;
|
|
2861
|
-
if (lane !== undefined) {
|
|
2862
|
-
try {
|
|
2863
|
-
const live = await lane.store.get(handle.id, lane.record.scope);
|
|
2864
|
-
if (live && (live.status === "parked" || live.status === "running")) {
|
|
2865
|
-
const term = structuredClone(live);
|
|
2866
|
-
term.status = "killed";
|
|
2867
|
-
if (handle.stoppedBy !== undefined)
|
|
2868
|
-
term.stoppedBy = handle.stoppedBy;
|
|
2869
|
-
term.settledAt = Date.now();
|
|
2870
|
-
term.updatedAt = Date.now();
|
|
2871
|
-
delete term.parkedCheckpointToken;
|
|
2872
|
-
delete term.parkClaimId;
|
|
2873
|
-
delete term.parkedAt;
|
|
2874
|
-
await lane.store.update(handle.id, lane.record.scope, term, { rev: live.rev });
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
catch {
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
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"]);
|
|
2887
|
-
return {
|
|
2888
|
-
content: `Terminated ${handle.id} (pending approval expired).`,
|
|
2889
|
-
details: {
|
|
2890
|
-
task_id: handle.id,
|
|
2891
|
-
type: "background_agent",
|
|
2892
|
-
status: "killed",
|
|
2893
|
-
retrieval_status: "success",
|
|
2894
|
-
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2895
|
-
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2896
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2897
|
-
},
|
|
2898
|
-
};
|
|
2899
|
-
}
|
|
2900
|
-
if (arbiterUnreachable) {
|
|
2901
|
-
return {
|
|
2902
|
-
content: `${handle.id} was not stopped: the approval arbitration store was unreachable — the row stays parked; retry the stop.`,
|
|
2903
|
-
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_arbiter_unreachable" },
|
|
2904
|
-
isError: true,
|
|
2905
|
-
};
|
|
2906
|
-
}
|
|
2907
|
-
return {
|
|
2908
|
-
content: `${handle.id} was not stopped: its pending approval was already consumed (a resume won the arbitration).`,
|
|
2909
|
-
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_resume_won" },
|
|
2910
|
-
isError: true,
|
|
2911
|
-
};
|
|
2912
|
-
}
|
|
2913
|
-
if (handle.status === "running") {
|
|
2914
|
-
this.markStopSource(handle.id, "parent");
|
|
2915
|
-
handle.abort.abort();
|
|
2916
|
-
handle.status = "killed";
|
|
2917
|
-
handle.stoppedBy = handle.stopSource ?? "parent";
|
|
2918
|
-
handle.updatedAt = Date.now();
|
|
2919
|
-
mintCompletionId(handle);
|
|
2920
|
-
handle.notify = undefined;
|
|
2921
|
-
for (const [, , qResolve] of handle.preAttachQueue ?? [])
|
|
2922
|
-
qResolve?.({ ok: false, reason: "not_running" });
|
|
2923
|
-
handle.preAttachQueue = undefined;
|
|
2924
|
-
for (const r of [...(handle.inFlightDirect ?? [])])
|
|
2925
|
-
r({ ok: false, reason: "not_running" });
|
|
2926
|
-
handle.inFlightDirect = undefined;
|
|
2927
|
-
this.pokeBgQuiescence(handle.owner);
|
|
2928
|
-
this.durableAgentWrite(handle, {
|
|
2929
|
-
status: "killed",
|
|
2930
|
-
settledAt: Date.now(),
|
|
2931
|
-
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2932
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2933
|
-
});
|
|
2934
|
-
}
|
|
2935
|
-
return {
|
|
2936
|
-
content: entryStatus === "running" ? `Terminated ${handle.id}.` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
2937
|
-
details: {
|
|
2938
|
-
task_id: handle.id,
|
|
2939
|
-
type: "background_agent",
|
|
2940
|
-
status: handle.status,
|
|
2941
|
-
retrieval_status: "success",
|
|
2942
|
-
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
2943
|
-
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
2944
|
-
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
2945
|
-
},
|
|
2946
|
-
};
|
|
1174
|
+
return stopMonitorLane(this.core, handle);
|
|
2947
1175
|
}
|
|
2948
1176
|
async stopWorkflow(handle) {
|
|
2949
|
-
|
|
2950
|
-
return {
|
|
2951
|
-
content: JSON.stringify({
|
|
2952
|
-
task_id: handle.id,
|
|
2953
|
-
runId: handle.id,
|
|
2954
|
-
type: "workflow",
|
|
2955
|
-
status: handle.status,
|
|
2956
|
-
stopped: false,
|
|
2957
|
-
note: `workflow already ${handle.status} — nothing to stop; poll TaskOutput for its final state.`,
|
|
2958
|
-
}),
|
|
2959
|
-
details: { task_id: handle.id, type: "workflow", status: handle.status, retrieval_status: "success" },
|
|
2960
|
-
};
|
|
2961
|
-
}
|
|
2962
|
-
handle.status = "cancelled";
|
|
2963
|
-
handle.updatedAt = Date.now();
|
|
2964
|
-
handle.handle.cancel("TaskStop requested");
|
|
2965
|
-
return {
|
|
2966
|
-
content: JSON.stringify({
|
|
2967
|
-
task_id: handle.id,
|
|
2968
|
-
runId: handle.id,
|
|
2969
|
-
type: "workflow",
|
|
2970
|
-
status: "cancelled",
|
|
2971
|
-
stopped: true,
|
|
2972
|
-
note: "workflow cancellation requested; durable run status maps cancellation to failed when it finalizes.",
|
|
2973
|
-
}),
|
|
2974
|
-
details: { task_id: handle.id, type: "workflow", status: "cancelled", retrieval_status: "success" },
|
|
2975
|
-
};
|
|
1177
|
+
return stopWorkflowLane(handle);
|
|
2976
1178
|
}
|
|
2977
1179
|
}
|
|
2978
1180
|
export const defaultTaskRegistry = new TaskRegistry();
|
|
2979
1181
|
export function createTaskOutputTool(opts) {
|
|
1182
|
+
const caps = { ...REGISTRY_TASK_TOOL_CAPS, notification: opts.notificationWired !== false };
|
|
2980
1183
|
return defineTool({
|
|
2981
|
-
name:
|
|
1184
|
+
name: TASK_OUTPUT_TOOL_NAME,
|
|
2982
1185
|
aliases: [...TASK_OUTPUT_ALIASES],
|
|
2983
|
-
contract:
|
|
2984
|
-
description:
|
|
2985
|
-
|
|
2986
|
-
"Retrieve output or status for a background task by task_id. Supports background Bash tasks and workflow " +
|
|
2987
|
-
"tasks returned by RunWorkflow. Returns only the caller's own task scope; unknown and out-of-scope ids are " +
|
|
2988
|
-
"reported the same way. Legacy bash_id and runId arguments are accepted for compatibility.",
|
|
2989
|
-
parameters: Type.Object({
|
|
2990
|
-
task_id: Type.Optional(Type.String({ description: "The task_id returned by Bash(run_in_background) or RunWorkflow." })),
|
|
2991
|
-
block: Type.Optional(Type.Boolean({ description: "Whether to wait for completion (default true — the call waits until the task finishes or `timeout`, saving re-poll turns). Pass false for an instantaneous snapshot." })),
|
|
2992
|
-
timeout: Type.Optional(Type.Number({ description: "Optional timeout in milliseconds when block is true (default 30000, max 600000)." })),
|
|
2993
|
-
filter: Type.Optional(Type.String({ description: "Optional regex filter for background Bash output (sema extension)." })),
|
|
2994
|
-
}),
|
|
1186
|
+
contract: TASK_OUTPUT_CONTRACT,
|
|
1187
|
+
description: composeTaskOutputDescription(caps),
|
|
1188
|
+
parameters: composeTaskOutputParams(caps),
|
|
2995
1189
|
effect: "read",
|
|
2996
1190
|
execute: async (rawArgs, ctx) => {
|
|
2997
1191
|
const args = rawArgs;
|
|
2998
|
-
const id =
|
|
1192
|
+
const id = resolveTaskIdArg(args.task_id, args.bash_id, args.runId);
|
|
2999
1193
|
if (!id)
|
|
3000
|
-
return errorResult(
|
|
1194
|
+
return errorResult(TASK_OUTPUT_MISSING_ID_MESSAGE);
|
|
3001
1195
|
const requestedMs = typeof args.timeout === "number" ? args.timeout : undefined;
|
|
3002
1196
|
let effectiveTimeoutMs = requestedMs;
|
|
3003
1197
|
let waitClampNote = "";
|
|
@@ -3034,24 +1228,17 @@ export function createTaskOutputTool(opts) {
|
|
|
3034
1228
|
}
|
|
3035
1229
|
export function createTaskStopTool(opts) {
|
|
3036
1230
|
return defineTool({
|
|
3037
|
-
name:
|
|
1231
|
+
name: TASK_STOP_TOOL_NAME,
|
|
3038
1232
|
aliases: [...TASK_STOP_ALIASES],
|
|
3039
|
-
contract:
|
|
3040
|
-
description:
|
|
3041
|
-
|
|
3042
|
-
"- To stop a background agent spawned with a name, pass that name as task_id\n" +
|
|
3043
|
-
"- Returns a success or failure status\n" +
|
|
3044
|
-
"- Use this tool when you need to terminate a long-running task",
|
|
3045
|
-
parameters: Type.Object({
|
|
3046
|
-
task_id: Type.Optional(Type.String({ description: "The ID of the background task to stop" })),
|
|
3047
|
-
shell_id: Type.Optional(Type.String({ description: "Deprecated: use task_id instead" })),
|
|
3048
|
-
}),
|
|
1233
|
+
contract: TASK_STOP_CONTRACT,
|
|
1234
|
+
description: composeTaskStopDescription(REGISTRY_TASK_TOOL_CAPS),
|
|
1235
|
+
parameters: TASK_STOP_PARAMS,
|
|
3049
1236
|
effect: "write",
|
|
3050
1237
|
execute: async (rawArgs, ctx) => {
|
|
3051
1238
|
const args = rawArgs;
|
|
3052
|
-
const id =
|
|
1239
|
+
const id = resolveTaskIdArg(args.task_id, args.shell_id, args.runId);
|
|
3053
1240
|
if (!id)
|
|
3054
|
-
return errorResult(
|
|
1241
|
+
return errorResult(TASK_STOP_MISSING_ID_MESSAGE);
|
|
3055
1242
|
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 });
|
|
3056
1243
|
const { type: taskType, ...rest } = r.details;
|
|
3057
1244
|
return {
|