@sema-agent/core 7.17.6 → 7.18.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 +165 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/auto-mode.d.ts +141 -45
- package/dist/core/auto-mode.js +109 -3
- package/dist/core/checkpoint-store.d.ts +16 -2
- package/dist/core/engine-notice.d.ts +47 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +17 -2
- package/dist/core/gate-outcome.js +16 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +8 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +89 -0
- package/dist/core/runner/classifier-ladder.js +65 -0
- package/dist/core/runner/contracts.d.ts +69 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
- package/dist/core/runner/prepare-config-doors.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.js +5 -3
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.js +4 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +18 -13
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +35 -41
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +3 -1
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +16 -19
- package/dist/core/runner/synthetic-tools.js +18 -10
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +20 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +8 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +10 -5
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
- package/dist/tools/fs/bash-readonly-classifier.js +375 -530
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +158 -22
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +3 -3
- package/dist/tools/fs/safety.js +4 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -9
- package/wire-changes/core@7.17.6.json +81 -0
- package/wire-changes/core@7.17.7.json +49 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -184,6 +184,22 @@ export function fallbackToPrimaryNotice(args) {
|
|
|
184
184
|
},
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
+
export function unroutableSeatNotice(args) {
|
|
188
|
+
return {
|
|
189
|
+
code: "route.classifier_unroutable",
|
|
190
|
+
message: `derived model "${args.from}" (${args.seat}) is not routable (${args.verdict.code}) and this deployment declared no fallback — ` +
|
|
191
|
+
`the seat is kept and its calls are refused rather than served by "${args.primary}". ${args.verdict.detail.fixHint}`,
|
|
192
|
+
detail: {
|
|
193
|
+
seat: args.seat,
|
|
194
|
+
from: args.from,
|
|
195
|
+
cause: args.verdict.code,
|
|
196
|
+
fixHint: args.verdict.detail.fixHint,
|
|
197
|
+
action: "refused",
|
|
198
|
+
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
199
|
+
...(args.runId !== undefined ? { runId: args.runId } : {}),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
187
203
|
export function authCarrierFingerprint(headers) {
|
|
188
204
|
if (!headers)
|
|
189
205
|
return "";
|
|
@@ -169,4 +169,14 @@ export interface SkillSpec {
|
|
|
169
169
|
path: string;
|
|
170
170
|
content: string;
|
|
171
171
|
}>;
|
|
172
|
+
/**
|
|
173
|
+
* The skill's own DIRECTORY on the machine the tools run on, so the model can reach its bundled material
|
|
174
|
+
* by path instead of searching the workspace. Core does not resolve it, read it, or check that it exists
|
|
175
|
+
* (the brain-outside posture {@link files} states). Core does exactly two textual things with it: the
|
|
176
|
+
* result opens with `Base directory for this skill: <baseDir>`, and both root placeholder spellings
|
|
177
|
+
* (`${SKILL_ROOT}`, `${CLAUDE_PLUGIN_ROOT}`) are substituted for it in the body and in every attachment's
|
|
178
|
+
* content, with `files[].path` then read RELATIVE to it. Absent ⇒ no line, no substitution, a
|
|
179
|
+
* byte-identical result; a placeholder with no directory stays verbatim.
|
|
180
|
+
*/
|
|
181
|
+
baseDir?: string;
|
|
172
182
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Artifacts hosting seam: the deployment-injected `ArtifactHost` (the `RunnerDeps.artifactHost` seat),
|
|
3
|
+
* its capability declaration, the call context the engine mints for every host call, the request /
|
|
4
|
+
* outcome shapes, and the closed word sets the tool face is assembled from. A host declares WHAT it
|
|
5
|
+
* supports (`capabilities()`); the engine assembles the `Artifact` tool from that declaration alone and
|
|
6
|
+
* never inspects the implementation. `localArtifactStub` (src/tools/artifact/local-stub.ts) is the first
|
|
7
|
+
* implementation of this interface; a server's hosting plane is the second.
|
|
8
|
+
*/
|
|
1
9
|
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
2
10
|
/** The model-facing action words. `publish` is the default action and the one every host must declare. */
|
|
3
11
|
export declare const ARTIFACT_ACTIONS: readonly ["publish", "list", "read", "list_files", "read_file"];
|
|
@@ -6,6 +14,17 @@ export type ArtifactAction = (typeof ARTIFACT_ACTIONS)[number];
|
|
|
6
14
|
* `"hosted"` = a hosting plane that mints its own URLs. Declared by the host, never detected. */
|
|
7
15
|
export declare const ARTIFACT_PLANES: readonly ["hosted", "local-stub"];
|
|
8
16
|
export type ArtifactPlane = (typeof ARTIFACT_PLANES)[number];
|
|
17
|
+
/** How a throw out of `publish` relates to the host's commit point. `"throw_means_uncommitted"`: every fallible
|
|
18
|
+
* step precedes the commit, so a throw proves nothing was committed; `"uncertain"`: a throw may follow it. */
|
|
19
|
+
export declare const ARTIFACT_COMMIT_CERTAINTIES: readonly ["throw_means_uncommitted", "uncertain"];
|
|
20
|
+
export type ArtifactCommitCertainty = (typeof ARTIFACT_COMMIT_CERTAINTIES)[number];
|
|
21
|
+
/** The `artifact.host_error` outcome word the tool reports when the host THREW, by declaration. An aborted or
|
|
22
|
+
* malformed answer is `"unknown"` under every declaration: the throw guarantee says nothing about those arms. */
|
|
23
|
+
export declare const ARTIFACT_COMMIT_THROW_OUTCOME: {
|
|
24
|
+
readonly throw_means_uncommitted: "not_executed";
|
|
25
|
+
readonly uncertain: "unknown";
|
|
26
|
+
};
|
|
27
|
+
export type ThrowOutcomeTableCoversEveryCommitCertainty = AssertAllKeysHandled<Exclude<ArtifactCommitCertainty, keyof typeof ARTIFACT_COMMIT_THROW_OUTCOME>>;
|
|
9
28
|
/** The `publishContext` word a publish carries (default `"plain"`). */
|
|
10
29
|
export declare const ARTIFACT_PUBLISH_CONTEXTS: readonly ["pr_review", "workshop", "whiteboard", "prototype", "plain"];
|
|
11
30
|
export type ArtifactPublishContext = (typeof ARTIFACT_PUBLISH_CONTEXTS)[number];
|
|
@@ -31,6 +50,16 @@ export declare const ARTIFACT_ACTION_HOST_METHOD: {
|
|
|
31
50
|
readonly read_file: "readFile";
|
|
32
51
|
};
|
|
33
52
|
export type HostMethodTableCoversEveryAction = AssertAllKeysHandled<Exclude<ArtifactAction, keyof typeof ARTIFACT_ACTION_HOST_METHOD>>;
|
|
53
|
+
/** Whether a call of the action may run inside a parallel batch: the read family is (host reads, no host write); a
|
|
54
|
+
* publish is not (it writes the host and the tool's path-binding state). Read by the tool's `isConcurrencySafe`. */
|
|
55
|
+
export declare const ARTIFACT_ACTION_CONCURRENCY_SAFE: {
|
|
56
|
+
readonly publish: false;
|
|
57
|
+
readonly list: true;
|
|
58
|
+
readonly read: true;
|
|
59
|
+
readonly list_files: true;
|
|
60
|
+
readonly read_file: true;
|
|
61
|
+
};
|
|
62
|
+
export type ConcurrencyTableCoversEveryAction = AssertAllKeysHandled<Exclude<ArtifactAction, keyof typeof ARTIFACT_ACTION_CONCURRENCY_SAFE>>;
|
|
34
63
|
/** The typed failure words a host outcome may carry (`ok: false`). */
|
|
35
64
|
export declare const ARTIFACT_HOST_FAILURE_CODES: readonly ["conflict", "url_foreign", "forbidden", "payload_too_large", "not_found", "force_refused", "refused"];
|
|
36
65
|
export type ArtifactHostFailureCode = (typeof ARTIFACT_HOST_FAILURE_CODES)[number];
|
|
@@ -56,11 +85,16 @@ export declare const ARTIFACT_CARD = "artifact";
|
|
|
56
85
|
* containing `"publish"`; `plane: "local-stub"` ⇒ `actions` is exactly `["publish"]`;
|
|
57
86
|
* `defaultVisibility` is `"private"`; both byte limits are positive finite integers; `multiFile` is a
|
|
58
87
|
* boolean; every declared action's host method exists; `plane: "local-stub"` ⇒ `storageDir` is a non-empty
|
|
59
|
-
*
|
|
88
|
+
* fully qualified path under its own spelling family (`C:\\…` / UNC / `/…` — read from the spelling, never from
|
|
89
|
+
* `process.platform`); `commit` is a word of {@link ARTIFACT_COMMIT_CERTAINTIES} (no default — an undeclared
|
|
90
|
+
* commit protocol is an invalid declaration, never read as either word).
|
|
60
91
|
*/
|
|
61
92
|
export interface ArtifactHostCapabilities {
|
|
62
93
|
plane: ArtifactPlane;
|
|
63
94
|
actions: ReadonlyArray<ArtifactAction>;
|
|
95
|
+
/** What a throw out of `publish` proves about the commit (see {@link ARTIFACT_COMMIT_CERTAINTIES}); the tool words
|
|
96
|
+
* its host-fault result from this and from nothing else about the host. */
|
|
97
|
+
commit: ArtifactCommitCertainty;
|
|
64
98
|
/** The on-disk store of a `"local-stub"` plane (required there; ignored on `"hosted"`). Contract: the store
|
|
65
99
|
* must lie OUTSIDE every root the run may write or read (the task root, `additionalDirectories`,
|
|
66
100
|
* `additionalReadDirectories`) — a store the model can reach through the fs tools is not a trust boundary;
|
|
@@ -77,9 +111,12 @@ export interface ArtifactHostCapabilities {
|
|
|
77
111
|
* Engine-minted call attribution and idempotency keys — closure-supplied, never a tool parameter.
|
|
78
112
|
* `taskId` / `sessionId` / `agentName` are audit attribution, never an authorization subject: a host
|
|
79
113
|
* instance is bound to one principal at construction. `runId` and `attemptId` are engine-minted trusted
|
|
80
|
-
* keys: `runId` is the same value `onMount` received; `attemptId` is one opaque value per
|
|
81
|
-
*
|
|
82
|
-
* `
|
|
114
|
+
* keys: `runId` is the same value `onMount` received; `attemptId` is one opaque value per HOST CALL — the
|
|
115
|
+
* engine mints a fresh one for every call it makes, so two calls of a single tool invocation (the probing
|
|
116
|
+
* `read` that establishes ownership and the `list_files` it gates, say) never arrive under one key — and it
|
|
117
|
+
* is stable across any retry the HOST makes of that call, so a host MUST replay the first outcome for a
|
|
118
|
+
* repeated `attemptId` (a fresh publish never mints a second page, an update never advances twice). The rule
|
|
119
|
+
* is keyed on the value alone: a host may hold one attempt ledger for all its methods.
|
|
83
120
|
*/
|
|
84
121
|
export interface ArtifactCallContext {
|
|
85
122
|
taskId: string;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAbsoluteForFamily, pathFamilyOf } from "../tools/fs/safety.js";
|
|
2
2
|
export const ARTIFACT_ACTIONS = ["publish", "list", "read", "list_files", "read_file"];
|
|
3
3
|
export const ARTIFACT_PLANES = ["hosted", "local-stub"];
|
|
4
|
+
export const ARTIFACT_COMMIT_CERTAINTIES = ["throw_means_uncommitted", "uncertain"];
|
|
5
|
+
export const ARTIFACT_COMMIT_THROW_OUTCOME = {
|
|
6
|
+
throw_means_uncommitted: "not_executed",
|
|
7
|
+
uncertain: "unknown",
|
|
8
|
+
};
|
|
4
9
|
export const ARTIFACT_PUBLISH_CONTEXTS = ["pr_review", "workshop", "whiteboard", "prototype", "plain"];
|
|
5
10
|
export const ARTIFACT_LIST_SCOPES = ["mine", "shared", "all"];
|
|
6
11
|
export const ARTIFACT_PLANE_CONTENT_ORIGIN = {
|
|
@@ -14,6 +19,13 @@ export const ARTIFACT_ACTION_HOST_METHOD = {
|
|
|
14
19
|
list_files: "listFiles",
|
|
15
20
|
read_file: "readFile",
|
|
16
21
|
};
|
|
22
|
+
export const ARTIFACT_ACTION_CONCURRENCY_SAFE = {
|
|
23
|
+
publish: false,
|
|
24
|
+
list: true,
|
|
25
|
+
read: true,
|
|
26
|
+
list_files: true,
|
|
27
|
+
read_file: true,
|
|
28
|
+
};
|
|
17
29
|
export const ARTIFACT_HOST_FAILURE_CODES = ["conflict", "url_foreign", "forbidden", "payload_too_large", "not_found", "force_refused", "refused"];
|
|
18
30
|
export const ARTIFACT_FAILURE_RESULT_CODE = {
|
|
19
31
|
conflict: "artifact.publish_conflict",
|
|
@@ -59,6 +71,9 @@ export function readArtifactHostCapabilities(host) {
|
|
|
59
71
|
return { ok: false, problem: 'capabilities.actions must include "publish"' };
|
|
60
72
|
if (plane === "local-stub" && (actions.length !== 1 || actions[0] !== "publish"))
|
|
61
73
|
return { ok: false, problem: 'a "local-stub" plane must declare exactly ["publish"]' };
|
|
74
|
+
const commit = c["commit"];
|
|
75
|
+
if (!wordOf(ARTIFACT_COMMIT_CERTAINTIES, commit))
|
|
76
|
+
return { ok: false, problem: `capabilities.commit must be one of ${ARTIFACT_COMMIT_CERTAINTIES.join(" | ")}` };
|
|
62
77
|
if (c["defaultVisibility"] !== "private")
|
|
63
78
|
return { ok: false, problem: 'capabilities.defaultVisibility must be "private"' };
|
|
64
79
|
if (!isPositiveInt(c["maxFileBytes"]))
|
|
@@ -68,7 +83,7 @@ export function readArtifactHostCapabilities(host) {
|
|
|
68
83
|
if (typeof c["multiFile"] !== "boolean")
|
|
69
84
|
return { ok: false, problem: "capabilities.multiFile must be a boolean" };
|
|
70
85
|
const storageDir = c["storageDir"];
|
|
71
|
-
if (plane === "local-stub" && (typeof storageDir !== "string" || storageDir.length === 0 || !
|
|
86
|
+
if (plane === "local-stub" && (typeof storageDir !== "string" || storageDir.length === 0 || !isAbsoluteForFamily(pathFamilyOf({ root: storageDir }), storageDir)))
|
|
72
87
|
return { ok: false, problem: 'a "local-stub" plane must declare its absolute storageDir' };
|
|
73
88
|
if (storageDir !== undefined && typeof storageDir !== "string")
|
|
74
89
|
return { ok: false, problem: "capabilities.storageDir must be a string when present" };
|
|
@@ -82,6 +97,7 @@ export function readArtifactHostCapabilities(host) {
|
|
|
82
97
|
capabilities: Object.freeze({
|
|
83
98
|
plane,
|
|
84
99
|
actions: Object.freeze([...actions]),
|
|
100
|
+
commit,
|
|
85
101
|
defaultVisibility: "private",
|
|
86
102
|
maxFileBytes: c["maxFileBytes"],
|
|
87
103
|
maxTotalBytes: c["maxTotalBytes"],
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -10,37 +10,117 @@ export declare const AUTO_MODE_UNAVAILABLE_CAUSES: readonly ["error", "timeout"]
|
|
|
10
10
|
export type AutoModeUnavailableCause = (typeof AUTO_MODE_UNAVAILABLE_CAUSES)[number];
|
|
11
11
|
/** Whether a value is a member of the unavailable-cause set — the screen a row reader applies to a stored word. */
|
|
12
12
|
export declare function isAutoModeUnavailableCause(v: unknown): v is AutoModeUnavailableCause;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* WHY a classification moved DOWN the candidate ladder — the closed set behind
|
|
15
|
+
* {@link AutoModeClassifierRound}'s `fallback.cause`, i.e. the ways a candidate produces NO RULING
|
|
16
|
+
* ({@link rungDidNotRule}). Two words are the unavailable causes verbatim (the leg threw / rejected; the
|
|
17
|
+
* round-trip cap elapsed); `parse_error` is the candidate that ANSWERED without a verdict. A ruling is not a
|
|
18
|
+
* member: the ladder stops at a verdict either way.
|
|
19
|
+
*/
|
|
20
|
+
export declare const AUTO_MODE_FALLBACK_CAUSES: readonly ["error", "timeout", "parse_error"];
|
|
21
|
+
export type AutoModeFallbackCause = (typeof AUTO_MODE_FALLBACK_CAUSES)[number];
|
|
22
|
+
/** Whether a value is a member of the fallback-cause set — the screen a row reader applies to a stored word. */
|
|
23
|
+
export declare function isAutoModeFallbackCause(v: unknown): v is AutoModeFallbackCause;
|
|
24
|
+
/**
|
|
25
|
+
* The deployment's answer to "what happens when the classifier seat does not rule?"
|
|
26
|
+
* (`RunnerDeps.autoMode.classifierFallback`) — the closed set behind the candidate ladder's LENGTH:
|
|
27
|
+
* `main` = [classifier seat, the run's main model]; `none` = [classifier seat].
|
|
28
|
+
*/
|
|
29
|
+
export declare const AUTO_MODE_CLASSIFIER_FALLBACKS: readonly ["main", "none"];
|
|
30
|
+
export type AutoModeClassifierFallback = (typeof AUTO_MODE_CLASSIFIER_FALLBACKS)[number];
|
|
31
|
+
/** Whether a value is a member of the classifier-fallback set. */
|
|
32
|
+
export declare function isAutoModeClassifierFallback(v: unknown): v is AutoModeClassifierFallback;
|
|
33
|
+
/** Read the knob: absent ⇒ the default `main`; a member ⇒ itself; ANYTHING ELSE ⇒ a loud refusal carrying
|
|
34
|
+
* `config.auto_mode_classifier_fallback_invalid`. A typo'd knob must not read as the default: `"non"` would
|
|
35
|
+
* keep paying for the rung it meant to turn off. */
|
|
36
|
+
export declare function autoModeClassifierFallbackOf(v: unknown): AutoModeClassifierFallback;
|
|
37
|
+
/** The classifier's verdict over one pending `ask`. Every arm may carry the classification's model
|
|
38
|
+
* attribution ({@link AutoModeClassifierRound}), present iff the decider was wired with a candidate ladder. */
|
|
39
|
+
export type AutoModeVerdict = ({
|
|
15
40
|
kind: "allow";
|
|
16
|
-
}
|
|
41
|
+
} & AutoModeRoundAttributed)
|
|
17
42
|
/** `category` is the matched BLOCK rule name (207 output contract); `reason` starts with `[Rule Name]`. */
|
|
18
|
-
| {
|
|
43
|
+
| ({
|
|
19
44
|
kind: "block";
|
|
20
45
|
category: string;
|
|
21
46
|
reason: string;
|
|
22
|
-
}
|
|
47
|
+
} & AutoModeRoundAttributed)
|
|
23
48
|
/** The classifier could not run: the model leg threw / rejected, or ran past the cap. NOT a decision — the
|
|
24
49
|
* gate denies and says so ({@link classifierUnavailableDenyMessage}). */
|
|
25
|
-
| {
|
|
50
|
+
| ({
|
|
26
51
|
kind: "unavailable";
|
|
27
52
|
cause: AutoModeUnavailableCause;
|
|
28
|
-
}
|
|
53
|
+
} & AutoModeRoundAttributed)
|
|
29
54
|
/** The model responded but not in the `<block>…` contract shape. Handled as a BLOCK with the CC
|
|
30
55
|
* parse-failure sentence ({@link CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE}), counted by the denial limit. */
|
|
31
|
-
| {
|
|
56
|
+
| ({
|
|
32
57
|
kind: "parse_error";
|
|
33
58
|
raw: string;
|
|
59
|
+
} & AutoModeRoundAttributed);
|
|
60
|
+
/** The member every verdict arm shares: WHICH MODEL this classification asked and which one answered. */
|
|
61
|
+
export interface AutoModeRoundAttributed {
|
|
62
|
+
/** Absent when the decider was given no candidate ladder (a hand-built decider, the cross-process rebuild's
|
|
63
|
+
* single fresh leg): it ran on whatever model the leg is bound to and can vouch for no name. Never faked. */
|
|
64
|
+
readonly classifier?: AutoModeClassifierRound;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* WHICH MODEL answered one classification — the record a walk of the CANDIDATE LADDER
|
|
68
|
+
* ({@link AutoModeDeciderOptions.candidates}) hands its consumers: `modelRequested` = the ladder's FIRST rung
|
|
69
|
+
* (the seat the classification was for); `modelUsed` = the rung whose answer the gate acted on, and where NO
|
|
70
|
+
* candidate ruled, the last rung ATTEMPTED (stated, not implied by absence); `fallback` = present exactly
|
|
71
|
+
* when the ladder MOVED to reach `modelUsed` — which rung it left, which it arrived at, and why
|
|
72
|
+
* ({@link AutoModeFallbackCause}); absent = the first rung answered.
|
|
73
|
+
*/
|
|
74
|
+
export interface AutoModeClassifierRound {
|
|
75
|
+
readonly modelRequested: string;
|
|
76
|
+
readonly modelUsed: string;
|
|
77
|
+
readonly fallback?: {
|
|
78
|
+
readonly from: string;
|
|
79
|
+
readonly to: string;
|
|
80
|
+
readonly cause: AutoModeFallbackCause;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The classify hook's word for "this classification was NEVER PUT TO A MODEL" — the failure is in assembling
|
|
85
|
+
* the QUESTION (the transcript window every rung shares), not in a rung's answer.
|
|
86
|
+
*
|
|
87
|
+
* WHY IT EXISTS. The ladder advances on "the rung produced no ruling", and a hook that rejects looks exactly
|
|
88
|
+
* like that. But a classification whose prompt could not be built fails IDENTICALLY on every rung (the build is one
|
|
89
|
+
* shared promise, by contract — "the same prompt, re-asked" is literal), so the walk recorded a fallback
|
|
90
|
+
* `{from: seat, to: main, cause: "error"}` and a deny sentence naming two models, for a classification in which NO
|
|
91
|
+
* MODEL WAS EVER DISPATCHED: a transcript-store outage read, on every face a person can see, as a broken
|
|
92
|
+
* classifier seat. The verdict is unchanged (fail-closed `unavailable`); what changes is that the record
|
|
93
|
+
* stops attributing it to models that were never asked.
|
|
94
|
+
*
|
|
95
|
+
* Carried as a `code` on the thrown error rather than a class so any classify hook — including one this
|
|
96
|
+
* package never constructed — can raise it without importing a constructor.
|
|
97
|
+
*
|
|
98
|
+
* WHAT IT DOES NOT COVER, stated: a build that OUTRUNS a rung's cap is still recorded as that rung's
|
|
99
|
+
* `timeout`. The cap bounds the whole classify call, assembly included, so at that point nothing distinguishes
|
|
100
|
+
* a slow transcript store from a slow model. Moving the per-classification build outside the rung cap is a
|
|
101
|
+
* seam change, filed for a later version.
|
|
102
|
+
*/
|
|
103
|
+
export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
|
|
104
|
+
/** Mint the marker above around whatever failed, keeping the original as `cause` for the host's own logs.
|
|
105
|
+
* TOTAL by construction: the failure it wraps is an arbitrary value, and an `instanceof` or a `.message` read
|
|
106
|
+
* on a revoked proxy (or a throwing getter) THROWS — which would replace the marker with an ordinary error and
|
|
107
|
+
* hand the walk back the attribution this whole seat exists to remove. So the detail is a guarded read that
|
|
108
|
+
* must already be a string, and the total describer otherwise. */
|
|
109
|
+
export declare function autoModeRoundNotAsked(reason: unknown): Error & {
|
|
110
|
+
code: string;
|
|
34
111
|
};
|
|
112
|
+
/** Why a rung did not settle the classification, or `undefined` when it RULED and the ladder stops — the ONE
|
|
113
|
+
* predicate behind the advance (there is no `if (fallback)` arm anywhere). */
|
|
114
|
+
export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCause | undefined;
|
|
35
115
|
/**
|
|
36
116
|
* The model-facing deny text for an `unavailable` verdict — CC 2.1.250 `x1t`, with the parenthetical `EIt`
|
|
37
117
|
* spells for the causes this engine distinguishes (a wall-clock / connection timeout reads " (timed out)";
|
|
38
|
-
* an
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
118
|
+
* an unknown kind reads nothing). The subject is the classifier itself rather than a model id: without a
|
|
119
|
+
* candidate ladder the decider cannot vouch for a name. One writer for every deny site. ROOT CAUSE, when
|
|
120
|
+
* `attribution.fallback` is present: the sentence names BOTH rungs that failed to answer; a ladder that did
|
|
121
|
+
* NOT move keeps the CC text byte for byte.
|
|
42
122
|
*/
|
|
43
|
-
export declare function classifierUnavailableDenyMessage(toolName: string, cause: AutoModeUnavailableCause): string;
|
|
123
|
+
export declare function classifierUnavailableDenyMessage(toolName: string, cause: AutoModeUnavailableCause, attribution?: AutoModeClassifierRound): string;
|
|
44
124
|
/** The model-facing deny text for a `parse_error` verdict — CC 2.1.250 `Ure` over the R3t stem: the classifier
|
|
45
125
|
* answered, but not with a verdict, and auto mode blocks what it could not evaluate. */
|
|
46
126
|
export declare const CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE = "Auto mode could not evaluate this action and is blocking it for safety \u2014 the classifier's reply carried no verdict.";
|
|
@@ -63,13 +143,11 @@ export declare const CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE = "Auto mode could no
|
|
|
63
143
|
* no whitespace inside (the word-boundary form — `<block>yesterday` is not a hit) and case does not matter.
|
|
64
144
|
*
|
|
65
145
|
* A stated BLOCK stands with or without its tail: `<category>` (the BLOCK rule NAME the prompt asked
|
|
66
|
-
* for, read case-insensitively as free text — the deployment authors those names, so there is no
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* downgrading a stated block to `parse_error` (a block with the generic parse-failure sentence instead
|
|
72
|
-
* of the classifier's own reason) would lose the classifier's explicit verdict on the exact calls it flagged.
|
|
146
|
+
* for, read case-insensitively as free text — the deployment authors those names, so there is no fixed
|
|
147
|
+
* vocabulary to screen them against) and `<reason>` (first `<reason>…</reason>` pair on the stripped
|
|
148
|
+
* text, trimmed) are carried when present and `""` when absent; the consumers render their own fallback
|
|
149
|
+
* line from the tool name in that case. Downgrading a stated block to `parse_error` would lose the
|
|
150
|
+
* classifier's explicit verdict on the exact calls it flagged.
|
|
73
151
|
*
|
|
74
152
|
* `parse_error` is fail-closed: the gate handles it as a BLOCK (CC 2.1.250's own outcome for a reply with no
|
|
75
153
|
* verdict) that the denial limit counts like any other — never a silent allow, and never a question handed
|
|
@@ -84,19 +162,42 @@ export interface AutoModeClassifyInput {
|
|
|
84
162
|
/** The human-readable text of the ask being decided (policy/safety message), when present. */
|
|
85
163
|
askMessage?: string;
|
|
86
164
|
}
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
89
|
-
export
|
|
165
|
+
/** One rung of the ladder, as the leg is told about it. `index` is the ADDRESS (a ladder may carry two rungs
|
|
166
|
+
* whose model id is the same string), while `model` is the NAME the record will carry. */
|
|
167
|
+
export interface AutoModeLadderRung {
|
|
168
|
+
readonly index: number;
|
|
169
|
+
readonly model: string;
|
|
170
|
+
}
|
|
171
|
+
/** The pluggable model leg: given the classify input and the RUNG the ladder is on, return that model's raw
|
|
172
|
+
* text response. Deployment-injected; MAY throw / reject — the decider fail-closes. A rejection carrying
|
|
173
|
+
* {@link AUTO_MODE_ROUND_NOT_ASKED_CODE} says the failure was in assembling the QUESTION rather than in this
|
|
174
|
+
* rung's answer, and stops the walk instead of blaming the next rung. `rung` is `undefined`
|
|
175
|
+
* for a decider wired with no ladder. Both trailing parameters are OPTIONAL, not `| undefined`: a caller
|
|
176
|
+
* INVOKING it with one or two arguments must keep compiling. */
|
|
177
|
+
export type AutoModeClassifyFn = (input: AutoModeClassifyInput, signal?: AbortSignal, rung?: AutoModeLadderRung) => Promise<string>;
|
|
90
178
|
export interface AutoModeDeciderOptions {
|
|
91
179
|
classify: AutoModeClassifyFn;
|
|
92
|
-
/**
|
|
93
|
-
*
|
|
180
|
+
/**
|
|
181
|
+
* The CANDIDATE LADDER this decider walks, in order — the models a classification may be asked on, NOT a
|
|
182
|
+
* second way to say what `classify` says (that hook is the DISPATCH; this is the sequence it dispatches
|
|
183
|
+
* to). Rung 0 is the seat the classification is for; every later rung is consulted only when the one
|
|
184
|
+
* before it produced NO RULING ({@link rungDidNotRule}). The FIRST ruling ends the walk and is the
|
|
185
|
+
* verdict; when no rung rules, the LAST rung's own non-ruling verdict stands.
|
|
186
|
+
*
|
|
187
|
+
* Omitted = a one-rung anonymous ladder: the leg as wired, no attribution on the verdict. An EMPTY array,
|
|
188
|
+
* or a rung that is not a non-empty string, is REFUSED loudly at construction (a deployment that computed
|
|
189
|
+
* its ladder and got nothing has a bug, not a default). COST: {@link timeoutMs} bounds ONE rung, so the
|
|
190
|
+
* worst case is the cap times the rungs actually walked.
|
|
191
|
+
*/
|
|
192
|
+
candidates?: readonly string[];
|
|
193
|
+
/** Hard cap on one classification round-trip — one LADDER RUNG, not the whole classification (see
|
|
194
|
+
* {@link candidates}). Default 15_000 ms: a permission gate must not stall the whole run on a slow classifier. */
|
|
94
195
|
timeoutMs?: number;
|
|
95
196
|
/**
|
|
96
|
-
* Fired once per `decide` call with the verdict the gate is about to act on and the wall time it
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
197
|
+
* Fired once per `decide` call with the verdict the gate is about to act on and the wall time it waited
|
|
198
|
+
* (`ms`, integer ≥ 0; a timed-out classification reads the deadline, one that walked two rungs the whole
|
|
199
|
+
* walk). `cause` is present iff the verdict is `unavailable`; `classifier` iff a ladder was wired. A
|
|
200
|
+
* throwing hook never breaks the gate.
|
|
100
201
|
*/
|
|
101
202
|
onClassified?: (info: AutoModeClassified) => void;
|
|
102
203
|
}
|
|
@@ -110,16 +211,17 @@ export interface AutoModeClassified {
|
|
|
110
211
|
cause?: Extract<AutoModeVerdict, {
|
|
111
212
|
kind: "unavailable";
|
|
112
213
|
}>["cause"];
|
|
214
|
+
/** The classification's model attribution, present exactly when the decider was wired with a candidate ladder
|
|
215
|
+
* ({@link AutoModeClassifierRound}) — the same object the verdict carries. */
|
|
216
|
+
classifier?: AutoModeClassifierRound;
|
|
113
217
|
}
|
|
114
218
|
export interface AutoModeDecider {
|
|
115
219
|
/** Never rejects. Any internal failure surfaces as `unavailable`/`parse_error` (fail-closed). */
|
|
116
220
|
decide(input: AutoModeClassifyInput, signal?: AbortSignal): Promise<AutoModeVerdict>;
|
|
117
221
|
}
|
|
118
|
-
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* next one, which asks the model again.
|
|
122
|
-
*/
|
|
222
|
+
/** The per-run decider: timeout + fail-closed error mapping, nothing carried between rounds. One instance per
|
|
223
|
+
* run (it binds the run's model leg and assembled prompt); a round that failed says nothing about the next,
|
|
224
|
+
* which asks the model again. */
|
|
123
225
|
export declare function createAutoModeDecider(opts: AutoModeDeciderOptions): AutoModeDecider;
|
|
124
226
|
/** The deployment's bounds for the denial limit (`RunnerDeps.autoMode.denialLimit`). Every member
|
|
125
227
|
* optional; an omitted member takes its CC default. A present member with a bad value is REFUSED
|
|
@@ -152,24 +254,18 @@ export interface DenialLimitCounts {
|
|
|
152
254
|
* The DECISION-layer member of a denial-limit fallback ask (`PermissionResult` ask arm): the counts
|
|
153
255
|
* that tripped the bound, and NOTHING about a window. A window is a fact of the ask's ROUTE — a live
|
|
154
256
|
* function approver can count down, a durable park row and a headless deny cannot — and the route is
|
|
155
|
-
* chosen downstream of the count
|
|
156
|
-
* a position to state one. The type says so: there is no window member to write here, and the only
|
|
257
|
+
* chosen downstream of the count. The type says so: there is no window member to write here, and the only
|
|
157
258
|
* ways to reach a {@link DenialLimitFallback} (the member an {@link
|
|
158
259
|
* import("./tool-policy.js").AskRequest} carries) are the two route stations,
|
|
159
|
-
* {@link AutoModeDenialTracker.armTimedWindow} and {@link unarmedWindow}.
|
|
160
|
-
* gate adapter therefore cannot forward a decision's member straight into an approval request: the
|
|
161
|
-
* compiler stops it and names the station it skipped.
|
|
260
|
+
* {@link AutoModeDenialTracker.armTimedWindow} and {@link unarmedWindow}.
|
|
162
261
|
*/
|
|
163
262
|
export interface UnarmedDenialLimitFallback extends DenialLimitCounts {
|
|
164
263
|
/** FORBIDDEN on a decision, and stated rather than merely omitted. Leaving the member out would only
|
|
165
264
|
* stop a FRESH object literal (TypeScript's excess-property check); a policy that writes its window
|
|
166
265
|
* through a `const`, an `as const`, or an annotated return type would still compile — and would then
|
|
167
|
-
* have its number silently replaced at whichever route station the ask reached
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* TOLD about rather than one it discovers when nobody is auto-denied. (A host that casts past the
|
|
171
|
-
* type still meets the route stations' runtime belt: they write the route's number, never the
|
|
172
|
-
* decision's.) */
|
|
266
|
+
* have its number silently replaced at whichever route station the ask reached. `never` makes the old
|
|
267
|
+
* shape a compile error at the policy boundary instead. (A host that casts past the type still meets
|
|
268
|
+
* the route stations' runtime belt: they write the route's number, never the decision's.) */
|
|
173
269
|
readonly autoDenyAfterMs?: never;
|
|
174
270
|
}
|
|
175
271
|
/** The REQUEST-layer member (`AskRequest`, a durable park row, a checkpoint summary's echo): the same
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -1,12 +1,65 @@
|
|
|
1
1
|
import { AUTO_MODE_DEFAULT_TIMEOUT_MS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_DENIAL_LIMIT_DEFAULTS } from "./auto-mode-defaults.js";
|
|
2
|
+
import { describeRejectedValue } from "./tool-search-setting.js";
|
|
2
3
|
export const AUTO_MODE_UNAVAILABLE_CAUSES = ["error", "timeout"];
|
|
3
4
|
const AUTO_MODE_UNAVAILABLE_CAUSE_SET = new Set(AUTO_MODE_UNAVAILABLE_CAUSES);
|
|
4
5
|
export function isAutoModeUnavailableCause(v) {
|
|
5
6
|
return AUTO_MODE_UNAVAILABLE_CAUSE_SET.has(v);
|
|
6
7
|
}
|
|
7
|
-
export
|
|
8
|
+
export const AUTO_MODE_FALLBACK_CAUSES = ["error", "timeout", "parse_error"];
|
|
9
|
+
const AUTO_MODE_FALLBACK_CAUSE_SET = new Set(AUTO_MODE_FALLBACK_CAUSES);
|
|
10
|
+
export function isAutoModeFallbackCause(v) {
|
|
11
|
+
return AUTO_MODE_FALLBACK_CAUSE_SET.has(v);
|
|
12
|
+
}
|
|
13
|
+
export const AUTO_MODE_CLASSIFIER_FALLBACKS = ["main", "none"];
|
|
14
|
+
const AUTO_MODE_CLASSIFIER_FALLBACK_SET = new Set(AUTO_MODE_CLASSIFIER_FALLBACKS);
|
|
15
|
+
export function isAutoModeClassifierFallback(v) {
|
|
16
|
+
return AUTO_MODE_CLASSIFIER_FALLBACK_SET.has(v);
|
|
17
|
+
}
|
|
18
|
+
export function autoModeClassifierFallbackOf(v) {
|
|
19
|
+
if (v === undefined)
|
|
20
|
+
return "main";
|
|
21
|
+
if (isAutoModeClassifierFallback(v))
|
|
22
|
+
return v;
|
|
23
|
+
const e = new Error(`RunnerDeps.autoMode.classifierFallback must be one of ${AUTO_MODE_CLASSIFIER_FALLBACKS.map((x) => `"${x}"`).join(" | ")} ` +
|
|
24
|
+
`(got ${describeRejectedValue(v)}) — omit it for the default "main"`);
|
|
25
|
+
e.code = "config.auto_mode_classifier_fallback_invalid";
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
export const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
|
|
29
|
+
export function autoModeRoundNotAsked(reason) {
|
|
30
|
+
let detail;
|
|
31
|
+
try {
|
|
32
|
+
const m = reason.message;
|
|
33
|
+
if (typeof m === "string")
|
|
34
|
+
detail = m.slice(0, 200);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
}
|
|
38
|
+
const e = new Error(`the auto-mode classification could not be assembled: ${detail ?? describeRejectedValue(reason)}`, { cause: reason });
|
|
39
|
+
e.code = AUTO_MODE_ROUND_NOT_ASKED_CODE;
|
|
40
|
+
return e;
|
|
41
|
+
}
|
|
42
|
+
function isAutoModeRoundNotAsked(e) {
|
|
43
|
+
try {
|
|
44
|
+
return typeof e === "object" && e !== null && e.code === AUTO_MODE_ROUND_NOT_ASKED_CODE;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function rungDidNotRule(v) {
|
|
51
|
+
if (v.kind === "unavailable")
|
|
52
|
+
return v.cause;
|
|
53
|
+
if (v.kind === "parse_error")
|
|
54
|
+
return "parse_error";
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
export function classifierUnavailableDenyMessage(toolName, cause, attribution) {
|
|
8
58
|
const parenthetical = cause === "timeout" ? " (timed out)" : "";
|
|
59
|
+
const fb = attribution?.fallback;
|
|
60
|
+
const rootCause = fb === undefined ? "" : `The classifier model "${fb.from}" did not answer (${fb.cause}) and neither did the model it fell back to, "${fb.to}" (${cause}). `;
|
|
9
61
|
return (`The auto-mode classifier is temporarily unavailable${parenthetical}, so auto mode cannot determine the safety of ${toolName} right now. ` +
|
|
62
|
+
rootCause +
|
|
10
63
|
"Wait a moment and then try this action again. " +
|
|
11
64
|
"If it keeps failing, continue with other tasks that don't require this action and come back to it later. " +
|
|
12
65
|
"Note: reading files, searching code, and other read-only operations do not require the classifier and can still be used.");
|
|
@@ -31,6 +84,15 @@ export function parseAutoModeResponse(text) {
|
|
|
31
84
|
}
|
|
32
85
|
export function createAutoModeDecider(opts) {
|
|
33
86
|
const timeoutMs = opts.timeoutMs ?? AUTO_MODE_DEFAULT_TIMEOUT_MS;
|
|
87
|
+
if (opts.candidates !== undefined) {
|
|
88
|
+
if (opts.candidates.length === 0) {
|
|
89
|
+
throw new Error("AutoModeDeciderOptions.candidates must name at least one model — omit the member for the leg's own model, never an empty ladder");
|
|
90
|
+
}
|
|
91
|
+
const bad = opts.candidates.findIndex((m) => typeof m !== "string" || m.trim() === "");
|
|
92
|
+
if (bad >= 0)
|
|
93
|
+
throw new Error(`AutoModeDeciderOptions.candidates[${bad}] must be a non-empty model id (got ${JSON.stringify(opts.candidates[bad])})`);
|
|
94
|
+
}
|
|
95
|
+
const ladder = opts.candidates ?? [undefined];
|
|
34
96
|
const decide = async (input, signal) => {
|
|
35
97
|
const startedAt = performance.now();
|
|
36
98
|
const verdict = await decideUnreported(input, signal);
|
|
@@ -41,6 +103,7 @@ export function createAutoModeDecider(opts) {
|
|
|
41
103
|
ms: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
42
104
|
verdict: verdict.kind,
|
|
43
105
|
...(verdict.kind === "unavailable" ? { cause: verdict.cause } : {}),
|
|
106
|
+
...(verdict.classifier !== undefined ? { classifier: verdict.classifier } : {}),
|
|
44
107
|
});
|
|
45
108
|
}
|
|
46
109
|
catch {
|
|
@@ -50,6 +113,37 @@ export function createAutoModeDecider(opts) {
|
|
|
50
113
|
};
|
|
51
114
|
return { decide };
|
|
52
115
|
async function decideUnreported(input, signal) {
|
|
116
|
+
const causes = [];
|
|
117
|
+
const round = { notAsked: false };
|
|
118
|
+
let verdict = { kind: "unavailable", cause: "error" };
|
|
119
|
+
let i = 0;
|
|
120
|
+
for (; i < ladder.length; i++) {
|
|
121
|
+
const name = ladder[i];
|
|
122
|
+
verdict = await askRung(input, signal, name !== undefined ? { index: i, model: name } : undefined, round);
|
|
123
|
+
const cause = rungDidNotRule(verdict);
|
|
124
|
+
if (cause === undefined)
|
|
125
|
+
break;
|
|
126
|
+
causes.push(cause);
|
|
127
|
+
if (round.notAsked || signal?.aborted)
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const landed = round.notAsked ? 0 : Math.min(i, ladder.length - 1);
|
|
131
|
+
const requested = ladder[0];
|
|
132
|
+
const used = ladder[landed];
|
|
133
|
+
if (requested === undefined || used === undefined)
|
|
134
|
+
return verdict;
|
|
135
|
+
const from = landed > 0 ? ladder[landed - 1] : undefined;
|
|
136
|
+
const movedBy = landed > 0 ? causes[landed - 1] : undefined;
|
|
137
|
+
return {
|
|
138
|
+
...verdict,
|
|
139
|
+
classifier: Object.freeze({
|
|
140
|
+
modelRequested: requested,
|
|
141
|
+
modelUsed: used,
|
|
142
|
+
...(from !== undefined && movedBy !== undefined ? { fallback: Object.freeze({ from, to: used, cause: movedBy }) } : {}),
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function askRung(input, signal, rung, round) {
|
|
53
147
|
let timer;
|
|
54
148
|
const inner = new AbortController();
|
|
55
149
|
const onOuterAbort = () => inner.abort();
|
|
@@ -69,7 +163,7 @@ export function createAutoModeDecider(opts) {
|
|
|
69
163
|
reject(new AutoModeTimeout());
|
|
70
164
|
}
|
|
71
165
|
}, timeoutMs);
|
|
72
|
-
opts.classify(input, inner.signal).then((v) => {
|
|
166
|
+
opts.classify(input, inner.signal, rung).then((v) => {
|
|
73
167
|
if (!settled) {
|
|
74
168
|
settled = true;
|
|
75
169
|
resolve(v);
|
|
@@ -84,9 +178,13 @@ export function createAutoModeDecider(opts) {
|
|
|
84
178
|
return parseAutoModeResponse(raw);
|
|
85
179
|
}
|
|
86
180
|
catch (e) {
|
|
181
|
+
if (isAutoModeRoundNotAsked(e)) {
|
|
182
|
+
round.notAsked = true;
|
|
183
|
+
return { kind: "unavailable", cause: "error" };
|
|
184
|
+
}
|
|
87
185
|
if (signal?.aborted)
|
|
88
186
|
return { kind: "unavailable", cause: "error" };
|
|
89
|
-
return { kind: "unavailable", cause: e
|
|
187
|
+
return { kind: "unavailable", cause: isAutoModeTimeout(e) ? "timeout" : "error" };
|
|
90
188
|
}
|
|
91
189
|
finally {
|
|
92
190
|
if (timer !== undefined)
|
|
@@ -101,6 +199,14 @@ class AutoModeTimeout extends Error {
|
|
|
101
199
|
super("auto-mode classify timeout");
|
|
102
200
|
}
|
|
103
201
|
}
|
|
202
|
+
function isAutoModeTimeout(e) {
|
|
203
|
+
try {
|
|
204
|
+
return e instanceof AutoModeTimeout;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
104
210
|
export function readDenialLimitFallback(v) {
|
|
105
211
|
if (typeof v !== "object" || v === null)
|
|
106
212
|
return undefined;
|
|
@@ -969,6 +969,16 @@ export interface CheckpointState {
|
|
|
969
969
|
* scope for any in-row invariant). Absent = the row was minted ungoverned. */
|
|
970
970
|
realApproval?: true;
|
|
971
971
|
};
|
|
972
|
+
/**
|
|
973
|
+
* The WRITE-face containment state at suspend: the RESOLVED write face, written ONLY when it is
|
|
974
|
+
* non-default ("open"). An absent section IS "roots" for every reader — which is why it carries NO
|
|
975
|
+
* version stamp: losing it can only make a resume STRICTER. Resume folds STRICTER-WINS, the read seat's
|
|
976
|
+
* law: roots unless the section says open AND the live resolution says open. No deny half and no
|
|
977
|
+
* governance marker.
|
|
978
|
+
*/
|
|
979
|
+
writeFace?: {
|
|
980
|
+
face: "open" | "roots";
|
|
981
|
+
};
|
|
972
982
|
/**
|
|
973
983
|
* design/319 (A ticket) — the session's reminder provenance mark
|
|
974
984
|
* ({@link import("./reminder-mint.js").mintReminderMark}), persisted so a durable resume keeps
|
|
@@ -1961,10 +1971,14 @@ export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
|
|
|
1961
1971
|
* parks a steer in the retired pre-queue single seat — terminal for the row's bytes; no worker version
|
|
1962
1972
|
* reads that seat, the host decides the parked instruction itself).
|
|
1963
1973
|
* · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`, `"real_approval_forged"`,
|
|
1964
|
-
* `"read_face_damaged"`, `"read_face_forged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
1974
|
+
* `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
1965
1975
|
* current bytes — no worker version redeems a damaged row).
|
|
1966
1976
|
*/
|
|
1967
|
-
export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "governed_unwired" | "origin_missing" | "ledger_event_unknown" | "seat_retired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged"
|
|
1977
|
+
export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "governed_unwired" | "origin_missing" | "ledger_event_unknown" | "seat_retired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged"
|
|
1978
|
+
/** `state.writeFace` is present but not a well-formed section. No `_forged` twin: the section is
|
|
1979
|
+
* legal-and-absent at every version, so there is no version to judge a fabrication against — and a
|
|
1980
|
+
* fabricated "open" still needs the LIVE resolution to say open too. */
|
|
1981
|
+
| "write_face_damaged" | "constraint_chain_missing";
|
|
1968
1982
|
/** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
|
|
1969
1983
|
export declare class CheckpointError extends Error {
|
|
1970
1984
|
readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
|