@sema-agent/core 5.8.0 → 5.10.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 (68) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/agents/cascade.js +24 -0
  3. package/dist/agents/roster-store.d.ts +1 -0
  4. package/dist/agents/send-message-tool.js +6 -0
  5. package/dist/agents/subagent.d.ts +33 -0
  6. package/dist/agents/subagent.js +125 -33
  7. package/dist/agents/teacher.js +15 -3
  8. package/dist/agents/team.js +10 -0
  9. package/dist/agents/verify.js +7 -0
  10. package/dist/brain/anthropic.js +27 -10
  11. package/dist/brain/open-responses.d.ts +11 -0
  12. package/dist/brain/open-responses.js +736 -0
  13. package/dist/brain/openai.js +32 -5
  14. package/dist/brain/request-params.d.ts +1 -0
  15. package/dist/brain/request-params.js +16 -0
  16. package/dist/core/a2a.js +1 -1
  17. package/dist/core/fs-write-gate-policy.js +2 -2
  18. package/dist/core/lsp-diagnostics.d.ts +3 -2
  19. package/dist/core/lsp-diagnostics.js +20 -7
  20. package/dist/core/memory-recall.js +8 -3
  21. package/dist/core/memory.d.ts +5 -0
  22. package/dist/core/memory.js +6 -4
  23. package/dist/core/runner/assemble-result.d.ts +1 -0
  24. package/dist/core/runner/assemble-result.js +14 -7
  25. package/dist/core/runner/prepare-task.d.ts +9 -1
  26. package/dist/core/runner/prepare-task.js +51 -14
  27. package/dist/core/runner/runtask.d.ts +12 -0
  28. package/dist/core/runner/runtask.js +153 -42
  29. package/dist/core/runner/session-file-state-replay.d.ts +7 -0
  30. package/dist/core/runner/session-file-state-replay.js +56 -0
  31. package/dist/core/runner/session-rule-policy.d.ts +1 -0
  32. package/dist/core/runner/session-rule-policy.js +4 -3
  33. package/dist/core/runner/synthetic-tools.js +1 -1
  34. package/dist/core/runner/tool-output-projection.js +5 -4
  35. package/dist/core/session-reconcile.d.ts +7 -3
  36. package/dist/core/session-reconcile.js +3 -2
  37. package/dist/core/strategy-store.d.ts +1 -1
  38. package/dist/core/strategy-store.js +27 -4
  39. package/dist/core/task-registry-shared.d.ts +0 -1
  40. package/dist/core/tool-policy.d.ts +8 -0
  41. package/dist/core/tool-policy.js +11 -0
  42. package/dist/core/tools.js +9 -1
  43. package/dist/core/trace.d.ts +0 -2
  44. package/dist/core/types.d.ts +8 -1
  45. package/dist/engine/harness/agent-harness.d.ts +1 -0
  46. package/dist/engine/harness/agent-harness.js +3 -0
  47. package/dist/engine/harness/types.d.ts +1 -0
  48. package/dist/engine/llm/types.d.ts +2 -73
  49. package/dist/engine/loop/agent-loop.js +168 -22
  50. package/dist/engine/loop/types.d.ts +1 -0
  51. package/dist/engine/session/repo-utils.d.ts +1 -2
  52. package/dist/engine/session/repo-utils.js +0 -7
  53. package/dist/index.d.ts +3 -2
  54. package/dist/index.js +2 -1
  55. package/dist/internal/llm.d.ts +1 -1
  56. package/dist/orchestration/run-workflow-tool.js +1 -1
  57. package/dist/orchestration/workflow-governance.js +19 -0
  58. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  59. package/dist/orchestration/workflow-primitives.js +4 -1
  60. package/dist/orchestration/workflow.js +15 -6
  61. package/dist/prompts/coordinator.d.ts +1 -1
  62. package/dist/prompts/coordinator.js +1 -1
  63. package/dist/stores/file/memory-store.js +3 -7
  64. package/dist/tools/fs/fs-bash.js +3 -3
  65. package/dist/tools/fs/fs-shared.d.ts +1 -0
  66. package/dist/tools/fs/fs-shared.js +4 -0
  67. package/dist/tools/web.js +20 -20
  68. package/package.json +5 -3
@@ -481,9 +481,17 @@ export function createOpenAIBrain(config = {}) {
481
481
  finalContent.push({ type: "text", text: textFace });
482
482
  const toolCalls = [];
483
483
  const malformed = [];
484
+ const unnamed = [];
485
+ let emptyPlaceholders = 0;
484
486
  for (const acc of [...toolAccum.entries()].sort((a, b) => a[0] - b[0]).map((e) => e[1])) {
485
- if (!acc.name)
487
+ if (!acc.name) {
488
+ if (!acc.id && acc.args === "") {
489
+ emptyPlaceholders++;
490
+ continue;
491
+ }
492
+ unnamed.push(`id="${acc.id || "?"}"(${acc.args.slice(0, 200)})`);
486
493
  continue;
494
+ }
487
495
  const tc = closeToolCallAccum(acc);
488
496
  if (tc) {
489
497
  toolCalls.push(tc);
@@ -508,16 +516,35 @@ export function createOpenAIBrain(config = {}) {
508
516
  }
509
517
  }
510
518
  }
511
- const toolError = malformed.length > 0
512
- ? `tool call argument(s) not valid JSON (likely truncated, finish_reason="${finishReason ?? "?"}"): ${malformed.join("; ")}`
513
- : undefined;
519
+ const emptySlots = `<empty slot>${emptyPlaceholders > 1 ? ` ×${emptyPlaceholders}` : ""}`;
520
+ const sawRealAction = toolCalls.length > 0 || malformed.length > 0 || unnamed.length > 0;
521
+ if (emptyPlaceholders > 0 && sawRealAction) {
522
+ unnamed.push(emptySlots);
523
+ }
524
+ else if (!sawRealAction && finishReason === "tool_calls") {
525
+ unnamed.push(emptyPlaceholders > 0 ? emptySlots : "<no tool_call delta arrived on the stream>");
526
+ }
527
+ const toolErrorParts = [];
528
+ if (malformed.length > 0) {
529
+ toolErrorParts.push(`tool call argument(s) not valid JSON (likely truncated, finish_reason="${finishReason ?? "?"}"): ${malformed.join("; ")}`);
530
+ }
531
+ if (unnamed.length > 0) {
532
+ toolErrorParts.push(`tool call(s) arrived with no tool name and cannot be executed (finish_reason="${finishReason ?? "?"}"): ${unnamed.join("; ")}`);
533
+ }
534
+ const toolError = toolErrorParts.length > 0 ? toolErrorParts.join(" | ") : undefined;
514
535
  const noUsableContent = toolCalls.length === 0 && !accumText.trim();
515
- if (toolError !== undefined) {
536
+ if (malformed.length > 0) {
516
537
  finalContent.push({
517
538
  type: "text",
518
539
  text: `\n[note: ${malformed.length} tool call(s) were truncated (finish_reason="${finishReason ?? "?"}") and dropped — re-issue them next turn: ${malformed.join("; ")}]`,
519
540
  });
520
541
  }
542
+ if (unnamed.length > 0) {
543
+ finalContent.push({
544
+ type: "text",
545
+ text: `\n[note: ${unnamed.length} tool call(s) arrived with no tool name and were dropped — re-issue them next turn with an explicit tool name: ${unnamed.join("; ")}]`,
546
+ });
547
+ }
521
548
  if (malformedFrames > 0) {
522
549
  finalContent.push({
523
550
  type: "text",
@@ -1,5 +1,6 @@
1
1
  export declare const OPENAI_RESERVED: ReadonlySet<string>;
2
2
  export declare const ANTHROPIC_RESERVED: ReadonlySet<string>;
3
+ export declare const RESPONSES_RESERVED: ReadonlySet<string>;
3
4
  export declare function reservedFor(api: string): ReadonlySet<string>;
4
5
  export declare function applyExtraBody(body: Record<string, unknown>, extraBody: Record<string, unknown> | undefined, reserved: ReadonlySet<string>): Record<string, unknown>;
5
6
  export declare function stripAuthHeaders(headers: Record<string, string>): void;
@@ -25,7 +25,23 @@ export const ANTHROPIC_RESERVED = new Set([
25
25
  "output_config",
26
26
  "context_management",
27
27
  ]);
28
+ export const RESPONSES_RESERVED = new Set([
29
+ "model",
30
+ "input",
31
+ "stream",
32
+ "instructions",
33
+ "tools",
34
+ "temperature",
35
+ "max_output_tokens",
36
+ "reasoning",
37
+ "store",
38
+ "previous_response_id",
39
+ "conversation",
40
+ ]);
41
+ const RESPONSES_APIS = new Set(["openai-responses", "azure-openai-responses", "openai-chatgpt-responses"]);
28
42
  export function reservedFor(api) {
43
+ if (RESPONSES_APIS.has(api))
44
+ return RESPONSES_RESERVED;
29
45
  return api === "anthropic-messages" ? ANTHROPIC_RESERVED : OPENAI_RESERVED;
30
46
  }
31
47
  export function applyExtraBody(body, extraBody, reserved) {
package/dist/core/a2a.js CHANGED
@@ -23,7 +23,7 @@ const A2A_CARD_DESCRIPTION_MAX_CHARS = 240;
23
23
  const A2A_ID_MAX_CHARS = 160;
24
24
  const A2A_RESULT_BODY_MAX_CHARS = 100_000;
25
25
  const A2A_ERROR_TEXT_MAX_CHARS = 240;
26
- const A2A_RESULT_FENCE_REASON = "the peer is an autonomous agent, so its output is worker text, not tool data";
26
+ const A2A_RESULT_FENCE_REASON = "the peer is an agent acting on its own, so its output is worker text, not tool data";
27
27
  const A2A_CARD_PATHS = ["/.well-known/agent-card.json", "/.well-known/agent.json"];
28
28
  const A2A_PROTOCOL_VERSION = "1.0";
29
29
  const A2A_JSONRPC_TRANSPORT = "JSONRPC";
@@ -1,9 +1,9 @@
1
1
  import { canonicalizeTarget, writeTargetPath } from "../tools/fs/safety.js";
2
- import { PATH_WRITE_TOOLS, isWithin } from "./runner/session-rule-policy.js";
2
+ import { PATH_CONFINABLE_WRITE_TOOLS, isWithin } from "./runner/session-rule-policy.js";
3
3
  const ask = (message) => ({ action: "ask", message, decisionReason: "rule" });
4
4
  export function createFsWriteGatePolicy(opts) {
5
5
  const { env, rootPath, defaultWrite } = opts;
6
- const gated = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]);
6
+ const gated = PATH_CONFINABLE_WRITE_TOOLS;
7
7
  const acceptDirs = opts.acceptDirs && opts.acceptDirs.length > 0 ? opts.acceptDirs : undefined;
8
8
  const exemptDirs = opts.exemptDirs && opts.exemptDirs.length > 0 ? opts.exemptDirs : undefined;
9
9
  return {
@@ -22,9 +22,10 @@ export declare class LspDiagnosticsRegistry {
22
22
  private readonly pending;
23
23
  private readonly delivered;
24
24
  publish(uri: string, diagnostics: LspDiagnostic[]): void;
25
- fileEdited(uri: string): void;
25
+ fileEdited(runIdent: string, uri: string): void;
26
+ releaseRun(runIdent: string): void;
26
27
  isEmpty(): boolean;
27
- drain(): LspFileDiagnostics[];
28
+ drain(runIdent: string): LspFileDiagnostics[];
28
29
  }
29
30
  export declare function formatDiagnosticsSummary(files: LspFileDiagnostics[]): string;
30
31
  export declare function formatDiagnosticsBlock(files: LspFileDiagnostics[]): string;
@@ -16,30 +16,39 @@ function diagnosticKey(uri, d) {
16
16
  }
17
17
  export class LspDiagnosticsRegistry {
18
18
  pending = new Map();
19
- delivered = new Set();
19
+ delivered = new Map();
20
20
  publish(uri, diagnostics) {
21
21
  if (diagnostics.length === 0)
22
22
  this.pending.delete(uri);
23
23
  else
24
24
  this.pending.set(uri, diagnostics);
25
25
  }
26
- fileEdited(uri) {
26
+ fileEdited(runIdent, uri) {
27
+ const forRun = this.delivered.get(runIdent);
28
+ if (forRun === undefined)
29
+ return;
27
30
  const prefix = `${uri}|`;
28
- for (const key of this.delivered) {
31
+ for (const key of forRun) {
29
32
  if (key.startsWith(prefix))
30
- this.delivered.delete(key);
33
+ forRun.delete(key);
31
34
  }
35
+ if (forRun.size === 0)
36
+ this.delivered.delete(runIdent);
37
+ }
38
+ releaseRun(runIdent) {
39
+ this.delivered.delete(runIdent);
32
40
  }
33
41
  isEmpty() {
34
42
  return this.pending.size === 0;
35
43
  }
36
- drain() {
44
+ drain(runIdent) {
37
45
  const out = [];
38
46
  const requeued = new Map();
47
+ let delivered = this.delivered.get(runIdent);
39
48
  let total = 0;
40
49
  for (const [uri, all] of this.pending) {
41
50
  const fresh = all
42
- .filter((d) => !this.delivered.has(diagnosticKey(uri, d)))
51
+ .filter((d) => delivered?.has(diagnosticKey(uri, d)) !== true)
43
52
  .sort((a, b) => (a.severity ?? 99) - (b.severity ?? 99));
44
53
  if (fresh.length === 0)
45
54
  continue;
@@ -49,8 +58,12 @@ export class LspDiagnosticsRegistry {
49
58
  requeued.set(uri, fresh.slice(take.length));
50
59
  if (take.length === 0)
51
60
  continue;
61
+ if (delivered === undefined) {
62
+ delivered = new Set();
63
+ this.delivered.set(runIdent, delivered);
64
+ }
52
65
  for (const d of take)
53
- this.delivered.add(diagnosticKey(uri, d));
66
+ delivered.add(diagnosticKey(uri, d));
54
67
  total += take.length;
55
68
  out.push({ uri, diagnostics: take });
56
69
  }
@@ -30,6 +30,11 @@ export const RECALL_CAVEAT = "These notes were recalled as relevant, but they ar
30
30
  "- If the user is about to act on your recommendation (not just asking about history), verify first.\n" +
31
31
  "- If a note names a file, check the file exists; if it names a function or symbol, grep for it; if it names a flag or value, read the current source.\n" +
32
32
  "- Memory is a snapshot from when it was written, not a live view — prefer `git log` or reading the code over recalling the snapshot.";
33
+ function isNewerNote(candidate, incumbent) {
34
+ if (candidate.timestampMissing || incumbent.timestampMissing)
35
+ return false;
36
+ return candidate.mtimeMs > incumbent.mtimeMs;
37
+ }
33
38
  export function resolveLinkedIds(headers, selected, max) {
34
39
  if (max <= 0 || selected.length === 0)
35
40
  return [];
@@ -38,7 +43,7 @@ export function resolveLinkedIds(headers, selected, max) {
38
43
  if (!h.name)
39
44
  continue;
40
45
  const prev = byName.get(h.name);
41
- if (!prev || h.mtimeMs > prev.mtimeMs)
46
+ if (!prev || isNewerNote(h, prev))
42
47
  byName.set(h.name, h);
43
48
  }
44
49
  const selectedIds = new Set(selected.map((r) => r.id));
@@ -100,8 +105,8 @@ export function validateSelectedIds(headers, ids, max) {
100
105
  export function composeSelectiveBody(manifestText, selected, nowMs, linked = [], recallable = true) {
101
106
  const renderNote = (r, label) => {
102
107
  const ageMs = nowMs - r.mtimeMs;
103
- const verify = ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
104
- const stale = ` (written ${formatMemoryAge(ageMs)}${verify})`;
108
+ const verify = r.timestampMissing || ageMs > ONE_DAY_MS ? " — verify it's still current" : "";
109
+ const stale = r.timestampMissing ? ` (write time unknown${verify})` : ` (written ${formatMemoryAge(ageMs)}${verify})`;
105
110
  const prefix = label ? `${sanitizeUntrustedText(label, ["user_memory"])} ` : "";
106
111
  return `- ${prefix}${sanitizeUntrustedText(r.text, ["user_memory"])}${stale}`;
107
112
  };
@@ -40,6 +40,7 @@ export interface MemoryNoteHeader {
40
40
  id: string;
41
41
  description: string;
42
42
  mtimeMs: number;
43
+ timestampMissing?: true;
43
44
  name?: string;
44
45
  type?: string;
45
46
  consolidationGenerated?: boolean;
@@ -84,6 +85,10 @@ export declare class InMemoryMemoryStore implements MemoryStore {
84
85
  export declare function expandLexicalTerms(term: string): string[];
85
86
  export declare function lexicalSearchMatch(query: string, text: string): boolean;
86
87
  export declare function firstSentence(text: string): string;
88
+ export declare function parseNoteTimestamp(ts: unknown): {
89
+ mtimeMs: number;
90
+ timestampMissing?: true;
91
+ };
87
92
  export interface NormalizedMemorySpec {
88
93
  scopes: string[];
89
94
  writeScope: string | null;
@@ -268,7 +268,7 @@ export class InMemoryMemoryStore {
268
268
  return entries.map((e) => ({
269
269
  id: e.id,
270
270
  description: e.description ?? firstSentence(e.text),
271
- mtimeMs: mtimeMsOf(e.ts),
271
+ ...parseNoteTimestamp(e.ts),
272
272
  ...(e.name ? { name: e.name } : {}),
273
273
  ...(e.type ? { type: e.type } : {}),
274
274
  ...(e.consolidationGenerated ? { consolidationGenerated: true } : {}),
@@ -291,7 +291,7 @@ export class InMemoryMemoryStore {
291
291
  id: e.id,
292
292
  text: e.text,
293
293
  description: e.description ?? firstSentence(e.text),
294
- mtimeMs: mtimeMsOf(e.ts),
294
+ ...parseNoteTimestamp(e.ts),
295
295
  ...(e.name ? { name: e.name } : {}),
296
296
  ...(e.type ? { type: e.type } : {}),
297
297
  ...(e.consolidationGenerated ? { consolidationGenerated: true } : {}),
@@ -347,9 +347,11 @@ export function firstSentence(text) {
347
347
  const cut = dot >= 0 && dot < 160 ? dot + 1 : Math.min(t.length, 120);
348
348
  return t.slice(0, cut).trim();
349
349
  }
350
- function mtimeMsOf(ts) {
350
+ export function parseNoteTimestamp(ts) {
351
+ if (typeof ts !== "string" || ts.trim() === "")
352
+ return { mtimeMs: 0, timestampMissing: true };
351
353
  const ms = Date.parse(`${ts.replace(" ", "T")}:00Z`);
352
- return Number.isFinite(ms) ? ms : 0;
354
+ return Number.isFinite(ms) ? { mtimeMs: ms } : { mtimeMs: 0, timestampMissing: true };
353
355
  }
354
356
  export function normalizeMemorySpec(input) {
355
357
  if (!input)
@@ -59,6 +59,7 @@ export interface ResultFlags {
59
59
  unpricedSpend?: boolean;
60
60
  rewindNotes?: TaskResult["rewindNotes"];
61
61
  remoteEnvFailures?: TaskResult["remoteEnvFailures"];
62
+ retryAfterMs?: number;
62
63
  abortedForTimeout: boolean;
63
64
  abortedForTurns: boolean;
64
65
  abortedLive?: boolean;
@@ -1,5 +1,14 @@
1
1
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
2
2
  import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
3
+ const SALVAGE_ELIGIBLE_TERMINALS = new Set([
4
+ "output.degenerate",
5
+ "limits.max_tokens_exceeded",
6
+ "limits.max_cost_exceeded",
7
+ "limits.max_turns_exceeded",
8
+ "limits.max_walltime_exceeded",
9
+ "env.lifetime_expired",
10
+ "usage.window_exhausted",
11
+ ]);
3
12
  export function errorCodeOf(err) {
4
13
  let cur = err;
5
14
  let fallback;
@@ -63,7 +72,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
63
72
  status = "failed";
64
73
  errorCode = "output.degenerate";
65
74
  errorMessage = final?.errorMessage;
66
- salvagedOutput = text.trim() || undefined;
67
75
  }
68
76
  else if (flags.budgetHit) {
69
77
  status = "failed";
@@ -73,7 +81,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
73
81
  flags.budgetHit === "precall"
74
82
  ? `the estimated cost of the first call already exceeds ${axisName}; the task was not started`
75
83
  : `cumulative usage exceeded ${axisName}`;
76
- salvagedOutput = text.trim() || undefined;
77
84
  }
78
85
  else if (flags.suspendLoop) {
79
86
  status = "failed";
@@ -90,8 +97,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
90
97
  : flags.conflict
91
98
  ? "conflict"
92
99
  : errorCodeOf(flags.threw);
93
- if (flags.abortedForTimeout || flags.abortedForTurns)
94
- salvagedOutput = text.trim() || undefined;
95
100
  }
96
101
  else if (flags.blockedReason) {
97
102
  status = "blocked";
@@ -114,8 +119,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
114
119
  status = "failed";
115
120
  errorCode = flags.abortedForTimeout ? "limits.max_walltime_exceeded" : flags.abortedForTurns ? "limits.max_turns_exceeded" : undefined;
116
121
  errorMessage = final?.errorMessage ?? (flags.abortedForTurns ? "max turns exceeded" : "run aborted");
117
- if (flags.abortedForTimeout || flags.abortedForTurns)
118
- salvagedOutput = text.trim() || undefined;
119
122
  }
120
123
  else if (!final) {
121
124
  status = "failed";
@@ -139,9 +142,13 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
139
142
  else {
140
143
  status = "completed";
141
144
  }
145
+ if (errorCode !== undefined && SALVAGE_ELIGIBLE_TERMINALS.has(errorCode)) {
146
+ salvagedOutput = text.trim() || undefined;
147
+ }
148
+ const retryAfterMs = errorCode === "usage.window_exhausted" ? flags.retryAfterMs : undefined;
142
149
  const { compactionMicroUsd: _internalCompaction, ...publicStats } = stats;
143
150
  void _internalCompaction;
144
151
  if (flags.unpricedSpend)
145
152
  delete publicStats.costMicroUsd;
146
- return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), stats: publicStats };
153
+ return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), stats: publicStats };
147
154
  }
@@ -9,6 +9,7 @@ import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
9
9
  import type { OnAsk, ToolPolicy } from "../tool-policy.js";
10
10
  import { type ActiveSkillFrame } from "./active-skill-scope.js";
11
11
  import type { SessionPermissionRules } from "../session-policy-store.js";
12
+ import { type RecoveredOrphan } from "../session-reconcile.js";
12
13
  import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
13
14
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
14
15
  import { type OutputRef, type BlockedRef, type SkillListingEntry } from "./synthetic-tools.js";
@@ -18,7 +19,7 @@ import type { TaskNotificationPayload } from "../task-notification.js";
18
19
  import { type CwdRef } from "../../tools/fs/index.js";
19
20
  import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
20
21
  import type { Runner } from "./runtask.js";
21
- import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
22
+ import { type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
22
23
  import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
23
24
  import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskLimits, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
24
25
  import type { RepairBundle } from "../../agents/repair-loop.js";
@@ -42,6 +43,11 @@ export declare function checkpointScopeOf(spec: {
42
43
  };
43
44
  principal?: string;
44
45
  }): string;
46
+ export declare function resolveCheckpointStore(spec: {
47
+ checkpointStore?: CheckpointStore | null;
48
+ }, deps: {
49
+ checkpointStore?: CheckpointStore;
50
+ }): CheckpointStore | undefined;
45
51
  export interface Prepared {
46
52
  harness: AgentHarness;
47
53
  session: StoredSession;
@@ -187,6 +193,7 @@ export interface Prepared {
187
193
  now: () => number;
188
194
  tools: AgentTool[];
189
195
  toolEffects: Map<string, ToolEffect>;
196
+ wakeRecovered: RecoveredOrphan[];
190
197
  promptOverheadTokens: number;
191
198
  readTaskFile?: (path: string) => Promise<string | null>;
192
199
  recentlyReadFiles?: () => string[];
@@ -199,6 +206,7 @@ export interface Prepared {
199
206
  lspDiagnostics?: {
200
207
  registry: import("../lsp-diagnostics.js").LspDiagnosticsRegistry;
201
208
  nudge: (rawPath: string) => void;
209
+ runIdent: string;
202
210
  };
203
211
  planModeRef: {
204
212
  active: boolean;
@@ -17,7 +17,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
17
17
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
18
18
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
19
19
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
20
- import { combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
20
+ import { askApproverIdentity, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
21
21
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
22
22
  import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
23
23
  import { inlineUntrusted } from "../untrusted-text.js";
@@ -57,7 +57,7 @@ import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.j
57
57
  import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
58
58
  import { createMonitorTool } from "../../tools/monitor.js";
59
59
  import { createWorktreeTools } from "../../tools/worktree.js";
60
- import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
60
+ import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, seedReadFileStateFromTranscript, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
61
61
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
62
62
  import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool } from "../ask-question.js";
63
63
  import { createSchedulerTools } from "../../tools/scheduler-tools.js";
@@ -67,6 +67,7 @@ import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestrati
67
67
  import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
68
68
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
69
69
  import { resolveKey } from "../../tools/fs/safety.js";
70
+ import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
70
71
  import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
71
72
  import { boundInputHashOf } from "../canonical-json.js";
72
73
  import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
@@ -171,6 +172,11 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
171
172
  export function checkpointScopeOf(spec) {
172
173
  return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
173
174
  }
175
+ export function resolveCheckpointStore(spec, deps) {
176
+ if (spec.checkpointStore === null)
177
+ return undefined;
178
+ return spec.checkpointStore ?? deps.checkpointStore;
179
+ }
174
180
  export function isFableFamilyModelId(id) {
175
181
  const tail = id.toLowerCase().split("/").pop() ?? "";
176
182
  return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
@@ -319,6 +325,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
319
325
  throw e;
320
326
  }
321
327
  resolveTaskLimits(spec.limits);
328
+ if (spec.resourceSuspend !== undefined) {
329
+ const rsus = spec.resourceSuspend;
330
+ if (typeof rsus.scope !== "string" || rsus.scope === "") {
331
+ throw limitConfigError("config.limit_invalid", `TaskSpec.resourceSuspend.scope must be a non-empty string (got ${String(rsus.scope)}) — it is the multi-tenant isolation key every resource checkpoint is filed under.`);
332
+ }
333
+ for (const key of ["totalBudgetUsd", "totalTokens", "maxSlices", "ttlMs"]) {
334
+ const value = rsus[key];
335
+ if (value === undefined)
336
+ continue;
337
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
338
+ throw limitConfigError("config.limit_invalid", `TaskSpec.resourceSuspend.${key} must be a finite, non-negative number (got ${String(value)}) — an unevaluable allocation is not an allocation, and a NaN here blinds even the validated per-slice window.`);
339
+ }
340
+ }
341
+ }
322
342
  const usageWindows = resolveUsageWindows(deps.usageWindows);
323
343
  const brainCallGuardrailRef = {};
324
344
  const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
@@ -411,6 +431,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
411
431
  let acquired;
412
432
  let session;
413
433
  let conflictRef;
434
+ let wakeRecovered = [];
414
435
  let resumeAtBeforeParentId = null;
415
436
  for (let attempt = 0;; attempt++) {
416
437
  try {
@@ -487,7 +508,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
487
508
  }
488
509
  await session.getStorage().setLeafId(spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt);
489
510
  }
490
- await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch);
511
+ wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
491
512
  break;
492
513
  }
493
514
  catch (err) {
@@ -910,6 +931,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
910
931
  const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
911
932
  const ownSessionRulesRef = {};
912
933
  const frozenOnAsk = spec.onAsk ?? deps.onAsk;
934
+ const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
913
935
  const inheritedGateForChildren = () => {
914
936
  const ancestorRules = [
915
937
  ...(inheritedAncestorRules ?? []),
@@ -944,6 +966,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
944
966
  model: harnessRef.current?.getModel(),
945
967
  thinkingLevel: harnessRef.current?.getThinkingLevel(),
946
968
  principal: spec.principal,
969
+ ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
970
+ ...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
971
+ ...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
972
+ ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
947
973
  oneShot: spec.oneShot,
948
974
  clientContext: spec.clientContext,
949
975
  excludeTools: toolFaceSnapshot.exclude,
@@ -960,6 +986,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
960
986
  inheritedGateForChildren,
961
987
  ...(autoModeDecider ? { autoModeReview: { decider: autoModeDecider } } : {}),
962
988
  ...(spec.durableApproval !== undefined ? { durableApprovalForChildren: { ...spec.durableApproval } } : {}),
989
+ ...(spec.checkpointStore === null ? { checkpointStoreDisabledForChildren: true } : {}),
963
990
  taskId: hostTaskId,
964
991
  ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
965
992
  ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
@@ -1013,7 +1040,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1013
1040
  tools.push(createReportFindingsTool());
1014
1041
  }
1015
1042
  if (spec.enablePlanMode === true && spec.interactiveTools !== false) {
1016
- const planReviewFace = (spec.checkpointStore ?? deps.checkpointStore) !== undefined;
1043
+ const planReviewFace = resolveCheckpointStore(spec, deps) !== undefined;
1017
1044
  if (spec.interactiveTools === true || planReviewFace) {
1018
1045
  tools.push(defineTool(createPresentPlanTool(requestReview)));
1019
1046
  if (planReviewFace) {
@@ -1351,6 +1378,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1351
1378
  }
1352
1379
  const readFileState = new Map((resume?.seed.readFileState ?? []).map(([k, v]) => [rebaseRestoredPath(k), v]));
1353
1380
  readFileStateForCheckpoint = readFileState;
1381
+ if (resume === undefined && spec.sessionId !== undefined) {
1382
+ const prior = await session.buildContext().catch(() => undefined);
1383
+ for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
1384
+ const rk = await resolveKey(executionEnv, rootCanonical, rec.path, undefined, undefined, additionalRootsCanonical);
1385
+ if (rk.ok)
1386
+ seedReadFileStateFromTranscript(readFileState, rk.key, rec.content, rec.at);
1387
+ }
1388
+ }
1354
1389
  seedContextFiles = async (files) => {
1355
1390
  for (const f of files) {
1356
1391
  const rk = await resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, additionalRootsCanonical);
@@ -1532,8 +1567,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1532
1567
  }
1533
1568
  if (offloadStore)
1534
1569
  tools.push(createReadToolResultTool(offloadStore));
1535
- const onQuestion = spec.onQuestion ?? deps.onQuestion;
1536
- const durableQuestionFace = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
1570
+ const onQuestion = frozenOnQuestion;
1571
+ const durableQuestionFace = resolveCheckpointStore(spec, deps) !== undefined &&
1537
1572
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
1538
1573
  if (spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace)))
1539
1574
  tools.push(createAskUserQuestionTool(onQuestion, { principal: spec.principal, sourceTaskId: sessionId }));
@@ -1562,11 +1597,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1562
1597
  const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
1563
1598
  ? lspManager.diagnostics
1564
1599
  : undefined;
1600
+ const lspRunIdent = sessionId;
1565
1601
  const nudgeLspOnEdit = lspDiagnostics
1566
1602
  ? (rawPath) => {
1567
1603
  const baseDir = handsCwdRef?.current ?? taskRootPath;
1568
1604
  const filePath = resolveLspPath(rawPath, baseDir);
1569
- lspDiagnostics.fileEdited(pathToUri(filePath));
1605
+ lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
1570
1606
  void lspManager
1571
1607
  .sessionFor(filePath, undefined, executionEnv)
1572
1608
  .then((session) => session?.notifyFileChanged?.(filePath))
@@ -2203,6 +2239,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2203
2239
  ? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined) }
2204
2240
  : undefined;
2205
2241
  const harness = new AgentHarness({
2242
+ abortResultDetails: () => suspendRef.token !== undefined || reviewRef.token !== undefined ? { code: "gate.parked" } : undefined,
2206
2243
  ...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
2207
2244
  ? { maxOutputTokens: spec.limits.maxOutputTokens }
2208
2245
  : {}),
@@ -2459,7 +2496,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2459
2496
  if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2460
2497
  return first;
2461
2498
  if (pc.durableMandate === true) {
2462
- if ((spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2499
+ if (resolveCheckpointStore(spec, deps) !== undefined &&
2463
2500
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
2464
2501
  markInheritedUnavailable(creq.toolCallId)) {
2465
2502
  return first;
@@ -2515,7 +2552,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2515
2552
  if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2516
2553
  return decision;
2517
2554
  if (pc.durableMandate === true) {
2518
- if ((spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2555
+ if (resolveCheckpointStore(spec, deps) !== undefined &&
2519
2556
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
2520
2557
  markInheritedUnavailable(creq.toolCallId)) {
2521
2558
  return decision;
@@ -2675,13 +2712,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2675
2712
  const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
2676
2713
  const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
2677
2714
  const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
2678
- const durableSuspendInfraReady = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2715
+ const durableSuspendInfraReady = resolveCheckpointStore(spec, deps) !== undefined &&
2679
2716
  !(offloadStore !== undefined && isVolatileOffloadStore(offloadStore)) &&
2680
2717
  (ownedEnv === undefined || isRemoteExecutionEnv(ownedEnv)) &&
2681
2718
  incompleteSuspendAdapter === undefined;
2682
2719
  const resourceSuspendEligible = spec.resourceSuspend !== undefined && durableSuspendInfraReady;
2683
2720
  if (spec.resourceSuspend !== undefined && !resourceSuspendEligible) {
2684
- const why = (spec.checkpointStore ?? deps.checkpointStore) === undefined
2721
+ const why = resolveCheckpointStore(spec, deps) === undefined
2685
2722
  ? "no CheckpointStore is wired"
2686
2723
  : offloadStore !== undefined && isVolatileOffloadStore(offloadStore)
2687
2724
  ? "tool-result offload uses the in-memory store (a resume needs durable results)"
@@ -2774,7 +2811,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2774
2811
  }
2775
2812
  if (decision.decisionReason === undefined || decision.decisionReason === "rule") {
2776
2813
  const grant = inheritedAskGrants.get(req.toolCallId);
2777
- if (grant !== undefined && grant.approver === onAsk && grant.argsJson === askGrantShapeOf(req.args)) {
2814
+ if (grant !== undefined && askApproverIdentity(grant.approver) === askApproverIdentity(onAsk) && grant.argsJson === askGrantShapeOf(req.args)) {
2778
2815
  inheritedAskGrants.delete(req.toolCallId);
2779
2816
  humanReviewRef.count += 1;
2780
2817
  humanReviewRef.totalWaitMs += grant.waitMs;
@@ -2839,7 +2876,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2839
2876
  catch {
2840
2877
  }
2841
2878
  };
2842
- const checkpointStore = spec.checkpointStore ?? deps.checkpointStore;
2879
+ const checkpointStore = resolveCheckpointStore(spec, deps);
2843
2880
  const durableApproval = spec.durableApproval ??
2844
2881
  (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
2845
2882
  const inFlightSpendMicroUsd = () => {
@@ -3599,7 +3636,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3599
3636
  : undefined;
3600
3637
  overheadState.promptChars = systemPrompt.length;
3601
3638
  const preparedHolder = {};
3602
- const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
3639
+ const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
3603
3640
  const prepared = buildPrepared();
3604
3641
  preparedHolder.current = prepared;
3605
3642
  return prepared;