@sema-agent/core 5.7.0 → 5.9.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 (102) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cascade.js +24 -0
  4. package/dist/agents/subagent.d.ts +7 -7
  5. package/dist/agents/subagent.js +74 -33
  6. package/dist/agents/teacher.js +3 -3
  7. package/dist/agents/team.d.ts +1 -1
  8. package/dist/agents/team.js +1 -1
  9. package/dist/agents/verify.js +1 -1
  10. package/dist/brain/anthropic.js +24 -35
  11. package/dist/brain/open-responses.d.ts +11 -0
  12. package/dist/brain/open-responses.js +721 -0
  13. package/dist/brain/openai.js +22 -33
  14. package/dist/brain/request-params.d.ts +1 -0
  15. package/dist/brain/request-params.js +16 -0
  16. package/dist/brain/stream-engine.d.ts +0 -2
  17. package/dist/brain/stream-engine.js +5 -47
  18. package/dist/brain/stream-shared.d.ts +0 -10
  19. package/dist/brain/stream-shared.js +0 -23
  20. package/dist/brain/terminal-cause.d.ts +0 -1
  21. package/dist/brain/terminal-cause.js +0 -3
  22. package/dist/brain/timeout.d.ts +4 -2
  23. package/dist/brain/timeout.js +4 -14
  24. package/dist/config/catalog.d.ts +3 -11
  25. package/dist/config/catalog.js +30 -69
  26. package/dist/config/defaults.d.ts +0 -3
  27. package/dist/config/defaults.js +0 -3
  28. package/dist/core/auto-compaction.d.ts +2 -11
  29. package/dist/core/auto-compaction.js +3 -67
  30. package/dist/core/checkpoint-store.d.ts +10 -8
  31. package/dist/core/checkpoint-store.js +7 -7
  32. package/dist/core/fs-write-gate-policy.js +2 -2
  33. package/dist/core/lsp-diagnostics.d.ts +3 -2
  34. package/dist/core/lsp-diagnostics.js +20 -7
  35. package/dist/core/memory-engine/file-backend.js +20 -17
  36. package/dist/core/memory-engine/layout.d.ts +5 -0
  37. package/dist/core/memory-engine/layout.js +72 -47
  38. package/dist/core/runner/assemble-result.d.ts +3 -8
  39. package/dist/core/runner/assemble-result.js +24 -18
  40. package/dist/core/runner/prepare-task.d.ts +27 -6
  41. package/dist/core/runner/prepare-task.js +209 -144
  42. package/dist/core/runner/runtask.d.ts +0 -2
  43. package/dist/core/runner/runtask.js +228 -266
  44. package/dist/core/runner/session-rule-policy.d.ts +1 -0
  45. package/dist/core/runner/session-rule-policy.js +4 -3
  46. package/dist/core/session-policy-store.d.ts +5 -0
  47. package/dist/core/session-policy-store.js +3 -1
  48. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  49. package/dist/core/task-registry-agent.d.ts +2 -0
  50. package/dist/core/task-registry-agent.js +33 -1
  51. package/dist/core/task-registry-shared.d.ts +0 -1
  52. package/dist/core/task-registry.d.ts +2 -0
  53. package/dist/core/task-registry.js +9 -1
  54. package/dist/core/tool-errors.js +10 -2
  55. package/dist/core/tool-policy.d.ts +8 -0
  56. package/dist/core/tool-policy.js +11 -0
  57. package/dist/core/trace.d.ts +1 -10
  58. package/dist/core/types.d.ts +27 -30
  59. package/dist/core/usage-window-store.d.ts +39 -0
  60. package/dist/core/usage-window-store.js +115 -0
  61. package/dist/engine/execution-env/node-execution-env.js +0 -22
  62. package/dist/engine/harness/agent-harness.d.ts +1 -7
  63. package/dist/engine/harness/agent-harness.js +16 -53
  64. package/dist/engine/harness/types.d.ts +3 -15
  65. package/dist/engine/llm/types.d.ts +3 -75
  66. package/dist/engine/loop/agent-loop.d.ts +1 -1
  67. package/dist/engine/loop/agent-loop.js +7 -173
  68. package/dist/engine/loop/types.d.ts +1 -18
  69. package/dist/engine/session/repo-utils.d.ts +1 -2
  70. package/dist/engine/session/repo-utils.js +0 -7
  71. package/dist/index.d.ts +13 -7
  72. package/dist/index.js +10 -5
  73. package/dist/internal/llm.d.ts +1 -1
  74. package/dist/orchestration/goal.js +1 -5
  75. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  76. package/dist/orchestration/run-workflow-tool.js +1 -2
  77. package/dist/orchestration/workflow-governance.d.ts +6 -6
  78. package/dist/orchestration/workflow-governance.js +33 -47
  79. package/dist/orchestration/workflow.js +14 -5
  80. package/dist/prompt-assembly/event-registry.js +2 -3
  81. package/dist/stores/cc/lockfile.js +9 -11
  82. package/dist/stores/cc/mailbox-store.js +49 -38
  83. package/dist/stores/file/index.d.ts +3 -0
  84. package/dist/stores/file/index.js +4 -0
  85. package/dist/stores/file/usage-window-store.d.ts +9 -0
  86. package/dist/stores/file/usage-window-store.js +86 -0
  87. package/dist/tools/fs/fs-bash.d.ts +1 -10
  88. package/dist/tools/fs/fs-bash.js +20 -60
  89. package/dist/tools/fs/index.d.ts +0 -2
  90. package/dist/tools/fs/index.js +1 -2
  91. package/dist/tools/web.js +20 -19
  92. package/package.json +4 -5
  93. package/dist/bin/sema-tb.d.ts +0 -31
  94. package/dist/bin/sema-tb.js +0 -448
  95. package/dist/bin/tb-env.d.ts +0 -2
  96. package/dist/bin/tb-env.js +0 -17
  97. package/dist/brain/walltime.d.ts +0 -1
  98. package/dist/brain/walltime.js +0 -1
  99. package/dist/core/runner/call-cap.d.ts +0 -67
  100. package/dist/core/runner/call-cap.js +0 -145
  101. package/dist/core/runner/cut-kill.d.ts +0 -10
  102. package/dist/core/runner/cut-kill.js +0 -37
@@ -8,7 +8,6 @@ import type { SessionStore } from "../session.js";
8
8
  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
- import { type CutKillRegistry } from "./cut-kill.js";
12
11
  import type { SessionPermissionRules } from "../session-policy-store.js";
13
12
  import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
14
13
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
@@ -19,10 +18,23 @@ import type { TaskNotificationPayload } from "../task-notification.js";
19
18
  import { type CwdRef } from "../../tools/fs/index.js";
20
19
  import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
21
20
  import type { Runner } from "./runtask.js";
22
- import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js";
21
+ import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type PlatformLimitReason, type ResourceLimitReason } from "../checkpoint-store.js";
23
22
  import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
24
- import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
23
+ import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskLimits, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
25
24
  import type { RepairBundle } from "../../agents/repair-loop.js";
25
+ export declare function resolveTaskLimits(limits: TaskLimits | undefined): TaskLimits | undefined;
26
+ export declare const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60000;
27
+ export declare const USAGE_WINDOW_REAP_MARGIN_MS: number;
28
+ export interface UsageGovernance {
29
+ readonly key: string;
30
+ check(now: number): Promise<number | undefined>;
31
+ commit(cumulativeTokens: number, now: number): Promise<void>;
32
+ }
33
+ export declare function resolveEnvLifetimeExpiry(env: ExecutionEnv, observedAt: number | undefined): {
34
+ expiresAt: number;
35
+ } | {
36
+ unanchored: true;
37
+ } | undefined;
26
38
  export declare const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
27
39
  export declare function checkpointScopeOf(spec: {
28
40
  durableApproval?: {
@@ -132,9 +144,17 @@ export interface Prepared {
132
144
  turns: number;
133
145
  walltimeMs: number;
134
146
  }) => Promise<boolean>;
135
- callCapRef?: import("./call-cap.js").CallCapRef;
136
- cutKills?: CutKillRegistry;
137
- callIssuedAtRef?: {
147
+ suspendForPlatformLimit?: (reason: PlatformLimitReason, sliceSpend: {
148
+ costMicroUsd: number;
149
+ tokens: number;
150
+ turns: number;
151
+ walltimeMs: number;
152
+ }, hint?: {
153
+ resumeAfterMs: number;
154
+ }) => Promise<boolean>;
155
+ envLifetimeSuspendAt?: number;
156
+ usageGovernance?: UsageGovernance;
157
+ callIssuedAtRef: {
138
158
  current?: number;
139
159
  };
140
160
  brainCallGuardrailRef: BrainCallGuardrailRef;
@@ -179,6 +199,7 @@ export interface Prepared {
179
199
  lspDiagnostics?: {
180
200
  registry: import("../lsp-diagnostics.js").LspDiagnosticsRegistry;
181
201
  nudge: (rawPath: string) => void;
202
+ runIdent: string;
182
203
  };
183
204
  planModeRef: {
184
205
  active: boolean;
@@ -17,10 +17,8 @@ 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
- import { computeCallCap, createCallCapRef, softExecDeadlineMs, toolCutDeadlineMs, WALLTIME_STALL_CONNECT_MS, WALLTIME_STALL_FIRST_TOKEN_MS, WALLTIME_STALL_IDLE_MS } from "./call-cap.js";
23
- import { createCutKillRegistry } from "./cut-kill.js";
24
22
  import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
25
23
  import { inlineUntrusted } from "../untrusted-text.js";
26
24
  import { emitTrace } from "../trace.js";
@@ -29,7 +27,7 @@ import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js
29
27
  import { reconcileInterruptedSession } from "../session-reconcile.js";
30
28
  import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
31
29
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
32
- import { resolveBrainCallGuardrailMs, withBrainCallGuardrail } from "../../brain/timeout.js";
30
+ import { resolveBrainCallGuardrailMs, STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
33
31
  import { defineTool, isDefineToolProduct } from "../tools.js";
34
32
  import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
35
33
  import { protocolOf, PROTOCOL_TABLE } from "../protocol-table.js";
@@ -69,13 +67,96 @@ import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestrati
69
67
  import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
70
68
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
71
69
  import { resolveKey } from "../../tools/fs/safety.js";
72
- import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, WALLTIME_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
70
+ import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
73
71
  import { boundInputHashOf } from "../canonical-json.js";
72
+ import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
74
73
  const RECONCILE_MAX_RETRIES = 3;
75
74
  const DEFAULT_MAX_SUSPENDS = 5;
76
- const DEFAULT_MAX_SLICES = 100;
75
+ const TASK_LIMIT_KEYS = [
76
+ "maxTokens",
77
+ "maxCostUsd",
78
+ "maxTurns",
79
+ "maxWalltimeMs",
80
+ "maxOutputTokens",
81
+ "approachNotice",
82
+ "budgetStreamCancel",
83
+ "degrade",
84
+ "brainCallGuardrailMs",
85
+ ];
86
+ const NUMERIC_TASK_LIMIT_KEYS = ["maxTokens", "maxCostUsd", "maxTurns", "maxWalltimeMs", "maxOutputTokens"];
87
+ const RETIRED_TASK_LIMIT_KEYS = {
88
+ timeoutSec: "maxWalltimeMs (milliseconds, not seconds)",
89
+ deadlineNudge: "approachNotice",
90
+ callCapByDeadline: "(retired — no replacement)",
91
+ gracefulFinalize: "(retired — the approach notice is the only end-of-budget prompt)",
92
+ };
93
+ function limitConfigError(code, message) {
94
+ const e = new Error(message);
95
+ e.code = code;
96
+ return e;
97
+ }
98
+ export function resolveTaskLimits(limits) {
99
+ if (limits === undefined)
100
+ return undefined;
101
+ if (typeof limits !== "object" || Array.isArray(limits)) {
102
+ throw limitConfigError("config.limit_invalid", `TaskSpec.limits must be an object (got ${Array.isArray(limits) ? "an array" : typeof limits})`);
103
+ }
104
+ const raw = limits;
105
+ const legal = TASK_LIMIT_KEYS;
106
+ for (const key of Object.keys(raw)) {
107
+ if (legal.includes(key))
108
+ continue;
109
+ const replacement = RETIRED_TASK_LIMIT_KEYS[key];
110
+ throw limitConfigError("config.limit_unknown_key", `TaskSpec.limits.${key} is not a limit this engine reads` +
111
+ (replacement !== undefined ? ` — it was replaced by \`${replacement}\`` : "") +
112
+ `. Legal keys: ${TASK_LIMIT_KEYS.join(", ")}. Refused rather than ignored: a limit that is silently dropped reads to the caller as an armed ceiling.`);
113
+ }
114
+ for (const key of NUMERIC_TASK_LIMIT_KEYS) {
115
+ const value = raw[key];
116
+ if (value === undefined)
117
+ continue;
118
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
119
+ throw limitConfigError("config.limit_invalid", `TaskSpec.limits.${key} must be a finite, non-negative number (got ${String(value)}) — an unevaluable ceiling is not a ceiling, and folding it to a default would run the task under a limit nobody chose.`);
120
+ }
121
+ }
122
+ const notice = raw.approachNotice;
123
+ if (notice !== undefined && notice !== false) {
124
+ if (typeof notice !== "object" || notice === null || Array.isArray(notice)) {
125
+ throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice must be \`false\` or { at?: [first, second] } (got ${String(notice)})`);
126
+ }
127
+ const at = notice.at;
128
+ if (at !== undefined) {
129
+ const ok = Array.isArray(at) &&
130
+ at.length === 2 &&
131
+ at.every((n) => typeof n === "number" && Number.isFinite(n) && n > 0 && n <= 1) &&
132
+ at[0] <= at[1];
133
+ if (!ok) {
134
+ throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice.at must be two fractions in (0, 1] with first <= second (got ${JSON.stringify(at)})`);
135
+ }
136
+ }
137
+ }
138
+ return limits;
139
+ }
77
140
  const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
78
141
  const DEFAULT_RESOURCE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
142
+ export const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60_000;
143
+ export const USAGE_WINDOW_REAP_MARGIN_MS = 60 * 60 * 1000;
144
+ export function resolveEnvLifetimeExpiry(env, observedAt) {
145
+ const lifetimeMs = env.lifetimeMs;
146
+ if (lifetimeMs === undefined)
147
+ return undefined;
148
+ if (typeof lifetimeMs !== "number" || !Number.isFinite(lifetimeMs) || lifetimeMs <= 0) {
149
+ throw limitConfigError("config.env_lifetime_invalid", `ExecutionEnv.lifetimeMs must be a finite number of milliseconds > 0 (got ${String(lifetimeMs)}) — an unevaluable lifetime is not a lifetime, and treating it as absent would run the task with no protection against the platform reclaiming its environment.`);
150
+ }
151
+ const declaredStart = env.lifetimeStartedAt;
152
+ if (declaredStart !== undefined && (typeof declaredStart !== "number" || !Number.isFinite(declaredStart))) {
153
+ throw limitConfigError("config.env_lifetime_invalid", `ExecutionEnv.lifetimeStartedAt must be a finite epoch-ms timestamp (got ${String(declaredStart)}).`);
154
+ }
155
+ const anchor = declaredStart ?? observedAt;
156
+ if (anchor === undefined)
157
+ return { unanchored: true };
158
+ return { expiresAt: anchor + lifetimeMs };
159
+ }
79
160
  const DEFAULT_UNATTENDED_APPROVAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
80
161
  function sanitizedTtlMs(ttlMs) {
81
162
  if (ttlMs === undefined)
@@ -237,6 +318,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
237
318
  e.code = "config.empty_objective";
238
319
  throw e;
239
320
  }
321
+ resolveTaskLimits(spec.limits);
322
+ const usageWindows = resolveUsageWindows(deps.usageWindows);
240
323
  const brainCallGuardrailRef = {};
241
324
  const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
242
325
  if (spec.agents !== undefined && spec.agents.length > 0) {
@@ -497,6 +580,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
497
580
  throw e;
498
581
  }
499
582
  let ownedEnv;
583
+ let ownedEnvObservedAt;
500
584
  let mcp;
501
585
  let a2a;
502
586
  if (internals?.requestedCwd !== undefined && deps.executionEnvFactory === undefined) {
@@ -514,6 +598,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
514
598
  ...(internals?.parentCwd ? { parentCwd: internals.parentCwd } : {}),
515
599
  })
516
600
  : undefined;
601
+ if (ownedEnv !== undefined)
602
+ ownedEnvObservedAt = Date.now();
517
603
  }
518
604
  catch (factoryErr) {
519
605
  await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (env-factory-throw leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
@@ -552,6 +638,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
552
638
  if (spec.signal?.aborted)
553
639
  abortController.abort();
554
640
  try {
641
+ const envLifetime = resolveEnvLifetimeExpiry(executionEnv, ownedEnvObservedAt);
642
+ if (envLifetime !== undefined && "unanchored" in envLifetime) {
643
+ deps.onError?.(new Error("ExecutionEnv.lifetimeMs is declared but NOT enforceable: this env was not minted by the run's executionEnvFactory and reports no `lifetimeStartedAt`, so the engine cannot tell how old it is. The run will NOT suspend before the platform reclaims the environment — have the adapter report `lifetimeStartedAt`."), { phase: "config", sessionId });
644
+ }
645
+ const envLifetimeSuspendAt = envLifetime !== undefined && "expiresAt" in envLifetime ? envLifetime.expiresAt - ENV_LIFETIME_SUSPEND_MARGIN_MS : undefined;
555
646
  if (internals?.isolation === "worktree") {
556
647
  const failIsolation = (reason) => {
557
648
  const e = new Error(`isolation "worktree" was requested but cannot be honored: ${reason}. The sub-agent was not started (fail-closed — no silent fallback to the shared working tree).`);
@@ -853,6 +944,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
853
944
  model: harnessRef.current?.getModel(),
854
945
  thinkingLevel: harnessRef.current?.getThinkingLevel(),
855
946
  principal: spec.principal,
947
+ ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
856
948
  oneShot: spec.oneShot,
857
949
  clientContext: spec.clientContext,
858
950
  excludeTools: toolFaceSnapshot.exclude,
@@ -1034,12 +1126,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1034
1126
  };
1035
1127
  const priorSuspendCount = resume?.priorSuspendCount ?? 0;
1036
1128
  const maxSuspends = spec.maxSuspends ?? deps.maxSuspends ?? DEFAULT_MAX_SUSPENDS;
1037
- const maxSlices = spec.resourceSuspend?.maxSlices ?? DEFAULT_MAX_SLICES;
1129
+ const maxSlices = spec.resourceSuspend?.maxSlices;
1038
1130
  const priorLedger = resume?.priorLedger;
1039
1131
  const resourceTotal = spec.resourceSuspend
1040
1132
  ? {
1041
1133
  totalBudgetMicroUsd: spec.resourceSuspend.totalBudgetUsd !== undefined ? Math.round(spec.resourceSuspend.totalBudgetUsd * 1e6) : undefined,
1042
- totalWalltimeSec: spec.resourceSuspend.totalWalltimeSec,
1134
+ totalTokens: spec.resourceSuspend.totalTokens,
1043
1135
  }
1044
1136
  : undefined;
1045
1137
  const budgetSnapshot = {
@@ -1058,6 +1150,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1058
1150
  let worktreeSessionRef;
1059
1151
  let backgroundTaskToolsActive = false;
1060
1152
  let suspendForResource;
1153
+ let suspendForPlatformLimit;
1061
1154
  let suspendForReview;
1062
1155
  const outputRef = {};
1063
1156
  if (resume?.seed.outputRef) {
@@ -1212,29 +1305,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1212
1305
  }
1213
1306
  tools.push(...a2a.tools.map((t) => remoteToolOffload(t)));
1214
1307
  foldProtocolAxes(a2a.toolAxes, "A2A");
1215
- const callCapOn = spec.limits?.callCapByDeadline !== false;
1216
- const callCapRef = spec.limits?.timeoutSec &&
1217
- spec.limits.timeoutSec > 0 &&
1218
- (callCapOn || spec.limits.gracefulFinalize !== false)
1219
- ? createCallCapRef()
1220
- : undefined;
1221
- if (callCapRef) {
1222
- callCapRef.staticCapTokens =
1223
- spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
1224
- ? spec.limits.maxOutputTokens
1225
- : model.maxTokens;
1226
- }
1227
- const cutKills = callCapRef ? createCutKillRegistry() : undefined;
1228
1308
  const callIssuedAtRef = {};
1229
- const execClamp = callCapRef
1230
- ? {
1231
- deadlineMs: () => softExecDeadlineMs(callCapRef),
1232
- onClamp: () => {
1233
- callCapRef.toolClamps = (callCapRef.toolClamps ?? 0) + 1;
1234
- },
1235
- ...(cutKills ? { registerKillHandle: cutKills.register } : {}),
1236
- }
1237
- : undefined;
1238
1309
  const memoryWriteGateRef = {};
1239
1310
  const additionalRootsCanonical = [];
1240
1311
  let attachmentRootCanonical;
@@ -1317,7 +1388,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1317
1388
  ...(sessionId !== undefined ? { sessionId } : {}),
1318
1389
  ...(internals?.onTaskNotification !== undefined ? { taskNotification: internals.onTaskNotification } : {}),
1319
1390
  ...(internals?.detachHub !== undefined ? { detachHub: internals.detachHub } : {}),
1320
- ...(execClamp !== undefined ? { execClamp } : {}),
1321
1391
  pdfModelCapabilities: pdfModelCapabilitiesOf(model),
1322
1392
  ...(deps.hands?.bashReadonlyAllow !== undefined ? { bashReadonlyAllow: deps.hands.bashReadonlyAllow } : {}),
1323
1393
  ...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
@@ -1366,7 +1436,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1366
1436
  if (backgroundTaskToolsActive || workflowToolsActive) {
1367
1437
  toolEffects.set("TaskOutput", "read");
1368
1438
  toolEffects.set("TaskStop", "write");
1369
- tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore, ...(callCapRef ? { deadlineMs: () => toolCutDeadlineMs(callCapRef, Date.now()) } : {}) })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
1439
+ tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
1370
1440
  if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
1371
1441
  const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
1372
1442
  const reviveSpawn = delegationForRevive !== undefined && deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined
@@ -1493,11 +1563,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1493
1563
  const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
1494
1564
  ? lspManager.diagnostics
1495
1565
  : undefined;
1566
+ const lspRunIdent = sessionId;
1496
1567
  const nudgeLspOnEdit = lspDiagnostics
1497
1568
  ? (rawPath) => {
1498
1569
  const baseDir = handsCwdRef?.current ?? taskRootPath;
1499
1570
  const filePath = resolveLspPath(rawPath, baseDir);
1500
- lspDiagnostics.fileEdited(pathToUri(filePath));
1571
+ lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
1501
1572
  void lspManager
1502
1573
  .sessionFor(filePath, undefined, executionEnv)
1503
1574
  .then((session) => session?.notifyFileChanged?.(filePath))
@@ -2134,49 +2205,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2134
2205
  ? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined) }
2135
2206
  : undefined;
2136
2207
  const harness = new AgentHarness({
2208
+ abortResultDetails: () => suspendRef.token !== undefined || reviewRef.token !== undefined ? { code: "gate.parked" } : undefined,
2137
2209
  ...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
2138
2210
  ? { maxOutputTokens: spec.limits.maxOutputTokens }
2139
2211
  : {}),
2140
- ...(callCapRef !== undefined && callCapOn
2141
- ? {
2142
- maxOutputTokensPerCall: () => {
2143
- if (callCapRef.reasoningObserved === true)
2144
- return undefined;
2145
- const cap = computeCallCap(callCapRef, Date.now(), (thinking ?? "off") !== "off");
2146
- if (cap !== undefined)
2147
- callCapRef.shrinks = (callCapRef.shrinks ?? 0) + 1;
2148
- return cap;
2149
- },
2150
- }
2151
- : {}),
2152
- ...(callCapRef !== undefined
2153
- ? {
2154
- callDeadlineMsPerCall: () => {
2155
- callIssuedAtRef.current = Date.now();
2156
- return softExecDeadlineMs(callCapRef);
2157
- },
2158
- stallTimeoutsPerCall: () => softExecDeadlineMs(callCapRef) !== undefined
2159
- ? { connectMs: WALLTIME_STALL_CONNECT_MS, firstTokenMs: WALLTIME_STALL_FIRST_TOKEN_MS, idleMs: WALLTIME_STALL_IDLE_MS }
2160
- : undefined,
2161
- }
2162
- : {}),
2163
- ...(callCapRef !== undefined
2164
- ? {
2165
- toolDeadline: {
2166
- deadlineMs: () => toolCutDeadlineMs(callCapRef, Date.now()),
2167
- onCut: (info) => {
2168
- callCapRef.toolCuts = (callCapRef.toolCuts ?? 0) + 1;
2169
- if (callCapRef.finalizeMode === true && cutKills !== undefined) {
2170
- cutKills.killFor(info.toolCallId);
2171
- }
2172
- callCapRef.pendingCutTools = (callCapRef.pendingCutTools ?? 0) + 1;
2173
- void info.settled.then(() => {
2174
- callCapRef.pendingCutTools = (callCapRef.pendingCutTools ?? 0) - 1;
2175
- });
2176
- },
2177
- },
2178
- }
2179
- : {}),
2212
+ stallTimeoutsPerCall: () => {
2213
+ callIssuedAtRef.current = Date.now();
2214
+ return { connectMs: STALL_CONNECT_MS, firstTokenMs: STALL_FIRST_TOKEN_MS, idleMs: STALL_IDLE_MS };
2215
+ },
2180
2216
  ...(spec.resilience !== undefined ? { resilience: spec.resilience } : {}),
2181
2217
  loopTrace: (step) => {
2182
2218
  if (step.kind !== "continue")
@@ -2642,11 +2678,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2642
2678
  const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
2643
2679
  const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
2644
2680
  const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
2645
- const resourceSuspendEligible = spec.resourceSuspend !== undefined &&
2646
- (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2681
+ const durableSuspendInfraReady = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
2647
2682
  !(offloadStore !== undefined && isVolatileOffloadStore(offloadStore)) &&
2648
2683
  (ownedEnv === undefined || isRemoteExecutionEnv(ownedEnv)) &&
2649
2684
  incompleteSuspendAdapter === undefined;
2685
+ const resourceSuspendEligible = spec.resourceSuspend !== undefined && durableSuspendInfraReady;
2650
2686
  if (spec.resourceSuspend !== undefined && !resourceSuspendEligible) {
2651
2687
  const why = (spec.checkpointStore ?? deps.checkpointStore) === undefined
2652
2688
  ? "no CheckpointStore is wired"
@@ -2660,7 +2696,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2660
2696
  sessionId,
2661
2697
  });
2662
2698
  }
2663
- if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || spec.enablePlanMode === true) {
2699
+ const usageGovernance = (() => {
2700
+ if (usageWindows === undefined || usageWindows.length === 0)
2701
+ return undefined;
2702
+ const store = deps.usageWindowStore;
2703
+ if (store === undefined) {
2704
+ deps.onError?.(new Error("RunnerDeps.usageWindows is set but INACTIVE: no `usageWindowStore` is wired, so cross-task usage cannot be counted and no window can be enforced. Wire InMemoryUsageWindowStore (process-local) or FileUsageWindowStore (restart-surviving)."), { phase: "config", sessionId });
2705
+ return undefined;
2706
+ }
2707
+ const key = spec.principal || GLOBAL_USAGE_KEY;
2708
+ const windows = usageWindows;
2709
+ let charged = 0;
2710
+ return {
2711
+ key,
2712
+ async check(now) {
2713
+ return usageRetryAfterMs(await store.read(key, windows, now));
2714
+ },
2715
+ async commit(cumulativeTokens, now) {
2716
+ const delta = cumulativeTokens - charged;
2717
+ if (delta <= 0)
2718
+ return;
2719
+ await store.charge(key, delta, now, windows);
2720
+ charged = cumulativeTokens;
2721
+ },
2722
+ };
2723
+ })();
2724
+ const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
2725
+ if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
2664
2726
  const adjudicate = effectivePolicy
2665
2727
  ? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot }, abortController.signal)), abortController.signal, () => ({
2666
2728
  action: "deny",
@@ -2715,7 +2777,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2715
2777
  }
2716
2778
  if (decision.decisionReason === undefined || decision.decisionReason === "rule") {
2717
2779
  const grant = inheritedAskGrants.get(req.toolCallId);
2718
- if (grant !== undefined && grant.approver === onAsk && grant.argsJson === askGrantShapeOf(req.args)) {
2780
+ if (grant !== undefined && askApproverIdentity(grant.approver) === askApproverIdentity(onAsk) && grant.argsJson === askGrantShapeOf(req.args)) {
2719
2781
  inheritedAskGrants.delete(req.toolCallId);
2720
2782
  humanReviewRef.count += 1;
2721
2783
  humanReviewRef.totalWaitMs += grant.waitMs;
@@ -2883,6 +2945,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2883
2945
  ref.scope = scope;
2884
2946
  };
2885
2947
  const suspendLoopCapHit = (count, cap, detail) => {
2948
+ if (cap === undefined)
2949
+ return false;
2886
2950
  if (count + 1 <= cap)
2887
2951
  return false;
2888
2952
  suspendLoopRef.hit = true;
@@ -2903,73 +2967,74 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2903
2967
  const { snapshotId: _lineage, ...identity } = env.workspaceHandle();
2904
2968
  return { ...identity, restoreMode: "park_only" };
2905
2969
  };
2906
- suspendForResource = resourceSuspendEligible
2907
- ? async (reason, sliceSpend) => {
2908
- const rs = spec.resourceSuspend;
2909
- if (!checkpointStore || !rs)
2910
- return false;
2911
- if (abortController.signal.aborted)
2912
- return false;
2913
- if (suspendLoopCapHit(priorLedger?.sliceCount ?? 0, maxSlices, " (resource slice limit)"))
2914
- return false;
2915
- const leafId = await session.getLeafId();
2916
- if (!leafId)
2917
- return false;
2918
- if (abortController.signal.aborted)
2919
- return false;
2920
- let remoteHandle;
2921
- if (suspendableEnv !== undefined) {
2922
- if (hasBackgroundShell(suspendableEnv))
2923
- await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
2924
- const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
2925
- if (!snap.ok) {
2926
- remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
2927
- deps.onError?.(new Error(`resource suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
2928
- return false;
2929
- }
2930
- remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
2931
- }
2932
- else if (parkOnlyRemoteEnv !== undefined) {
2933
- if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
2934
- await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
2935
- remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
2936
- }
2937
- const token = mintCheckpointToken();
2938
- const gate = { kind: "resource_limit", reason };
2939
- const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
2940
- const mintedAt = Date.now();
2941
- const cp = {
2942
- token,
2943
- version: resourceLedgerOut.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
2944
- scope: rs.scope,
2945
- sessionId,
2946
- leafId,
2947
- gate,
2948
- pendingAction: { kind: "resource_limit", reason },
2949
- state: serializeCheckpointState(remoteHandle),
2950
- status: "pending",
2951
- createdAt: mintedAt,
2952
- deadline: mintedAt + (sanitizedTtlMs(rs.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
2953
- suspendCount: priorSuspendCount,
2954
- humanReview: humanReviewRef.count > 0
2955
- ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
2956
- : undefined,
2957
- resourceLedger: resourceLedgerOut,
2958
- ...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
2959
- };
2960
- if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
2970
+ const mintResourceSuspend = async (reason, sliceSpend, hint) => {
2971
+ const rs = spec.resourceSuspend;
2972
+ if (!checkpointStore)
2973
+ return false;
2974
+ const scope = rs?.scope ?? checkpointScopeOf(spec);
2975
+ if (abortController.signal.aborted)
2976
+ return false;
2977
+ if (suspendLoopCapHit(priorLedger?.sliceCount ?? 0, maxSlices, " (resource slice limit)"))
2978
+ return false;
2979
+ const leafId = await session.getLeafId();
2980
+ if (!leafId)
2981
+ return false;
2982
+ if (abortController.signal.aborted)
2983
+ return false;
2984
+ let remoteHandle;
2985
+ if (suspendableEnv !== undefined) {
2986
+ if (hasBackgroundShell(suspendableEnv))
2987
+ await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
2988
+ const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
2989
+ if (!snap.ok) {
2990
+ remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
2991
+ deps.onError?.(new Error(`resource suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
2961
2992
  return false;
2962
- publishCommittedSuspend(token, gate, rs.scope, remoteHandle);
2963
- try {
2964
- await sessions.pin?.(sessionId);
2965
2993
  }
2966
- catch (pinErr) {
2967
- deps.onError?.(pinErr, { phase: "config", sessionId });
2968
- }
2969
- harness.requestStopAfterTurn();
2970
- return true;
2994
+ remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
2995
+ }
2996
+ else if (parkOnlyRemoteEnv !== undefined) {
2997
+ if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
2998
+ await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
2999
+ remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
3000
+ }
3001
+ const token = mintCheckpointToken();
3002
+ const gate = { kind: "resource_limit", reason, ...(hint !== undefined ? { resumeAfterMs: hint.resumeAfterMs } : {}) };
3003
+ const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
3004
+ const mintedAt = Date.now();
3005
+ const cp = {
3006
+ token,
3007
+ version: resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3008
+ scope,
3009
+ sessionId,
3010
+ leafId,
3011
+ gate,
3012
+ pendingAction: { kind: "resource_limit", reason },
3013
+ state: serializeCheckpointState(remoteHandle),
3014
+ status: "pending",
3015
+ createdAt: mintedAt,
3016
+ deadline: mintedAt + Math.max(sanitizedTtlMs(rs?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS, hint !== undefined ? hint.resumeAfterMs + USAGE_WINDOW_REAP_MARGIN_MS : 0),
3017
+ suspendCount: priorSuspendCount,
3018
+ humanReview: humanReviewRef.count > 0
3019
+ ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
3020
+ : undefined,
3021
+ resourceLedger: resourceLedgerOut,
3022
+ ...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
3023
+ };
3024
+ if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
3025
+ return false;
3026
+ publishCommittedSuspend(token, gate, scope, remoteHandle);
3027
+ try {
3028
+ await sessions.pin?.(sessionId);
2971
3029
  }
2972
- : undefined;
3030
+ catch (pinErr) {
3031
+ deps.onError?.(pinErr, { phase: "config", sessionId });
3032
+ }
3033
+ harness.requestStopAfterTurn();
3034
+ return true;
3035
+ };
3036
+ suspendForResource = resourceSuspendEligible ? mintResourceSuspend : undefined;
3037
+ suspendForPlatformLimit = durableSuspendInfraReady ? mintResourceSuspend : undefined;
2973
3038
  suspendForReview = checkpointStore
2974
3039
  ? async (reason) => {
2975
3040
  if (!checkpointStore)
@@ -3021,7 +3086,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3021
3086
  const mintedAt = Date.now();
3022
3087
  const cp = {
3023
3088
  token,
3024
- version: reviewLedger.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3089
+ version: reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3025
3090
  scope,
3026
3091
  sessionId,
3027
3092
  leafId,
@@ -3146,7 +3211,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3146
3211
  const mintedAt = Date.now();
3147
3212
  cp = {
3148
3213
  token,
3149
- version: approvalLedger.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3214
+ version: approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3150
3215
  scope,
3151
3216
  sessionId,
3152
3217
  leafId,
@@ -3537,7 +3602,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3537
3602
  : undefined;
3538
3603
  overheadState.promptChars = systemPrompt.length;
3539
3604
  const preparedHolder = {};
3540
- 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, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { 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 } : {}) });
3605
+ 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, 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 } : {}) });
3541
3606
  const prepared = buildPrepared();
3542
3607
  preparedHolder.current = prepared;
3543
3608
  return prepared;
@@ -5,8 +5,6 @@ import { type TaskOutcome } from "../task-outcome.js";
5
5
  import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
6
6
  import type { SessionStore } from "../session.js";
7
7
  import type { AgentDefinition, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
8
- import { DEFAULT_MAX_TURNS } from "../../config/defaults.js";
9
- export { DEFAULT_MAX_TURNS };
10
8
  export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
11
9
  interface ResumeRun {
12
10
  cp: Checkpoint;