@sema-agent/core 7.40.1 → 7.41.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 (134) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +10 -2
  3. package/dist/agents/agent-transcript-tool.js +52 -26
  4. package/dist/agents/list-agents-tool.d.ts +17 -2
  5. package/dist/agents/list-agents-tool.js +28 -16
  6. package/dist/agents/peer-session-drain.d.ts +3 -1
  7. package/dist/agents/peer-session-drain.js +1 -0
  8. package/dist/agents/subagent-steps.d.ts +12 -2
  9. package/dist/agents/subagent-steps.js +8 -4
  10. package/dist/agents/subagent.d.ts +1 -17
  11. package/dist/agents/subagent.js +12 -24
  12. package/dist/core/agent-definition.d.ts +19 -40
  13. package/dist/core/agent-state.d.ts +91 -0
  14. package/dist/core/agent-state.js +79 -0
  15. package/dist/core/ask-origin.d.ts +28 -6
  16. package/dist/core/ask-origin.js +9 -5
  17. package/dist/core/ask-unresolvable-notice.d.ts +27 -4
  18. package/dist/core/ask-unresolvable-notice.js +8 -0
  19. package/dist/core/auto-promote.d.ts +14 -27
  20. package/dist/core/auto-promote.js +5 -5
  21. package/dist/core/checkpoint-store.d.ts +11 -12
  22. package/dist/core/decision-receipt.d.ts +5 -2
  23. package/dist/core/decision-receipt.js +1 -1
  24. package/dist/core/engine-notice.d.ts +7 -1
  25. package/dist/core/gate-crash-trace.d.ts +1 -1
  26. package/dist/core/gate-fold.js +7 -3
  27. package/dist/core/gate-lanes.js +22 -3
  28. package/dist/core/governance-codes.d.ts +3 -1
  29. package/dist/core/governance-codes.js +3 -0
  30. package/dist/core/hooks.d.ts +20 -6
  31. package/dist/core/hooks.js +28 -28
  32. package/dist/core/mcp-tool-meta.d.ts +24 -0
  33. package/dist/core/mcp-tool-meta.js +17 -0
  34. package/dist/core/mcp.d.ts +1 -6
  35. package/dist/core/mcp.js +6 -7
  36. package/dist/core/mode-policy.d.ts +25 -0
  37. package/dist/core/mode-policy.js +8 -0
  38. package/dist/core/protocol-table.d.ts +3 -0
  39. package/dist/core/protocol-table.js +3 -0
  40. package/dist/core/reminder-mint.d.ts +21 -1
  41. package/dist/core/reminder-mint.js +20 -0
  42. package/dist/core/retired-keys.d.ts +2 -1
  43. package/dist/core/retired-keys.js +13 -2
  44. package/dist/core/rule-denial-sentence.d.ts +2 -2
  45. package/dist/core/runner/call-hook-stations.d.ts +106 -0
  46. package/dist/core/runner/call-hook-stations.js +118 -0
  47. package/dist/core/runner/contracts.d.ts +35 -25
  48. package/dist/core/runner/gate-exit.d.ts +5 -1
  49. package/dist/core/runner/gate-exit.js +10 -6
  50. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -0
  51. package/dist/core/runner/prepare-delegation-surface.js +4 -4
  52. package/dist/core/runner/prepare-gate-stations.d.ts +8 -3
  53. package/dist/core/runner/prepare-gate-stations.js +13 -70
  54. package/dist/core/runner/prepare-inherited-gate.d.ts +6 -3
  55. package/dist/core/runner/prepare-inherited-gate.js +5 -3
  56. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  57. package/dist/core/runner/prepare-park-ask.js +4 -1
  58. package/dist/core/runner/prepare-policy-chain.d.ts +6 -5
  59. package/dist/core/runner/prepare-policy-chain.js +14 -25
  60. package/dist/core/runner/prepare-project-context.d.ts +15 -16
  61. package/dist/core/runner/prepare-project-context.js +15 -11
  62. package/dist/core/runner/prepare-run-refs.d.ts +8 -8
  63. package/dist/core/runner/prepare-run-refs.js +3 -3
  64. package/dist/core/runner/prepare-safety-scan.js +3 -3
  65. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -1
  66. package/dist/core/runner/prepare-suspend-saga.js +2 -1
  67. package/dist/core/runner/prepare-task.js +14 -12
  68. package/dist/core/runner/prepare-turn-wiring.d.ts +3 -1
  69. package/dist/core/runner/prepare-turn-wiring.js +16 -13
  70. package/dist/core/runner/resume-admission.js +2 -1
  71. package/dist/core/runner/resume-apply.js +12 -23
  72. package/dist/core/runner/resume-checkpoint-screen.js +3 -0
  73. package/dist/core/runner/run-leg.js +61 -47
  74. package/dist/core/runner/runtask.d.ts +1 -1
  75. package/dist/core/runner/session-rule-policy.d.ts +7 -19
  76. package/dist/core/runner/session-rule-policy.js +1 -2
  77. package/dist/core/runner/skill-base-dir.d.ts +10 -0
  78. package/dist/core/runner/skill-base-dir.js +4 -0
  79. package/dist/core/runner/synthetic-tools.d.ts +11 -17
  80. package/dist/core/runner/synthetic-tools.js +7 -8
  81. package/dist/core/safety-axis-vocab.d.ts +2 -2
  82. package/dist/core/safety-axis-vocab.js +2 -0
  83. package/dist/core/session-policy-store.d.ts +63 -11
  84. package/dist/core/session-policy-store.js +52 -6
  85. package/dist/core/skill-grant.d.ts +239 -0
  86. package/dist/core/skill-grant.js +313 -0
  87. package/dist/core/skills-directory.d.ts +5 -34
  88. package/dist/core/skills-directory.js +9 -58
  89. package/dist/core/task-registry-agent.d.ts +14 -2
  90. package/dist/core/task-registry-agent.js +23 -8
  91. package/dist/core/task-registry-shared.d.ts +6 -0
  92. package/dist/core/task-registry.d.ts +16 -0
  93. package/dist/core/task-registry.js +19 -6
  94. package/dist/core/task-spec.d.ts +10 -0
  95. package/dist/core/tool-face.d.ts +3 -2
  96. package/dist/core/tool-name-aliases.d.ts +38 -12
  97. package/dist/core/tool-name-aliases.js +16 -0
  98. package/dist/core/tool-name-readings.d.ts +6 -2
  99. package/dist/core/tool-name-readings.js +6 -4
  100. package/dist/core/tool-policy.d.ts +2 -2
  101. package/dist/core/tool-registry.d.ts +1 -4
  102. package/dist/core/tool-registry.js +0 -3
  103. package/dist/core/tool-spec.d.ts +11 -13
  104. package/dist/core/tools.d.ts +1 -1
  105. package/dist/core/trace.d.ts +15 -0
  106. package/dist/core/types.d.ts +1 -1
  107. package/dist/engine/harness/agent-harness.d.ts +12 -1
  108. package/dist/engine/harness/agent-harness.js +17 -13
  109. package/dist/engine/loop/agent-loop.d.ts +16 -1
  110. package/dist/engine/loop/agent-loop.js +9 -15
  111. package/dist/engine/loop/types.d.ts +2 -0
  112. package/dist/index.d.ts +9 -4
  113. package/dist/index.js +4 -2
  114. package/dist/stores/file/adoption/adopt.js +23 -16
  115. package/dist/stores/file/index.d.ts +8 -2
  116. package/dist/stores/file/index.js +2 -2
  117. package/dist/stores/file/mailbox-store.js +2 -2
  118. package/dist/stores/file/permission-rule-adopt.js +10 -1
  119. package/dist/stores/file/permission-rule-store.d.ts +6 -4
  120. package/dist/stores/file/permission-rule-store.js +127 -59
  121. package/dist/stores/file/session-policy-store.d.ts +86 -14
  122. package/dist/stores/file/session-policy-store.js +135 -118
  123. package/dist/stores/file/task-list-store.d.ts +5 -0
  124. package/dist/stores/file/task-list-store.js +18 -4
  125. package/dist/stores/file/usage-window-store.d.ts +9 -1
  126. package/dist/stores/file/usage-window-store.js +47 -33
  127. package/dist/tools/fs/safety.d.ts +1 -1
  128. package/package.json +1 -1
  129. package/test/export-surface.snapshot.json +45 -5
  130. package/wire-changes/core@7.41.0.json +1865 -0
  131. package/dist/core/runner/active-skill-scope.d.ts +0 -58
  132. package/dist/core/runner/active-skill-scope.js +0 -133
  133. package/dist/core/skill-tool-specifier.d.ts +0 -80
  134. package/dist/core/skill-tool-specifier.js +0 -57
@@ -1,7 +1,7 @@
1
1
  import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
2
- import { mountedToolPermissionSeat } from "../mode-policy.js";
2
+ import { mountedToolRequiresUserInteraction } from "../mode-policy.js";
3
3
  import { complianceCallDenial } from "../compliance.js";
4
- import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, runHookSeat, runToolGate } from "../hooks.js";
4
+ import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js";
5
5
  import { screenGateOutcome } from "../gate-outcome.js";
6
6
  import { directDeny } from "./gate-exit.js";
7
7
  import { emitTrace } from "../trace.js";
@@ -12,7 +12,7 @@ import { classifierUnavailableSeat } from "./classifier-ladder.js";
12
12
  import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
13
13
  import { callFaceSeat, rosterToolName, toolCallFaceOf } from "../tool-roster.js";
14
14
  import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
15
- import { noteHostRewrite } from "../frame-settlement.js";
15
+ import { createApprovedCallScreen, createToolResultStation } from "./call-hook-stations.js";
16
16
  const PEER_MESSAGE_NAMES = new Set([SEND_MESSAGE_TOOL_NAME]);
17
17
  function screenGateResult(result) {
18
18
  if (result.suspend !== undefined)
@@ -46,7 +46,8 @@ export function maybeHumanRejectionHalt(input) {
46
46
  };
47
47
  }
48
48
  export function prepareGateStations(input) {
49
- const { askLane, parkAsk, tools, toolRosterDeltas, 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, hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement } = input;
49
+ const { askLane, parkAsk, tools, toolRosterDeltas, 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, hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement, seatInputsFor } = input;
50
+ const gatePreToolUse = ownGatePreToolUse !== undefined ? (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) : undefined;
50
51
  const releasesAnnounced = new Map();
51
52
  const onModeReleased = (info) => {
52
53
  const table = info.readUnestablished === true ? input.readDenyInForce() : undefined;
@@ -116,7 +117,7 @@ export function prepareGateStations(input) {
116
117
  ...(e.signal !== undefined ? { callSignal: e.signal } : {}),
117
118
  identity: hookIdentity,
118
119
  reminderMark,
119
- ...(ownGatePreToolUse !== undefined ? { preToolUse: (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) } : {}),
120
+ ...(gatePreToolUse !== undefined ? { preToolUse: gatePreToolUse } : {}),
120
121
  hookTimeoutMs,
121
122
  ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
122
123
  ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
@@ -138,7 +139,8 @@ export function prepareGateStations(input) {
138
139
  ...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
139
140
  permissionMode: () => permissionMode.current,
140
141
  ...(markSources.size > 0 ? { toolMarkSources: markSources } : {}),
141
- ...(mountedToolPermissionSeat(tools, e.toolName) === "passthrough" ? { permissionSeat: "passthrough" } : {}),
142
+ ...seatInputsFor(e.toolName, wireOf("gate.skillActivationSeat", call)),
143
+ ...(mountedToolRequiresUserInteraction(tools, e.toolName) ? { requiresUserInteraction: true } : {}),
142
144
  ...(a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
143
145
  onModeReleased,
144
146
  ancestorArbitrationOf: (toolCallId) => ancestorArbitrations.get(toolCallId),
@@ -239,68 +241,9 @@ export function prepareGateStations(input) {
239
241
  });
240
242
  }
241
243
  }
242
- const toolResultStationArmed = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure);
243
- if (toolResultStationArmed) {
244
- harness.on("tool_result", async (e) => {
245
- if (blockedToolCalls.delete(e.toolCallId)) {
246
- return undefined;
247
- }
248
- let content = e.content;
249
- let changed = false;
250
- const post = { toolName: rosterToolName(toolRosterDeltas.current, e.toolName) };
251
- if (e.isError) {
252
- if (hooks?.postToolUseFailure) {
253
- let clonedDetails = e.details;
254
- try {
255
- clonedDetails = structuredClone(e.details);
256
- }
257
- catch {
258
- }
259
- const failedName = wireOf("hook.postToolUseFailure", post);
260
- const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(failedName, e.input, {
261
- error: e.content
262
- .filter((c) => c.type === "text")
263
- .map((c) => c.text)
264
- .join("\n"),
265
- isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
266
- content: e.content.map((c) => ({ ...c })),
267
- details: clonedDetails,
268
- }, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
269
- if (failureSeat.expired) {
270
- notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
271
- }
272
- const patch = failureSeat.expired ? undefined : failureSeat.value;
273
- if (patch?.additionalContext) {
274
- content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
275
- changed = true;
276
- }
277
- }
278
- }
279
- else if (hooks?.postToolUse) {
280
- const ranName = wireOf("hook.postToolUse", post);
281
- const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(ranName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: ranName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
282
- if (successSeat.expired) {
283
- notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
284
- }
285
- const patch = successSeat.expired ? undefined : successSeat.value;
286
- if (patch?.updatedOutput) {
287
- content = patch.updatedOutput;
288
- changed = true;
289
- noteHostRewrite(frameSettlement, e.toolCallId);
290
- }
291
- if (patch?.additionalContext) {
292
- content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
293
- changed = true;
294
- }
295
- }
296
- const pre = preToolContexts.get(e.toolCallId);
297
- if (pre) {
298
- preToolContexts.delete(e.toolCallId);
299
- content = [...content, ...pre.map((t) => ({ type: "text", text: formatHookFeedback(t, reminderMark) }))];
300
- changed = true;
301
- }
302
- return changed ? { content } : undefined;
303
- });
304
- }
305
- return { toolCallStationArmed: toolCallGateArmedRef.armed, toolResultStationArmed };
244
+ const toolResultStation = createToolResultStation({ hooks, blockedToolCalls, toolRosterDeltas, preToolContexts, hookTimeoutMs, abortController, spec, handsCwdRef, hookEnvFace, hookIdentity, permissionMode, notifyOwnHookCrash, reminderMark, frameSettlement });
245
+ if (toolResultStation !== undefined)
246
+ harness.on("tool_result", toolResultStation);
247
+ const approvedCallScreen = createApprovedCallScreen({ preToolUse: gatePreToolUse, hookTimeoutMs, abortController, handsCwdRef, hookEnvFace, hookIdentity, notifyOwnHookCrash, preToolContexts });
248
+ return { toolCallStationArmed: toolCallGateArmedRef.armed, toolResultStationArmed: toolResultStation !== undefined, ...(approvedCallScreen !== undefined ? { approvedCallScreen } : {}) };
306
249
  }
@@ -46,7 +46,7 @@ import type { SessionStore } from "../session.js";
46
46
  import { type OnAsk } from "../tool-policy.js";
47
47
  import type { PermissionMode, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext, ToolSpec } from "../types.js";
48
48
  import { PermissionModeController } from "../mode-policy.js";
49
- import type { ActiveSkillScope } from "./active-skill-scope.js";
49
+ import { type SkillGrantTable } from "../skill-grant.js";
50
50
  import type { InheritedGate, Prepared, PrepareResume, RunInternals, StopForDenialLimit, ToolFaceSnapshot, WriteFaceSeat } from "./contracts.js";
51
51
  export interface PrepareInheritedGateInput {
52
52
  /** borrowed-readonly — the REBOUND spec. Read: `shellGate` (the own doctrine the chain fold tightens), `durableApproval`
@@ -107,8 +107,8 @@ export interface PrepareInheritedGateInput {
107
107
  harnessRef: {
108
108
  current?: AgentHarness;
109
109
  };
110
- /** borrowed-readonly — the per-task skill scope (the ctx's `activeSkillScope` snapshot reads it call-time). Not written here. */
111
- skillScope: ActiveSkillScope;
110
+ /** borrowed-readonly — the per-task skill grant table (the ctx's `activeSkillGrants` capture and the checkpoint section read it call-time). Not written here. */
111
+ skillGrants: SkillGrantTable;
112
112
  /** borrowed-readonly — the filtered display-forwarding wrapper (ctx seat; absent ⇒ no key). */
113
113
  forwardEvent: ((e: TaskEvent) => void) | undefined;
114
114
  /** borrowed-readonly — the retain ledger (ctx seat; absent ⇒ no key). */
@@ -276,6 +276,9 @@ export interface PrepareInheritedGateResult {
276
276
  writeFaceCheckpointSection: () => {
277
277
  face: "open" | "roots";
278
278
  } | undefined;
279
+ /** owned — the SKILL-GRANT checkpoint section builder: the rows this run's table grew (the model's activations, a
280
+ * delegated child's capture), read off the live table at mint time; absent when there are none. */
281
+ skillGrantCheckpointSection: () => import("../skill-grant.js").SkillGrantRecord[] | undefined;
279
282
  /** owned — the F-012 stamp predicate (opaque constraints present ∨ seed frozen chain ∨ provenance-armed). */
280
283
  f012CheckpointState: () => boolean;
281
284
  /** owned — the FROZEN approver seat (spec over deps) every station of this leg and its children's chain resolve at. */
@@ -9,10 +9,11 @@ import { deliverEngineNotice } from "../types.js";
9
9
  import { PermissionModeController, modeRequestAfterResume } from "../mode-policy.js";
10
10
  import { SessionModeSeats } from "./session-mode-seats.js";
11
11
  import { recordedModeSeed } from "../checkpoint-store.js";
12
+ import { frozenSkillGrants } from "../skill-grant.js";
12
13
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
13
14
  import { asThrownError } from "../thrown-value.js";
14
15
  export function prepareInheritedGate(input) {
15
- const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
16
+ const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
16
17
  const liveInheritedGate = internals?.inheritedGate;
17
18
  const seedInheritedGate = resume?.seed.inheritedGate;
18
19
  const inheritedAncestorRules = (() => {
@@ -108,6 +109,7 @@ export function prepareInheritedGate(input) {
108
109
  const seat = input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace();
109
110
  return seat === undefined ? undefined : seat.inForce() === "open" ? { face: "open" } : seat.fenced ? { face: "roots" } : undefined;
110
111
  };
112
+ const skillGrantCheckpointSection = () => frozenSkillGrants(skillGrants);
111
113
  const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
112
114
  seedInheritedGate?.constraintChain !== undefined ||
113
115
  internals?.delegationProvenance !== undefined;
@@ -247,7 +249,7 @@ export function prepareInheritedGate(input) {
247
249
  reminderDisclosureCounts,
248
250
  ...(centerAdoption !== undefined ? { centerArtifactDigest: centerAdoption.artifact.artifactDigest } : {}),
249
251
  ...(centerAdoption?.sourceRevision !== undefined ? { centerSourceRevision: centerAdoption.sourceRevision } : {}),
250
- activeSkillScope: () => skillScope.active(),
252
+ activeSkillGrants: () => skillGrants.records(),
251
253
  inheritedGateForChildren,
252
254
  delegationProvenanceForChildren,
253
255
  delegationSettlement: delegationSettlementForChildren,
@@ -294,5 +296,5 @@ export function prepareInheritedGate(input) {
294
296
  ...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
295
297
  };
296
298
  };
297
- return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, commitModeSeats, endModeCeiling, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
299
+ return { 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 };
298
300
  }
@@ -32,6 +32,7 @@ import { type ContentAskBinding } from "./content-ask-bindings.js";
32
32
  import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, SuspendSaga } from "./contracts.js";
33
33
  import type { createRuleOffersOf } from "./permission-rule-lanes.js";
34
34
  import { type ToolRosterDeltaSeat } from "../tool-roster.js";
35
+ import { type SkillGrantSource } from "../skill-grant.js";
35
36
  export interface PrepareParkAskInput {
36
37
  /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then both closures are
37
38
  * absent). Read: `composedCallSignal` (the fence and the delivery race), `approvalPreviewOf` (the row's preview). */
@@ -140,6 +141,9 @@ export interface PrepareParkAskInput {
140
141
  orgAdmissionCheckpointState: () => boolean;
141
142
  /** borrowed-readonly — the policy-chain phase's rule-offer factory (the row's suggestion members). */
142
143
  ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
144
+ /** borrowed-readonly — the leg's loaded skills (what its `Skill` tool serves); a parked `Skill` call's row binds the named
145
+ * skill — its name and its lists — from them (`boundGrantDigest`). Empty when the leg loads none. */
146
+ skillSpecs: readonly SkillGrantSource[];
143
147
  /** borrowed-readonly — the injectable wall clock (the row's `suspendedAt`). */
144
148
  now: () => number;
145
149
  /** borrowed-readonly — the per-task human-review accumulator; snapshotted onto the row (never written here). */
@@ -17,6 +17,8 @@ import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedT
17
17
  import { remoteEnvFailureNote } from "./remote-env-retry.js";
18
18
  import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js";
19
19
  import { seatOf, wireOf } from "../tool-name-readings.js";
20
+ import { skillCallGrantDigest } from "../skill-grant.js";
21
+ import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
20
22
  class ParkRefusal extends Error {
21
23
  constructor(message, options) {
22
24
  super(message, options);
@@ -27,7 +29,7 @@ const wordlessDemand = (toolName) => {
27
29
  throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
28
30
  };
29
31
  export function prepareParkAsk(input) {
30
- const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, 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, now, humanReviewRef, abortController, harness, pausedRef } = input;
32
+ const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, 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 } = input;
31
33
  if (askLane === undefined || saga === undefined)
32
34
  return { parkAsk: undefined };
33
35
  const { composedCallSignal, approvalPreviewOf } = askLane;
@@ -316,6 +318,7 @@ export function prepareParkAsk(input) {
316
318
  ...askCarryRowMembers(carry),
317
319
  ...ruleOffersOf(req, parkedArgs, clearanceFacts),
318
320
  boundInputHash: boundInputHashOf(parkedArgs),
321
+ ...((grantDigest) => (grantDigest !== undefined ? { boundGrantDigest: grantDigest } : {}))(skillCallGrantDigest(req, parkedArgs, skillSpecs, SKILL_TOOL_NAME)),
319
322
  batchToolCallIds,
320
323
  completedCallIds,
321
324
  }),
@@ -30,7 +30,7 @@ import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
30
30
  import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect } from "../types.js";
31
31
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
32
32
  import type { ReadDenyMatcher } from "../../tools/fs/read-deny.js";
33
- import { type ActiveSkillScope } from "./active-skill-scope.js";
33
+ import type { SkillGrantLane, SkillGrantTable } from "../skill-grant.js";
34
34
  import type { InheritedGate, IrreversibilityTier, PrepareResume, RunInternals, StopForDenialLimit } from "./contracts.js";
35
35
  import { type Arbitration } from "../ask-origin.js";
36
36
  import { type ModeGateSeats, type PermissionModeController } from "../mode-policy.js";
@@ -118,10 +118,9 @@ export interface PreparePolicyChainInput {
118
118
  inheritedParentConstraints: NonNullable<InheritedGate["parentConstraints"]>[number][] | undefined;
119
119
  /** borrowed-readonly — the resumed leg's SEED half; only `constraintChain` (the frozen deny projections). */
120
120
  seedInheritedGate: Pick<NonNullable<PrepareResume["seed"]["inheritedGate"]>, "constraintChain"> | undefined;
121
- /** borrowed-readonly — whether any skill carries a manifest or the task inherited a frame (the skill-scope layer). */
122
- hasSkillManifest: boolean;
123
- /** borrowed-readonly — the live skill scope the Gate-3 layer judges over. */
124
- skillScope: ActiveSkillScope;
121
+ /** borrowed-readonly — the run's skill grant table; this phase binds its lane to the call facts the person's rule lane
122
+ * reads (root, session, live cwd, the execution environment's home). Not written here. */
123
+ skillGrants: SkillGrantTable;
125
124
  /** borrowed-readonly — the FROZEN approver seat (the pass-through test's approver identity). */
126
125
  frozenOnAsk: OnAsk | undefined;
127
126
  /** borrowed-readonly — the identity of this task's hook-env source (the pass-through test's environment term). */
@@ -172,6 +171,8 @@ export interface PreparePolicyChainResult {
172
171
  basePolicyForResumeEdit: ToolPolicy | undefined;
173
172
  /** owned — the deny-narrowing fold (`Prepared.denyNarrowingPolicy`), or undefined when no layer exists. */
174
173
  denyNarrowingPolicy: ToolPolicy | undefined;
174
+ /** owned — the skill grant lane (`Prepared.skillGrants`; the gate's skill member and deny rows read it). */
175
+ skillGrantLane: SkillGrantLane;
175
176
  /** owned — the caller-facing policy layers (the wiring manifest reads their COUNT). */
176
177
  policyLayers: ToolPolicy[];
177
178
  /** owned — THE effective policy the gate station adjudicates with (the fold plus its final-form re-check), or undefined. */
@@ -7,10 +7,9 @@ import { assertHookSeatsCallable } from "../hook-seats.js";
7
7
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
8
8
  import { isIsolated } from "../remote-env.js";
9
9
  import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, wrapToolPolicy, attestAskUnresolvableCause } from "../tool-policy.js";
10
- import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
11
- import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
10
+ import { judgeRuleToolName } from "../tool-name-aliases.js";
11
+ import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
12
12
  import { emitTrace } from "../trace.js";
13
- import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
14
13
  import { arbitrateByMode, askClassMembersOf, askMandateMember } from "../ask-origin.js";
15
14
  import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js";
16
15
  const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true });
@@ -22,7 +21,7 @@ import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-
22
21
  import { thrownText } from "../thrown-value.js";
23
22
  import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
24
23
  export async function preparePolicyChain(input) {
25
- const { 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, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
24
+ const { 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, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
26
25
  const ancestorArbitrations = new Map();
27
26
  const arbitrateInherited = (ask, toolCallId, judged) => {
28
27
  const verdict = arbitrateByMode(permissionMode.current, judged.classification, { origin: judged.origin, ...askMandateMember(ancestorFacts(judged.ask.action === "ask" ? judged.ask : undefined)) });
@@ -44,21 +43,18 @@ export async function preparePolicyChain(input) {
44
43
  for (const g of nameGroups) {
45
44
  for (const [kind, list] of [["deny", g.deny], ["ask", g.ask], ["allow", g.allow]]) {
46
45
  for (const n of list ?? []) {
46
+ const verdict = judgeRuleToolName(n, known);
47
+ if (verdict.kind !== "current") {
48
+ const err = new Error(verdict.kind === "retired"
49
+ ? `tool policy ${kind}-list entry "${n}" is a RETIRED tool name (${verdict.guidance}) and matches nothing in this run's roster — legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Update the deployment's rule to the current name.`
50
+ : `tool policy ${kind}-list entry "${n}" carries the "__" namespace separator but no protocol prefix, and matches nothing in this run's roster — protocol tools are named ${NAMESPACED_NAME_SHAPES}, and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with the owning protocol's marker.`);
51
+ err.code = "config.legacy_tool_name";
52
+ throw err;
53
+ }
47
54
  if (known.has(n))
48
55
  continue;
49
56
  if (isNamespacedCoveringRuleName(n) && [...known].some((k) => namespacedRuleNameCovers(n, k)))
50
57
  continue;
51
- const retired = RETIRED_TOOL_NAMES.get(n);
52
- if (retired !== undefined) {
53
- const err = new Error(`tool policy ${kind}-list entry "${n}" is a RETIRED tool name (${retired}) and matches nothing in this run's roster — legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Update the deployment's rule to the current name.`);
54
- err.code = "config.legacy_tool_name";
55
- throw err;
56
- }
57
- if (n.includes("__") && protocolOf(n) === undefined) {
58
- const err = new Error(`tool policy ${kind}-list entry "${n}" carries the "__" namespace separator but no protocol prefix, and matches nothing in this run's roster — protocol tools are named ${NAMESPACED_NAME_SHAPES}, and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with the owning protocol's marker.`);
59
- err.code = "config.legacy_tool_name";
60
- throw err;
61
- }
62
58
  unmatched.add(`"${n}" (${kind})`);
63
59
  }
64
60
  }
@@ -131,20 +127,12 @@ export async function preparePolicyChain(input) {
131
127
  : {}),
132
128
  }, { env: executionEnv, rootPath: taskRootFinal, toolEffects }));
133
129
  }
134
- const skillScopePolicy = hasSkillManifest
135
- ? createActiveSkillScopePolicy({
136
- scope: skillScope,
137
- env: executionEnv,
138
- rootPath: taskRootFinal,
139
- toolEffects,
140
- })
141
- : undefined;
142
130
  const frozenConstraintLayers = (seedInheritedGate?.constraintChain ?? [])
143
131
  .filter((e) => !("opaque" in e))
144
132
  .map((entry) => ({
145
133
  check: (creq) => checkToolPolicyProjection(entry, creq) ?? { action: "allow" },
146
134
  }));
147
- const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, ...frozenConstraintLayers, skillScopePolicy].filter((p) => p !== undefined);
135
+ const narrowingLayers = [sessionRulePolicy, ...ancestorRulePolicies, ...frozenConstraintLayers].filter((p) => p !== undefined);
148
136
  for (const layer of narrowingLayers)
149
137
  auditPolicyNames(layer);
150
138
  const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
@@ -306,6 +294,7 @@ export async function preparePolicyChain(input) {
306
294
  };
307
295
  };
308
296
  const inheritedAskEvidence = inheritedAskRuleEvidence(deps.permissionRuleStore);
297
+ const skillGrantLane = skillGrants.lane({ root: taskRootFinal, sessionId, liveCwd: () => handsCwdRef?.current, home: executionEnv.homeDir });
309
298
  const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({
310
299
  provider: deps.permissionRuleStore,
311
300
  localOwnerDeclared: deps.localOwnerRules === true,
@@ -677,7 +666,7 @@ export async function preparePolicyChain(input) {
677
666
  return decision;
678
667
  })
679
668
  : foldedPolicy;
680
- return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
669
+ return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
681
670
  }
682
671
  const advisedPolicyNames = new WeakMap();
683
672
  const ADVISED_KEYS_CAP = 64;
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedSkillListingBudget } from "../skill-listing-budget.js";
2
2
  import { type StoredSession } from "../session.js";
3
3
  import type { RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
4
- import type { ActiveSkillScope } from "./active-skill-scope.js";
4
+ import { type SkillGrantTable, type SkillSeatReader } from "../skill-grant.js";
5
5
  import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
6
6
  import { normalizeSkills } from "./synthetic-tools.js";
7
7
  import { RosterBuilder } from "../tool-roster.js";
@@ -11,12 +11,12 @@ export interface PrepareProjectContextInput {
11
11
  * (the shared-memory pair's switch: wired ⇒ mount). Receiver preserved (`deps.onError?.(…)`). */
12
12
  deps: Pick<RunnerDeps, "loadProjectMemory" | "onError" | "sharedMemoryStores" | "onNotice">;
13
13
  /** borrowed-readonly — the REBOUND spec (`spec′`). Read: `sessionId` (phase derivation + the
14
- * continuation projections), `skills` (the roster), `principal` (the shared-memory tools' trusted
15
- * identity). Never mutated. */
16
- spec: Pick<TaskSpec, "sessionId" | "skills" | "principal">;
17
- /** borrowed-readonly — trusted internals: `agentName` (seam ctx), `inheritedManifestScope` (the
18
- * parent's active skill frames, seeded as the base of the LIFO). `undefined` on a standalone prepare. */
19
- internals: Pick<RunInternals, "agentName" | "inheritedManifestScope"> | undefined;
14
+ * continuation projections), `skills` (the roster), `userInvokedSkills` (the person's invoked skills — their
15
+ * rows are written here), `principal` (the shared-memory tools' trusted identity). Never mutated. */
16
+ spec: Pick<TaskSpec, "sessionId" | "skills" | "userInvokedSkills" | "principal">;
17
+ /** borrowed-readonly — trusted internals: `agentName` (seam ctx), `inheritedSkillGrants` (the parent's captured
18
+ * skill grant rows, seeded as `inherited`). `undefined` on a standalone prepare. */
19
+ internals: Pick<RunInternals, "agentName" | "inheritedSkillGrants"> | undefined;
20
20
  /** borrowed-readonly — the acquired session; only `getBranch()` is read (phase derivation walk).
21
21
  * The read is best-effort: a throw keeps the provisional phase. */
22
22
  session: Pick<StoredSession, "getBranch">;
@@ -43,10 +43,10 @@ export interface PrepareProjectContextInput {
43
43
  * files). This phase APPENDS the project layer and hands the result back as
44
44
  * {@link PrepareProjectContextResult.memoryBlock}; the input value itself is never mutated. */
45
45
  memoryBlockFromEngine: string | undefined;
46
- /** borrowed-mutable — the run's LIFO skill-scope stack (identity is the contract: the scope policy
47
- * and the `skill` tool alias this exact object). Mutated here: `push` of every inherited parent
48
- * frame; after this phase only the `skill` tool's execute pushes/pops. */
49
- skillScope: ActiveSkillScope;
46
+ /** borrowed-mutable — the run's skill grant table (identity is the contract: the `skill` tool, the gate's lane and the
47
+ * ctx capture alias this exact object). Mutated here: the parent's captured rows (`inherited`) and the person's invoked
48
+ * skills (`user`); after this phase only the `skill` tool writes (on activation). */
49
+ skillGrants: SkillGrantTable;
50
50
  /** borrowed-mutable — the run's roster builder (later refs alias its read face; in-place contract). Mutated
51
51
  * here: `mount` of the `skill` tool, the shared-memory pair and the engine-memory pair. Occupancy
52
52
  * is judged on the ALREADY-MOUNTED read face (names + aliases). */
@@ -67,8 +67,8 @@ export interface PrepareProjectContextInput {
67
67
  memoryPairExcludedName: string | undefined;
68
68
  /** borrowed-readonly — the engine-pair name a declared tool already occupies, if any (disclosure text). */
69
69
  memoryPairOccupiedName: string | undefined;
70
- /** borrowed-readonly — the durable-resume carrier; read for `!== undefined` (seedAnnounced) and
71
- * `seed.announcedListings.skills` (the removal-to-zero projection's gate). */
70
+ /** borrowed-readonly — the durable-resume carrier; read for `!== undefined` (seedAnnounced),
71
+ * `seed.announcedListings.skills` (the removal-to-zero projection's gate) and `seed.skillGrants` (the frozen grant rows). */
72
72
  resume: Pick<PrepareResume, "seed"> | undefined;
73
73
  /** borrowed-readonly — the lowered listing-budget seats from the config door. The listing projection
74
74
  * carries it to the boundary renderer, and the over-budget disclosure below is priced against it. */
@@ -91,9 +91,8 @@ export interface PrepareProjectContextResult {
91
91
  /** owned — the normalized, load-gated skills (first-wins dedupe, name-sorted). Consumed inside this
92
92
  * phase (tool + listing); exposed as the phase's own product. */
93
93
  skillSpecs: ReturnType<typeof normalizeSkills>;
94
- /** owned — whether THIS task carries a manifested skill or inherits a parent frame (the skill-scope
95
- * policy's activation predicate). */
96
- hasSkillManifest: boolean;
94
+ /** owned — the `Skill` tool's per-call seat over the loaded skills (`Execute skill: <name>`); `undefined` with no skill. */
95
+ skillSeatOf: SkillSeatReader | undefined;
97
96
  /** owned — the skills-listing projection the run loop's announce lane consumes (`Prepared.skillsListing`). */
98
97
  skillsListing: Prepared["skillsListing"];
99
98
  /** owned — whether the shared-memory pair mounted here (the support-name pre-check reads it). */
@@ -5,12 +5,14 @@ import { hasConversationContent } from "../session.js";
5
5
  import { createSharedMemoryTools } from "../shared-memory/tools.js";
6
6
  import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
7
7
  import { defineTool } from "../tools.js";
8
+ import { readFrozenSkillGrants, seedSkillGrants, skillEntriesNotGrantedNotice, skillSeatReader } from "../skill-grant.js";
9
+ import { deliverEngineNotice } from "../engine-notice.js";
8
10
  import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
9
- import { substitutedSkillLength } from "./skill-base-dir.js";
11
+ import { skillLoadReading } from "./skill-base-dir.js";
10
12
  import { RosterBuilder } from "../tool-roster.js";
11
13
  import { asThrownError } from "../thrown-value.js";
12
14
  export async function prepareProjectContext(input) {
13
- const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget } = input;
15
+ const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillGrants, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget } = input;
14
16
  const tools = roster.tools;
15
17
  let memoryBlock = memoryBlockFromEngine;
16
18
  let instructionSources;
@@ -76,25 +78,27 @@ export async function prepareProjectContext(input) {
76
78
  if (!declaredSkillRank.has(s.name))
77
79
  declaredSkillRank.set(s.name, declaredSkillRank.size);
78
80
  const skillSpecs = normalizeSkills(spec.skills ?? []).filter((s) => {
79
- const delivered = substitutedSkillLength(s.content, s.baseDir);
80
- if (delivered <= SKILL_CONTENT_MAX_CHARS)
81
+ const { loads, delivered } = skillLoadReading(s, SKILL_CONTENT_MAX_CHARS);
82
+ if (loads)
81
83
  return true;
82
84
  deps.onError?.(new Error(`Skill "${s.name}" content is ${delivered} chars${delivered === s.content.length ? "" : ` once its base directory resolves (${s.content.length} as written)`}, over the ${SKILL_CONTENT_MAX_CHARS}-char load limit — skill not loaded (skills are never truncated; shrink the body or move material to attachments/files).`), { phase: "config", sessionId });
83
85
  return false;
84
86
  });
85
- const inheritedFrames = internals?.inheritedManifestScope ?? [];
86
- for (const frame of inheritedFrames) {
87
- skillScope.push(frame);
88
- }
89
- const hasSkillManifest = skillSpecs.some((s) => s.manifest !== undefined) || inheritedFrames.length > 0;
87
+ const frozenSkillGrants = readFrozenSkillGrants(resume?.seed.skillGrants);
88
+ if (frozenSkillGrants === undefined)
89
+ 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)");
90
+ const inert = seedSkillGrants(skillGrants, { skills: skillSpecs, userInvokedSkills: spec.userInvokedSkills, inherited: [...(internals?.inheritedSkillGrants ?? []), ...frozenSkillGrants] });
91
+ if (inert.length > 0)
92
+ deliverEngineNotice(deps.onNotice, skillEntriesNotGrantedNotice(inert, sessionId));
90
93
  if (skillSpecs.length > 0) {
91
94
  if (tools.some((t) => t.name === SKILL_TOOL_NAME)) {
92
95
  const e = new Error(`Tool name "${SKILL_TOOL_NAME}" is reserved when spec.skills is present.`);
93
96
  e.code = "config.reserved_tool_name";
94
97
  throw e;
95
98
  }
96
- roster.mount(createSkillTool(skillSpecs, skillScope), { source: "builtin", mountedBy: "optional" });
99
+ roster.mount(createSkillTool(skillSpecs, skillGrants), { source: "builtin", mountedBy: "optional" });
97
100
  }
101
+ const skillSeatOf = skillSeatReader(skillSpecs);
98
102
  const sharedMemoryProvider = deps.sharedMemoryStores;
99
103
  let sharedMemoryPairMounted = false;
100
104
  if (sharedMemoryProvider !== undefined) {
@@ -150,5 +154,5 @@ export async function prepareProjectContext(input) {
150
154
  ?
151
155
  { entries: [], seedAnnounced: true, budget: skillListingBudget }
152
156
  : undefined;
153
- return { memoryBlock, instructionSources, projectInstructionContent, skillSpecs, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted };
157
+ return { memoryBlock, instructionSources, projectInstructionContent, skillSpecs, skillSeatOf, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted };
154
158
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * PrepareTask's RUN-REFS phase, verbatim from the driver: the run's own edited-files
3
- * ledger, the harness ref, the per-task skill scope, the display-forwarding wrapper, the three run-local refs
3
+ * ledger, the harness ref, the per-task skill grant table, the display-forwarding wrapper, the three run-local refs
4
4
  * (review request / stop-after-turn / plan mode) with the ctx primitives that write them, the subagent-retain
5
5
  * ledger and the worktree-isolation helper. Nothing here has behavior of its own: the phase MINTS SEATS —
6
6
  * mutable cells and the closures bound to them — that later phases, the tool ctx and the run loop read and
@@ -11,15 +11,15 @@
11
11
  * await is extracted as a sync function — an `async` wrapper would open a microtask window).
12
12
  *
13
13
  * Every cell that leaves here as `borrowed-mutable` carries its writer table on the Result member:
14
- * the closures minted here, the driver's harness build, the project-context phase's inherited-skill
15
- * seed, the skill tool, and the run loop's two writes — nothing else writes them. Read-stability of the host
14
+ * the closures minted here, the driver's harness build, the project-context phase's skill-grant
15
+ * seeds, the skill tool, and the run loop's two writes — nothing else writes them. Read-stability of the host
16
16
  * handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
17
17
  */
18
18
  import type { AgentHarness, ExecutionEnv } from "../../internal/harness.js";
19
19
  import type { SubagentWorktreeIsolation } from "../../agents/subagent.js";
20
20
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
21
21
  import type { TaskEvent, TaskSpec } from "../types.js";
22
- import { ActiveSkillScope } from "./active-skill-scope.js";
22
+ import { SkillGrantTable } from "../skill-grant.js";
23
23
  import type { Prepared, RunInternals, ToolProgressLane } from "./contracts.js";
24
24
  import type { PermissionModeController } from "../mode-policy.js";
25
25
  import { type EditedFilesLedger } from "./edited-files-ledger.js";
@@ -63,10 +63,10 @@ export interface PrepareRunRefsResult {
63
63
  toolProgressRef: {
64
64
  current?: ToolProgressLane;
65
65
  };
66
- /** borrowed-mutable — the per-task LIFO skill scope. Writers: the project-context phase (the inherited-
67
- * from-parent seed frame, `push`) and the `skill` tool minted over it (`push` on a manifest load; a frame
68
- * closes by `pop`). Readers: the tool ctx's `activeSkillScope()` snapshot and the skill-scope policy layer. */
69
- skillScope: ActiveSkillScope;
66
+ /** borrowed-mutable — the per-task skill grant table (its rows live for this run). Writers: the project-context phase (the
67
+ * parent's captured rows, the person's invoked skills) and the `Skill` tool minted over it (a successful activation).
68
+ * Readers: the tool ctx's `activeSkillGrants()` capture and the gate's skill lane. */
69
+ skillGrants: SkillGrantTable;
70
70
  /** owned — the FILTERED display-forwarding wrapper, or undefined when the deployment wired no sink (the
71
71
  * tool ctx then carries no `forwardEvent` key). */
72
72
  forwardEvent: ((e: TaskEvent) => void) | undefined;
@@ -1,6 +1,6 @@
1
1
  import { createSubagentWorktreeHelper } from "../../agents/subagent.js";
2
2
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
3
- import { ActiveSkillScope } from "./active-skill-scope.js";
3
+ import { SkillGrantTable } from "../skill-grant.js";
4
4
  import { forwardSafely } from "./forward-sink.js";
5
5
  import { createEditedFilesLedger } from "./edited-files-ledger.js";
6
6
  export function forwardsSubagentEvents(spec) {
@@ -14,7 +14,7 @@ export function prepareRunRefs(input) {
14
14
  const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
15
15
  const harnessRef = {};
16
16
  const toolProgressRef = {};
17
- const skillScope = new ActiveSkillScope();
17
+ const skillGrants = new SkillGrantTable();
18
18
  const sinkWired = internals?.onForwardEvent !== undefined;
19
19
  const forwardEvent = sinkWired
20
20
  ? (e) => {
@@ -48,5 +48,5 @@ export function prepareRunRefs(input) {
48
48
  };
49
49
  const subagentRetain = spec.retainSubagentSessions ? new SubagentRetainLedger(spec.retainSubagentSessions) : undefined;
50
50
  const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootFinal) : undefined;
51
- return { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, bindPermissionModeSeat, enterPlanMode, subagentRetain, worktreeIsolation };
51
+ return { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillGrants, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, bindPermissionModeSeat, enterPlanMode, subagentRetain, worktreeIsolation };
52
52
  }
@@ -1,4 +1,4 @@
1
- import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
1
+ import { NAMESPACED_NAME_SHAPES, carriesNamespaceSeparator } from "../protocol-table.js";
2
2
  import { engineCardTypes, handBandEffects } from "../tool-registry.js";
3
3
  import { callerCardIdOf, offendingResultCard, rosterMemberBoundProblem, schemaKeyBoundProblem } from "../tool-roster.js";
4
4
  import { toolFaceProblem } from "./tool-face-overlay.js";
@@ -64,13 +64,13 @@ export function prepareSafetyScan(input) {
64
64
  throw e;
65
65
  }
66
66
  }
67
- if (t.name.includes("__")) {
67
+ if (carriesNamespaceSeparator(t.name)) {
68
68
  const e = new Error(`Tool name "${t.name}" is invalid: "__" is reserved for the engine's protocol tool namespaces (${NAMESPACED_NAME_SHAPES}) and must not appear in a caller tool name.`);
69
69
  e.code = "config.tool_name_invalid";
70
70
  throw e;
71
71
  }
72
72
  for (const alias of t.aliases ?? []) {
73
- if (alias.includes("__")) {
73
+ if (carriesNamespaceSeparator(alias)) {
74
74
  const e = new Error(`Tool alias "${alias}" (of "${t.name}") is invalid: "__" is reserved for the engine's protocol tool namespaces (${NAMESPACED_NAME_SHAPES}) and must not appear in a caller tool alias.`);
75
75
  e.code = "config.tool_name_invalid";
76
76
  throw e;
@@ -112,6 +112,8 @@ export interface PrepareSuspendSagaInput {
112
112
  faceCheckpointSection: () => SerializedCheckpointState["readFace"];
113
113
  /** borrowed-readonly — the inherited-gate phase's WRITE-face section builder (the row's `writeFace`). */
114
114
  writeFaceCheckpointSection: () => SerializedCheckpointState["writeFace"];
115
+ /** borrowed-readonly — the inherited-gate phase's SKILL-GRANT section builder (the row's `skillGrants`). */
116
+ skillGrantCheckpointSection: () => SerializedCheckpointState["skillGrants"];
115
117
  /** borrowed-readonly — the leg's reminder provenance mark (travels with the row). */
116
118
  reminderMark: string;
117
119
  /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read at mint time. Not written here. */
@@ -136,7 +138,6 @@ export interface PrepareSuspendSagaInput {
136
138
  /** borrowed-readonly — the leg's live permission-mode seat; the DECLARED mode (request clamped under the chain's ceiling,
137
139
  * before the deployment's fold) is what the row records — the resume leg re-judges the fold for its own deployment. */
138
140
  permissionMode: PermissionModeController;
139
- /** borrowed-readonly — the auto-mode decider, or undefined (the latch-health reading only). */
140
141
  /** borrowed-readonly — the inherited admitted org scopes, or undefined (copied onto the row). */
141
142
  inheritedAdmittedOrgScopes: readonly string[] | undefined;
142
143
  /** borrowed-readonly — this leg's OWN org verdict seat; `current` is read at mint time. Not written here. */