@sema-agent/core 5.13.0 → 5.15.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 (108) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +4 -0
  4. package/dist/agents/subagent.js +133 -41
  5. package/dist/brain/anthropic.js +33 -10
  6. package/dist/brain/context-overflow.d.ts +20 -0
  7. package/dist/brain/context-overflow.js +58 -0
  8. package/dist/brain/errors.js +21 -1
  9. package/dist/brain/open-responses.js +24 -10
  10. package/dist/brain/openai.js +29 -11
  11. package/dist/brain/request-params.d.ts +2 -0
  12. package/dist/brain/request-params.js +16 -0
  13. package/dist/brain/retry.d.ts +5 -0
  14. package/dist/brain/retry.js +16 -4
  15. package/dist/brain/stream-engine.d.ts +9 -1
  16. package/dist/brain/stream-engine.js +261 -28
  17. package/dist/brain/timeout.d.ts +1 -0
  18. package/dist/brain/timeout.js +1 -0
  19. package/dist/core/a2a.d.ts +2 -2
  20. package/dist/core/a2a.js +3 -3
  21. package/dist/core/ask-question.d.ts +47 -2
  22. package/dist/core/ask-question.js +209 -28
  23. package/dist/core/background-agent-store.d.ts +2 -0
  24. package/dist/core/checkpoint-store.d.ts +41 -17
  25. package/dist/core/checkpoint-store.js +137 -6
  26. package/dist/core/hooks.d.ts +24 -2
  27. package/dist/core/hooks.js +97 -10
  28. package/dist/core/human-input-projection.d.ts +12 -0
  29. package/dist/core/human-input-projection.js +27 -0
  30. package/dist/core/mcp.d.ts +7 -2
  31. package/dist/core/mcp.js +7 -7
  32. package/dist/core/memory-admission.d.ts +4 -0
  33. package/dist/core/memory-admission.js +3 -0
  34. package/dist/core/memory-engine/engine.d.ts +3 -1
  35. package/dist/core/memory-engine/engine.js +4 -3
  36. package/dist/core/memory-recall.d.ts +1 -1
  37. package/dist/core/memory-recall.js +3 -2
  38. package/dist/core/runner/assemble-result.d.ts +1 -0
  39. package/dist/core/runner/assemble-result.js +1 -1
  40. package/dist/core/runner/prepare-memory.d.ts +1 -0
  41. package/dist/core/runner/prepare-memory.js +3 -3
  42. package/dist/core/runner/prepare-task.d.ts +19 -6
  43. package/dist/core/runner/prepare-task.js +343 -35
  44. package/dist/core/runner/runtask.d.ts +3 -6
  45. package/dist/core/runner/runtask.js +290 -64
  46. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  47. package/dist/core/runner/tool-disclosure.js +65 -16
  48. package/dist/core/runner/tool-output-projection.js +1 -0
  49. package/dist/core/runner/turn-attachments.d.ts +4 -0
  50. package/dist/core/runner/turn-attachments.js +15 -2
  51. package/dist/core/session-store.d.ts +3 -0
  52. package/dist/core/session-store.js +4 -0
  53. package/dist/core/session.d.ts +1 -0
  54. package/dist/core/store-contracts/background-agent-store-contract.js +19 -0
  55. package/dist/core/store-contracts/checkpoint-store-contract.js +62 -3
  56. package/dist/core/task-notification.d.ts +2 -0
  57. package/dist/core/task-notification.js +5 -3
  58. package/dist/core/task-registry-agent.d.ts +1 -0
  59. package/dist/core/task-registry-agent.js +6 -0
  60. package/dist/core/task-registry.d.ts +1 -0
  61. package/dist/core/task-registry.js +4 -1
  62. package/dist/core/task-tool-shape.js +4 -3
  63. package/dist/core/tool-policy.d.ts +5 -0
  64. package/dist/core/tool-policy.js +2 -1
  65. package/dist/core/trace.d.ts +6 -0
  66. package/dist/core/types.d.ts +32 -1
  67. package/dist/core/wiring-manifest.d.ts +97 -0
  68. package/dist/core/wiring-manifest.js +186 -0
  69. package/dist/engine/compaction/compaction.js +2 -2
  70. package/dist/engine/harness/agent-harness.d.ts +2 -1
  71. package/dist/engine/harness/agent-harness.js +8 -1
  72. package/dist/engine/harness/types.d.ts +3 -1
  73. package/dist/engine/llm/types.d.ts +7 -0
  74. package/dist/engine/llm/types.js +8 -1
  75. package/dist/engine/session/import-validate.d.ts +6 -1
  76. package/dist/engine/session/import-validate.js +29 -6
  77. package/dist/engine/session/memory-repo.d.ts +3 -1
  78. package/dist/engine/session/memory-repo.js +2 -2
  79. package/dist/index.d.ts +10 -7
  80. package/dist/index.js +8 -5
  81. package/dist/internal/harness-types.d.ts +1 -1
  82. package/dist/internal/llm.d.ts +2 -2
  83. package/dist/internal/llm.js +1 -1
  84. package/dist/orchestration/run-workflow-tool.d.ts +4 -0
  85. package/dist/orchestration/run-workflow-tool.js +3 -0
  86. package/dist/orchestration/workflow-types.d.ts +8 -0
  87. package/dist/orchestration/workflow-types.js +14 -0
  88. package/dist/orchestration/workflow.d.ts +4 -0
  89. package/dist/orchestration/workflow.js +134 -5
  90. package/dist/prompts/default.js +1 -1
  91. package/dist/prompts/supervisor.d.ts +2 -2
  92. package/dist/prompts/supervisor.js +5 -4
  93. package/dist/stores/file/checkpoint-store.d.ts +3 -5
  94. package/dist/stores/file/checkpoint-store.js +31 -2
  95. package/dist/stores/file/index.js +1 -1
  96. package/dist/stores/file/session-store.d.ts +3 -1
  97. package/dist/stores/file/session-store.js +2 -2
  98. package/dist/stores/file/shared-ledger.js +8 -1
  99. package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -0
  100. package/dist/tools/fs/bash-readonly-classifier.js +94 -0
  101. package/dist/tools/fs/fs-bash.d.ts +1 -0
  102. package/dist/tools/fs/fs-bash.js +32 -13
  103. package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
  104. package/dist/tools/fs/gh-rate-limit.js +4 -3
  105. package/dist/tools/fs/index.d.ts +1 -0
  106. package/dist/tools/fs/index.js +1 -0
  107. package/dist/tools/fs/safety.js +34 -10
  108. package/package.json +1 -1
@@ -12,6 +12,8 @@ import { primaryActivityArg } from "../arg-summary.js";
12
12
  import { materializeMcpTools } from "../mcp.js";
13
13
  import { materializeA2aTools } from "../a2a.js";
14
14
  import { Type } from "typebox";
15
+ import { Value } from "typebox/value";
16
+ import { uuidv7 } from "../../engine/session/uuid.js";
15
17
  import { brainToRuntime } from "../runtime.js";
16
18
  import { StoredSession, isSessionConflict, hasSessionFork } from "../session.js";
17
19
  import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents/subagent.js";
@@ -24,7 +26,7 @@ import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentList
24
26
  import { inlineUntrusted } from "../untrusted-text.js";
25
27
  import { emitTrace } from "../trace.js";
26
28
  import { createSessionRulePolicy } from "./session-rule-policy.js";
27
- import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js";
29
+ import { cloneObserverInput, createHookEnvCapabilities, formatHookFeedback, runToolGate } from "../hooks.js";
28
30
  import { reconcileInterruptedSession } from "../session-reconcile.js";
29
31
  import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
30
32
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
@@ -36,7 +38,7 @@ import { pathToUri } from "../lsp-protocol.js";
36
38
  import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
37
39
  import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
38
40
  import { compileOutputSchema } from "./strict-output-schema.js";
39
- import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, } from "./tool-disclosure.js";
41
+ import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, staticSchemaRenderable, } from "./tool-disclosure.js";
40
42
  import { composeMemoryBlock } from "../memory.js";
41
43
  import { preflightLockedConfig } from "../locked-config.js";
42
44
  import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
@@ -64,7 +66,7 @@ import { createMonitorTool } from "../../tools/monitor.js";
64
66
  import { createWorktreeTools } from "../../tools/worktree.js";
65
67
  import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, seedReadFileStateFromTranscript, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
66
68
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
67
- import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool } from "../ask-question.js";
69
+ import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool, classifyQuestionOutcome, isLiveQuestionFace, validateAskQuestions, } from "../ask-question.js";
68
70
  import { createSchedulerTools } from "../../tools/scheduler-tools.js";
69
71
  import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
70
72
  import { isSelfOrchestrationActive, selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
@@ -73,8 +75,9 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
73
75
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
74
76
  import { resolveKey } from "../../tools/fs/safety.js";
75
77
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
76
- import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
78
+ import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, resolveCheckpointStore, } from "../checkpoint-store.js";
77
79
  import { boundInputHashOf } from "../canonical-json.js";
80
+ import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
78
81
  import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
79
82
  const RECONCILE_MAX_RETRIES = 3;
80
83
  const DEFAULT_MAX_SUSPENDS = 5;
@@ -177,11 +180,7 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
177
180
  export function checkpointScopeOf(spec) {
178
181
  return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
179
182
  }
180
- export function resolveCheckpointStore(spec, deps) {
181
- if (spec.checkpointStore === null)
182
- return undefined;
183
- return spec.checkpointStore ?? deps.checkpointStore;
184
- }
183
+ export { resolveCheckpointStore } from "../checkpoint-store.js";
185
184
  export function isFableFamilyModelId(id) {
186
185
  const tail = id.toLowerCase().split("/").pop() ?? "";
187
186
  return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
@@ -351,6 +350,54 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
351
350
  { name: "toolResultStore", store: deps.toolResultStore },
352
351
  ],
353
352
  });
353
+ const resolvedInteractionPosture = spec.interactionPosture ?? internals?.parentInteractionPosture ?? deps.interactionPosture;
354
+ {
355
+ const interactionPosture = resolvedInteractionPosture;
356
+ const discloseInteractionPostureRefusal = (err) => {
357
+ try {
358
+ deps.onError?.(err, { phase: "config", sessionId: spec.sessionId ?? "(pre-session)", classification: "interaction-posture-refused" });
359
+ }
360
+ catch {
361
+ }
362
+ };
363
+ if (interactionPosture !== undefined && interactionPosture !== "interactive" && interactionPosture !== "headless") {
364
+ const e = new Error(`interactionPosture ${JSON.stringify(interactionPosture)} is not a recognized posture ("interactive" | "headless") — ` +
365
+ `an unevaluable declaration is refused loudly, never folded to either posture.`);
366
+ e.code = "config.interaction_posture";
367
+ discloseInteractionPostureRefusal(e);
368
+ throw e;
369
+ }
370
+ if (interactionPosture === "interactive") {
371
+ const askDoor = resolveAskSeamForm(spec, deps);
372
+ const humanReachable = deriveAskEffective(askDoor.form, "unresolved") === "human_reachable";
373
+ const questionDoor = resolveQuestionSeam(spec, deps);
374
+ const strippedByEngine = internals?.questionFaceStripped === true;
375
+ if (spec.interactiveTools === false) {
376
+ const e = new Error(`interaction posture "interactive" declared together with interactiveTools: false — the hard-headless ` +
377
+ `clamp removes the AskUserQuestion mount, so no content question can ever reach the human this posture ` +
378
+ `promises. Drop one of the two declarations.`);
379
+ e.code = "config.interaction_posture";
380
+ discloseInteractionPostureRefusal(e);
381
+ throw e;
382
+ }
383
+ if (!humanReachable || !(questionDoor.wired || strippedByEngine)) {
384
+ const missing = [];
385
+ if (!humanReachable) {
386
+ missing.push(askDoor.form === "absent"
387
+ ? "no onAsk approver is wired (spec.onAsk ?? deps.onAsk is absent — asks would auto-deny or park)"
388
+ : `the resolved onAsk seat is the blanket policy "${askDoor.form}" (${askDoor.provenance ?? "?"}) — a policy setting is not a reachable human`);
389
+ }
390
+ if (!questionDoor.wired && !strippedByEngine) {
391
+ missing.push("no content-question channel (spec.onQuestion ?? deps.onQuestion is absent, and this leg is not an engine-stripped background lane)");
392
+ }
393
+ const e = new Error(`interaction posture "interactive" declared, but this assembly cannot reach a human: ${missing.join("; ")}. ` +
394
+ `Wire the missing seam(s), or drop the posture declaration (absent = no check).`);
395
+ e.code = "config.interaction_posture";
396
+ discloseInteractionPostureRefusal(e);
397
+ throw e;
398
+ }
399
+ }
400
+ }
354
401
  resolveTaskLimits(spec.limits);
355
402
  if (spec.resourceSuspend !== undefined) {
356
403
  const rsus = spec.resourceSuspend;
@@ -392,6 +439,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
392
439
  const egressTools = new Set();
393
440
  const irreversibleTools = new Set();
394
441
  const irreversibilityTier = new Map();
442
+ const axisExplicitNegatives = new Map();
395
443
  const reversibilityProbes = new Map();
396
444
  let shellGatedBash = false;
397
445
  let shellGatedMonitor = false;
@@ -581,6 +629,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
581
629
  if (extraBodyCollisions.length > 0) {
582
630
  deps.onError?.(new Error(`model.extraBody contains reserved key(s) [${extraBodyCollisions.join(", ")}] that core owns and sets itself — they are ignored. Remove them from extraBody.`), { phase: "config", sessionId });
583
631
  }
632
+ if (model.extraBody !== undefined && Object.prototype.hasOwnProperty.call(model.extraBody, "max_completion_tokens")) {
633
+ const e = new Error(`model.extraBody supplies "max_completion_tokens" — this spelling is engine-owned (the overflow retry ` +
634
+ `adjusts the per-request output cap, and a constant extraBody value would override that adjustment on ` +
635
+ `every retry). Set the cap via limits.maxOutputTokens (per request) or model.maxTokens instead.`);
636
+ e.code = "config.extra_body_output_cap";
637
+ throw e;
638
+ }
584
639
  const nestedStats = { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false };
585
640
  if (resume) {
586
641
  nestedStats.tokens = resume.seed.nestedStats.tokens;
@@ -958,9 +1013,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
958
1013
  const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
959
1014
  const inheritedAdmittedOrgScopes = liveAdmittedOrg === undefined ? seedAdmittedOrg : seedAdmittedOrg === undefined ? liveAdmittedOrg : liveAdmittedOrg.filter((s) => seedAdmittedOrg.includes(s));
960
1015
  const inheritedOrgGoverned = liveInheritedGate?.orgAdmissionGoverned === true || seedInheritedGate?.orgAdmissionGoverned === true;
961
- const priorOwnOrgVerdict = resume?.seed.inheritedGate?.ownAdmittedOrgScopes !== undefined
1016
+ const checkpointOwnOrgVerdict = resume?.seed.inheritedGate?.ownAdmittedOrgScopes !== undefined
962
1017
  ? { scopes: resume.seed.inheritedGate.ownAdmittedOrgScopes, writeScope: resume.seed.inheritedGate.ownAdmittedOrgWriteScope ?? null }
963
1018
  : undefined;
1019
+ const refOwnOrgVerdict = internals?.ownOrgAdmissionRef?.current;
1020
+ const priorOwnOrgVerdict = checkpointOwnOrgVerdict === undefined
1021
+ ? refOwnOrgVerdict
1022
+ : refOwnOrgVerdict === undefined
1023
+ ? checkpointOwnOrgVerdict
1024
+ : {
1025
+ scopes: checkpointOwnOrgVerdict.scopes.filter((sc) => refOwnOrgVerdict.scopes.includes(sc)),
1026
+ writeScope: checkpointOwnOrgVerdict.writeScope !== null && checkpointOwnOrgVerdict.writeScope === refOwnOrgVerdict.writeScope
1027
+ ? checkpointOwnOrgVerdict.writeScope
1028
+ : null,
1029
+ };
964
1030
  const orgGovernedProvenance = deps.memoryScopeAdmission !== undefined ||
965
1031
  (deps.deploymentMemoryScopes !== undefined && deps.deploymentMemoryScopes.length > 0) ||
966
1032
  deps.compliancePostureResolver !== undefined ||
@@ -1055,6 +1121,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1055
1121
  : {}),
1056
1122
  sessionId,
1057
1123
  ...(spec.backgroundScope !== undefined ? { backgroundScope: spec.backgroundScope } : {}),
1124
+ ...(resolvedInteractionPosture !== undefined ? { interactionPosture: resolvedInteractionPosture } : {}),
1058
1125
  ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
1059
1126
  ...(internals?.onTaskNotification ? { onTaskNotification: internals.onTaskNotification } : {}),
1060
1127
  ...(forwardEvent ? { forwardEvent } : {}),
@@ -1208,6 +1275,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1208
1275
  ...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
1209
1276
  principal: spec.principal,
1210
1277
  oneShot: spec.oneShot,
1278
+ ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1279
+ autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1211
1280
  workflowDepth: internals?.workflowDepth,
1212
1281
  parentCwd: taskRootPath,
1213
1282
  parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
@@ -1380,6 +1449,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1380
1449
  irreversibilityTier.set(axis.name, "always");
1381
1450
  irreversibleTools.add(axis.name);
1382
1451
  }
1452
+ if (axis.irreversibility === "never") {
1453
+ axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), irreversible: false });
1454
+ }
1455
+ if (axis.egress === false) {
1456
+ axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), egress: false });
1457
+ }
1383
1458
  if (axis.egress) {
1384
1459
  if (axis.effect !== undefined && axis.effect !== "write") {
1385
1460
  const e = new Error(`${protocolLabel} tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" — an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
@@ -1524,6 +1599,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1524
1599
  return memoryWriteGateRef.current?.(w);
1525
1600
  },
1526
1601
  mountBackgroundTaskTools: false,
1602
+ monitorToolActive: backgroundTaskToolsActive && !(toolFaceSnapshot.exclude?.includes("Monitor") ?? false),
1527
1603
  });
1528
1604
  {
1529
1605
  const bandNames = new Set(band.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
@@ -1535,6 +1611,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1535
1611
  }
1536
1612
  tools.push(...band.map((t) => firstPartyOffload(t)));
1537
1613
  const shellGate = effectiveShellGate;
1614
+ if (shellGate === "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
1615
+ deps.onError?.(new Error(`shell gate doctrine is "off" while a real writable shell (Bash) is mounted — no shell safety-axis ` +
1616
+ `fold applies to this run (commands are adjudicated by the ordinary policy/hook chain only). ` +
1617
+ `Set spec.shellGate to "classify" or "always" if this deployment expects doctrine-gated shell behavior.`), { phase: "config", sessionId, classification: "shell-gate-off" });
1618
+ }
1538
1619
  if (shellGate !== "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
1539
1620
  shellGatedBash = !egressTools.has("Bash") && !irreversibilityTier.has("Bash");
1540
1621
  irreversibilityTier.set("Bash", shellGate === "always" ? "always" : "maybe");
@@ -1655,10 +1736,79 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1655
1736
  if (offloadStore)
1656
1737
  tools.push(createReadToolResultTool(offloadStore));
1657
1738
  const onQuestion = frozenOnQuestion;
1739
+ const liveQuestionFace = isLiveQuestionFace(onQuestion) ? onQuestion : undefined;
1740
+ const contentAskBindings = new Map();
1741
+ const CONTENT_ASK_BINDING_CAP = 32;
1742
+ const contentAskRoutable = (toolCallId) => liveQuestionFace !== undefined &&
1743
+ mountedQuestionTool !== undefined &&
1744
+ tools.includes(mountedQuestionTool) &&
1745
+ runtimeCaps?.forceDurableGate !== true &&
1746
+ !inheritedUnavailableAsks.has(toolCallId);
1747
+ const lateStrandedAnswers = [];
1748
+ const discloseStrandedAnswers = (records, why) => {
1749
+ if (records.length === 0)
1750
+ return;
1751
+ try {
1752
+ deps.onError?.(new Error(`AskUserQuestion: ${records.length} question(s) were answered by a person but the call never ` +
1753
+ `executed to collect the answer (${records.map((r) => `${r.toolCallId} [delivery ${r.deliveryId}]`).join(", ")}) — ${why}. The answer(s) were NOT ` +
1754
+ `delivered to the model and are gone with this leg; re-ask if the decision is still needed.`), { phase: "degraded", sessionId, classification: "unconsumed-human-answer" });
1755
+ }
1756
+ catch {
1757
+ }
1758
+ };
1759
+ const settleContentAskBindings = () => {
1760
+ const stranded = [];
1761
+ for (const [callId, bound] of contentAskBindings) {
1762
+ if (bound.kind === "answered")
1763
+ stranded.push({ deliveryId: bound.deliveryId, toolCallId: callId });
1764
+ }
1765
+ contentAskBindings.clear();
1766
+ discloseStrandedAnswers(stranded, "the leg ended first (abort, batch teardown, or a loop failure)");
1767
+ const byDelivery = new Map();
1768
+ for (const r of [...stranded, ...lateStrandedAnswers])
1769
+ byDelivery.set(r.deliveryId, r);
1770
+ return [...byDelivery.values()];
1771
+ };
1658
1772
  const durableQuestionFace = resolveCheckpointStore(spec, deps) !== undefined &&
1659
1773
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
1660
- if (spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace)))
1661
- tools.push(createAskUserQuestionTool(onQuestion, { principal: spec.principal, sourceTaskId: sessionId }));
1774
+ const mountedQuestionFace = liveQuestionFace !== undefined
1775
+ ? async (req, signal) => {
1776
+ const bound = contentAskBindings.get(req.toolCallId);
1777
+ if (bound !== undefined) {
1778
+ if (bound.questionsHash === boundInputHashOf(req.questions)) {
1779
+ contentAskBindings.delete(req.toolCallId);
1780
+ if (bound.kind === "answered")
1781
+ return bound.answer;
1782
+ throw bound.error;
1783
+ }
1784
+ contentAskBindings.delete(req.toolCallId);
1785
+ }
1786
+ return liveQuestionFace(req, signal);
1787
+ }
1788
+ : onQuestion;
1789
+ const questionToolMounted = spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace));
1790
+ let mountedQuestionTool;
1791
+ if (questionToolMounted)
1792
+ tools.push((mountedQuestionTool =
1793
+ createAskUserQuestionTool(mountedQuestionFace, { principal: spec.principal, sourceTaskId: sessionId }, {
1794
+ ...(resume?.redeemedContentAskCallId !== undefined
1795
+ ? {
1796
+ redeemedApprovalCallId: resume.redeemedContentAskCallId,
1797
+ ...(resume.redeemedContentAskQuestionsHash !== undefined ? { redeemedApprovalQuestionsHash: resume.redeemedContentAskQuestionsHash } : {}),
1798
+ }
1799
+ : {}),
1800
+ ...(resolvedInteractionPosture !== undefined ? { posture: resolvedInteractionPosture } : {}),
1801
+ ...(spec.interactiveQuestionFallback === true ? { interactiveFallback: true } : {}),
1802
+ onSyntheticContinuation: ({ questionId, reason }) => {
1803
+ deps.onError?.(new Error(`AskUserQuestion ${questionId}: no human answer was obtainable (` +
1804
+ (reason === "seam_absent"
1805
+ ? "no onQuestion seam is wired"
1806
+ : reason === "declined_unavailable"
1807
+ ? "the wired question channel reported nobody was reachable"
1808
+ : "the wired question channel failed") +
1809
+ `) — the model was instructed to self-answer and the run CONTINUES (warning, not a failure).`), { phase: "degraded", sessionId, classification: "no-human-autoanswered" });
1810
+ },
1811
+ })));
1662
1812
  if (spec.handsReadOnly !== true) {
1663
1813
  const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
1664
1814
  tools.push(...createSchedulerTools(executionEnv, {
@@ -1702,6 +1852,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1702
1852
  sessionId,
1703
1853
  taskRootPath,
1704
1854
  memoryWriteGateRef,
1855
+ writeToolsMounted: handsEnabled && spec.handsReadOnly !== true && !(toolFaceSnapshot.exclude?.includes("Write") ?? false),
1705
1856
  admissionCtx: {
1706
1857
  orgMemoryDenied: complianceDenies.has("org_memory_mount"),
1707
1858
  complianceDegraded,
@@ -1721,6 +1872,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1721
1872
  });
1722
1873
  memoryAdmittedOrgScopesRef.current = memoryAdmittedOrgScopes;
1723
1874
  ownOrgVerdictRef.current = memoryOwnOrgVerdict ?? priorOwnOrgVerdict;
1875
+ if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
1876
+ internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
1877
+ }
1724
1878
  let memoryBlock = memoryBlockFromEngine;
1725
1879
  if (memorySeedFiles?.length && seedContextFiles) {
1726
1880
  try {
@@ -2117,6 +2271,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2117
2271
  model,
2118
2272
  deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
2119
2273
  alwaysLoadNames: [
2274
+ ASK_USER_QUESTION_TOOL_NAME,
2120
2275
  ...(toolFaceSnapshot.alwaysLoad ?? []),
2121
2276
  ...mcp.tools
2122
2277
  .filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
@@ -2136,6 +2291,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2136
2291
  .map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
2137
2292
  let toolsDeltaRef;
2138
2293
  let toolMaterializeStatic = false;
2294
+ const staticFaceForRef = {};
2139
2295
  if (deferred.size > 0 || failedMcpServers.length > 0) {
2140
2296
  toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
2141
2297
  }
@@ -2147,6 +2303,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2147
2303
  throw e;
2148
2304
  }
2149
2305
  const registry = buildDeferredRegistry(deferred, tools);
2306
+ const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
2307
+ if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
2308
+ const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
2309
+ e.code = "config.tool_materialize_invalid";
2310
+ throw e;
2311
+ }
2312
+ const requestedStrategy = spec.toolMaterializeStrategy ?? envStrategy ?? "swap";
2313
+ const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
2314
+ const materializeStatic = requestedStrategy === "static" && !laneDegrade;
2315
+ toolMaterializeStatic = materializeStatic;
2316
+ promptManifest.toolDisclosure = {
2317
+ deferredTools: deferred.size,
2318
+ strategy: materializeStatic ? "static" : "swap",
2319
+ source: laneDegrade ? "degraded_no_direct_lane" : spec.toolMaterializeStrategy !== undefined ? "spec" : envStrategy !== undefined ? "env" : "default",
2320
+ };
2321
+ const everExempt = new Set();
2322
+ const staticFaceFor = (name) => {
2323
+ if (!materializeStatic)
2324
+ return false;
2325
+ if (everExempt.has(name))
2326
+ return false;
2327
+ if (staticSchemaRenderable(tools.find((t) => t.name === name)?.parameters))
2328
+ return true;
2329
+ everExempt.add(name);
2330
+ return false;
2331
+ };
2332
+ staticFaceForRef.current = staticFaceFor;
2150
2333
  let activationChain = Promise.resolve();
2151
2334
  const serializeActivation = (section) => {
2152
2335
  const p = activationChain.then(section);
@@ -2168,6 +2351,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2168
2351
  };
2169
2352
  },
2170
2353
  ...(executionMode !== undefined ? { executionMode } : {}),
2354
+ staticFace: () => staticFaceFor(name),
2171
2355
  activate: async () => serializeActivation(async () => {
2172
2356
  if (activeTools.has(name))
2173
2357
  return undefined;
@@ -2212,16 +2396,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2212
2396
  };
2213
2397
  offloadReachableToolsRef.current = callableToolNames;
2214
2398
  let toolSearch;
2215
- const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
2216
- if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
2217
- const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
2218
- e.code = "config.tool_materialize_invalid";
2219
- throw e;
2220
- }
2221
- const materializeStatic = (spec.toolMaterializeStrategy ?? envStrategy ?? "static") === "static" && spec.deferSelfResolve !== false;
2222
- toolMaterializeStatic = materializeStatic;
2223
2399
  const buildToolList = (active) => {
2224
- const list = tools.map((t) => (deferred.has(t.name) && (materializeStatic || !active.has(t.name)) ? placeholders.get(t.name) : t));
2400
+ const list = tools.map((t) => (deferred.has(t.name) && (staticFaceFor(t.name) || !active.has(t.name)) ? placeholders.get(t.name) : t));
2225
2401
  list.push(toolSearch);
2226
2402
  return list;
2227
2403
  };
@@ -2262,8 +2438,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2262
2438
  listingRide: (newly) => listingRideRef.current?.(newly),
2263
2439
  mountedNames: callableToolNames,
2264
2440
  directCallEnabled: spec.deferSelfResolve !== false,
2441
+ isMounted: (name) => tools.some((t) => t.name === name),
2265
2442
  ...(materializeStatic
2266
- ? { staticSchemaFor: (name) => tools.find((t) => t.name === name)?.parameters }
2443
+ ? { staticSchemaFor: (name) => (staticFaceFor(name) ? tools.find((t) => t.name === name)?.parameters : undefined) }
2267
2444
  : {}),
2268
2445
  serializeActivation,
2269
2446
  });
@@ -2598,6 +2775,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2598
2775
  ? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName }
2599
2776
  : {}),
2600
2777
  });
2778
+ const riskAxesOf = (toolName) => {
2779
+ const tier = irreversibilityTier.get(toolName);
2780
+ const negatives = axisExplicitNegatives.get(toolName);
2781
+ const irreversible = tier === "always" || tier === "maybe" ? true : tier === "never" ? false : negatives?.irreversible;
2782
+ const egress = egressTools.has(toolName) ? true : negatives?.egress;
2783
+ if (irreversible === undefined && egress === undefined)
2784
+ return {};
2785
+ return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
2786
+ };
2601
2787
  const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal) => {
2602
2788
  let editArgs = edit;
2603
2789
  for (let round = 0;; round++) {
@@ -2630,6 +2816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2630
2816
  args: editArgs,
2631
2817
  message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
2632
2818
  ...askSourceIdentity(),
2819
+ ...riskAxesOf(creq.toolName),
2633
2820
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2634
2821
  }, onAskOf, csignal ?? abortController.signal);
2635
2822
  if (rr.action !== "allow")
@@ -2654,8 +2841,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2654
2841
  return first;
2655
2842
  if (first.action === "allow")
2656
2843
  return { action: "allow" };
2657
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2844
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
2658
2845
  return first;
2846
+ }
2659
2847
  if (pc.durableMandate === true) {
2660
2848
  if (resolveCheckpointStore(spec, deps) !== undefined &&
2661
2849
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
@@ -2676,6 +2864,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2676
2864
  args: presentedArgs,
2677
2865
  message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
2678
2866
  ...askSourceIdentity(),
2867
+ ...riskAxesOf(creq.toolName),
2679
2868
  ...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2680
2869
  }, pc.onAsk, csignal ?? abortController.signal);
2681
2870
  const askWaitMs = Math.max(0, now() - askT0);
@@ -2710,8 +2899,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2710
2899
  }
2711
2900
  if (decision.action !== "ask")
2712
2901
  return decision;
2713
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2902
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
2714
2903
  return decision;
2904
+ }
2715
2905
  if (pc.durableMandate === true) {
2716
2906
  if (resolveCheckpointStore(spec, deps) !== undefined &&
2717
2907
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
@@ -2732,6 +2922,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2732
2922
  args: presentedArgs,
2733
2923
  message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
2734
2924
  ...askSourceIdentity(),
2925
+ ...riskAxesOf(creq.toolName),
2735
2926
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2736
2927
  }, pc.onAsk, csignal ?? abortController.signal);
2737
2928
  const askWaitMs = Math.max(0, now() - askT0);
@@ -2917,6 +3108,42 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2917
3108
  };
2918
3109
  })();
2919
3110
  const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
3111
+ const checkpointStore = resolveCheckpointStore(spec, deps);
3112
+ const durableApproval = spec.durableApproval ??
3113
+ (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
3114
+ const wiringQuestionSeam = resolveQuestionSeam(spec, deps);
3115
+ const wiringQuestionStripped = internals?.questionFaceStripped === true;
3116
+ const wiringAskSeam = resolveAskSeamForm(spec, deps);
3117
+ const wiringManifest = deriveWiringManifest({
3118
+ half: "effective",
3119
+ leg: resume !== undefined ? "resume" : internals?.isDelegatedChild === true ? "child" : "root",
3120
+ askForm: wiringAskSeam.form,
3121
+ ...(wiringAskSeam.provenance !== undefined ? { askProvenance: wiringAskSeam.provenance } : {}),
3122
+ questionWired: wiringQuestionSeam.wired,
3123
+ ...(wiringQuestionSeam.provenance !== undefined ? { questionProvenance: wiringQuestionSeam.provenance } : {}),
3124
+ ...(wiringQuestionStripped ? { questionStrippedByEngine: true } : {}),
3125
+ ...(resolvedInteractionPosture !== undefined ? { interactionPosture: resolvedInteractionPosture } : {}),
3126
+ ...(spec.interactiveTools === true && !wiringQuestionSeam.wired && !durableQuestionFace && !wiringQuestionStripped
3127
+ ? { interactiveToolsWithoutDeliveryFace: true }
3128
+ : {}),
3129
+ elicitSeamWired: resolveElicitSeam(deps),
3130
+ elicitServersOptedIn: countElicitOptIns(spec),
3131
+ parkCapable: checkpointStore !== undefined,
3132
+ parkDurableApprovalOptIn: spec.durableApproval !== undefined,
3133
+ parkForceDurableGate: runtimeCaps?.forceDurableGate === true,
3134
+ parkSafetyVocabularyArmed: irreversibleTools.size > 0 || egressTools.size > 0,
3135
+ ...(checkpointStore !== undefined ? { checkpointDurability: resolveDeclaredDurability(checkpointStore, "checkpointStore") } : {}),
3136
+ sessionDurability: resolveDeclaredDurability(sessions, "sessionStore"),
3137
+ backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
3138
+ hostChildEventSinkWired: deps.onBackgroundChildEvent !== undefined,
3139
+ lockedConfigWired: deps.lockedConfig !== undefined,
3140
+ complianceWired: deps.compliancePostureResolver !== undefined,
3141
+ memoryAdmissionWired: deps.memoryScopeAdmission !== undefined,
3142
+ retentionPolicyWired: deps.retentionPolicy !== undefined,
3143
+ });
3144
+ const parkLaneArmed = wiringManifest.parkLane.effective === true;
3145
+ const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
3146
+ const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
2920
3147
  if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
2921
3148
  const adjudicate = effectivePolicy
2922
3149
  ? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot }, abortController.signal)), abortController.signal, () => ({
@@ -2998,6 +3225,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2998
3225
  })(),
2999
3226
  message: decision.message ?? `approval required for "${req.toolName}"`,
3000
3227
  ...askSourceIdentity(),
3228
+ ...riskAxesOf(req.toolName),
3001
3229
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
3002
3230
  }, onAsk, abortController.signal);
3003
3231
  const waitMs = Math.max(0, now() - t0);
@@ -3037,9 +3265,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3037
3265
  catch {
3038
3266
  }
3039
3267
  };
3040
- const checkpointStore = resolveCheckpointStore(spec, deps);
3041
- const durableApproval = spec.durableApproval ??
3042
- (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
3043
3268
  const inFlightSpendMicroUsd = () => {
3044
3269
  const own = liveSpendRef.get?.().costMicroUsd ?? 0;
3045
3270
  const seededNested = resume?.seed.nestedStats.costMicroUsd;
@@ -3078,6 +3303,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3078
3303
  return live.length > 0 ? live : undefined;
3079
3304
  })(),
3080
3305
  pendingSteer: undefined,
3306
+ pendingSteerQueue: undefined,
3081
3307
  inheritedGate: (() => {
3082
3308
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
3083
3309
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
@@ -3325,12 +3551,92 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3325
3551
  return true;
3326
3552
  }
3327
3553
  : undefined;
3328
- const suspendAsk = checkpointStore && (durableApproval || irreversibleTools.size > 0 || egressTools.size > 0)
3329
- ? async (req, postHookArgs, safety, approverUnavailable) => {
3330
- if (onAsk !== undefined &&
3554
+ const resolveContentAsk = async (req) => {
3555
+ if (!contentAskRoutable(req.toolCallId) || liveQuestionFace === undefined || mountedQuestionTool === undefined) {
3556
+ return { kind: "unavailable", parkDeclined: false };
3557
+ }
3558
+ const deliveryId = uuidv7();
3559
+ const bindOutcome = (entry, questionsHash) => {
3560
+ if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId))
3561
+ return;
3562
+ contentAskBindings.set(req.toolCallId, { ...entry, questionsHash, deliveryId });
3563
+ };
3564
+ const refuseBeforeDelivery = (code, presentedInput) => ({
3565
+ kind: "delivery_failure",
3566
+ code,
3567
+ ...(presentedInput !== undefined ? { presentedInput } : {}),
3568
+ });
3569
+ let retained;
3570
+ let presented;
3571
+ try {
3572
+ retained = structuredClone(req.args);
3573
+ presented = structuredClone(retained.questions);
3574
+ }
3575
+ catch {
3576
+ return refuseBeforeDelivery("question.malformed_request");
3577
+ }
3578
+ const retainedQuestionsHash = boundInputHashOf(retained.questions);
3579
+ if (!Value.Check(mountedQuestionTool.parameters, retained) || validateAskQuestions(presented) !== undefined) {
3580
+ return refuseBeforeDelivery("question.malformed_request", retained);
3581
+ }
3582
+ if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId)) {
3583
+ return { kind: "unavailable", parkDeclined: true };
3584
+ }
3585
+ if (abortController.signal.aborted) {
3586
+ return { kind: "delivery_failure", code: "question.aborted", presentedInput: retained };
3587
+ }
3588
+ try {
3589
+ const facePromise = (async () => liveQuestionFace({
3590
+ toolCallId: req.toolCallId,
3591
+ questions: presented,
3592
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
3593
+ sourceTaskId: sessionId,
3594
+ boundInputHash: boundInputHashOf(retained),
3595
+ deliveryId,
3596
+ }, abortController.signal))();
3597
+ const settlement = await raceAbort(facePromise.then((outcome) => ({ tag: "outcome", outcome }), (error) => ({ tag: "threw", error })), abortController.signal, () => ({ tag: "aborted" }));
3598
+ if (settlement.tag === "aborted") {
3599
+ void facePromise.then((late) => {
3600
+ if (classifyQuestionOutcome(late).shape !== "answered")
3601
+ return;
3602
+ lateStrandedAnswers.push({ deliveryId, toolCallId: req.toolCallId });
3603
+ discloseStrandedAnswers([{ deliveryId, toolCallId: req.toolCallId }], "the task had already ended when the answer arrived");
3604
+ }, () => undefined).catch(() => undefined);
3605
+ }
3606
+ if (settlement.tag === "outcome") {
3607
+ const outcome = settlement.outcome;
3608
+ const reading = classifyQuestionOutcome(outcome);
3609
+ if (reading.shape === "contradictory") {
3610
+ bindOutcome({ kind: "failed", error: new Error("the question channel returned a contradictory outcome (an unavailable answer)") }, retainedQuestionsHash);
3611
+ return { kind: "delivery_failure", code: "question.human_channel_failed", presentedInput: retained };
3612
+ }
3613
+ if (reading.shape === "unavailable")
3614
+ return { kind: "unavailable", parkDeclined: true, presentedInput: retained };
3615
+ const answerSnapshot = reading.answer;
3616
+ bindOutcome({ kind: "answered", answer: answerSnapshot }, retainedQuestionsHash);
3617
+ return { kind: "answered", presentedInput: retained };
3618
+ }
3619
+ const error = settlement.tag === "threw"
3620
+ ? settlement.error
3621
+ : Object.assign(new Error("the task ended while this question was still out"), { name: "AbortError" });
3622
+ bindOutcome({ kind: "failed", error }, retainedQuestionsHash);
3623
+ return {
3624
+ kind: "delivery_failure",
3625
+ code: settlement.tag === "aborted" || abortController.signal.aborted ? "question.aborted" : "question.human_channel_failed",
3626
+ presentedInput: retained,
3627
+ };
3628
+ }
3629
+ catch (err) {
3630
+ bindOutcome({ kind: "failed", error: err }, retainedQuestionsHash);
3631
+ return { kind: "delivery_failure", code: "question.human_channel_failed", presentedInput: retained };
3632
+ }
3633
+ };
3634
+ const suspendAsk = parkLaneArmed && checkpointStore !== undefined
3635
+ ? async (req, postHookArgs, safety, liveFaceUnavailable) => {
3636
+ const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : onAsk !== undefined;
3637
+ if (syncFirstEligible &&
3331
3638
  runtimeCaps?.forceDurableGate !== true &&
3332
- approverUnavailable !== true &&
3333
- req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
3639
+ liveFaceUnavailable !== true &&
3334
3640
  !inheritedUnavailableAsks.has(req.toolCallId)) {
3335
3641
  return undefined;
3336
3642
  }
@@ -3519,9 +3825,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3519
3825
  onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: spec.taskId ?? sessionId, site: f.site, message: f.error.message, ts: Date.now() })),
3520
3826
  event: e,
3521
3827
  preToolUse: hooks?.preToolUse,
3828
+ ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
3522
3829
  adjudicate,
3523
3830
  resolveAsk: resolveAskBound,
3524
3831
  suspendAsk,
3832
+ resolveContentAsk,
3525
3833
  egress: egressTools.has(e.toolName),
3526
3834
  irreversibility: irreversibilityTier.get(e.toolName),
3527
3835
  reversibilityProbe: reversibilityProbes.get(e.toolName),
@@ -3574,7 +3882,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3574
3882
  isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
3575
3883
  content: e.content.map((c) => ({ ...c })),
3576
3884
  details: clonedDetails,
3577
- }, { toolCallId: e.toolCallId, toolName: e.toolName });
3885
+ }, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}) });
3578
3886
  if (patch?.additionalContext) {
3579
3887
  content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext) }];
3580
3888
  changed = true;
@@ -3582,7 +3890,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3582
3890
  }
3583
3891
  }
3584
3892
  else if (hooks?.postToolUse) {
3585
- const patch = await hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName });
3893
+ const patch = await hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}) });
3586
3894
  if (patch?.updatedOutput) {
3587
3895
  content = patch.updatedOutput;
3588
3896
  changed = true;
@@ -3831,7 +4139,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3831
4139
  : undefined;
3832
4140
  overheadState.promptChars = systemPrompt.length;
3833
4141
  const preparedHolder = {};
3834
- 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 } : {}), toolMaterializeStatic, 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 } : {}) });
4142
+ 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, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, ...(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 } : {}) });
3835
4143
  const prepared = buildPrepared();
3836
4144
  preparedHolder.current = prepared;
3837
4145
  return prepared;