@sema-agent/core 7.38.2 → 7.39.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.
- package/CHANGELOG.md +175 -6
- package/dist/core/ask-origin.d.ts +16 -7
- package/dist/core/ask-origin.js +4 -2
- package/dist/core/auto-mode-directive.d.ts +114 -19
- package/dist/core/auto-mode-directive.js +67 -19
- package/dist/core/checkpoint-store.d.ts +72 -41
- package/dist/core/checkpoint-store.js +28 -48
- package/dist/core/classifier-unavailable-arm.js +2 -2
- package/dist/core/decision-receipt.d.ts +2 -0
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/engine-notice.d.ts +41 -17
- package/dist/core/engine-notice.js +26 -14
- package/dist/core/gate-outcome.d.ts +80 -42
- package/dist/core/gate-outcome.js +21 -6
- package/dist/core/governance-codes.d.ts +1 -3
- package/dist/core/governance-codes.js +0 -3
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/hooks.js +2 -2
- package/dist/core/human-input-projection.d.ts +12 -0
- package/dist/core/human-input-projection.js +12 -0
- package/dist/core/memory-engine/engine.js +11 -11
- package/dist/core/memory-engine/layout.d.ts +21 -2
- package/dist/core/memory-engine/layout.js +17 -5
- package/dist/core/mode-policy.d.ts +26 -0
- package/dist/core/mode-policy.js +37 -1
- package/dist/core/output-style.d.ts +21 -0
- package/dist/core/output-style.js +29 -0
- package/dist/core/real-approval.d.ts +25 -0
- package/dist/core/real-approval.js +27 -0
- package/dist/core/reminder-mint.d.ts +0 -9
- package/dist/core/reminder-mint.js +0 -11
- package/dist/core/retired-keys.d.ts +2 -1
- package/dist/core/retired-keys.js +3 -1
- package/dist/core/runner/assemble-result.d.ts +2 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/compaction-restate.d.ts +45 -4
- package/dist/core/runner/compaction-restate.js +89 -11
- package/dist/core/runner/contracts.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/inherited-ask-grants.d.ts +4 -2
- package/dist/core/runner/inherited-ask-grants.js +0 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +18 -0
- package/dist/core/runner/permission-rule-lanes.js +35 -25
- package/dist/core/runner/prepare-context-lane.d.ts +1 -0
- package/dist/core/runner/prepare-context-lane.js +6 -0
- package/dist/core/runner/prepare-inherited-gate.js +2 -9
- package/dist/core/runner/prepare-task.js +10 -24
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prompt-block.d.ts +9 -0
- package/dist/core/runner/prompt-block.js +8 -0
- package/dist/core/runner/resume-admission.js +4 -1
- package/dist/core/runner/resume-apply.js +28 -18
- package/dist/core/runner/resume-policy-outcome.js +10 -4
- package/dist/core/runner/run-attachment-seats.js +1 -1
- package/dist/core/runner/run-identity-wiring.js +9 -2
- package/dist/core/runner/run-leg.js +42 -31
- package/dist/core/runner/run-settle-and-teardown.js +4 -3
- package/dist/core/runner/run-turn-boundary.js +4 -3
- package/dist/core/runner/runtask.d.ts +4 -2
- package/dist/core/runner/steer-admission.d.ts +9 -0
- package/dist/core/runner/steer-admission.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +16 -11
- package/dist/core/runner/synthetic-tools.d.ts +4 -0
- package/dist/core/runner/tool-turn-repair-seat.js +1 -1
- package/dist/core/runner/turn-attachments.d.ts +16 -22
- package/dist/core/runner/turn-attachments.js +3 -20
- package/dist/core/runner-deps.d.ts +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +8 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
- package/dist/core/task-event.d.ts +7 -2
- package/dist/core/task-spec.d.ts +29 -7
- package/dist/core/terminal-cause.d.ts +44 -3
- package/dist/core/terminal-cause.js +5 -0
- package/dist/core/tool-policy.d.ts +55 -29
- package/dist/core/tool-policy.js +42 -31
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +17 -3
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/engine/harness/agent-harness.d.ts +10 -0
- package/dist/engine/harness/agent-harness.js +12 -1
- package/dist/engine/llm/tool-turns.d.ts +15 -2
- package/dist/engine/llm/tool-turns.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/prompt-assembly/event-registry.js +2 -1
- package/dist/stores/file/fs-atomic.d.ts +29 -4
- package/dist/stores/file/fs-atomic.js +55 -6
- package/dist/tools/fs/index.d.ts +2 -1
- package/dist/tools/fs/read-deny-home.d.ts +3 -2
- package/dist/tools/fs/read-deny-home.js +6 -4
- package/dist/tools/fs/shell-read-check.d.ts +10 -3
- package/dist/tools/fs/shell-read-check.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +21 -1
- package/wire-changes/core@7.38.2.json +20 -2
- package/wire-changes/core@7.38.3.json +36 -0
- package/wire-changes/core@7.39.0.json +845 -0
|
@@ -703,7 +703,7 @@ export type TaskEvent = ({
|
|
|
703
703
|
* is unchanged. `preview` is the injected text clipped to a display bound.
|
|
704
704
|
*
|
|
705
705
|
* design/133 (LOW-11/F11): the turn-boundary attachment producers ride the same echo — sources
|
|
706
|
-
* `todo_reminder` / `task_reminder` / `changed_files`
|
|
706
|
+
* `todo_reminder` / `task_reminder` / `changed_files` and the G1/续批 members
|
|
707
707
|
* `background_tasks` / `tools_delta` / `agent_listing` / `skills_listing` / `mcp_instructions`
|
|
708
708
|
* (one event per attachment; a multi-attachment boundary still coalesces into ONE steer message
|
|
709
709
|
* on the model lane). [c209-C]: the listing family's FIRST-FRAME deliveries (initial roster /
|
|
@@ -714,9 +714,14 @@ export type TaskEvent = ({
|
|
|
714
714
|
* (first-frame / resume-continuation / compaction re-assert deliveries alike). Its `preview`
|
|
715
715
|
* is a CONSTANT wording on purpose (never frame bytes): branch names and status text are
|
|
716
716
|
* repo-controlled and must not enter the event telemetry plane through this echo.
|
|
717
|
+
*
|
|
718
|
+
* `output_style` (additive member): the frame stating the deployment-declared `TaskSpec.outputStyle`, echoed
|
|
719
|
+
* at every delivery (a leg's first turn, a resumed leg's continuation, each continuing boundary).
|
|
720
|
+
* The permission-posture frames (planning / bypass / auto, and their exits) are model-lane only and have no
|
|
721
|
+
* echo; the retired `plan_mode` member was the echo of the opt-in planning reminder they replaced.
|
|
717
722
|
*/
|
|
718
723
|
type: "steering_injected";
|
|
719
|
-
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "
|
|
724
|
+
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "output_style" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "total_tokens_reminder" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification" | "git_status";
|
|
720
725
|
preview: string;
|
|
721
726
|
} & TaskEventIdentity) | ({
|
|
722
727
|
/**
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -252,6 +252,28 @@ export interface TaskSpec {
|
|
|
252
252
|
* so it never breaks prefix-cache friendliness. Composes WITH `systemPrompt` (appended after it).
|
|
253
253
|
*/
|
|
254
254
|
appendSystemPrompt?: string;
|
|
255
|
+
/**
|
|
256
|
+
* The OUTPUT STYLE the deployment's shell has active, declared so the engine can state it to the model in its
|
|
257
|
+
* own marked reminder: `"<name> output style is active. <turnReminder>"` (the reference implementation's
|
|
258
|
+
* per-turn reminder sentence; absent `turnReminder` ⇒ its default "Remember to follow the specific guidelines
|
|
259
|
+
* for this style."). The style's guidelines themselves are not carried here — they go in `systemPrompt` /
|
|
260
|
+
* `appendSystemPrompt`; this seat is the per-turn statement that the style is in force, which a reminder the
|
|
261
|
+
* shell splices into the objective cannot be (unmarked reminder-shaped text is data). Declare it only for a
|
|
262
|
+
* non-default style; there is no client-trusted reminder seat behind it — the engine renders the sentence.
|
|
263
|
+
*
|
|
264
|
+
* Cadence: the first turn of every leg (a resumed leg's continuation included) and every continuing turn
|
|
265
|
+
* boundary — the reference attaches it at every attachment cycle, with no window. Echoed as
|
|
266
|
+
* `steering_injected` with source `output_style`.
|
|
267
|
+
*
|
|
268
|
+
* Refused at prepare (`config.output_style_invalid`), never ignored: a value that is not an object of exactly
|
|
269
|
+
* these two members; a `name` that is not a string, is empty, or is longer than 256 characters
|
|
270
|
+
* (`OUTPUT_STYLE_NAME_MAX`, the reference implementation's own bound — it drops the reminder past it); a
|
|
271
|
+
* `turnReminder` that is present but not a non-empty string.
|
|
272
|
+
*/
|
|
273
|
+
outputStyle?: {
|
|
274
|
+
name: string;
|
|
275
|
+
turnReminder?: string;
|
|
276
|
+
};
|
|
255
277
|
/**
|
|
256
278
|
* Per-task ROLE-LAYER prompt provider (overrides `RunnerDeps.promptProvider`). Lets one Runner serve
|
|
257
279
|
* different scenarios with different role prompts. 提示词主权批: `stableSystem` supplies the role base
|
|
@@ -889,7 +911,7 @@ export interface TaskSpec {
|
|
|
889
911
|
enableFork?: boolean;
|
|
890
912
|
/**
|
|
891
913
|
* The OPERATOR's shell floor: `"always"` ⇒ every `bash` / `Monitor` command is a confirmed ask (an
|
|
892
|
-
* `operator_forced` ask — kept under `bypassPermissions`,
|
|
914
|
+
* `operator_forced` ask — kept under `bypassPermissions`, refused by `dontAsk` like every ask; a standing allow rule never
|
|
893
915
|
* clears it). Absent ⇒ no floor: the shape tier the engine installs on every full shell (a provably read-only
|
|
894
916
|
* single command runs, everything else asks) is arbitrated by the task's {@link permissionMode}. The retired
|
|
895
917
|
* values `"off"` / `"classify"` are refused at the door (`config.shell_gate_value_retired`): the tier is no longer a
|
|
@@ -912,7 +934,7 @@ export interface TaskSpec {
|
|
|
912
934
|
* `bypassPermissions` releases every ask but the immune classes (an explicit ask rule, a hook's ask, the
|
|
913
935
|
* question tool, an unverifiable recursive delete, an org rule, an operator's forced confirmation, unreadable
|
|
914
936
|
* governance, the classifier's denial-limit fallback) and discloses each release; `dontAsk` refuses every ask
|
|
915
|
-
* but a hook's or
|
|
937
|
+
* but a hook's own (no rule or safety check also asked); `auto` arms the classifier (`RunnerDeps.autoMode` present and
|
|
916
938
|
* `RuntimeCaps.autoMode` not `false`, else folded to `default` and announced); `plan` reads only and mounts the
|
|
917
939
|
* plan pair; `acceptEdits` writes inside the root without asking; `default` asks. The mode also installs its own
|
|
918
940
|
* seats ({@link import("./mode-policy.js").modePolicyOf}).
|
|
@@ -1240,9 +1262,6 @@ export interface TaskSpec {
|
|
|
1240
1262
|
* whose mtime moved past the recorded read time (+2s epsilon) is listed (path-only v1, "do not
|
|
1241
1263
|
* blindly revert — re-read"). OFF ⇒ zero stat calls. Self-edits are immune (write-back
|
|
1242
1264
|
* refreshes the read stamp); mtime skew on exotic fs remains a documented false-positive source.
|
|
1243
|
-
* - `planModeReminder`: while `enter_plan_mode` is active, re-surface the read-only constraint
|
|
1244
|
-
* every 5 assistant turns (full copy every 5th, sparse between) so a long plan phase doesn't
|
|
1245
|
-
* drift into write-deny thrash. Exiting plan mode stops it.
|
|
1246
1265
|
* - `backgroundTasks` (G1 通告层 — CC /compact parity): after a compaction LANDS, the
|
|
1247
1266
|
* next boundary announces this run's still pending/running background tasks (task registry
|
|
1248
1267
|
* snapshot: id/description/status, CC three-state copy). ONE-SHOT per compaction — never
|
|
@@ -1274,8 +1293,12 @@ export interface TaskSpec {
|
|
|
1274
1293
|
* the add lane for a future dynamic-connect face. Added instruction text is UNTRUSTED server
|
|
1275
1294
|
* text → fenced with `delimitUntrusted` (deliberate CC deviation: CC injects it raw).
|
|
1276
1295
|
*
|
|
1296
|
+
* The read-only planning posture is not a member: the engine states it whenever the leg's mode is `plan`
|
|
1297
|
+
* (with its restatement cadence and its exit frame), as it states the bypass and auto postures. A spec still
|
|
1298
|
+
* carrying the retired `planModeReminder` key is refused at prepare (`config.spec_retired_key`).
|
|
1299
|
+
*
|
|
1277
1300
|
* Telemetry: `steering_injected` (source `todo_reminder`/`task_reminder`/`changed_files`/
|
|
1278
|
-
* `
|
|
1301
|
+
* `output_style`/`background_tasks`/`tools_delta`/`agent_listing`/`skills_listing`/
|
|
1279
1302
|
* `mcp_instructions`) + `stats.mechanisms.attachmentsInjected` (first-frame listing deliveries
|
|
1280
1303
|
* emit the same echo frames and count too — source-accurate observability for the shell).
|
|
1281
1304
|
*/
|
|
@@ -1308,7 +1331,6 @@ export interface TaskSpec {
|
|
|
1308
1331
|
changedFiles?: true | {
|
|
1309
1332
|
maxFiles?: number;
|
|
1310
1333
|
};
|
|
1311
|
-
planModeReminder?: true;
|
|
1312
1334
|
/**
|
|
1313
1335
|
* SR-3 (CC 198 `budget_usd` lane, producer QFm pretty.js:480424-480428 + renderer :600225): the
|
|
1314
1336
|
* progressive $-budget readout `USD budget: $used/$total; $remaining remaining` (CC-verbatim
|
|
@@ -20,7 +20,9 @@ import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
|
20
20
|
* the one cause→plane derivation, for the faces that still speak the word.
|
|
21
21
|
* - `completed` — the run finished (a person's clean halt included — see `TaskResult.haltedByUser`).
|
|
22
22
|
* - `failed` — a limit, a provider failure, an abort, an invalid output…
|
|
23
|
-
* - `blocked` — the
|
|
23
|
+
* - `blocked` — the run ended without doing the work, and `detail` says by whom: ABSENT = the AGENT reported,
|
|
24
|
+
* through `ReportBlocked`, that it cannot finish, and why; `detail.kind === "prompt_blocked_by_hook"` = a
|
|
25
|
+
* `userPromptSubmit` hook kept the prompt from being submitted (see {@link TerminalBlockedDetail}). A TERMINAL
|
|
24
26
|
* cause: the leg is over and it waits for nobody (nothing to resume, no token). The waiting-on-a-person
|
|
25
27
|
* states are the `paused` cause's two status words — `suspended` (a pre-action approval of a tool
|
|
26
28
|
* call) and `needs_review` (a review pause); the task-registry face spells both as `parked`.
|
|
@@ -87,9 +89,23 @@ export type TerminalCause = {
|
|
|
87
89
|
nestedPause?: PausedCause;
|
|
88
90
|
} | {
|
|
89
91
|
kind: "blocked";
|
|
90
|
-
/**
|
|
91
|
-
*
|
|
92
|
+
/**
|
|
93
|
+
* Why the run ended without doing the work — one of two sources, told apart by {@link detail}. With no
|
|
94
|
+
* `detail`: the agent's own words from its `ReportBlocked` call. With `detail.kind ===
|
|
95
|
+
* "prompt_blocked_by_hook"`: the engine's sentence about the deployment's `userPromptSubmit` hook, framed
|
|
96
|
+
* as a reminder for the model-facing lane (the hook's own words, when it gave some, sit inside the
|
|
97
|
+
* frame). Terminal: the run is over and nobody is being waited on (contrast `paused`, whose token is the
|
|
98
|
+
* resume capability).
|
|
99
|
+
*/
|
|
92
100
|
reason: string;
|
|
101
|
+
/**
|
|
102
|
+
* WHO stopped the run, for the one shape a consumer offers its own way out of: `prompt_blocked_by_hook` —
|
|
103
|
+
* the prompt was NOT submitted (the objective never became a message and the loop never started), so
|
|
104
|
+
* changing it and sending again is meaningful; `cause` says which of the hook's four endings it was.
|
|
105
|
+
* PRESENCE: only on that terminal. ABSENT means the agent reported `ReportBlocked` ("I cannot go on"), or an
|
|
106
|
+
* engine from before this key — branch on `detail?.kind`, never on the prose in `reason`.
|
|
107
|
+
*/
|
|
108
|
+
detail?: TerminalBlockedDetail;
|
|
93
109
|
} | {
|
|
94
110
|
kind: "paused";
|
|
95
111
|
/**
|
|
@@ -136,6 +152,31 @@ export interface TerminalFailureDetail {
|
|
|
136
152
|
/** The provider's own word for the refusal (its error envelope's `error.code`, else `error.type`), when stated. */
|
|
137
153
|
code?: string;
|
|
138
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The endings of a `userPromptSubmit` hook that keep a prompt from being submitted — the closed set of
|
|
157
|
+
* {@link TerminalBlockedDetail.cause} words:
|
|
158
|
+
* - `"block"` — the hook answered and refused the prompt (its words ride `hookReason`).
|
|
159
|
+
* - `"crashed"` — the hook threw or rejected; the seat is fail-closed, a screen that failed has not cleared the prompt.
|
|
160
|
+
* - `"timeout"` — the hook did not answer within its `timeoutMs` bound; fail-closed the same way.
|
|
161
|
+
* - `"cancelled"` — the task was cancelled while the hook was still screening.
|
|
162
|
+
*/
|
|
163
|
+
export declare const PROMPT_BLOCK_CAUSES: readonly ["block", "crashed", "timeout", "cancelled"];
|
|
164
|
+
export type PromptBlockCause = (typeof PROMPT_BLOCK_CAUSES)[number];
|
|
165
|
+
/** Membership test for {@link PromptBlockCause}. */
|
|
166
|
+
export declare function isPromptBlockCause(v: unknown): v is PromptBlockCause;
|
|
167
|
+
/** The machine-readable account a `blocked` cause may carry ({@link TerminalCause}). `kind` is closed; one member. */
|
|
168
|
+
export interface TerminalBlockedDetail {
|
|
169
|
+
kind: "prompt_blocked_by_hook";
|
|
170
|
+
/** Which of the hook seat's endings kept the prompt back ({@link PROMPT_BLOCK_CAUSES}). */
|
|
171
|
+
cause: PromptBlockCause;
|
|
172
|
+
/**
|
|
173
|
+
* The hook's own words for refusing the prompt, folded to one line, bounded, with the engine's envelope tags
|
|
174
|
+
* defused — still the hook's text, so a consumer renders it as untrusted. PRESENT only on cause `"block"` and
|
|
175
|
+
* only when the hook gave words; never carries the engine's reminder frame or its session mark (the framed
|
|
176
|
+
* sentence is `reason`).
|
|
177
|
+
*/
|
|
178
|
+
hookReason?: string;
|
|
179
|
+
}
|
|
139
180
|
/** The paused arm alone — the shape the run's committed-pause holder carries. */
|
|
140
181
|
/**
|
|
141
182
|
* The one disposition table over `TerminalCause["kind"]`: which causes a resume journal may REPLAY
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export const PROMPT_BLOCK_CAUSES = Object.freeze(["block", "crashed", "timeout", "cancelled"]);
|
|
2
|
+
const PROMPT_BLOCK_CAUSE_SET = new Set(PROMPT_BLOCK_CAUSES);
|
|
3
|
+
export function isPromptBlockCause(v) {
|
|
4
|
+
return PROMPT_BLOCK_CAUSE_SET.has(v);
|
|
5
|
+
}
|
|
1
6
|
export const TERMINAL_CAUSE_IS_REPLAYABLE = {
|
|
2
7
|
completed: true,
|
|
3
8
|
failed: false,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AskOrigin, type AssertAllKeysHandled } from "./ask-origin.js";
|
|
2
2
|
import { type ReadRootGrantScreen } from "./read-root-grant-screen.js";
|
|
3
3
|
import { type Settlement } from "./gate-outcome.js";
|
|
4
|
+
export { policyCannotAnswer, rowDemandsAPerson } from "./real-approval.js";
|
|
4
5
|
import { type DecisionReason } from "./decision-receipt.js";
|
|
5
6
|
export { isEngineRefusal } from "./decision-receipt.js";
|
|
6
7
|
/** A tool call presented to a policy before it executes. */
|
|
@@ -160,8 +161,8 @@ export declare function advertisedWritableRootsSeat(seat: {
|
|
|
160
161
|
export type { DecisionReason };
|
|
161
162
|
/** Carry every engine attestation from a decision onto a reconstruction of it — the
|
|
162
163
|
* seam for the engine's OWN clone sites outside this module (the inherited chain's final-form re-check carries the
|
|
163
|
-
* post-rewrite args on a spread of an ancestor's deny). Not re-exported from `src/index.ts`. */
|
|
164
|
-
export declare function carryEngineAttestations(from: object, to:
|
|
164
|
+
* post-rewrite args on a spread of an ancestor's deny). Returns the reconstruction. Not re-exported from `src/index.ts`. */
|
|
165
|
+
export declare function carryEngineAttestations<T extends object>(from: object, to: T): T;
|
|
165
166
|
/** Carry a PRECEDING decision's settlement onto a later deny that has none of its own — the veto record (a person's
|
|
166
167
|
* yes on the settlement, the refusing layer on the disposition; invariant I3) for the engine's own clone sites,
|
|
167
168
|
* where the deny is minted over a decision that already carried an attested approval. A deny that carries its own
|
|
@@ -255,7 +256,9 @@ export declare function screenApproverAttribution(v: unknown): {
|
|
|
255
256
|
* configuration is "no approver looked at this at all", indistinguishable from headless, for a rule
|
|
256
257
|
* whose whole point is that a blanket policy setting must not be the thing that clears it (see
|
|
257
258
|
* {@link resolveAsk}). A live auto-mode classifier verdict (tried earlier in the gate, if configured)
|
|
258
|
-
* or a genuine approver CALLBACK (actual judgment, even if it always says yes) still can
|
|
259
|
+
* or a genuine approver CALLBACK (actual judgment, even if it always says yes) still can — unless the
|
|
260
|
+
* callback itself reports its allow as the deployment's POLICY (`settledBy: "policy"`): that is the same
|
|
261
|
+
* "nobody judged this" a blanket seat is, and it is refused the same way.
|
|
259
262
|
* - `deny` blocks the call.
|
|
260
263
|
* Human-readable text is `message`; read it via {@link decisionText}.
|
|
261
264
|
*
|
|
@@ -402,14 +405,15 @@ export type PermissionResult = {
|
|
|
402
405
|
ruleUnreadable?: "content_deny" | "content_ask" | "persisted_call";
|
|
403
406
|
/** The ask was authored by the DEPLOYMENT's own governance (an operator's must-ask list, a managed command
|
|
404
407
|
* policy's default ask) — a fact a host's policy states on its own ask so the class derivation reads it
|
|
405
|
-
* as `operator_forced`: a permission mode releases nothing an operator mandated
|
|
406
|
-
*
|
|
408
|
+
* as `operator_forced`: a permission mode releases nothing an operator mandated (`dontAsk` refuses it like
|
|
409
|
+
* every ask). Public: a host composing such a policy sets it; {@link combinePolicies} carries it onto the
|
|
407
410
|
* surviving ask (tighten-only). Not on the approval request — the card shows `origin` / `mandate`. */
|
|
408
411
|
authoredBy?: "deployment";
|
|
409
412
|
/** The classes of the OTHER asks a policy fold collapsed into this one
|
|
410
413
|
* ({@link import("./ask-origin.js").decisionAskClassesOf} per contributing ask, unioned), so a member the
|
|
411
414
|
* fold does not copy — a guard mark, an unreadable-rule fact, a bare real-approval bit — is not lost behind
|
|
412
|
-
* the ask that won the slot. ENGINE-WRITTEN by {@link combinePolicies}
|
|
415
|
+
* the ask that won the slot. ENGINE-WRITTEN by {@link combinePolicies}, which attests the object it returns; the receipt
|
|
416
|
+
* refuses the member on any other object (a policy's own, a copy, an edited list). Fold-internal, never on the request. */
|
|
413
417
|
foldedAskClasses?: readonly import("./ask-origin.js").AskClass[];
|
|
414
418
|
} | {
|
|
415
419
|
action: "deny";
|
|
@@ -757,7 +761,7 @@ export declare const DECISION_MEMBERS: readonly DecisionMember[];
|
|
|
757
761
|
* consumers with two values, and which consumer read which value would decide the call. So the caller's object is read
|
|
758
762
|
* ONCE, here, and every station after the receipt (the fold's ranking, the origin stamp, the classifier's eligibility, the
|
|
759
763
|
* mode arbitration, the approval request, the rewrite that executes) reads the copy; a copy handed to the screen again
|
|
760
|
-
* comes back as it is, unread. In order: (1) the CARRIER's type — a decision that is not an object (a string, a function
|
|
764
|
+
* comes back as it is, unread (so does the engine's own refusal, {@link isEngineRefusal}). In order: (1) the CARRIER's type — a decision that is not an object (a string, a function
|
|
761
765
|
* holding the members) has no `action`; (2) the ONE READ — ONE function ({@link receiveContract}) for the decision and for the
|
|
762
766
|
* records it nests (`receiveNestedRecords`, decision-receipt.ts), on an ORDINARY object (a Proxy, as the carrier or anywhere on its prototype
|
|
763
767
|
* chain, is refused before a trap of its could run): each contract member absent or an own enumerable DATA property, its value
|
|
@@ -793,6 +797,20 @@ export declare const DECISION_MEMBERS: readonly DecisionMember[];
|
|
|
793
797
|
export declare function refuseOutOfContractDecision(d: PermissionResult, opts?: {
|
|
794
798
|
reasonIsNonInput?: boolean;
|
|
795
799
|
}): PermissionResult;
|
|
800
|
+
/**
|
|
801
|
+
* The PreToolUse hook seat's receipt under its public name — what a host that composes several hooks into the one the
|
|
802
|
+
* engine takes runs on EACH slot's result before folding it, so it reads the result the way the engine will. It is
|
|
803
|
+
* {@link refuseOutOfContractDecision} at the hook seam (`reasonIsNonInput`), the same function the engine's seat calls (hooks.ts
|
|
804
|
+
* `screenPreToolUseResult`): no second statement of the contract.
|
|
805
|
+
*
|
|
806
|
+
* What comes back, for an object: the engine's own refusal (a `deny` for which {@link isEngineRefusal} is true — return it as the
|
|
807
|
+
* composed hook's answer and the engine takes it as its own fail-closed deny), or the engine's frozen, detached copy of the
|
|
808
|
+
* result (fold the copy, never the original). `undefined`, `null` and `false` are no opinion and come back as themselves. The
|
|
809
|
+
* caller's object is not changed and none of its code runs; an answer handed back to this function returns as itself.
|
|
810
|
+
*/
|
|
811
|
+
export declare function receiveHookResult(result: unknown): (PermissionResult & {
|
|
812
|
+
additionalContext?: string;
|
|
813
|
+
}) | null | undefined | false;
|
|
796
814
|
/** The raw tool-name lists a name-keyed policy was built from (audit feed, see block note above). */
|
|
797
815
|
export interface ToolPolicyNameSets {
|
|
798
816
|
readonly allow?: readonly string[];
|
|
@@ -861,9 +879,10 @@ export type ApprovalSeatOutcome = boolean | (Extract<AskOutcome, {
|
|
|
861
879
|
* WHAT `approve` MAY RETURN: a boolean, as always, or the `{allow, …}` OUTCOME OBJECT the `onAsk` seat
|
|
862
880
|
* takes, read by the SAME screen ({@link readApproverOutcomeObject}) and narrowed to what this seat has
|
|
863
881
|
* a lane for ({@link ApprovalSeatOutcome}) — so a deployment here can report WHO ended the wait
|
|
864
|
-
* (`settledBy:"policy"`
|
|
865
|
-
* `
|
|
866
|
-
* the
|
|
882
|
+
* (`settledBy:"policy"` for its own policy's yes or no, `"timeout"` for its own elapsed window), attach the
|
|
883
|
+
* decider's `reason` and name the `approver`. A bare `true` / `false` is unchanged: a person's approval or
|
|
884
|
+
* refusal, the refusal with this factory's own terse sentence. Before the object arm a bare boolean was the
|
|
885
|
+
* only spelling there was, so a machine's decision filed a PERSON's record.
|
|
867
886
|
*/
|
|
868
887
|
export declare function createApprovalPolicy(opts: {
|
|
869
888
|
/** Tools that need an approval decision. */
|
|
@@ -1302,9 +1321,10 @@ export interface AskRequest {
|
|
|
1302
1321
|
* drives which deny text the model is told (stop-and-wait vs adapt-or-report), forks included. */
|
|
1303
1322
|
readonly isDelegatedChild?: true;
|
|
1304
1323
|
/** Carried from the originating {@link PermissionResult}'s ask
|
|
1305
|
-
* variant of the same name: `true` ⇒ {@link resolveAsk} must not let
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1324
|
+
* variant of the same name: `true` ⇒ {@link resolveAsk} must not let an allow NO PERSON JUDGED clear
|
|
1325
|
+
* this ask — a blanket `onAsk: "allow"`, or an approver's allow it reports as the deployment's policy
|
|
1326
|
+
* (`settledBy: "policy"`) (see that field's doc). `readonly`, filled by the gate from the decision, never
|
|
1327
|
+
* a caller/worker-settable field. */
|
|
1308
1328
|
readonly requiresRealApproval?: boolean;
|
|
1309
1329
|
/**
|
|
1310
1330
|
* PRESENCE ONLY, and a FACT ABOUT THIS ASK: no persisted allow rule and no STANDING ANSWER — a release
|
|
@@ -1499,15 +1519,19 @@ export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal)
|
|
|
1499
1519
|
* refusing. The three words the host may self-report:
|
|
1500
1520
|
* - `"human"` — a person answered (identical to omitting the field);
|
|
1501
1521
|
* - `"timeout"` — the host's own approval window elapsed with no answer;
|
|
1502
|
-
* - `"policy"` — the deployment's OWN policy answered: a machine
|
|
1503
|
-
* whose approver seat exists but can show nobody a
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1522
|
+
* - `"policy"` — the deployment's OWN policy answered, either way: a machine decided, not a person (an
|
|
1523
|
+
* unattended lane answering by class, a headless lane whose approver seat exists but can show nobody a
|
|
1524
|
+
* prompt). Its allow settles `policy_allowed`, its refusal `policy_refused`. Spelled `true` / `false`, such
|
|
1525
|
+
* an answer filed a PERSON's record, and its refusal told the model the USER did not want to proceed and to
|
|
1526
|
+
* wait for their direction — a sentence about a person, in a run where none is coming; with the word the
|
|
1527
|
+
* refusal reads {@link policyRefusalMessage} instead, and the note travels as the policy's words. A
|
|
1528
|
+
* policy's allow is the same allow a person's is — the edit, the `readRootGrant` and every lane after the
|
|
1529
|
+
* gate treat it alike — and only the record names a different party, with ONE exception that is about the
|
|
1530
|
+
* party itself: an ask carrying {@link AskRequest.requiresRealApproval} demands a person, and a policy's
|
|
1531
|
+
* allow is refused there exactly as the blanket `"allow"` seat is (`blanket_allow_refused`). OMITTING the
|
|
1532
|
+
* field still reads as `"human"`, so an unrevised host is unchanged.
|
|
1533
|
+
* `"timeout"` is legal ONLY with `allow: false` — an elapsed window is a wait nobody answered, and it cannot be
|
|
1534
|
+
* what APPROVED an action — so `{allow: true, settledBy: "timeout"}` is refused fail-closed.
|
|
1511
1535
|
* `"aborted"` is deliberately NOT accepted here — that word names the engine's OWN fail-closed ends
|
|
1512
1536
|
* (abort, throw, unavailable, out-of-contract value), each already stamped at its own arm, and a
|
|
1513
1537
|
* self-reported one would let a host relabel its refusal as an engine failure.
|
|
@@ -1542,14 +1566,15 @@ export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal)
|
|
|
1542
1566
|
* the party that ended this wait. Core does not authenticate it, compare it, or read it back to decide
|
|
1543
1567
|
* anything; it transcribes it onto the settlement observation next to `settledBy`, so an audit that can
|
|
1544
1568
|
* already say "a person ended this wait" can also say which one, without core growing an identity
|
|
1545
|
-
* surface it deliberately does not have. Read the two together: `settledBy:"
|
|
1546
|
-
*
|
|
1569
|
+
* surface it deliberately does not have. Read the two together: `settledBy:"policy"` with an `approver` names
|
|
1570
|
+
* WHICH of the deployment's answering seats decided (a closed word the host owns, never a person).
|
|
1547
1571
|
* Screened, not trusted (see {@link screenApproverAttribution}): a non-string, an over-long value or one
|
|
1548
1572
|
* carrying control characters is a loud fail-closed refusal at {@link resolveAsk}, the same posture this
|
|
1549
1573
|
* seam takes for every other out-of-contract value — and the same reason, since a defective attribution
|
|
1550
1574
|
* that were quietly dropped would leave a settlement looking unattributed rather than misreported. It is
|
|
1551
|
-
* carried on the arms
|
|
1552
|
-
*
|
|
1575
|
+
* carried on the DECIDER arms (a person's or a policy's allow or refusal) and never on an end the engine
|
|
1576
|
+
* produced (an abort, a throw, an unclonable edit): nobody approved those. A host's own elapsed window has
|
|
1577
|
+
* no attribution seat on its record (`approval_window_expired` names the host as a party, not a seat).
|
|
1553
1578
|
*
|
|
1554
1579
|
* `readRootGrant` — "yes, and add this directory": the card's {@link AskRequest.readRootCandidate} `dir`, sent back
|
|
1555
1580
|
* byte for byte (`shell.read_boundary.grant_candidate_is_the_grant`). Beside `allow: true` on an ask carrying that
|
|
@@ -1708,8 +1733,8 @@ export declare function policyRefusalMessage(toolName: string, askMessage: strin
|
|
|
1708
1733
|
export type ResolvedAsk = (Extract<PermissionResult, {
|
|
1709
1734
|
action: "allow";
|
|
1710
1735
|
}> & {
|
|
1711
|
-
/** Present exactly when a
|
|
1712
|
-
* reported one); absent on the posture allows (a blanket `"allow"
|
|
1736
|
+
/** Present exactly when a DECIDER approved (`human_allowed` / `policy_allowed`, with the channel's
|
|
1737
|
+
* attribution when it reported one); absent on the posture allows (a blanket `"allow"`) — nobody was
|
|
1713
1738
|
* asked, so nothing was settled. */
|
|
1714
1739
|
settlement?: Settlement;
|
|
1715
1740
|
/** The EXACT data snapshot the approver was shown (the `structuredClone` of the presented args). On
|
|
@@ -1766,7 +1791,8 @@ export declare function carriesBidiControls(value: unknown, limits?: {
|
|
|
1766
1791
|
* arm: the approver's own verdicts settle as a person's unless the outcome names another word on
|
|
1767
1792
|
* {@link AskOutcome.settledBy} (its own window, its own policy), and the fail-closed ends around them
|
|
1768
1793
|
* (abort, a throw, an "unavailable" report, an unclonable presentation, an out-of-contract value) settle
|
|
1769
|
-
* as the engine's own nobody-ended family
|
|
1794
|
+
* as the engine's own nobody-ended family; a policy's allow on a `requiresRealApproval` ask is refused as the
|
|
1795
|
+
* blanket seat's is ({@link unjudgedAllowRefused}). The three POSTURE arms at the top — blanket `"allow"`, its
|
|
1770
1796
|
* `requiresRealApproval` refusal, and the headless auto-deny — name no person and no window on purpose:
|
|
1771
1797
|
* nobody was asked, so there is no wait for anyone to have ended, and `decisionReason: "mode"` is already
|
|
1772
1798
|
* the honest word for what produced them.
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -3,7 +3,9 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
4
4
|
import { decisionAskClassesOf, headlessDenialMessage } from "./ask-origin.js";
|
|
5
5
|
import { ownReadRootCandidate, readRootGrantSeat } from "./read-root-grant-screen.js";
|
|
6
|
-
import { decidersNote,
|
|
6
|
+
import { decidersNote, settledByDecider } from "./gate-outcome.js";
|
|
7
|
+
import { unjudgedAllowRefused } from "./real-approval.js";
|
|
8
|
+
export { policyCannotAnswer, rowDemandsAPerson } from "./real-approval.js";
|
|
7
9
|
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
8
10
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
9
11
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
@@ -13,7 +15,7 @@ import { boundInputHashOf } from "./canonical-json.js";
|
|
|
13
15
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
|
|
14
16
|
import { describeRejectedValue } from "./tool-search-setting.js";
|
|
15
17
|
import { thrownText } from "./thrown-value.js";
|
|
16
|
-
import { nestedRecordsOf, receiveContract, receiveNestedRecords, refusedOutOfContract, screenContractValues } from "./decision-receipt.js";
|
|
18
|
+
import { isEngineRefusal, nestedRecordsOf, receiveContract, receiveNestedRecords, refusedOutOfContract, screenContractValues } from "./decision-receipt.js";
|
|
17
19
|
export { isEngineRefusal } from "./decision-receipt.js";
|
|
18
20
|
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
|
|
19
21
|
import { protocolOf } from "./protocol-table.js";
|
|
@@ -46,6 +48,7 @@ function hasEngineAttestation(o) {
|
|
|
46
48
|
}
|
|
47
49
|
export function carryEngineAttestations(from, to) {
|
|
48
50
|
transferEngineAttestations(from, to);
|
|
51
|
+
return to;
|
|
49
52
|
}
|
|
50
53
|
export function carryPrecedingSettlement(preceding, to) {
|
|
51
54
|
if (ENGINE_SETTLEMENTS.get(to) !== undefined)
|
|
@@ -93,7 +96,12 @@ export function askUnresolvableCauseOf(d, call) {
|
|
|
93
96
|
return undefined;
|
|
94
97
|
return v.cause;
|
|
95
98
|
}
|
|
96
|
-
const
|
|
99
|
+
const FOLDED_CLASSES_SEAT = new WeakMap();
|
|
100
|
+
function foldedAskClassesAttested(d, held) {
|
|
101
|
+
const signed = FOLDED_CLASSES_SEAT.get(d);
|
|
102
|
+
return signed !== undefined && Array.isArray(held) && signed.length === held.length && signed.every((c, i) => c === held[i]);
|
|
103
|
+
}
|
|
104
|
+
const ENGINE_ATTESTATION_SEATS = [ENGINE_SETTLEMENTS, CLASSIFIER_DENY_CAUSES_SEAT, ASK_UNRESOLVABLE_CAUSES_SEAT, CLASSIFIER_ROUNDS_SEAT, FOLDED_CLASSES_SEAT];
|
|
97
105
|
export function engineSettlementOf(d, call) {
|
|
98
106
|
if (typeof d !== "object" || d === null)
|
|
99
107
|
return undefined;
|
|
@@ -265,7 +273,7 @@ export function refuseOutOfContractDecision(d, opts) {
|
|
|
265
273
|
if (typeof d !== "object" || d === null) {
|
|
266
274
|
return refusedOutOfContract(`a permission decision's "action" is absent (the decision is ${d === null || d === undefined ? String(d) : `a ${typeof d}`}, not an object) — the closed set is allow/deny/ask; denied fail-closed rather than reading a word outside it as an allow`);
|
|
267
275
|
}
|
|
268
|
-
if ((hookSeam ? HOOK_RECEIPTS : ENGINE_RECEIPTS).has(d))
|
|
276
|
+
if ((hookSeam ? HOOK_RECEIPTS : ENGINE_RECEIPTS).has(d) || isEngineRefusal(d))
|
|
269
277
|
return d;
|
|
270
278
|
const decision = receiveContract(d, hookSeam ? HOOK_RESULT_MEMBERS : DECISION_MEMBERS, "");
|
|
271
279
|
if (decision.kind === "refused")
|
|
@@ -276,6 +284,11 @@ export function refuseOutOfContractDecision(d, opts) {
|
|
|
276
284
|
const refused = receiveNestedRecords(contract) ?? screenContractValues(contract, hookSeam ? { skip: HOOK_SEAM_UNREAD, optional: HOOK_SEAM_OPTIONAL } : undefined);
|
|
277
285
|
if (refused !== undefined)
|
|
278
286
|
return refused;
|
|
287
|
+
if (contract.foldedAskClasses !== undefined && !foldedAskClassesAttested(d, contract.foldedAskClasses)) {
|
|
288
|
+
return refusedOutOfContract(`a permission decision carries "foldedAskClasses" that no policy fold of the engine wrote on this object — the member is ` +
|
|
289
|
+
`engine-written (combinePolicies records the classes of the asks it collapsed); denied fail-closed rather than letting a ` +
|
|
290
|
+
`class no station judged reach the class derivation`);
|
|
291
|
+
}
|
|
279
292
|
if (hookSeam && contract.additionalContext !== undefined && typeof contract.additionalContext !== "string") {
|
|
280
293
|
return refusedOutOfContract(`a PreToolUse hook result's "additionalContext" is not a string — the member is the text appended for the model; denied fail-closed rather than rendering a value this layer cannot classify`);
|
|
281
294
|
}
|
|
@@ -298,6 +311,9 @@ export function refuseOutOfContractDecision(d, opts) {
|
|
|
298
311
|
(hookSeam ? HOOK_RECEIPTS : ENGINE_RECEIPTS).add(receipt);
|
|
299
312
|
return receipt;
|
|
300
313
|
}
|
|
314
|
+
export function receiveHookResult(result) {
|
|
315
|
+
return refuseOutOfContractDecision(result, { reasonIsNonInput: true });
|
|
316
|
+
}
|
|
301
317
|
const DEADLINE_ELAPSED = Symbol("approval.deadline_elapsed");
|
|
302
318
|
function withTimeout(p, ms, onTimeout) {
|
|
303
319
|
if (ms === undefined) {
|
|
@@ -474,7 +490,7 @@ export function createApprovalPolicy(opts) {
|
|
|
474
490
|
}
|
|
475
491
|
const okRaw = ok;
|
|
476
492
|
if (okRaw === true)
|
|
477
|
-
return attest({ action: "allow" },
|
|
493
|
+
return attest({ action: "allow" }, settledByDecider("person", "allowed", undefined));
|
|
478
494
|
if (okRaw !== false) {
|
|
479
495
|
return attest({
|
|
480
496
|
action: "deny",
|
|
@@ -482,7 +498,7 @@ export function createApprovalPolicy(opts) {
|
|
|
482
498
|
`(this policy's \`approve\` returns true, false, or the {allow, settledBy?, reason?, approver?} outcome object)`,
|
|
483
499
|
}, settledByNobody("approver_contract"));
|
|
484
500
|
}
|
|
485
|
-
return attest({ action: "deny", message: `approval denied for "${req.toolName}"` },
|
|
501
|
+
return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByDecider("person", "refused", undefined));
|
|
486
502
|
}
|
|
487
503
|
if (opts.denyByDefault && !nameListHolds(auto, req) && !namespacedCoveringHit(autoCovering, req)) {
|
|
488
504
|
return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
|
|
@@ -584,7 +600,10 @@ export function combinePolicies(...policies) {
|
|
|
584
600
|
for (const k of decisionAskClassesOf(c))
|
|
585
601
|
folded.add(k);
|
|
586
602
|
const withClasses = folded.size > 0 ? { ...withAuthor, foldedAskClasses: [...folded] } : withAuthor;
|
|
587
|
-
|
|
603
|
+
const out = merged !== undefined ? { ...withClasses, updatedInput: merged } : withClasses;
|
|
604
|
+
if (folded.size > 0)
|
|
605
|
+
FOLDED_CLASSES_SEAT.set(out, Object.freeze([...folded]));
|
|
606
|
+
return out;
|
|
588
607
|
}
|
|
589
608
|
const allowed = rewrite ?? ALLOW;
|
|
590
609
|
if (!anyRecord)
|
|
@@ -1251,11 +1270,7 @@ function readApproverOutcomeObject(ok, toolName, editLane) {
|
|
|
1251
1270
|
if (editLane === "none" && grant !== undefined) {
|
|
1252
1271
|
return refused(`the approver for "${toolName}" attached a readRootGrant on a seat that offers no directory — denied fail-closed (a grant answers an approval card's readRootCandidate)`, settledByNobody("approver_contract"));
|
|
1253
1272
|
}
|
|
1254
|
-
|
|
1255
|
-
return refused(`the approver for "${toolName}" returned an allow settled by "${settledWord}" — only a person approves, and that word ` +
|
|
1256
|
-
`names a wait that ended with nobody judging: it cannot be what approved a call; denied fail-closed ` +
|
|
1257
|
-
`(report "${settledWord}" with allow:false, or allow with settledBy "human"/omitted)`, settledByNobody("approver_contract"));
|
|
1258
|
-
}
|
|
1273
|
+
const decider = settledWord === "policy" ? "policy" : settledWord === "human" ? "person" : undefined;
|
|
1259
1274
|
if (allowed !== true) {
|
|
1260
1275
|
let reasonText;
|
|
1261
1276
|
try {
|
|
@@ -1275,15 +1290,16 @@ function readApproverOutcomeObject(ok, toolName, editLane) {
|
|
|
1275
1290
|
word: settledWord,
|
|
1276
1291
|
note,
|
|
1277
1292
|
grant,
|
|
1278
|
-
settlement:
|
|
1279
|
-
? { kind: "approval_window_expired", who: { party: "host" }, when: Date.now() }
|
|
1280
|
-
: settledWord === "policy"
|
|
1281
|
-
? settledByNobody("policy_refused")
|
|
1282
|
-
: settledByPerson("human_refused", attribution.approver, note),
|
|
1293
|
+
settlement: decider === undefined ? { kind: "approval_window_expired", who: { party: "host" }, when: Date.now() } : settledByDecider(decider, "refused", attribution.approver, note),
|
|
1283
1294
|
};
|
|
1284
1295
|
}
|
|
1296
|
+
if (decider === undefined) {
|
|
1297
|
+
return refused(`the approver for "${toolName}" returned an allow settled by "${settledWord}" — that word names a wait that ended ` +
|
|
1298
|
+
`with nobody answering: it cannot be what approved a call; denied fail-closed ` +
|
|
1299
|
+
`(report "${settledWord}" with allow:false; a person's allow is settledBy "human"/omitted, the deployment policy's is "policy")`, settledByNobody("approver_contract"));
|
|
1300
|
+
}
|
|
1285
1301
|
if (suppliedEdit === undefined)
|
|
1286
|
-
return { kind: "allow", edit: undefined, settlement:
|
|
1302
|
+
return { kind: "allow", edit: undefined, settlement: settledByDecider(decider, "allowed", attribution.approver), grant };
|
|
1287
1303
|
if (editLane === "none") {
|
|
1288
1304
|
return refused(`the approver for "${toolName}" attached an edit (updatedInput) on a seat that has no lane to adjudicate one — ` +
|
|
1289
1305
|
`this seat's answer is the deployment POLICY's verdict, and an edit arriving through it would execute without the ` +
|
|
@@ -1294,20 +1310,12 @@ function readApproverOutcomeObject(ok, toolName, editLane) {
|
|
|
1294
1310
|
if (!edit.ok) {
|
|
1295
1311
|
return refused(`the approved edit for "${toolName}" is not safely clonable (${edit.reason}) — denied fail-closed`, settledByNobody("presentation_failed"));
|
|
1296
1312
|
}
|
|
1297
|
-
return { kind: "allow", edit: { value: edit.value }, settlement:
|
|
1313
|
+
return { kind: "allow", edit: { value: edit.value }, settlement: settledByDecider(decider, "allowed", attribution.approver), grant };
|
|
1298
1314
|
}
|
|
1299
1315
|
async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
1300
1316
|
if (onAsk === "allow") {
|
|
1301
|
-
if (req.requiresRealApproval === true)
|
|
1302
|
-
return
|
|
1303
|
-
action: "deny",
|
|
1304
|
-
message: `approval required for "${req.toolName}" but the wired approver is a blanket "allow" (no ` +
|
|
1305
|
-
`judgment was applied) — this decision requires an actual auto-mode classifier verdict or a ` +
|
|
1306
|
-
`real approval callback, neither of which a blanket bypass can provide: ${req.message}`,
|
|
1307
|
-
decisionReason: "mode",
|
|
1308
|
-
settlement: settledByNobody("blanket_allow_refused"),
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1317
|
+
if (req.requiresRealApproval === true)
|
|
1318
|
+
return unjudgedAllowRefused(req, "blanket");
|
|
1311
1319
|
return { action: "allow", decisionReason: "mode" };
|
|
1312
1320
|
}
|
|
1313
1321
|
if (onAsk === undefined || onAsk === "deny") {
|
|
@@ -1396,6 +1404,9 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1396
1404
|
const reading = readApproverOutcomeObject(ok, req.toolName, "readjudicated");
|
|
1397
1405
|
if (reading.kind === "refused")
|
|
1398
1406
|
return { action: "deny", message: reading.message, decisionReason: "mode", settlement: reading.settlement };
|
|
1407
|
+
if (reading.kind === "allow" && reading.settlement.who.party === "policy" && req.requiresRealApproval === true) {
|
|
1408
|
+
return { ...unjudgedAllowRefused(req, "policy"), ...readRootGrantSeat(reading.grant, offered, false) };
|
|
1409
|
+
}
|
|
1399
1410
|
const grantSeat = readRootGrantSeat(reading.grant, offered, reading.kind === "allow");
|
|
1400
1411
|
if (reading.kind === "deny") {
|
|
1401
1412
|
return {
|
|
@@ -1416,7 +1427,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1416
1427
|
}
|
|
1417
1428
|
const okRaw = ok;
|
|
1418
1429
|
if (okRaw === true)
|
|
1419
|
-
return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settlement:
|
|
1430
|
+
return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settlement: settledByDecider("person", "allowed", undefined) };
|
|
1420
1431
|
if (okRaw !== false) {
|
|
1421
1432
|
return {
|
|
1422
1433
|
action: "deny",
|
|
@@ -1425,5 +1436,5 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1425
1436
|
settlement: settledByNobody("approver_contract"),
|
|
1426
1437
|
};
|
|
1427
1438
|
}
|
|
1428
|
-
return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", settlement:
|
|
1439
|
+
return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", settlement: settledByDecider("person", "refused", undefined) };
|
|
1429
1440
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -30,5 +30,5 @@ export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegatio
|
|
|
30
30
|
export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
|
|
31
31
|
export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
|
|
32
32
|
export type { EngineNotice, OwedUserInputAccount, UserSteerUndrainedCause } from "./engine-notice.js";
|
|
33
|
-
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
|
|
33
|
+
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
|
|
34
34
|
export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
|
package/dist/core/types.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { MODEL_ROLES } from "./model-seat.js";
|
|
2
2
|
export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
3
3
|
export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
|
|
4
|
-
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
|
|
4
|
+
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* copies of the derivation would drift, so there is exactly one. A startup snapshot and a leg's
|
|
21
21
|
* effective snapshot are two DIFFERENT facts; neither may impersonate the other.
|
|
22
22
|
*/
|
|
23
|
+
import { type PermissionMode } from "./ask-origin.js";
|
|
23
24
|
import type { RunnerDeps, TaskSpec } from "./types.js";
|
|
24
25
|
import { type StoreDurability } from "./checkpoint-store.js";
|
|
25
26
|
import type { WiringManifestHookEntry } from "./hook-seats.js";
|
|
@@ -600,8 +601,13 @@ export declare function resolveSubagentTranscriptTier(agentStoreWired: boolean,
|
|
|
600
601
|
} | undefined): SubagentTranscriptTier;
|
|
601
602
|
/**
|
|
602
603
|
* The ONE `ask.effective` derivation (design/173) — shared by the effective manifest AND
|
|
603
|
-
* the posture door (`config.interaction_posture`)
|
|
604
|
-
* the manifest
|
|
604
|
+
* the posture door (`config.interaction_posture`): the door passes no `mode` and judges the SEAT alone, the
|
|
605
|
+
* manifest passes its leg's effective mode AT THE LEG'S START (the manifest is minted once; a runtime reader pairs
|
|
606
|
+
* {@link askSeatEffective} with the live mode instead), so the two read one table and differ only by the one fact the
|
|
607
|
+
* door does not have:
|
|
608
|
+
* - MODE BEFORE SEAT: under `dontAsk` the mode refuses every ask a policy raises (`arbitrateByMode`; only a
|
|
609
|
+
* hook's own ask beside no rule or safety ask is handed on, and that is the hook lane's decision, not a policy
|
|
610
|
+
* `ask`), so no seat answers one — whatever the seat and the park lane say, the word is `auto_deny`;
|
|
605
611
|
* - a live approver callback ⇒ `human_reachable` (regardless of the park lane);
|
|
606
612
|
* - blanket `"allow"`/`"deny"` ⇒ the park lane decides first: a string seat is NOT a live approver,
|
|
607
613
|
* so on an armed park lane the ask parks and the honest report is `park_only` — reporting the
|
|
@@ -610,7 +616,15 @@ export declare function resolveSubagentTranscriptTier(agentStoreWired: boolean,
|
|
|
610
616
|
* - no seat at all ⇒ the park lane decides: armed ⇒ `park_only`, unresolved ⇒ `unresolved`,
|
|
611
617
|
* else the headless degrade `auto_deny`.
|
|
612
618
|
*/
|
|
613
|
-
export declare function deriveAskEffective(form: AskSeamForm, parkEffective: boolean | "unresolved"): AskEffective;
|
|
619
|
+
export declare function deriveAskEffective(form: AskSeamForm, parkEffective: boolean | "unresolved", mode?: PermissionMode): AskEffective;
|
|
620
|
+
/**
|
|
621
|
+
* The SEAT's own `ask.effective` of an effective manifest — {@link deriveAskEffective} with no mode, from the manifest's own
|
|
622
|
+
* form and park facts. The manifest's `ask.effective` is the word AS OF THE LEG'S START under its start mode, and a steer moves
|
|
623
|
+
* the mode afterwards; a runtime consumer that reads the word per call (the classifier's approval seat, the posture row, the peer
|
|
624
|
+
* lane's mode fold) therefore reads THIS word and the leg's LIVE mode beside it — never the start-of-leg fold, which would carry a
|
|
625
|
+
* `dontAsk` start's refusal into the leg's later modes.
|
|
626
|
+
*/
|
|
627
|
+
export declare function askSeatEffective(manifest: Pick<WiringManifest, "ask" | "parkLane">): AskEffective;
|
|
614
628
|
/**
|
|
615
629
|
* Does an ask this leg hands on carrying `requiresRealApproval` reach a PERSON — never an automatic yes? The leg's
|
|
616
630
|
* APPROVAL SEAT, read off the ONE ask derivation above, never re-derived: `human_reachable` (a live approver) and
|