@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
|
@@ -1,35 +1,130 @@
|
|
|
1
1
|
import type { AskEffective } from "./wiring-manifest.js";
|
|
2
|
-
/** The permission-mode words the composition reads (the engine's closed set, as the wire spells it). */
|
|
3
2
|
import type { PermissionMode } from "./ask-origin.js";
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { type EngineRegionMessage } from "./reminder-mint.js";
|
|
4
|
+
/** The auto-mode frame (title + the two paragraphs). */
|
|
6
5
|
export declare const AUTO_MODE_DIRECTIVE: string;
|
|
7
6
|
/** The shell-first paragraph. The reference implementation serves it under three different headings;
|
|
8
7
|
* the one heading this engine has a fact for is the bypass posture below. */
|
|
9
8
|
export declare const BASH_FIRST_DIRECTIVE: string;
|
|
10
|
-
/** The bypass-posture
|
|
11
|
-
*
|
|
9
|
+
/** The bypass-posture frame: the reference implementation's heading over the shell-first paragraph, the
|
|
10
|
+
* exact pair its bypass arm renders. */
|
|
12
11
|
export declare const BYPASS_POSTURE_DIRECTIVE: string;
|
|
12
|
+
/** The auto-mode frame's exit: the reference exit renderer's long arm (the retired frame was not steer-only)
|
|
13
|
+
* with no shell-first sentence (the retired frame carried none). */
|
|
14
|
+
export declare const AUTO_MODE_EXIT_DIRECTIVE: string;
|
|
15
|
+
/** The bypass frame's exit: the same renderer's short arm (the retired frame was steer-only) with its
|
|
16
|
+
* shell-first sentence (the retired frame carried the shell-first paragraph). */
|
|
17
|
+
export declare const BYPASS_POSTURE_EXIT_DIRECTIVE: string;
|
|
18
|
+
/** The planning frame — the reference full reminder's opening sentence verbatim (its own "supercedes"
|
|
19
|
+
* spelling included); the tail is adapted to the engine's exit (the plan-exit tool, a human approves). */
|
|
20
|
+
export declare const PLAN_MODE_DIRECTIVE: string;
|
|
21
|
+
/** The planning frame's sparse restatement — the reference sparse shape minus the plan-file clause. */
|
|
22
|
+
export declare const PLAN_MODE_SPARSE_DIRECTIVE: string;
|
|
23
|
+
/** The planning frame for a leg with NO plan-exit tool on its roster: the same opening, and the plan is the leg's
|
|
24
|
+
* final answer (no tool is named, no approval request is promised). */
|
|
25
|
+
export declare const PLAN_MODE_NO_EXIT_DIRECTIVE: string;
|
|
26
|
+
/** The no-tool planning frame's sparse restatement. */
|
|
27
|
+
export declare const PLAN_MODE_NO_EXIT_SPARSE_DIRECTIVE: string;
|
|
28
|
+
/** The planning frame of a DELEGATED child — the reference's sub-agent variant: the same opening, no exit tool named,
|
|
29
|
+
* the child answers the request it was given (its final answer goes back to the agent that delegated to it). The
|
|
30
|
+
* reference has no sparse arm for a sub-agent, so a restatement of this row is this body. */
|
|
31
|
+
export declare const PLAN_MODE_CHILD_DIRECTIVE: string;
|
|
32
|
+
/** The planning frame's exit — the reference exit renderer's text without its plan-file clause. Names no tool, so
|
|
33
|
+
* one exit retires every planning wording. */
|
|
34
|
+
export declare const PLAN_MODE_EXIT_DIRECTIVE: string;
|
|
13
35
|
/** The mode facts of one leg, both the ENGINE's own resolution — never a requested value, never spec shape. */
|
|
14
36
|
export interface ApprovalPostureFacts {
|
|
15
37
|
/** The leg's EFFECTIVE permission mode (a requested mode the deployment refused has already folded back). */
|
|
16
38
|
mode: PermissionMode;
|
|
17
|
-
/** The
|
|
39
|
+
/** The seat's own ask derivation (`askSeatEffective`, no mode) — `auto_allow` means no person answers an ask on this leg.
|
|
40
|
+
* The LIVE `mode` is read before it: the manifest's reported word is the leg's start under its start mode, and a steer moves
|
|
41
|
+
* the mode after it was minted. */
|
|
18
42
|
askEffective: AskEffective;
|
|
19
43
|
}
|
|
20
44
|
/**
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
23
|
-
* — a caller's own bytes are never in one, so a spliced copy cannot suppress the engine's); a segment
|
|
24
|
-
* already standing there is not restated, and a segment that has left the context — compacted away, or
|
|
25
|
-
* never delivered — is. Returns `undefined` when there is nothing left to say, which is both "no segment
|
|
26
|
-
* for this mode" (zero bytes) and "already said".
|
|
27
|
-
*
|
|
28
|
-
* Segment-wise rather than body-wise on purpose: a leg whose mode CHANGES — it now runs under bypass, so the
|
|
29
|
-
* segment it owes changes — must state the new segment without restating one already standing, and must be
|
|
30
|
-
* able to read a body an earlier leg minted under a composition that joined two. The caller wraps the result
|
|
31
|
-
* in the engine's marked reminder shell; this module never wraps, so the mark stays owned by its own mint home.
|
|
45
|
+
* The shape facts of one leg the planning rows word themselves by — read off what the leg IS (its minted tool roster, its
|
|
46
|
+
* minted delegation identity), never off the requested spec.
|
|
32
47
|
*/
|
|
33
|
-
export
|
|
34
|
-
/**
|
|
48
|
+
export interface PostureLegFacts {
|
|
49
|
+
/** The plan-exit tool ({@link PRESENT_PLAN_TOOL_NAME}) is on this leg's roster — its wire name or an alias a call resolves. */
|
|
50
|
+
exitToolMounted: boolean;
|
|
51
|
+
/** This leg runs as a delegated child (every core spawn lane, a fork included). */
|
|
52
|
+
isChildLeg: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Every fact a row's `owedBy` reads: the mode facts and the leg's shape. */
|
|
55
|
+
export type PostureFacts = ApprovalPostureFacts & PostureLegFacts;
|
|
56
|
+
/** A posture family: one family stands in a context at a time per family, and its exit retires whichever of its rows stands. */
|
|
57
|
+
export type PostureFamily = "plan" | "auto";
|
|
58
|
+
/** One row of {@link POSTURE_TABLE}. */
|
|
59
|
+
export interface PostureRow {
|
|
60
|
+
readonly family: PostureFamily;
|
|
61
|
+
/** Whether the leg's mode facts call for this row. */
|
|
62
|
+
readonly owedBy: (facts: PostureFacts) => boolean;
|
|
63
|
+
/** The frame that states the posture. */
|
|
64
|
+
readonly frame: string;
|
|
65
|
+
/** The frame that retires it. */
|
|
66
|
+
readonly exit: string;
|
|
67
|
+
/**
|
|
68
|
+
* A restatement while the row stays owed and standing: due once `everyTurns` assistant turns have passed since
|
|
69
|
+
* the family's last frame; the restatement is the full frame when it is the family's `1 + k·fullEvery`-th frame
|
|
70
|
+
* since its last exit, else `sparse`. Absent ⇒ stated once and never restated while it stands.
|
|
71
|
+
*/
|
|
72
|
+
readonly cadence?: {
|
|
73
|
+
readonly everyTurns: number;
|
|
74
|
+
readonly fullEvery: number;
|
|
75
|
+
readonly sparse: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Every posture the engine states, in the order the owed row is chosen. `plan` first: it is the mode the leg
|
|
80
|
+
* was put in, and the bypass frame's shell-first instruction would contradict it. Within it the leg's shape picks the
|
|
81
|
+
* wording — a delegated child (the reference's sub-agent variant), else a leg with the plan-exit tool on its roster
|
|
82
|
+
* (the planning body that names it), else the body that names no tool. Then the reference
|
|
83
|
+
* implementation's own arm order for its one posture attachment: bypass (effective `bypassPermissions`, or — outside
|
|
84
|
+
* `dontAsk`, whose mode refuses every ask before any seat could answer — no person answers an ask), then auto (the
|
|
85
|
+
* classifier is armed — a requested auto mode the engine folded back is not stated, because the run is not operating
|
|
86
|
+
* under it).
|
|
87
|
+
*/
|
|
88
|
+
export declare const POSTURE_TABLE: readonly PostureRow[];
|
|
89
|
+
/** The row the leg's facts call for, or undefined (no posture is owed). */
|
|
90
|
+
export declare function owedPostureRow(facts: PostureFacts): PostureRow | undefined;
|
|
91
|
+
/** What one family's standing frame is, as the fold over the active context left it. */
|
|
92
|
+
export interface StandingPosture {
|
|
93
|
+
readonly row: PostureRow;
|
|
94
|
+
/** The family's frames since its last exit (the cadence's full/sparse count). */
|
|
95
|
+
readonly statements: number;
|
|
96
|
+
/** Assistant turns since the family's last frame (the cadence's due clock). */
|
|
97
|
+
readonly turnsSince: number;
|
|
98
|
+
}
|
|
99
|
+
/** The standing families of a context, keyed by family, in the order they first stood. */
|
|
100
|
+
export type PostureStanding = ReadonlyMap<PostureFamily, StandingPosture>;
|
|
101
|
+
/**
|
|
102
|
+
* The ordered fold over the ACTIVE context's messages (the compaction boundary applied — never the raw ancestry,
|
|
103
|
+
* which still carries what a compaction replaced): the engine's marked reminder bodies, read off ENGINE REGIONS
|
|
104
|
+
* only (metadata, never text shape, so a caller's spliced copy — marked or not — cannot answer), per minted block;
|
|
105
|
+
* an assistant message advances every standing family's turn clock.
|
|
106
|
+
*/
|
|
107
|
+
export declare function postureStandingOf(messages: readonly EngineRegionMessage[], reminderMark: string): PostureStanding;
|
|
108
|
+
/**
|
|
109
|
+
* The ONE composition: the frames that make the active context state exactly the posture the leg's facts call for
|
|
110
|
+
* — the exit of every standing family the owed row does not belong to, then the owed row's frame (or its cadence's
|
|
111
|
+
* restatement) when it is owed. Empty when the context already says it.
|
|
112
|
+
*/
|
|
113
|
+
export declare function composePostureFrames(facts: PostureFacts, standing: PostureStanding): string[];
|
|
114
|
+
/**
|
|
115
|
+
* The run-local half of the posture: the newest posture carrier's text (the context lane's protection slot —
|
|
116
|
+
* a clipped request keeps or reinserts the message carrying it; the carrier of an exit takes the slot, so a
|
|
117
|
+
* retired frame is no longer protected); the owed row the last composition answered for (a boundary whose facts
|
|
118
|
+
* still call for that row, with no cadence to keep, asks nothing of the context — absent means the last
|
|
119
|
+
* composition read nothing or its carrier was not written, so the next boundary asks again); the frames a boundary
|
|
120
|
+
* could not write (stated on each request's view, never persisted, until a boundary writes them); and where a
|
|
121
|
+
* write failure is disclosed.
|
|
122
|
+
*/
|
|
123
|
+
export interface PostureFrameRef {
|
|
124
|
+
protectedText?: string;
|
|
125
|
+
composedFor?: PostureRow | null;
|
|
126
|
+
unwritten?: string;
|
|
127
|
+
report?: (err: Error) => void;
|
|
128
|
+
}
|
|
129
|
+
/** SHA-256 of a frame body — the pins' lock port, so the lock is computed the one way. */
|
|
35
130
|
export declare function directiveDigest(body: string): string;
|
|
@@ -1,34 +1,82 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { PRESENT_PLAN_TOOL_NAME } from "./present-plan-tool.js";
|
|
3
|
+
import { engineRegionTextsOf, markedReminderBodiesOf } from "./reminder-mint.js";
|
|
2
4
|
export const AUTO_MODE_DIRECTIVE = "## Auto Mode Active\n\nBias toward working without stopping for clarifying questions — when you'd normally pause to check, make the reasonable call and keep going; they'll redirect you if needed. If the user, a skill, or the shape of the task suggests they want you to ask (with AskUserQuestion or otherwise), do so. And even absent that signal, it's still fine to stop when you're genuinely blocked — unclear direction, missing input, a decision only they can make.\n\nBefore any command that could discard uncommitted work — `git checkout`/`restore`/`reset`/`clean`, `rm -rf` in the repo, restoring from a snapshot — run `git status` first and stash (with `-u` for untracked) or commit anything that's there. When staging or committing, review what's included (`git status` after a broad `git add`), and if you see anything suspicious that might reveal secrets — even if the filename looks innocuous — double-check the file's contents before pushing.";
|
|
3
5
|
export const BASH_FIRST_DIRECTIVE = "Do your work through the Bash tool wherever it can accomplish the job: read files with cat, head, or sed -n, search with grep and find, and make file changes with sed, heredocs, or short scripts, rather than using the dedicated Read, Edit, or Write tools. Fall back to a dedicated tool only when Bash genuinely cannot do the job.";
|
|
4
6
|
export const BYPASS_POSTURE_DIRECTIVE = `While bypass permissions mode is active:\n\n${BASH_FIRST_DIRECTIVE}`;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export const AUTO_MODE_EXIT_DIRECTIVE = "## Exited Auto Mode\n\nYou have exited auto mode. The user may now want to interact more directly. You should ask clarifying questions when the approach is ambiguous rather than making assumptions.";
|
|
8
|
+
export const BYPASS_POSTURE_EXIT_DIRECTIVE = "## Exited Auto Mode\n\nYou have exited auto mode. Resume using the dedicated tools for file reads, searches, and edits.";
|
|
9
|
+
export const PLAN_MODE_DIRECTIVE = `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and when you have formed a concrete plan, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. That tool inherently requests plan approval — never ask for approval via plain text.`;
|
|
10
|
+
export const PLAN_MODE_SPARSE_DIRECTIVE = `Plan mode still active (see full instructions earlier in conversation). Read-only: no edits, commits, or config changes. When your plan is ready, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. Never ask about plan approval via plain text.`;
|
|
11
|
+
export const PLAN_MODE_NO_EXIT_DIRECTIVE = "Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and when you have formed a concrete plan, give it as your final answer and stop.";
|
|
12
|
+
export const PLAN_MODE_NO_EXIT_SPARSE_DIRECTIVE = "Plan mode still active (see full instructions earlier in conversation). Read-only: no edits, commits, or config changes. When your plan is ready, give it as your final answer and stop.";
|
|
13
|
+
export const PLAN_MODE_CHILD_DIRECTIVE = "Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and answer the request comprehensively — your final answer, carrying the plan, goes back to the agent that delegated to you.";
|
|
14
|
+
export const PLAN_MODE_EXIT_DIRECTIVE = "## Exited Plan Mode\n\nYou have exited plan mode. You can now make edits, run tools, and take actions.";
|
|
15
|
+
const PLAN_CADENCE = { everyTurns: 5, fullEvery: 5, sparse: PLAN_MODE_SPARSE_DIRECTIVE };
|
|
16
|
+
export const POSTURE_TABLE = Object.freeze([
|
|
17
|
+
{ family: "plan", owedBy: (f) => f.mode === "plan" && f.isChildLeg, frame: PLAN_MODE_CHILD_DIRECTIVE, exit: PLAN_MODE_EXIT_DIRECTIVE, cadence: { ...PLAN_CADENCE, sparse: PLAN_MODE_CHILD_DIRECTIVE } },
|
|
18
|
+
{ family: "plan", owedBy: (f) => f.mode === "plan" && f.exitToolMounted, frame: PLAN_MODE_DIRECTIVE, exit: PLAN_MODE_EXIT_DIRECTIVE, cadence: PLAN_CADENCE },
|
|
19
|
+
{ family: "plan", owedBy: (f) => f.mode === "plan", frame: PLAN_MODE_NO_EXIT_DIRECTIVE, exit: PLAN_MODE_EXIT_DIRECTIVE, cadence: { ...PLAN_CADENCE, sparse: PLAN_MODE_NO_EXIT_SPARSE_DIRECTIVE } },
|
|
20
|
+
{ family: "auto", owedBy: (f) => f.mode === "bypassPermissions" || (f.mode !== "dontAsk" && f.askEffective === "auto_allow"), frame: BYPASS_POSTURE_DIRECTIVE, exit: BYPASS_POSTURE_EXIT_DIRECTIVE },
|
|
21
|
+
{ family: "auto", owedBy: (f) => f.mode === "auto", frame: AUTO_MODE_DIRECTIVE, exit: AUTO_MODE_EXIT_DIRECTIVE },
|
|
22
|
+
]);
|
|
23
|
+
export function owedPostureRow(facts) {
|
|
24
|
+
return POSTURE_TABLE.find((row) => row.owedBy(facts));
|
|
9
25
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return undefined;
|
|
17
|
-
return owed;
|
|
18
|
-
}
|
|
19
|
-
const POSTURE_SEGMENTS = [AUTO_MODE_DIRECTIVE, BYPASS_POSTURE_DIRECTIVE];
|
|
20
|
-
function postureSegmentsOf(body) {
|
|
26
|
+
const POSTURE_VOCABULARY = POSTURE_TABLE.flatMap((row) => [
|
|
27
|
+
[row.frame, { row, exit: false }],
|
|
28
|
+
[row.exit, { row, exit: true }],
|
|
29
|
+
...(row.cadence !== undefined ? [[row.cadence.sparse, { row, exit: false }]] : []),
|
|
30
|
+
]);
|
|
31
|
+
function postureItemsOf(body) {
|
|
21
32
|
const out = [];
|
|
22
33
|
let rest = body;
|
|
23
34
|
while (rest.length > 0) {
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
35
|
+
const hit = POSTURE_VOCABULARY.find(([text]) => rest === text || rest.startsWith(`${text}\n\n`));
|
|
36
|
+
if (hit === undefined)
|
|
26
37
|
return undefined;
|
|
27
|
-
out.push(
|
|
28
|
-
rest = rest ===
|
|
38
|
+
out.push(hit[1]);
|
|
39
|
+
rest = rest === hit[0] ? "" : rest.slice(hit[0].length + 2);
|
|
29
40
|
}
|
|
30
41
|
return out.length > 0 ? out : undefined;
|
|
31
42
|
}
|
|
43
|
+
export function postureStandingOf(messages, reminderMark) {
|
|
44
|
+
const standing = new Map();
|
|
45
|
+
for (const m of messages) {
|
|
46
|
+
if (m.role === "assistant") {
|
|
47
|
+
for (const [family, s] of standing)
|
|
48
|
+
standing.set(family, { ...s, turnsSince: s.turnsSince + 1 });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const region of engineRegionTextsOf([m])) {
|
|
52
|
+
for (const body of markedReminderBodiesOf(region, reminderMark)) {
|
|
53
|
+
for (const item of postureItemsOf(body) ?? []) {
|
|
54
|
+
if (item.exit)
|
|
55
|
+
standing.delete(item.row.family);
|
|
56
|
+
else
|
|
57
|
+
standing.set(item.row.family, { row: item.row, statements: (standing.get(item.row.family)?.statements ?? 0) + 1, turnsSince: 0 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return standing;
|
|
63
|
+
}
|
|
64
|
+
export function composePostureFrames(facts, standing) {
|
|
65
|
+
const owed = owedPostureRow(facts);
|
|
66
|
+
const frames = [];
|
|
67
|
+
for (const [family, s] of standing)
|
|
68
|
+
if (family !== owed?.family)
|
|
69
|
+
frames.push(s.row.exit);
|
|
70
|
+
if (owed === undefined)
|
|
71
|
+
return frames;
|
|
72
|
+
const s = standing.get(owed.family);
|
|
73
|
+
if (s === undefined || s.row !== owed)
|
|
74
|
+
frames.push(owed.frame);
|
|
75
|
+
else if (owed.cadence !== undefined && s.turnsSince >= owed.cadence.everyTurns) {
|
|
76
|
+
frames.push((s.statements + 1) % owed.cadence.fullEvery === 1 ? owed.frame : owed.cadence.sparse);
|
|
77
|
+
}
|
|
78
|
+
return frames;
|
|
79
|
+
}
|
|
32
80
|
export function directiveDigest(body) {
|
|
33
81
|
return createHash("sha256").update(body, "utf8").digest("hex");
|
|
34
82
|
}
|
|
@@ -613,15 +613,16 @@ export type ResumeOutcome = {
|
|
|
613
613
|
* The host's DECISION FACTS — who ended this wait: a person (`"person"`, the decide an operator made
|
|
614
614
|
* through the deployment's approval channel, with the attribution that channel reports), the
|
|
615
615
|
* host's own SLA sweep at the row's deadline (`"sla_timeout"` — FACET A of the deadline contract; only
|
|
616
|
-
* a sweep may say it), or the deployment's OWN POLICY (`"policy"` — a machine
|
|
617
|
-
* approval surface
|
|
618
|
-
* synchronous leg's `AskOutcome.settledBy: "policy"`, so ONE
|
|
619
|
-
*
|
|
616
|
+
* a sweep may say it), or the deployment's OWN POLICY (`"policy"` — a machine decided, either way: an
|
|
617
|
+
* unattended seat answering by class, no interactive approval surface for this row, a standing posture;
|
|
618
|
+
* the durable spelling of the synchronous leg's `AskOutcome.settledBy: "policy"`, so ONE decision reads
|
|
619
|
+
* the same on both faces). A person and a policy may each `allow` or `deny`; `"sla_timeout"` names a wait
|
|
620
|
+
* that ended with nobody answering and cannot `allow`.
|
|
620
621
|
* REQUIRED: a decide that names nobody is not a record of anything that happened, and core does not guess.
|
|
621
622
|
*
|
|
622
623
|
* The host supplies FACTS, never the settlement WORD: core mints the record
|
|
623
|
-
* ({@link import("./gate-outcome.js").GateOutcome} — `human_allowed` / `human_refused` / `
|
|
624
|
-
* / `policy_refused`, joined with the row's persisted `origin`) at the resume entry, writes it onto the resolved row
|
|
624
|
+
* ({@link import("./gate-outcome.js").GateOutcome} — `human_allowed` / `human_refused` / `policy_allowed`
|
|
625
|
+
* / `policy_refused` / `park_sla_expired`, joined with the row's persisted `origin`) at the resume entry, writes it onto the resolved row
|
|
625
626
|
* (`resolvedOutcome.gateOutcome`) and projects it onto the resolved call's `tool_end` frame. A caller
|
|
626
627
|
* that supplies `gateOutcome` itself is refused pre-CAS.
|
|
627
628
|
*
|
|
@@ -1134,8 +1135,8 @@ export interface CheckpointState {
|
|
|
1134
1135
|
* law) and re-judges the deny bits and the classifier face for its own leg: a `bypassPermissions` row does not
|
|
1135
1136
|
* revive the mode where the redeeming deployment denies it, and an `auto` row arms only where a face is
|
|
1136
1137
|
* wired. Absent on a task that declared nothing and inherited nothing (`default`). Rows written before this
|
|
1137
|
-
* member carried `autoModeRequested` / `approverPosture` / a doctrine `shellGate`;
|
|
1138
|
-
* them
|
|
1138
|
+
* member carried `autoModeRequested` / `approverPosture` / a doctrine `shellGate`; no engine since 7.39.0 reads
|
|
1139
|
+
* them — such a row is refused before the claim ({@link refuseRetiredModeSeed}). */
|
|
1139
1140
|
permissionMode?: PermissionMode;
|
|
1140
1141
|
/** Beside a recorded `plan`: the declared word the leg had when a steer or the model's `EnterPlanMode` moved it into
|
|
1141
1142
|
* plan — the mode an approved or edited `plan_review` resumes under ({@link import("./mode-policy.js").modeRequestAfterResume}). */
|
|
@@ -2029,8 +2030,9 @@ export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
|
|
|
2029
2030
|
* LEDGER carries an event kind this binary does not know, so the replay is refused rather than served
|
|
2030
2031
|
* incomplete — the one arm that is not row-scoped: NO row in that data directory is readable here, so
|
|
2031
2032
|
* the host rolls the binary forward instead of routing one token elsewhere), `"seat_retired"` (the row
|
|
2032
|
-
*
|
|
2033
|
-
*
|
|
2033
|
+
* carries a seat this engine no longer reads — terminal for the row's bytes: the retired pre-queue single
|
|
2034
|
+
* steer seat, which no worker version reads and the host decides itself, or a retired mode-seed member of
|
|
2035
|
+
* `inheritedGate`, which an engine at 7.38.x or earlier still reads once).
|
|
2034
2036
|
* · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`, `"real_approval_forged"`,
|
|
2035
2037
|
* `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
2036
2038
|
* current bytes — no worker version redeems a damaged row).
|
|
@@ -2040,6 +2042,17 @@ export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "g
|
|
|
2040
2042
|
* legal-and-absent at every version, so there is no version to judge a fabrication against — and a
|
|
2041
2043
|
* fabricated "open" still needs the LIVE resolution to say open too. */
|
|
2042
2044
|
| "write_face_damaged" | "constraint_chain_missing";
|
|
2045
|
+
/**
|
|
2046
|
+
* The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the four
|
|
2047
|
+
* words prepare's mode, output-style and recorded-mode doors throw, held unchanged so one refusal reads the same on a fresh
|
|
2048
|
+
* run (`TaskResult.errorCode`) and on a resume (this member). `config.spec_retired_key`: a retired spec key is present;
|
|
2049
|
+
* `config.output_style_invalid`: the output-style seat is malformed; `config.permission_mode_invalid`: a mode word is outside
|
|
2050
|
+
* the closed set (the config's `permissionMode`, or a mode the row recorded); `config.shell_gate_value_retired`: the shell
|
|
2051
|
+
* floor is a retired doctrine word.
|
|
2052
|
+
*/
|
|
2053
|
+
export declare const CHECKPOINT_CONFIG_REFUSAL_CODES: readonly ["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"];
|
|
2054
|
+
export type CheckpointConfigRefusalCode = (typeof CHECKPOINT_CONFIG_REFUSAL_CODES)[number];
|
|
2055
|
+
export declare const isCheckpointConfigRefusalCode: (word: unknown) => word is CheckpointConfigRefusalCode;
|
|
2043
2056
|
/** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
|
|
2044
2057
|
export declare class CheckpointError extends Error {
|
|
2045
2058
|
readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
|
|
@@ -2061,6 +2074,14 @@ export declare class CheckpointError extends Error {
|
|
|
2061
2074
|
* Rejected pre-CAS so the checkpoint stays `pending` and a capable worker can still resume it
|
|
2062
2075
|
* (design/49 §2/§3, code-ready council round-2). */
|
|
2063
2076
|
| "checkpoint.unsupported_version"
|
|
2077
|
+
/** The resume's re-supplied config, or a mode the checkpoint row recorded, is one prepare would refuse
|
|
2078
|
+
* (a retired spec key, a malformed output style, a mode word outside the closed set, a retired shell-floor
|
|
2079
|
+
* word). Refused PRE-CAS, so the checkpoint stays `pending` and byte-identical. `message` is prepare's own
|
|
2080
|
+
* sentence; `detail.code` is prepare's own code ({@link CheckpointConfigRefusalCode}) and `detail.field`
|
|
2081
|
+
* says which input carries it: `"taskConfig"` (correct the config and resume the SAME token) or
|
|
2082
|
+
* `"inheritedGate"` (the recorded row — no config reaches it; expire the row). A fresh run of the same
|
|
2083
|
+
* bad config reports the same `detail.code` as its `TaskResult.errorCode`. */
|
|
2084
|
+
| "checkpoint.config_refused"
|
|
2064
2085
|
/** `resume`/`resumeStream` was handed `internals.requestedCwd` for a checkpoint that
|
|
2065
2086
|
* carries a remote `workspaceHandle`. The restored workspace's mountPath is authoritative for the
|
|
2066
2087
|
* task root, so the cwd would be ignored at best — and verifying it would touch the env BEFORE the
|
|
@@ -2201,14 +2222,19 @@ export declare class CheckpointError extends Error {
|
|
|
2201
2222
|
* action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
2202
2223
|
* decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
|
|
2203
2224
|
* `"hostDecision"` (the decision facts are missing, name a decider outside `person`/`sla_timeout`/`policy`,
|
|
2204
|
-
* pair
|
|
2225
|
+
* pair the sweep's `sla_timeout` with an allow, or try to supply the minted word), or — design/252 G-7 — `"approver"` (the attribution is not a
|
|
2205
2226
|
* well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
|
|
2206
|
-
* it).
|
|
2227
|
+
* it). On a `checkpoint.config_refused` `field` names which INPUT carries the refusal — `"taskConfig"`
|
|
2228
|
+
* (the re-supplied config: correct it and resume the same token) or `"inheritedGate"` (the mode the row
|
|
2229
|
+
* recorded: no config reaches it; expire the row). The throw
|
|
2207
2230
|
* sites always knew which arm fired; this carries that fact instead of dropping it (ruled
|
|
2208
2231
|
* 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
|
|
2209
2232
|
* discriminant. */
|
|
2210
2233
|
readonly detail?: {
|
|
2211
|
-
field?: "boundCallId" | "boundInputHash" | "answer" | "hostDecision" | "approver";
|
|
2234
|
+
field?: "boundCallId" | "boundInputHash" | "answer" | "hostDecision" | "approver" | "taskConfig" | "inheritedGate";
|
|
2235
|
+
/** On `checkpoint.config_refused` — prepare's own code for the refusal ({@link CheckpointConfigRefusalCode}), the
|
|
2236
|
+
* word a fresh run of the same bad config reports as its `errorCode`. Absent on every other code. */
|
|
2237
|
+
code?: CheckpointConfigRefusalCode;
|
|
2212
2238
|
/** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
|
|
2213
2239
|
* (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
|
|
2214
2240
|
* retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
|
|
@@ -2240,6 +2266,14 @@ export declare class CheckpointError extends Error {
|
|
|
2240
2266
|
* Rejected pre-CAS so the checkpoint stays `pending` and a capable worker can still resume it
|
|
2241
2267
|
* (design/49 §2/§3, code-ready council round-2). */
|
|
2242
2268
|
| "checkpoint.unsupported_version"
|
|
2269
|
+
/** The resume's re-supplied config, or a mode the checkpoint row recorded, is one prepare would refuse
|
|
2270
|
+
* (a retired spec key, a malformed output style, a mode word outside the closed set, a retired shell-floor
|
|
2271
|
+
* word). Refused PRE-CAS, so the checkpoint stays `pending` and byte-identical. `message` is prepare's own
|
|
2272
|
+
* sentence; `detail.code` is prepare's own code ({@link CheckpointConfigRefusalCode}) and `detail.field`
|
|
2273
|
+
* says which input carries it: `"taskConfig"` (correct the config and resume the SAME token) or
|
|
2274
|
+
* `"inheritedGate"` (the recorded row — no config reaches it; expire the row). A fresh run of the same
|
|
2275
|
+
* bad config reports the same `detail.code` as its `TaskResult.errorCode`. */
|
|
2276
|
+
| "checkpoint.config_refused"
|
|
2243
2277
|
/** `resume`/`resumeStream` was handed `internals.requestedCwd` for a checkpoint that
|
|
2244
2278
|
* carries a remote `workspaceHandle`. The restored workspace's mountPath is authoritative for the
|
|
2245
2279
|
* task root, so the cwd would be ignored at best — and verifying it would touch the env BEFORE the
|
|
@@ -2380,14 +2414,19 @@ export declare class CheckpointError extends Error {
|
|
|
2380
2414
|
* action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
2381
2415
|
* decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
|
|
2382
2416
|
* `"hostDecision"` (the decision facts are missing, name a decider outside `person`/`sla_timeout`/`policy`,
|
|
2383
|
-
* pair
|
|
2417
|
+
* pair the sweep's `sla_timeout` with an allow, or try to supply the minted word), or — design/252 G-7 — `"approver"` (the attribution is not a
|
|
2384
2418
|
* well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
|
|
2385
|
-
* it).
|
|
2419
|
+
* it). On a `checkpoint.config_refused` `field` names which INPUT carries the refusal — `"taskConfig"`
|
|
2420
|
+
* (the re-supplied config: correct it and resume the same token) or `"inheritedGate"` (the mode the row
|
|
2421
|
+
* recorded: no config reaches it; expire the row). The throw
|
|
2386
2422
|
* sites always knew which arm fired; this carries that fact instead of dropping it (ruled
|
|
2387
2423
|
* 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
|
|
2388
2424
|
* discriminant. */
|
|
2389
2425
|
detail?: {
|
|
2390
|
-
field?: "boundCallId" | "boundInputHash" | "answer" | "hostDecision" | "approver";
|
|
2426
|
+
field?: "boundCallId" | "boundInputHash" | "answer" | "hostDecision" | "approver" | "taskConfig" | "inheritedGate";
|
|
2427
|
+
/** On `checkpoint.config_refused` — prepare's own code for the refusal ({@link CheckpointConfigRefusalCode}), the
|
|
2428
|
+
* word a fresh run of the same bad config reports as its `errorCode`. Absent on every other code. */
|
|
2429
|
+
code?: CheckpointConfigRefusalCode;
|
|
2391
2430
|
/** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
|
|
2392
2431
|
* (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
|
|
2393
2432
|
* retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
|
|
@@ -3030,31 +3069,23 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
|
3030
3069
|
/** Test/inspection helper: number of stored checkpoints. */
|
|
3031
3070
|
get size(): number;
|
|
3032
3071
|
}
|
|
3033
|
-
/**
|
|
3034
|
-
*
|
|
3035
|
-
|
|
3072
|
+
/**
|
|
3073
|
+
* The ONE refusal of a retired mode seed, read PRE-CAS on every resume kind (resume-admission): a row whose `inheritedGate`
|
|
3074
|
+
* still carries a retired member is refused as the engine refuses every retired row seat (`readPendingSteerQueue`) —
|
|
3075
|
+
* `checkpoint.unsupported_version` with `reason: "seat_retired"` — terminal for the row's bytes on this engine, and
|
|
3076
|
+
* nothing consumed: the row stays byte-identical and `pending`. No engine reads the retired keys into a mode any more; an
|
|
3077
|
+
* engine at 7.38.x or earlier still does, so the row can be resumed there once (its next suspend writes `permissionMode`), or
|
|
3078
|
+
* expired (`CheckpointStore.expire`).
|
|
3079
|
+
*/
|
|
3080
|
+
export declare function refuseRetiredModeSeed(state: Pick<CheckpointState, "inheritedGate">): void;
|
|
3081
|
+
/**
|
|
3082
|
+
* The mode seed a row records (the 7.34.0 shape), read at prepare after {@link refuseRetiredModeSeed} has screened the row:
|
|
3083
|
+
* `permissionMode` must be a member of the closed set and `prePlanMode` a mode a plan can return to (else
|
|
3084
|
+
* `config.permission_mode_invalid`, the row cannot be resumed under a word this engine does not know); the shell floor is
|
|
3085
|
+
* `"always"` or absent. Nothing here migrates or guesses.
|
|
3086
|
+
*/
|
|
3087
|
+
export declare function recordedModeSeed(gate: Pick<NonNullable<CheckpointState["inheritedGate"]>, "permissionMode" | "prePlanMode" | "shellGate"> | undefined): {
|
|
3036
3088
|
permissionMode?: PermissionMode;
|
|
3037
3089
|
prePlanMode?: PermissionMode;
|
|
3038
3090
|
shellGate?: ShellGateDoctrine;
|
|
3039
|
-
|
|
3040
|
-
migrated?: {
|
|
3041
|
-
permissionMode?: PermissionMode;
|
|
3042
|
-
droppedKeys: readonly string[];
|
|
3043
|
-
};
|
|
3044
|
-
}
|
|
3045
|
-
/**
|
|
3046
|
-
* Read a row's mode seed ONCE, migrating the pre-7.34.0 keys the row may still carry. The matrix, key by key:
|
|
3047
|
-
* - `permissionMode` present ⇒ it must be a member of the closed set (else refused, `config.permission_mode_invalid`,
|
|
3048
|
-
* without reading the retired keys); the two retired mode keys beside it are DROPPED unvalidated (the new key is
|
|
3049
|
-
* the truth), `shellGate` follows the value rule below;
|
|
3050
|
-
* - `autoModeRequested`: absent / `false` ⇒ no mode from it; `true` ⇒ `auto`; anything else ⇒ refused
|
|
3051
|
-
* (`resume.legacy_mode_seed`);
|
|
3052
|
-
* - `approverPosture`: any value ⇒ dropped (it never authorized anything; the mode comes from the request / chain,
|
|
3053
|
-
* and a `"bypass"` posture beside `autoModeRequested: true` reads `auto` — a posture never becomes an executable
|
|
3054
|
-
* bypass);
|
|
3055
|
-
* - `shellGate`: absent ⇒ none; `"always"` ⇒ kept (a live key, not a retired encoding); `"off"` / `"classify"` ⇒
|
|
3056
|
-
* dropped (no floor); anything else ⇒ refused (`resume.legacy_mode_seed`).
|
|
3057
|
-
* Idempotent: a row the resume leg writes back carries `permissionMode` and the floor only, so a second read migrates nothing ("once" is per row).
|
|
3058
|
-
* Migration precedes any retired-key door, so a window row is read rather than refused. The window: this migration is kept through 7.38.x and removed in 7.39.0, which refuses a row still carrying these keys — resume or abandon a row parked before 7.34.0 before upgrading to 7.39.0.
|
|
3059
|
-
*/
|
|
3060
|
-
export declare function migrateLegacyModeSeed(gate: object | undefined): LegacyModeSeedMigration;
|
|
3091
|
+
};
|
|
@@ -313,6 +313,8 @@ export function summarizeCheckpoint(cp) {
|
|
|
313
313
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
314
314
|
};
|
|
315
315
|
}
|
|
316
|
+
export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired"]);
|
|
317
|
+
export const isCheckpointConfigRefusalCode = (word) => CHECKPOINT_CONFIG_REFUSAL_CODES.some((w) => w === word);
|
|
316
318
|
export class CheckpointError extends Error {
|
|
317
319
|
code;
|
|
318
320
|
detail;
|
|
@@ -734,57 +736,35 @@ export class InMemoryCheckpointStore {
|
|
|
734
736
|
return this.cps.size;
|
|
735
737
|
}
|
|
736
738
|
}
|
|
737
|
-
|
|
739
|
+
const RETIRED_MODE_SEED_MEMBERS = Object.freeze([
|
|
740
|
+
["autoModeRequested", () => true],
|
|
741
|
+
["approverPosture", () => true],
|
|
742
|
+
["shellGate", (v) => v !== "always"],
|
|
743
|
+
]);
|
|
744
|
+
export function refuseRetiredModeSeed(state) {
|
|
745
|
+
const gate = state.inheritedGate;
|
|
746
|
+
if (gate === undefined)
|
|
747
|
+
return;
|
|
748
|
+
for (const [key, retired] of RETIRED_MODE_SEED_MEMBERS) {
|
|
749
|
+
const value = gate[key];
|
|
750
|
+
if (value === undefined || !retired(value))
|
|
751
|
+
continue;
|
|
752
|
+
const shown = typeof value === "string" ? JSON.stringify(value.slice(0, 40)) : typeof value === "boolean" ? String(value) : typeof value;
|
|
753
|
+
throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's inheritedGate carries the retired mode-seed member \`${key}\` (${shown}), written before 7.34.0 and read by no engine since 7.39.0 — resume it once on an engine at 7.38.x or earlier (which reads it into \`permissionMode\`), or expire the row (\`CheckpointStore.expire\`); nothing was consumed`, { reason: "seat_retired" });
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
export function recordedModeSeed(gate) {
|
|
738
757
|
if (gate === undefined)
|
|
739
758
|
return {};
|
|
740
|
-
const
|
|
741
|
-
const refusal = (code, message) => {
|
|
759
|
+
const refusal = (message) => {
|
|
742
760
|
const e = new Error(message);
|
|
743
|
-
e.code =
|
|
761
|
+
e.code = "config.permission_mode_invalid";
|
|
744
762
|
return e;
|
|
745
763
|
};
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
permissionMode = declared;
|
|
753
|
-
if (raw["autoModeRequested"] !== undefined)
|
|
754
|
-
dropped.push("autoModeRequested");
|
|
755
|
-
if (raw["approverPosture"] !== undefined)
|
|
756
|
-
dropped.push("approverPosture");
|
|
757
|
-
}
|
|
758
|
-
else {
|
|
759
|
-
const intent = raw["autoModeRequested"];
|
|
760
|
-
if (intent === true) {
|
|
761
|
-
permissionMode = "auto";
|
|
762
|
-
dropped.push("autoModeRequested");
|
|
763
|
-
}
|
|
764
|
-
else if (intent !== undefined && intent !== false) {
|
|
765
|
-
throw refusal("resume.legacy_mode_seed", `the checkpoint row's inheritedGate.autoModeRequested is ${JSON.stringify(intent)}, which the one-time mode migration cannot read (only true / false / absent are) — the row is refused rather than resumed under a guessed mode`);
|
|
766
|
-
}
|
|
767
|
-
else if (intent === false) {
|
|
768
|
-
dropped.push("autoModeRequested");
|
|
769
|
-
}
|
|
770
|
-
if (raw["approverPosture"] !== undefined)
|
|
771
|
-
dropped.push("approverPosture");
|
|
772
|
-
}
|
|
773
|
-
const prePlan = raw["prePlanMode"];
|
|
774
|
-
if (prePlan !== undefined && (!isPermissionMode(prePlan) || prePlan === "plan"))
|
|
775
|
-
throw refusal("config.permission_mode_invalid", `the checkpoint row's inheritedGate.prePlanMode is ${JSON.stringify(prePlan)}, not a permission mode a plan can return to`);
|
|
776
|
-
const floor = raw["shellGate"];
|
|
777
|
-
let shellGate;
|
|
778
|
-
if (floor === "always")
|
|
779
|
-
shellGate = "always";
|
|
780
|
-
else if (floor === "off" || floor === "classify")
|
|
781
|
-
dropped.push("shellGate");
|
|
782
|
-
else if (floor !== undefined)
|
|
783
|
-
throw refusal("resume.legacy_mode_seed", `the checkpoint row's inheritedGate.shellGate is ${JSON.stringify(floor)}, which is neither the floor word "always" nor a retired doctrine word — the row is refused rather than resumed under a guessed floor`);
|
|
784
|
-
return {
|
|
785
|
-
...(permissionMode !== undefined ? { permissionMode } : {}),
|
|
786
|
-
...(prePlan !== undefined ? { prePlanMode: prePlan } : {}),
|
|
787
|
-
...(shellGate !== undefined ? { shellGate } : {}),
|
|
788
|
-
...(dropped.length > 0 ? { migrated: { ...(declared === undefined && permissionMode !== undefined ? { permissionMode } : {}), droppedKeys: dropped } } : {}),
|
|
789
|
-
};
|
|
764
|
+
const { permissionMode, prePlanMode, shellGate } = gate;
|
|
765
|
+
if (permissionMode !== undefined && !isPermissionMode(permissionMode))
|
|
766
|
+
throw refusal(`the checkpoint row's inheritedGate.permissionMode is ${JSON.stringify(permissionMode)}, not a permission mode — the row cannot be resumed under a mode this engine does not know`);
|
|
767
|
+
if (prePlanMode !== undefined && (!isPermissionMode(prePlanMode) || prePlanMode === "plan"))
|
|
768
|
+
throw refusal(`the checkpoint row's inheritedGate.prePlanMode is ${JSON.stringify(prePlanMode)}, not a permission mode a plan can return to`);
|
|
769
|
+
return { ...(permissionMode !== undefined ? { permissionMode } : {}), ...(prePlanMode !== undefined ? { prePlanMode } : {}), ...(shellGate !== undefined ? { shellGate } : {}) };
|
|
790
770
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { classifierUnavailableAskMessage, classifierUnavailableDenyMessage } from "./auto-mode.js";
|
|
2
|
-
import { attestClassifierDenyCause } from "./tool-policy.js";
|
|
2
|
+
import { attestClassifierDenyCause, carryEngineAttestations } from "./tool-policy.js";
|
|
3
3
|
export function classifierUnavailableArm(args) {
|
|
4
4
|
const { ask, req, cause } = args;
|
|
5
5
|
const seated = args.seat?.approvalSeat === true;
|
|
@@ -11,7 +11,7 @@ export function classifierUnavailableArm(args) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
if (seated)
|
|
14
|
-
return { kind: "asked", ask: { ...ask, message: classifierUnavailableAskMessage(ask.message ?? `approval required for "${req.toolName}"`, cause), requiresRealApproval: true } };
|
|
14
|
+
return { kind: "asked", ask: carryEngineAttestations(ask, { ...ask, message: classifierUnavailableAskMessage(ask.message ?? `approval required for "${req.toolName}"`, cause), requiresRealApproval: true }) };
|
|
15
15
|
const deny = { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, cause, args.attribution), decisionReason: "classifier" };
|
|
16
16
|
return { kind: "denied", deny: attestClassifierDenyCause(deny, "unavailable", req) };
|
|
17
17
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { PermissionResult } from "./tool-policy.js";
|
|
2
2
|
export declare const refusedOutOfContract: (message: string) => PermissionResult;
|
|
3
|
+
/** Is `d` a refusal the engine itself minted (by object identity — a look-alike `deny` a caller built is not one)? Public: the answer of
|
|
4
|
+
* `receiveHookResult` is one of two things, and this tells them apart. */
|
|
3
5
|
export declare const isEngineRefusal: (d: unknown) => boolean;
|
|
4
6
|
/** A decision's `decisionReason` — the closed set; the type is derived FROM this list, so a word added to either side is a
|
|
5
7
|
* compile error rather than a drift between the type and the screen. Module-private (the package publishes the type). */
|
|
@@ -3,7 +3,7 @@ import { ASK_CLASSES, ASK_ORIGINS, isAskClass, isAskOrigin, isPersistedRuleManda
|
|
|
3
3
|
import { thrownText } from "./thrown-value.js";
|
|
4
4
|
const ENGINE_REFUSALS = new WeakSet();
|
|
5
5
|
export const refusedOutOfContract = (message) => {
|
|
6
|
-
const refused = { action: "deny", message, decisionReason: "rule" };
|
|
6
|
+
const refused = Object.freeze({ action: "deny", message, decisionReason: "rule" });
|
|
7
7
|
ENGINE_REFUSALS.add(refused);
|
|
8
8
|
return refused;
|
|
9
9
|
};
|