@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.
Files changed (71) hide show
  1. package/dist/agents/observer.d.ts +14 -0
  2. package/dist/agents/observer.js +66 -12
  3. package/dist/agents/send-message-tool.js +164 -88
  4. package/dist/agents/subagent.d.ts +11 -4
  5. package/dist/agents/subagent.js +166 -62
  6. package/dist/core/context-edit.js +16 -3
  7. package/dist/core/file-snapshot-store.js +10 -1
  8. package/dist/core/memory-engine/dual-root.js +2 -0
  9. package/dist/core/memory-engine/engine.d.ts +4 -0
  10. package/dist/core/memory-engine/engine.js +6 -1
  11. package/dist/core/runner/prepare-memory.d.ts +4 -0
  12. package/dist/core/runner/prepare-memory.js +4 -1
  13. package/dist/core/runner/prepare-task.d.ts +9 -2
  14. package/dist/core/runner/prepare-task.js +68 -13
  15. package/dist/core/runner/runtask.js +919 -865
  16. package/dist/core/runner/synthetic-tools.d.ts +1 -0
  17. package/dist/core/runner/synthetic-tools.js +18 -15
  18. package/dist/core/runner/turn-attachments.d.ts +27 -3
  19. package/dist/core/runner/turn-attachments.js +101 -12
  20. package/dist/core/task-registry-agent.d.ts +9 -1
  21. package/dist/core/task-registry-agent.js +23 -2
  22. package/dist/core/task-registry-monitor.js +79 -24
  23. package/dist/core/task-registry-shared.d.ts +13 -1
  24. package/dist/core/task-registry-shared.js +21 -0
  25. package/dist/core/task-registry.d.ts +2 -0
  26. package/dist/core/task-registry.js +24 -26
  27. package/dist/core/tool-result-budget.js +2 -2
  28. package/dist/core/tool-result-store.d.ts +2 -0
  29. package/dist/core/tool-result-store.js +27 -2
  30. package/dist/core/types.d.ts +4 -2
  31. package/dist/core/workflow-journal-store.d.ts +13 -0
  32. package/dist/engine/session/import-validate.js +29 -0
  33. package/dist/engine/session/memory-repo.js +5 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/orchestration/workflow-size-guideline.d.ts +6 -1
  37. package/dist/orchestration/workflow-size-guideline.js +19 -9
  38. package/dist/orchestration/workflow.d.ts +1 -0
  39. package/dist/orchestration/workflow.js +44 -1
  40. package/dist/prompt-assembly/assemble.js +3 -7
  41. package/dist/prompt-assembly/event-registry.js +1 -1
  42. package/dist/prompt-assembly/packs/sema-default.js +8 -5
  43. package/dist/prompts/coordinator.d.ts +1 -1
  44. package/dist/prompts/coordinator.js +45 -0
  45. package/dist/prompts/default.d.ts +4 -5
  46. package/dist/prompts/default.js +16 -18
  47. package/dist/prompts/simple-sections.d.ts +3 -1
  48. package/dist/prompts/simple-sections.js +11 -1
  49. package/dist/stores/cc/task-list-store.js +3 -3
  50. package/dist/stores/file/memory-store.d.ts +3 -0
  51. package/dist/stores/file/memory-store.js +39 -12
  52. package/dist/stores/file/tool-result-store.js +16 -2
  53. package/dist/stores/file/workflow-journal-store.d.ts +23 -0
  54. package/dist/stores/file/workflow-journal-store.js +140 -3
  55. package/dist/tools/fs/bash-readonly-classifier.js +21 -2
  56. package/dist/tools/fs/fs-bash.d.ts +1 -0
  57. package/dist/tools/fs/fs-bash.js +10 -3
  58. package/dist/tools/fs/fs-read.js +12 -12
  59. package/dist/tools/fs/fs-search-tools.js +42 -7
  60. package/dist/tools/fs/fs-write.js +18 -6
  61. package/dist/tools/fs/index.d.ts +1 -0
  62. package/dist/tools/fs/index.js +2 -1
  63. package/dist/tools/fs/safety.d.ts +4 -0
  64. package/dist/tools/fs/safety.js +113 -10
  65. package/dist/tools/fs/search.d.ts +1 -0
  66. package/dist/tools/fs/search.js +23 -3
  67. package/dist/tools/monitor.js +1 -1
  68. package/dist/tools/task-list.d.ts +1 -0
  69. package/dist/tools/task-list.js +13 -2
  70. package/dist/tools/web.js +36 -6
  71. 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
- batchTimes: [],
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: Math.max(1, Math.floor(input.maxBatchesPerMinute ?? MONITOR_MAX_BATCHES_PER_MINUTE)),
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 terminal = (status, summary, finalLines) => {
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)} — ${summary}${droppedGapNote(handle.spool)}`,
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.`, pendingFinalLines([]));
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, pendingFinalLines(lines));
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.`, pendingFinalLines(lines));
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.`, pendingFinalLines(lines));
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
- const cutoff = now - 60_000;
152
- handle.batchTimes = handle.batchTimes.filter((t) => t > cutoff);
153
- handle.batchTimes.push(now);
154
- if (handle.batchTimes.length > handle.maxBatchesPerMinute) {
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 (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));
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: it produced too many events (more than ${handle.maxBatchesPerMinute} notification batches within a minute). ` +
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(lines.join("\n")),
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.`, pendingFinalLines([]));
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
- batchTimes: number[];
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: JSON.stringify({ error: "task not found" }),
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?.suggestion !== undefined)
36
+ if (enrich.suggestion !== undefined)
37
37
  content += `. Did you mean: ${enrich.suggestion}?`;
38
- if (enrich?.runningAgents !== undefined && enrich.runningAgents.length > 0)
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.runningBackgroundAgentLabels(access),
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 = `tr_${opts.sessionId}_${m.toolCallId ?? `idx${k}`}`;
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
- return `${this.scope.length}:${this.scope}:${ref}`;
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 = `tr_${sessionId}_${toolCallId}`;
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] };
@@ -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/subagent.js").SubagentRetainLedger;
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;