@sema-agent/core 7.5.2 → 7.6.1
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.
- package/CHANGELOG.md +67 -0
- package/dist/agents/agent-transcript-tool.d.ts +2 -2
- package/dist/agents/cascade.d.ts +4 -5
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +7 -5
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/retain-ledger.d.ts +2 -3
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/session-util.d.ts +2 -2
- package/dist/agents/subagent.d.ts +27 -46
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.d.ts +2 -2
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +6 -5
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +8 -9
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/agent-definition.d.ts +172 -0
- package/dist/core/agent-definition.js +1 -0
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/delegation-frames.d.ts +298 -0
- package/dist/core/delegation-frames.js +21 -0
- package/dist/core/engine-notice.d.ts +555 -0
- package/dist/core/engine-notice.js +55 -0
- package/dist/core/gate-fold.d.ts +12 -0
- package/dist/core/gate-fold.js +158 -0
- package/dist/core/gate-lanes.d.ts +93 -0
- package/dist/core/gate-lanes.js +626 -0
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hands-band.d.ts +134 -0
- package/dist/core/hands-band.js +1 -0
- package/dist/core/hooks.d.ts +22 -177
- package/dist/core/hooks.js +53 -851
- package/dist/core/mcp-failure.d.ts +142 -0
- package/dist/core/mcp-failure.js +145 -0
- package/dist/core/mcp-server-spec.d.ts +217 -0
- package/dist/core/mcp-server-spec.js +1 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/model-seat.d.ts +99 -0
- package/dist/core/model-seat.js +1 -0
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/reminder-mint.d.ts +10 -0
- package/dist/core/reminder-mint.js +3 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/contracts.d.ts +427 -69
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/denial-limit-arms.js +3 -3
- package/dist/core/runner/gate-exit.d.ts +242 -0
- package/dist/core/runner/gate-exit.js +124 -0
- package/dist/core/runner/park-commit.d.ts +17 -23
- package/dist/core/runner/park-commit.js +14 -15
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -5
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
- package/dist/core/runner/prepare-boundary-parks.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
- package/dist/core/runner/prepare-gate-stations.js +29 -54
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +2 -4
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-task.d.ts +2 -2
- package/dist/core/runner/prepare-task.js +8 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
- package/dist/core/runner/prepare-wiring-manifest.js +9 -10
- package/dist/core/runner/runtask.d.ts +20 -102
- package/dist/core/runner/runtask.js +119 -121
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/runner-deps.d.ts +1416 -0
- package/dist/core/runner-deps.js +1 -0
- package/dist/core/runtime-caps.d.ts +164 -0
- package/dist/core/runtime-caps.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/task-event.d.ts +910 -0
- package/dist/core/task-event.js +1 -0
- package/dist/core/task-limits.d.ts +110 -0
- package/dist/core/task-limits.js +1 -0
- package/dist/core/task-result.d.ts +809 -0
- package/dist/core/task-result.js +1 -0
- package/dist/core/task-spec.d.ts +1370 -0
- package/dist/core/task-spec.js +1 -0
- package/dist/core/task-stream.d.ts +382 -0
- package/dist/core/task-stream.js +1 -0
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/tool-spec.d.ts +1174 -0
- package/dist/core/tool-spec.js +1 -0
- package/dist/core/types.d.ts +27 -7789
- package/dist/core/types.js +2 -76
- package/dist/core/warm-resume.d.ts +2 -2
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +9 -6
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.d.ts +2 -2
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.d.ts +2 -2
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +15 -4
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/dist/scenarios/teacher-quickstart.d.ts +2 -2
- package/dist/server/http.d.ts +2 -2
- package/dist/stores/file/fs-atomic.d.ts +88 -12
- package/dist/stores/file/fs-atomic.js +184 -55
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +1 -0
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +82 -22
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool gate's RECORD FLOOR — everything about how one gate pass is recorded and how it ends:
|
|
3
|
+
*
|
|
4
|
+
* - the ONE mint of a {@link GateOutcome} ({@link mintGateOutcome}) — the record of what a tool-gate pass
|
|
5
|
+
* decided, what ask it settled and who asked. The gate accumulates two facts on its way to an exit (WHO
|
|
6
|
+
* refused, written once at the refusing site; WHAT was settled, written at the resolution site) and hands
|
|
7
|
+
* them here; the mint assembles the record, runs the invariant screen, and refuses a record the engine
|
|
8
|
+
* composed wrong. Every projection of the outcome (the deny observer's payload, the call's `tool_end`
|
|
9
|
+
* frame, the engine's per-call sideband) is the object minted here, never a re-derivation;
|
|
10
|
+
* - the settlement LEDGER of a pass ({@link createSettlementLedger}) — the one rule every attestation read
|
|
11
|
+
* goes through;
|
|
12
|
+
* - the PASS RECORD ({@link GatePass}) — the per-call cells the gate's station groups read and write, and
|
|
13
|
+
* the hook-machinery seats they borrow, built once per call by `runToolGate` and handed to every station;
|
|
14
|
+
* - the EXITS — the two phase-1 short-circuits ({@link engineFailClosedExit}, {@link hookDenyExit}) and the
|
|
15
|
+
* single deny / allow exit of the adjudicate chain ({@link exitGate}), the only places a pass turns into a
|
|
16
|
+
* `ToolGateResult`; plus the two observer-facing helpers every exit uses (the payload isolation clone and
|
|
17
|
+
* the deployment-lane crash report).
|
|
18
|
+
*
|
|
19
|
+
* A floor module (layer 0 of the prepare/run path): the two station modules (`gate-fold.ts`, `gate-lanes.ts`)
|
|
20
|
+
* and the gate's dispatch in `hooks.ts` all reach DOWN for it, and it reaches down only for the vocabulary,
|
|
21
|
+
* the reminder envelope and the text helpers. Its `import type` edges up to `hooks.ts` name the gate's public
|
|
22
|
+
* contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
|
|
23
|
+
*/
|
|
24
|
+
import { type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
|
|
25
|
+
import type { AskOrigin, AskOriginFacts } from "../ask-origin.js";
|
|
26
|
+
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
27
|
+
import type { SafeNotifier } from "../safe-notify.js";
|
|
28
|
+
import type { HookSeatOutcome, PermissionDeniedPayload, PreToolUseResult, ToolGateInput, ToolGateResult } from "../hooks.js";
|
|
29
|
+
/** The ask a gate pass settled, as the gate captured it at its resolution site: the settlement the
|
|
30
|
+
* resolver composed and the origin word the ask carried. Both or neither (invariant I1). */
|
|
31
|
+
export interface SettledAsk {
|
|
32
|
+
settlement: Settlement;
|
|
33
|
+
origin: AskOrigin;
|
|
34
|
+
}
|
|
35
|
+
/** The facts a gate pass hands the mint. `deniedBy` is present exactly when the pass refused; `settled`
|
|
36
|
+
* exactly when the pass consumed an ask's settlement (a person's yes that was later vetoed included). */
|
|
37
|
+
export interface GateExitFacts {
|
|
38
|
+
deniedBy?: DeniedBy;
|
|
39
|
+
settled?: SettledAsk;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
|
|
43
|
+
* settlement without its origin, a person's yes beside a non-veto layer's deny) and throws: the gate's
|
|
44
|
+
* own composition is not a caller input, so there is no fail-closed "deny" to fall back to that would be
|
|
45
|
+
* more honest than the fault.
|
|
46
|
+
*/
|
|
47
|
+
export declare function mintGateOutcome(facts: GateExitFacts): GateOutcome;
|
|
48
|
+
/** A direct refusal by one layer, with no ask settled — the five short-circuit exits (a hook that threw or
|
|
49
|
+
* never answered, the hook's own deny, the compliance lock, the plan-mode block). */
|
|
50
|
+
export declare function directDeny(deniedBy: Exclude<DeniedBy, "ask_resolution">): GateOutcome;
|
|
51
|
+
/**
|
|
52
|
+
* The gate's settlement LEDGER for one pass — the one place "which ask did this pass consume" is written,
|
|
53
|
+
* with ONE rule for every attestation read (the main adjudication, the approval-edit re-check, the gate's
|
|
54
|
+
* own ask resolutions), so the three sites cannot disagree about what an attested decision means:
|
|
55
|
+
* - an engine attestation on a decision object (the approval factory's wait ended inside a policy
|
|
56
|
+
* `check()`; an inherited lane resolved an ancestor's ask) is a settled ask of THIS call — read off the
|
|
57
|
+
* identity-keyed seat, never off a caller-composable member;
|
|
58
|
+
* - a deny carrying a REFUSAL attestation is the ask's own resolution (`ask_resolution`); a deny carrying a
|
|
59
|
+
* person's YES is a later layer's VETO (the yes stays on the settlement, the layer is the disposition —
|
|
60
|
+
* invariant I3);
|
|
61
|
+
* - the LATEST resolution wins (a re-check refusal replaces the first approval; a re-approval renews it);
|
|
62
|
+
* - a SURVIVING ask supersedes any settlement read off an earlier decision (a tighten minted a new
|
|
63
|
+
* question; whoever resolves it settles it afresh).
|
|
64
|
+
* For a resolver's object the attestation outranks the exposed member; the member is the fallback for a
|
|
65
|
+
* resolver seat that composes without attesting (a hand-built resolver; the ask lane's own intercept
|
|
66
|
+
* deny). TRUST BOUNDARY, stated: `ToolGateInput.resolveAsk` is wired by prepare from the engine's own
|
|
67
|
+
* resolver — a wrapper that spreads a resolved object (dropping the identity-keyed attestation) and
|
|
68
|
+
* REPLACES the member is inside the engine's trust line, not a deployment's; making the member
|
|
69
|
+
* attestation-only is the follow-up that migrates the ask lane's composition and the test resolvers
|
|
70
|
+
* onto an engine-owned mint.
|
|
71
|
+
*/
|
|
72
|
+
export interface SettlementLedger {
|
|
73
|
+
/** The ask this pass consumed, or undefined when no ask was settled (a direct deny, a plain allow). */
|
|
74
|
+
readonly settled: SettledAsk | undefined;
|
|
75
|
+
/** An engine-composed settlement the gate itself minted (the unshown content ask's refusal). */
|
|
76
|
+
set(settled: SettledAsk): void;
|
|
77
|
+
/** Record an ask's resolution: the resolver's settlement beside the ask's own origin word. */
|
|
78
|
+
fromResolved(resolved: ResolvedAsk, asked: PermissionResult, toolName: string): void;
|
|
79
|
+
/** Read an adjudication's attestation. For a deny, returns the refusing layer word (`ask_resolution`
|
|
80
|
+
* for an attested refusal, `policy` otherwise — a veto when a yes rides the settlement); undefined
|
|
81
|
+
* for an allow or an ask. */
|
|
82
|
+
fromDecision(decision: PermissionResult): DeniedBy | undefined;
|
|
83
|
+
/** A surviving ask supersedes whatever an earlier decision settled. */
|
|
84
|
+
supersede(): void;
|
|
85
|
+
}
|
|
86
|
+
export declare function createSettlementLedger(call: {
|
|
87
|
+
toolCallId: string;
|
|
88
|
+
toolName: string;
|
|
89
|
+
}): SettlementLedger;
|
|
90
|
+
/**
|
|
91
|
+
* 1.256 复审 MED-1 — observe-only payload isolation for {@link import("../hooks.js").Hooks.permissionDenied}:
|
|
92
|
+
* clone the tool args before they ride the observer payload, so a hook mutating `payload.input` can never
|
|
93
|
+
* pollute the LIVE args object (later events / audit records share it). Same posture as the postToolUseFailure
|
|
94
|
+
* details clone (the gate-stations phase): `structuredClone` first; a non-structured-cloneable graph
|
|
95
|
+
* (functions/handles) falls back to a SHALLOW plain object/array copy (top-level mutation isolated);
|
|
96
|
+
* a non-object primitive passes through as-is (immutable anyway).
|
|
97
|
+
*/
|
|
98
|
+
export declare function cloneObserverInput(input: unknown): unknown;
|
|
99
|
+
/** RB-442: hand the crash itself to the deployment's trace lane (`onError(phase:"hook")`), never letting a
|
|
100
|
+
* throwing sink alter the fail-closed outcome — the same swallow posture as every other observer here.
|
|
101
|
+
* Shared by the gate's dispatch (the phase-1 crash arm, the seat-bound refusal), the reversibility probe's
|
|
102
|
+
* two failure arms and the approved-edit re-screen. */
|
|
103
|
+
export declare function traceHookCrash(input: ToolGateInput, err: unknown, notifier: SafeNotifier): void;
|
|
104
|
+
/**
|
|
105
|
+
* The PreToolUse screening face as the gate's stations see it — the hook machinery of `runToolGate` (the seat
|
|
106
|
+
* runner, the per-call seat bound, the fresh per-screening context, the result screen, the two fail-closed
|
|
107
|
+
* reason texts) behind three methods, so the approved-edit re-screen in the lanes runs the SAME screening the
|
|
108
|
+
* phase-1 pass ran without the lanes module reaching into the hook machinery. Present on the pass exactly when
|
|
109
|
+
* a `preToolUse` face is wired.
|
|
110
|
+
*/
|
|
111
|
+
export interface PreToolUseScreening {
|
|
112
|
+
/** Run the deployment's PreToolUse face over `args` under the seat bound. The answer is the SEAT's, unscreened —
|
|
113
|
+
* the caller passes it through {@link PreToolUseScreening.screened} in the seat's own continuation. The seat
|
|
114
|
+
* promise is returned as is (no wrapping `async`): what the gate does after the face answers — the policy in
|
|
115
|
+
* phase 1, the re-check in the edit chain — runs on the tick the seat settles, as it did when this call was the
|
|
116
|
+
* seat runner's own; a wrapper that screened the answer inside a second `async` cost one more tick there. An
|
|
117
|
+
* expired seat is reported, not thrown; a crashing face rejects. */
|
|
118
|
+
screen(args: unknown): Promise<HookSeatOutcome<PreToolUseResult | undefined>>;
|
|
119
|
+
/** The result screen over an answered seat's value: an out-of-contract decision becomes the ordinary hook deny. */
|
|
120
|
+
screened(answer: PreToolUseResult | undefined): PreToolUseResult | undefined;
|
|
121
|
+
/** The model-facing text for a face that THREW while screening `subject`. */
|
|
122
|
+
crashReason(subject: string, err: unknown): string;
|
|
123
|
+
/** The model-facing text for a face that never ANSWERED while screening `subject` (the timeout arm also
|
|
124
|
+
* reports on the deployment lane; a task-abort expiry does not). */
|
|
125
|
+
unansweredReason(subject: string, cause: "timeout" | "aborted"): Promise<string>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* ONE gate pass — the record the station groups (`runGateFold`, `runGateLanes`) and the exits read and write,
|
|
129
|
+
* built once per call by `runToolGate` before the screening pass. What used to be the closure-captured `let`
|
|
130
|
+
* cells of a 1 500-line function are its MUTABLE cells (each written by the station named on it, read by the
|
|
131
|
+
* ones after); what the stations borrow from the hook machinery are its SEATS (fixed for the pass). Ownership
|
|
132
|
+
* classes follow the phase-Input convention: `borrowed-readonly` = fixed for the pass, the stations only read
|
|
133
|
+
* it; `borrowed-mutable` = a cell a station writes.
|
|
134
|
+
*/
|
|
135
|
+
export interface GatePass {
|
|
136
|
+
/** borrowed-readonly — the gate's input: every deployment seat the stations read by name (`egress`,
|
|
137
|
+
* `orgRules`, `persistedRules`, `autoMode`, …), and the identity / reminder mark the exits stamp. */
|
|
138
|
+
readonly input: ToolGateInput;
|
|
139
|
+
/** borrowed-readonly — the gate's event, read ONCE at entry (the dispatch's entry destructure): the exit compares the
|
|
140
|
+
* final args against THIS event's input, never against a re-read of the input record. */
|
|
141
|
+
readonly event: ToolGateInput["event"];
|
|
142
|
+
/** borrowed-readonly — the deployment's policy face, captured ONCE at entry before the screening pass. The fold
|
|
143
|
+
* adjudicates through it and the edit chain re-adjudicates through the SAME binding: a stateful accessor on the
|
|
144
|
+
* input cannot hand the fold one policy and the approved-edit re-check another (or none). */
|
|
145
|
+
readonly adjudicate: ToolGateInput["adjudicate"];
|
|
146
|
+
/** borrowed-readonly — the ask resolver, captured ONCE at entry (same rule: the first-round resolution and the edit
|
|
147
|
+
* round's re-ask resolve through one binding). */
|
|
148
|
+
readonly resolveAsk: ToolGateInput["resolveAsk"];
|
|
149
|
+
/** borrowed-readonly — the durable park closure, captured ONCE at entry (the three park sites hand it the same
|
|
150
|
+
* thirteen seats; presence is judged off this one read). */
|
|
151
|
+
readonly suspendAsk: ToolGateInput["suspendAsk"];
|
|
152
|
+
/** borrowed-readonly — `input.event.toolName`, read once. */
|
|
153
|
+
readonly toolName: string;
|
|
154
|
+
/** borrowed-readonly — `input.event.toolCallId`, read once. */
|
|
155
|
+
readonly toolCallId: string;
|
|
156
|
+
/** borrowed-readonly — design/384: ONE read of the per-call signal for the whole gate call (the one-read
|
|
157
|
+
* discipline the settlement screen already states): every conditional seat invocation (the three wait
|
|
158
|
+
* seats plus the three suspendAsk park sites) judges presence and passes the value from this same cell, so
|
|
159
|
+
* a stateful accessor cannot answer the arity check with a signal and the call with something else. */
|
|
160
|
+
readonly callSignal: AbortSignal | undefined;
|
|
161
|
+
/** borrowed-readonly — RB-463: the one host-callback isolation scope of this gate call (the deny observer,
|
|
162
|
+
* the hook-error trace sink, the org and rule lanes' disclosure sinks). */
|
|
163
|
+
readonly notifier: SafeNotifier;
|
|
164
|
+
/** borrowed-readonly — the settlement LEDGER of this pass (the binding is fixed; the ledger's own methods
|
|
165
|
+
* are how the resolution sites write it — one rule per read, see {@link createSettlementLedger}). */
|
|
166
|
+
readonly ledger: SettlementLedger;
|
|
167
|
+
/** borrowed-readonly — the ONE emission seat of the deny observer for this pass, bounded and contained like
|
|
168
|
+
* every other seat (the hook machinery owns the seat runner; the exits call this). */
|
|
169
|
+
readonly notifyPermissionDenied: (payload: Omit<PermissionDeniedPayload, "signal">) => Promise<void>;
|
|
170
|
+
/** borrowed-readonly — the PreToolUse screening face ({@link PreToolUseScreening}), or undefined when no
|
|
171
|
+
* `preToolUse` face is wired (the edit chain then re-screens nothing, as before). */
|
|
172
|
+
readonly screening: PreToolUseScreening | undefined;
|
|
173
|
+
/** borrowed-readonly — the station group that runs AFTER the fold (the lanes: org layer, rule lane, classifier,
|
|
174
|
+
* parks, resolution, edit chain, and the exit they end in), handed the pass by the fold's LAST station on the
|
|
175
|
+
* fold's own completing tick. A seat rather than an import because the two station modules sit on one layer
|
|
176
|
+
* and may not name each other; a seat rather than a second `await` in the dispatch because that await is a
|
|
177
|
+
* suspension point of its own: the org face and the rule store read HOST state the host may refresh
|
|
178
|
+
* concurrently, and the tick on which they are consulted is the contract — when the station bodies were one
|
|
179
|
+
* function, the org face was invoked in the same continuation that ran the last tighten, and a refresh queued
|
|
180
|
+
* behind the policy's answer could not land in between. Returns the gate result (a park's suspension or the
|
|
181
|
+
* exit's record). Writer: the dispatch (`runToolGate`, which wires `runGateLanes`). Reader: the fold's tail. */
|
|
182
|
+
readonly afterFold: (pass: GatePass) => Promise<ToolGateResult>;
|
|
183
|
+
/** borrowed-mutable — the FINAL args: the model's input, rewritten by the phase-1 hook, by the policy
|
|
184
|
+
* rewrite on the ask path, by the content-ask lane's presented snapshot and by the exits' rewrite fold.
|
|
185
|
+
* Writers: the dispatch (phase 1), the lanes, the exits. */
|
|
186
|
+
currentInput: unknown;
|
|
187
|
+
/** borrowed-mutable — the adjudication request. Minted over the model's input so the record is whole from
|
|
188
|
+
* the first station, and RE-MINTED by the adjudication station over the FINAL post-screen args (the
|
|
189
|
+
* pre-existing `const req` site); `args` is re-pointed at the policy rewrite on the ask path and at the
|
|
190
|
+
* content-ask lane's presented snapshot. Writers: the fold (mint), the lanes (`args`). */
|
|
191
|
+
req: ToolCallRequest;
|
|
192
|
+
/** borrowed-mutable — the PreToolUse `additionalContext` collected for this call (pushed into; the binding
|
|
193
|
+
* is fixed). Writers: the dispatch (phase 1), the lanes (sandbox admission note, the edit re-screen). */
|
|
194
|
+
readonly preToolContext: string[];
|
|
195
|
+
/** borrowed-mutable — the phase-1 hook's ASK, remembered for the fold. Only ever an ask (assigned under the
|
|
196
|
+
* `r.action === "ask"` arm); typed as the narrowed member so the fold's reconstruction reads the ask-only
|
|
197
|
+
* fields without a runtime re-test that could, if it ever failed, silently skip the promotion (i.e. loosen).
|
|
198
|
+
* Writer: the dispatch (phase 1). Readers: the fold, the sandbox-admission conjunct. */
|
|
199
|
+
hookAsk: Extract<PermissionResult, {
|
|
200
|
+
action: "ask";
|
|
201
|
+
}> | undefined;
|
|
202
|
+
/** borrowed-mutable — set by a park attempt that FAILED (see `ParkAttemptFailed`). Read at the single deny
|
|
203
|
+
* exit — whatever the fallback chain then decides to say, it says it knowing the park was tried first.
|
|
204
|
+
* Writers: the three park sites. */
|
|
205
|
+
parkFailed: string | undefined;
|
|
206
|
+
/** borrowed-mutable — WHO refused, written ONCE by the refusing site (a direct policy / org / classifier
|
|
207
|
+
* deny, a hook re-screen refusal, the write-protection re-judge, the edit chain's cap, an ask resolution
|
|
208
|
+
* that ended as a refusal). The exit mints the outcome from it beside the ledger's `settled`; a refusal
|
|
209
|
+
* that reaches the exit without it is an engine defect and the mint throws. */
|
|
210
|
+
deniedBy: DeniedBy | undefined;
|
|
211
|
+
/** borrowed-mutable — the standing verdict. The no-policy default until the adjudication station writes the
|
|
212
|
+
* policy's answer; then rewritten by every station that changes the verdict (fold, tightens, org, rule
|
|
213
|
+
* lane, stamp, classifier, sandbox, content lane, resolution, edit chain). */
|
|
214
|
+
decision: PermissionResult;
|
|
215
|
+
/** borrowed-mutable — which of the gate's OWN post-fold tightens minted the standing ask, if one did — an
|
|
216
|
+
* input of the ask-origin derivation (`AskOriginFacts.tightened`). Written at most once per pass: every
|
|
217
|
+
* tighten fires only over a surviving `allow`, and the first one to fire leaves an `ask` the others do not
|
|
218
|
+
* touch. Recorded by the gate rather than read off `decisionReason` because that member is a policy's to
|
|
219
|
+
* compose (a policy stamping `"safety"` on its own ask must read as `policy`). Writer: the fold. */
|
|
220
|
+
tightenedBy: AskOriginFacts["tightened"];
|
|
221
|
+
/** borrowed-mutable — the rewrite remembered BEFORE a hook-ask promotion / tighten, re-applied on the
|
|
222
|
+
* approved path (a redaction is not cancelled because a person confirmed). Writer: the fold. Readers: the
|
|
223
|
+
* fold's probe and write-protection judges, the lanes, the allow exit. */
|
|
224
|
+
policyRewrite: unknown;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The engine's fail-closed block of phase 1 — the PreToolUse face THREW or never ANSWERED, so the engine
|
|
228
|
+
* decides, on its own rule, that the call does not run. design/134 R5 BOUNDARY (deliberate asymmetry with
|
|
229
|
+
* {@link hookDenyExit} — do NOT unify the two): a hook `deny` is the HOOK's decision and the observer
|
|
230
|
+
* exclusion covers it; a crash / an unanswered seat produces no decision at all, so the deny OBSERVER fires
|
|
231
|
+
* here — an attribution channel whose job is "why was this call denied" must see an engine deny, or the
|
|
232
|
+
* deployment's only evidence of a broken hook is a tool that mysteriously stops working.
|
|
233
|
+
*/
|
|
234
|
+
export declare function engineFailClosedExit(pass: GatePass, reason: string): Promise<ToolGateResult>;
|
|
235
|
+
/** The hook's OWN deny in phase 1. design/134 R5: a PreToolUse hook deny does NOT fire `permissionDenied`
|
|
236
|
+
* (the exclusion recorded on `Hooks.permissionDenied`) — this return stays observer-free. */
|
|
237
|
+
export declare function hookDenyExit(pass: GatePass, r: PreToolUseResult): ToolGateResult;
|
|
238
|
+
/**
|
|
239
|
+
* The exit of the adjudicate chain — the pass has a standing `deny` or `allow` (every ask was resolved or
|
|
240
|
+
* parked before this), and this is where it becomes the gate result and the ONE record of the pass.
|
|
241
|
+
*/
|
|
242
|
+
export declare function exitGate(pass: GatePass): Promise<ToolGateResult>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
2
|
+
import { decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
3
|
+
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
|
+
import { formatHookFeedback } from "../reminder-mint.js";
|
|
5
|
+
export function mintGateOutcome(facts) {
|
|
6
|
+
const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
|
|
7
|
+
const outcome = Object.freeze({
|
|
8
|
+
disposition: Object.freeze(facts.deniedBy !== undefined ? { kind: "denied", deniedBy: facts.deniedBy } : { kind: "allowed" }),
|
|
9
|
+
...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
|
|
10
|
+
});
|
|
11
|
+
const defects = screenGateOutcome(outcome);
|
|
12
|
+
if (defects.length > 0)
|
|
13
|
+
throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
|
|
14
|
+
return outcome;
|
|
15
|
+
}
|
|
16
|
+
export function directDeny(deniedBy) {
|
|
17
|
+
return mintGateOutcome({ deniedBy });
|
|
18
|
+
}
|
|
19
|
+
export function createSettlementLedger(call) {
|
|
20
|
+
let settled;
|
|
21
|
+
return {
|
|
22
|
+
get settled() {
|
|
23
|
+
return settled;
|
|
24
|
+
},
|
|
25
|
+
set(next) {
|
|
26
|
+
settled = next;
|
|
27
|
+
},
|
|
28
|
+
fromResolved(resolved, asked, toolName) {
|
|
29
|
+
if (asked.action !== "ask" || asked.origin === undefined) {
|
|
30
|
+
if (resolved.settlement === undefined)
|
|
31
|
+
return;
|
|
32
|
+
throw new Error(`an ask for "${toolName}" was resolved without an origin word — the origin stamp precedes every resolution`);
|
|
33
|
+
}
|
|
34
|
+
const attested = engineSettlementOf(resolved, call);
|
|
35
|
+
if (attested !== undefined) {
|
|
36
|
+
settled = { settlement: attested.settlement, origin: asked.origin };
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (resolved.settlement === undefined)
|
|
40
|
+
return;
|
|
41
|
+
settled = { settlement: resolved.settlement, origin: asked.origin };
|
|
42
|
+
},
|
|
43
|
+
fromDecision(decision) {
|
|
44
|
+
if (decision.action === "ask")
|
|
45
|
+
return undefined;
|
|
46
|
+
const attested = engineSettlementOf(decision, call);
|
|
47
|
+
if (attested !== undefined)
|
|
48
|
+
settled = { settlement: attested.settlement, origin: attested.origin };
|
|
49
|
+
if (decision.action !== "deny")
|
|
50
|
+
return undefined;
|
|
51
|
+
return attested !== undefined && SETTLEMENT_IS_REFUSAL[attested.settlement.kind] ? "ask_resolution" : "policy";
|
|
52
|
+
},
|
|
53
|
+
supersede() {
|
|
54
|
+
settled = undefined;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function cloneObserverInput(input) {
|
|
59
|
+
try {
|
|
60
|
+
return structuredClone(input);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
if (Array.isArray(input))
|
|
64
|
+
return [...input];
|
|
65
|
+
if (input !== null && typeof input === "object")
|
|
66
|
+
return { ...input };
|
|
67
|
+
return input;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function traceHookCrash(input, err, notifier) {
|
|
71
|
+
notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
|
|
72
|
+
}
|
|
73
|
+
const PARK_FAILURE_CAUSE_MAX = 600;
|
|
74
|
+
function withParkFailureCause(reason, parkFailed) {
|
|
75
|
+
if (parkFailed === undefined)
|
|
76
|
+
return reason;
|
|
77
|
+
return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
|
|
78
|
+
`(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
|
|
79
|
+
`left to say, not the reason the call stopped.`);
|
|
80
|
+
}
|
|
81
|
+
export async function engineFailClosedExit(pass, reason) {
|
|
82
|
+
const { input, toolName, toolCallId, notifyPermissionDenied } = pass;
|
|
83
|
+
const gate = directDeny("hook");
|
|
84
|
+
await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
|
|
85
|
+
return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, preToolContext: pass.preToolContext };
|
|
86
|
+
}
|
|
87
|
+
export function hookDenyExit(pass, r) {
|
|
88
|
+
const { input, toolName } = pass;
|
|
89
|
+
return {
|
|
90
|
+
block: true,
|
|
91
|
+
reason: formatHookFeedback(decisionText(r) ?? `tool "${toolName}" blocked by a PreToolUse hook`, input.reminderMark),
|
|
92
|
+
gate: directDeny("hook"),
|
|
93
|
+
preToolContext: pass.preToolContext,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export async function exitGate(pass) {
|
|
97
|
+
const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
|
|
98
|
+
if (pass.decision.action === "deny") {
|
|
99
|
+
const denyReason = withParkFailureCause(decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`, pass.parkFailed);
|
|
100
|
+
if (pass.decision.updatedInput !== undefined) {
|
|
101
|
+
pass.currentInput = pass.decision.updatedInput;
|
|
102
|
+
}
|
|
103
|
+
if (pass.deniedBy === undefined)
|
|
104
|
+
throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
|
|
105
|
+
const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
|
|
106
|
+
await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason: denyReason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
|
|
107
|
+
return {
|
|
108
|
+
block: true,
|
|
109
|
+
reason: formatHookFeedback(denyReason, input.reminderMark),
|
|
110
|
+
gate,
|
|
111
|
+
preToolContext: pass.preToolContext,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (pass.decision.action === "allow") {
|
|
115
|
+
const rw = pass.decision.updatedInput !== undefined ? pass.decision.updatedInput : pass.policyRewrite;
|
|
116
|
+
if (rw !== undefined)
|
|
117
|
+
pass.currentInput = rw;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
|
|
121
|
+
gate: mintGateOutcome(ledger.settled !== undefined ? { settled: ledger.settled } : {}),
|
|
122
|
+
preToolContext: pass.preToolContext,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -55,23 +55,23 @@ export declare function sanitizedTtlMs(ttlMs: number | undefined): number | unde
|
|
|
55
55
|
* SINGLE derivation on purpose. Two consumers need this answer: the abort-classification seam, which
|
|
56
56
|
* stamps it into the `details` of the results the loop mints for the contaminated siblings, and the
|
|
57
57
|
* `tool_end` projection, which puts it on the wire. Reading it from one function keeps the two from
|
|
58
|
-
* disagreeing about
|
|
58
|
+
* disagreeing about what a pause with no call means. They are not otherwise
|
|
59
59
|
* interchangeable: the frame face applies strictly narrower conditions on top of this answer (see
|
|
60
60
|
* `tool_end.gatedCallId`), so a frame may omit an id this function returns — never the reverse.
|
|
61
|
+
* "Which holder wins" is no longer a question: there is one holder, and a pause that holds no call
|
|
62
|
+
* publishes absence.
|
|
61
63
|
*
|
|
62
64
|
* The frame side must not read the id back out of a tool RESULT even though the marker is there: a
|
|
63
65
|
* result's `details` is written by the tool (and replaceable by post-tool hooks), so lifting a
|
|
64
66
|
* cross-call attribution from it would let any failing tool name an arbitrary call and put a phantom
|
|
65
|
-
* approval wait on someone else's frame. Same rule, same reason, as `
|
|
67
|
+
* approval wait on someone else's frame. Same rule, same reason, as `tool_end.gate`.
|
|
66
68
|
*
|
|
67
|
-
* The two holders are never both set (the commit-side discriminant writes exactly one); they are read
|
|
68
|
-
* here in assemble-result's slot order so the winner is the same one the terminal status is built from.
|
|
69
69
|
*/
|
|
70
|
-
export declare function gatedCallIdOf(p: Pick<Prepared, "
|
|
70
|
+
export declare function gatedCallIdOf(p: Pick<Prepared, "pausedRef">): string | undefined;
|
|
71
71
|
/**
|
|
72
72
|
* The abort-classification marker for the loop's short-circuit "Operation aborted" family: when a gate
|
|
73
|
-
* committed a durable park this run (`
|
|
74
|
-
*
|
|
73
|
+
* committed a durable park this run (`pausedRef.current`, whatever the pause's kind), the contaminated
|
|
74
|
+
* sibling frames carry something a consumer can key
|
|
75
75
|
* on instead of parsing the (contract-stable) result text (ruled 2026-08-04). The commit is published
|
|
76
76
|
* BEFORE the abort fires, so this read is race-free at mint time.
|
|
77
77
|
*
|
|
@@ -83,26 +83,20 @@ export declare function gatedCallIdOf(p: Pick<Prepared, "suspendRef" | "reviewRe
|
|
|
83
83
|
* Module-level rather than a closure in `prepareTask` (design/238 D-7: extract, don't accrete) — the
|
|
84
84
|
* marker's shape and its rules belong beside {@link gatedCallIdOf}, the derivation it reads.
|
|
85
85
|
*/
|
|
86
|
-
export declare function parkContaminationMarker(refs: Pick<Prepared, "
|
|
86
|
+
export declare function parkContaminationMarker(refs: Pick<Prepared, "pausedRef">): Record<string, unknown> | undefined;
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* the
|
|
91
|
-
*
|
|
92
|
-
* approval
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* dry-run `{kind:"needs_review"}` gate (design/76 §2.5) AND the design/80 D-B `{kind:"plan_review"}`
|
|
96
|
-
* gate — both are human-review pauses that share the `needs_review` terminal (a `plan_review` resumes
|
|
97
|
-
* with a `plan_review` outcome, a `needs_review` with `dry_run_review`; the distinct gate.kind keeps the
|
|
98
|
-
* resume discriminant unambiguous). (The review checkpoints themselves are minted by a PROFILE, out of
|
|
99
|
-
* core scope; core only owns this routing seam + the gate/status/resume discriminants.)
|
|
88
|
+
* THE COMMIT-SIDE PUBLICATION. Called AFTER `commitSuspendSaga` returns true (the checkpoint is durably
|
|
89
|
+
* committed) to publish the run's one committed pause for the run loop's terminal assembly. There is no
|
|
90
|
+
* routing: the cause carries the committed gate whole, and WHICH terminal status it reads
|
|
91
|
+
* (`suspended` / `needs_review`) is the gate kind's registry row — the two holders the review family and
|
|
92
|
+
* the approval family used to be split across, and the branch that chose between them, are gone.
|
|
93
|
+
* (Review checkpoints themselves are minted by a PROFILE, out of core scope; core owns this seam and the
|
|
94
|
+
* gate/status/resume discriminants.)
|
|
100
95
|
*
|
|
101
|
-
* Module-level, taking the
|
|
102
|
-
* closure form captured nothing else, so hoisting it costs nothing and puts the discriminant beside the
|
|
96
|
+
* Module-level, taking the holder as a parameter (design/238 D-7: extract, don't accrete) — beside the
|
|
103
97
|
* two readers of what it publishes ({@link gatedCallIdOf}, {@link parkContaminationMarker}).
|
|
104
98
|
*/
|
|
105
|
-
export declare function publishCommittedSuspend(refs: Pick<Prepared, "
|
|
99
|
+
export declare function publishCommittedSuspend(refs: Pick<Prepared, "pausedRef">, token: CheckpointToken, gate: CheckpointGate, scope: string, remoteHandle: WorkspaceHandle | undefined, checkpointId?: string,
|
|
106
100
|
/** The COMMITTED row's pendingAction — passed whole, not as a pre-extracted id, so no call site can
|
|
107
101
|
* hand this seam an id the committed row does not actually park on. */
|
|
108
102
|
pendingAction?: PendingAction): void;
|
|
@@ -7,26 +7,25 @@ export function sanitizedTtlMs(ttlMs) {
|
|
|
7
7
|
return Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : DEFAULT_RESOURCE_TTL_MS;
|
|
8
8
|
}
|
|
9
9
|
export function gatedCallIdOf(p) {
|
|
10
|
-
|
|
11
|
-
return p.suspendRef.gatedCallId;
|
|
12
|
-
if (p.reviewRef.token !== undefined)
|
|
13
|
-
return p.reviewRef.gatedCallId;
|
|
14
|
-
return undefined;
|
|
10
|
+
return p.pausedRef.current?.gatedCallId;
|
|
15
11
|
}
|
|
16
12
|
export function parkContaminationMarker(refs) {
|
|
17
|
-
if (refs.
|
|
13
|
+
if (refs.pausedRef.current === undefined)
|
|
18
14
|
return undefined;
|
|
19
15
|
const gatedCallId = gatedCallIdOf(refs);
|
|
20
16
|
return { code: "gate.parked", ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
21
17
|
}
|
|
22
18
|
export function publishCommittedSuspend(refs, token, gate, scope, remoteHandle, checkpointId, pendingAction) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
const pause = {
|
|
20
|
+
cause: {
|
|
21
|
+
kind: "paused",
|
|
22
|
+
gate,
|
|
23
|
+
token,
|
|
24
|
+
...(checkpointId !== undefined ? { checkpointId } : {}),
|
|
25
|
+
...(remoteHandle !== undefined ? { restoreMode: remoteHandle.restoreMode === "park_only" ? "park_only" : "snapshot" } : {}),
|
|
26
|
+
},
|
|
27
|
+
scope,
|
|
28
|
+
...(pendingAction?.kind === "tool_approval" ? { gatedCallId: pendingAction.toolCallId } : {}),
|
|
29
|
+
};
|
|
30
|
+
refs.pausedRef.current = pause;
|
|
32
31
|
}
|
|
@@ -60,9 +60,6 @@ export interface PrepareAskLaneInput {
|
|
|
60
60
|
* synchronous resolve that a person actually judged (count / totalWaitMs / gates push); runtask adds the durable
|
|
61
61
|
* resume latency. */
|
|
62
62
|
humanReviewRef: Prepared["humanReviewRef"];
|
|
63
|
-
/** borrowed-mutable — the bare-human-rejection sideband. Writer here: a `settledBy:"human"` deny with no note
|
|
64
|
-
* (`add`); the gate station judges and sweeps it. */
|
|
65
|
-
humanBareRejections: Set<string>;
|
|
66
63
|
/** borrowed-readonly — the injectable wall clock every human-time observable reads (`Prepared.now`). */
|
|
67
64
|
now: () => number;
|
|
68
65
|
/** borrowed-readonly — the policy-chain phase's rule-offer factory (the suggestion members the ask mint threads). */
|
|
@@ -55,7 +55,7 @@ function resolveApprovalPreview(tools, toolName, args) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
export function prepareAskLane(input) {
|
|
58
|
-
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef,
|
|
58
|
+
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash } = input;
|
|
59
59
|
if (!gateMachineryActive)
|
|
60
60
|
return { askLane: undefined };
|
|
61
61
|
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
|
|
@@ -77,6 +77,7 @@ export function prepareAskLane(input) {
|
|
|
77
77
|
`(no live approver reachable, or a durable-park mandate applies), and no durable approval park is ` +
|
|
78
78
|
`available — denied fail-closed (the inherited constraint stands).`,
|
|
79
79
|
decisionReason: "mode",
|
|
80
|
+
settlement: { kind: "approver_unavailable", who: { party: "none" }, when: now() },
|
|
80
81
|
approverUnavailable: true,
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -104,7 +105,7 @@ export function prepareAskLane(input) {
|
|
|
104
105
|
...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
|
|
105
106
|
}, onAsk, composedCallSignal(callSignal), lateAskSettlementObserver({ toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
|
|
106
107
|
const waitMs = Math.max(0, now() - t0);
|
|
107
|
-
if (resolved.approverUnavailable !== true && resolved.
|
|
108
|
+
if (resolved.approverUnavailable !== true && resolved.settlement?.kind !== "task_aborted") {
|
|
108
109
|
humanReviewRef.count += 1;
|
|
109
110
|
humanReviewRef.totalWaitMs += waitMs;
|
|
110
111
|
const toolArg = primaryActivityArg(req.args);
|
|
@@ -116,9 +117,6 @@ export function prepareAskLane(input) {
|
|
|
116
117
|
...(toolArg !== undefined ? { toolArg } : {}),
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
|
-
if (resolved.action === "deny" && resolved.settledBy === "human" && resolved.humanRefusalNote !== true) {
|
|
120
|
-
humanBareRejections.add(req.toolCallId);
|
|
121
|
-
}
|
|
122
120
|
return resolved;
|
|
123
121
|
};
|
|
124
122
|
const notifyPermissionDenied = createDenyObserverNotifier(hooks, hookTimeoutMs, abortController.signal, notifyOwnHookCrash);
|
|
@@ -78,12 +78,9 @@ export interface PrepareBoundaryParksInput {
|
|
|
78
78
|
f012CheckpointState: () => boolean;
|
|
79
79
|
/** borrowed-readonly — see `faceCheckpointState`. */
|
|
80
80
|
orgAdmissionCheckpointState: () => boolean;
|
|
81
|
-
/** borrowed-mutable — the
|
|
82
|
-
* committed resource
|
|
83
|
-
|
|
84
|
-
/** borrowed-mutable — the review holder (`Prepared.reviewRef`). Writer here: the commit-side publication of a committed
|
|
85
|
-
* plan_review row. */
|
|
86
|
-
reviewRef: Prepared["reviewRef"];
|
|
81
|
+
/** borrowed-mutable — the run's committed-pause holder (`Prepared.pausedRef`). Writer here: the commit-side publication
|
|
82
|
+
* of a committed resource / platform / plan-review checkpoint. */
|
|
83
|
+
pausedRef: Prepared["pausedRef"];
|
|
87
84
|
/** borrowed-mutable — the run-scoped remote-lifecycle failure log. Writer here: a refused `suspendVM` (`push`). */
|
|
88
85
|
remoteEnvFailures: Prepared["remoteEnvFailures"];
|
|
89
86
|
/** borrowed-readonly — the wiring-manifest phase's task-axis predicate (opt-in ∧ durable infra): exposes `suspendForResource`. */
|
|
@@ -5,7 +5,7 @@ import { checkpointScopeOf } from "./checkpoint-scope.js";
|
|
|
5
5
|
import { DEFAULT_RESOURCE_TTL_MS, publishCommittedSuspend, sanitizedTtlMs, USAGE_WINDOW_REAP_MARGIN_MS } from "./park-commit.js";
|
|
6
6
|
import { remoteEnvFailureNote } from "./remote-env-retry.js";
|
|
7
7
|
export function prepareBoundaryParks(input) {
|
|
8
|
-
const { saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState,
|
|
8
|
+
const { saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter } = input;
|
|
9
9
|
if (saga === undefined)
|
|
10
10
|
return { suspendForResource: undefined, suspendForPlatformLimit: undefined, suspendForReview: undefined };
|
|
11
11
|
const { inFlightSpendMicroUsd, serializeCheckpointState, commitSuspendSaga, suspendLoopCapHit, suspendableEnv, parkOnlyRemoteEnv, parkOnlyHandle } = saga;
|
|
@@ -67,7 +67,7 @@ export function prepareBoundaryParks(input) {
|
|
|
67
67
|
};
|
|
68
68
|
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
69
69
|
return false;
|
|
70
|
-
publishCommittedSuspend({
|
|
70
|
+
publishCommittedSuspend({ pausedRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
71
71
|
try {
|
|
72
72
|
await sessions.pin?.(sessionId);
|
|
73
73
|
}
|
|
@@ -153,7 +153,7 @@ export function prepareBoundaryParks(input) {
|
|
|
153
153
|
};
|
|
154
154
|
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
155
155
|
return false;
|
|
156
|
-
publishCommittedSuspend({
|
|
156
|
+
publishCommittedSuspend({ pausedRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
157
157
|
try {
|
|
158
158
|
await sessions.pin?.(sessionId);
|
|
159
159
|
}
|
|
@@ -22,7 +22,6 @@ import type { AgentHarness, AgentTool, ThinkingLevel } from "../../internal/harn
|
|
|
22
22
|
import type { Model } from "../../internal/llm.js";
|
|
23
23
|
import { forkGovernanceDenial } from "../../agents/subagent.js";
|
|
24
24
|
import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
|
|
25
|
-
import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
|
|
26
25
|
import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
27
26
|
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
28
27
|
import { type AutoModeDecider, type AutoModeDenialTracker } from "../auto-mode.js";
|
|
@@ -33,11 +32,8 @@ import type { OnAsk } from "../tool-policy.js";
|
|
|
33
32
|
import type { RunnerDeps, RuntimeCaps, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
|
|
34
33
|
import type { AutoModeArmReason } from "../wiring-manifest.js";
|
|
35
34
|
import type { ReadFace } from "../../tools/fs/index.js";
|
|
36
|
-
import type { InheritedGate, Prepared, RunInternals, ToolFaceSnapshot } from "./contracts.js";
|
|
35
|
+
import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
|
|
37
36
|
import { type BlockedRef } from "./synthetic-tools.js";
|
|
38
|
-
/** The runner the Workflow mount accepts — spelled as the factory's own option type so this phase names no runner
|
|
39
|
-
* module of its own (the narrow `RunnerSelfSeat` contract is a design decision still open; see the layering registry). */
|
|
40
|
-
type WorkflowMountRunner = Parameters<typeof createRunWorkflowTool>[0]["runner"];
|
|
41
37
|
export interface PrepareCapsAndWorkflowInput {
|
|
42
38
|
/** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
|
|
43
39
|
* `enablePlanMode`, `interactiveTools`, `checkpointStore`; the caps/compliance stations read `principal`, `selfOrchestration`,
|
|
@@ -113,7 +109,7 @@ export interface PrepareCapsAndWorkflowInput {
|
|
|
113
109
|
peerSendMessageBuiltIn: boolean;
|
|
114
110
|
/** borrowed-readonly — the trusted Runner self-reference the Workflow mount executes children through; undefined when
|
|
115
111
|
* prepareTask runs standalone (then Workflow is simply not mounted). */
|
|
116
|
-
runnerSelf:
|
|
112
|
+
runnerSelf: RunnerSelfSeat | undefined;
|
|
117
113
|
/** borrowed-readonly — LATE-BOUND: the memory-engine session; the Workflow mount's capture-floor getter reads it at ITS call time. */
|
|
118
114
|
memoryEngineSession: () => Prepared["memoryEngineSession"];
|
|
119
115
|
/** borrowed-readonly — LATE-BOUND: the adopted center artifact; the Workflow mount's two center getters read it at THEIR call time. */
|
|
@@ -167,4 +163,3 @@ export interface PrepareCapsAndWorkflowResult {
|
|
|
167
163
|
}
|
|
168
164
|
/** The M4 phase body — prepareTask's caps-and-workflow stretch, verbatim (see the module header). */
|
|
169
165
|
export declare function prepareCapsAndWorkflow(input: PrepareCapsAndWorkflowInput): Promise<PrepareCapsAndWorkflowResult>;
|
|
170
|
-
export {};
|
|
@@ -244,7 +244,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
244
244
|
},
|
|
245
245
|
...(spec.handsReadOnly === true ? { parentHandsReadOnly: true } : {}), ...(spec.interactiveTools === false ? { parentInteractiveTools: false } : {}),
|
|
246
246
|
onNotice: deps.onNotice,
|
|
247
|
-
parentCheckpointStoreDisabled: spec.checkpointStore ===
|
|
247
|
+
parentCheckpointStoreDisabled: spec.checkpointStore === "disabled",
|
|
248
248
|
parentCenterArtifactDigest: () => input.centerAdoption()?.artifact.artifactDigest,
|
|
249
249
|
parentCenterSourceRevision: () => input.centerAdoption()?.sourceRevision,
|
|
250
250
|
...(forwardEvent ? { forwardEvent } : {}),
|