@sema-agent/core 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/observer.d.ts +14 -0
- package/dist/agents/observer.js +66 -12
- package/dist/agents/send-message-tool.js +164 -88
- package/dist/agents/subagent.d.ts +11 -4
- package/dist/agents/subagent.js +166 -62
- package/dist/core/context-edit.js +16 -3
- package/dist/core/file-snapshot-store.js +10 -1
- package/dist/core/memory-engine/dual-root.js +2 -0
- package/dist/core/memory-engine/engine.d.ts +4 -0
- package/dist/core/memory-engine/engine.js +6 -1
- package/dist/core/runner/prepare-memory.d.ts +4 -0
- package/dist/core/runner/prepare-memory.js +4 -1
- package/dist/core/runner/prepare-task.d.ts +9 -2
- package/dist/core/runner/prepare-task.js +68 -13
- package/dist/core/runner/runtask.js +919 -865
- package/dist/core/runner/synthetic-tools.d.ts +1 -0
- package/dist/core/runner/synthetic-tools.js +18 -15
- package/dist/core/runner/turn-attachments.d.ts +27 -3
- package/dist/core/runner/turn-attachments.js +101 -12
- package/dist/core/task-registry-agent.d.ts +9 -1
- package/dist/core/task-registry-agent.js +23 -2
- package/dist/core/task-registry-monitor.js +79 -24
- package/dist/core/task-registry-shared.d.ts +13 -1
- package/dist/core/task-registry-shared.js +21 -0
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +24 -26
- package/dist/core/tool-result-budget.js +2 -2
- package/dist/core/tool-result-store.d.ts +2 -0
- package/dist/core/tool-result-store.js +27 -2
- package/dist/core/types.d.ts +4 -2
- package/dist/core/workflow-journal-store.d.ts +13 -0
- package/dist/engine/session/import-validate.js +29 -0
- package/dist/engine/session/memory-repo.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow-size-guideline.d.ts +6 -1
- package/dist/orchestration/workflow-size-guideline.js +19 -9
- package/dist/orchestration/workflow.d.ts +1 -0
- package/dist/orchestration/workflow.js +44 -1
- package/dist/prompt-assembly/assemble.js +3 -7
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompt-assembly/packs/sema-default.js +8 -5
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +45 -0
- package/dist/prompts/default.d.ts +4 -5
- package/dist/prompts/default.js +16 -18
- package/dist/prompts/simple-sections.d.ts +3 -1
- package/dist/prompts/simple-sections.js +11 -1
- package/dist/stores/cc/task-list-store.js +3 -3
- package/dist/stores/file/memory-store.d.ts +3 -0
- package/dist/stores/file/memory-store.js +39 -12
- package/dist/stores/file/tool-result-store.js +16 -2
- package/dist/stores/file/workflow-journal-store.d.ts +23 -0
- package/dist/stores/file/workflow-journal-store.js +140 -3
- package/dist/tools/fs/bash-readonly-classifier.js +21 -2
- package/dist/tools/fs/fs-bash.d.ts +1 -0
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/fs-read.js +12 -12
- package/dist/tools/fs/fs-search-tools.js +42 -7
- package/dist/tools/fs/fs-write.js +18 -6
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +2 -1
- package/dist/tools/fs/safety.d.ts +4 -0
- package/dist/tools/fs/safety.js +113 -10
- package/dist/tools/fs/search.d.ts +1 -0
- package/dist/tools/fs/search.js +23 -3
- package/dist/tools/monitor.js +1 -1
- package/dist/tools/task-list.d.ts +1 -0
- package/dist/tools/task-list.js +13 -2
- package/dist/tools/web.js +36 -6
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
2
|
-
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
2
|
+
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
3
3
|
export function registerMonitorLane(core, input) {
|
|
4
4
|
assertOwnership(input, "registerMonitor");
|
|
5
5
|
const id = core.mintTaskId("monitor");
|
|
@@ -9,6 +9,8 @@ export function registerMonitorLane(core, input) {
|
|
|
9
9
|
const timeoutMs = persistent
|
|
10
10
|
? undefined
|
|
11
11
|
: Math.min(MONITOR_MAX_TIMEOUT_MS, Math.max(1, Math.floor(input.timeoutMs ?? MONITOR_DEFAULT_TIMEOUT_MS)));
|
|
12
|
+
const maxBatchesPerMinute = Math.max(1, Math.floor(input.maxBatchesPerMinute ?? MONITOR_MAX_BATCHES_PER_MINUTE));
|
|
13
|
+
const stormBurst = Math.max(1, Math.floor(input.stormBurst ?? MONITOR_STORM_BURST));
|
|
12
14
|
const handle = {
|
|
13
15
|
id,
|
|
14
16
|
type: "monitor",
|
|
@@ -26,9 +28,12 @@ export function registerMonitorLane(core, input) {
|
|
|
26
28
|
spool: { stdout: "", stderr: "", rolledChars: 0 },
|
|
27
29
|
lineBuf: "",
|
|
28
30
|
seq: 0,
|
|
29
|
-
|
|
31
|
+
stormTokens: stormBurst,
|
|
32
|
+
stormBurst,
|
|
33
|
+
stormRefillIntervalMs: Math.max(1, Math.round(60_000 / maxBatchesPerMinute)),
|
|
34
|
+
stormRefilledAt: timers.now(),
|
|
30
35
|
batchWindowMs: Math.max(1, Math.floor(input.batchWindowMs ?? MONITOR_BATCH_WINDOW_MS)),
|
|
31
|
-
maxBatchesPerMinute
|
|
36
|
+
maxBatchesPerMinute,
|
|
32
37
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt: timers.now() + timeoutMs } : {}),
|
|
33
38
|
timers,
|
|
34
39
|
...(input.onEvent !== undefined ? { onEvent: input.onEvent } : {}),
|
|
@@ -44,10 +49,23 @@ export function absorbMonitorPollLane(handle, v) {
|
|
|
44
49
|
spool.stdout = rollSpoolText(spool, spool.stdout + v.stdout, ROLL_CAP);
|
|
45
50
|
spool.stderr = rollSpoolText(spool, spool.stderr + v.stderr, ROLL_CAP);
|
|
46
51
|
handle.lineBuf += v.stdout;
|
|
52
|
+
if (handle.lineBuf.length > MONITOR_LINE_BUF_CAP)
|
|
53
|
+
handle.lineBuf = handle.lineBuf.slice(-MONITOR_LINE_BUF_CAP);
|
|
47
54
|
const parts = handle.lineBuf.split(/\r?\n/);
|
|
48
55
|
handle.lineBuf = parts.pop() ?? "";
|
|
49
56
|
return parts.filter((l) => l !== "");
|
|
50
57
|
}
|
|
58
|
+
function consumeStormToken(handle, now) {
|
|
59
|
+
const gained = Math.floor((now - handle.stormRefilledAt) / handle.stormRefillIntervalMs);
|
|
60
|
+
if (gained > 0) {
|
|
61
|
+
handle.stormTokens = Math.min(handle.stormBurst, handle.stormTokens + gained);
|
|
62
|
+
handle.stormRefilledAt += gained * handle.stormRefillIntervalMs;
|
|
63
|
+
}
|
|
64
|
+
if (handle.stormTokens <= 0)
|
|
65
|
+
return false;
|
|
66
|
+
handle.stormTokens -= 1;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
51
69
|
export function emitMonitorEventLane(handle, n) {
|
|
52
70
|
try {
|
|
53
71
|
handle.onEvent?.(n);
|
|
@@ -75,7 +93,8 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
75
93
|
handle.lineBuf = "";
|
|
76
94
|
return out;
|
|
77
95
|
};
|
|
78
|
-
const
|
|
96
|
+
const pendingSuppressionNote = () => consumePendingSuppressionNote(handle);
|
|
97
|
+
const terminal = (status, detail, finalLines, exitCode) => {
|
|
79
98
|
handle.updatedAt = Date.now();
|
|
80
99
|
mintCompletionId(handle);
|
|
81
100
|
stop();
|
|
@@ -85,8 +104,8 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
85
104
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
86
105
|
status,
|
|
87
106
|
...(status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
88
|
-
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${
|
|
89
|
-
...(finalLines.length > 0 ? { lines: finalLines, result: clipMonitorEvent(finalLines.join("\n")), ...(status === "killed" ? { partial: true } : {}) } : {}),
|
|
107
|
+
summary: `${terminalTaskSummary("monitor", (handle.description ?? "monitor").slice(0, 200), status, exitCode)} — ${detail}${droppedGapNote(handle.spool)}`,
|
|
108
|
+
...(finalLines.length > 0 ? { lines: finalLines.map(clipMonitorLine), result: clipMonitorEvent(finalLines.map(clipMonitorLine).join("\n")), ...(status === "killed" ? { partial: true } : {}) } : {}),
|
|
90
109
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
91
110
|
}));
|
|
92
111
|
};
|
|
@@ -107,7 +126,7 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
107
126
|
if ((r.error.code === "timeout" || r.error.code === "io") && ++pollFailures <= MAX_RETRYABLE_POLL_FAILURES)
|
|
108
127
|
return;
|
|
109
128
|
handle.status = "failed";
|
|
110
|
-
return terminal("failed", `monitor lost its process (${r.error.message}); watch ended
|
|
129
|
+
return terminal("failed", `monitor lost its process (${r.error.message}); watch ended.${pendingSuppressionNote()}`, pendingFinalLines([]));
|
|
111
130
|
}
|
|
112
131
|
pollFailures = 0;
|
|
113
132
|
const spool = handle.spool;
|
|
@@ -127,7 +146,7 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
127
146
|
: r.value.timedOut === true
|
|
128
147
|
? `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.`
|
|
129
148
|
: `${r.value.status}; watch ended.`;
|
|
130
|
-
return terminal(handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed", label
|
|
149
|
+
return terminal(handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed", `${label}${pendingSuppressionNote()}`, pendingFinalLines(lines), r.value.status === "exited" ? r.value.exitCode : undefined);
|
|
131
150
|
}
|
|
132
151
|
const now = timers.now();
|
|
133
152
|
if (handle.deadlineAt !== undefined && now >= handle.deadlineAt) {
|
|
@@ -137,45 +156,74 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
137
156
|
return stop();
|
|
138
157
|
if (!k.ok && k.error.code !== "not_found") {
|
|
139
158
|
handle.status = "failed";
|
|
140
|
-
return terminal("failed", `timed out after ${handle.timeoutMs}ms, but the kill failed (${k.error.message}); the process may still be running. Watch ended
|
|
159
|
+
return terminal("failed", `timed out after ${handle.timeoutMs}ms, but the kill failed (${k.error.message}); the process may still be running. Watch ended.${pendingSuppressionNote()}`, pendingFinalLines(lines));
|
|
141
160
|
}
|
|
142
161
|
handle.status = "killed";
|
|
143
162
|
handle.stoppedBy = handle.stopSource ?? "timeout";
|
|
144
|
-
return terminal("killed", `timed out after ${handle.timeoutMs}ms; the process was killed and the watch ended
|
|
163
|
+
return terminal("killed", `timed out after ${handle.timeoutMs}ms; the process was killed and the watch ended.${pendingSuppressionNote()}`, pendingFinalLines(lines));
|
|
145
164
|
}
|
|
146
165
|
if (lines.length === 0) {
|
|
147
166
|
if (r.value.stdout.length > 0 || r.value.stderr.length > 0)
|
|
148
167
|
handle.updatedAt = Date.now();
|
|
149
168
|
return;
|
|
150
169
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
if (!consumeStormToken(handle, now)) {
|
|
171
|
+
const previousSuppressedAt = handle.lastSuppressedAt;
|
|
172
|
+
handle.suppressedBatches = (handle.suppressedBatches ?? 0) + 1;
|
|
173
|
+
handle.lastSuppressedAt = now;
|
|
174
|
+
if (previousSuppressedAt !== undefined && now - previousSuppressedAt > handle.stormRefillIntervalMs * 3) {
|
|
175
|
+
handle.overloadSince = now;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
handle.overloadSince ??= now;
|
|
179
|
+
}
|
|
180
|
+
handle.updatedAt = Date.now();
|
|
181
|
+
if (now - handle.overloadSince <= MONITOR_STORM_KILL_AFTER_MS)
|
|
182
|
+
return;
|
|
183
|
+
const suppressed = handle.suppressedBatches;
|
|
184
|
+
const overloadSecs = Math.round((now - handle.overloadSince) / 1000);
|
|
155
185
|
core.markStopSource(id, "system");
|
|
156
186
|
const k = await handle.env.killBackground(handle.shellId);
|
|
157
187
|
if (core.handles.get(id) !== handle || handle.status !== "running")
|
|
158
188
|
return stop();
|
|
159
189
|
if (!k.ok && k.error.code !== "not_found") {
|
|
160
190
|
handle.status = "failed";
|
|
161
|
-
return terminal("failed", `auto-stop triggered (
|
|
191
|
+
return terminal("failed", `auto-stop triggered (${suppressed} event batch(es) suppressed over ${overloadSecs}s of sustained overload), but the kill failed (${k.error.message}); the process may still be running. Watch ended.`, pendingFinalLines(lines));
|
|
162
192
|
}
|
|
163
193
|
handle.status = "killed";
|
|
164
194
|
handle.stoppedBy = handle.stopSource ?? "system";
|
|
165
|
-
return terminal("killed", `auto-stopped:
|
|
195
|
+
return terminal("killed", `auto-stopped: too much output (${suppressed} event batch(es) suppressed over ${overloadSecs}s of sustained overload; sustained budget is ${handle.maxBatchesPerMinute} batches/minute). ` +
|
|
166
196
|
`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));
|
|
167
197
|
}
|
|
198
|
+
if ((handle.suppressedBatches ?? 0) > 0) {
|
|
199
|
+
const suppressed = handle.suppressedBatches;
|
|
200
|
+
handle.suppressedBatches = 0;
|
|
201
|
+
if (handle.lastSuppressedAt !== undefined && now - handle.lastSuppressedAt > handle.stormRefillIntervalMs * 3)
|
|
202
|
+
handle.overloadSince = undefined;
|
|
203
|
+
handle.seq += 1;
|
|
204
|
+
const note = `[${suppressed} events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]`;
|
|
205
|
+
emit({
|
|
206
|
+
task_id: id,
|
|
207
|
+
task_type: "monitor",
|
|
208
|
+
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
209
|
+
status: "event",
|
|
210
|
+
seq: handle.seq,
|
|
211
|
+
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${suppressed} event batch(es) suppressed (output rate too high); the suppressed output is still readable via TaskOutput`,
|
|
212
|
+
result: note,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
168
215
|
handle.seq += 1;
|
|
169
216
|
handle.updatedAt = Date.now();
|
|
217
|
+
const shown = lines.map(clipMonitorLine);
|
|
170
218
|
emit({
|
|
171
219
|
task_id: id,
|
|
172
220
|
task_type: "monitor",
|
|
173
221
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
174
222
|
status: "event",
|
|
175
223
|
seq: handle.seq,
|
|
176
|
-
lines,
|
|
224
|
+
lines: shown,
|
|
177
225
|
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${lines.length} new output line(s)${firstDropNote(spool)}`,
|
|
178
|
-
result: clipMonitorEvent(
|
|
226
|
+
result: clipMonitorEvent(shown.join("\n")),
|
|
179
227
|
});
|
|
180
228
|
}
|
|
181
229
|
catch (e) {
|
|
@@ -184,7 +232,7 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
184
232
|
if (++pollFailures <= MAX_RETRYABLE_POLL_FAILURES)
|
|
185
233
|
return;
|
|
186
234
|
handle.status = "failed";
|
|
187
|
-
terminal("failed", `monitor watcher crashed (${e instanceof Error ? e.message : String(e)}); watch ended
|
|
235
|
+
terminal("failed", `monitor watcher crashed (${e instanceof Error ? e.message : String(e)}); watch ended.${pendingSuppressionNote()}`, pendingFinalLines([]));
|
|
188
236
|
}
|
|
189
237
|
finally {
|
|
190
238
|
ticking = false;
|
|
@@ -244,6 +292,13 @@ export async function pollMonitorLane(handle, filter, deadline, signal) {
|
|
|
244
292
|
},
|
|
245
293
|
};
|
|
246
294
|
}
|
|
295
|
+
function consumePendingSuppressionNote(handle) {
|
|
296
|
+
const suppressed = handle.suppressedBatches ?? 0;
|
|
297
|
+
if (suppressed <= 0)
|
|
298
|
+
return "";
|
|
299
|
+
handle.suppressedBatches = 0;
|
|
300
|
+
return ` [${suppressed} event batch(es) suppressed before the watch ended — output rate was too high; the suppressed output is still readable via TaskOutput.]`;
|
|
301
|
+
}
|
|
247
302
|
export async function stopMonitorLane(core, handle) {
|
|
248
303
|
const wasRunning = handle.status === "running";
|
|
249
304
|
if (handle.watcher !== undefined) {
|
|
@@ -266,9 +321,9 @@ export async function stopMonitorLane(core, handle) {
|
|
|
266
321
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
267
322
|
status: "event",
|
|
268
323
|
seq: handle.seq,
|
|
269
|
-
lines: finalLines,
|
|
324
|
+
lines: finalLines.map(clipMonitorLine),
|
|
270
325
|
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${finalLines.length} final output line(s) drained at stop${firstDropNote(handle.spool)}`,
|
|
271
|
-
result: clipMonitorEvent(finalLines.join("\n")),
|
|
326
|
+
result: clipMonitorEvent(finalLines.map(clipMonitorLine).join("\n")),
|
|
272
327
|
});
|
|
273
328
|
}
|
|
274
329
|
}
|
|
@@ -285,7 +340,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
285
340
|
const r = await handle.env.killBackground(handle.shellId);
|
|
286
341
|
if (handle.status !== "running") {
|
|
287
342
|
return {
|
|
288
|
-
content: wasRunning ? `Terminated ${handle.id}.${drainNote}` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
343
|
+
content: wasRunning ? `Terminated ${handle.id}.${drainNote}${consumePendingSuppressionNote(handle)}` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
289
344
|
details: {
|
|
290
345
|
task_id: handle.id,
|
|
291
346
|
type: "monitor",
|
|
@@ -305,7 +360,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
305
360
|
mintCompletionId(handle);
|
|
306
361
|
core.pokeBgQuiescence(handle.owner);
|
|
307
362
|
return {
|
|
308
|
-
content: `Terminated ${handle.id} (the process was already gone).${drainNote}`,
|
|
363
|
+
content: `Terminated ${handle.id} (the process was already gone).${drainNote}${consumePendingSuppressionNote(handle)}`,
|
|
309
364
|
details: {
|
|
310
365
|
task_id: handle.id,
|
|
311
366
|
type: "monitor",
|
|
@@ -334,7 +389,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
334
389
|
core.pokeBgQuiescence(handle.owner);
|
|
335
390
|
}
|
|
336
391
|
return {
|
|
337
|
-
content: `Terminated ${handle.id}.${drainNote}`,
|
|
392
|
+
content: `Terminated ${handle.id}.${drainNote}${consumePendingSuppressionNote(handle)}`,
|
|
338
393
|
details: {
|
|
339
394
|
task_id: handle.id,
|
|
340
395
|
type: "monitor",
|
|
@@ -161,7 +161,13 @@ export interface MonitorTaskHandle extends SemaTaskHandle {
|
|
|
161
161
|
};
|
|
162
162
|
lineBuf: string;
|
|
163
163
|
seq: number;
|
|
164
|
-
|
|
164
|
+
stormTokens: number;
|
|
165
|
+
stormBurst: number;
|
|
166
|
+
stormRefillIntervalMs: number;
|
|
167
|
+
stormRefilledAt: number;
|
|
168
|
+
suppressedBatches?: number;
|
|
169
|
+
overloadSince?: number;
|
|
170
|
+
lastSuppressedAt?: number;
|
|
165
171
|
batchWindowMs: number;
|
|
166
172
|
maxBatchesPerMinute: number;
|
|
167
173
|
persistent?: true;
|
|
@@ -190,6 +196,7 @@ export interface RegisterMonitorInput extends TaskAccess {
|
|
|
190
196
|
timers?: MonitorTimers;
|
|
191
197
|
batchWindowMs?: number;
|
|
192
198
|
maxBatchesPerMinute?: number;
|
|
199
|
+
stormBurst?: number;
|
|
193
200
|
now?: number;
|
|
194
201
|
}
|
|
195
202
|
export interface RegisterWorkflowInput extends TaskAccess {
|
|
@@ -208,7 +215,12 @@ export declare const MONITOR_BATCH_WINDOW_MS = 200;
|
|
|
208
215
|
export declare const MONITOR_DEFAULT_TIMEOUT_MS = 300000;
|
|
209
216
|
export declare const MONITOR_MAX_TIMEOUT_MS = 3600000;
|
|
210
217
|
export declare const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
|
|
218
|
+
export declare const MONITOR_STORM_BURST = 10;
|
|
219
|
+
export declare const MONITOR_STORM_KILL_AFTER_MS = 30000;
|
|
220
|
+
export declare const MONITOR_LINE_BUF_CAP = 1048576;
|
|
211
221
|
export declare function clipMonitorEvent(s: string): string;
|
|
222
|
+
export declare function clipMonitorLine(s: string): string;
|
|
223
|
+
export declare function terminalTaskSummary(kind: "bash" | "monitor", label: string, status: "completed" | "failed" | "killed", exitCode?: number): string;
|
|
212
224
|
export declare const TASK_OUTPUT_MAX_CHARS = 160000;
|
|
213
225
|
export declare function clipTaskOutput(s: string, fullOutputPath?: string): string;
|
|
214
226
|
export declare function statusFromBackground(status: string, exitCode?: number): SemaTaskStatus;
|
|
@@ -32,10 +32,31 @@ export const MONITOR_BATCH_WINDOW_MS = 200;
|
|
|
32
32
|
export const MONITOR_DEFAULT_TIMEOUT_MS = 300_000;
|
|
33
33
|
export const MONITOR_MAX_TIMEOUT_MS = 3_600_000;
|
|
34
34
|
export const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
|
|
35
|
+
export const MONITOR_STORM_BURST = 10;
|
|
36
|
+
export const MONITOR_STORM_KILL_AFTER_MS = 30_000;
|
|
35
37
|
const MONITOR_EVENT_RESULT_CAP = 2_000;
|
|
38
|
+
const MONITOR_EVENT_LINE_CAP = 500;
|
|
39
|
+
export const MONITOR_LINE_BUF_CAP = 1_048_576;
|
|
36
40
|
export function clipMonitorEvent(s) {
|
|
37
41
|
return s.length <= MONITOR_EVENT_RESULT_CAP ? s : `${s.slice(0, MONITOR_EVENT_RESULT_CAP)}\n…[+${s.length - MONITOR_EVENT_RESULT_CAP} chars truncated]`;
|
|
38
42
|
}
|
|
43
|
+
export function clipMonitorLine(s) {
|
|
44
|
+
return s.length <= MONITOR_EVENT_LINE_CAP ? s : `${s.slice(0, MONITOR_EVENT_LINE_CAP)}...(truncated)`;
|
|
45
|
+
}
|
|
46
|
+
export function terminalTaskSummary(kind, label, status, exitCode) {
|
|
47
|
+
if (kind === "monitor") {
|
|
48
|
+
if (status === "completed")
|
|
49
|
+
return `Monitor "${label}" stream ended`;
|
|
50
|
+
if (status === "failed")
|
|
51
|
+
return `Monitor "${label}" script failed${exitCode !== undefined ? ` (exit ${exitCode})` : ""}`;
|
|
52
|
+
return `Monitor "${label}" stopped`;
|
|
53
|
+
}
|
|
54
|
+
if (status === "completed")
|
|
55
|
+
return `Background command "${label}" completed${exitCode !== undefined ? ` (exit code ${exitCode})` : ""}`;
|
|
56
|
+
if (status === "failed")
|
|
57
|
+
return `Background command "${label}" failed${exitCode !== undefined ? ` with exit code ${exitCode}` : ""}`;
|
|
58
|
+
return `Background command "${label}" was stopped`;
|
|
59
|
+
}
|
|
39
60
|
const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
|
|
40
61
|
export const TASK_OUTPUT_MAX_CHARS = 160_000;
|
|
41
62
|
const TASK_OUTPUT_MIN_CHARS = 512;
|
|
@@ -32,6 +32,7 @@ export interface TaskPollOptions {
|
|
|
32
32
|
block?: boolean;
|
|
33
33
|
timeoutMs?: number;
|
|
34
34
|
signal?: AbortSignal;
|
|
35
|
+
oneShot?: boolean;
|
|
35
36
|
}
|
|
36
37
|
export interface TaskStopOptions {
|
|
37
38
|
workflowStore?: WorkflowRunStore;
|
|
@@ -43,6 +44,7 @@ export interface TaskToolOptions extends TaskAccess {
|
|
|
43
44
|
agentStore?: BackgroundAgentStore;
|
|
44
45
|
deadlineMs?: () => number | undefined;
|
|
45
46
|
notificationWired?: boolean;
|
|
47
|
+
oneShot?: boolean;
|
|
46
48
|
}
|
|
47
49
|
export interface AccessibleTaskRow {
|
|
48
50
|
task_id: string;
|
|
@@ -6,10 +6,10 @@ import { canAccessAgentRecord, } from "./background-agent-store.js";
|
|
|
6
6
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
7
7
|
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
8
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";
|
|
9
|
+
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, 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
10
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
11
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";
|
|
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, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
13
13
|
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
14
14
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
15
15
|
const BLOCK_MAX_TIMEOUT_MS = 600_000;
|
|
@@ -24,19 +24,18 @@ const TASK_PREFIX = {
|
|
|
24
24
|
background_agent: "a",
|
|
25
25
|
monitor: "m",
|
|
26
26
|
};
|
|
27
|
-
function noTask(taskId) {
|
|
27
|
+
function noTask(taskId, runningAgents) {
|
|
28
28
|
return {
|
|
29
|
-
content:
|
|
29
|
+
content: `No task found with ID: ${taskId}${runningAgents ? notFoundRunningAgentsTail(runningAgents) : ""}`,
|
|
30
30
|
details: { task_id: taskId, type: "unknown", status: "not_found", retrieval_status: "not_ready", error: "not_found" },
|
|
31
31
|
isError: true,
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
function noTaskForStop(taskId, enrich) {
|
|
35
35
|
let content = `No task found with ID: ${taskId}`;
|
|
36
|
-
if (enrich
|
|
36
|
+
if (enrich.suggestion !== undefined)
|
|
37
37
|
content += `. Did you mean: ${enrich.suggestion}?`;
|
|
38
|
-
|
|
39
|
-
content += `. Running background agents: ${enrich.runningAgents.join(", ")}`;
|
|
38
|
+
content += notFoundRunningAgentsTail(enrich.runningAgents);
|
|
40
39
|
return {
|
|
41
40
|
content,
|
|
42
41
|
details: { task_id: taskId, type: "unknown", status: "not_found", retrieval_status: "not_ready", error: "not_found" },
|
|
@@ -152,8 +151,8 @@ export class TaskRegistry {
|
|
|
152
151
|
runningBackgroundAgentLabels(access) {
|
|
153
152
|
return runningBackgroundAgentLabelsLane(this.core, access);
|
|
154
153
|
}
|
|
155
|
-
async pollBackgroundAgent(handle, deadline, signal) {
|
|
156
|
-
return pollBackgroundAgentLane(handle, deadline, signal);
|
|
154
|
+
async pollBackgroundAgent(handle, deadline, signal, oneShot) {
|
|
155
|
+
return pollBackgroundAgentLane(handle, deadline, signal, oneShot);
|
|
157
156
|
}
|
|
158
157
|
async stopBackgroundAgent(handle) {
|
|
159
158
|
return stopBackgroundAgentLane(this.core, handle);
|
|
@@ -387,7 +386,7 @@ export class TaskRegistry {
|
|
|
387
386
|
...(handle.outputFile !== undefined ? { output_file: handle.outputFile } : {}),
|
|
388
387
|
status: "killed",
|
|
389
388
|
stoppedBy: handle.stoppedBy,
|
|
390
|
-
summary: `${(handle.description ?? "background command").slice(0, 200)} — killed before completion (${clause(handle.stoppedBy)})${killFailed ? ` — kill attempt reported ${kill.ok ? "" : kill.error.code}: the process may still be terminating; the environment teardown retries` : ""}${bashMirrorGapNote(handle)}`,
|
|
389
|
+
summary: `${terminalTaskSummary("bash", (handle.description ?? "background command").slice(0, 200), "killed")} — killed before completion (${clause(handle.stoppedBy)})${killFailed ? ` — kill attempt reported ${kill.ok ? "" : kill.error.code}: the process may still be terminating; the environment teardown retries` : ""}${bashMirrorGapNote(handle)}`,
|
|
391
390
|
...(resultText.length > 0 ? { result: resultText, partial: true } : {}),
|
|
392
391
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
393
392
|
}));
|
|
@@ -413,7 +412,7 @@ export class TaskRegistry {
|
|
|
413
412
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
414
413
|
status: "killed",
|
|
415
414
|
stoppedBy: handle.stoppedBy,
|
|
416
|
-
summary: `${(handle.description ?? "monitor").slice(0, 200)} — killed before completion (${clause(handle.stoppedBy)}); watch ended.${killFailed ? ` Kill attempt reported ${kill.ok ? "" : kill.error.code}: the process may still be terminating; the environment teardown retries.` : ""}`,
|
|
415
|
+
summary: `${terminalTaskSummary("monitor", (handle.description ?? "monitor").slice(0, 200), "killed")} — killed before completion (${clause(handle.stoppedBy)}); watch ended.${killFailed ? ` Kill attempt reported ${kill.ok ? "" : kill.error.code}: the process may still be terminating; the environment teardown retries.` : ""}`,
|
|
417
416
|
...(lastLines.length > 0 ? { lines: lastLines, result: lastLines.join("\n"), partial: true } : {}),
|
|
418
417
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
419
418
|
}));
|
|
@@ -624,7 +623,7 @@ export class TaskRegistry {
|
|
|
624
623
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
625
624
|
...(handle.outputFile !== undefined ? { output_file: handle.outputFile } : {}),
|
|
626
625
|
status: "failed",
|
|
627
|
-
summary: `${(handle.description ?? "background command").slice(0, 200)} — lost its background process (${r.error.message}); no further output will arrive.${bashMirrorGapNote(handle)}`,
|
|
626
|
+
summary: `${terminalTaskSummary("bash", (handle.description ?? "background command").slice(0, 200), "failed")} — lost its background process (${r.error.message}); no further output will arrive.${bashMirrorGapNote(handle)}`,
|
|
628
627
|
...(lostResult.length > 0 ? { result: lostResult } : {}),
|
|
629
628
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
630
629
|
}));
|
|
@@ -666,14 +665,12 @@ export class TaskRegistry {
|
|
|
666
665
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
667
666
|
...(r.value.status === "exited" && r.value.exitCode !== undefined ? { exitCode: r.value.exitCode } : {}),
|
|
668
667
|
summary: (r.value.status === "exited"
|
|
669
|
-
? r.value.exitCode === 0
|
|
670
|
-
? `Background command "${desc}" completed (exit code 0)`
|
|
671
|
-
: `Background command "${desc}" failed (exit code ${r.value.exitCode})`
|
|
668
|
+
? terminalTaskSummary("bash", desc, r.value.exitCode === 0 ? "completed" : "failed", r.value.exitCode)
|
|
672
669
|
: r.value.timedOut === true
|
|
673
670
|
? r.value.timeoutSec !== undefined
|
|
674
|
-
? `${desc} — timed out after ${r.value.timeoutSec}s (its own background time budget); the process was killed`
|
|
675
|
-
: `${desc} — timed out (its own background time budget); the process was killed`
|
|
676
|
-
: `${desc} — ${r.value.status}`) +
|
|
671
|
+
? `${terminalTaskSummary("bash", desc, "killed")} — timed out after ${r.value.timeoutSec}s (its own background time budget); the process was killed`
|
|
672
|
+
: `${terminalTaskSummary("bash", desc, "killed")} — timed out (its own background time budget); the process was killed`
|
|
673
|
+
: `${terminalTaskSummary("bash", desc, handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed")} — ${r.value.status}`) +
|
|
677
674
|
bashMirrorGapNote(handle) +
|
|
678
675
|
droppedGapNote(spool),
|
|
679
676
|
...(resultText.length > 0 ? { result: resultText } : {}),
|
|
@@ -698,7 +695,7 @@ export class TaskRegistry {
|
|
|
698
695
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
699
696
|
...(handle.outputFile !== undefined ? { output_file: handle.outputFile } : {}),
|
|
700
697
|
status: "failed",
|
|
701
|
-
summary: `${(handle.description ?? "background command").slice(0, 200)} — lost its background process (poll failed: ${e instanceof Error ? e.message : String(e)}); no further output will arrive.${bashMirrorGapNote(handle)}`,
|
|
698
|
+
summary: `${terminalTaskSummary("bash", (handle.description ?? "background command").slice(0, 200), "failed")} — lost its background process (poll failed: ${e instanceof Error ? e.message : String(e)}); no further output will arrive.${bashMirrorGapNote(handle)}`,
|
|
702
699
|
...(crashedResult.length > 0 ? { result: crashedResult } : {}),
|
|
703
700
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
704
701
|
}));
|
|
@@ -749,9 +746,9 @@ export class TaskRegistry {
|
|
|
749
746
|
const handle = this.resolveInternal(taskId, access);
|
|
750
747
|
if (handle) {
|
|
751
748
|
if (handle.type === "background_bash")
|
|
752
|
-
return this.pollBackgroundBash(handle, taskId, opts.filter, deadline, opts.signal);
|
|
749
|
+
return this.pollBackgroundBash(handle, taskId, access, opts.filter, deadline, opts.signal);
|
|
753
750
|
if (handle.type === "background_agent")
|
|
754
|
-
return this.pollBackgroundAgent(handle, deadline, opts.signal);
|
|
751
|
+
return this.pollBackgroundAgent(handle, deadline, opts.signal, opts.oneShot);
|
|
755
752
|
if (handle.type === "monitor")
|
|
756
753
|
return this.pollMonitor(handle, opts.filter, deadline, opts.signal);
|
|
757
754
|
return this.pollWorkflow(handle, access, deadline, opts.signal);
|
|
@@ -803,7 +800,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
803
800
|
await sleepPollStep(deadline, opts.signal);
|
|
804
801
|
}
|
|
805
802
|
}
|
|
806
|
-
return noTask(taskId);
|
|
803
|
+
return noTask(taskId, runningAgentFooterLane(this.core, access));
|
|
807
804
|
}
|
|
808
805
|
async stopTask(id, access, opts = {}) {
|
|
809
806
|
const taskId = id.trim();
|
|
@@ -876,7 +873,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
876
873
|
}
|
|
877
874
|
return noTaskForStop(taskId, {
|
|
878
875
|
...(byName.suggestion !== undefined ? { suggestion: byName.suggestion } : {}),
|
|
879
|
-
runningAgents: this.
|
|
876
|
+
runningAgents: runningAgentFooterLane(this.core, access),
|
|
880
877
|
});
|
|
881
878
|
}
|
|
882
879
|
deliverConfirmTimeoutMs = 10_000;
|
|
@@ -974,7 +971,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
974
971
|
return undefined;
|
|
975
972
|
return handle;
|
|
976
973
|
}
|
|
977
|
-
async pollBackgroundBash(handle, requestedId, filter, deadline, signal) {
|
|
974
|
+
async pollBackgroundBash(handle, requestedId, access, filter, deadline, signal) {
|
|
978
975
|
let rx;
|
|
979
976
|
if (filter !== undefined) {
|
|
980
977
|
try {
|
|
@@ -989,7 +986,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
989
986
|
}
|
|
990
987
|
}
|
|
991
988
|
if (!hasBackgroundShell(handle.env))
|
|
992
|
-
return noTask(requestedId);
|
|
989
|
+
return noTask(requestedId, runningAgentFooterLane(this.core, access));
|
|
993
990
|
if (handle.spool !== undefined) {
|
|
994
991
|
while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
|
|
995
992
|
await sleepPollStep(deadline, signal);
|
|
@@ -1043,7 +1040,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1043
1040
|
const r = await handle.env.pollBackground(handle.shellId);
|
|
1044
1041
|
if (!r.ok) {
|
|
1045
1042
|
if (r.error.code === "not_found")
|
|
1046
|
-
return noTask(requestedId);
|
|
1043
|
+
return noTask(requestedId, runningAgentFooterLane(this.core, access));
|
|
1047
1044
|
const retrieval_status = r.error.code === "timeout" ? "timeout" : "not_ready";
|
|
1048
1045
|
const salvaged = stdout.length > 0 || stderr.length > 0
|
|
1049
1046
|
? `\n--- output accumulated before the error ---\n${clipTaskOutput(stdout, handle.outputFile)}${stderr ? `\n--- stderr ---\n${clipTaskOutput(stderr, handle.outputFile)}` : ""}`
|
|
@@ -1216,6 +1213,7 @@ export function createTaskOutputTool(opts) {
|
|
|
1216
1213
|
block: args.block !== false,
|
|
1217
1214
|
timeoutMs: effectiveTimeoutMs,
|
|
1218
1215
|
signal: ctx.signal,
|
|
1216
|
+
oneShot: opts.oneShot,
|
|
1219
1217
|
});
|
|
1220
1218
|
const { type: taskType, ...rest } = r.details;
|
|
1221
1219
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isToolResult } from "./message-utils.js";
|
|
2
|
-
import { buildPreview, OFFLOAD_TOOL_NAME, PERSISTED_OUTPUT_PREFIX } from "./tool-result-store.js";
|
|
2
|
+
import { buildPreview, buildToolResultRef, OFFLOAD_TOOL_NAME, PERSISTED_OUTPUT_PREFIX } from "./tool-result-store.js";
|
|
3
3
|
export const AGGREGATE_TOOL_RESULT_BUDGET_CHARS = 200_000;
|
|
4
4
|
const HEAD = 1_000;
|
|
5
5
|
const TAIL = 1_000;
|
|
@@ -59,7 +59,7 @@ export async function capAggregateToolResults(messages, opts) {
|
|
|
59
59
|
const previewOne = async (k, before, head, tail, fromOriginal) => {
|
|
60
60
|
const m = out[k];
|
|
61
61
|
const full = textOf((fromOriginal ? messages[k] : m).content);
|
|
62
|
-
const ref =
|
|
62
|
+
const ref = buildToolResultRef(opts.sessionId, m.toolCallId ?? `idx${k}`);
|
|
63
63
|
const sizes = head === HEAD && tail === TAIL ? undefined : { head, tail };
|
|
64
64
|
let previewText;
|
|
65
65
|
let storeFallback;
|
|
@@ -6,6 +6,8 @@ export interface ToolResultStore {
|
|
|
6
6
|
limit?: number;
|
|
7
7
|
}): Promise<ToolResultSlice | undefined> | ToolResultSlice | undefined;
|
|
8
8
|
}
|
|
9
|
+
export declare function assertSafeToolResultRef(ref: string): void;
|
|
10
|
+
export declare function buildToolResultRef(sessionId: string, toolCallId: string): string;
|
|
9
11
|
export interface ToolResultSlice {
|
|
10
12
|
content: string;
|
|
11
13
|
offset: number;
|
|
@@ -1,5 +1,27 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import { defineTool, errorResult } from "./tools.js";
|
|
4
|
+
export function assertSafeToolResultRef(ref) {
|
|
5
|
+
const bad = ref === "" ||
|
|
6
|
+
ref === "." ||
|
|
7
|
+
ref === ".." ||
|
|
8
|
+
ref.includes("/") ||
|
|
9
|
+
ref.includes("\\") ||
|
|
10
|
+
[...ref].some((ch) => ch.charCodeAt(0) < 0x20 || ch.charCodeAt(0) === 0x7f);
|
|
11
|
+
if (bad)
|
|
12
|
+
throw new Error(`tool-result store: unsafe ref ${JSON.stringify(ref)}`);
|
|
13
|
+
}
|
|
14
|
+
const NATIVE_REF_CHARSET = /^[A-Za-z0-9_.-]+$/;
|
|
15
|
+
const MAX_REF_SEGMENT_CHARS = 128;
|
|
16
|
+
export function buildToolResultRef(sessionId, toolCallId) {
|
|
17
|
+
return `tr_${refSegment(sessionId)}_${refSegment(toolCallId)}`;
|
|
18
|
+
}
|
|
19
|
+
function refSegment(raw) {
|
|
20
|
+
if (raw.length <= MAX_REF_SEGMENT_CHARS && NATIVE_REF_CHARSET.test(raw))
|
|
21
|
+
return raw;
|
|
22
|
+
const base = raw.replace(/[^A-Za-z0-9_.-]/g, "-").slice(0, 32);
|
|
23
|
+
return `${base || "x"}-${createHash("sha256").update(raw, "utf8").digest("hex")}`;
|
|
24
|
+
}
|
|
3
25
|
export class InMemoryToolResultStore {
|
|
4
26
|
opts;
|
|
5
27
|
map = new Map();
|
|
@@ -8,6 +30,7 @@ export class InMemoryToolResultStore {
|
|
|
8
30
|
this.opts = opts;
|
|
9
31
|
}
|
|
10
32
|
put(ref, content) {
|
|
33
|
+
assertSafeToolResultRef(ref);
|
|
11
34
|
if (this.map.has(ref))
|
|
12
35
|
return;
|
|
13
36
|
this.map.set(ref, content);
|
|
@@ -48,9 +71,11 @@ export class ScopedToolResultStore {
|
|
|
48
71
|
this.volatileBacking = inner instanceof InMemoryToolResultStore;
|
|
49
72
|
}
|
|
50
73
|
key(ref) {
|
|
51
|
-
|
|
74
|
+
const scope = encodeURIComponent(this.scope);
|
|
75
|
+
return `${scope.length}:${scope}:${ref}`;
|
|
52
76
|
}
|
|
53
77
|
put(ref, content) {
|
|
78
|
+
assertSafeToolResultRef(ref);
|
|
54
79
|
this.localPuts++;
|
|
55
80
|
return this.inner.put(this.key(ref), content);
|
|
56
81
|
}
|
|
@@ -121,7 +146,7 @@ export function withToolResultOffload(tool, store, thresholdChars, sessionId) {
|
|
|
121
146
|
.join("\n");
|
|
122
147
|
if (full.length <= PREVIEW_HEAD_CHARS + PREVIEW_TAIL_CHARS)
|
|
123
148
|
return res;
|
|
124
|
-
const ref =
|
|
149
|
+
const ref = buildToolResultRef(sessionId, toolCallId);
|
|
125
150
|
await store.put(ref, full);
|
|
126
151
|
const images = res.content.filter((b) => b.type !== "text");
|
|
127
152
|
return { ...res, content: [{ type: "text", text: buildPreview(full, ref) }, ...images] };
|
package/dist/core/types.d.ts
CHANGED
|
@@ -92,6 +92,7 @@ export interface ToolExecuteContext {
|
|
|
92
92
|
model?: Model;
|
|
93
93
|
thinkingLevel?: ThinkingLevel;
|
|
94
94
|
principal?: string;
|
|
95
|
+
oneShot?: boolean;
|
|
95
96
|
clientContext?: TaskSpec["clientContext"];
|
|
96
97
|
excludeTools?: readonly string[];
|
|
97
98
|
deferTools?: readonly string[];
|
|
@@ -117,7 +118,7 @@ export interface ToolExecuteContext {
|
|
|
117
118
|
}) => void;
|
|
118
119
|
forwardEvent?: (event: TaskEvent) => void;
|
|
119
120
|
onSubagentSpawn?: (handle: import("../agents/subagent.js").SubagentSteerHandle) => void;
|
|
120
|
-
subagentRetain?: import("../agents/
|
|
121
|
+
subagentRetain?: import("../agents/retain-ledger.js").SubagentRetainLedger;
|
|
121
122
|
worktreeIsolation?: import("../agents/subagent.js").SubagentWorktreeIsolation;
|
|
122
123
|
insideFork?: boolean;
|
|
123
124
|
observersAllowed?: true;
|
|
@@ -344,6 +345,7 @@ export interface TaskSpec {
|
|
|
344
345
|
attachments?: {
|
|
345
346
|
todoReminder?: true;
|
|
346
347
|
todoReminderMode?: "baseline" | "off";
|
|
348
|
+
toolSearchReminder?: true;
|
|
347
349
|
changedFiles?: true | {
|
|
348
350
|
maxFiles?: number;
|
|
349
351
|
};
|
|
@@ -534,7 +536,7 @@ export type TaskEvent = ({
|
|
|
534
536
|
phaseDurations?: import("./auto-compaction.js").CompactionPhaseDurations;
|
|
535
537
|
} | ({
|
|
536
538
|
type: "steering_injected";
|
|
537
|
-
source: "deadline_nudge" | "finalize" | "todo_reminder" | "task_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
539
|
+
source: "deadline_nudge" | "finalize" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
538
540
|
preview: string;
|
|
539
541
|
} & TaskEventIdentity) | ({
|
|
540
542
|
type: "diagnostics";
|
|
@@ -7,6 +7,19 @@ export interface WorkflowJournalStore {
|
|
|
7
7
|
load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
|
|
8
8
|
append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
|
|
9
9
|
locator?(runId: string, scope: string): string | undefined;
|
|
10
|
+
resumeClaim?(input: {
|
|
11
|
+
sourceRunId: string;
|
|
12
|
+
newRunId: string;
|
|
13
|
+
scope: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
granted: boolean;
|
|
16
|
+
holder?: string;
|
|
17
|
+
}>;
|
|
18
|
+
releaseResumeClaim?(input: {
|
|
19
|
+
sourceRunId: string;
|
|
20
|
+
newRunId: string;
|
|
21
|
+
scope: string;
|
|
22
|
+
}): Promise<void>;
|
|
10
23
|
}
|
|
11
24
|
export declare const MAX_JOURNAL_RESULT_BYTES: number;
|
|
12
25
|
export declare function oversizeJournalResult(serialized: string): boolean;
|