@sema-agent/core 7.42.1 → 7.44.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 (115) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +3 -1
  4. package/dist/agents/subagent.js +5 -3
  5. package/dist/agents/teacher.js +1 -1
  6. package/dist/agents/verify.js +2 -0
  7. package/dist/brain/errors.d.ts +23 -2
  8. package/dist/brain/errors.js +26 -2
  9. package/dist/brain/stream-engine.d.ts +2 -1
  10. package/dist/brain/stream-engine.js +10 -9
  11. package/dist/core/allow-entry.d.ts +50 -31
  12. package/dist/core/allow-entry.js +100 -37
  13. package/dist/core/ask-origin.d.ts +16 -5
  14. package/dist/core/ask-origin.js +11 -3
  15. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  16. package/dist/core/ask-unresolvable-notice.js +1 -1
  17. package/dist/core/auto-mode.d.ts +5 -4
  18. package/dist/core/auto-mode.js +3 -1
  19. package/dist/core/checkpoint-store.d.ts +44 -20
  20. package/dist/core/checkpoint-store.js +10 -16
  21. package/dist/core/child-clamp-seats.d.ts +15 -1
  22. package/dist/core/child-clamp-seats.js +6 -2
  23. package/dist/core/classifier-unavailable-arm.d.ts +42 -23
  24. package/dist/core/classifier-unavailable-arm.js +14 -3
  25. package/dist/core/decision-receipt.js +2 -0
  26. package/dist/core/engine-notice.d.ts +16 -14
  27. package/dist/core/engine-notice.js +4 -3
  28. package/dist/core/gate-lanes.js +37 -29
  29. package/dist/core/governance-codes.d.ts +4 -3
  30. package/dist/core/governance-codes.js +1 -0
  31. package/dist/core/hooks.d.ts +6 -1
  32. package/dist/core/hooks.js +3 -0
  33. package/dist/core/memory-engine/engine.d.ts +2 -2
  34. package/dist/core/memory-engine/engine.js +7 -14
  35. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  36. package/dist/core/memory-engine/file-backend.js +5 -7
  37. package/dist/core/memory-engine/layout.d.ts +37 -4
  38. package/dist/core/memory-engine/layout.js +34 -5
  39. package/dist/core/mode-policy.d.ts +5 -4
  40. package/dist/core/mode-policy.js +4 -0
  41. package/dist/core/permission-rule-consent.d.ts +3 -2
  42. package/dist/core/permission-rule-consent.js +25 -20
  43. package/dist/core/permission-rule-model.d.ts +47 -7
  44. package/dist/core/permission-rule-model.js +80 -6
  45. package/dist/core/permission-rules.d.ts +8 -2
  46. package/dist/core/permission-rules.js +26 -9
  47. package/dist/core/runner/classifier-ladder.d.ts +10 -8
  48. package/dist/core/runner/classifier-ladder.js +3 -1
  49. package/dist/core/runner/contracts.d.ts +1 -16
  50. package/dist/core/runner/denial-limit-arms.d.ts +25 -8
  51. package/dist/core/runner/denial-limit-arms.js +24 -10
  52. package/dist/core/runner/gate-exit.js +2 -1
  53. package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
  54. package/dist/core/runner/permission-rule-lanes.js +8 -2
  55. package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
  56. package/dist/core/runner/prepare-ask-lane.js +2 -1
  57. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -3
  58. package/dist/core/runner/prepare-caps-and-workflow.js +3 -2
  59. package/dist/core/runner/prepare-config-doors.d.ts +3 -0
  60. package/dist/core/runner/prepare-config-doors.js +6 -5
  61. package/dist/core/runner/prepare-gate-stations.js +1 -1
  62. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
  63. package/dist/core/runner/prepare-inherited-gate.js +5 -3
  64. package/dist/core/runner/prepare-park-ask.d.ts +4 -4
  65. package/dist/core/runner/prepare-park-ask.js +2 -4
  66. package/dist/core/runner/prepare-policy-chain.d.ts +7 -1
  67. package/dist/core/runner/prepare-policy-chain.js +59 -38
  68. package/dist/core/runner/prepare-project-context.js +6 -2
  69. package/dist/core/runner/prepare-task.js +6 -6
  70. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -5
  71. package/dist/core/runner/prepare-wiring-manifest.js +8 -9
  72. package/dist/core/runner/resume-checkpoint-screen.js +1 -1
  73. package/dist/core/runner/runtask.js +1 -5
  74. package/dist/core/runner/steer-admission.d.ts +10 -11
  75. package/dist/core/runner/steer-admission.js +6 -8
  76. package/dist/core/runner/stream-steer-verb.d.ts +5 -4
  77. package/dist/core/runner/stream-steer-verb.js +6 -8
  78. package/dist/core/runner/synthetic-tools.d.ts +3 -2
  79. package/dist/core/runner/synthetic-tools.js +1 -1
  80. package/dist/core/session-policy-store.d.ts +3 -3
  81. package/dist/core/session-policy-store.js +18 -4
  82. package/dist/core/skill-grant-binding.d.ts +44 -0
  83. package/dist/core/skill-grant-binding.js +22 -0
  84. package/dist/core/skill-grant.d.ts +59 -28
  85. package/dist/core/skill-grant.js +96 -23
  86. package/dist/core/steer-replay.d.ts +46 -0
  87. package/dist/core/steer-replay.js +22 -0
  88. package/dist/core/task-event.d.ts +3 -2
  89. package/dist/core/task-spec.d.ts +14 -1
  90. package/dist/core/task-stream.d.ts +12 -6
  91. package/dist/core/tighten-task-spec.d.ts +2 -0
  92. package/dist/core/tighten-task-spec.js +3 -0
  93. package/dist/core/tool-errors.d.ts +2 -2
  94. package/dist/core/tool-name-aliases.d.ts +35 -5
  95. package/dist/core/tool-name-aliases.js +29 -4
  96. package/dist/core/tool-policy.d.ts +10 -0
  97. package/dist/core/tool-policy.js +9 -2
  98. package/dist/core/tool-spec.d.ts +7 -0
  99. package/dist/core/types.d.ts +2 -2
  100. package/dist/core/types.js +1 -1
  101. package/dist/core/wiring-manifest.d.ts +56 -14
  102. package/dist/core/wiring-manifest.js +29 -4
  103. package/dist/index.d.ts +6 -4
  104. package/dist/index.js +5 -4
  105. package/dist/orchestration/run-workflow-tool.d.ts +4 -0
  106. package/dist/orchestration/run-workflow-tool.js +1 -1
  107. package/dist/orchestration/workflow-primitives.d.ts +3 -1
  108. package/dist/orchestration/workflow-primitives.js +6 -6
  109. package/dist/orchestration/workflow-types.d.ts +5 -0
  110. package/dist/orchestration/workflow.d.ts +3 -2
  111. package/dist/orchestration/workflow.js +14 -6
  112. package/package.json +1 -1
  113. package/test/export-surface.snapshot.json +21 -5
  114. package/wire-changes/core@7.43.0.json +985 -0
  115. package/wire-changes/core@7.44.0.json +469 -0
@@ -11,6 +11,7 @@ import { deliverEngineNotice } from "../engine-notice.js";
11
11
  import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
12
12
  import { skillLoadReading } from "./skill-base-dir.js";
13
13
  import { RosterBuilder } from "../tool-roster.js";
14
+ import { mountedToolNames } from "../tool-name-aliases.js";
14
15
  import { asThrownError } from "../thrown-value.js";
15
16
  export async function prepareProjectContext(input) {
16
17
  const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillGrants, allowedTools, allowedToolTables, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget } = input;
@@ -88,7 +89,10 @@ export async function prepareProjectContext(input) {
88
89
  const frozenSkillGrants = readFrozenSkillGrants(resume?.seed.skillGrants);
89
90
  if (frozenSkillGrants === undefined)
90
91
  throw new Error("the resumed checkpoint's skill-grant section does not read as grant rows — refusing to seed the run without the refusals it carries (the resume screen refuses this row before the claim)");
91
- const inert = seedAllowEntryTables(allowedToolTables, { skillInert: seedSkillGrants(skillGrants, { skills: skillSpecs, userInvokedSkills: spec.userInvokedSkills, inherited: [...(internals?.inheritedSkillGrants ?? []), ...frozenSkillGrants] }), inherited: internals?.inheritedAllowedTools, request: allowedTools, deployment: deps.allowedTools });
92
+ const mounted = () => mountedToolNames(tools);
93
+ skillGrants.bindMountedNames(mounted);
94
+ skillGrants.bindSkills(skillSpecs, SKILL_TOOL_NAME);
95
+ const inert = seedAllowEntryTables(allowedToolTables, { skillInert: seedSkillGrants(skillGrants, { skills: skillSpecs, userInvokedSkills: spec.userInvokedSkills, inherited: [...(internals?.inheritedSkillGrants ?? []), ...frozenSkillGrants] }), inherited: internals?.inheritedAllowedTools, request: allowedTools, deployment: deps.allowedTools }, mounted());
92
96
  if (inert.length > 0)
93
97
  deliverEngineNotice(deps.onNotice, allowedToolsNotGrantedNotice(inert, sessionId));
94
98
  if (skillSpecs.length > 0) {
@@ -99,7 +103,7 @@ export async function prepareProjectContext(input) {
99
103
  }
100
104
  roster.mount(createSkillTool(skillSpecs, skillGrants), { source: "builtin", mountedBy: "optional" });
101
105
  }
102
- const skillSeatOf = skillSeatReader(skillSpecs);
106
+ const skillSeatOf = skillSeatReader(skillSpecs, mounted);
103
107
  const sharedMemoryProvider = deps.sharedMemoryStores;
104
108
  let sharedMemoryPairMounted = false;
105
109
  if (sharedMemoryProvider !== undefined) {
@@ -407,7 +407,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
407
407
  let readDenyAdditionsNormalized = [];
408
408
  let handsLessResolvedFace;
409
409
  let handsLessResolvedWriteFace;
410
- const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, commitModeSeats, endModeCeiling, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, skillGrantCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, requestAllowedTools: allowedToolTables.request, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDeciderRef.current, autoModeDenialTracking: () => autoModeDenialTrackingRef.current, autoModeArming: () => autoModeArmingRef.current, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
410
+ const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, commitModeSeats, endModeCeiling, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, skillGrantCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, askSeat: doors.askSeat, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, requestAllowedTools: allowedToolTables.request, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDeciderRef.current, autoModeDenialTracking: () => autoModeDenialTrackingRef.current, autoModeArming: () => autoModeArmingRef.current, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
411
411
  bindPermissionModeSeat(permissionMode);
412
412
  const announceRuleStoreSkipped = ruleStoreUnreadableNotice(deps.onNotice, { sessionId, runId });
413
413
  const persistedPathRowsRead = persistedPathRowsOfTask({ deps, principal: spec.principal, sessionId, root: taskRootFinal, home: executionEnv.homeDir, taskId: hostTaskId, handsEnabled, signals: [abortController.signal, ...(spec.signal !== undefined ? [spec.signal] : [])], onUnread: announceRuleStoreSkipped });
@@ -415,7 +415,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
415
415
  const effectivePathDenyRules = [...toolPolicyPathDenyRules([lockedPreflight.toolPolicy, ...(inheritedParentConstraints ?? []).map((c) => c.policy)]), ...persistedPathRows.denyRows];
416
416
  const writeProtectionJudgeRef = {};
417
417
  const writeGuards = { protection: () => writeProtectionJudgeRef.current, target: toolPolicyWriteTargetGuard([lockedPreflight.toolPolicy, ...(inheritedParentConstraints ?? []).map((c) => c.policy)]) };
418
- const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDeciderRef, autoModeDenialTrackingRef, autoModeArmingRef, armAutoMode, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, parentSpecSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries: () => ({ entries: readDenyMatcher?.entries ?? [], ruleRows: effectivePathDenyRules }), peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
418
+ const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDeciderRef, autoModeDenialTrackingRef, autoModeArmingRef, armAutoMode, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, askSeat: doors.askSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, parentSpecSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries: () => ({ entries: readDenyMatcher?.entries ?? [], ruleRows: effectivePathDenyRules }), peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
419
419
  const tools = roster.tools;
420
420
  const pausedRef = {};
421
421
  const undeliveredContinuationRef = { current: { parkedSteers: [], orphanTasks: [] } };
@@ -733,8 +733,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
733
733
  releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
734
734
  }
735
735
  }
736
- const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider: () => autoModeDeciderRef.current, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
737
- const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtection, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: handsReadFace.shellReadStationArmed, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
736
+ const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider: () => autoModeDeciderRef.current, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
737
+ const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtection, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, askSeat: doors.askSeat, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: handsReadFace.shellReadStationArmed, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
738
738
  const writeProtectionCheck = writeProtection?.check;
739
739
  writeProtectionJudgeRef.current = writeProtection?.judgeTarget;
740
740
  if (fileHistoryBoundary !== undefined) {
@@ -743,10 +743,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
743
743
  return undefined;
744
744
  });
745
745
  }
746
- const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking: autoModeDenialTrackingRef, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
746
+ const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking: autoModeDenialTrackingRef, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
747
747
  const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, skillGrantCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
748
748
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
749
- const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, skillSpecs, now, humanReviewRef, abortController, harness, pausedRef });
749
+ const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef });
750
750
  const seatInputsFor = skillGateWiring({ tools, skillToolName: SKILL_TOOL_NAME, seatOf: skillSeatOf, mode: () => permissionMode.current, lane: skillGrantLane, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.skill_allowed", version: 1, taskId: hostTaskId, ...info, entries: [...info.entries], ts: Date.now() })), allowEntries: { deploymentAllows: { ...allowEntryLanes.deployment, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.allowed_tools_allowed", version: 1, taskId: hostTaskId, source: "deployment", ...info, entries: [...info.entries], ts: Date.now() })) }, requestAllows: { ...allowEntryLanes.request, onAllowed: (info) => emitTrace(deps.tracer, () => ({ kind: "permission.allowed_tools_allowed", version: 1, taskId: hostTaskId, source: "request", ...info, entries: [...info.entries], ts: Date.now() })) } } });
751
751
  const { approvedCallScreen } = prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: { ...wiringManifest.ask, effective: askSeatEffective(wiringManifest) }, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, seatInputsFor, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
752
752
  const postureFrameRef = { report: (err) => deps.onError?.(err, { phase: "degraded", sessionId }) };
@@ -36,12 +36,12 @@ import { type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry
36
36
  import { type PermissionModeController } from "../mode-policy.js";
37
37
  import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, UsageGovernance } from "./contracts.js";
38
38
  export interface PrepareWiringManifestInput {
39
- /** borrowed-readonly — the REBOUND spec. Read: `onAsk` (the own seat), `onQuestion` (the question seam), `handsReadOnly` / `tools` (the ungated scan),
39
+ /** borrowed-readonly — the REBOUND spec. Read: `onQuestion` (the question seam), `handsReadOnly` / `tools` (the ungated scan),
40
40
  * `resourceSuspend` (the task-axis lane), `durableApproval` / `principal` (the synthesis and the notice), `interactiveTools` /
41
41
  * `mcp` / `checkpointStore` (manifest cells and the notice's cause), `enablePlanMode` (the gate-machinery predicate's
42
42
  * plan-mode term). Never mutated. */
43
- spec: Pick<TaskSpec, "onAsk" | "onQuestion" | "handsReadOnly" | "tools" | "resourceSuspend" | "durableApproval" | "principal" | "interactiveTools" | "mcp" | "checkpointStore" | "enablePlanMode" | "hooks" | "peerPresence">;
44
- /** borrowed-readonly — the deployment seats, whole: `onAsk` (the seat's deps half), `onError` (the warning), `executionEnv`
43
+ spec: Pick<TaskSpec, "onQuestion" | "handsReadOnly" | "tools" | "resourceSuspend" | "durableApproval" | "principal" | "interactiveTools" | "mcp" | "checkpointStore" | "enablePlanMode" | "hooks" | "peerPresence">;
44
+ /** borrowed-readonly — the deployment seats, whole: `onError` (the warning), `executionEnv`
45
45
  * (the hook-env predicate), `checkpointStore` (durable infra), the manifest's dozen wiring facts (`backgroundAgentStore`,
46
46
  * `permissionRuleStore`, `permissionRuleSyncWired`, `onBackgroundChildEvent`, `lockedConfig`, `compliancePostureResolver`,
47
47
  * `memoryScopeAdmission`, `retentionPolicy`, `onElicit`), `onNotice`, the write-protection judge's compile seats
@@ -141,8 +141,9 @@ export interface PrepareWiringManifestInput {
141
141
  harness: AgentHarness;
142
142
  /** borrowed-readonly — the durable-suspend holder; the drain's parked predicate reads `token` at run time. Not written here. */
143
143
  pausedRef: Prepared["pausedRef"];
144
- /** borrowed-readonly — the FROZEN approver seat (the notice's liveApprover fact). */
145
- frozenOnAsk: OnAsk | undefined;
144
+ /** borrowed-readonly — the run's approval seat as the config doors froze it: the gate's approver and the notice's facts (its
145
+ * value), the manifest's ask cell (its form and provenance). */
146
+ askSeat: import("../wiring-manifest.js").AskSeat;
146
147
  /** borrowed-readonly — the LIVE half of the inherited chain (the notice reads its content mandates). */
147
148
  liveInheritedGate: RunInternals["inheritedGate"];
148
149
  /** borrowed-readonly — the effective delegation facts (the identity's isDelegatedChild). */
@@ -11,7 +11,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
11
11
  import { mountWriteProtection } from "./write-protection-station.js";
12
12
  import { autoRunOnRecommendationEffective } from "./memory-consolidation-driver.js";
13
13
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
14
- import { LSP_CONTRACT_ID, askSeatEffective, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
14
+ import { LSP_CONTRACT_ID, askSeatEffective, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
15
15
  import { modePolicyOf } from "../mode-policy.js";
16
16
  import { DEFAULT_IRREVERSIBLE_SCOPE } from "./checkpoint-scope.js";
17
17
  export function mcpManifestEntries(entries, statuses) {
@@ -102,9 +102,9 @@ function announceDurableGateUnavailable(args) {
102
102
  message: `Durable approval gate mandated but unavailable: ${source === "force_durable_gate" ? "the runtime entitlement forceDurableGate is in force for this principal" : "this task opted into durable approval (durableApproval) with no live approver seat"}, ` +
103
103
  `but ${cause === "task_store_disabled" ? 'this task disabled the checkpoint store (checkpointStore: "disabled")' : "this deployment wired no checkpoint store"}, ` +
104
104
  `so no interactive ask of this task can be parked for a durable answer. Asks resolve on the live chain instead ` +
105
- `(${args.liveApprover ? "a live approver answers permission asks in-stream" : args.blanketAllow ? 'a blanket "allow" posture answers ordinary permission asks without judgment (a mandated ask is refused)' : "permission asks are denied fail-closed — no live approver is wired"}; ` +
105
+ `(${args.liveApprover ? "a live approver answers permission asks in-stream" : args.blanketAllow ? 'a blanket "allow" posture answers ordinary permission asks without judgment (a mandated ask is refused)' : "permission asks are denied fail-closed — nobody in this run can be asked to approve them"}; ` +
106
106
  `${liveQuestionFace ? "a live question face answers AskUserQuestion in-stream" : args.liveQuestionFace ? "AskUserQuestion is withheld from the live face by an inherited durable mandate and refused" : "AskUserQuestion has no live face and is refused"}) ` +
107
- `and NO durable approval record is written. ${cause === "task_store_disabled" ? "Run this task with its store" : "Wire a checkpoint store on this deployment"}, or ${source === "force_durable_gate" ? "withdraw the entitlement for principals served here" : "give the task a live approver seat (or drop durableApproval)"}.`,
107
+ `and NO durable approval record is written. ${cause === "task_store_disabled" ? "Run this task with its store" : "Wire a checkpoint store on this deployment"}, or ${source === "force_durable_gate" ? "withdraw the entitlement for principals served here" : args.declaredNone ? 'run the task where a person can be asked (without approvalReach: "none"), or drop durableApproval' : "give the task a live approver seat (or drop durableApproval)"}.`,
108
108
  detail: {
109
109
  sessionId: args.sessionId,
110
110
  runId: args.runId,
@@ -117,8 +117,8 @@ function announceDurableGateUnavailable(args) {
117
117
  });
118
118
  }
119
119
  export function prepareWiringManifest(input) {
120
- const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, sessionBox, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, shellReadStationArmed, lspDiagnosticsLive, fileHistoryFact } = input;
121
- const onAsk = spec.onAsk ?? deps.onAsk;
120
+ const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, sessionBox, harness, pausedRef, askSeat, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, shellReadStationArmed, lspDiagnosticsLive, fileHistoryFact } = input;
121
+ const onAsk = askSeat.value;
122
122
  const handWriteTools = toolRoster.entries.filter((e) => e.mountedBy === "hands" && (e.effect === "write" || e.effect === "idempotent")).map((e) => e.name);
123
123
  const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
124
124
  const peerWriteUngated = modePolicyOf(permissionMode.current).unstated === "ask"
@@ -181,14 +181,13 @@ export function prepareWiringManifest(input) {
181
181
  const writeProtection = mountWriteProtection({ deps, env: handsEnabled ? executionEnv : undefined, rootPath: taskRootFinal, roster: toolRoster, sessionId, runId });
182
182
  const wiringQuestionSeam = resolveQuestionSeam(spec, deps);
183
183
  const wiringQuestionStripped = internals?.questionFaceStripped === true;
184
- const wiringAskSeam = resolveAskSeamForm(spec, deps);
185
184
  const wiringManifest = deriveWiringManifest({
186
185
  half: "effective",
187
186
  leg: resume !== undefined ? "resume" : internals?.isDelegatedChild === true ? "child" : "root",
188
187
  teamRole: teamRoleOf({ isDelegatedChild: delegation.isDelegatedChild, ...(internals?.explicitAgentName !== undefined ? { spawnName: internals.explicitAgentName } : {}) }),
189
188
  ...(spec.peerPresence !== undefined ? { peerPresence: spec.peerPresence } : {}),
190
- askForm: wiringAskSeam.form,
191
- ...(wiringAskSeam.provenance !== undefined ? { askProvenance: wiringAskSeam.provenance } : {}),
189
+ askForm: askSeat.form,
190
+ ...(askSeat.provenance !== undefined ? { askProvenance: askSeat.provenance } : {}),
192
191
  questionWired: wiringQuestionSeam.wired,
193
192
  ...(wiringQuestionSeam.provenance !== undefined ? { questionProvenance: wiringQuestionSeam.provenance } : {}),
194
193
  ...(wiringQuestionStripped ? { questionStrippedByEngine: true } : {}),
@@ -254,7 +253,7 @@ export function prepareWiringManifest(input) {
254
253
  resourceSuspendEligible ||
255
254
  platformSuspendArmed ||
256
255
  spec.enablePlanMode === true;
257
- announceDurableGateUnavailable({ onNotice: deps.onNotice, mandate: durableMandateSourceOf({ forceDurableGate: runtimeCaps?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined, onAsk: frozenOnAsk }), storeWired: checkpointStore !== undefined, taskStoreDisabled: spec.checkpointStore === "disabled", liveApprover: isLiveApproverSeat(frozenOnAsk), blanketAllow: frozenOnAsk === "allow", liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
256
+ announceDurableGateUnavailable({ onNotice: deps.onNotice, mandate: durableMandateSourceOf({ forceDurableGate: runtimeCaps?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined, onAsk }), storeWired: checkpointStore !== undefined, taskStoreDisabled: spec.checkpointStore === "disabled", liveApprover: isLiveApproverSeat(onAsk), blanketAllow: onAsk === "allow", declaredNone: askSeat.reach === "none", liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
258
257
  const hookIdentity = mintHookInvocationIdentity({
259
258
  sessionId,
260
259
  taskId: hostTaskId,
@@ -48,7 +48,7 @@ export function resumeCheckpointScreen(input) {
48
48
  }
49
49
  }
50
50
  if (readFrozenSkillGrants(cp.state.skillGrants) === undefined)
51
- throw new CheckpointError("checkpoint.invalid_outcome", "this checkpoint carries a skill-grant section (state.skillGrants) that does not read as grant rows — refusing to resume without the grant rows and refusals it carries (corruption guard), the checkpoint stays pending", { reason: "skill_grants_damaged" });
51
+ throw new CheckpointError("checkpoint.invalid_outcome", "this checkpoint carries a skill-grant section (state.skillGrants) that does not read as grant rows under this engine — a damaged section, or a row an earlier release wrote whose entry this release reads as nothing (an entry with a surrounding space, say) — refusing to resume without the grant rows and refusals it carries; the checkpoint stays pending: finish it on a worker of the release that parked it, or expire it", { reason: "skill_grants_damaged" });
52
52
  }
53
53
  if (realApprovalOrgFact(preCasGateBit) !== undefined && runner.deps.permissionRuleStore?.partitions.org !== true) {
54
54
  throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (org ${String(realApprovalOrgFact(preCasGateBit))}, routed as ${String(preCasGateBit?.origin)}) and this worker has no org adjudication wiring (its permission-rule store carries no org partition) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
@@ -40,6 +40,7 @@ import { resumeClaim } from "./resume-claim.js";
40
40
  import { streamSettleBackstop } from "./stream-settle-backstop.js";
41
41
  import { streamReap } from "./stream-reap.js";
42
42
  import { streamSteerVerb } from "./stream-steer-verb.js";
43
+ import { steeringError } from "./steer-admission.js";
43
44
  import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
44
45
  import { streamHaltVerbs } from "./stream-halt-verbs.js";
45
46
  import { streamModeVerb } from "./stream-mode-verb.js";
@@ -411,11 +412,6 @@ export class Runner {
411
412
  locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
412
413
  }).onRunRejected);
413
414
  void settled.then(() => acceptedSteerInputs.clear(), () => acceptedSteerInputs.clear());
414
- const steeringError = (msg, code = "steering.not_running") => {
415
- const e = new Error(`cannot steer: ${msg}`);
416
- e.code = code;
417
- return e;
418
- };
419
415
  const { reapSuspended } = streamReap({
420
416
  live, runner: this.depsSeat,
421
417
  locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Admission bookkeeping for HUMAN INPUTS accepted mid-run: the per-leg ordinal an
3
- * accepted input is stamped with, and the field-for-field "is this repeat the same instruction" test the
4
- * live steer verb applies under an already-used `inputId`. Two accepting arms share both — the stream's
5
- * `steer` verb and the run body — which is why the ordinal is keyed on the leg's harness in a module-level
6
- * WeakMap rather than held by either arm: one counter per leg, one ascending sequence across both.
2
+ * Admission bookkeeping for HUMAN INPUTS accepted mid-run: the per-leg ordinal an accepted input is stamped
3
+ * with, and the acceptance frame's once-only seat. Two accepting arms share the ordinal — the stream's `steer`
4
+ * verb and the run body — which is why it is keyed on the leg's harness in a module-level WeakMap rather than
5
+ * held by either arm: one counter per leg, one ascending sequence across both. Also the stream verbs' typed
6
+ * refusal. (Whether a repeat under a used `inputId` is the same instruction is steer-replay.ts's one judgement,
7
+ * shared with the parked queue.)
7
8
  *
8
9
  * Run-loop machinery (layer 1), not a driven lane: nothing here takes an Input or hands back a Result, and
9
10
  * nothing here reads host state. Named without the `run-` family prefix for that reason.
10
11
  */
11
- import type { AcceptedSteerInput } from "./contracts.js";
12
+ import type { SteerReplayRefusalDetail } from "../steer-replay.js";
12
13
  export declare function nextHumanInputSeq(key: object): number;
13
14
  /** The identity keys a published `queued` frame carried beyond its own `eventId` (`parentToolCallId`, `sourceTaskId`): one fact, stated
14
15
  * once by the frame's publisher and handed to whoever mints that input's other frames, so two frames of one input are never stamped
@@ -19,8 +20,6 @@ export type AcceptanceIdentity = {
19
20
  };
20
21
  export declare function armAcceptance(record: object, publish: () => AcceptanceIdentity): void;
21
22
  export declare function announceAcceptance(record: object): AcceptanceIdentity | undefined;
22
- /** Is a repeat under an already-accepted `inputId` the SAME
23
- * instruction (an idempotent retry) or a different one wearing a used key? Compared field for field,
24
- * exactly like the parked leg's replay check: the key alone is never evidence of a replay, and two
25
- * callers colliding on one id must not silently lose the second instruction. */
26
- export declare function sameAcceptedSteerInput(a: AcceptedSteerInput, b: AcceptedSteerInput): boolean;
23
+ /** The stream verbs' typed refusal: `cannot steer: <msg>`, `code` (`steering.not_running` by default), and the
24
+ * refusal's `detail` when the code carries one (`steering.duplicate_input_id` always does). */
25
+ export declare function steeringError(msg: string, code?: string, detail?: SteerReplayRefusalDetail): Error;
@@ -1,4 +1,3 @@
1
- import { steerPriorityOf } from "../task-notification.js";
2
1
  const humanInputOrdinals = new WeakMap();
3
2
  export function nextHumanInputSeq(key) {
4
3
  let box = humanInputOrdinals.get(key);
@@ -16,11 +15,10 @@ export function armAcceptance(record, publish) {
16
15
  export function announceAcceptance(record) {
17
16
  return acceptanceSeats.get(record)?.();
18
17
  }
19
- export function sameAcceptedSteerInput(a, b) {
20
- return (a.payload === b.payload &&
21
- a.trusted === b.trusted &&
22
- steerPriorityOf(a) === steerPriorityOf(b) &&
23
- a.actor?.id === b.actor?.id &&
24
- a.actor?.hostAsserted === b.actor?.hostAsserted &&
25
- a.actor?.issuer === b.actor?.issuer);
18
+ export function steeringError(msg, code = "steering.not_running", detail) {
19
+ const e = new Error(`cannot steer: ${msg}`);
20
+ e.code = code;
21
+ if (detail !== undefined)
22
+ e.detail = detail;
23
+ return e;
26
24
  }
@@ -1,6 +1,7 @@
1
1
  import type { PushQueue } from "../push-queue.js";
2
2
  import { type TaskEvent, type TaskSpec, type TaskStream } from "../types.js";
3
- import type { AcceptedSteerInput, LiveHandle, RunInternals, RunnerDepsSeat, TaskStreamLiveSeat } from "./contracts.js";
3
+ import type { LiveHandle, RunInternals, RunnerDepsSeat, TaskStreamLiveSeat } from "./contracts.js";
4
+ import { type SteerReplayRecord, type SteerReplayRefusalDetail } from "../steer-replay.js";
4
5
  export interface StreamSteerVerbInput {
5
6
  /** borrowed-readonly — the task spec (the hooks seat, `principal`, `taskId`). */
6
7
  spec: TaskSpec;
@@ -9,7 +10,7 @@ export interface StreamSteerVerbInput {
9
10
  /** borrowed-mutable — the stream's event queue: the `human_input` receipt is pushed here on acceptance. */
10
11
  queue: PushQueue<TaskEvent>;
11
12
  /** borrowed-mutable — this stream's accepted-input dedup domain (the SAME Map the driver clears when the run settles). */
12
- acceptedSteerInputs: Map<string, AcceptedSteerInput>;
13
+ acceptedSteerInputs: Map<string, SteerReplayRecord>;
13
14
  /** borrowed-readonly — the stream's live state: `resultValue` and `handle`, read at each liveness test. */
14
15
  live: TaskStreamLiveSeat;
15
16
  /** borrowed-readonly — the readiness promise (settles to the live handle, or `undefined` when the run ended first). */
@@ -18,8 +19,8 @@ export interface StreamSteerVerbInput {
18
19
  orTimeout: <T>(p: Promise<T>) => Promise<T | undefined>;
19
20
  /** borrowed-readonly — the readiness bound in ms; the birth-window poll's deadline reads it too. */
20
21
  readyTimeoutMs: number;
21
- /** borrowed-readonly — the driver's typed steering refusal (`steering.not_running` by default). */
22
- steeringError: (msg: string, code?: string) => Error;
22
+ /** borrowed-readonly — the driver's typed steering refusal (`steering.not_running` by default; `detail` rides the error). */
23
+ steeringError: (msg: string, code?: string, detail?: SteerReplayRefusalDetail) => Error;
23
24
  /** borrowed-readonly — the Runner's deployment deps, read LIVE (`hooks`, `onError`, `onNotice`, after awaits too). */
24
25
  runner: RunnerDepsSeat;
25
26
  }
@@ -6,7 +6,8 @@ import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "
6
6
  import { isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
7
7
  import { deliverEngineNotice } from "../types.js";
8
8
  import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
9
- import { announceAcceptance, armAcceptance, nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
9
+ import { announceAcceptance, armAcceptance, nextHumanInputSeq } from "./steer-admission.js";
10
+ import { steerReplayRefusal } from "../steer-replay.js";
10
11
  import { asThrownError } from "../thrown-value.js";
11
12
  export function streamSteerVerb(input) {
12
13
  const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input;
@@ -43,7 +44,6 @@ export function streamSteerVerb(input) {
43
44
  };
44
45
  let payload;
45
46
  let mintsAFrame;
46
- let replay;
47
47
  const publishQueued = (h) => {
48
48
  const identity = internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) } : {};
49
49
  queue.push({
@@ -66,7 +66,7 @@ export function streamSteerVerb(input) {
66
66
  if (!mintsAFrame)
67
67
  return;
68
68
  if (typeof inputId === "string")
69
- acceptedSteerInputs.set(inputId, replay);
69
+ acceptedSteerInputs.set(inputId, parkRecord);
70
70
  announceAcceptance(parkRecord);
71
71
  };
72
72
  const deliver = async () => {
@@ -77,16 +77,14 @@ export function streamSteerVerb(input) {
77
77
  throw steeringError("the task is not running");
78
78
  payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
79
79
  mintsAFrame = payload.trim().length !== 0;
80
- replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) };
81
80
  if (typeof inputId === "string") {
82
81
  const prior = acceptedSteerInputs.get(inputId);
83
82
  if (prior !== undefined) {
84
83
  if (live.resultValue !== undefined || h.loop.ended)
85
84
  throw steeringError("the task is no longer running");
86
- if (!sameAcceptedSteerInput(prior, replay)) {
87
- throw steeringError("a different steering instruction was already accepted under this inputId — re-issue this one with a fresh inputId " +
88
- "(an identical payload would have been an idempotent retry)", "steering.duplicate_input_id");
89
- }
85
+ const refusal = steerReplayRefusal(prior, parkRecord, { seat: "accepted" });
86
+ if (refusal !== undefined)
87
+ throw steeringError(refusal.message, "steering.duplicate_input_id", refusal.detail);
90
88
  return;
91
89
  }
92
90
  }
@@ -169,9 +169,10 @@ export declare function renderSkillLinesWithin(skills: ReadonlyArray<SkillListin
169
169
  *
170
170
  * A SUCCESSFUL activation writes the skill's two lists into the run's grant table (`grants`, origin `model`): its
171
171
  * `allowed-tools` then run without asking and its `disallowed-tools` are refused for the rest of the run; an unknown skill
172
- * throws first and writes nothing. Activating a skill that declares a list was itself a question (the gate reads this
172
+ * throws first and writes nothing, as does one whose `disallowed-tools` holds an entry that reads as nothing (the table
173
+ * refuses it: the body is not served, nothing is granted). Activating a skill that declares a list was itself a question (the gate reads this
173
174
  * tool's seat off the call's arguments — `Execute skill: <name>`), so a write here always follows a statement. The tool
174
175
  * runs ALONE in its batch: a call after it in the same message is judged after the rows exist.
175
176
  */
176
- export declare function createSkillTool(skills: SkillLike[], grants?: SkillGrantTable): AgentTool;
177
+ export declare function createSkillTool(skills: SkillLike[], grants: SkillGrantTable): AgentTool;
177
178
  export {};
@@ -188,7 +188,7 @@ export function createSkillTool(skills, grants) {
188
188
  `(Only skills registered with this deployment are loadable — other products' skills directories are not read. ` +
189
189
  `If the user wants one of those here, read its source file and recreate its content for this deployment instead.)`);
190
190
  }
191
- grants?.activate(skill, "model");
191
+ grants.activate(skill, "model");
192
192
  const baseDir = skill.baseDir;
193
193
  let body = baseDir !== undefined ? substituteSkillRoot(skill.content, baseDir) : skill.content;
194
194
  if (skill.files !== undefined && skill.files.length > 0) {
@@ -53,7 +53,7 @@ export interface PutRulesOptions {
53
53
  operator?: boolean;
54
54
  }
55
55
  /** The {@link SessionPolicyError.detail} of a tool-name refusal: which bucket, which entry, the judgment's word, and the
56
- * 1:1 successor when the retired name has one. */
56
+ * spelling to write instead when the judgment has one (a retired name's 1:1 successor, an unmintable segment's minted name). */
57
57
  export interface SessionRuleNameRefusal {
58
58
  field: SessionRuleToolNameField;
59
59
  name: string;
@@ -63,8 +63,8 @@ export interface SessionRuleNameRefusal {
63
63
  /** Typed error from {@link SessionPolicyStore.putRules}. `conflict` = stale `expectedRev`; `loosen_forbidden`
64
64
  * = a non-operator write tried to relax the rules; `corrupt` = a backend could not read the erase-surviving
65
65
  * rev mark ({@link SessionPolicyStore.deleteBySession}) and refuses rather than stamp an unproven rev; the fourth
66
- * code = a tool-name bucket names a retired or prefix-less name ({@link assertSessionRuleNamesCurrent}) — `detail` is
67
- * present on that code and only on it. */
66
+ * code = a tool-name bucket names a retired name, a prefix-less `__` name or a peer segment no mint spells
67
+ * ({@link assertSessionRuleNamesCurrent}) — `detail` is present on that code and only on it. */
68
68
  export declare class SessionPolicyError extends Error {
69
69
  readonly code: "conflict" | "loosen_forbidden" | "corrupt" | "legacy_tool_name";
70
70
  readonly detail?: SessionRuleNameRefusal | undefined;
@@ -40,10 +40,24 @@ export function assertSessionRuleNamesCurrent(rules, mountedToolNames = NO_DECLA
40
40
  if (verdict.kind === "current")
41
41
  continue;
42
42
  const quoted = JSON.stringify(name);
43
- const successor = verdict.kind === "retired" ? verdict.currentName : undefined;
44
- throw new SessionPolicyError("legacy_tool_name", verdict.kind === "retired"
45
- ? `session rules ${field} names ${quoted}, a retired tool name (${verdict.guidance}) — with it in place no run of this session could start, so nothing was written.${successor !== undefined ? ` Use the current name ${JSON.stringify(successor)}.` : ""} If this deployment mounts its own tool under ${quoted}, declare it in the store's mountedToolNames.`
46
- : `session rules ${field} names ${quoted}, which carries the "__" namespace separator but no protocol prefix — no tool is named that way (protocol tools are named ${NAMESPACED_NAME_SHAPES}), and with it in place no run of this session could start, so nothing was written.`, { field, name, verdict: verdict.kind, ...(successor !== undefined ? { currentName: successor } : {}) });
43
+ const successor = verdict.kind === "unprefixed_namespace" ? undefined : verdict.currentName;
44
+ let message;
45
+ switch (verdict.kind) {
46
+ case "retired":
47
+ message = `session rules ${field} names ${quoted}, a retired tool name (${verdict.guidance}) — with it in place no run of this session could start, so nothing was written.${successor !== undefined ? ` Use the current name ${JSON.stringify(successor)}.` : ""} If this deployment mounts its own tool under ${quoted}, declare it in the store's mountedToolNames.`;
48
+ break;
49
+ case "unprefixed_namespace":
50
+ message = `session rules ${field} names ${quoted}, which carries the "__" namespace separator but no protocol prefix — no tool is named that way (protocol tools are named ${NAMESPACED_NAME_SHAPES}), and with it in place no run of this session could start, so nothing was written.`;
51
+ break;
52
+ case "unmintable_segment":
53
+ message = `session rules ${field} names ${quoted}, which can match no tool (${verdict.guidance}) — with it in place no run of this session could start, so nothing was written.${successor !== undefined ? ` Write ${JSON.stringify(successor)}.` : ""}`;
54
+ break;
55
+ default: {
56
+ const exhaustive = verdict;
57
+ return exhaustive;
58
+ }
59
+ }
60
+ throw new SessionPolicyError("legacy_tool_name", message, { field, name, verdict: verdict.kind, ...(successor !== undefined ? { currentName: successor } : {}) });
47
61
  }
48
62
  }
49
63
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What a Skill card's approval is bound to, as ONE value: the skill the call names and the two lists approving it releases,
3
+ * in the rule grammar's canonical spelling (de-duplicated, sorted). Its hash is the `boundGrantDigest` a parked approval is
4
+ * judged by, so a reader holds the list a person approved and the binding that covers it as the same fact — the live
5
+ * `AskRequest`, the parked row and the inbox projection all carry it (minted by `skillGrantBindingMembers`).
6
+ *
7
+ * A leaf on purpose: the checkpoint projection reads it at its boundary and may not import the module that mints it (it imports only
8
+ * the hash).
9
+ */
10
+ export interface BoundSkillGrants {
11
+ /** The skill's name as the loaded table spells it, or `null` when the call names no loaded skill (it grants nothing). */
12
+ readonly skill: string | null;
13
+ /** The skill's `allowed-tools` entries — what approving the activation lets be used without asking. */
14
+ readonly allowedTools: readonly string[];
15
+ /** The skill's `disallowed-tools` entries — what activating it refuses for the rest of the run. */
16
+ readonly disallowedTools: readonly string[];
17
+ }
18
+ /**
19
+ * A stored value read as a {@link BoundSkillGrants}, whole or not at all: a fresh copy of the three members and nothing else,
20
+ * or `undefined` when any member is not of its type. Every member is read ONCE, so the value screened is the value published.
21
+ */
22
+ export declare function readBoundSkillGrants(raw: unknown): BoundSkillGrants | undefined;
23
+ /** The card members a call to the engine's `Skill` tool carries on every face its card has — empty for any other call. */
24
+ export interface SkillGrantBindingMembers {
25
+ /** The hash of {@link boundGrants}: the binding a parked approval of the card is judged by. */
26
+ boundGrantDigest?: string;
27
+ boundGrants?: BoundSkillGrants;
28
+ }
29
+ /** The leg's binding factory ({@link SkillGrantBindingMembers} for a call over `args`), the one every ask-mint site spreads. */
30
+ export type GrantBindingOf = (call: {
31
+ readonly toolName: string;
32
+ }, args: unknown) => SkillGrantBindingMembers;
33
+ /**
34
+ * The binding members of a stored parked row, as the inbox projection publishes them: the digest when it is a string, and the lists
35
+ * only as the value that digest hashes — {@link readBoundSkillGrants} whole, and equal to the digest by hash. A pair that
36
+ * disagrees, or lists beside no digest, would show an approver one list while the approval binds another: the lists read as absent.
37
+ */
38
+ export declare function readBoundBinding(row: {
39
+ readonly boundGrantDigest?: unknown;
40
+ readonly boundGrants?: unknown;
41
+ } | undefined): {
42
+ boundGrantDigest?: string;
43
+ boundGrants?: BoundSkillGrants;
44
+ };
@@ -0,0 +1,22 @@
1
+ import { boundInputHashOf } from "./canonical-json.js";
2
+ export function readBoundSkillGrants(raw) {
3
+ if (typeof raw !== "object" || raw === null)
4
+ return undefined;
5
+ const { skill, allowedTools: allowed, disallowedTools: disallowed } = raw;
6
+ if (skill !== null && typeof skill !== "string")
7
+ return undefined;
8
+ const entries = (list) => {
9
+ if (!Array.isArray(list))
10
+ return undefined;
11
+ const copy = Array.from(list);
12
+ return copy.every((e) => typeof e === "string") ? copy : undefined;
13
+ };
14
+ const allowedTools = entries(allowed);
15
+ const disallowedTools = entries(disallowed);
16
+ return allowedTools !== undefined && disallowedTools !== undefined ? { skill, allowedTools, disallowedTools } : undefined;
17
+ }
18
+ export function readBoundBinding(row) {
19
+ const digest = row?.boundGrantDigest;
20
+ const grants = readBoundSkillGrants(row?.boundGrants);
21
+ return { ...(typeof digest === "string" ? { boundGrantDigest: digest } : {}), ...(grants !== undefined && typeof digest === "string" && boundInputHashOf(grants) === digest ? { boundGrants: grants } : {}) };
22
+ }