@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
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The per-call hook stations a tool call passes OUTSIDE the tool gate. ONE rule for every executed call, live or
3
+ * approved on a parked checkpoint: before it runs, the PreToolUse screening seat; after it runs, the `tool_result`
4
+ * station (the PostToolUse / PostToolUseFailure seats and the PreToolUse context drain).
5
+ * · a LIVE call meets the screening inside the gate and the station through the loop's finalize (the gate-stations
6
+ * phase registers the station on the harness);
7
+ * · an APPROVED call meets {@link createApprovedCallScreen} — the SAME seat (`createPreToolUseScreening`), folded under
8
+ * the decide that approved it — and the station through `AgentHarness.finalizeToolCall`, the loop's own finalize.
9
+ */
10
+ import type { DocumentContent, ImageContent, TextContent } from "../../internal/llm.js";
11
+ import { type HookEnvCapabilities, type HookInvocationIdentity, type Hooks } from "../hooks.js";
12
+ import type { PermissionModeController } from "../mode-policy.js";
13
+ import type { TaskSpec } from "../types.js";
14
+ import type { CwdRef } from "../../tools/fs/fs-shared.js";
15
+ import { type ToolRosterDeltaSeat } from "../tool-roster.js";
16
+ import { type FrameSettlement } from "../frame-settlement.js";
17
+ import type { Prepared } from "./contracts.js";
18
+ import type { Checkpoint } from "../checkpoint-store.js";
19
+ /** One executed call's result as the station reads it (the harness `tool_result` event's members). */
20
+ export interface ToolResultSeatEvent {
21
+ toolCallId: string;
22
+ toolName: string;
23
+ input: Record<string, unknown>;
24
+ content: Array<TextContent | ImageContent | DocumentContent>;
25
+ details: unknown;
26
+ isError: boolean;
27
+ }
28
+ /** The station: the patch the loop applies to the result (`undefined` = unchanged). */
29
+ export type ToolResultStation = (e: ToolResultSeatEvent) => Promise<{
30
+ content: Array<TextContent | ImageContent | DocumentContent>;
31
+ } | undefined>;
32
+ export interface ToolResultStationInput {
33
+ /** borrowed-readonly — the resolved hook record: the two post seats and the failure seat, called through the record. */
34
+ hooks: Hooks | undefined;
35
+ /** borrowed-mutable — the blocked/suspended call ids. Writer here: the one-shot `delete`. */
36
+ blockedToolCalls: Set<string>;
37
+ /** borrowed-readonly — the leg's delta seat: the post seats receive the call's wire name off its current roster. */
38
+ toolRosterDeltas: ToolRosterDeltaSeat;
39
+ /** borrowed-mutable — PreToolUse `additionalContext` per call id. Writer here: the one-shot drain (`delete`). */
40
+ preToolContexts: Map<string, string[]>;
41
+ /** borrowed-readonly — the validated per-prepare hook seat bound. */
42
+ hookTimeoutMs: number;
43
+ /** borrowed-readonly — prepare's abort seat; `.signal` bounds the post seats and is the failure seat's interrupt fact. */
44
+ abortController: {
45
+ readonly signal: AbortSignal;
46
+ };
47
+ /** borrowed-readonly — the REBOUND spec's `signal` (the failure seat's interrupt fact). */
48
+ spec: Pick<TaskSpec, "signal">;
49
+ /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read per post seat. */
50
+ handsCwdRef: CwdRef | undefined;
51
+ /** borrowed-readonly — the env face the post seats' contexts carry, or undefined. */
52
+ hookEnvFace: HookEnvCapabilities | undefined;
53
+ /** borrowed-readonly — the leg's identity envelope every HookToolContext carries. */
54
+ hookIdentity: HookInvocationIdentity;
55
+ /** borrowed-readonly — the leg's live permission-mode seat (each context reads `observed`). */
56
+ permissionMode: PermissionModeController;
57
+ /** borrowed-readonly — where an expired post seat is reported (the leg's own hook-crash lane). */
58
+ notifyOwnHookCrash: (err: unknown) => void;
59
+ /** borrowed-readonly — the leg's reminder provenance mark (every hook context line carries it). */
60
+ reminderMark: string;
61
+ /** borrowed-mutable — the leg's settlement seam. Writer here: a carrier whose output a host hook rewrote (`noteHostRewrite`). */
62
+ frameSettlement: FrameSettlement;
63
+ }
64
+ /** The `tool_result` station, or undefined when no post / screening hook is wired (nothing to register). */
65
+ export declare function createToolResultStation(input: ToolResultStationInput): ToolResultStation | undefined;
66
+ /** What the resumed leg does with the approved call: refuse it with a sentence (it is NOT executed), or run these bytes.
67
+ * `admit` stashes the screen's context for the result's drain — called only once the call is past every belt and about
68
+ * to run (a refused call stashes nothing, the live gate's rule). */
69
+ export type ApprovedCallScreen = NonNullable<Prepared["approvedCallScreen"]>;
70
+ export type ApprovedCallVerdict = Awaited<ReturnType<ApprovedCallScreen>>;
71
+ export interface ApprovedCallScreenInput {
72
+ /** borrowed-readonly — the face the gate screens with (the observational wrapper when declared; the leg's mode on every context). */
73
+ preToolUse: Hooks["preToolUse"] | undefined;
74
+ /** borrowed-readonly — the validated per-prepare hook seat bound. */
75
+ hookTimeoutMs: number;
76
+ /** borrowed-readonly — prepare's abort seat (the screening seat is abort-ended on it). */
77
+ abortController: {
78
+ readonly signal: AbortSignal;
79
+ };
80
+ /** borrowed-readonly — the hand's live tracked cwd, or undefined. */
81
+ handsCwdRef: CwdRef | undefined;
82
+ /** borrowed-readonly — the read-only env capability face, or undefined. */
83
+ hookEnvFace: HookEnvCapabilities | undefined;
84
+ /** borrowed-readonly — the leg's identity envelope. */
85
+ hookIdentity: HookInvocationIdentity;
86
+ /** borrowed-readonly — the own hook-crash reporter (a crashing or timed-out screening face is reported through it). */
87
+ notifyOwnHookCrash: (err: unknown) => void;
88
+ /** borrowed-mutable — PreToolUse `additionalContext` per call id. Writer here: `admit`'s stash (the station drains it). */
89
+ preToolContexts: Map<string, string[]>;
90
+ }
91
+ /**
92
+ * The approved call's PreToolUse screening — the gate's own seat, its answer FOLDED UNDER THE DECIDE that approved the
93
+ * call (the decide is the upper bound; the screen can only narrow): a deny, a crash, a silence or a refused answer
94
+ * refuses the call in the live gate's own sentences; an ask is the question the decide already answered — unless it
95
+ * demands a person and the decide was the deployment's policy (the one predicate every resume station holds a decide
96
+ * against); a rewrite is what executes when the decide carried none of its own (the person's bytes are the upper bound);
97
+ * context rides the result. Undefined when no face is wired.
98
+ */
99
+ export declare function createApprovedCallScreen(input: ApprovedCallScreenInput): ApprovedCallScreen | undefined;
100
+ /**
101
+ * Why the frozen inherited chain cannot judge bytes no gate judged (an approver's edit, the re-screen's rewrite): the
102
+ * row predates the chain, a layer is opaque, or a layer declares a live remainder — undefined when the frozen
103
+ * projections can judge them, or when no parent constraint binds. ONE rule for the pre-claim edit rung (which refuses
104
+ * the edit, the row pending) and the resumed leg's belt (which refuses a rewrite, the approval spent).
105
+ */
106
+ export declare function chainCannotJudge(ig: Checkpoint["state"]["inheritedGate"]): string | undefined;
@@ -0,0 +1,118 @@
1
+ import { createPreToolUseScreening, formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
2
+ import { isEngineRefusal } from "../tool-policy.js";
3
+ import { policyCannotAnswer } from "../real-approval.js";
4
+ import { traceHookCrash } from "../gate-crash-trace.js";
5
+ import { createSafeNotifier } from "../safe-notify.js";
6
+ import { rosterToolName } from "../tool-roster.js";
7
+ import { wireOf } from "../tool-name-readings.js";
8
+ import { noteHostRewrite } from "../frame-settlement.js";
9
+ export function createToolResultStation(input) {
10
+ const { hooks, blockedToolCalls, toolRosterDeltas, preToolContexts, hookTimeoutMs, abortController, spec, handsCwdRef, hookEnvFace, hookIdentity, permissionMode, notifyOwnHookCrash, reminderMark, frameSettlement } = input;
11
+ const toolResultStationArmed = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure);
12
+ if (toolResultStationArmed) {
13
+ return async (e) => {
14
+ if (blockedToolCalls.delete(e.toolCallId)) {
15
+ return undefined;
16
+ }
17
+ let content = e.content;
18
+ let changed = false;
19
+ const post = { toolName: rosterToolName(toolRosterDeltas.current, e.toolName) };
20
+ if (e.isError) {
21
+ if (hooks?.postToolUseFailure) {
22
+ let clonedDetails = e.details;
23
+ try {
24
+ clonedDetails = structuredClone(e.details);
25
+ }
26
+ catch {
27
+ }
28
+ const failedName = wireOf("hook.postToolUseFailure", post);
29
+ const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(failedName, e.input, {
30
+ error: e.content
31
+ .filter((c) => c.type === "text")
32
+ .map((c) => c.text)
33
+ .join("\n"),
34
+ isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
35
+ content: e.content.map((c) => ({ ...c })),
36
+ details: clonedDetails,
37
+ }, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
38
+ if (failureSeat.expired) {
39
+ notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
40
+ }
41
+ const patch = failureSeat.expired ? undefined : failureSeat.value;
42
+ if (patch?.additionalContext) {
43
+ content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
44
+ changed = true;
45
+ }
46
+ }
47
+ }
48
+ else if (hooks?.postToolUse) {
49
+ const ranName = wireOf("hook.postToolUse", post);
50
+ 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 }));
51
+ if (successSeat.expired) {
52
+ notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
53
+ }
54
+ const patch = successSeat.expired ? undefined : successSeat.value;
55
+ if (patch?.updatedOutput) {
56
+ content = patch.updatedOutput;
57
+ changed = true;
58
+ noteHostRewrite(frameSettlement, e.toolCallId);
59
+ }
60
+ if (patch?.additionalContext) {
61
+ content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
62
+ changed = true;
63
+ }
64
+ }
65
+ const pre = preToolContexts.get(e.toolCallId);
66
+ if (pre) {
67
+ preToolContexts.delete(e.toolCallId);
68
+ content = [...content, ...pre.map((t) => ({ type: "text", text: formatHookFeedback(t, reminderMark) }))];
69
+ changed = true;
70
+ }
71
+ return changed ? { content } : undefined;
72
+ };
73
+ }
74
+ return undefined;
75
+ }
76
+ export function createApprovedCallScreen(input) {
77
+ const { preToolUse, hookTimeoutMs, abortController, handsCwdRef, hookEnvFace, hookIdentity, notifyOwnHookCrash, preToolContexts } = input;
78
+ if (preToolUse === undefined)
79
+ return undefined;
80
+ return async ({ toolCallId, toolName, args, decidedBy }) => {
81
+ const notifier = createSafeNotifier();
82
+ const seatInput = { event: { toolCallId, toolName, input: args }, ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}), identity: hookIdentity, onHookError: notifyOwnHookCrash, abortSignal: abortController.signal };
83
+ const screening = createPreToolUseScreening(preToolUse, seatInput, hookTimeoutMs, notifier);
84
+ const subject = `this call to "${toolName}"`;
85
+ let r;
86
+ try {
87
+ const seat = await screening.screen(args);
88
+ if (seat.expired)
89
+ return { refused: await screening.unansweredReason(subject, seat.cause) };
90
+ r = screening.screened(seat.value);
91
+ }
92
+ catch (err) {
93
+ traceHookCrash(seatInput, err, notifier);
94
+ return { refused: screening.crashReason(subject, err) };
95
+ }
96
+ if (r !== undefined && isEngineRefusal(r))
97
+ return { refused: screening.refusedReason(r) };
98
+ if (r?.action === "deny")
99
+ return { refused: screening.deniedReason(r) };
100
+ if (r !== undefined && policyCannotAnswer(r, decidedBy))
101
+ return { refused: `a PreToolUse hook requires a person's approval for ${subject}, and this approval was the deployment's policy — not executed` };
102
+ const context = r?.additionalContext;
103
+ return { args: r?.updatedInput !== undefined ? r.updatedInput : args, admit: () => {
104
+ if (context)
105
+ preToolContexts.set(toolCallId, [context]);
106
+ } };
107
+ };
108
+ }
109
+ export function chainCannotJudge(ig) {
110
+ if (ig?.requiresParentConstraint !== true)
111
+ return undefined;
112
+ const chain = ig.constraintChain;
113
+ if (chain === undefined)
114
+ return "the checkpoint predates the frozen projection chain (no constraintChain recorded)";
115
+ if (chain.some((e) => "opaque" in e))
116
+ return "the inherited chain carries an OPAQUE layer (no projection exported)";
117
+ return chain.some((e) => !("opaque" in e) && e.requiresLiveRemainder) ? "an inherited chain layer declares a live remainder beyond its projection" : undefined;
118
+ }
@@ -55,7 +55,7 @@ import type { ToolDisclosureManifest } from "../trace.js";
55
55
  import type { AgentDefinition, Brain, CompactOutcome, ModelRef, ModelRoles, NestedUsageAccum, PermissionMode, RunnerDeps, ShellGateDoctrine, TaskEvent, TaskEventIdentity, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
56
56
  import type { PermissionModeController } from "../mode-policy.js";
57
57
  import type { WiringManifest } from "../wiring-manifest.js";
58
- import type { ActiveSkillFrame } from "./active-skill-scope.js";
58
+ import type { SkillGateSeats, SkillGrantLane, SkillGrantRecord, SkillGrantSource } from "../skill-grant.js";
59
59
  import type { BudgetAxis } from "./assemble-result.js";
60
60
  import type { GitStatusLaneRef } from "./git-status-frame.js";
61
61
  import type { SessionReadFileStates } from "./session-read-file-states.js";
@@ -239,6 +239,8 @@ export type StopForDenialLimit = (info: {
239
239
  fallback: DenialLimitFallbackFace;
240
240
  settlement?: SettlementKind;
241
241
  }) => void;
242
+ /** The gate's seat inputs for one call (the static seat or the `Skill` tool's per-call reader, and the run's skill rows). */
243
+ export type GateSeatInputsFor = (calledName: string, wireName: string) => import("../mode-policy.js").PermissionSeatInputs & SkillGateSeats;
242
244
  export interface Prepared {
243
245
  harness: AgentHarness;
244
246
  /** The CONCRETE built-in session (engine-internal: prepare constructs/acquires `StoredSession` itself,
@@ -418,9 +420,14 @@ export interface Prepared {
418
420
  * the run's settle, every terminal (suspend included), under the session lock; undefined argument
419
421
  * (no commits) is a no-op. Synchronous, never throws. Absent ⇔ hands-less / no seat channel. */
420
422
  sealReadStateSeat?: (ownCommittedTailEntryId: string | undefined) => void;
421
- /** The DENY-NARROWING layers (session rules + skill scope, deny-only). Re-checked on RESUME
422
- * before an approved pending tool executes, so a rule tightened during the suspend still applies. */
423
+ /** The DENY-NARROWING layers (session rules, inherited ancestor rules, the frozen constraint projections; deny-only).
424
+ * Re-checked on RESUME before an approved pending tool executes, so a rule tightened during the suspend still applies. */
423
425
  denyNarrowingPolicy?: ToolPolicy;
426
+ /** The run's skill grant lane: its deny rows (a skill's `disallowed-tools`) are re-read on RESUME at the same station as the
427
+ * person's deny rows, on the bytes that will run. */
428
+ skillGrants: SkillGrantLane;
429
+ /** The skills this leg loads (normalized, past the load gate; empty when none) — the one list its `Skill` tool activates from, its park binds and the resumed execution boundary judges a parked Skill approval against. */
430
+ skillSpecs: readonly SkillGrantSource[];
424
431
  /** The person's own persisted-rule lane (deny/ask rows), re-adjudicated on RESUME at the same
425
432
  * station as the deny-narrowing layers: a deny row added while the call was parked vetoes the execution
426
433
  * (`deniedBy: "persisted_rule"`), and so does a call the lane could not read against the rows (re-issue and
@@ -429,9 +436,19 @@ export interface Prepared {
429
436
  persistedRuleLane?: {
430
437
  adjudicate: (req: ToolCallRequest) => Promise<import("../hooks.js").PersistedRuleAnswer>;
431
438
  };
432
- /** The deployment's own caller policy, re-checked on a durable resume ONLY when the approver
433
- * rewrote the pending call's args (see the composition site for why the edit case is special). */
439
+ /** The deployment's caller policy, re-checked on a durable resume when the bytes to execute are not the bytes the gate saw. */
434
440
  basePolicyForResumeEdit?: ToolPolicy;
441
+ approvedCallScreen?: (call: {
442
+ toolCallId: string;
443
+ toolName: string;
444
+ args: unknown;
445
+ decidedBy: "person" | "sla_timeout" | "policy";
446
+ }) => Promise<{
447
+ readonly refused: string;
448
+ } | {
449
+ readonly args: unknown;
450
+ readonly admit: () => void;
451
+ }>;
435
452
  /** The leg's shell READ STATION, re-run on a durable RESUME before an approved shell call executes: the parked leg's
436
453
  * admission died with that leg, so the resumed bytes are judged (and admitted for their pre-spawn re-establishment)
437
454
  * on THIS leg — a deny row found now vetoes, an operator's edit is judged on the `rejudge` seat. */
@@ -1608,10 +1625,10 @@ export interface RunInternals {
1608
1625
  /**
1609
1626
  * Parent effective-policy inheritance (tighten-only): the spawning parent's already-evaluated FINAL
1610
1627
  * gate, threaded into a child task so the child inherits it as ADDITIONAL constraint layers — a child
1611
- * can only ever be narrowed by this, never widened (its own session rules / skill scope / caller policy
1628
+ * can only ever be narrowed by this, never widened (its own session rules / skill rows / caller policy
1612
1629
  * still apply in full). Filled ONLY by core delegation callers (`createSubagentTool`'s execute via
1613
1630
  * `ToolExecuteContext.inheritedGateForChildren`, the workflow spawn legs) — NEVER a {@link TaskSpec}
1614
- * field (the untrusted-caller surface), mirroring `inheritedManifestScope`'s ctx-injection posture.
1631
+ * field (the untrusted-caller surface), mirroring `inheritedSkillGrants`'s ctx-injection posture.
1615
1632
  * Absent ⇒ no inheritance (a top-level task; fully backward-compatible).
1616
1633
  */
1617
1634
  inheritedGate?: InheritedGate;
@@ -1686,7 +1703,7 @@ export interface RunInternals {
1686
1703
  * itself inside a workflow (e.g. service's `/v1/workflows`), it threads `workflowDepth = parentDepth + 1`
1687
1704
  * into `startWorkflow`/`runWorkflow` so the one-level nesting guard fires across the process boundary.
1688
1705
  * In-process nesting needs nothing here — the engine's `AsyncLocalStorage` propagates depth automatically.
1689
- * Mirrors how `repairBundle`/`inheritedManifestScope` thread trusted run-scoped internals the Runner cannot
1706
+ * Mirrors how `repairBundle`/`inheritedSkillGrants` thread trusted run-scoped internals the Runner cannot
1690
1707
  * see from `spec`. Consumed by the `run_workflow` tool wiring (S8c), not by `prepareTask` itself.
1691
1708
  */
1692
1709
  workflowDepth?: number;
@@ -1711,7 +1728,7 @@ export interface RunInternals {
1711
1728
  * child it spawns: this run IS a forked child. `prepareTask` threads it to tool ctx as `insideFork` so the
1712
1729
  * child's own Agent tool refuses a nested fork (nesting guard — mirrors CC's "fork is not available inside a
1713
1730
  * forked worker"; a fork can still delegate via `Agent`, just not fork again). TRUSTED internal (NOT a
1714
- * `TaskSpec` field — the untrusted-caller surface), mirrors `inheritedManifestScope`.
1731
+ * `TaskSpec` field — the untrusted-caller surface), mirrors `inheritedSkillGrants`.
1715
1732
  */
1716
1733
  insideFork?: boolean;
1717
1734
  /**
@@ -1783,23 +1800,16 @@ export interface RunInternals {
1783
1800
  */
1784
1801
  goalMode?: boolean;
1785
1802
  /**
1786
- * 🔴 Skill→subagent manifest-scope PROPAGATION (must close before ON). The parent task's
1787
- * ACTIVE skill-manifest frames, snapshotted at the moment a subagent was spawned WHILE a manifest scope
1788
- * was live on the parent. The child's {@link prepareTask} seeds its own {@link ActiveSkillScope} from
1789
- * these so the child inherits the parent skill's deny-narrowing — fail-closed and MONOTONIC: a child of
1790
- * a manifested skill is AT MOST as capable as the manifest (its own manifests can only narrow further,
1791
- * never re-grant a tool/path the parent removed).
1792
- *
1793
- * This is a TRUSTED, run-scoped channel filled ONLY by `createSubagentTool`'s `execute` (a core caller),
1794
- * NEVER a {@link TaskSpec} field (TaskSpec is the untrusted-caller surface). It mirrors
1795
- * how `repairBundle`/`resume.seed` thread live per-task state the Runner cannot see from `spec`.
1803
+ * The skill grant rows the PARENT task held at the moment it spawned this child (its `allowed-tools` / `disallowed-tools`
1804
+ * rows of the run, whatever wrote them), captured by `createSubagentTool`'s `execute` from the parent's
1805
+ * `ctx.activeSkillGrants()` and seeded into this child's table as `inherited` rows: they hold until THIS child ends
1806
+ * (the parent's run ending ends the parent's rows, not this copy), beside the child's own activations.
1796
1807
  *
1797
- * Fail-closed: when the parent HAD an active manifest at spawn but the precise frames cannot be
1798
- * snapshotted, the subagent tool threads a single DENY-ALL `unresolved` frame here rather than letting the
1799
- * child run unmanifested — the safe path is the default. An empty/absent value = no inheritance (a
1800
- * subagent spawned with no active parent manifest behaves exactly as before, backward-compatible).
1808
+ * A TRUSTED, run-scoped channel (never a {@link TaskSpec} field — TaskSpec is the untrusted-caller surface), in the
1809
+ * posture `repairBundle` / `resume.seed` keep. A capture the delegation could not read refuses the spawn rather than
1810
+ * seeding nothing (it carries refusals too). Absent or empty ⇒ nothing inherited.
1801
1811
  */
1802
- inheritedManifestScope?: readonly ActiveSkillFrame[];
1812
+ inheritedSkillGrants?: readonly SkillGrantRecord[];
1803
1813
  /**
1804
1814
  * When this task runs as a SUB-AGENT spawned under a parent task's
1805
1815
  * tool call, the spawning tool's `ToolExecuteContext.toolCallId`. The Runner stamps it onto this task's
@@ -1807,7 +1817,7 @@ export interface RunInternals {
1807
1817
  * content to the delegation subtree WITHOUT core merging the child stream into the parent (lightweight
1808
1818
  * message-identity, not stream-merge). A TRUSTED, run-scoped channel filled by a core caller
1809
1819
  * (`createSubagentTool`'s `execute`) — NEVER a {@link TaskSpec} field (the untrusted-caller surface),
1810
- * mirroring `inheritedManifestScope`/`workflowDepth`. Absent for a top-level (non-delegated) task.
1820
+ * mirroring `inheritedSkillGrants`/`workflowDepth`. Absent for a top-level (non-delegated) task.
1811
1821
  */
1812
1822
  parentToolCallId?: string;
1813
1823
  /**
@@ -138,6 +138,10 @@ export interface PreToolUseScreening {
138
138
  /** The model-facing text for a face that never ANSWERED while screening `subject` (the timeout arm also
139
139
  * reports on the deployment lane; a task-abort expiry does not). */
140
140
  unansweredReason(subject: string, cause: "timeout" | "aborted"): Promise<string>;
141
+ /** The model-facing text for the face's OWN deny of this call (unwrapped; every exit wraps it with the leg's mark). */
142
+ deniedReason(answer: PreToolUseResult): string;
143
+ /** The model-facing text for an answer the decision receipt refused (an ENGINE fail-closed, not the face's deny). */
144
+ refusedReason(answer: PreToolUseResult): string;
141
145
  }
142
146
  /**
143
147
  * ONE gate pass — the record the station groups (`runGateFold`, `runGateLanes`) and the exits read and write,
@@ -318,7 +322,7 @@ export interface GatePass {
318
322
  export declare function engineFailClosedExit(pass: GatePass, reason: string): Promise<ToolGateResult>;
319
323
  /** The hook's OWN deny in phase 1. A PreToolUse hook deny does NOT fire `permissionDenied`
320
324
  * (the exclusion recorded on `Hooks.permissionDenied`) — this return stays observer-free. */
321
- export declare function hookDenyExit(pass: GatePass, r: PreToolUseResult): ToolGateResult;
325
+ export declare function hookDenyExit(pass: GatePass, deniedReason: string): ToolGateResult;
322
326
  /**
323
327
  * The exit of the adjudicate chain — the pass has a standing `deny` or `allow` (every ask was resolved or
324
328
  * parked before this), and this is where it becomes the gate result and the ONE record of the pass.
@@ -1,7 +1,8 @@
1
1
  import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
2
- import { headlessApprovalRemedy } from "../ask-origin.js";
2
+ import { classifyAsk, headlessApprovalRemedy } from "../ask-origin.js";
3
3
  import { isProtectedWrite } from "../tool-registry.js";
4
4
  import { askUnresolvableCauseOf, classifierDenyCauseOf, classifierRoundOf, decisionText, engineSettlementOf } from "../tool-policy.js";
5
+ import { askUnresolvableRemedySeat } from "../ask-unresolvable-notice.js";
5
6
  import { inlineUntrusted } from "../untrusted-text.js";
6
7
  import { formatHookFeedback } from "../reminder-mint.js";
7
8
  export function mintGateOutcome(facts) {
@@ -102,15 +103,16 @@ export async function engineFailClosedExit(pass, reason) {
102
103
  await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
103
104
  return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, preToolContext: pass.preToolContext };
104
105
  }
105
- export function hookDenyExit(pass, r) {
106
- const { input, toolName } = pass;
106
+ export function hookDenyExit(pass, deniedReason) {
107
+ const { input } = pass;
107
108
  return {
108
109
  block: true,
109
- reason: formatHookFeedback(decisionText(r) ?? `tool "${toolName}" blocked by a PreToolUse hook`, input.reminderMark),
110
+ reason: formatHookFeedback(deniedReason, input.reminderMark),
110
111
  gate: directDeny("hook"),
111
112
  preToolContext: pass.preToolContext,
112
113
  };
113
114
  }
115
+ const ANCESTOR_UNRESOLVED_CLASSIFICATION = classifyAsk({ action: "ask", message: "" }, { contentQuestion: false, markedUnresolvable: true, org: undefined, tightened: undefined, mandateMarks: {} });
114
116
  function unresolvableCauseOfSettlement(gate) {
115
117
  if (gate.disposition.kind !== "denied" || gate.disposition.deniedBy !== "ask_resolution" || gate.settlement === undefined)
116
118
  return undefined;
@@ -135,8 +137,10 @@ export async function exitGate(pass) {
135
137
  ledger.supersede();
136
138
  }
137
139
  const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(modeRefusal !== undefined ? { modeRefusal: { mode: modeRefusal.mode, askClass: modeRefusal.askClass, classes: modeRefusal.classes } } : {}) });
138
- const unresolvable = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName }) ?? unresolvableCauseOfSettlement(gate);
139
- const remedy = unresolvable === "no_approver_wired" ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: pass.askClassification, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
140
+ const foldCause = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName });
141
+ const unresolvable = foldCause ?? unresolvableCauseOfSettlement(gate);
142
+ const remedySeat = unresolvable !== undefined ? askUnresolvableRemedySeat(unresolvable) : undefined;
143
+ const remedy = remedySeat !== undefined ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: foldCause !== undefined ? ANCESTOR_UNRESOLVED_CLASSIFICATION : pass.askClassification, approverSeat: remedySeat, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
140
144
  const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed);
141
145
  if (unresolvable !== undefined) {
142
146
  const cause = unresolvable;
@@ -99,6 +99,7 @@ export interface PrepareDelegationSurfaceInput {
99
99
  /** borrowed-readonly — the caller-tool ctx enrichment: the revival spawner re-enters the delegation tool through it, and
100
100
  * the SendMessage / AgentTranscript mounts activate their ctx-first arms with it. */
101
101
  enrichSpecToolCtx: (ctx: ToolExecuteContext) => ToolExecuteContext;
102
+ /** borrowed-readonly — the seat this run hands its children (`checkpointSeatOf`); the background-agent read faces read a parked agent's approval card through it. */ checkpointSeat: import("../checkpoint-seat.js").CheckpointSeat | undefined;
102
103
  /** borrowed-mutable — the hands mount's tracked-cwd seat: present ⇔ real shell + write hands; the worktree pair's mount
103
104
  * predicate. Not written by this phase, but the tools it mounts over it ARE its writers: EnterWorktree and
104
105
  * ExitWorktree assign `current` at run time (the hands band's shared cwd then points at the worktree, and back); the
@@ -12,14 +12,14 @@ import { deliverEngineNotice } from "../types.js";
12
12
  import { RosterBuilder } from "../tool-roster.js";
13
13
  import { sendMessageContinuationSeats } from "./send-message-continuation.js";
14
14
  export function prepareDelegationSurface(input) {
15
- const { spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, taskRootCanonical, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, sessionBox, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle } = input;
15
+ const { spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, taskRootCanonical, executionEnv, runnerSelf, backgroundTaskToolsActive, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, sessionBox, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, checkpointSeat, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle } = input;
16
16
  let worktreeSessionRef;
17
17
  const delegationSurfaceActive = backgroundTaskToolsActive || workflowToolsActive;
18
18
  let sendMessageTier;
19
19
  if (delegationSurfaceActive || internals?.parentNotify !== undefined || peerLaneActive) {
20
20
  if (delegationSurfaceActive) {
21
21
  roster.mountAll([
22
- firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })),
22
+ firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore, ...(checkpointSeat !== undefined ? { checkpointSeat } : {}) })),
23
23
  firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })),
24
24
  ], { source: "builtin", mountedBy: "delegation" });
25
25
  }
@@ -66,7 +66,7 @@ export function prepareDelegationSurface(input) {
66
66
  roster.mount(firstPartyOffload(createAgentTranscriptTool({
67
67
  runner: runnerSelf,
68
68
  registry: defaultTaskRegistry,
69
- agentStore: deps.backgroundAgentStore,
69
+ agentStore: deps.backgroundAgentStore, ...(checkpointSeat !== undefined ? { checkpointSeat } : {}),
70
70
  owner: hostTaskId,
71
71
  scope: taskScope,
72
72
  ...(sessionId !== undefined ? { sessionId } : {}),
@@ -84,7 +84,7 @@ export function prepareDelegationSurface(input) {
84
84
  toolEffects.set(READ_INBOX_TOOL_NAME, "read");
85
85
  roster.mount(firstPartyOffload(createReadInboxTool({ pull: () => peerLaneRefs.pull })), { source: "builtin", mountedBy: "peerLane" });
86
86
  }
87
- const listAgents = (peerLaneActive || delegationSurfaceActive) && peerSendMessageBuiltIn && listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) ? mountListAgents({ ...(peerLaneActive ? { peerDirectory: deps.peerDirectory } : {}), freshWindowMs: resolvePeerLaneConfig(deps.peerLane, deps.onNotice).listingFreshWindowMs, sessionId, scope: taskScope, hostTaskId, ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}), registry: defaultTaskRegistry, ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}), ...(sendMessageTier !== undefined ? { continuationTier: sendMessageTier } : {}), specTools: spec.tools ?? [], toolEffects }) : undefined;
87
+ const listAgents = (peerLaneActive || delegationSurfaceActive) && peerSendMessageBuiltIn && listAgentsMountable({ exclude: toolFaceSnapshot.exclude, specTools: spec.tools ?? [] }) ? mountListAgents({ ...(peerLaneActive ? { peerDirectory: deps.peerDirectory } : {}), freshWindowMs: resolvePeerLaneConfig(deps.peerLane, deps.onNotice).listingFreshWindowMs, sessionId, scope: taskScope, hostTaskId, ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}), registry: defaultTaskRegistry, ...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}), ...(sendMessageTier !== undefined ? { continuationTier: sendMessageTier } : {}), ...(checkpointSeat !== undefined ? { readSeats: { checkpointSeat } } : {}), specTools: spec.tools ?? [], toolEffects }) : undefined;
88
88
  if (listAgents !== undefined)
89
89
  roster.mount(firstPartyOffload(listAgents), { source: "builtin", mountedBy: "peerLane" });
90
90
  if (backgroundTaskToolsActive) {
@@ -3,7 +3,8 @@
3
3
  * effect sync, the write-protection judge compiled once per task, the arming predicate, the compliance and plan-mode
4
4
  * short-circuits, the `runToolGate` call with every seat it is handed, the lifecycle sweep, the deferred sandbox-admission
5
5
  * disclosure, the PreToolUse context stash, the settlement sideband write and the bare-human-rejection boundary) and the
6
- * `tool_result` station (the PostToolUse / PostToolUseFailure seats and the PreToolUse context drain). The two module-scope
6
+ * `tool_result` station's registration (its body, the PostToolUse / PostToolUseFailure seats and the PreToolUse context
7
+ * drain, lives in call-hook-stations.ts). The two module-scope
7
8
  * helpers only these stations called moved with them (the settlement screen, the rejection-boundary judgment). The
8
9
  * interface is the dependency list the stations had implicitly.
9
10
  *
@@ -36,11 +37,12 @@ import { type WiringManifest } from "../wiring-manifest.js";
36
37
  import type { WriteProtectionCheck } from "../write-protect.js";
37
38
  import type { ShellReadCheck } from "../../tools/fs/shell-read-check.js";
38
39
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
39
- import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes, StopForDenialLimit } from "./contracts.js";
40
+ import type { AskLane, GateSeatInputsFor, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes, StopForDenialLimit } from "./contracts.js";
40
41
  import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
41
42
  import { type ToolRosterDeltaSeat } from "../tool-roster.js";
42
43
  import type { ToolCallLike } from "../tool-registry.js";
43
- import { type FrameSettlement } from "../frame-settlement.js";
44
+ import type { FrameSettlement } from "../frame-settlement.js";
45
+ import { type ApprovedCallScreen } from "./call-hook-stations.js";
44
46
  /**
45
47
  * The parent-thread human-rejection boundary — judged ONCE here, from the pass's own record (CC parity:
46
48
  * the reference CLI's reject path aborts exactly when there is no feedback and the run is not a
@@ -205,6 +207,7 @@ export interface PrepareGateStationsInput {
205
207
  stopForDenialLimit: StopForDenialLimit;
206
208
  /** borrowed-readonly — the persisted allow-rule lane, or undefined (absent entirely with no store). */
207
209
  permissionRuleLane: PermissionRuleLanes["personal"];
210
+ /** borrowed-readonly — the call's seat inputs: the permission seat (static, or the `Skill` tool's per-call reader) and the run's skill rows. */ seatInputsFor: GateSeatInputsFor;
208
211
  /** borrowed-readonly — the org rule lane, or undefined (absent on an ungoverned deployment). */
209
212
  permissionRuleOrgLane: PermissionRuleLanes["org"];
210
213
  /** borrowed-readonly — whether the ENGINE's question tool is what is mounted under the reserved name (the org lane's
@@ -239,6 +242,8 @@ export interface PrepareGateStationsResult {
239
242
  toolCallStationArmed: boolean;
240
243
  /** owned — whether the `tool_result` station registered (a post or screening hook is wired; observation seat only). */
241
244
  toolResultStationArmed: boolean;
245
+ /** owned — the approved call's PreToolUse re-screen (the gate's face and seat), present exactly when a face is wired. */
246
+ approvedCallScreen?: ApprovedCallScreen;
242
247
  }
243
248
  /** The M20 phase body — prepareTask's two gate stations, verbatim (see the module header). */
244
249
  export declare function prepareGateStations(input: PrepareGateStationsInput): PrepareGateStationsResult;