@sema-agent/core 5.12.0 → 5.14.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 (101) hide show
  1. package/CHANGELOG.md +320 -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 +137 -43
  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/open-responses.js +24 -10
  9. package/dist/brain/openai.js +29 -11
  10. package/dist/brain/request-params.d.ts +2 -0
  11. package/dist/brain/request-params.js +16 -0
  12. package/dist/brain/stream-engine.d.ts +9 -1
  13. package/dist/brain/stream-engine.js +256 -27
  14. package/dist/brain/timeout.d.ts +1 -0
  15. package/dist/brain/timeout.js +1 -0
  16. package/dist/core/a2a.d.ts +2 -2
  17. package/dist/core/a2a.js +3 -3
  18. package/dist/core/ask-question.d.ts +47 -2
  19. package/dist/core/ask-question.js +209 -28
  20. package/dist/core/background-agent-store.d.ts +2 -0
  21. package/dist/core/checkpoint-store.d.ts +49 -18
  22. package/dist/core/checkpoint-store.js +117 -4
  23. package/dist/core/compliance.d.ts +11 -0
  24. package/dist/core/compliance.js +34 -0
  25. package/dist/core/governance-codes.d.ts +12 -0
  26. package/dist/core/governance-codes.js +24 -0
  27. package/dist/core/hooks.d.ts +24 -2
  28. package/dist/core/hooks.js +97 -10
  29. package/dist/core/human-input-projection.d.ts +12 -0
  30. package/dist/core/human-input-projection.js +27 -0
  31. package/dist/core/locked-config.d.ts +27 -0
  32. package/dist/core/locked-config.js +42 -0
  33. package/dist/core/mcp.d.ts +7 -2
  34. package/dist/core/mcp.js +7 -7
  35. package/dist/core/memory-admission.d.ts +51 -0
  36. package/dist/core/memory-admission.js +159 -0
  37. package/dist/core/memory.d.ts +2 -0
  38. package/dist/core/memory.js +3 -2
  39. package/dist/core/retention.d.ts +36 -0
  40. package/dist/core/retention.js +31 -0
  41. package/dist/core/runner/assemble-result.d.ts +1 -0
  42. package/dist/core/runner/assemble-result.js +2 -2
  43. package/dist/core/runner/prepare-memory.d.ts +9 -0
  44. package/dist/core/runner/prepare-memory.js +28 -2
  45. package/dist/core/runner/prepare-task.d.ts +18 -6
  46. package/dist/core/runner/prepare-task.js +428 -38
  47. package/dist/core/runner/runtask.d.ts +3 -6
  48. package/dist/core/runner/runtask.js +258 -77
  49. package/dist/core/runner/tool-output-projection.js +1 -0
  50. package/dist/core/session-store.d.ts +4 -0
  51. package/dist/core/session-store.js +5 -0
  52. package/dist/core/session.d.ts +1 -0
  53. package/dist/core/store-contracts/background-agent-store-contract.js +19 -0
  54. package/dist/core/store-contracts/checkpoint-store-contract.js +62 -3
  55. package/dist/core/task-notification.d.ts +2 -0
  56. package/dist/core/task-notification.js +5 -3
  57. package/dist/core/task-registry-agent.d.ts +1 -0
  58. package/dist/core/task-registry-agent.js +6 -0
  59. package/dist/core/task-registry.d.ts +1 -0
  60. package/dist/core/task-registry.js +4 -1
  61. package/dist/core/tool-policy.d.ts +5 -0
  62. package/dist/core/tool-policy.js +2 -1
  63. package/dist/core/tool-result-store.d.ts +2 -0
  64. package/dist/core/tool-result-store.js +1 -0
  65. package/dist/core/types.d.ts +38 -2
  66. package/dist/core/wiring-manifest.d.ts +97 -0
  67. package/dist/core/wiring-manifest.js +186 -0
  68. package/dist/engine/compaction/compaction.js +2 -2
  69. package/dist/engine/harness/agent-harness.d.ts +2 -1
  70. package/dist/engine/harness/agent-harness.js +19 -2
  71. package/dist/engine/harness/types.d.ts +3 -1
  72. package/dist/engine/llm/types.d.ts +7 -0
  73. package/dist/engine/llm/types.js +8 -1
  74. package/dist/engine/llm/validation.js +11 -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 +12 -4
  80. package/dist/index.js +12 -4
  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/stores/file/checkpoint-store.d.ts +3 -5
  92. package/dist/stores/file/checkpoint-store.js +31 -2
  93. package/dist/stores/file/index.js +1 -1
  94. package/dist/stores/file/session-store.d.ts +3 -1
  95. package/dist/stores/file/session-store.js +2 -2
  96. package/dist/stores/file/shared-ledger.js +8 -1
  97. package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -0
  98. package/dist/tools/fs/bash-readonly-classifier.js +94 -0
  99. package/dist/tools/fs/fs-bash.js +31 -12
  100. package/dist/tools/fs/safety.js +34 -10
  101. 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";
@@ -38,6 +40,10 @@ import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, S
38
40
  import { compileOutputSchema } from "./strict-output-schema.js";
39
41
  import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, } from "./tool-disclosure.js";
40
42
  import { composeMemoryBlock } from "../memory.js";
43
+ import { preflightLockedConfig } from "../locked-config.js";
44
+ import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
45
+ import { assertRetentionCapability } from "../retention.js";
46
+ import { foldAdmissionFreeze } from "../memory-admission.js";
41
47
  import { prepareMemory } from "./prepare-memory.js";
42
48
  import { defaultPromptProvider, buildEnvironmentContext, buildGitSnapshot, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
43
49
  import { assemblePrompt } from "../../prompt-assembly/assemble.js";
@@ -60,7 +66,7 @@ import { createMonitorTool } from "../../tools/monitor.js";
60
66
  import { createWorktreeTools } from "../../tools/worktree.js";
61
67
  import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, seedReadFileStateFromTranscript, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
62
68
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
63
- 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";
64
70
  import { createSchedulerTools } from "../../tools/scheduler-tools.js";
65
71
  import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
66
72
  import { isSelfOrchestrationActive, selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
@@ -69,8 +75,9 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
69
75
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
70
76
  import { resolveKey } from "../../tools/fs/safety.js";
71
77
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
72
- import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, 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";
73
79
  import { boundInputHashOf } from "../canonical-json.js";
80
+ import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
74
81
  import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
75
82
  const RECONCILE_MAX_RETRIES = 3;
76
83
  const DEFAULT_MAX_SUSPENDS = 5;
@@ -173,11 +180,7 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
173
180
  export function checkpointScopeOf(spec) {
174
181
  return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
175
182
  }
176
- export function resolveCheckpointStore(spec, deps) {
177
- if (spec.checkpointStore === null)
178
- return undefined;
179
- return spec.checkpointStore ?? deps.checkpointStore;
180
- }
183
+ export { resolveCheckpointStore } from "../checkpoint-store.js";
181
184
  export function isFableFamilyModelId(id) {
182
185
  const tail = id.toLowerCase().split("/").pop() ?? "";
183
186
  return /^claude-fable-\d/.test(tail) || /^claude-mythos-5(?!\d)/.test(tail);
@@ -337,6 +340,64 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
337
340
  e.code = "config.empty_objective";
338
341
  throw e;
339
342
  }
343
+ const lockedPreflight = preflightLockedConfig(spec, deps);
344
+ assertRetentionCapability({
345
+ policy: deps.retentionPolicy,
346
+ locked: lockedPreflight.lockedKeys.has("retentionPolicy"),
347
+ stores: [
348
+ { name: "sessionStore", store: sessions },
349
+ { name: "checkpointStore", store: resolveCheckpointStore(spec, deps) },
350
+ { name: "toolResultStore", store: deps.toolResultStore },
351
+ ],
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
+ }
340
401
  resolveTaskLimits(spec.limits);
341
402
  if (spec.resourceSuspend !== undefined) {
342
403
  const rsus = spec.resourceSuspend;
@@ -378,6 +439,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
378
439
  const egressTools = new Set();
379
440
  const irreversibleTools = new Set();
380
441
  const irreversibilityTier = new Map();
442
+ const axisExplicitNegatives = new Map();
381
443
  const reversibilityProbes = new Map();
382
444
  let shellGatedBash = false;
383
445
  let shellGatedMonitor = false;
@@ -940,9 +1002,35 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
940
1002
  ? liveShellGate
941
1003
  : seedShellGate;
942
1004
  const inheritedParentConstraints = liveInheritedGate?.parentConstraints;
1005
+ const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
1006
+ const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
1007
+ const inheritedAdmittedOrgScopes = liveAdmittedOrg === undefined ? seedAdmittedOrg : seedAdmittedOrg === undefined ? liveAdmittedOrg : liveAdmittedOrg.filter((s) => seedAdmittedOrg.includes(s));
1008
+ const inheritedOrgGoverned = liveInheritedGate?.orgAdmissionGoverned === true || seedInheritedGate?.orgAdmissionGoverned === true;
1009
+ const checkpointOwnOrgVerdict = resume?.seed.inheritedGate?.ownAdmittedOrgScopes !== undefined
1010
+ ? { scopes: resume.seed.inheritedGate.ownAdmittedOrgScopes, writeScope: resume.seed.inheritedGate.ownAdmittedOrgWriteScope ?? null }
1011
+ : undefined;
1012
+ const refOwnOrgVerdict = internals?.ownOrgAdmissionRef?.current;
1013
+ const priorOwnOrgVerdict = checkpointOwnOrgVerdict === undefined
1014
+ ? refOwnOrgVerdict
1015
+ : refOwnOrgVerdict === undefined
1016
+ ? checkpointOwnOrgVerdict
1017
+ : {
1018
+ scopes: checkpointOwnOrgVerdict.scopes.filter((sc) => refOwnOrgVerdict.scopes.includes(sc)),
1019
+ writeScope: checkpointOwnOrgVerdict.writeScope !== null && checkpointOwnOrgVerdict.writeScope === refOwnOrgVerdict.writeScope
1020
+ ? checkpointOwnOrgVerdict.writeScope
1021
+ : null,
1022
+ };
1023
+ const orgGovernedProvenance = deps.memoryScopeAdmission !== undefined ||
1024
+ (deps.deploymentMemoryScopes !== undefined && deps.deploymentMemoryScopes.length > 0) ||
1025
+ deps.compliancePostureResolver !== undefined ||
1026
+ inheritedOrgGoverned ||
1027
+ priorOwnOrgVerdict !== undefined;
943
1028
  const specShellGate = spec.shellGate ?? "off";
944
1029
  const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
945
1030
  const ownSessionRulesRef = {};
1031
+ const memoryAdmittedOrgScopesRef = { current: [] };
1032
+ const ownOrgVerdictRef = { current: undefined };
1033
+ const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
946
1034
  const frozenOnAsk = spec.onAsk ?? deps.onAsk;
947
1035
  const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
948
1036
  const inheritedGateForChildren = () => {
@@ -950,7 +1038,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
950
1038
  ...(inheritedAncestorRules ?? []),
951
1039
  ...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
952
1040
  ];
953
- const ownCallerPolicy = spec.toolPolicy ?? deps.toolPolicy;
1041
+ const ownCallerPolicy = lockedPreflight.toolPolicy;
954
1042
  const durableMandate = runtimeCaps?.forceDurableGate === true || (spec.durableApproval !== undefined && frozenOnAsk === undefined);
955
1043
  const parentConstraints = [
956
1044
  ...(inheritedParentConstraints ?? []),
@@ -968,6 +1056,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
968
1056
  ...(ancestorRules.length > 0 ? { ancestorRules } : {}),
969
1057
  ...(effectiveShellGate !== "off" ? { shellGate: effectiveShellGate } : {}),
970
1058
  ...(parentConstraints.length > 0 ? { parentConstraints } : {}),
1059
+ admittedOrgScopes: memoryAdmittedOrgScopesRef.current,
1060
+ ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
971
1061
  };
972
1062
  };
973
1063
  let agentForkDenial;
@@ -1024,6 +1114,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1024
1114
  : {}),
1025
1115
  sessionId,
1026
1116
  ...(spec.backgroundScope !== undefined ? { backgroundScope: spec.backgroundScope } : {}),
1117
+ ...(resolvedInteractionPosture !== undefined ? { interactionPosture: resolvedInteractionPosture } : {}),
1027
1118
  ...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
1028
1119
  ...(internals?.onTaskNotification ? { onTaskNotification: internals.onTaskNotification } : {}),
1029
1120
  ...(forwardEvent ? { forwardEvent } : {}),
@@ -1072,6 +1163,39 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1072
1163
  runtimeCaps = { allowWorkflows: false, allowFork: false };
1073
1164
  }
1074
1165
  }
1166
+ let complianceDenies = new Set();
1167
+ let complianceDegraded = false;
1168
+ if (deps.compliancePostureResolver) {
1169
+ try {
1170
+ const posture = (await deps.compliancePostureResolver(spec.principal)) ?? undefined;
1171
+ if (posture !== undefined)
1172
+ complianceDenies = resolveComplianceDenies(posture);
1173
+ }
1174
+ catch (err) {
1175
+ deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
1176
+ complianceDenies = new Set(COMPLIANCE_CAPABILITIES);
1177
+ complianceDegraded = true;
1178
+ }
1179
+ const complianceRefuse = (capability, requested) => {
1180
+ const e = new Error(`${requested} is denied for this principal by the compliance posture (capability "${capability}"` +
1181
+ `${complianceDegraded ? "; the posture resolver is currently failing, so every managed capability is denied fail-closed" : ""}) — ` +
1182
+ `the task is refused rather than silently narrowed.`);
1183
+ e.code = complianceDegraded ? "config.compliance_required" : "config.compliance_denied";
1184
+ throw e;
1185
+ };
1186
+ if (complianceDenies.has("mcp_servers") && lockedPreflight.mcp?.length) {
1187
+ complianceRefuse("mcp_servers", "TaskSpec.mcp (MCP server materialization)");
1188
+ }
1189
+ if (complianceDenies.has("workflows") && spec.selfOrchestration === true) {
1190
+ complianceRefuse("workflows", "TaskSpec.selfOrchestration (workflow self-orchestration)");
1191
+ }
1192
+ if (complianceDenies.has("web_fetch")) {
1193
+ const webTool = (spec.tools ?? []).find((t) => t.name === WEB_FETCH_TOOL_NAME || (t.aliases ?? []).includes(WEB_FETCH_TOOL_NAME));
1194
+ if (webTool !== undefined) {
1195
+ complianceRefuse("web_fetch", `TaskSpec.tools["${webTool.name}"] (the WebFetch tool face)`);
1196
+ }
1197
+ }
1198
+ }
1075
1199
  agentForkDenial = forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork);
1076
1200
  observersActive = runtimeCaps?.allowObservers === true;
1077
1201
  if (runtimeCaps?.autoMode === true && deps.autoMode !== undefined) {
@@ -1144,6 +1268,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1144
1268
  ...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
1145
1269
  principal: spec.principal,
1146
1270
  oneShot: spec.oneShot,
1271
+ ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1272
+ autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1147
1273
  workflowDepth: internals?.workflowDepth,
1148
1274
  parentCwd: taskRootPath,
1149
1275
  parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
@@ -1220,8 +1346,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1220
1346
  }
1221
1347
  tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
1222
1348
  }
1223
- mcp = spec.mcp?.length
1224
- ? await materializeMcpTools(spec.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer)
1349
+ mcp = lockedPreflight.mcp?.length
1350
+ ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer)
1225
1351
  : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
1226
1352
  for (const w of mcp.warnings)
1227
1353
  deps.onError?.(w, { phase: "mcp", sessionId });
@@ -1241,7 +1367,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1241
1367
  tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
1242
1368
  const rebuildHarnessToolsRef = {};
1243
1369
  const toolCallGateArmedRef = { armed: false };
1244
- if (spec.mcp?.length) {
1370
+ if (lockedPreflight.mcp?.length) {
1245
1371
  tools.push({
1246
1372
  name: "RefreshMcpTools",
1247
1373
  label: "RefreshMcpTools",
@@ -1316,6 +1442,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1316
1442
  irreversibilityTier.set(axis.name, "always");
1317
1443
  irreversibleTools.add(axis.name);
1318
1444
  }
1445
+ if (axis.irreversibility === "never") {
1446
+ axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), irreversible: false });
1447
+ }
1448
+ if (axis.egress === false) {
1449
+ axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), egress: false });
1450
+ }
1319
1451
  if (axis.egress) {
1320
1452
  if (axis.effect !== undefined && axis.effect !== "write") {
1321
1453
  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".`);
@@ -1471,6 +1603,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1471
1603
  }
1472
1604
  tools.push(...band.map((t) => firstPartyOffload(t)));
1473
1605
  const shellGate = effectiveShellGate;
1606
+ if (shellGate === "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
1607
+ deps.onError?.(new Error(`shell gate doctrine is "off" while a real writable shell (Bash) is mounted — no shell safety-axis ` +
1608
+ `fold applies to this run (commands are adjudicated by the ordinary policy/hook chain only). ` +
1609
+ `Set spec.shellGate to "classify" or "always" if this deployment expects doctrine-gated shell behavior.`), { phase: "config", sessionId, classification: "shell-gate-off" });
1610
+ }
1474
1611
  if (shellGate !== "off" && !(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
1475
1612
  shellGatedBash = !egressTools.has("Bash") && !irreversibilityTier.has("Bash");
1476
1613
  irreversibilityTier.set("Bash", shellGate === "always" ? "always" : "maybe");
@@ -1591,10 +1728,79 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1591
1728
  if (offloadStore)
1592
1729
  tools.push(createReadToolResultTool(offloadStore));
1593
1730
  const onQuestion = frozenOnQuestion;
1731
+ const liveQuestionFace = isLiveQuestionFace(onQuestion) ? onQuestion : undefined;
1732
+ const contentAskBindings = new Map();
1733
+ const CONTENT_ASK_BINDING_CAP = 32;
1734
+ const contentAskRoutable = (toolCallId) => liveQuestionFace !== undefined &&
1735
+ mountedQuestionTool !== undefined &&
1736
+ tools.includes(mountedQuestionTool) &&
1737
+ runtimeCaps?.forceDurableGate !== true &&
1738
+ !inheritedUnavailableAsks.has(toolCallId);
1739
+ const lateStrandedAnswers = [];
1740
+ const discloseStrandedAnswers = (records, why) => {
1741
+ if (records.length === 0)
1742
+ return;
1743
+ try {
1744
+ deps.onError?.(new Error(`AskUserQuestion: ${records.length} question(s) were answered by a person but the call never ` +
1745
+ `executed to collect the answer (${records.map((r) => `${r.toolCallId} [delivery ${r.deliveryId}]`).join(", ")}) — ${why}. The answer(s) were NOT ` +
1746
+ `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" });
1747
+ }
1748
+ catch {
1749
+ }
1750
+ };
1751
+ const settleContentAskBindings = () => {
1752
+ const stranded = [];
1753
+ for (const [callId, bound] of contentAskBindings) {
1754
+ if (bound.kind === "answered")
1755
+ stranded.push({ deliveryId: bound.deliveryId, toolCallId: callId });
1756
+ }
1757
+ contentAskBindings.clear();
1758
+ discloseStrandedAnswers(stranded, "the leg ended first (abort, batch teardown, or a loop failure)");
1759
+ const byDelivery = new Map();
1760
+ for (const r of [...stranded, ...lateStrandedAnswers])
1761
+ byDelivery.set(r.deliveryId, r);
1762
+ return [...byDelivery.values()];
1763
+ };
1594
1764
  const durableQuestionFace = resolveCheckpointStore(spec, deps) !== undefined &&
1595
1765
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true);
1596
- if (spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace)))
1597
- tools.push(createAskUserQuestionTool(onQuestion, { principal: spec.principal, sourceTaskId: sessionId }));
1766
+ const mountedQuestionFace = liveQuestionFace !== undefined
1767
+ ? async (req, signal) => {
1768
+ const bound = contentAskBindings.get(req.toolCallId);
1769
+ if (bound !== undefined) {
1770
+ if (bound.questionsHash === boundInputHashOf(req.questions)) {
1771
+ contentAskBindings.delete(req.toolCallId);
1772
+ if (bound.kind === "answered")
1773
+ return bound.answer;
1774
+ throw bound.error;
1775
+ }
1776
+ contentAskBindings.delete(req.toolCallId);
1777
+ }
1778
+ return liveQuestionFace(req, signal);
1779
+ }
1780
+ : onQuestion;
1781
+ const questionToolMounted = spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace));
1782
+ let mountedQuestionTool;
1783
+ if (questionToolMounted)
1784
+ tools.push((mountedQuestionTool =
1785
+ createAskUserQuestionTool(mountedQuestionFace, { principal: spec.principal, sourceTaskId: sessionId }, {
1786
+ ...(resume?.redeemedContentAskCallId !== undefined
1787
+ ? {
1788
+ redeemedApprovalCallId: resume.redeemedContentAskCallId,
1789
+ ...(resume.redeemedContentAskQuestionsHash !== undefined ? { redeemedApprovalQuestionsHash: resume.redeemedContentAskQuestionsHash } : {}),
1790
+ }
1791
+ : {}),
1792
+ ...(resolvedInteractionPosture !== undefined ? { posture: resolvedInteractionPosture } : {}),
1793
+ ...(spec.interactiveQuestionFallback === true ? { interactiveFallback: true } : {}),
1794
+ onSyntheticContinuation: ({ questionId, reason }) => {
1795
+ deps.onError?.(new Error(`AskUserQuestion ${questionId}: no human answer was obtainable (` +
1796
+ (reason === "seam_absent"
1797
+ ? "no onQuestion seam is wired"
1798
+ : reason === "declined_unavailable"
1799
+ ? "the wired question channel reported nobody was reachable"
1800
+ : "the wired question channel failed") +
1801
+ `) — the model was instructed to self-answer and the run CONTINUES (warning, not a failure).`), { phase: "degraded", sessionId, classification: "no-human-autoanswered" });
1802
+ },
1803
+ })));
1598
1804
  if (spec.handsReadOnly !== true) {
1599
1805
  const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
1600
1806
  tools.push(...createSchedulerTools(executionEnv, {
@@ -1632,7 +1838,34 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1632
1838
  .catch(() => undefined);
1633
1839
  }
1634
1840
  : undefined;
1635
- const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, seedFiles: memorySeedFiles } = await prepareMemory({ spec, deps, sessionId, taskRootPath, memoryWriteGateRef });
1841
+ const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, } = await prepareMemory({
1842
+ spec,
1843
+ deps,
1844
+ sessionId,
1845
+ taskRootPath,
1846
+ memoryWriteGateRef,
1847
+ admissionCtx: {
1848
+ orgMemoryDenied: complianceDenies.has("org_memory_mount"),
1849
+ complianceDegraded,
1850
+ parentAdmittedOrgScopes: foldAdmissionFreeze({
1851
+ delegated: internals?.isDelegatedChild === true ||
1852
+ internals?.inheritedGate !== undefined ||
1853
+ (resume?.seed.inheritedGate !== undefined &&
1854
+ (resume.seed.inheritedGate.ancestorRules !== undefined ||
1855
+ resume.seed.inheritedGate.shellGate !== undefined ||
1856
+ resume.seed.inheritedGate.admittedOrgScopes !== undefined ||
1857
+ resume.seed.inheritedGate.requiresParentConstraint)),
1858
+ inherited: inheritedAdmittedOrgScopes,
1859
+ }),
1860
+ priorOwnVerdict: priorOwnOrgVerdict,
1861
+ governedProvenance: orgGovernedProvenance,
1862
+ },
1863
+ });
1864
+ memoryAdmittedOrgScopesRef.current = memoryAdmittedOrgScopes;
1865
+ ownOrgVerdictRef.current = memoryOwnOrgVerdict ?? priorOwnOrgVerdict;
1866
+ if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
1867
+ internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
1868
+ }
1636
1869
  let memoryBlock = memoryBlockFromEngine;
1637
1870
  if (memorySeedFiles?.length && seedContextFiles) {
1638
1871
  try {
@@ -1737,7 +1970,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1737
1970
  { entries: [], seedAnnounced: true }
1738
1971
  : undefined;
1739
1972
  const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
1740
- const promptPolicyEnabled = Boolean(spec.toolPolicy ?? deps.toolPolicy);
1973
+ const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
1741
1974
  const promptHooks = spec.hooks ?? deps.hooks;
1742
1975
  const promptHooksEnabled = Boolean(promptHooks?.preToolUse || promptHooks?.postToolUse);
1743
1976
  const failClosedReason = selfOrchestrationFailClosedReason(spec, deps);
@@ -2029,6 +2262,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2029
2262
  model,
2030
2263
  deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
2031
2264
  alwaysLoadNames: [
2265
+ ASK_USER_QUESTION_TOOL_NAME,
2032
2266
  ...(toolFaceSnapshot.alwaysLoad ?? []),
2033
2267
  ...mcp.tools
2034
2268
  .filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
@@ -2328,7 +2562,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2328
2562
  }));
2329
2563
  },
2330
2564
  ...(spec.streamingToolExecution === true ? { streamingToolExecution: true } : {}),
2331
- followUpMode: "all",
2332
2565
  env: executionEnv,
2333
2566
  session,
2334
2567
  tools: harnessTools,
@@ -2368,7 +2601,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2368
2601
  releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
2369
2602
  }
2370
2603
  }
2371
- const policy = spec.toolPolicy ?? deps.toolPolicy;
2604
+ const policy = lockedPreflight.toolPolicy;
2372
2605
  const auditPolicyNames = (auditee) => {
2373
2606
  const nameGroups = toolPolicyNameSets(auditee);
2374
2607
  if (nameGroups.length > 0) {
@@ -2456,7 +2689,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2456
2689
  for (const layer of narrowingLayers)
2457
2690
  auditPolicyNames(layer);
2458
2691
  const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
2459
- const basePolicyForResumeEdit = spec.toolPolicy ?? deps.toolPolicy;
2692
+ const basePolicyForResumeEdit = lockedPreflight.toolPolicy;
2460
2693
  const sameInstanceAncestorCount = policy === undefined ? 0 : (inheritedParentConstraints ?? []).reduce((n, pc) => (pc.policy === policy ? n + 1 : n), 0);
2461
2694
  const sharedFirstDecision = new Map();
2462
2695
  const SHARED_FIRST_DECISION_CAP = 256;
@@ -2511,6 +2744,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2511
2744
  ? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName }
2512
2745
  : {}),
2513
2746
  });
2747
+ const riskAxesOf = (toolName) => {
2748
+ const tier = irreversibilityTier.get(toolName);
2749
+ const negatives = axisExplicitNegatives.get(toolName);
2750
+ const irreversible = tier === "always" || tier === "maybe" ? true : tier === "never" ? false : negatives?.irreversible;
2751
+ const egress = egressTools.has(toolName) ? true : negatives?.egress;
2752
+ if (irreversible === undefined && egress === undefined)
2753
+ return {};
2754
+ return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
2755
+ };
2514
2756
  const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal) => {
2515
2757
  let editArgs = edit;
2516
2758
  for (let round = 0;; round++) {
@@ -2543,6 +2785,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2543
2785
  args: editArgs,
2544
2786
  message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
2545
2787
  ...askSourceIdentity(),
2788
+ ...riskAxesOf(creq.toolName),
2546
2789
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2547
2790
  }, onAskOf, csignal ?? abortController.signal);
2548
2791
  if (rr.action !== "allow")
@@ -2567,8 +2810,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2567
2810
  return first;
2568
2811
  if (first.action === "allow")
2569
2812
  return { action: "allow" };
2570
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2813
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
2571
2814
  return first;
2815
+ }
2572
2816
  if (pc.durableMandate === true) {
2573
2817
  if (resolveCheckpointStore(spec, deps) !== undefined &&
2574
2818
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
@@ -2589,6 +2833,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2589
2833
  args: presentedArgs,
2590
2834
  message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
2591
2835
  ...askSourceIdentity(),
2836
+ ...riskAxesOf(creq.toolName),
2592
2837
  ...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2593
2838
  }, pc.onAsk, csignal ?? abortController.signal);
2594
2839
  const askWaitMs = Math.max(0, now() - askT0);
@@ -2623,8 +2868,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2623
2868
  }
2624
2869
  if (decision.action !== "ask")
2625
2870
  return decision;
2626
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME)
2871
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
2627
2872
  return decision;
2873
+ }
2628
2874
  if (pc.durableMandate === true) {
2629
2875
  if (resolveCheckpointStore(spec, deps) !== undefined &&
2630
2876
  (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
@@ -2645,6 +2891,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2645
2891
  args: presentedArgs,
2646
2892
  message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
2647
2893
  ...askSourceIdentity(),
2894
+ ...riskAxesOf(creq.toolName),
2648
2895
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2649
2896
  }, pc.onAsk, csignal ?? abortController.signal);
2650
2897
  const askWaitMs = Math.max(0, now() - askT0);
@@ -2830,6 +3077,42 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2830
3077
  };
2831
3078
  })();
2832
3079
  const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
3080
+ const checkpointStore = resolveCheckpointStore(spec, deps);
3081
+ const durableApproval = spec.durableApproval ??
3082
+ (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
3083
+ const wiringQuestionSeam = resolveQuestionSeam(spec, deps);
3084
+ const wiringQuestionStripped = internals?.questionFaceStripped === true;
3085
+ const wiringAskSeam = resolveAskSeamForm(spec, deps);
3086
+ const wiringManifest = deriveWiringManifest({
3087
+ half: "effective",
3088
+ leg: resume !== undefined ? "resume" : internals?.isDelegatedChild === true ? "child" : "root",
3089
+ askForm: wiringAskSeam.form,
3090
+ ...(wiringAskSeam.provenance !== undefined ? { askProvenance: wiringAskSeam.provenance } : {}),
3091
+ questionWired: wiringQuestionSeam.wired,
3092
+ ...(wiringQuestionSeam.provenance !== undefined ? { questionProvenance: wiringQuestionSeam.provenance } : {}),
3093
+ ...(wiringQuestionStripped ? { questionStrippedByEngine: true } : {}),
3094
+ ...(resolvedInteractionPosture !== undefined ? { interactionPosture: resolvedInteractionPosture } : {}),
3095
+ ...(spec.interactiveTools === true && !wiringQuestionSeam.wired && !durableQuestionFace && !wiringQuestionStripped
3096
+ ? { interactiveToolsWithoutDeliveryFace: true }
3097
+ : {}),
3098
+ elicitSeamWired: resolveElicitSeam(deps),
3099
+ elicitServersOptedIn: countElicitOptIns(spec),
3100
+ parkCapable: checkpointStore !== undefined,
3101
+ parkDurableApprovalOptIn: spec.durableApproval !== undefined,
3102
+ parkForceDurableGate: runtimeCaps?.forceDurableGate === true,
3103
+ parkSafetyVocabularyArmed: irreversibleTools.size > 0 || egressTools.size > 0,
3104
+ ...(checkpointStore !== undefined ? { checkpointDurability: resolveDeclaredDurability(checkpointStore, "checkpointStore") } : {}),
3105
+ sessionDurability: resolveDeclaredDurability(sessions, "sessionStore"),
3106
+ backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
3107
+ hostChildEventSinkWired: deps.onBackgroundChildEvent !== undefined,
3108
+ lockedConfigWired: deps.lockedConfig !== undefined,
3109
+ complianceWired: deps.compliancePostureResolver !== undefined,
3110
+ memoryAdmissionWired: deps.memoryScopeAdmission !== undefined,
3111
+ retentionPolicyWired: deps.retentionPolicy !== undefined,
3112
+ });
3113
+ const parkLaneArmed = wiringManifest.parkLane.effective === true;
3114
+ const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
3115
+ const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
2833
3116
  if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
2834
3117
  const adjudicate = effectivePolicy
2835
3118
  ? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot }, abortController.signal)), abortController.signal, () => ({
@@ -2911,6 +3194,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2911
3194
  })(),
2912
3195
  message: decision.message ?? `approval required for "${req.toolName}"`,
2913
3196
  ...askSourceIdentity(),
3197
+ ...riskAxesOf(req.toolName),
2914
3198
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2915
3199
  }, onAsk, abortController.signal);
2916
3200
  const waitMs = Math.max(0, now() - t0);
@@ -2950,9 +3234,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2950
3234
  catch {
2951
3235
  }
2952
3236
  };
2953
- const checkpointStore = resolveCheckpointStore(spec, deps);
2954
- const durableApproval = spec.durableApproval ??
2955
- (runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
2956
3237
  const inFlightSpendMicroUsd = () => {
2957
3238
  const own = liveSpendRef.get?.().costMicroUsd ?? 0;
2958
3239
  const seededNested = resume?.seed.nestedStats.costMicroUsd;
@@ -2991,13 +3272,24 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2991
3272
  return live.length > 0 ? live : undefined;
2992
3273
  })(),
2993
3274
  pendingSteer: undefined,
3275
+ pendingSteerQueue: undefined,
2994
3276
  inheritedGate: (() => {
2995
3277
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
2996
3278
  const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
2997
- return inheritedAncestorRules !== undefined || inheritedShellGate !== undefined || requiresParentConstraint
3279
+ return inheritedAncestorRules !== undefined ||
3280
+ inheritedShellGate !== undefined ||
3281
+ inheritedAdmittedOrgScopes !== undefined ||
3282
+ ownOrgVerdictRef.current !== undefined ||
3283
+ orgGovernedProvenance ||
3284
+ requiresParentConstraint
2998
3285
  ? {
2999
3286
  ...(inheritedAncestorRules !== undefined ? { ancestorRules: structuredClone(inheritedAncestorRules) } : {}),
3000
3287
  ...(inheritedShellGate !== undefined ? { shellGate: inheritedShellGate } : {}),
3288
+ ...(inheritedAdmittedOrgScopes !== undefined ? { admittedOrgScopes: [...inheritedAdmittedOrgScopes] } : {}),
3289
+ ...(ownOrgVerdictRef.current !== undefined
3290
+ ? { ownAdmittedOrgScopes: [...ownOrgVerdictRef.current.scopes], ownAdmittedOrgWriteScope: ownOrgVerdictRef.current.writeScope }
3291
+ : {}),
3292
+ ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
3001
3293
  requiresParentConstraint,
3002
3294
  ...(requiresParentConstraint && parentConstraintCount !== undefined ? { parentConstraintCount } : {}),
3003
3295
  }
@@ -3112,7 +3404,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3112
3404
  const mintedAt = Date.now();
3113
3405
  const cp = {
3114
3406
  token,
3115
- version: resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3407
+ version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
3116
3408
  scope,
3117
3409
  sessionId,
3118
3410
  leafId,
@@ -3194,7 +3486,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3194
3486
  const mintedAt = Date.now();
3195
3487
  const cp = {
3196
3488
  token,
3197
- version: reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3489
+ version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3198
3490
  scope,
3199
3491
  sessionId,
3200
3492
  leafId,
@@ -3228,13 +3520,92 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3228
3520
  return true;
3229
3521
  }
3230
3522
  : undefined;
3231
- const suspendAsk = checkpointStore && (durableApproval || irreversibleTools.size > 0 || egressTools.size > 0)
3232
- ? async (req, postHookArgs, safety, approverUnavailable) => {
3233
- if (safety === undefined &&
3234
- onAsk !== undefined &&
3523
+ const resolveContentAsk = async (req) => {
3524
+ if (!contentAskRoutable(req.toolCallId) || liveQuestionFace === undefined || mountedQuestionTool === undefined) {
3525
+ return { kind: "unavailable", parkDeclined: false };
3526
+ }
3527
+ const deliveryId = uuidv7();
3528
+ const bindOutcome = (entry, questionsHash) => {
3529
+ if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId))
3530
+ return;
3531
+ contentAskBindings.set(req.toolCallId, { ...entry, questionsHash, deliveryId });
3532
+ };
3533
+ const refuseBeforeDelivery = (code, presentedInput) => ({
3534
+ kind: "delivery_failure",
3535
+ code,
3536
+ ...(presentedInput !== undefined ? { presentedInput } : {}),
3537
+ });
3538
+ let retained;
3539
+ let presented;
3540
+ try {
3541
+ retained = structuredClone(req.args);
3542
+ presented = structuredClone(retained.questions);
3543
+ }
3544
+ catch {
3545
+ return refuseBeforeDelivery("question.malformed_request");
3546
+ }
3547
+ const retainedQuestionsHash = boundInputHashOf(retained.questions);
3548
+ if (!Value.Check(mountedQuestionTool.parameters, retained) || validateAskQuestions(presented) !== undefined) {
3549
+ return refuseBeforeDelivery("question.malformed_request", retained);
3550
+ }
3551
+ if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId)) {
3552
+ return { kind: "unavailable", parkDeclined: true };
3553
+ }
3554
+ if (abortController.signal.aborted) {
3555
+ return { kind: "delivery_failure", code: "question.aborted", presentedInput: retained };
3556
+ }
3557
+ try {
3558
+ const facePromise = (async () => liveQuestionFace({
3559
+ toolCallId: req.toolCallId,
3560
+ questions: presented,
3561
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
3562
+ sourceTaskId: sessionId,
3563
+ boundInputHash: boundInputHashOf(retained),
3564
+ deliveryId,
3565
+ }, abortController.signal))();
3566
+ const settlement = await raceAbort(facePromise.then((outcome) => ({ tag: "outcome", outcome }), (error) => ({ tag: "threw", error })), abortController.signal, () => ({ tag: "aborted" }));
3567
+ if (settlement.tag === "aborted") {
3568
+ void facePromise.then((late) => {
3569
+ if (classifyQuestionOutcome(late).shape !== "answered")
3570
+ return;
3571
+ lateStrandedAnswers.push({ deliveryId, toolCallId: req.toolCallId });
3572
+ discloseStrandedAnswers([{ deliveryId, toolCallId: req.toolCallId }], "the task had already ended when the answer arrived");
3573
+ }, () => undefined).catch(() => undefined);
3574
+ }
3575
+ if (settlement.tag === "outcome") {
3576
+ const outcome = settlement.outcome;
3577
+ const reading = classifyQuestionOutcome(outcome);
3578
+ if (reading.shape === "contradictory") {
3579
+ bindOutcome({ kind: "failed", error: new Error("the question channel returned a contradictory outcome (an unavailable answer)") }, retainedQuestionsHash);
3580
+ return { kind: "delivery_failure", code: "question.human_channel_failed", presentedInput: retained };
3581
+ }
3582
+ if (reading.shape === "unavailable")
3583
+ return { kind: "unavailable", parkDeclined: true, presentedInput: retained };
3584
+ const answerSnapshot = reading.answer;
3585
+ bindOutcome({ kind: "answered", answer: answerSnapshot }, retainedQuestionsHash);
3586
+ return { kind: "answered", presentedInput: retained };
3587
+ }
3588
+ const error = settlement.tag === "threw"
3589
+ ? settlement.error
3590
+ : Object.assign(new Error("the task ended while this question was still out"), { name: "AbortError" });
3591
+ bindOutcome({ kind: "failed", error }, retainedQuestionsHash);
3592
+ return {
3593
+ kind: "delivery_failure",
3594
+ code: settlement.tag === "aborted" || abortController.signal.aborted ? "question.aborted" : "question.human_channel_failed",
3595
+ presentedInput: retained,
3596
+ };
3597
+ }
3598
+ catch (err) {
3599
+ bindOutcome({ kind: "failed", error: err }, retainedQuestionsHash);
3600
+ return { kind: "delivery_failure", code: "question.human_channel_failed", presentedInput: retained };
3601
+ }
3602
+ };
3603
+ const suspendAsk = parkLaneArmed && checkpointStore !== undefined
3604
+ ? async (req, postHookArgs, safety, liveFaceUnavailable) => {
3605
+ const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : onAsk !== undefined;
3606
+ if (syncFirstEligible &&
3235
3607
  runtimeCaps?.forceDurableGate !== true &&
3236
- approverUnavailable !== true &&
3237
- req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
3608
+ liveFaceUnavailable !== true &&
3238
3609
  !inheritedUnavailableAsks.has(req.toolCallId)) {
3239
3610
  return undefined;
3240
3611
  }
@@ -3320,7 +3691,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3320
3691
  const mintedAt = Date.now();
3321
3692
  cp = {
3322
3693
  token,
3323
- version: approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3694
+ version: orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
3324
3695
  scope,
3325
3696
  sessionId,
3326
3697
  leafId,
@@ -3381,11 +3752,28 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3381
3752
  if (eff !== undefined && !toolEffects.has(t.name))
3382
3753
  toolEffects.set(t.name, eff);
3383
3754
  }
3384
- toolCallGateArmedRef.armed = effectivePolicy !== undefined || hooks?.preToolUse !== undefined || egressTools.size > 0 || irreversibleTools.size > 0 || spec.enablePlanMode === true;
3755
+ toolCallGateArmedRef.armed =
3756
+ effectivePolicy !== undefined ||
3757
+ hooks?.preToolUse !== undefined ||
3758
+ egressTools.size > 0 ||
3759
+ irreversibleTools.size > 0 ||
3760
+ spec.enablePlanMode === true ||
3761
+ complianceDenies.has("web_fetch");
3385
3762
  if (toolCallGateArmedRef.armed) {
3386
3763
  harness.on("tool_call", async (e) => {
3387
3764
  blockedToolCalls.delete(e.toolCallId);
3388
3765
  inheritedAskGrants.delete(e.toolCallId);
3766
+ {
3767
+ const complianceDeny = complianceCallDenial(complianceDenies, e.toolName);
3768
+ if (complianceDeny !== undefined) {
3769
+ if (blockedTracked)
3770
+ blockedToolCalls.add(e.toolCallId);
3771
+ if (notifyPermissionDenied) {
3772
+ await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, source: "safety" });
3773
+ }
3774
+ return { block: true, reason: formatHookFeedback(complianceDeny), preToolContext: [] };
3775
+ }
3776
+ }
3389
3777
  if (planModeRef.active && (toolEffects.get(e.toolName) ?? "write") !== "read") {
3390
3778
  if (blockedTracked)
3391
3779
  blockedToolCalls.add(e.toolCallId);
@@ -3406,9 +3794,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3406
3794
  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() })),
3407
3795
  event: e,
3408
3796
  preToolUse: hooks?.preToolUse,
3797
+ ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
3409
3798
  adjudicate,
3410
3799
  resolveAsk: resolveAskBound,
3411
3800
  suspendAsk,
3801
+ resolveContentAsk,
3412
3802
  egress: egressTools.has(e.toolName),
3413
3803
  irreversibility: irreversibilityTier.get(e.toolName),
3414
3804
  reversibilityProbe: reversibilityProbes.get(e.toolName),
@@ -3461,7 +3851,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3461
3851
  isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
3462
3852
  content: e.content.map((c) => ({ ...c })),
3463
3853
  details: clonedDetails,
3464
- }, { toolCallId: e.toolCallId, toolName: e.toolName });
3854
+ }, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}) });
3465
3855
  if (patch?.additionalContext) {
3466
3856
  content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext) }];
3467
3857
  changed = true;
@@ -3469,7 +3859,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3469
3859
  }
3470
3860
  }
3471
3861
  else if (hooks?.postToolUse) {
3472
- const patch = await hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName });
3862
+ 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 } : {}) });
3473
3863
  if (patch?.updatedOutput) {
3474
3864
  content = patch.updatedOutput;
3475
3865
  changed = true;
@@ -3718,7 +4108,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3718
4108
  : undefined;
3719
4109
  overheadState.promptChars = systemPrompt.length;
3720
4110
  const preparedHolder = {};
3721
- 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 } : {}) });
4111
+ 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, 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 } : {}) });
3722
4112
  const prepared = buildPrepared();
3723
4113
  preparedHolder.current = prepared;
3724
4114
  return prepared;