@sema-agent/core 7.35.2 → 7.37.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 +142 -0
- package/dist/brain/anthropic.js +6 -6
- package/dist/brain/errors.d.ts +1 -14
- package/dist/brain/errors.js +2 -6
- package/dist/brain/open-responses.js +7 -7
- package/dist/brain/openai.js +7 -7
- package/dist/brain/provider-echo-shape.d.ts +26 -0
- package/dist/brain/provider-echo-shape.js +12 -0
- package/dist/brain/tool-call-id.d.ts +27 -15
- package/dist/brain/tool-call-id.js +43 -0
- package/dist/core/ask-origin.d.ts +35 -5
- package/dist/core/ask-origin.js +23 -4
- package/dist/core/ask-question.d.ts +0 -30
- package/dist/core/ask-question.js +3 -2
- package/dist/core/checkpoint-store.d.ts +18 -6
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/context-edit.d.ts +4 -0
- package/dist/core/context-edit.js +5 -2
- package/dist/core/decision-receipt.d.ts +21 -0
- package/dist/core/decision-receipt.js +63 -0
- package/dist/core/engine-notice.d.ts +15 -5
- package/dist/core/gate-fold.js +2 -1
- package/dist/core/gate-lanes.js +20 -23
- package/dist/core/gate-outcome.d.ts +7 -5
- package/dist/core/gate-outcome.js +5 -1
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +14 -2
- package/dist/core/hooks.js +7 -3
- package/dist/core/mode-policy.js +2 -1
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/permission-rule-model.d.ts +2 -2
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rules.js +2 -5
- package/dist/core/runner/active-skill-scope.js +5 -5
- package/dist/core/runner/decide-continuation.js +2 -1
- package/dist/core/runner/denial-limit-arms.d.ts +28 -18
- package/dist/core/runner/denial-limit-arms.js +11 -7
- package/dist/core/runner/inherited-ask-grants.d.ts +32 -31
- package/dist/core/runner/inherited-ask-grants.js +5 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
- package/dist/core/runner/permission-rule-lanes.js +7 -6
- package/dist/core/runner/prepare-context-lane.d.ts +4 -0
- package/dist/core/runner/prepare-context-lane.js +7 -5
- package/dist/core/runner/prepare-gate-stations.js +17 -12
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-park-ask.d.ts +4 -2
- package/dist/core/runner/prepare-park-ask.js +8 -11
- package/dist/core/runner/prepare-policy-chain.d.ts +5 -8
- package/dist/core/runner/prepare-policy-chain.js +50 -31
- package/dist/core/runner/prepare-safety-scan.js +9 -2
- package/dist/core/runner/prepare-task.js +3 -3
- package/dist/core/runner/resume-apply.js +2 -1
- package/dist/core/runner/run-attachment-seats.js +3 -1
- package/dist/core/runner/run-harness-handlers.js +6 -1
- package/dist/core/runner/run-leg.js +11 -9
- package/dist/core/runner/run-recovery-lanes.js +2 -0
- package/dist/core/runner/session-rule-policy.js +5 -4
- package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
- package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
- package/dist/core/sensitive-path-policy.js +2 -2
- package/dist/core/side-query.d.ts +5 -1
- package/dist/core/side-query.js +5 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +18 -0
- package/dist/core/tool-name-readings.d.ts +370 -0
- package/dist/core/tool-name-readings.js +137 -0
- package/dist/core/tool-policy.d.ts +79 -89
- package/dist/core/tool-policy.js +86 -49
- package/dist/core/tool-registry.d.ts +1 -31
- package/dist/core/tool-registry.js +2 -22
- package/dist/core/tool-result-budget.d.ts +3 -0
- package/dist/core/tool-result-budget.js +5 -3
- package/dist/core/tool-roster.d.ts +30 -9
- package/dist/core/tool-roster.js +45 -5
- package/dist/engine/llm/types.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/file-read-check.d.ts +1 -1
- package/dist/tools/fs/file-read-check.js +4 -3
- package/dist/tools/fs/shell-read-check.d.ts +15 -1
- package/dist/tools/fs/shell-read-check.js +5 -2
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +7 -1
- package/wire-changes/core@7.36.0.json +288 -0
- package/wire-changes/core@7.37.0.json +405 -0
|
@@ -4,10 +4,10 @@ import { type Settlement } from "./gate-outcome.js";
|
|
|
4
4
|
/** A tool call presented to a policy before it executes. */
|
|
5
5
|
/** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
|
|
6
6
|
export interface ToolCallFace {
|
|
7
|
-
/** Every name a tool-name rule may spell this tool by: the
|
|
8
|
-
* it on the leg's roster (`rosterToolName`). A rule
|
|
9
|
-
*
|
|
10
|
-
* (the call's own name is then the list). */
|
|
7
|
+
/** Every name a tool-name rule may spell this tool by: the name the call was made under first (the model's spelling),
|
|
8
|
+
* then the tool's wire name and each other alias that resolves to it on the leg's roster (`rosterToolName`). A rule
|
|
9
|
+
* reaches the call iff its tool name is on this list — read it through `ruleNamesOf`, never off `toolName` alone.
|
|
10
|
+
* Absent on a face rebuilt from a park row that predates it (the call's own name is then the list). */
|
|
11
11
|
names?: readonly string[];
|
|
12
12
|
family: import("./tool-face.js").ToolFamily;
|
|
13
13
|
pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
|
|
@@ -18,7 +18,7 @@ export interface ToolCallFace {
|
|
|
18
18
|
}
|
|
19
19
|
export interface ToolCallRequest {
|
|
20
20
|
/** The tool's wire name — the Runner's gate resolves a call made under an alias to the wire name of the tool it
|
|
21
|
-
* dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are
|
|
21
|
+
* dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are `ruleNamesOf`. */
|
|
22
22
|
toolName: string;
|
|
23
23
|
args: unknown;
|
|
24
24
|
toolCallId: string;
|
|
@@ -262,18 +262,9 @@ export declare function screenApproverAttribution(v: unknown): {
|
|
|
262
262
|
* or `approver` member on its verdict is putting an unread property on it.
|
|
263
263
|
* RB-479-B① (ruled 2026-08-02): the legacy `reason` field is REMOVED — one name for one thing.
|
|
264
264
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* = {…}`, or a `check` written with an explicit `: PermissionResult` return annotation. The ordinary
|
|
269
|
-
* policy shape, `{ check: () => ({…}) }` with an INFERRED return type (also the method-syntax and
|
|
270
|
-
* assign-then-return forms), compiles GREEN with a stray `reason`. That is not a hole to plug in the
|
|
271
|
-
* type system — it is how contextual typing works — so the enforcement is at RUNTIME instead: every
|
|
272
|
-
* point that CONSUMES a decision REFUSES one carrying an own `reason` property, fail-closed (deny, with
|
|
273
|
-
* a migration message) — the {@link combinePolicies} fold, the PreToolUse hook face, and the durable
|
|
274
|
-
* resume-edit rechecks (see {@link refuseOutOfContractDecision}). Same doctrine as the strict `approve`
|
|
275
|
-
* / `onAsk` folds in this file: an out-of-contract value on the permission face is a loud rejection,
|
|
276
|
-
* never a best-effort read.
|
|
265
|
+
* The TYPE carries that retirement only where a decision is a literal in an annotated position; an inferred `check`
|
|
266
|
+
* return compiles green with a stray `reason`, so the enforcement is at RUNTIME — every point that consumes a decision
|
|
267
|
+
* refuses one carrying an own `reason` property, fail-closed ({@link refuseOutOfContractDecision}).
|
|
277
268
|
*/
|
|
278
269
|
export type PermissionResult = {
|
|
279
270
|
action: "allow";
|
|
@@ -387,6 +378,13 @@ export type PermissionResult = {
|
|
|
387
378
|
* render the card by it. Optional on the TYPE only because a decision is caller-composable; every ask
|
|
388
379
|
* the gate hands to its resolver carries it. */
|
|
389
380
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
381
|
+
/** The ask's CLASS — the first member of {@link classes} (see {@link AskRequest.askClass}). ENGINE-STAMPED at the
|
|
382
|
+
* same single stamp as {@link origin}, from the class set the gate's lanes read; a policy that self-declares it is
|
|
383
|
+
* overwritten there, and no lane reads it back (they read the classification itself). */
|
|
384
|
+
askClass?: import("./ask-origin.js").AskClass;
|
|
385
|
+
/** The ask's whole class set, in {@link import("./ask-origin.js").ASK_CLASSES} order (see
|
|
386
|
+
* {@link AskRequest.classes}). Stamped beside {@link askClass}, under the same rule. */
|
|
387
|
+
classes?: readonly import("./ask-origin.js").AskClass[];
|
|
390
388
|
/** WHICH integrity guard minted this ask — the unverifiable recursive delete or the session-transcript
|
|
391
389
|
* write. ENGINE-STAMPED by the two guard policies; a fold-internal fact the class derivation reads
|
|
392
390
|
* ({@link import("./ask-origin.js").classifyAsk}) so the two guards, which mint the same
|
|
@@ -737,63 +735,53 @@ export declare function constraintChainEntryOfSuppliedLayer(layer: ConstraintCha
|
|
|
737
735
|
* different frozen deny data now digest differently.
|
|
738
736
|
*/
|
|
739
737
|
export declare function constraintChainDigest(chain: readonly ConstraintChainEntry[]): string;
|
|
738
|
+
/** Every member of the decision union — a member the union gains and this list lacks is a type error at `satisfies`.
|
|
739
|
+
* The receipt walks it: a contract member not held as an own enumerable data property is refused if a descriptor on the
|
|
740
|
+
* prototype chain holds one (an `undefined` data value spells absence). Not re-exported from `src/index.ts`. */
|
|
741
|
+
export type DecisionMember = PermissionResult extends infer U ? (U extends unknown ? keyof U : never) : never;
|
|
742
|
+
export declare const DECISION_MEMBERS: readonly DecisionMember[];
|
|
740
743
|
/**
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
* the
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
* SCOPE of the carrier check (ruled after three review rounds converged on the same root): it screens
|
|
784
|
-
* STRUCTURALLY unstable carriers — getters, prototype-supplied values — i.e. shapes an ordinary
|
|
785
|
-
* deployment can write by accident. It does NOT try to defeat a Proxy whose descriptor trap reports a
|
|
786
|
-
* data property while its get trap stays stateful: same-process JavaScript has no trust boundary a
|
|
787
|
-
* function can enforce (an untrusted party who ships such a Proxy can as easily patch this module), so
|
|
788
|
-
* chasing that shape adds complexity without adding a guarantee. The screen's promise is against
|
|
789
|
-
* drift and accident, not against a untrusted co-resident.
|
|
790
|
-
*
|
|
791
|
-
* `reasonIsNonInput` (the HOOK seam's spelling — merged-code scan, 5.28 window): at that seam the
|
|
792
|
-
* field is documented as DISCARDED — every mint point downstream unconditionally re-stamps
|
|
793
|
-
* `decisionReason:"hook"` (fold, deny arm, delegated twin), and the allow path never reads it — so
|
|
794
|
-
* both reason arms are skipped there: refusing a value that cannot travel would fail-close a call
|
|
795
|
-
* over a field with no consumer, which inverted the discard contract the stamp exists to enforce.
|
|
796
|
-
* The retired-`reason` arm still applies (that one is about the MESSAGE channel, which does travel).
|
|
744
|
+
* The RECEIPT of a permission decision — the one point at which the engine takes a decision in from whoever composed it,
|
|
745
|
+
* on every lane: each layer of {@link combinePolicies}' fold, the gate's adjudication entry and its approved-edit re-check,
|
|
746
|
+
* the inherited lane's receipts of an ancestor policy's decision, the final-form re-check, the direct `check()` rechecks
|
|
747
|
+
* (the resume edit, the park re-projection, the changed-files probe), and the PreToolUse hook's result seam (its own
|
|
748
|
+
* reach, below). NOT re-exported from `src/index.ts`: an internal screen on inputs the engine receives.
|
|
749
|
+
*
|
|
750
|
+
* WHAT COMES BACK is the engine's own frozen copy, never the caller's object. A decision is caller-supplied data: a
|
|
751
|
+
* stateful accessor, a Proxy, or a policy that keeps and mutates an object it returned could otherwise answer two
|
|
752
|
+
* consumers with two values, and which consumer read which value would decide the call. So the caller's object is read
|
|
753
|
+
* ONCE, here, and every station after the receipt (the fold's ranking, the origin stamp, the classifier's eligibility, the
|
|
754
|
+
* mode arbitration, the approval request, the rewrite that executes) reads the copy; a copy handed to the screen again
|
|
755
|
+
* comes back as it is, unread. In order: (1) the CARRIER's type — a decision that is not an object (a string, a function
|
|
756
|
+
* holding the members) has no `action`; (2) the ONE READ — ONE function ({@link receiveContract}) for the decision and for the
|
|
757
|
+
* records it nests ({@link NESTED_CONTRACTS}), on an ORDINARY object (a Proxy, as the carrier or anywhere on its prototype
|
|
758
|
+
* chain, is refused before a trap of its could run): each contract member absent or an own enumerable DATA property, its value
|
|
759
|
+
* taken off the carrier's own descriptor (a member outside the contract is neither read nor carried — a policy's self-declared
|
|
760
|
+
* record on another name finds no reader); any other spelling refused by name — an accessor (never invoked), a member the
|
|
761
|
+
* prototype or a non-enumerable property supplies unless that descriptor spells absence (an `undefined` data value) — judged by
|
|
762
|
+
* descriptor, never read; (3) the retired `reason` field as an OWN property, judged without a read (authorship of the old
|
|
763
|
+
* spelling: a prototype exposing it never wrote it; ignoring it silently dropped the policy's text); (4) the VALUES, off the engine's
|
|
764
|
+
* one-read record, never a second read of the caller's object — the retired window absent, `action` in
|
|
765
|
+
* {@link DECISION_ACTIONS} (a consumer ranks a decision by testing two of its words, so any other value used to rank as an
|
|
766
|
+
* allow; a `canUseTool` result outside its set is a failure in CC) and `decisionReason` in {@link DECISION_REASONS} when
|
|
767
|
+
* present (absence stays legal); (5) the DETACH —
|
|
768
|
+
* the contract members' values structured-cloned ({@link tryCloneArgs}; one that cannot be, a function inside a rewrite,
|
|
769
|
+
* is refused), the engine's identity-keyed records carried across ({@link carryEngineAttestations}), the copy frozen and
|
|
770
|
+
* marked as this module's. A refusal is a deny in the screen's words, one code for every arm (`decisionReason: "rule"`),
|
|
771
|
+
* carrying no `updatedInput` from the refused object (a rewrite from an EARLIER, contract-shaped layer still rides out of
|
|
772
|
+
* {@link combinePolicies}' deny path). `null` / `undefined` carry nothing and take the carrier's refusal (the caller's own
|
|
773
|
+
* read of `.action` would throw instead, and that throw was being reported as an aborted check).
|
|
774
|
+
*
|
|
775
|
+
* SCOPE: the receipt runs NONE of the carrier's code — a Proxy (its traps are the caller's code) is refused unread, and on an
|
|
776
|
+
* ordinary object descriptor looks and `Object.getPrototypeOf` invoke nothing. What the receipt does not reach: code inside a
|
|
777
|
+
* member's VALUE, run by the detach itself (an accessor inside a rewrite is invoked by the structured clone, and what it answers
|
|
778
|
+
* there is what is cloned and what runs — one read, consistent) and by a refusal's rendering of a foreign word (`String(v)` on an
|
|
779
|
+
* out-of-set `action`, after the deny is decided). Same-process JavaScript has no trust boundary a function can enforce.
|
|
780
|
+
*
|
|
781
|
+
* `reasonIsNonInput` (the HOOK seam): a hook's `decisionReason` is DISCARDED there — every mint point downstream re-stamps
|
|
782
|
+
* `"hook"`, and the seam maps a hook's deny and ask and reads anything else as no objection, the policy still adjudicating
|
|
783
|
+
* the call — so that seam takes the two retired-field arms alone and its object back unchanged (`undefined` = no
|
|
784
|
+
* opinion): refusing a value that cannot travel would fail-close a call over a field with no consumer.
|
|
797
785
|
*/
|
|
798
786
|
export declare function refuseOutOfContractDecision(d: PermissionResult, opts?: {
|
|
799
787
|
reasonIsNonInput?: boolean;
|
|
@@ -911,22 +899,11 @@ export declare function createApprovalPolicy(opts: {
|
|
|
911
899
|
*/
|
|
912
900
|
export declare function combinePolicies(...policies: ToolPolicy[]): ToolPolicy;
|
|
913
901
|
/**
|
|
914
|
-
* The shell tools whose `{ command }` arg a coarse command-name policy parses
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
* tighten (else it becomes a policy side-door around a gated Bash)". The shellGate half was implemented;
|
|
920
|
-
* this half — the two ALWAYS-ON, core-owned parity gates that a caller's policy cannot remove — was keyed
|
|
921
|
-
* on a list containing only "Bash". The probe drove the whole thing end to end: the identical
|
|
922
|
-
* `rm -rf $(cat targets)` was denied as Bash and executed as Monitor, deleting a real directory with no
|
|
923
|
-
* gate at all; the same substitution makes `rm -rf ~/.ai-agent/sessions` a way to erase the audit trail.
|
|
924
|
-
*/
|
|
925
|
-
/**
|
|
926
|
-
* EXPORTED. A downstream governance layer keeps its own copy of this list so its
|
|
927
|
-
* per-command ask-list gates the same tools core does, and it had no way to read the real one — its guard
|
|
928
|
-
* had to probe a hand-written candidate list of names, which stays green for any name nobody thought to
|
|
929
|
-
* put in it. The list itself is the fact; publishing it removes the guess.
|
|
902
|
+
* The shell tools whose `{ command }` arg a coarse command-name policy parses; other tools are out of scope. `Monitor`
|
|
903
|
+
* belongs here: it runs an arbitrary shell `command` through the same env seam as Bash, so the always-on parity gates
|
|
904
|
+
* keyed on this list reach it too (a list of `Bash` alone let the identical command run ungated as `Monitor`). EXPORTED:
|
|
905
|
+
* a downstream governance layer keeps its own copy of this list so its per-command ask-list gates the same tools core
|
|
906
|
+
* does — the list itself is the fact, and publishing it removes the guess.
|
|
930
907
|
*/
|
|
931
908
|
export declare const COARSE_SHELL_TOOLS: string[];
|
|
932
909
|
/**
|
|
@@ -1389,6 +1366,19 @@ export interface AskRequest {
|
|
|
1389
1366
|
* therefore reading the deployment's own claim about its own ask, which is the direction that cannot
|
|
1390
1367
|
* loosen anything. */
|
|
1391
1368
|
readonly origin?: import("./ask-origin.js").AskOrigin;
|
|
1369
|
+
/** The ask's CLASS: the first member of {@link classes} in {@link import("./ask-origin.js").ASK_CLASSES} order — the
|
|
1370
|
+
* row key of {@link import("./ask-origin.js").MODE_ARBITRATION} a permission mode reads. Filled by every station that
|
|
1371
|
+
* fills {@link origin}, from the same classification the gate's lanes arbitrated this ask by; never written by a
|
|
1372
|
+
* caller or worker (a policy's self-declared value is replaced at the stamp), and a policy can move it only through
|
|
1373
|
+
* the tighten-only inputs the classification reads. ABSENT = unclassified: a request from a station that fills no
|
|
1374
|
+
* {@link origin} (the shared-artifact read confirmation), or a request built by a deployment itself. An approver
|
|
1375
|
+
* that answers by class treats absence as a class it will not answer. */
|
|
1376
|
+
readonly askClass?: import("./ask-origin.js").AskClass;
|
|
1377
|
+
/** Every class this ask belongs to, in {@link import("./ask-origin.js").ASK_CLASSES} order, `classes[0] ===
|
|
1378
|
+
* askClass`. One ask can carry several (an org verdict beside a hook's question); an approver that answers by class
|
|
1379
|
+
* reads the whole set — a class it will not answer anywhere in it is a question it does not answer. Present exactly
|
|
1380
|
+
* when {@link askClass} is. */
|
|
1381
|
+
readonly classes?: readonly import("./ask-origin.js").AskClass[];
|
|
1392
1382
|
/** Engine-judged risk axes of the action awaiting approval, for the human decision surface: a
|
|
1393
1383
|
* coarse `requiresRealApproval` cannot tell "cannot be undone" from "data leaves the machine",
|
|
1394
1384
|
* and those call for different scrutiny. Filled by the gate from the SAME resolved axes the
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -8,15 +8,18 @@ import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
|
8
8
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
9
9
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
10
10
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
11
|
-
import {
|
|
11
|
+
import { heldBy } from "./tool-name-readings.js";
|
|
12
12
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
13
13
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
|
|
14
14
|
import { describeRejectedValue } from "./tool-search-setting.js";
|
|
15
15
|
import { thrownText } from "./thrown-value.js";
|
|
16
|
+
import { receiveContract, refusedOutOfContract } from "./decision-receipt.js";
|
|
16
17
|
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
|
|
17
18
|
import { protocolOf } from "./protocol-table.js";
|
|
18
19
|
import { isAbsoluteForFamily, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
|
|
19
20
|
import { ASSIGNMENT_PREFIX, writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
|
|
21
|
+
const nameListHolds = (set, req) => heldBy("policy.nameLists", set, req);
|
|
22
|
+
const shellToolsListHolds = (set, req) => heldBy("policy.shellToolsList", set, req);
|
|
20
23
|
export function advertisedWritableRootsSeat(seat) {
|
|
21
24
|
return seat === undefined ? {} : { advertisedWritableRoots: Object.freeze([...seat.roots]) };
|
|
22
25
|
}
|
|
@@ -165,21 +168,21 @@ export function namespacedCoveringHit(covering, toolName) {
|
|
|
165
168
|
return covering.length > 0 && covering.some((e) => namespacedRuleNameCovers(e, toolName));
|
|
166
169
|
}
|
|
167
170
|
export function checkToolPolicyProjection(projection, req) {
|
|
168
|
-
const
|
|
171
|
+
const listed = (list) => heldBy("policy.projectionReplay", { has: (n) => list.includes(n) }, req);
|
|
169
172
|
for (const c of projection.components) {
|
|
170
173
|
if (c.kind === "tool_deny") {
|
|
171
|
-
if (
|
|
174
|
+
if (listed(c.names) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
|
|
172
175
|
return { action: "deny", message: `tool "${req.toolName}" is denied by a frozen inherited policy projection` };
|
|
173
176
|
}
|
|
174
177
|
continue;
|
|
175
178
|
}
|
|
176
179
|
if (c.kind === "tool_allowlist") {
|
|
177
|
-
if (!
|
|
180
|
+
if (!listed(c.names) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
|
|
178
181
|
return { action: "deny", message: `tool "${req.toolName}" is not in a frozen inherited policy projection's allowlist` };
|
|
179
182
|
}
|
|
180
183
|
continue;
|
|
181
184
|
}
|
|
182
|
-
if (!
|
|
185
|
+
if (!listed(c.tools))
|
|
183
186
|
continue;
|
|
184
187
|
const command = req.args?.command;
|
|
185
188
|
if (typeof command !== "string") {
|
|
@@ -242,56 +245,90 @@ function stableJson(v) {
|
|
|
242
245
|
export function constraintChainDigest(chain) {
|
|
243
246
|
return `cpv1:${createHash("sha256").update(stableJson(chain)).digest("hex")}`;
|
|
244
247
|
}
|
|
245
|
-
const ALLOW = { action: "allow" };
|
|
248
|
+
const ALLOW = Object.freeze({ action: "allow" });
|
|
246
249
|
const RETIRED_TEXT_FIELD = "reason";
|
|
247
250
|
const RETIRED_WINDOW_MEMBER = "autoDenyAfterMs";
|
|
248
251
|
const RETIRED_WINDOW_MEMBER_DENY_MESSAGE = `a permission decision's "denialLimitFallback" carries "${RETIRED_WINDOW_MEMBER}" — the decision names the counts only; ` +
|
|
249
252
|
`the auto-deny window is decided by the ask's route (a live approver arms it, every other route runs none) and cannot be ` +
|
|
250
253
|
`declared by a policy; denied fail-closed rather than silently replacing the declared number`;
|
|
254
|
+
const carriesRetiredWindow = (fallback) => typeof fallback === "object" && fallback !== null && fallback[RETIRED_WINDOW_MEMBER] !== undefined;
|
|
255
|
+
const DECISION_ACTIONS = Object.keys({ allow: 0, deny: 0, ask: 0 });
|
|
256
|
+
const DECISION_ACTION_SET = new Set(DECISION_ACTIONS);
|
|
257
|
+
export const DECISION_MEMBERS = Object.keys({
|
|
258
|
+
action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, persistedRuleShadowed: 0, probeReason: 0,
|
|
259
|
+
probeCause: 0, probeMandated: 0, mandated: 0, mandate: 0, probeReadRootCandidate: 0, matchedAskRule: 0, ruleEvidence: 0, segmentCoverage: 0,
|
|
260
|
+
origin: 0, askClass: 0, classes: 0, guard: 0, ruleUnreadable: 0, authoredBy: 0, foldedAskClasses: 0,
|
|
261
|
+
});
|
|
262
|
+
const OPERAND_MEMBERS = Object.keys({ shown: 0, total: 0 });
|
|
263
|
+
const NESTED_CONTRACTS = [
|
|
264
|
+
["", "denialLimitFallback", Object.keys({ consecutive: 0, total: 0, limit: 0, autoDenyAfterMs: 0 })],
|
|
265
|
+
["", "probeCause", Object.keys({ code: 0, roots: 0, further: 0 })],
|
|
266
|
+
["probeCause.", "roots", OPERAND_MEMBERS],
|
|
267
|
+
["probeCause.", "further", OPERAND_MEMBERS],
|
|
268
|
+
];
|
|
269
|
+
const refusedAction = (what) => refusedOutOfContract(`a permission decision's "action" is ${what} — the closed set is ${DECISION_ACTIONS.join("/")}; denied fail-closed rather than reading a word outside it as an allow`);
|
|
270
|
+
function renderForeignValue(v) {
|
|
271
|
+
try {
|
|
272
|
+
return JSON.stringify(String(v).slice(0, 64));
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return `[unprintable ${typeof v}]`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
251
278
|
const RETIRED_TEXT_FIELD_DENY_MESSAGE = `a permission decision carries the retired "${RETIRED_TEXT_FIELD}" field — rename it to "message" (the one text field ` +
|
|
252
279
|
`a decision carries); denied fail-closed rather than executing a decision whose text this layer cannot read`;
|
|
280
|
+
const ENGINE_RECEIPTS = new WeakSet();
|
|
253
281
|
export function refuseOutOfContractDecision(d, opts) {
|
|
282
|
+
if (opts?.reasonIsNonInput === true) {
|
|
283
|
+
if (d === null || d === undefined)
|
|
284
|
+
return d;
|
|
285
|
+
if (Object.prototype.hasOwnProperty.call(d, RETIRED_TEXT_FIELD))
|
|
286
|
+
return refusedOutOfContract(RETIRED_TEXT_FIELD_DENY_MESSAGE);
|
|
287
|
+
return carriesRetiredWindow(d.denialLimitFallback) ? refusedOutOfContract(RETIRED_WINDOW_MEMBER_DENY_MESSAGE) : d;
|
|
288
|
+
}
|
|
254
289
|
if (typeof d !== "object" || d === null)
|
|
290
|
+
return refusedAction(`absent (the decision is ${d === null || d === undefined ? String(d) : `a ${typeof d}`}, not an object)`);
|
|
291
|
+
if (ENGINE_RECEIPTS.has(d))
|
|
255
292
|
return d;
|
|
256
|
-
|
|
257
|
-
|
|
293
|
+
const decision = receiveContract(d, DECISION_MEMBERS, "");
|
|
294
|
+
if (decision.kind === "refused")
|
|
295
|
+
return decision.refused;
|
|
296
|
+
if (Object.prototype.hasOwnProperty.call(d, RETIRED_TEXT_FIELD))
|
|
297
|
+
return refusedOutOfContract(RETIRED_TEXT_FIELD_DENY_MESSAGE);
|
|
298
|
+
const contract = decision.held;
|
|
299
|
+
const records = Object.assign(Object.create(null), { "": contract });
|
|
300
|
+
for (const [holder, member, members] of NESTED_CONTRACTS) {
|
|
301
|
+
const held = records[holder];
|
|
302
|
+
if (held === undefined || typeof held[member] !== "object" || held[member] === null)
|
|
303
|
+
continue;
|
|
304
|
+
const nested = receiveContract(held[member], members, `${holder}${member}.`);
|
|
305
|
+
if (nested.kind === "refused")
|
|
306
|
+
return nested.refused;
|
|
307
|
+
held[member] = records[`${holder}${member}.`] = nested.held;
|
|
258
308
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
309
|
+
if (carriesRetiredWindow(contract.denialLimitFallback))
|
|
310
|
+
return refusedOutOfContract(RETIRED_WINDOW_MEMBER_DENY_MESSAGE);
|
|
311
|
+
if (!DECISION_ACTION_SET.has(contract.action))
|
|
312
|
+
return refusedAction(contract.action === undefined ? "absent" : renderForeignValue(contract.action));
|
|
313
|
+
if (contract.decisionReason !== undefined && !DECISION_REASON_SET.has(contract.decisionReason)) {
|
|
314
|
+
return refusedOutOfContract(`a permission decision carries an unrecognized "decisionReason" value (${renderForeignValue(contract.decisionReason)}) — ` +
|
|
315
|
+
`the closed set is ${DECISION_REASONS.join("/")}; denied fail-closed rather than letting consumers branch on a value this layer cannot classify`);
|
|
262
316
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return d;
|
|
268
|
-
if (!DECISION_REASON_SET.has(dr)) {
|
|
269
|
-
let rendered;
|
|
270
|
-
try {
|
|
271
|
-
rendered = JSON.stringify(String(dr).slice(0, 64));
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
rendered = `[unprintable ${typeof dr}]`;
|
|
275
|
-
}
|
|
276
|
-
return {
|
|
277
|
-
action: "deny",
|
|
278
|
-
message: `a permission decision carries an unrecognized "decisionReason" value (${rendered}) — ` +
|
|
279
|
-
`the closed set is ${DECISION_REASONS.join("/")}; denied fail-closed rather than letting consumers ` +
|
|
280
|
-
`branch on a value this layer cannot classify`,
|
|
281
|
-
decisionReason: "rule",
|
|
282
|
-
};
|
|
317
|
+
const detached = tryCloneArgs(contract);
|
|
318
|
+
if (!detached.ok) {
|
|
319
|
+
return refusedOutOfContract(`a permission decision's members could not be detached from the policy that returned them (${detached.reason}) — ` +
|
|
320
|
+
`the engine keeps its own copy of what it read; denied fail-closed`);
|
|
283
321
|
}
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
`different value than this screen validated; denied fail-closed`,
|
|
291
|
-
decisionReason: "rule",
|
|
292
|
-
};
|
|
322
|
+
const receipt = Object.freeze(detached.value);
|
|
323
|
+
const ownRecord = (o, k) => (typeof o === "object" && o !== null && Object.hasOwn(o, k) ? o[k] : undefined);
|
|
324
|
+
for (const [holder, member] of NESTED_CONTRACTS) {
|
|
325
|
+
const record = ownRecord(holder.split(".").filter(Boolean).reduce(ownRecord, receipt), member);
|
|
326
|
+
if (typeof record === "object" && record !== null)
|
|
327
|
+
Object.freeze(record);
|
|
293
328
|
}
|
|
294
|
-
|
|
329
|
+
carryEngineAttestations(d, receipt);
|
|
330
|
+
ENGINE_RECEIPTS.add(receipt);
|
|
331
|
+
return receipt;
|
|
295
332
|
}
|
|
296
333
|
const DEADLINE_ELAPSED = Symbol("approval.deadline_elapsed");
|
|
297
334
|
function withTimeout(p, ms, onTimeout) {
|
|
@@ -388,10 +425,10 @@ export function createAllowDenyPolicy(opts) {
|
|
|
388
425
|
nameSets: [{ ...(opts.allow ? { allow: [...opts.allow] } : {}), ...(opts.deny ? { deny: [...opts.deny] } : {}) }],
|
|
389
426
|
check(req) {
|
|
390
427
|
const toolName = req.toolName;
|
|
391
|
-
if (
|
|
428
|
+
if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
|
|
392
429
|
return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
|
|
393
430
|
}
|
|
394
|
-
if (allow && !
|
|
431
|
+
if (allow && !nameListHolds(allow, req) && !namespacedCoveringHit(allowCovering, toolName)) {
|
|
395
432
|
return { action: "deny", message: `tool "${req.toolName}" is not in the allowlist` };
|
|
396
433
|
}
|
|
397
434
|
return ALLOW;
|
|
@@ -428,10 +465,10 @@ export function createApprovalPolicy(opts) {
|
|
|
428
465
|
],
|
|
429
466
|
async check(req, signal) {
|
|
430
467
|
const toolName = req.toolName;
|
|
431
|
-
if (
|
|
468
|
+
if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
|
|
432
469
|
return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
|
|
433
470
|
}
|
|
434
|
-
if (
|
|
471
|
+
if (nameListHolds(need, req) || namespacedCoveringHit(needCovering, toolName)) {
|
|
435
472
|
const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
|
|
436
473
|
if (signal?.aborted) {
|
|
437
474
|
return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
|
|
@@ -481,7 +518,7 @@ export function createApprovalPolicy(opts) {
|
|
|
481
518
|
}
|
|
482
519
|
return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByPerson("human_refused", undefined));
|
|
483
520
|
}
|
|
484
|
-
if (opts.denyByDefault && !
|
|
521
|
+
if (opts.denyByDefault && !nameListHolds(auto, req) && !namespacedCoveringHit(autoCovering, toolName)) {
|
|
485
522
|
return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
|
|
486
523
|
}
|
|
487
524
|
return ALLOW;
|
|
@@ -621,7 +658,7 @@ export function createCoarseCommandNamePolicy(opts) {
|
|
|
621
658
|
return brandPolicyAskClass({
|
|
622
659
|
projection,
|
|
623
660
|
check(req) {
|
|
624
|
-
if (!
|
|
661
|
+
if (!shellToolsListHolds(shellTools, req))
|
|
625
662
|
return ALLOW;
|
|
626
663
|
const command = req.args?.command;
|
|
627
664
|
if (typeof command !== "string") {
|
|
@@ -875,7 +912,7 @@ export function createUnverifiableDeletePolicy(opts) {
|
|
|
875
912
|
const shellTools = canonicalToolNameSet(opts?.tools);
|
|
876
913
|
return brandPolicyAskClass({
|
|
877
914
|
check(req) {
|
|
878
|
-
if (!
|
|
915
|
+
if (!shellToolsListHolds(shellTools, req))
|
|
879
916
|
return ALLOW;
|
|
880
917
|
const command = req.args?.command;
|
|
881
918
|
if (typeof command !== "string")
|
|
@@ -972,7 +1009,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
972
1009
|
});
|
|
973
1010
|
return brandPolicyAskClass({
|
|
974
1011
|
check(req) {
|
|
975
|
-
if (
|
|
1012
|
+
if (shellToolsListHolds(shellTools, req)) {
|
|
976
1013
|
const command = req.args?.command;
|
|
977
1014
|
if (typeof command !== "string")
|
|
978
1015
|
return ALLOW;
|
|
@@ -52,36 +52,6 @@ export interface ToolCallLike {
|
|
|
52
52
|
} | undefined;
|
|
53
53
|
}
|
|
54
54
|
type CallPathTarget = ToolCallPathTarget;
|
|
55
|
-
/**
|
|
56
|
-
* The names a tool-name rule may spell this call's tool by — the face's `names` when they include the call's name,
|
|
57
|
-
* else the call's name alone (a bare request, or a face minted for another name). Every name-keyed matcher and the
|
|
58
|
-
* protective path floor read a rule's tool name against THIS list, which is the roster's one canonicalization seen
|
|
59
|
-
* from the call's side: a rule written against an alias and one written against the wire name reach the same calls.
|
|
60
|
-
*/
|
|
61
|
-
export declare function ruleNamesOf(req: Pick<ToolCallLike, "toolName" | "face">): readonly string[];
|
|
62
|
-
/** Does the call answer to `name` — is it one of the call's names ({@link ruleNamesOf})? The engine's TIGHTENS keyed on
|
|
63
|
-
* one of its own tool names read this, so a tool that wears the name under any of its names is tightened (the
|
|
64
|
-
* fail-safe direction); a grant or a routing keyed on an engine tool name reads the wire name. */
|
|
65
|
-
export declare function callAnswersTo(req: Pick<ToolCallLike, "toolName" | "face">, name: string): boolean;
|
|
66
|
-
/** Does a set of rule tool names name this call's tool ({@link ruleNamesOf})? */
|
|
67
|
-
export declare function toolNameSetHas(set: {
|
|
68
|
-
has(name: string): boolean;
|
|
69
|
-
}, req: Pick<ToolCallLike, "toolName" | "face">): boolean;
|
|
70
|
-
/** Does `set` hold any of the call's names ({@link ruleNamesOf})? The read of a MEMBERSHIP tighten keyed on the engine's
|
|
71
|
-
* own tool names (the egress set, a read station's tools, the compliance veto): a tool is held under every name it
|
|
72
|
-
* answers to. A grant or a routing keyed on an engine tool name reads the wire name instead. */
|
|
73
|
-
export declare function anyNamed(set: {
|
|
74
|
-
has(name: string): boolean;
|
|
75
|
-
}, names: readonly string[]): boolean;
|
|
76
|
-
/** The first of the call's names ({@link ruleNamesOf}, the spelling first) that `map` holds an entry for, with the entry.
|
|
77
|
-
* The read of a SEAT keyed on the engine's own tool names (a tier, a mark's sources): every key of one seat is read under
|
|
78
|
-
* the ONE name this returns — a seat is never assembled from two names' entries. */
|
|
79
|
-
export declare function firstNamed<V>(map: {
|
|
80
|
-
get(name: string): V | undefined;
|
|
81
|
-
}, names: readonly string[]): {
|
|
82
|
-
readonly name: string;
|
|
83
|
-
readonly value: V;
|
|
84
|
-
} | undefined;
|
|
85
55
|
/**
|
|
86
56
|
* design/388 B6/B17 — the path target a PROTECTIVE fence reads (a deny-list judging a write's target: sensitive paths,
|
|
87
57
|
* frozen paths, write protection, the transcript gate; edit tracking): the object face's own declaration first, the
|
|
@@ -89,7 +59,7 @@ export declare function firstNamed<V>(map: {
|
|
|
89
59
|
* face that declares none. Reading the catalog for an undeclared same-name tool can only TIGHTEN a deny-list (it adds
|
|
90
60
|
* a slot to judge — the pre-388 posture, never wider), which is exactly why this floor is wrong for a grant.
|
|
91
61
|
*
|
|
92
|
-
* The floor
|
|
62
|
+
* The floor is a `member` seat over every name the call answers to (the face's `names`, else the call's name alone) in
|
|
93
63
|
* the face's order — the name the call was made under first, then the wire name, then the other aliases — and the
|
|
94
64
|
* first name the catalog declares a target for wins: a call made under a catalog name and resolved to a tool that
|
|
95
65
|
* declares none keeps THAT name's slot, also when the tool answers to a second catalog name with another slot.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TOOL_MOUNT_TAGS, pathTargetSlotOnly } from "./tool-face.js";
|
|
2
2
|
import { ENGINE_CARDS, TOOL_CATALOG_ENTRIES } from "./tool-catalog-entries.js";
|
|
3
|
+
import { seatOf } from "./tool-name-readings.js";
|
|
3
4
|
export function describeToolCatalog() {
|
|
4
5
|
return TOOL_CATALOG_ENTRIES;
|
|
5
6
|
}
|
|
@@ -51,31 +52,10 @@ export function pathTargetValue(args, pt) {
|
|
|
51
52
|
}
|
|
52
53
|
return undefined;
|
|
53
54
|
}
|
|
54
|
-
export function ruleNamesOf(req) {
|
|
55
|
-
const names = req.face?.names;
|
|
56
|
-
return names !== undefined && names.includes(req.toolName) ? names : [req.toolName];
|
|
57
|
-
}
|
|
58
|
-
export function callAnswersTo(req, name) {
|
|
59
|
-
return ruleNamesOf(req).includes(name);
|
|
60
|
-
}
|
|
61
|
-
export function toolNameSetHas(set, req) {
|
|
62
|
-
return anyNamed(set, ruleNamesOf(req));
|
|
63
|
-
}
|
|
64
|
-
export function anyNamed(set, names) {
|
|
65
|
-
return names.some((n) => set.has(n));
|
|
66
|
-
}
|
|
67
|
-
export function firstNamed(map, names) {
|
|
68
|
-
for (const name of names) {
|
|
69
|
-
const value = map.get(name);
|
|
70
|
-
if (value !== undefined)
|
|
71
|
-
return { name, value };
|
|
72
|
-
}
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
55
|
export function protectivePathTargetOf(call) {
|
|
76
56
|
if (call.face?.pathTarget !== undefined)
|
|
77
57
|
return call.face.pathTarget;
|
|
78
|
-
const floor =
|
|
58
|
+
const floor = seatOf("floor.protectivePathTarget", { get: pathTargetOf }, call)?.value;
|
|
79
59
|
return floor === undefined || call.face === undefined ? floor : pathTargetSlotOnly(floor);
|
|
80
60
|
}
|
|
81
61
|
export const BARE_REQUEST_CODE = "policy.bare_request";
|
|
@@ -23,6 +23,9 @@ export interface AggregateBudgetOptions {
|
|
|
23
23
|
budgetChars?: number;
|
|
24
24
|
/** Tool names exempt from the budget (their own caps bound them) — e.g. read_file, read_tool_result. */
|
|
25
25
|
exemptTools?: ReadonlySet<string>;
|
|
26
|
+
/** The wire name a result's recorded tool name resolves to on the leg's roster (an exemption is judged on the tool the
|
|
27
|
+
* call ran, not the spelling the model used). Absent ⇒ the recorded name. */
|
|
28
|
+
toolNameOf?: (recorded: string) => string;
|
|
26
29
|
/** design/30 store for offload+read-back. Absent ⇒ self-contained truncation preview. */
|
|
27
30
|
store?: ToolResultStore;
|
|
28
31
|
/** Namespaces the stable ref so an offloaded result is globally addressable. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isToolResult } from "./message-utils.js";
|
|
2
2
|
import { buildPreview, buildToolResultRef, PERSISTED_OUTPUT_PREFIX, toolResultContentSegment, toolResultProvenanceOf, } from "./tool-result-store.js";
|
|
3
3
|
import { budgetExemptToolNames } from "./tool-registry.js";
|
|
4
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
4
5
|
export const AGGREGATE_TOOL_RESULT_BUDGET_CHARS = 200_000;
|
|
5
6
|
const HEAD = 1_000;
|
|
6
7
|
const TAIL = 1_000;
|
|
@@ -38,6 +39,7 @@ const CATALOG_BUDGET_EXEMPT_TOOLS = budgetExemptToolNames();
|
|
|
38
39
|
export async function capAggregateToolResults(messages, opts) {
|
|
39
40
|
const budget = opts.budgetChars ?? AGGREGATE_TOOL_RESULT_BUDGET_CHARS;
|
|
40
41
|
const exempt = opts.exemptTools ?? CATALOG_BUDGET_EXEMPT_TOOLS;
|
|
42
|
+
const exemptResult = (m) => m.toolName !== undefined && exempt.has(wireOf("budget.exemptResult", { toolName: opts.toolNameOf !== undefined ? opts.toolNameOf(m.toolName) : m.toolName }));
|
|
41
43
|
let out;
|
|
42
44
|
let i = 0;
|
|
43
45
|
while (i < messages.length) {
|
|
@@ -52,7 +54,7 @@ export async function capAggregateToolResults(messages, opts) {
|
|
|
52
54
|
let total = 0;
|
|
53
55
|
for (let k = i; k < j; k++) {
|
|
54
56
|
const m = arr[k];
|
|
55
|
-
if (
|
|
57
|
+
if (exemptResult(m))
|
|
56
58
|
continue;
|
|
57
59
|
total += textChars(m.content);
|
|
58
60
|
}
|
|
@@ -94,7 +96,7 @@ export async function capAggregateToolResults(messages, opts) {
|
|
|
94
96
|
const cand = [];
|
|
95
97
|
for (let k = i; k < j; k++) {
|
|
96
98
|
const m = out[k];
|
|
97
|
-
if (
|
|
99
|
+
if (exemptResult(m))
|
|
98
100
|
continue;
|
|
99
101
|
if (isOffloadedPreview(m.content))
|
|
100
102
|
continue;
|
|
@@ -112,7 +114,7 @@ export async function capAggregateToolResults(messages, opts) {
|
|
|
112
114
|
const cand2 = [];
|
|
113
115
|
for (let k = i; k < j; k++) {
|
|
114
116
|
const m = out[k];
|
|
115
|
-
if (
|
|
117
|
+
if (exemptResult(m))
|
|
116
118
|
continue;
|
|
117
119
|
if (isOffloadedPreview(messages[k].content))
|
|
118
120
|
continue;
|