@sema-agent/core 5.31.0 → 5.33.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 (42) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/agents/cascade.d.ts +49 -1
  3. package/dist/agents/cascade.js +2 -2
  4. package/dist/agents/verify.d.ts +70 -4
  5. package/dist/agents/verify.js +62 -16
  6. package/dist/core/checkpoint-store.d.ts +95 -0
  7. package/dist/core/checkpoint-store.js +40 -0
  8. package/dist/core/hooks.d.ts +14 -6
  9. package/dist/core/hooks.js +14 -3
  10. package/dist/core/memory-engine/file-backend.d.ts +172 -22
  11. package/dist/core/memory-engine/file-backend.js +877 -79
  12. package/dist/core/memory-engine/memory-backend-contract.js +33 -0
  13. package/dist/core/runner/assemble-result.d.ts +7 -0
  14. package/dist/core/runner/assemble-result.js +1 -1
  15. package/dist/core/runner/prepare-acquire-reconcile.d.ts +72 -0
  16. package/dist/core/runner/prepare-acquire-reconcile.js +126 -0
  17. package/dist/core/runner/prepare-config-doors.d.ts +140 -0
  18. package/dist/core/runner/prepare-config-doors.js +250 -0
  19. package/dist/core/runner/prepare-safety-scan.d.ts +53 -0
  20. package/dist/core/runner/prepare-safety-scan.js +80 -0
  21. package/dist/core/runner/prepare-task.d.ts +28 -80
  22. package/dist/core/runner/prepare-task.js +102 -586
  23. package/dist/core/runner/prepare-workspace-restore.d.ts +102 -0
  24. package/dist/core/runner/prepare-workspace-restore.js +144 -0
  25. package/dist/core/runner/runtask.js +8 -2
  26. package/dist/core/tool-policy.d.ts +25 -0
  27. package/dist/core/types.d.ts +149 -13
  28. package/dist/index.d.ts +5 -4
  29. package/dist/index.js +2 -2
  30. package/dist/orchestration/workflow-governance.d.ts +6 -4
  31. package/dist/tools/fs/bash-readonly-classifier.d.ts +9 -3
  32. package/dist/tools/fs/bash-readonly-classifier.js +4 -1
  33. package/dist/tools/fs/fs-bash.d.ts +19 -3
  34. package/dist/tools/fs/fs-bash.js +26 -1
  35. package/dist/tools/fs/index.d.ts +27 -7
  36. package/dist/tools/fs/index.js +7 -2
  37. package/dist/tools/fs/read-deny.d.ts +66 -8
  38. package/dist/tools/fs/read-deny.js +75 -39
  39. package/dist/tools/fs/read-face.d.ts +24 -2
  40. package/dist/tools/fs/read-face.js +9 -0
  41. package/dist/tools/fs/search.js +2 -0
  42. package/package.json +1 -1
@@ -7,7 +7,7 @@ import { sanitizeCompactionSettings } from "../auto-compaction.js";
7
7
  import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
8
8
  import { createAutoModeDecider } from "../auto-mode.js";
9
9
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
10
- import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
10
+ import { resolveTaskModel } from "../roles.js";
11
11
  import { primaryActivityArg } from "../arg-summary.js";
12
12
  import { materializeMcpTools } from "../mcp.js";
13
13
  import { materializeA2aTools } from "../a2a.js";
@@ -15,7 +15,7 @@ import { Type } from "typebox";
15
15
  import { Value } from "typebox/value";
16
16
  import { uuidv7 } from "../../engine/session/uuid.js";
17
17
  import { brainToRuntime } from "../runtime.js";
18
- import { StoredSession, isSessionConflict, hasSessionFork } from "../session.js";
18
+ import { hasSessionFork } from "../session.js";
19
19
  import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents/subagent.js";
20
20
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
21
21
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
@@ -31,13 +31,12 @@ import { emitTrace } from "../trace.js";
31
31
  import { createSessionRulePolicy } from "./session-rule-policy.js";
32
32
  import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, persistedRuleMandateOf, runToolGate } from "../hooks.js";
33
33
  import { orgRuleVerdictFor } from "../permission-rule-org.js";
34
- import { reconcileInterruptedSession } from "../session-reconcile.js";
35
34
  import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
36
35
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
37
- import { resolveBrainCallGuardrailMs, STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
36
+ import { STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
38
37
  import { defineTool, isDefineToolProduct } from "../tools.js";
39
38
  import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
40
- import { protocolOf, PROTOCOL_TABLE } from "../protocol-table.js";
39
+ import { protocolOf } from "../protocol-table.js";
41
40
  import { pathToUri } from "../lsp-protocol.js";
42
41
  import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
43
42
  import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
@@ -50,11 +49,13 @@ import { MEMORY_RECALL_DISCIPLINE } from "../memory-engine/engine.js";
50
49
  import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
51
50
  import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
52
51
  import { composeMemoryBlock } from "../memory.js";
53
- import { preflightLockedConfig } from "../locked-config.js";
54
52
  import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
55
- import { assertRetentionCapability } from "../retention.js";
56
53
  import { foldAdmissionFreeze } from "../memory-admission.js";
57
54
  import { prepareMemory } from "./prepare-memory.js";
55
+ import { limitConfigError, prepareConfigDoors } from "./prepare-config-doors.js";
56
+ import { NAMESPACED_NAME_SHAPES, prepareSafetyScan } from "./prepare-safety-scan.js";
57
+ import { prepareAcquireReconcile } from "./prepare-acquire-reconcile.js";
58
+ import { prepareWorkspaceRestore, rebaseWorkspacePath, remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./prepare-workspace-restore.js";
58
59
  import { defaultPromptProvider, buildEnvironmentContext, buildGitSnapshot, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
59
60
  import { assemblePrompt } from "../../prompt-assembly/assemble.js";
60
61
  import { auditToolCollisions, getToolContract, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
@@ -67,18 +68,17 @@ import { capAggregateToolResults } from "../tool-result-budget.js";
67
68
  import { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "../media-byte-cap.js";
68
69
  import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
69
70
  import { StubExecutionEnv } from "../stub-env.js";
70
- import { hasDestroy, isIsolated, isRemoteExecutionEnv, isRetryableRemoteErrorCode, isSuspendable, missingRestoreSurface, RETRYABLE_REMOTE_ERROR_CODES } from "../remote-env.js";
71
- import { withRetry } from "../with-retry.js";
71
+ import { hasDestroy, isIsolated, isRemoteExecutionEnv, isSuspendable, missingRestoreSurface } from "../remote-env.js";
72
72
  import { settleTeardownLeg } from "./teardown-bounded.js";
73
73
  import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
74
74
  import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
75
75
  import { createMonitorTool } from "../../tools/monitor.js";
76
76
  import { createWorktreeTools } from "../../tools/worktree.js";
77
- import { applyCompactionToReadFileState, assertReadFaceValue, bashReversibilityProbe, compileReadDeny, createHandsToolkit, isReadDedupStubResult, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, FULL_SHELL_CONTRACT_ID, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
77
+ import { applyCompactionToReadFileState, bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, isReadDedupStubResult, resolveReadDenyBuiltins, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, FULL_SHELL_CONTRACT_ID, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
78
78
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
79
79
  import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool, classifyQuestionOutcome, isLiveQuestionFace, validateAskQuestions, } from "../ask-question.js";
80
80
  import { createSchedulerTools } from "../../tools/scheduler-tools.js";
81
- import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
81
+ import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME } from "../present-plan-tool.js";
82
82
  import { isSelfOrchestrationActive, selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
83
83
  import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestration/run-workflow-tool.js";
84
84
  import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
@@ -87,8 +87,8 @@ import { resolveKey } from "../../tools/fs/safety.js";
87
87
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
88
88
  import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
89
89
  import { boundInputHashOf } from "../canonical-json.js";
90
- import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
91
- import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
90
+ import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
91
+ import { GLOBAL_USAGE_KEY, usageRetryAfterMs } from "../usage-window-store.js";
92
92
  import { deliverEngineNotice } from "../types.js";
93
93
  const announcedMaterializeEnv = new Set();
94
94
  export function __resetMaterializeEnvAnnouncements() {
@@ -97,74 +97,12 @@ export function __resetMaterializeEnvAnnouncements() {
97
97
  function emitMaterializeEnvNotice(onNotice, message, detail) {
98
98
  deliverEngineNotice(onNotice, { code: "config.materialize_env_discarded", message, detail });
99
99
  }
100
- const RECONCILE_MAX_RETRIES = 3;
101
- const DEFAULT_MAX_SUSPENDS = 5;
102
- const TASK_LIMIT_KEY_DICT = {
103
- maxTokens: true,
104
- maxCostUsd: true,
105
- maxTurns: true,
106
- maxWalltimeMs: true,
107
- maxOutputTokens: true,
108
- approachNotice: true,
109
- budgetStreamCancel: true,
110
- degrade: true,
111
- brainCallGuardrailMs: true,
112
- };
113
- const TASK_LIMIT_KEYS = Object.keys(TASK_LIMIT_KEY_DICT);
114
- const NUMERIC_TASK_LIMIT_KEYS = ["maxTokens", "maxCostUsd", "maxTurns", "maxWalltimeMs", "maxOutputTokens"];
115
- const RETIRED_TASK_LIMIT_KEYS = {
116
- timeoutSec: "maxWalltimeMs (milliseconds, not seconds)",
117
- deadlineNudge: "approachNotice",
118
- callCapByDeadline: "(retired — no replacement)",
119
- gracefulFinalize: "(retired — the approach notice is the only end-of-budget prompt)",
120
- };
121
- function limitConfigError(code, message) {
122
- const e = new Error(message);
123
- e.code = code;
124
- return e;
125
- }
126
- export function resolveTaskLimits(limits) {
127
- if (limits === undefined)
128
- return undefined;
129
- if (typeof limits !== "object" || Array.isArray(limits)) {
130
- throw limitConfigError("config.limit_invalid", `TaskSpec.limits must be an object (got ${Array.isArray(limits) ? "an array" : typeof limits})`);
131
- }
132
- const raw = limits;
133
- const legal = TASK_LIMIT_KEYS;
134
- for (const key of Object.keys(raw)) {
135
- if (legal.includes(key))
136
- continue;
137
- const replacement = RETIRED_TASK_LIMIT_KEYS[key];
138
- throw limitConfigError("config.limit_unknown_key", `TaskSpec.limits.${key} is not a limit this engine reads` +
139
- (replacement !== undefined ? ` — it was replaced by \`${replacement}\`` : "") +
140
- `. Legal keys: ${TASK_LIMIT_KEYS.join(", ")}. Refused rather than ignored: a limit that is silently dropped reads to the caller as an armed ceiling.`);
141
- }
142
- for (const key of NUMERIC_TASK_LIMIT_KEYS) {
143
- const value = raw[key];
144
- if (value === undefined)
145
- continue;
146
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
147
- 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.`);
148
- }
149
- }
150
- const notice = raw.approachNotice;
151
- if (notice !== undefined && notice !== false) {
152
- if (typeof notice !== "object" || notice === null || Array.isArray(notice)) {
153
- throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice must be \`false\` or { at?: [first, second] } (got ${String(notice)})`);
154
- }
155
- const at = notice.at;
156
- if (at !== undefined) {
157
- const ok = Array.isArray(at) &&
158
- at.length === 2 &&
159
- at.every((n) => typeof n === "number" && Number.isFinite(n) && n > 0 && n <= 1) &&
160
- at[0] <= at[1];
161
- if (!ok) {
162
- throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice.at must be two fractions in (0, 1] with first <= second (got ${JSON.stringify(at)})`);
163
- }
164
- }
165
- }
166
- return limits;
100
+ const readFaceClampAnnouncedSinks = new WeakSet();
101
+ let readFaceClampConsoleAnnounced = false;
102
+ export function __resetReadFaceClampAnnouncement() {
103
+ readFaceClampConsoleAnnounced = false;
167
104
  }
105
+ const DEFAULT_MAX_SUSPENDS = 5;
168
106
  const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
169
107
  const DEFAULT_RESOURCE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
170
108
  export const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60_000;
@@ -197,7 +135,6 @@ function sanitizedTtlMs(ttlMs) {
197
135
  const ungatedWarnedShapes = new WeakMap();
198
136
  const advisedPolicyNames = new WeakMap();
199
137
  const ADVISED_KEYS_CAP = 64;
200
- const NAMESPACED_NAME_SHAPES = PROTOCOL_TABLE.map((ns) => `${ns.prefix}<peer>__<tool>`).join(", ");
201
138
  export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
202
139
  export function checkpointScopeOf(spec) {
203
140
  return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
@@ -209,16 +146,8 @@ class ParkRefusal extends Error {
209
146
  }
210
147
  }
211
148
  export { resolveCheckpointStore } from "../checkpoint-store.js";
212
- export function isFableFamilyModelId(id) {
213
- const tail = id.toLowerCase().split("/").pop() ?? "";
214
- return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
215
- }
216
- export function resolveModelPromptTraits(model, spec, internals) {
217
- return {
218
- promptProfile: spec.promptProfile ?? internals?.promptProfile ?? "simple",
219
- fableMitigations: isFableFamilyModelId(model.id),
220
- };
221
- }
149
+ export { isFableFamilyModelId, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
150
+ export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
222
151
  function hasConversationContent(branch) {
223
152
  return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
224
153
  }
@@ -238,31 +167,6 @@ export function batchContextAt(messages, currentId) {
238
167
  const completedCallIds = batch.filter((id) => id !== currentId && resolved.has(id));
239
168
  return { batchToolCallIds: batch, completedCallIds };
240
169
  }
241
- function detectConflicts(storage, ref) {
242
- const guard = (fn) => async (...args) => {
243
- try {
244
- return await fn(...args);
245
- }
246
- catch (err) {
247
- if (isSessionConflict(err)) {
248
- ref.hit = true;
249
- }
250
- throw err;
251
- }
252
- };
253
- return new Proxy(storage, {
254
- get(target, prop, receiver) {
255
- const value = Reflect.get(target, prop, receiver);
256
- if (typeof value !== "function") {
257
- return value;
258
- }
259
- const bound = value.bind(target);
260
- return prop === "appendEntry" || prop === "setLeafId"
261
- ? guard(bound)
262
- : bound;
263
- },
264
- });
265
- }
266
170
  function raceAbort(p, signal, onAbort) {
267
171
  if (signal.aborted)
268
172
  return Promise.resolve(onAbort());
@@ -288,353 +192,19 @@ async function forgetQuietly(sessions, sessionId) {
288
192
  catch {
289
193
  }
290
194
  }
291
- export function rebaseWorkspacePath(p, fromRaw, toRaw) {
292
- if (p.includes("\\") || fromRaw.includes("\\") || toRaw.includes("\\"))
293
- return p;
294
- const stripTrail = (s) => (s.length > 1 && s.endsWith("/") ? stripTrail(s.slice(0, -1)) : s);
295
- const from = stripTrail(fromRaw);
296
- const to = stripTrail(toRaw);
297
- if (p === from || stripTrail(p) === from)
298
- return to;
299
- const fromPrefix = from === "/" ? from : `${from}/`;
300
- if (!p.startsWith(fromPrefix))
301
- return p;
302
- const suffix = p.slice(fromPrefix.length).replace(/^\/+/, "");
303
- return to === "/" ? `${to}${suffix}` : `${to}/${suffix}`;
304
- }
305
- function remoteEnvFailureNote(op, error, attempts) {
306
- return { op, code: error.code, retryable: isRetryableRemoteErrorCode(error.code), attempts, message: error.message };
307
- }
308
- const REMOTE_RESTORE_MAX_ATTEMPTS = 2;
309
- const REMOTE_RESTORE_BACKOFF_MS = 200;
310
- async function restoreWorkspaceWithRetry(env, snapshotId, options) {
311
- let attempts = 0;
312
- const outcome = await withRetry(async (attempt) => {
313
- attempts = attempt;
314
- return env.resumeVM(snapshotId, options);
315
- }, { retryableCodes: RETRYABLE_REMOTE_ERROR_CODES, maxAttempts: REMOTE_RESTORE_MAX_ATTEMPTS, backoffMs: () => REMOTE_RESTORE_BACKOFF_MS }, { ...(options.abortSignal !== undefined ? { signal: options.abortSignal } : {}) });
316
- return { outcome, attempts };
317
- }
318
- export function rebaseWorkspacePathAcross(p, froms, to) {
319
- for (const from of froms) {
320
- const out = rebaseWorkspacePath(p, from, to);
321
- if (out !== p)
322
- return out;
323
- }
324
- return p;
325
- }
326
195
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
327
- const toolFaceSnapshot = {
328
- exclude: spec.excludeTools ? Object.freeze([...spec.excludeTools]) : undefined,
329
- defer: spec.deferTools ? Object.freeze([...spec.deferTools]) : undefined,
330
- alwaysLoad: spec.alwaysLoadTools ? Object.freeze([...spec.alwaysLoadTools]) : undefined,
331
- };
332
- const promptProfile = resolveModelPromptTraits({ id: "" }, spec, internals).promptProfile;
333
- if (spec.resumeAt !== undefined) {
334
- if (resume) {
335
- const e = new Error("resumeAt cannot be combined with a durable resume");
336
- e.code = "resume_at.conflicts_resume";
337
- throw e;
338
- }
339
- if (!spec.sessionId) {
340
- const e = new Error("resumeAt requires a sessionId (the session to branch)");
341
- e.code = "resume_at.no_session";
342
- throw e;
343
- }
344
- }
345
- assertReadFaceValue(spec.readFace, "TaskSpec.readFace");
346
- assertReadFaceValue(deps.readFace, "readFace (deployment seat)");
347
- if (spec.resumeAtMode !== undefined) {
348
- if (spec.resumeAt === undefined) {
349
- const e = new Error(`resumeAtMode "${spec.resumeAtMode}" requires resumeAt (there is no branch target to position against)`);
350
- e.code = "resume_at.mode_without_target";
351
- throw e;
352
- }
353
- if (spec.resumeAtMode !== "at" && spec.resumeAtMode !== "before") {
354
- const e = new Error(`resumeAtMode "${String(spec.resumeAtMode)}" is invalid — expected "at" (inclusive branch, the default) or "before" (exclusive branch)`);
355
- e.code = "resume_at.invalid_mode";
356
- throw e;
357
- }
358
- }
359
- if (spec.toolMaterializeStrategy !== undefined && spec.toolMaterializeStrategy !== "swap" && spec.toolMaterializeStrategy !== "static") {
360
- const e = new Error(`toolMaterializeStrategy must be "swap" or "static" (got ${JSON.stringify(spec.toolMaterializeStrategy)}).`);
361
- e.code = "config.tool_materialize_invalid";
362
- throw e;
363
- }
364
- if (spec.memoryPersistenceCapable !== undefined && typeof spec.memoryPersistenceCapable !== "boolean") {
365
- const e = new Error(`memoryPersistenceCapable must be a boolean when present (got ${JSON.stringify(spec.memoryPersistenceCapable)}) — a non-boolean would silently read as capable.`);
366
- e.code = "config.memory_persistence_invalid";
367
- throw e;
368
- }
369
- if (spec.toolMaterializeStrategy === "static" && spec.deferSelfResolve === false) {
370
- const e = new Error(`toolMaterializeStrategy "static" cannot be combined with deferSelfResolve: false — with the direct-call ` +
371
- `lane disabled a placeholder is never swapped and never self-resolves, so no deferred tool could ever be ` +
372
- `called. Use "swap", or leave deferSelfResolve on.`);
373
- e.code = "config.tool_materialize_unreachable";
374
- throw e;
375
- }
376
- if (resume === undefined && spec.objective.trim().length === 0) {
377
- const e = new Error("TaskSpec.objective is empty — a task needs an instruction (an empty user message is rejected by strict model endpoints and would fail every later request of the session).");
378
- e.code = "config.empty_objective";
379
- throw e;
380
- }
381
- const lockedPreflight = preflightLockedConfig(spec, deps);
382
- assertRetentionCapability({
383
- policy: deps.retentionPolicy,
384
- locked: lockedPreflight.lockedKeys.has("retentionPolicy"),
385
- stores: [
386
- { name: "sessionStore", store: sessions },
387
- { name: "checkpointStore", store: resolveCheckpointStore(spec, deps) },
388
- { name: "toolResultStore", store: deps.toolResultStore },
389
- ],
390
- });
391
- const resolvedInteractionPosture = spec.interactionPosture ?? internals?.parentInteractionPosture ?? deps.interactionPosture;
392
- {
393
- const interactionPosture = resolvedInteractionPosture;
394
- const discloseInteractionPostureRefusal = (err) => {
395
- try {
396
- deps.onError?.(err, { phase: "config", sessionId: spec.sessionId ?? "(pre-session)", classification: "interaction-posture-refused" });
397
- }
398
- catch {
399
- }
400
- };
401
- if (interactionPosture !== undefined && interactionPosture !== "interactive" && interactionPosture !== "headless") {
402
- const e = new Error(`interactionPosture ${JSON.stringify(interactionPosture)} is not a recognized posture ("interactive" | "headless") — ` +
403
- `an unevaluable declaration is refused loudly, never folded to either posture.`);
404
- e.code = "config.interaction_posture";
405
- discloseInteractionPostureRefusal(e);
406
- throw e;
407
- }
408
- if (interactionPosture === "interactive") {
409
- const askDoor = resolveAskSeamForm(spec, deps);
410
- const humanReachable = deriveAskEffective(askDoor.form, "unresolved") === "human_reachable";
411
- const questionDoor = resolveQuestionSeam(spec, deps);
412
- const strippedByEngine = internals?.questionFaceStripped === true;
413
- if (spec.interactiveTools === false) {
414
- const e = new Error(`interaction posture "interactive" declared together with interactiveTools: false — the hard-headless ` +
415
- `clamp removes the AskUserQuestion mount, so no content question can ever reach the human this posture ` +
416
- `promises. Drop one of the two declarations.`);
417
- e.code = "config.interaction_posture";
418
- discloseInteractionPostureRefusal(e);
419
- throw e;
420
- }
421
- if (!humanReachable || !(questionDoor.wired || strippedByEngine)) {
422
- const missing = [];
423
- if (!humanReachable) {
424
- missing.push(askDoor.form === "absent"
425
- ? "no onAsk approver is wired (spec.onAsk ?? deps.onAsk is absent — asks would auto-deny or park)"
426
- : `the resolved onAsk seat is the blanket policy "${askDoor.form}" (${askDoor.provenance ?? "?"}) — a policy setting is not a reachable human`);
427
- }
428
- if (!questionDoor.wired && !strippedByEngine) {
429
- missing.push("no content-question channel (spec.onQuestion ?? deps.onQuestion is absent, and this leg is not an engine-stripped background lane)");
430
- }
431
- const e = new Error(`interaction posture "interactive" declared, but this assembly cannot reach a human: ${missing.join("; ")}. ` +
432
- `Wire the missing seam(s), or drop the posture declaration (absent = no check).`);
433
- e.code = "config.interaction_posture";
434
- discloseInteractionPostureRefusal(e);
435
- throw e;
436
- }
437
- }
438
- }
439
- resolveTaskLimits(spec.limits);
440
- if (spec.resourceSuspend !== undefined) {
441
- const rsus = spec.resourceSuspend;
442
- if (typeof rsus.scope !== "string" || rsus.scope === "") {
443
- 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.`);
444
- }
445
- for (const key of ["totalBudgetUsd", "totalTokens", "maxSlices", "ttlMs"]) {
446
- const value = rsus[key];
447
- if (value === undefined)
448
- continue;
449
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
450
- 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.`);
451
- }
452
- }
453
- }
454
- const usageWindows = resolveUsageWindows(deps.usageWindows);
455
- const brainCallGuardrailRef = {};
456
- const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
457
- if (spec.agents !== undefined && spec.agents.length > 0) {
458
- const pool = spec.tools ?? [];
459
- if (!pool.some((t) => typeof t.withAgents === "function")) {
460
- const e = new Error(`TaskSpec.agents was provided but no delegation tool (createSubagentTool) is mounted in spec.tools — the per-task agents could never be offered. Mount the delegation tool, or drop spec.agents.`);
461
- e.code = "config.agents.no_delegation_tool";
462
- throw e;
463
- }
464
- const perTask = spec.agents;
465
- spec = { ...spec, tools: pool.map((t) => (typeof t.withAgents === "function" ? t.withAgents(perTask) : t)) };
466
- }
467
- const resolvedRole = resolveTaskModel(spec, deps);
468
- const model = resolvedRole.model;
469
- const fableMitigations = resolveModelPromptTraits(model, spec, internals).fableMitigations;
470
- const thinking = spec.thinking ?? resolvedRole.thinking ?? model.defaultThinking;
471
- const compModel = spec.compactionModel
472
- ? resolveModel(spec.compactionModel, deps.models)
473
- : roleModelIfSet("summarize", spec, deps);
474
- const toolEffects = new Map();
475
- const ownToolNames = new Set((spec.tools ?? []).map((t) => t.name));
476
- const claimedAliases = new Set();
477
- const egressTools = new Set();
478
- const irreversibleTools = new Set();
479
- const irreversibilityTier = new Map();
480
- const axisExplicitNegatives = new Map();
481
- const reversibilityProbes = new Map();
196
+ const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
197
+ spec = doors.spec;
198
+ const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
199
+ const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
482
200
  let shellGatedBash = false;
483
201
  let shellGatedMonitor = false;
484
- for (const t of spec.tools ?? []) {
485
- if (t.name.includes("__")) {
486
- const e = new Error(`Tool name "${t.name}" is invalid: "__" is reserved for the engine's protocol tool namespaces (${NAMESPACED_NAME_SHAPES}) and must not appear in a caller tool name.`);
487
- e.code = "config.tool_name_invalid";
488
- throw e;
489
- }
490
- if (t.effect) {
491
- toolEffects.set(t.name, t.effect);
492
- }
493
- const tier = t.irreversibility ?? (t.reversibilityProbe ? "maybe" : undefined);
494
- if (tier !== undefined) {
495
- irreversibilityTier.set(t.name, tier);
496
- if (tier === "always" || tier === "maybe")
497
- irreversibleTools.add(t.name);
498
- }
499
- if (t.reversibilityProbe)
500
- reversibilityProbes.set(t.name, t.reversibilityProbe);
501
- if (t.egress) {
502
- if (t.effect !== undefined && t.effect !== "write") {
503
- const e = new Error(`Tool "${t.name}" declares egress:true with effect:"${t.effect}" — an egress tool (external write) must have effect:"write" (or omit effect; write is the default).`);
504
- e.code = "config.egress_requires_write_effect";
505
- throw e;
506
- }
507
- egressTools.add(t.name);
508
- }
509
- for (const alias of t.aliases ?? []) {
510
- if (alias === t.name || ownToolNames.has(alias))
511
- continue;
512
- if (claimedAliases.has(alias))
513
- continue;
514
- claimedAliases.add(alias);
515
- if (t.effect)
516
- toolEffects.set(alias, t.effect);
517
- if (tier !== undefined) {
518
- irreversibilityTier.set(alias, tier);
519
- if (tier === "always" || tier === "maybe")
520
- irreversibleTools.add(alias);
521
- }
522
- if (t.reversibilityProbe)
523
- reversibilityProbes.set(alias, t.reversibilityProbe);
524
- if (t.egress)
525
- egressTools.add(alias);
526
- }
527
- }
528
- toolEffects.set(OFFLOAD_TOOL_NAME, "read");
529
- if (deps.executionEnvFactory || deps.executionEnv) {
530
- for (const [name, effect] of Object.entries(HAND_TOOL_EFFECTS))
531
- toolEffects.set(name, effect);
532
- if (spec.handsReadOnly === true)
533
- toolEffects.set("Bash", "read");
534
- }
535
- if (spec.enablePlanMode === true) {
536
- toolEffects.set(PRESENT_PLAN_TOOL_NAME, "read");
537
- toolEffects.set(ENTER_PLAN_MODE_TOOL_NAME, "read");
538
- }
539
202
  if (spec.requireExistingSession && !spec.sessionId) {
540
203
  const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
541
204
  e.code = "resume.session_not_found";
542
205
  throw e;
543
206
  }
544
- let acquired;
545
- let session;
546
- let conflictRef;
547
- let wakeRecovered = [];
548
- let resumeAtBeforeParentId = null;
549
- for (let attempt = 0;; attempt++) {
550
- try {
551
- acquired = await sessions.acquire(spec.sessionId, spec.requireExistingSession ? { requireExisting: true } : undefined);
552
- }
553
- catch (err) {
554
- if (spec.requireExistingSession && err?.code === "not_found") {
555
- const e = new Error(`requireExistingSession: session "${spec.sessionId}" does not exist — refusing a silent fresh run (design/114 Phase3)`);
556
- e.code = "resume.session_not_found";
557
- throw e;
558
- }
559
- throw err;
560
- }
561
- conflictRef = { hit: false };
562
- session = new StoredSession(detectConflicts(acquired.session.getStorage(), conflictRef));
563
- if (!spec.sessionId) {
564
- break;
565
- }
566
- try {
567
- if (resume) {
568
- await session.getStorage().setLeafId(resume.leafId);
569
- }
570
- else if (spec.resumeAt !== undefined) {
571
- const entry = await session.getEntry(spec.resumeAt);
572
- if (!entry) {
573
- const e = new Error(`resumeAt entry "${spec.resumeAt}" not found in session "${spec.sessionId}"`);
574
- e.code = "resume_at.not_found";
575
- throw e;
576
- }
577
- if (entry.type !== "message" && entry.type !== "custom_message") {
578
- const e = new Error(`resumeAt entry "${spec.resumeAt}" is a "${entry.type}" entry; resume-at requires a message boundary`);
579
- e.code = "resume_at.not_a_message";
580
- throw e;
581
- }
582
- if (entry.type === "message") {
583
- const msg = entry.message;
584
- if (msg?.role === "toolResult") {
585
- const e = new Error(`resumeAt entry "${spec.resumeAt}" is a tool-result (mid-turn); resume-at requires a settled turn boundary (a user message or a finished assistant turn)`);
586
- e.code = "resume_at.not_a_message";
587
- throw e;
588
- }
589
- if (msg?.role === "assistant" && Array.isArray(msg.content) && msg.content.some((p) => p?.type === "toolCall")) {
590
- const e = new Error(`resumeAt entry "${spec.resumeAt}" ends a turn mid-tool-call; resume-at requires a settled turn boundary`);
591
- e.code = "resume_at.not_a_message";
592
- throw e;
593
- }
594
- }
595
- if (spec.resumeAtMode === "before") {
596
- const role = entry.type === "message" ? entry.message.role : undefined;
597
- if (role !== "user") {
598
- const kind = entry.type === "message" ? `role "${String(role)}" message` : `"${entry.type}" entry`;
599
- const e = new Error(`resumeAt entry "${spec.resumeAt}" is a ${kind}; resumeAtMode "before" supports only USER-message targets (the exclusive-rewind shape: the removed message is a user message)`);
600
- e.code = "resume_at.before_target_not_user";
601
- throw e;
602
- }
603
- let ancestorId = entry.parentId;
604
- let sawConversationAbove = false;
605
- while (ancestorId !== null) {
606
- const ancestor = await session.getEntry(ancestorId);
607
- if (!ancestor)
608
- break;
609
- if (ancestor.type === "message" || ancestor.type === "custom_message") {
610
- sawConversationAbove = true;
611
- break;
612
- }
613
- ancestorId = ancestor.parentId;
614
- }
615
- if (!sawConversationAbove) {
616
- const e = new Error(`resumeAt entry "${spec.resumeAt}" is the session's first message; resumeAtMode "before" cannot rewind past the session root — start a NEW session instead`);
617
- e.code = "resume_at.before_root_unsupported";
618
- throw e;
619
- }
620
- resumeAtBeforeParentId = entry.parentId;
621
- }
622
- await session.getStorage().setLeafId(spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt);
623
- }
624
- wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
625
- break;
626
- }
627
- catch (err) {
628
- if (isSessionConflict(err) && attempt < RECONCILE_MAX_RETRIES) {
629
- if (sessions.forget) {
630
- await Promise.resolve(sessions.forget(spec.sessionId)).catch(() => undefined);
631
- }
632
- continue;
633
- }
634
- await Promise.resolve(sessions.forget ? sessions.forget(acquired.sessionId) : undefined).catch(() => undefined);
635
- throw err;
636
- }
637
- }
207
+ const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects });
638
208
  const sessionId = acquired.sessionId;
639
209
  const hostTaskId = spec.taskId ?? sessionId;
640
210
  if (compModel !== undefined) {
@@ -776,7 +346,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
776
346
  }
777
347
  }
778
348
  const handsEnabled = ownedEnv !== undefined || deps.executionEnv !== undefined;
779
- let taskRootPath = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
349
+ const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
780
350
  const abortController = new AbortController();
781
351
  if (spec.signal?.aborted)
782
352
  abortController.abort();
@@ -824,108 +394,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
824
394
  }
825
395
  }
826
396
  }
827
- let restoredRootRebase;
828
- const rebaseRestoredPath = (p) => restoredRootRebase === undefined ? p : rebaseWorkspacePathAcross(p, restoredRootRebase.from, restoredRootRebase.to);
829
- if (resume?.workspaceHandle !== undefined) {
830
- const failResume = (message, cause, note) => {
831
- const e = new Error(message, cause ? { cause } : undefined);
832
- e.code = "resume.env_failed";
833
- if (note !== undefined)
834
- e.remoteEnvFailure = note;
835
- throw e;
836
- };
837
- const handle = resume.workspaceHandle;
838
- if (ownedEnv === undefined || !isRemoteExecutionEnv(ownedEnv)) {
839
- failResume("resume needs a RemoteExecutionEnv from executionEnvFactory to restore the workspace snapshot");
840
- }
841
- else if (handle.snapshotId === undefined) {
842
- if (handle.restoreMode !== "park_only" && ownedEnv.capabilities.suspendable) {
843
- failResume("checkpoint workspaceHandle has no snapshotId and is not a park_only handle, but the resumed env is suspendable — refusing to resume on a possibly-unrestored workspace (corrupt checkpoint?)");
844
- }
845
- if (handle.mountPath && handle.mountPath !== taskRootPath) {
846
- taskRootPath = handle.mountPath;
847
- }
848
- }
849
- else {
850
- const restoreSignal = spec.signal
851
- ? AbortSignal.any([abortController.signal, spec.signal])
852
- : abortController.signal;
853
- const missingHere = missingRestoreSurface(ownedEnv);
854
- if (missingHere.length > 0) {
855
- failResume(`the resumed execution env cannot restore a workspace snapshot: its adapter does not implement ${missingHere.join(" or ")}. The checkpoint holds snapshot "${handle.snapshotId}" — wire an adapter that implements the full RemoteExecutionEnv restore surface and re-resume.`);
856
- }
857
- const { outcome: restored, attempts: restoreAttempts } = await restoreWorkspaceWithRetry(ownedEnv, handle.snapshotId, {
858
- abortSignal: restoreSignal,
859
- priorHandle: handle,
860
- });
861
- if (!restored.ok) {
862
- failResume(`resumeVM failed after ${restoreAttempts} attempt(s) (${restored.error.code}): ${restored.error.message}`, restored.error, remoteEnvFailureNote("resumeVM", restored.error, restoreAttempts));
863
- }
864
- else {
865
- const restoredEnv = ownedEnv;
866
- const canonicalInEnv = async (p) => {
867
- try {
868
- const r = await restoredEnv.canonicalPath(p, restoreSignal);
869
- return r.ok ? r.value : undefined;
870
- }
871
- catch {
872
- return undefined;
873
- }
874
- };
875
- let checkpointedCanonical;
876
- let sameRootUnderAlias = false;
877
- if (restored.value.mountPath !== handle.mountPath) {
878
- checkpointedCanonical = await canonicalInEnv(handle.mountPath);
879
- const restoredCanonical = await canonicalInEnv(restored.value.mountPath);
880
- sameRootUnderAlias =
881
- checkpointedCanonical !== undefined && restoredCanonical !== undefined && checkpointedCanonical === restoredCanonical;
882
- }
883
- if (restored.value.mountPath !== handle.mountPath && !sameRootUnderAlias) {
884
- if (resume.executesApprovedAction === true) {
885
- failResume(`resumeVM workspace-root divergence with a pending approved action: checkpointed mountPath "${handle.mountPath}" but the restored handle reports "${restored.value.mountPath}" — the approved args are bound to the checkpointed root; refusing to execute them against a moved workspace (adapter should honor priorHandle)`);
886
- }
887
- try {
888
- deps.onError?.(new Error(`resumeVM workspace-root divergence: checkpointed mountPath "${handle.mountPath}" but the restored handle reports "${restored.value.mountPath}" — the resumed task follows the restored root`), { phase: "config", sessionId });
889
- }
890
- catch {
891
- }
892
- }
893
- if (restored.value.mountPath && restored.value.mountPath !== taskRootPath) {
894
- taskRootPath = restored.value.mountPath;
895
- }
896
- if (restored.value.mountPath && restored.value.mountPath !== handle.mountPath && !sameRootUnderAlias) {
897
- const from = checkpointedCanonical !== undefined && checkpointedCanonical !== handle.mountPath
898
- ? [handle.mountPath, checkpointedCanonical]
899
- : [handle.mountPath];
900
- restoredRootRebase = { from, to: restored.value.mountPath };
901
- try {
902
- deps.onError?.(new Error(from.length > 1
903
- ? `resumeVM workspace-root rebase accepts both spellings of the checkpointed root (${from.map((f) => `"${f}"`).join(" and ")}) when migrating persisted paths to "${restored.value.mountPath}"`
904
- : `resumeVM workspace-root rebase is spelling-exact: it matches the checkpointed root "${handle.mountPath}" only, so persisted paths recorded under an equivalent alias of it are NOT migrated to "${restored.value.mountPath}" and stay as written`), { phase: "config", sessionId });
905
- }
906
- catch {
907
- }
908
- }
909
- }
910
- const init = await ownedEnv.postResumeInit();
911
- if (!init.ok) {
912
- failResume(`postResumeInit failed (${init.error.code}): ${init.error.message}`, init.error, remoteEnvFailureNote("postResumeInit", init.error, 1));
913
- }
914
- }
915
- }
916
- if (internals?.onWorkspaceResolved !== undefined) {
917
- try {
918
- const workspaceEnv = ownedEnv ?? deps.executionEnv;
919
- internals.onWorkspaceResolved({
920
- cwd: taskRootPath,
921
- isolated: internals.isolation === "worktree",
922
- remote: workspaceEnv !== undefined &&
923
- (isRemoteExecutionEnv(workspaceEnv) || workspaceEnv.hostLocalPaths === false),
924
- });
925
- }
926
- catch {
927
- }
928
- }
397
+ const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
929
398
  const rewindNotes = [];
930
399
  const rewindCaptureRequested = spec.rewindFiles === true;
931
400
  if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
@@ -982,7 +451,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
982
451
  }
983
452
  rewindTarget = anchor;
984
453
  }
985
- const restoreRoot = taskRootPath;
454
+ const restoreRoot = taskRootFinal;
986
455
  const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
987
456
  const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
988
457
  if (!restored.ok) {
@@ -1027,7 +496,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1027
496
  planModeRef.active = true;
1028
497
  };
1029
498
  const subagentRetain = spec.retainSubagentSessions ? new SubagentRetainLedger(spec.retainSubagentSessions) : undefined;
1030
- const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootPath) : undefined;
499
+ const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootFinal) : undefined;
1031
500
  const liveInheritedGate = internals?.inheritedGate;
1032
501
  const seedInheritedGate = resume?.seed.inheritedGate;
1033
502
  const inheritedAncestorRules = (() => {
@@ -1085,12 +554,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1085
554
  const ownOrgVerdictRef = { current: undefined };
1086
555
  const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
1087
556
  const faceCheckpointSection = () => {
1088
- if (resolvedReadFace === undefined) {
1089
- const seed = resume?.seed.readFace;
557
+ if (resolvedReadFace === undefined && resume !== undefined) {
558
+ const seed = resume.seed.readFace;
1090
559
  return seed !== undefined ? { face: seed.face, ...(seed.denyEntries !== undefined ? { denyEntries: seed.denyEntries.map((e) => ({ ...e })) } : {}) } : undefined;
1091
560
  }
1092
- if (resolvedReadFace === "open" || readDenyAdditionsNormalized.length > 0) {
1093
- return { face: resolvedReadFace, ...(readDenyAdditionsNormalized.length > 0 ? { denyEntries: readDenyAdditionsNormalized.map((e) => ({ ...e })) } : {}) };
561
+ const face = resolvedReadFace ?? handsLessResolvedFace;
562
+ if (face === undefined)
563
+ return undefined;
564
+ if (face === "open" || readDenyAdditionsNormalized.length > 0) {
565
+ return { face, ...(readDenyAdditionsNormalized.length > 0 ? { denyEntries: readDenyAdditionsNormalized.map((e) => ({ ...e })) } : {}) };
1094
566
  }
1095
567
  return undefined;
1096
568
  };
@@ -1167,6 +639,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1167
639
  let observersActive = false;
1168
640
  let resolvedReadFace;
1169
641
  let readDenyAdditionsNormalized = [];
642
+ let handsLessResolvedFace;
643
+ const carrierReadFace = () => resolvedReadFace ?? handsLessResolvedFace;
1170
644
  const fullShellReachable = handsEnabled &&
1171
645
  !(executionEnv instanceof StubExecutionEnv) &&
1172
646
  spec.handsReadOnly !== true &&
@@ -1181,7 +655,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1181
655
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
1182
656
  ...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
1183
657
  ...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
1184
- ...(resolvedReadFace === "roots" ? { readFace: "roots" } : {}),
658
+ ...(carrierReadFace() === "roots" ? { readFace: "roots" } : {}),
1185
659
  ...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
1186
660
  ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
1187
661
  oneShot: spec.oneShot,
@@ -1195,7 +669,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1195
669
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
1196
670
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
1197
671
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
1198
- parentCwd: taskRootPath,
672
+ parentCwd: taskRootFinal,
1199
673
  ...(centerAdoption !== undefined ? { centerArtifactDigest: centerAdoption.artifact.artifactDigest } : {}),
1200
674
  ...(centerAdoption?.sourceRevision !== undefined ? { centerSourceRevision: centerAdoption.sourceRevision } : {}),
1201
675
  activeSkillScope: () => skillScope.active(),
@@ -1386,9 +860,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1386
860
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1387
861
  autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1388
862
  workflowDepth: internals?.workflowDepth,
1389
- parentCwd: taskRootPath,
863
+ parentCwd: taskRootFinal,
1390
864
  parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
1391
- parentReadFace: () => resolvedReadFace,
865
+ parentReadFace: () => carrierReadFace(),
1392
866
  parentReadDenyPatterns: () => (readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized : undefined),
1393
867
  onNotice: deps.onNotice,
1394
868
  parentCheckpointStoreDisabled: spec.checkpointStore === null,
@@ -1608,7 +1082,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1608
1082
  let attachmentRootCanonical;
1609
1083
  let readDenyMatcher;
1610
1084
  if (handsEnabled) {
1611
- const rootRaw = taskRootPath;
1085
+ const rootRaw = taskRootFinal;
1612
1086
  const canon = await executionEnv.canonicalPath(rootRaw);
1613
1087
  const rootCanonical = canon.ok ? canon.value : rootRaw;
1614
1088
  attachmentRootCanonical = rootCanonical;
@@ -1681,17 +1155,32 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1681
1155
  }
1682
1156
  const seedReadFaceSection = resume?.seed.readFace;
1683
1157
  const readDenyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seedReadFaceSection?.denyEntries ?? [])];
1684
- readDenyMatcher = compileReadDeny(readDenyAdditions, "readDenyPatterns");
1685
- {
1686
- const builtinCount = compileReadDeny([], "built-in").entries.length;
1687
- readDenyAdditionsNormalized = readDenyMatcher.entries.slice(builtinCount);
1688
- }
1158
+ const readDenyBuiltinCfg = {
1159
+ ...(deps.readDenyBuiltinTiers !== undefined ? { tiers: [...deps.readDenyBuiltinTiers] } : {}),
1160
+ ...(deps.readDenyBuiltinExclude !== undefined ? { exclude: [...deps.readDenyBuiltinExclude] } : {}),
1161
+ };
1162
+ readDenyMatcher = compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg);
1163
+ readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
1689
1164
  let liveReadFace = resolveReadFace({
1690
1165
  specReadFace: spec.readFace,
1691
1166
  depsReadFace: deps.readFace,
1692
1167
  readOnlyMount: handsReadOnly,
1693
1168
  orgGoverned: deps.permissionRuleOrg !== undefined,
1694
1169
  fullShellReachable,
1170
+ onDeploymentClamp: () => {
1171
+ const sink = deps.onNotice;
1172
+ if (typeof sink === "function") {
1173
+ if (readFaceClampAnnouncedSinks.has(sink))
1174
+ return;
1175
+ readFaceClampAnnouncedSinks.add(sink);
1176
+ }
1177
+ else {
1178
+ if (readFaceClampConsoleAnnounced)
1179
+ return;
1180
+ readFaceClampConsoleAnnounced = true;
1181
+ }
1182
+ deliverEngineNotice(sink, deploymentReadFaceClampNotice());
1183
+ },
1695
1184
  });
1696
1185
  if (resume !== undefined && (seedReadFaceSection === undefined || seedReadFaceSection.face === "roots"))
1697
1186
  liveReadFace = "roots";
@@ -1708,6 +1197,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1708
1197
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
1709
1198
  ...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
1710
1199
  ...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
1200
+ ...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
1201
+ ...(readDenyBuiltinCfg.exclude !== undefined ? { readDenyBuiltinExclude: readDenyBuiltinCfg.exclude } : {}),
1711
1202
  readFace: liveReadFace,
1712
1203
  includeShell: handsIncludeShell,
1713
1204
  readOnly: handsReadOnly,
@@ -1778,6 +1269,25 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1778
1269
  }
1779
1270
  }
1780
1271
  }
1272
+ else {
1273
+ const liveFace = resolveReadFace({
1274
+ specReadFace: spec.readFace,
1275
+ depsReadFace: deps.readFace,
1276
+ readOnlyMount: spec.handsReadOnly === true,
1277
+ orgGoverned: deps.permissionRuleOrg !== undefined,
1278
+ fullShellReachable,
1279
+ });
1280
+ const seed = resume?.seed.readFace;
1281
+ handsLessResolvedFace = resume !== undefined && (seed === undefined || seed.face === "roots") ? "roots" : liveFace;
1282
+ const denyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seed?.denyEntries ?? [])];
1283
+ resolveReadDenyBuiltins({
1284
+ ...(deps.readDenyBuiltinTiers !== undefined ? { tiers: deps.readDenyBuiltinTiers } : {}),
1285
+ ...(deps.readDenyBuiltinExclude !== undefined ? { exclude: deps.readDenyBuiltinExclude } : {}),
1286
+ });
1287
+ if (denyAdditions.length > 0) {
1288
+ readDenyAdditionsNormalized = compileReadDeny(denyAdditions, "readDenyPatterns", { tiers: [] }).entries;
1289
+ }
1290
+ }
1781
1291
  if (backgroundTaskToolsActive || workflowToolsActive) {
1782
1292
  toolEffects.set("TaskOutput", "read");
1783
1293
  toolEffects.set("TaskStop", "write");
@@ -1879,7 +1389,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1879
1389
  };
1880
1390
  if (workspaceStateSettle !== undefined)
1881
1391
  workspaceStateSettle.restoredWorktreeDir = worktreeSessionRef.current?.worktreeDir;
1882
- tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootPath, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => firstPartyOffload(t)));
1392
+ tools.push(...createWorktreeTools(executionEnv, { repoRoot: taskRootFinal, cwdRef: handsCwdRef, session: worktreeSessionRef }).map((t) => firstPartyOffload(t)));
1883
1393
  }
1884
1394
  if (offloadStore)
1885
1395
  tools.push(createReadToolResultTool(offloadStore));
@@ -1976,7 +1486,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1976
1486
  }
1977
1487
  const lspManager = spec.lspManager ?? deps.lspManager;
1978
1488
  if (lspManager) {
1979
- const lspRoot = taskRootPath;
1489
+ const lspRoot = taskRootFinal;
1980
1490
  tools.push(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }));
1981
1491
  }
1982
1492
  const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
@@ -1985,7 +1495,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1985
1495
  const lspRunIdent = sessionId;
1986
1496
  const nudgeLspOnEdit = lspDiagnostics
1987
1497
  ? (rawPath) => {
1988
- const baseDir = handsCwdRef?.current ?? taskRootPath;
1498
+ const baseDir = handsCwdRef?.current ?? taskRootFinal;
1989
1499
  const filePath = resolveLspPath(rawPath, baseDir);
1990
1500
  lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
1991
1501
  void lspManager
@@ -2007,7 +1517,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2007
1517
  spec,
2008
1518
  deps,
2009
1519
  sessionId,
2010
- taskRootPath,
1520
+ taskRootPath: taskRootFinal,
2011
1521
  memoryWriteGateRef,
2012
1522
  writeToolsMounted: tools.some((t) => t.name === "Write") &&
2013
1523
  !(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
@@ -2096,7 +1606,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2096
1606
  let loaded = null;
2097
1607
  try {
2098
1608
  loaded = await Promise.resolve(deps.loadProjectMemory({
2099
- cwd: taskRootPath,
1609
+ cwd: taskRootFinal,
2100
1610
  handsEnabled,
2101
1611
  isSubagent: isDelegatedNonForkChild(internals),
2102
1612
  ...(internals?.agentName ? { agentName: internals.agentName } : {}),
@@ -2263,7 +1773,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2263
1773
  envFacts.resumeFacts = copy;
2264
1774
  }
2265
1775
  if (handsEnabled) {
2266
- envFacts.cwd = taskRootPath;
1776
+ envFacts.cwd = taskRootFinal;
2267
1777
  if (additionalRootsCanonical.length > 0) {
2268
1778
  envFacts.additionalDirectories = [...additionalRootsCanonical];
2269
1779
  }
@@ -3088,7 +2598,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3088
2598
  if (deps.sessionPolicyStore && spec.sessionId) {
3089
2599
  const rules = await deps.sessionPolicyStore.getRules(spec.sessionId, spec.principal);
3090
2600
  if (rules) {
3091
- sessionRulePolicy = createSessionRulePolicy(rules, { env: executionEnv, rootPath: taskRootPath, toolEffects });
2601
+ sessionRulePolicy = createSessionRulePolicy(rules, { env: executionEnv, rootPath: taskRootFinal, toolEffects });
3092
2602
  ownSessionRulesRef.current = {
3093
2603
  sessionId: spec.sessionId,
3094
2604
  ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
@@ -3109,13 +2619,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3109
2619
  catch {
3110
2620
  }
3111
2621
  }
3112
- ancestorRulePolicies.push(createSessionRulePolicy(ancestorRules, { env: executionEnv, rootPath: taskRootPath, toolEffects }));
2622
+ ancestorRulePolicies.push(createSessionRulePolicy(ancestorRules, { env: executionEnv, rootPath: taskRootFinal, toolEffects }));
3113
2623
  }
3114
2624
  const skillScopePolicy = hasSkillManifest
3115
2625
  ? createActiveSkillScopePolicy({
3116
2626
  scope: skillScope,
3117
2627
  env: executionEnv,
3118
- rootPath: taskRootPath,
2628
+ rootPath: taskRootFinal,
3119
2629
  toolEffects,
3120
2630
  })
3121
2631
  : undefined;
@@ -3281,7 +2791,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3281
2791
  }
3282
2792
  if (provider === undefined)
3283
2793
  return undefined;
3284
- const root = taskRootPath;
2794
+ const root = taskRootFinal;
3285
2795
  return {
3286
2796
  admits: async (req) => {
3287
2797
  const anonymous = spec.principal === undefined || spec.principal === "";
@@ -3901,6 +3411,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3901
3411
  ...riskAxesOf(req.toolName),
3902
3412
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
3903
3413
  ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
3414
+ ...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
3415
+ ...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
3904
3416
  }, onAsk, abortController.signal);
3905
3417
  const waitMs = Math.max(0, now() - t0);
3906
3418
  if (resolved.approverUnavailable !== true) {
@@ -4383,7 +3895,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4383
3895
  }
4384
3896
  };
4385
3897
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
4386
- ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason) => {
3898
+ ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause) => {
4387
3899
  const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
4388
3900
  if (syncFirstEligible &&
4389
3901
  runtimeCaps?.forceDurableGate !== true &&
@@ -4489,6 +4001,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4489
4001
  args: parkedArgs,
4490
4002
  safety,
4491
4003
  ...(shadowedRule !== undefined ? { shadowedRule } : {}),
4004
+ ...(probeReason !== undefined ? { probeReason } : {}),
4005
+ ...(probeCause !== undefined ? { probeCause } : {}),
4492
4006
  shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
4493
4007
  ...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
4494
4008
  });
@@ -5035,8 +4549,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
5035
4549
  }
5036
4550
  : undefined;
5037
4551
  overheadState.promptChars = systemPrompt.length;
4552
+ const effectiveReadFaceObserved = carrierReadFace();
4553
+ const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
5038
4554
  const preparedHolder = {};
5039
- const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettledBy, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, 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, lastBrainContext, 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 } : {}) });
4555
+ const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettledBy, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, 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, lastBrainContext, 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 } : {}) });
5040
4556
  const prepared = buildPrepared();
5041
4557
  preparedHolder.current = prepared;
5042
4558
  return prepared;