@sema-agent/core 7.19.0 → 7.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +199 -2
- package/dist/agents/subagent.js +12 -20
- package/dist/agents/teacher.js +2 -5
- package/dist/agents/verify.js +21 -10
- package/dist/brain/anthropic.js +3 -1
- package/dist/brain/open-responses.js +4 -2
- package/dist/brain/openai.js +3 -1
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +4 -0
- package/dist/core/auto-mode.d.ts +37 -18
- package/dist/core/auto-mode.js +1 -1
- package/dist/core/child-clamp-seats.d.ts +79 -0
- package/dist/core/child-clamp-seats.js +29 -0
- package/dist/core/engine-notice.d.ts +76 -49
- package/dist/core/fs-write-gate-policy.d.ts +17 -9
- package/dist/core/fs-write-gate-policy.js +51 -4
- package/dist/core/gate-bounded-await.d.ts +38 -0
- package/dist/core/gate-bounded-await.js +53 -0
- package/dist/core/gate-fold.js +16 -48
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +9 -6
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +8 -8
- package/dist/core/memory-engine/engine.d.ts +14 -9
- package/dist/core/memory-engine/engine.js +21 -8
- package/dist/core/memory-engine/layout.d.ts +31 -0
- package/dist/core/memory-engine/layout.js +68 -3
- package/dist/core/memory-engine/tools.js +6 -0
- package/dist/core/memory-engine/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
- package/dist/core/runner/advertised-writable-dirs.js +51 -3
- package/dist/core/runner/announce-once-ledger.d.ts +15 -0
- package/dist/core/runner/announce-once-ledger.js +8 -0
- package/dist/core/runner/classifier-ladder.d.ts +5 -0
- package/dist/core/runner/classifier-ladder.js +13 -2
- package/dist/core/runner/contracts.d.ts +44 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +5 -4
- package/dist/core/runner/env-capability-census.d.ts +30 -0
- package/dist/core/runner/env-capability-census.js +38 -0
- package/dist/core/runner/forward-sink.d.ts +32 -0
- package/dist/core/runner/forward-sink.js +41 -0
- package/dist/core/runner/gate-exit.d.ts +4 -0
- package/dist/core/runner/gate-exit.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +0 -9
- package/dist/core/runner/listing-fold.d.ts +57 -0
- package/dist/core/runner/listing-fold.js +75 -0
- package/dist/core/runner/prepare-execution-env.d.ts +1 -1
- package/dist/core/runner/prepare-execution-env.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +3 -5
- package/dist/core/runner/prepare-listings.d.ts +3 -0
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory.js +7 -1
- package/dist/core/runner/prepare-policy-chain.js +5 -5
- package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
- package/dist/core/runner/prepare-run-refs.d.ts +4 -2
- package/dist/core/runner/prepare-run-refs.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -1
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/run-clock-and-content.js +3 -7
- package/dist/core/runner/run-harness-handlers.js +10 -10
- package/dist/core/runner/run-leg.js +28 -19
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -33
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +13 -39
- package/dist/core/runner/synthetic-tools.js +15 -59
- package/dist/core/runner/task-root-canonical.d.ts +45 -0
- package/dist/core/runner/task-root-canonical.js +18 -0
- package/dist/core/runner/turn-attachments.d.ts +125 -49
- package/dist/core/runner/turn-attachments.js +124 -66
- package/dist/core/runner/write-protection-station.d.ts +59 -0
- package/dist/core/runner/write-protection-station.js +32 -0
- package/dist/core/safe-notify.d.ts +5 -0
- package/dist/core/safe-notify.js +24 -2
- package/dist/core/sensitive-path-policy.d.ts +53 -27
- package/dist/core/sensitive-path-policy.js +16 -26
- package/dist/core/skill-listing-budget.d.ts +34 -40
- package/dist/core/skill-listing-budget.js +25 -26
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +8 -7
- package/dist/core/tool-policy.d.ts +12 -1
- package/dist/core/tool-policy.js +39 -25
- package/dist/core/wiring-manifest.d.ts +31 -0
- package/dist/core/wiring-manifest.js +1 -0
- package/dist/core/write-protect.d.ts +124 -18
- package/dist/core/write-protect.js +91 -34
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/orchestration/workflow-primitives.js +10 -20
- package/dist/stores/file/memory-store.d.ts +5 -2
- package/dist/stores/file/memory-store.js +8 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
- package/dist/tools/fs/bash-readonly-classifier.js +318 -36
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +12 -25
- package/dist/tools/fs/fs-shared.d.ts +22 -0
- package/dist/tools/fs/fs-shared.js +30 -0
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/index.js +1 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +25 -3
- package/wire-changes/core@7.20.0.json +568 -0
- package/wire-changes/core@7.20.1.json +183 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
|
+
import { protectivePathTargetOf } from "../tool-registry.js";
|
|
3
|
+
import { createWriteProtectionCheck } from "../write-protect.js";
|
|
4
|
+
export function mountWriteProtection(input) {
|
|
5
|
+
const mount = createWriteProtectionCheck(input.deps.writeProtectedPaths, {
|
|
6
|
+
...(input.deps.memoryEngineDir !== undefined ? { dataRoot: input.deps.memoryEngineDir } : {}),
|
|
7
|
+
...(input.env !== undefined ? { env: input.env } : {}),
|
|
8
|
+
rootPath: input.rootPath,
|
|
9
|
+
});
|
|
10
|
+
if (mount === undefined || mount.targetView !== "spelling-only")
|
|
11
|
+
return mount;
|
|
12
|
+
const covered = input.roster.entries
|
|
13
|
+
.filter((e) => [e.name, ...e.aliases].some((name) => {
|
|
14
|
+
const target = protectivePathTargetOf({ toolName: name, args: {}, face: e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {} });
|
|
15
|
+
return target !== undefined && target.access !== "read";
|
|
16
|
+
}))
|
|
17
|
+
.map((e) => e.name)
|
|
18
|
+
.sort();
|
|
19
|
+
if (covered.length === 0)
|
|
20
|
+
return mount;
|
|
21
|
+
deliverEngineNotice(input.deps.onNotice, {
|
|
22
|
+
code: "config.write_protection_target_view_absent",
|
|
23
|
+
message: `This run mounted path-confinable write tools [${covered.join(", ")}] while its write-protection judge has NO execution environment, ` +
|
|
24
|
+
`so the judge compares path SPELLINGS against its table and nothing else. A write whose spelling misses the table but whose real target ` +
|
|
25
|
+
`resolves onto a protected path — through a symlink, a bind mount or a ".." walk — is cleared, and cleared silently: the resolved-target ` +
|
|
26
|
+
`half of the judgment needs a filesystem to resolve in. Nothing about this run changed. Wire an execution environment (RunnerDeps.executionEnv) ` +
|
|
27
|
+
`to get the target view back, or accept the spelling-only floor knowingly — the leg's wiring manifest reports which one is in force at ` +
|
|
28
|
+
`writeProtection.targetView.`,
|
|
29
|
+
detail: { tools: covered, sessionId: input.sessionId, runId: input.runId },
|
|
30
|
+
});
|
|
31
|
+
return mount;
|
|
32
|
+
}
|
|
@@ -56,6 +56,11 @@ export interface SafeNotifierOptions {
|
|
|
56
56
|
*/
|
|
57
57
|
readonly onError?: (failure: SafeNotifyFailure) => void;
|
|
58
58
|
}
|
|
59
|
+
/** The sentence of a contained failure, for a consumer that puts it in front of a person. Total by the
|
|
60
|
+
* same rule as {@link containedError}: an error object handed in from elsewhere (a `lastErrorAt`
|
|
61
|
+
* reading, a rejection an adapter wrapped) may still carry a hostile `message`, and the disclosure a
|
|
62
|
+
* site composes from it must not be the thing that faults. */
|
|
63
|
+
export declare function safeErrorMessage(error: unknown): string;
|
|
59
64
|
export interface SafeNotifier {
|
|
60
65
|
/**
|
|
61
66
|
* Run one host-callback invocation, containing any throw. Returns `true` when it completed, `false` when it
|
package/dist/core/safe-notify.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
function containedError(thrown) {
|
|
2
|
+
try {
|
|
3
|
+
if (thrown instanceof Error && typeof thrown.message === "string")
|
|
4
|
+
return thrown;
|
|
5
|
+
return new Error(String(thrown));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return new Error(`a thrown value of type ${typeof thrown} that cannot be inspected or converted to text`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function safeErrorMessage(error) {
|
|
12
|
+
try {
|
|
13
|
+
const m = error?.message;
|
|
14
|
+
if (typeof m === "string")
|
|
15
|
+
return m;
|
|
16
|
+
return String(error);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return "a failure whose message could not be read";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
1
22
|
export function createSafeNotifier(opts) {
|
|
2
23
|
const onError = opts?.onError;
|
|
3
24
|
let sites;
|
|
4
25
|
let total = 0;
|
|
5
26
|
const record = (site, thrown) => {
|
|
6
|
-
const error =
|
|
27
|
+
const error = containedError(thrown);
|
|
7
28
|
total += 1;
|
|
8
29
|
if (sites === undefined)
|
|
9
30
|
sites = new Map();
|
|
@@ -60,8 +81,9 @@ export function createSafeNotifier(opts) {
|
|
|
60
81
|
export function observeThenableRejection(r, notifier, site) {
|
|
61
82
|
if (typeof r?.then === "function") {
|
|
62
83
|
r.then(undefined, (err) => {
|
|
84
|
+
const error = containedError(err);
|
|
63
85
|
notifier.notify(() => {
|
|
64
|
-
throw
|
|
86
|
+
throw error;
|
|
65
87
|
}, site);
|
|
66
88
|
});
|
|
67
89
|
}
|
|
@@ -54,20 +54,13 @@ export declare function agentConfigDenyPatterns(): readonly string[];
|
|
|
54
54
|
* hard refusal for every deployment that wires this list.
|
|
55
55
|
*
|
|
56
56
|
* SEVERITY LAYERING for the agent-configuration class: the law is {@link agentConfigDenyPatterns} —
|
|
57
|
-
* configuration FILES here, the DIRECTORY one step down on
|
|
58
|
-
* (
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* configuration FILES here, the DIRECTORY one step down on the always-on write-protection table
|
|
58
|
+
* (`WRITE_PROTECTED_DEFAULT_TABLE`'s `.claude` / `.sema` rows — the same reach, since that judge sees the
|
|
59
|
+
* real target too, {@link import("./write-protect.js").WriteProtectionMatcher.judge}); a directory also
|
|
60
|
+
* holds the person's own material, and a directory-wide DENY entry turned an explicit instruction to edit
|
|
61
|
+
* `.claude/projects/…` into a refusal nobody could lift (a deny outranks an ask under the fold).
|
|
61
62
|
*/
|
|
62
63
|
export declare const RECOMMENDED_SENSITIVE_PATTERNS: readonly string[];
|
|
63
|
-
/**
|
|
64
|
-
* The ASK-severity half of the same guard: the agent-configuration DIRECTORIES, on the SAME judge
|
|
65
|
-
* ({@link createSensitivePathPolicy}'s `askPatterns` seat, whose ABSENCE reads as this list) — only the
|
|
66
|
-
* directories, since the configuration FILES inside them are deny material above. It lives HERE and not
|
|
67
|
-
* only on `WRITE_PROTECTED_DEFAULT_TABLE`, which names the same segments but judges LEXICALLY: a
|
|
68
|
-
* symlink with an innocent name walks past that one, so demoting to it alone trades severity for REACH.
|
|
69
|
-
*/
|
|
70
|
-
export declare const RECOMMENDED_SENSITIVE_ASK_PATTERNS: readonly string[];
|
|
71
64
|
/**
|
|
72
65
|
* HRD-PRM-10 — the engine data root, read from the same knob its two consumers read
|
|
73
66
|
* (`$AGENT_DATA_DIR ?? ~/.ai-agent`, off the HOST at wiring time, NFC-normalized) and returned in
|
|
@@ -85,16 +78,53 @@ export declare const RECOMMENDED_SENSITIVE_ASK_PATTERNS: readonly string[];
|
|
|
85
78
|
*/
|
|
86
79
|
export declare function dataRootSpellings(explicit: string | undefined): string[];
|
|
87
80
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
81
|
+
* The data root's identity in the SAME namespace a judged key lives in: every declared spelling
|
|
82
|
+
* ({@link dataRootSpellings}) run through the SAME `canonicalizeTarget` that mints the target key. Not a
|
|
83
|
+
* second canonicalization — the same one, on the other operand, which is what lets
|
|
84
|
+
* {@link relativeUnderRoot}'s comparison be exact.
|
|
85
|
+
*
|
|
86
|
+
* Resolved PER CALL, deliberately not cached. A cached canonical root is a snapshot of the symlink
|
|
87
|
+
* topology at first use: with a data root reached through a symlink, retargeting it left the policy
|
|
88
|
+
* still exempting the OLD target for the life of the run. The module header already accepts paying
|
|
89
|
+
* `canonicalizeTarget` twice for a guarded write as the price of a decoupled gate; this is the same
|
|
90
|
+
* bargain on the other operand, and a request-scoped cache is the same note's answer if profiling
|
|
91
|
+
* ever asks for one. A spelling that cannot be resolved contributes NO exemption (the guard's side wins).
|
|
92
|
+
*
|
|
93
|
+
* ONE resolver for both write judges (the deny set here, the always-on table's target view) — an
|
|
94
|
+
* engine-internal seam, like the two neighbours above.
|
|
95
|
+
*/
|
|
96
|
+
export declare function canonicalDataRootKeys(env: ExecutionEnv, spellings: readonly string[], signal?: AbortSignal): Promise<string[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Is `targetKey` inside the data root `rootKey`, and if so what is its path relative to it? Returns
|
|
99
|
+
* the relative sub-path (`""` when the target IS the root), or `null` for everything else.
|
|
100
|
+
*
|
|
101
|
+
* 🔴 EXACT, and deliberately nothing else. Both operands are minted by the SAME canonicalizer, so
|
|
102
|
+
* every transformation this comparison could apply — a case fold, an NFC pass, splitting a foreign
|
|
103
|
+
* separator family, unioning several readings of the root — can only ADD matches. On the DENY side of
|
|
104
|
+
* this file that is the safe direction and several of those transforms are applied on purpose (a
|
|
105
|
+
* spelling filter is not a path filter). Here the direction inverts: this test grants an EXEMPTION, so
|
|
106
|
+
* an added match is not a wider guard, it is a bypass of one — each of case folding, an NFC pass and a
|
|
107
|
+
* foreign-family read was measured leaking an unrelated sibling into the exemption (their pins name the
|
|
108
|
+
* shapes). The rule: compare what the canonicalizer produced, byte for byte, every uncertainty falling
|
|
109
|
+
* to the deny side.
|
|
110
|
+
*
|
|
111
|
+
* The separator family comes from the ROOT key (a backslash is an ordinary filename character on
|
|
112
|
+
* POSIX, so a root `/x/.sema/data` must not swallow the sibling FILE `/x/.sema/data\evil`), and a root
|
|
113
|
+
* that reduces to nothing exempts nothing rather than exempting the filesystem.
|
|
92
114
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
* Exported as an ENGINE-INTERNAL seam (not re-exported from `src/index.ts`), like {@link dataRootSpellings}:
|
|
116
|
+
* the always-on write-protection judge applies the same exemption on its target view, and a second
|
|
117
|
+
* containment test would be a second place for the two judges to disagree about what "inside" means.
|
|
118
|
+
*/
|
|
119
|
+
export declare function relativeUnderRoot(targetKey: string, rootKey: string): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* Build a composable policy that DENIES writes (via the guarded fs tools) whose CANONICAL target lands on
|
|
122
|
+
* a guarded pattern. `patterns` is deployment-supplied — no default list is shipped; empty = a no-op allow.
|
|
123
|
+
* A deny is a refusal no approver and no persisted allow rule can lift. The ASK severity of the same class
|
|
124
|
+
* (the agent-configuration DIRECTORIES) is not a tier here: it is the always-on write-protection table's,
|
|
125
|
+
* which judges the same real target ({@link import("./write-protect.js").WriteProtectionMatcher.judge} —
|
|
126
|
+
* its contract sentence `write_protection.judges_the_real_target` says the two differ in vocabulary and
|
|
127
|
+
* severity, never in reach).
|
|
98
128
|
*
|
|
99
129
|
* `env` is the same ExecutionEnv the hands run against (inject the one you pass to the Runner), so
|
|
100
130
|
* canonicalization sees the task's real filesystem (a remote/E2B task resolves in its container).
|
|
@@ -104,17 +134,13 @@ export declare function dataRootSpellings(explicit: string | undefined): string[
|
|
|
104
134
|
*
|
|
105
135
|
* Fail-open by design: a tool with no string `path` returns `allow` here — this guard adds friction on
|
|
106
136
|
* top of the other gates, it is not the only line of defense, and it must not turn an unrelated tool
|
|
107
|
-
* call into a spurious verdict. The
|
|
108
|
-
* guarded path → deny
|
|
137
|
+
* call into a spurious verdict. The verdict it owns is narrow and certain: "the real target IS a
|
|
138
|
+
* guarded path → deny". (A target it could not RESOLVE is the one fail-CLOSED arm, below.)
|
|
109
139
|
*/
|
|
110
140
|
export declare function createSensitivePathPolicy(opts: {
|
|
111
141
|
env: ExecutionEnv;
|
|
112
142
|
/** `readonly` accepted so {@link RECOMMENDED_SENSITIVE_PATTERNS} can be passed as-is (widening, non-breaking). */
|
|
113
143
|
patterns: readonly string[];
|
|
114
|
-
/** The ASK-severity set, same syntax and same judge as `patterns`, consulted only where the deny set
|
|
115
|
-
* misses. ABSENT ⇒ {@link RECOMMENDED_SENSITIVE_ASK_PATTERNS}, so a deployment already wiring this
|
|
116
|
-
* guard keeps its reach over those directories without a code change; `[]` = no ask tier. */
|
|
117
|
-
askPatterns?: readonly string[];
|
|
118
144
|
rootPath?: string;
|
|
119
145
|
/**
|
|
120
146
|
* HRD-PRM-10 — the engine data root this deployment runs on. Default
|
|
@@ -50,7 +50,6 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
50
50
|
".bash_history",
|
|
51
51
|
".zsh_history",
|
|
52
52
|
];
|
|
53
|
-
export const RECOMMENDED_SENSITIVE_ASK_PATTERNS = [".claude", ".sema"];
|
|
54
53
|
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
55
54
|
export function dataRootSpellings(explicit) {
|
|
56
55
|
const raw = explicit ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
@@ -65,7 +64,20 @@ export function dataRootSpellings(explicit) {
|
|
|
65
64
|
}
|
|
66
65
|
return out;
|
|
67
66
|
}
|
|
68
|
-
function
|
|
67
|
+
export async function canonicalDataRootKeys(env, spellings, signal) {
|
|
68
|
+
const keys = [];
|
|
69
|
+
for (const spelling of spellings) {
|
|
70
|
+
try {
|
|
71
|
+
const r = await canonicalizeTarget(env, spelling, signal, pathFamilyOf({ root: spelling }));
|
|
72
|
+
if (r.ok && !keys.includes(r.key))
|
|
73
|
+
keys.push(r.key);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return keys;
|
|
79
|
+
}
|
|
80
|
+
export function relativeUnderRoot(targetKey, rootKey) {
|
|
69
81
|
const seps = isWinFormPath(rootKey) ? ["\\", "/"] : ["/"];
|
|
70
82
|
let root = rootKey;
|
|
71
83
|
while (root.length > 0 && seps.includes(root[root.length - 1]))
|
|
@@ -96,25 +108,11 @@ function matchSensitive(canonicalKey, compiled, aliasResolved) {
|
|
|
96
108
|
}
|
|
97
109
|
export function createSensitivePathPolicy(opts) {
|
|
98
110
|
const compiled = compilePatterns(opts.patterns);
|
|
99
|
-
const compiledAsk = compilePatterns(opts.askPatterns ?? RECOMMENDED_SENSITIVE_ASK_PATTERNS);
|
|
100
111
|
const dataRootPaths = dataRootSpellings(opts.dataRoot);
|
|
101
|
-
const dataRootKeys = async (signal) => {
|
|
102
|
-
const keys = [];
|
|
103
|
-
for (const spelling of dataRootPaths) {
|
|
104
|
-
try {
|
|
105
|
-
const r = await canonicalizeTarget(opts.env, spelling, signal, pathFamilyOf({ root: spelling }));
|
|
106
|
-
if (r.ok && !keys.includes(r.key))
|
|
107
|
-
keys.push(r.key);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return keys;
|
|
113
|
-
};
|
|
114
112
|
const guardedByName = opts.tools !== undefined ? new Set(opts.tools) : undefined;
|
|
115
113
|
return {
|
|
116
114
|
async check(req, signal) {
|
|
117
|
-
if (compiled.length === 0
|
|
115
|
+
if (compiled.length === 0)
|
|
118
116
|
return { action: "allow" };
|
|
119
117
|
if (guardedByName !== undefined ? !guardedByName.has(req.toolName) : !isProtectedWrite(req))
|
|
120
118
|
return { action: "allow" };
|
|
@@ -133,7 +131,7 @@ export function createSensitivePathPolicy(opts) {
|
|
|
133
131
|
};
|
|
134
132
|
}
|
|
135
133
|
let relative = null;
|
|
136
|
-
for (const rootKey of await
|
|
134
|
+
for (const rootKey of await canonicalDataRootKeys(opts.env, dataRootPaths, signal)) {
|
|
137
135
|
relative = relativeUnderRoot(canon.key, rootKey);
|
|
138
136
|
if (relative !== null)
|
|
139
137
|
break;
|
|
@@ -149,14 +147,6 @@ export function createSensitivePathPolicy(opts) {
|
|
|
149
147
|
decisionReason: "safety",
|
|
150
148
|
};
|
|
151
149
|
}
|
|
152
|
-
const askHit = matchSensitive(judged, compiledAsk, canon.aliasResolved === true);
|
|
153
|
-
if (askHit) {
|
|
154
|
-
return {
|
|
155
|
-
action: "ask",
|
|
156
|
-
message: `this write's real target resolves inside the guarded directory "${askHit}" — explicit approval required`,
|
|
157
|
-
decisionReason: "safety",
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
150
|
return { action: "allow" };
|
|
161
151
|
},
|
|
162
152
|
};
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* budget costs a skill its description. The reader is pure; the announcer's only effect is the notice.
|
|
6
6
|
* `runner/synthetic-tools.ts` owns the renderer that consumes the numbers.
|
|
7
7
|
*
|
|
8
|
+
* The listing frame is an INTACT-class turn attachment (`runner/turn-attachments.ts`
|
|
9
|
+
* `ATTACHMENT_DELIVERY_CLASS`): the delivery lane never clips it, so the number this reader produces is
|
|
10
|
+
* the number the frame is rendered to — there is no lane ceiling above it.
|
|
11
|
+
*
|
|
8
12
|
* Sibling of `tool-search-setting.ts` in shape and in posture: a value outside its domain REFUSES
|
|
9
13
|
* loudly rather than folding back to the default, because a knob that is silently dropped reads to the
|
|
10
14
|
* deployment as a knob that took effect.
|
|
@@ -28,50 +32,37 @@ export declare const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200000;
|
|
|
28
32
|
* default deployment's listing, removed by an estimator's safety margin.
|
|
29
33
|
*/
|
|
30
34
|
export declare const SKILL_LISTING_BYTES_PER_TOKEN = 4;
|
|
31
|
-
/**
|
|
32
|
-
* The hard ceiling on the rendered `<skills>` block, in UTF-8 bytes.
|
|
33
|
-
*
|
|
34
|
-
* @contract skills.listing_budget_ceiling — this is a STRUCTURAL property of the delivery lane, not a
|
|
35
|
-
* policy choice and not a superset optimization. The block is not part of the system prompt: it is the
|
|
36
|
-
* body of the `skills_listing` turn attachment, and every attachment bundle is capped at
|
|
37
|
-
* `ATTACHMENT_BYTE_CAP` (8 KiB) by the boundary's `capAttachments`, which CLIPS anything over. A
|
|
38
|
-
* clipped skills frame never satisfies the run loop's intact-survival commit predicate, so the
|
|
39
|
-
* announced set never advances and the frame is re-rendered and re-clipped at every subsequent
|
|
40
|
-
* boundary — permanently. The cap is therefore enforced byte-exact over the WHOLE render (fences,
|
|
41
|
-
* headers, markers, joiners included) and MUST stay ≤ `ATTACHMENT_BYTE_CAP`; the renderer's module
|
|
42
|
-
* pins that relation, since importing the attachment module here would invert the dependency.
|
|
43
|
-
*
|
|
44
|
-
* The consequence is a one-way knob: {@link resolveSkillListingBudget} clamps the declared budget to
|
|
45
|
-
* this number, so a raised fraction (or a model with a very large window) can LOWER the listing budget
|
|
46
|
-
* but can never raise it above this ceiling. When the clamp costs a description, both numbers ride the
|
|
47
|
-
* disclosure ({@link announceSkillListingTruncation}), so the deployment reads what it asked for beside
|
|
48
|
-
* what it got instead of inferring the ceiling from a short listing.
|
|
49
|
-
*/
|
|
50
|
-
export declare const SKILLS_BLOCK_MAX_BYTES = 8000;
|
|
51
35
|
/** The typed refusal a budget seat outside its domain raises: a bad value never selects a smaller
|
|
52
36
|
* listing silently, and never folds back to the default budget. */
|
|
53
37
|
export declare const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
|
|
54
38
|
/** The notice code minted when the budget cost at least one skill its description or its line. */
|
|
55
39
|
export declare const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
|
|
56
|
-
/** The
|
|
40
|
+
/** The agent-type listing's twin: minted when the budget cost at least one agent type its detail or its line. */
|
|
41
|
+
export declare const AGENT_LISTING_TRUNCATED_CODE = "agents.listing_truncated";
|
|
42
|
+
/** Which listing frame a degradation report describes. */
|
|
43
|
+
export type ListingKind = "skills" | "agents";
|
|
44
|
+
/** The lowered seat: the two numbers the renderer measures against. */
|
|
57
45
|
export interface ResolvedSkillListingBudget {
|
|
58
|
-
/** The budget the renderer enforces over the whole block,
|
|
59
|
-
*
|
|
46
|
+
/** The budget the renderer enforces over the whole block (fences, headers, markers, joiners
|
|
47
|
+
* included), in UTF-8 bytes. */
|
|
60
48
|
blockMaxBytes: number;
|
|
61
|
-
/** What the declared fraction asked for BEFORE the structural clamp. Equal to `blockMaxBytes`
|
|
62
|
-
* whenever the ceiling did not bind; strictly greater when it did. */
|
|
63
|
-
requestedBytes: number;
|
|
64
49
|
/** Per-skill description cap in code units, ellipsis included. */
|
|
65
50
|
descMaxChars: number;
|
|
66
51
|
}
|
|
67
52
|
/**
|
|
68
53
|
* The ONE reader of the two listing-budget seats.
|
|
69
54
|
*
|
|
55
|
+
* @contract skills.listing_budget_enforced_as_declared — the resolved `blockMaxBytes` is
|
|
56
|
+
* `floor(window × 4 × fraction)` with no ceiling above it: the `<skills>` frame is an intact-class
|
|
57
|
+
* attachment the delivery lane never clips, so what the deployment declares is what the renderer
|
|
58
|
+
* folds the listing into, on the initial block and on every drift frame alike. The fraction's own
|
|
59
|
+
* domain `(0, 1]` is the only upper bound (a whole window at most), and a value outside it refuses.
|
|
60
|
+
*
|
|
70
61
|
* `skillListingBudgetFraction` must be a finite number in `(0, 1]` — zero is excluded deliberately: a
|
|
71
62
|
* zero budget is not "spend nothing", it is a listing every skill loses its description to, which the
|
|
72
63
|
* deployment expresses by declaring fewer skills. `skillListingMaxDescChars` must be a positive safe
|
|
73
64
|
* integer. Both absent ⇒ the documented defaults, which reproduce the pre-seat render byte for byte
|
|
74
|
-
* (`200 000 × 4 × 0.01` = 8 000
|
|
65
|
+
* (`200 000 × 4 × 0.01` = 8 000 bytes).
|
|
75
66
|
*
|
|
76
67
|
* The window is `contextTokens ?? contextWindow`, the same physical coordinate the deferral gate
|
|
77
68
|
* measures against. A window that is absent OR unusable (non-finite, non-positive) reads as
|
|
@@ -87,23 +78,26 @@ export declare function resolveSkillListingBudget(opts: {
|
|
|
87
78
|
contextWindow?: number;
|
|
88
79
|
} | undefined;
|
|
89
80
|
}): ResolvedSkillListingBudget;
|
|
81
|
+
/** The reader's answer to "no knobs declared, no window declared" — the budget a renderer measures
|
|
82
|
+
* against when its caller hands it none. One rule: only the reader turns knobs into numbers. */
|
|
83
|
+
export declare const SKILL_LISTING_DEFAULT_BUDGET: Readonly<ResolvedSkillListingBudget>;
|
|
90
84
|
/**
|
|
91
|
-
* Announce a listing the budget degraded — delivered only when
|
|
92
|
-
* (`nameOnly`) or its line (`dropped`), so a deployment whose roster fits hears nothing.
|
|
85
|
+
* Announce a listing frame the budget degraded — delivered only when an entry actually lost its detail
|
|
86
|
+
* (`nameOnly`) or its line (`dropped`), so a deployment whose roster fits hears nothing. One announcer
|
|
87
|
+
* for both listing frames; the code names the frame (`skills.listing_truncated` / `agents.listing_truncated`).
|
|
93
88
|
*
|
|
94
|
-
* Called from the
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
89
|
+
* Called from the commit of the frame's receipt — the moment the carrying message is in the transcript —
|
|
90
|
+
* never from the config door: the door cannot see the deployment's listing switches (resolved at run
|
|
91
|
+
* start), it runs per prepared task rather than per delivered listing, and a resume leg reaches only
|
|
92
|
+
* the boundary.
|
|
98
93
|
*
|
|
99
|
-
* Audience is `operator`: the person who can raise the fraction, shorten a description or remove
|
|
100
|
-
*
|
|
101
|
-
* precisely so the structural ceiling ({@link SKILLS_BLOCK_MAX_BYTES}) is legible at the moment it
|
|
102
|
-
* costs something — the two differ only when the clamp bound.
|
|
94
|
+
* Audience is `operator`: the person who can raise the fraction, shorten a description or remove an
|
|
95
|
+
* entry runs the deployment, not the session.
|
|
103
96
|
*/
|
|
104
|
-
export declare function
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
export declare function announceListingTruncation(onNotice: ((notice: EngineNotice) => void) | undefined, opts: {
|
|
98
|
+
listing: ListingKind;
|
|
99
|
+
/** The denominator: how many entries the DELIVERED frame set out to list — the whole roster on an
|
|
100
|
+
* initial frame, the newly announced set on a drift frame. Read off that frame's own render. */
|
|
107
101
|
total: number;
|
|
108
102
|
nameOnly: readonly string[];
|
|
109
103
|
dropped: readonly string[];
|
|
@@ -4,9 +4,9 @@ export const SKILL_LISTING_BUDGET_FRACTION_DEFAULT = 0.01;
|
|
|
4
4
|
export const SKILL_LISTING_DESC_MAX_CHARS_DEFAULT = 1_536;
|
|
5
5
|
export const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200_000;
|
|
6
6
|
export const SKILL_LISTING_BYTES_PER_TOKEN = 4;
|
|
7
|
-
export const SKILLS_BLOCK_MAX_BYTES = 8_000;
|
|
8
7
|
export const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
|
|
9
8
|
export const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
|
|
9
|
+
export const AGENT_LISTING_TRUNCATED_CODE = "agents.listing_truncated";
|
|
10
10
|
function budgetRefusal(problem) {
|
|
11
11
|
const e = new Error(`TaskSpec.${problem}; a bad value refuses loudly, nothing falls back to the default listing budget.`);
|
|
12
12
|
e.code = SKILL_LISTING_BUDGET_INVALID_CODE;
|
|
@@ -24,35 +24,34 @@ export function resolveSkillListingBudget(opts) {
|
|
|
24
24
|
const windowTokens = typeof declaredWindow === "number" && Number.isFinite(declaredWindow) && declaredWindow > 0
|
|
25
25
|
? declaredWindow
|
|
26
26
|
: SKILL_LISTING_DEFAULT_CONTEXT_TOKENS;
|
|
27
|
-
const requestedBytes = Math.max(1, Math.floor(windowTokens * SKILL_LISTING_BYTES_PER_TOKEN * (fraction ?? SKILL_LISTING_BUDGET_FRACTION_DEFAULT)));
|
|
28
27
|
return {
|
|
29
|
-
blockMaxBytes: Math.
|
|
30
|
-
requestedBytes,
|
|
28
|
+
blockMaxBytes: Math.max(1, Math.floor(windowTokens * SKILL_LISTING_BYTES_PER_TOKEN * (fraction ?? SKILL_LISTING_BUDGET_FRACTION_DEFAULT))),
|
|
31
29
|
descMaxChars: maxDescChars ?? SKILL_LISTING_DESC_MAX_CHARS_DEFAULT,
|
|
32
30
|
};
|
|
33
31
|
}
|
|
34
|
-
export
|
|
35
|
-
|
|
32
|
+
export const SKILL_LISTING_DEFAULT_BUDGET = Object.freeze(resolveSkillListingBudget({}));
|
|
33
|
+
export function announceListingTruncation(onNotice, opts) {
|
|
34
|
+
const { listing, total, nameOnly, dropped, budget } = opts;
|
|
36
35
|
if (nameOnly.length === 0 && dropped.length === 0)
|
|
37
36
|
return;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
const skills = listing === "skills";
|
|
38
|
+
const message = `The ${skills ? "skills" : "agent-type"} listing does not fit its ${budget.blockMaxBytes}-byte budget` +
|
|
39
|
+
`: ${nameOnly.length} of ${total} are listed name-only` +
|
|
40
|
+
(dropped.length > 0 ? `, and ${dropped.length} did not fit the listing at all` : "") +
|
|
41
|
+
(skills
|
|
42
|
+
? `. The \`Skill\` tool still serves every one of them. Raise skillListingBudgetFraction, lower skillListingMaxDescChars, or declare fewer skills.`
|
|
43
|
+
: `. Raise skillListingBudgetFraction or declare fewer agent types.`);
|
|
44
|
+
const detail = {
|
|
45
|
+
budgetBytes: budget.blockMaxBytes,
|
|
46
|
+
...(skills ? { descMaxChars: budget.descMaxChars } : {}),
|
|
47
|
+
total,
|
|
48
|
+
kept: total - nameOnly.length - dropped.length,
|
|
49
|
+
nameOnly: [...nameOnly],
|
|
50
|
+
dropped: [...dropped],
|
|
51
|
+
...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
|
|
52
|
+
};
|
|
53
|
+
if (skills)
|
|
54
|
+
deliverEngineNotice(onNotice, { code: SKILL_LISTING_TRUNCATED_CODE, message, detail });
|
|
55
|
+
else
|
|
56
|
+
deliverEngineNotice(onNotice, { code: AGENT_LISTING_TRUNCATED_CODE, message, detail });
|
|
58
57
|
}
|
|
@@ -197,6 +197,19 @@ function listRelativeFiles(base, dir, prefix, out, walk) {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
function skillPhysicalRoot(skillDir, skillName, warn) {
|
|
201
|
+
try {
|
|
202
|
+
return physicalPathOf(skillDir);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
warn({
|
|
206
|
+
code: "read_failed",
|
|
207
|
+
skill: skillName,
|
|
208
|
+
detail: `the skill directory could not be resolved (${errText(err)}) — attachments skipped and no base directory is stated`,
|
|
209
|
+
});
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
200
213
|
function resolveInsideSkill(abs, rel, walk) {
|
|
201
214
|
let real;
|
|
202
215
|
let stats;
|
|
@@ -218,15 +231,7 @@ function resolveInsideSkill(abs, rel, walk) {
|
|
|
218
231
|
}
|
|
219
232
|
return { real, stats };
|
|
220
233
|
}
|
|
221
|
-
function readAttachments(skillDir, skillName, budgetBytes, warn) {
|
|
222
|
-
let realRoot;
|
|
223
|
-
try {
|
|
224
|
-
realRoot = physicalPathOf(skillDir);
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
warn({ code: "read_failed", skill: skillName, detail: `the skill directory could not be resolved (${errText(err)}) — attachments skipped` });
|
|
228
|
-
return [];
|
|
229
|
-
}
|
|
234
|
+
function readAttachments(skillDir, realRoot, skillName, budgetBytes, warn) {
|
|
230
235
|
const walk = { realRoot, skill: skillName, warn, visited: new Set([realRoot]) };
|
|
231
236
|
const relPaths = [];
|
|
232
237
|
for (const d of RESOURCE_DIRS) {
|
|
@@ -442,11 +447,13 @@ export function createSkillsFromDirectory(dir, options = {}) {
|
|
|
442
447
|
detail: `disallowed-tools names ${disallowed.map((n) => `"${n}"`).join(", ")}, but this skill produced no allowlist to subtract from (allowed-tools is absent or claims every tool) — the deny declaration is NOT enforced (express it in the task's own tool policy)`,
|
|
443
448
|
});
|
|
444
449
|
}
|
|
445
|
-
const
|
|
450
|
+
const baseDir = skillPhysicalRoot(skillDir, name, warn);
|
|
451
|
+
const files = baseDir === undefined ? [] : readAttachments(skillDir, baseDir, name, budget, warn);
|
|
446
452
|
skills.push({
|
|
447
453
|
name: declaredName,
|
|
448
454
|
description,
|
|
449
455
|
content: parsed.body,
|
|
456
|
+
...(baseDir !== undefined ? { baseDir } : {}),
|
|
450
457
|
...(manifest !== undefined ? { manifest } : {}),
|
|
451
458
|
...(files.length > 0 ? { files } : {}),
|
|
452
459
|
});
|
|
@@ -5,6 +5,7 @@ import { shutdownDebug } from "./shutdown-debug.js";
|
|
|
5
5
|
import { canAccessAgentRecord, } from "./background-agent-store.js";
|
|
6
6
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
7
7
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
8
|
+
import { stripCwdSentinel } from "../tools/fs/fs-shared.js";
|
|
8
9
|
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
9
10
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
10
11
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
@@ -46,7 +47,7 @@ function noTaskForStop(taskId, enrich) {
|
|
|
46
47
|
}
|
|
47
48
|
function bashTerminalResult(spool) {
|
|
48
49
|
const out = spool.stdout.slice(-2_000);
|
|
49
|
-
const err = spool.stderr.slice(-2_000);
|
|
50
|
+
const err = stripCwdSentinel(spool.stderr).stderr.slice(-2_000);
|
|
50
51
|
return err.length > 0 ? `${out}${out.length > 0 ? "\n" : ""}--- stderr (tail) ---\n${err}` : out;
|
|
51
52
|
}
|
|
52
53
|
async function drainBashTailBestEffort(handle) {
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -944,11 +944,12 @@ export interface TaskSpec {
|
|
|
944
944
|
* listing degrades inside it: every skill stays listed, the lower-priority ones (declaration order)
|
|
945
945
|
* lose their descriptions first. Raising this is opting in to a higher per-turn context cost.
|
|
946
946
|
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
* `
|
|
947
|
+
* Enforced as declared (`@contract skills.listing_budget_enforced_as_declared`): the listing frames are
|
|
948
|
+
* intact-class turn attachments the delivery lane never clips, so the resolved budget has no ceiling
|
|
949
|
+
* above it — a fraction that asks for more gets more (a 1 000 000-token window at the default 0.01 is
|
|
950
|
+
* 40 000 bytes). The same fraction is EACH listing attachment's budget: the agent-type roster frame
|
|
951
|
+
* folds into `window × 4 × fraction` too (every type stays listed, the last-declared lose their detail
|
|
952
|
+
* first; `agents.listing_truncated` reports it).
|
|
952
953
|
*
|
|
953
954
|
* ⚠️ A model declaring a window SMALLER than 200 000 tokens now gets a proportionally smaller listing
|
|
954
955
|
* budget than the flat 8 000 bytes that preceded this seat.
|
|
@@ -1251,8 +1252,8 @@ export interface TaskSpec {
|
|
|
1251
1252
|
* back to a transcript probe for older checkpoints — never replayed when already delivered.
|
|
1252
1253
|
* - `skillsListing` ([c209-C], DEFAULT-ON, mechanism-family sibling of `agentListing` — no CC
|
|
1253
1254
|
* delta anchor, registered in cc-divergences): the FIRST user turn carries the full `<skills>`
|
|
1254
|
-
* disclosure block (the exact render that used to sit in the stable system prompt —
|
|
1255
|
-
* byte-exact
|
|
1255
|
+
* disclosure block (the exact render that used to sit in the stable system prompt — folded
|
|
1256
|
+
* byte-exact into the listing budget, `skillListingBudgetFraction` × the window, with
|
|
1256
1257
|
* name-only overflow); boundaries announce only skill-set drift. Resume seeding mirrors
|
|
1257
1258
|
* `agentListing`.
|
|
1258
1259
|
* - `mcpInstructions` (G1 续批 — CC `mcp_instructions_delta` parity): the INITIAL server
|
|
@@ -147,7 +147,7 @@ export declare function advertisedWritableRootsSeat(seat: {
|
|
|
147
147
|
};
|
|
148
148
|
declare const DECISION_REASONS: readonly ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only"];
|
|
149
149
|
export type DecisionReason = (typeof DECISION_REASONS)[number];
|
|
150
|
-
/** Carry every engine attestation
|
|
150
|
+
/** Carry every engine attestation from a decision onto a reconstruction of it — the
|
|
151
151
|
* seam for the engine's OWN clone sites outside this module (the inherited chain's final-form re-check carries the
|
|
152
152
|
* post-rewrite args on a spread of an ancestor's deny). Not re-exported from `src/index.ts`. */
|
|
153
153
|
export declare function carryEngineAttestations(from: object, to: object): void;
|
|
@@ -168,6 +168,17 @@ export declare function classifierDenyCauseOf(d: unknown, call: {
|
|
|
168
168
|
toolCallId: string;
|
|
169
169
|
toolName: string;
|
|
170
170
|
}): import("./gate-outcome.js").ClassifierDenyCause | undefined;
|
|
171
|
+
/** Attest that `d` is a decision a classifier station minted for `call` out of the named classification. */
|
|
172
|
+
export declare function attestClassifierRound<T extends object>(d: T, round: import("./auto-mode.js").AutoModeClassifierRound, call: {
|
|
173
|
+
toolCallId: string;
|
|
174
|
+
toolName: string;
|
|
175
|
+
}): T;
|
|
176
|
+
/** Read the attested classification off a decision FOR the named call (the gate's fold is the consumer): a
|
|
177
|
+
* binding to a different call is a replayed object and answers absence. Internal seam — not re-exported. */
|
|
178
|
+
export declare function classifierRoundOf(d: unknown, call: {
|
|
179
|
+
toolCallId: string;
|
|
180
|
+
toolName: string;
|
|
181
|
+
}): import("./auto-mode.js").AutoModeClassifierRound | undefined;
|
|
171
182
|
/** Attest that `d` is an in-fold deny of an ask nobody could resolve, for `call`, for the named cause. */
|
|
172
183
|
export declare function attestAskUnresolvableCause<T extends object>(d: T, cause: import("./ask-unresolvable-notice.js").AskUnresolvableCause, call: {
|
|
173
184
|
toolCallId: string;
|