@sema-agent/core 5.58.0 → 5.60.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 +88 -0
- package/dist/brain/anthropic.js +15 -5
- package/dist/brain/errors.d.ts +18 -1
- package/dist/brain/errors.js +7 -1
- package/dist/brain/input-too-long.d.ts +57 -0
- package/dist/brain/input-too-long.js +35 -0
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +8 -1
- package/dist/brain/stream-engine.js +9 -1
- package/dist/core/auto-compaction.js +2 -2
- package/dist/core/checkpoint-store.d.ts +116 -19
- package/dist/core/checkpoint-store.js +15 -8
- package/dist/core/context-edit.d.ts +243 -41
- package/dist/core/context-edit.js +247 -32
- package/dist/core/governance-codes.d.ts +37 -10
- package/dist/core/governance-codes.js +57 -1
- package/dist/core/locked-config.d.ts +36 -4
- package/dist/core/locked-config.js +34 -1
- package/dist/core/mcp.js +10 -6
- package/dist/core/memory-engine/consolidation-driver.d.ts +6 -2
- package/dist/core/memory-engine/consolidation-driver.js +54 -5
- package/dist/core/memory-engine/consolidation.d.ts +73 -1
- package/dist/core/memory-engine/consolidation.js +21 -1
- package/dist/core/memory-engine/content-origin.d.ts +24 -2
- package/dist/core/memory-engine/content-origin.js +6 -1
- package/dist/core/memory-engine/engine.d.ts +97 -8
- package/dist/core/memory-engine/engine.js +112 -20
- package/dist/core/memory-engine/file-backend.d.ts +13 -1
- package/dist/core/memory-engine/file-backend.js +3 -0
- package/dist/core/memory-engine/index.d.ts +4 -3
- package/dist/core/memory-engine/layout.js +20 -6
- package/dist/core/memory-engine/types.d.ts +17 -0
- package/dist/core/memory.d.ts +10 -0
- package/dist/core/park-selfcheck.js +1 -0
- package/dist/core/permission-rule-consent.js +9 -5
- package/dist/core/permission-rule-model.d.ts +42 -1
- package/dist/core/permission-rule-model.js +12 -0
- package/dist/core/runner/prepare-config-doors.d.ts +22 -1
- package/dist/core/runner/prepare-config-doors.js +36 -0
- package/dist/core/runner/prepare-task.d.ts +28 -1
- package/dist/core/runner/prepare-task.js +109 -11
- package/dist/core/runner/runtask.js +45 -8
- package/dist/core/store-contracts/checkpoint-store-contract.js +32 -0
- package/dist/core/tool-policy.d.ts +74 -0
- package/dist/core/tool-policy.js +80 -1
- package/dist/core/tools.js +1 -1
- package/dist/core/trace.d.ts +36 -0
- package/dist/core/types.d.ts +172 -22
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +11 -0
- package/dist/core/untrusted-text.js +1 -0
- package/dist/engine/llm/types.d.ts +21 -2
- package/dist/engine/loop/agent-loop.js +7 -1
- package/dist/engine/loop/types.d.ts +4 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/tools/fs/fs-bash.js +1 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +1771 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.60.0 — 2026-08-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **CC clearing machine + micro-compact recovery (design/374 slices 1a/1b/2/4; every default byte-identical)**:
|
|
7
|
+
`ContextEditOptions.machine: "legacy" | "cc"` selects the clearing machine — `cc` is CC's rejection-leg
|
|
8
|
+
form carried whole (keep 5, a >=20000-token minimum-savings gate with the field-exact `Sby` estimator,
|
|
9
|
+
one deep clear, a window base over ALL compactable occurrences, five cleared-predicate forms);
|
|
10
|
+
`RunnerDeps.microCompact { machine, clearOnRejection }` (both default off). A typed `input_too_long`
|
|
11
|
+
cause classifies real provider context refusals (position-0 anchored family over the parsed envelope
|
|
12
|
+
diagnostic; reflected copies stay ruled out) and feeds an MC-R recovery arm at the head of the
|
|
13
|
+
recovery chain (attempt-1 latch; plan over the rejected projection; a projection LEDGER — occurrence
|
|
14
|
+
keys (toolCallId, timestamp, dup ordinal), new-machine writes only — is the clear's sole survival
|
|
15
|
+
channel across per-request rebuilds). `resolveTriggerWindow` becomes the one trigger-window source
|
|
16
|
+
(four consumers); a clamped window announces `config.autocompact_window_clamped` once per task.
|
|
17
|
+
Trace frames `context.mc_clear`/`context.mc_null`. Nine CC divergences registered (cc-parity ledger).
|
|
18
|
+
- **Durable park rows carry the bidi twin (design/377, #438)**: `PendingAction.tool_approval.hasBidiControls?: true`
|
|
19
|
+
minted at park over the fidelity-projected args snapshot + preview; `CheckpointSummary.hasBidiControls`
|
|
20
|
+
projected, bit-less legacy rows BACKFILLED at projection from their own bounded materials (32K units /
|
|
21
|
+
512 nodes per row; never written back; a stored `false` reads as absent). Checkpoint version unchanged.
|
|
22
|
+
- **MCP entry-level content-trust declaration (design/378, #439; default byte-identical)**:
|
|
23
|
+
`McpServerSpec.contentOrigin?: "local" | "execution" | "external"` (the existing ToolContentOrigin
|
|
24
|
+
vocabulary), minted once at intake from a frozen copy; `memory.content_class_declared` announces one
|
|
25
|
+
per-value line per declared entry per prepared leg (explicit `"external"` announces too — it
|
|
26
|
+
forecloses the trustedTools exemption); malformed declarations refuse at preparation
|
|
27
|
+
(`config.mcp_content_class`).
|
|
28
|
+
|
|
29
|
+
### Changed — narrowings (named)
|
|
30
|
+
- **Inbox display family goes Cf-safe (two byte generations)**: descriptor-owned `RiskDescriptor` seats
|
|
31
|
+
(`summary` both arms, `touchedPaths`, `shadowedRule`) render through `renderUntrustedCommandText` —
|
|
32
|
+
newly-minted rows strip the `\p{Cf}` format class (emoji ZWJ sequences display decomposed); persisted
|
|
33
|
+
rows keep their old bytes forever. Byte-pinning consumers must read per row generation.
|
|
34
|
+
- **Confirm-face refusal messages stop interpolating raw command bytes (#430)**: >120-code-point
|
|
35
|
+
commands cap with an ellipsis, control bytes render `\uXXXX`; short clean commands byte-identical.
|
|
36
|
+
Two `record_malformed` damage sentences escape record-sourced values.
|
|
37
|
+
- **Vocabulary-outside `declared` content origins now classify `external`** (previously escaped marking
|
|
38
|
+
silently — a fail-open hole closed; untyped host-mounted tools with garbage declarations are the
|
|
39
|
+
affected shape).
|
|
40
|
+
- **`approvalRecordDamageOf` gains the reverse integrity clause (#441)**: an orphan candidate row
|
|
41
|
+
(belonging to no offer) answers `record_malformed` at all three consent entries.
|
|
42
|
+
|
|
43
|
+
## 5.59.0 — 2026-08-24
|
|
44
|
+
|
|
45
|
+
### BREAKING (narrow, named)
|
|
46
|
+
- **`ConsolidationDriverEngine` requires `readConsolidationPlanFoldEvidence`** (the member-level
|
|
47
|
+
conservation wall's read face). `MemoryEngine` satisfies it as before; a custom driver-engine stub
|
|
48
|
+
must add the member (compile error otherwise). Default-off feature shipped one version ago —
|
|
49
|
+
blast radius is stub authors only.
|
|
50
|
+
|
|
51
|
+
### Changed — consolidation safety narrowings (design/376 slice ③; default-off face, all fail-closed)
|
|
52
|
+
- **Member-level conservation wall**: a run whose planned members were deleted/excluded/folded by
|
|
53
|
+
another writer mid-run now settles `driver_failed` naming the missing ids, where a numeric offset
|
|
54
|
+
previously produced a false `converged`. Attribution uses the exact plan-frozen cycle-id grammar
|
|
55
|
+
(prefix-shadow ids cannot re-satisfy a member). Count witnesses remain the fast pass.
|
|
56
|
+
- **C-2 scan-refusal reach**: scan findings refuse per PRODUCT (disclosed; whole-plan refusal past
|
|
57
|
+
`min(floor(0.25×submitted), 8)` — knobs `scanRefusalRatioCap`/`scanRefusalMaxPerPlan`, bad values
|
|
58
|
+
refuse loudly); an empty or frozen-empty plan refuses instead of settling a vacuous completed;
|
|
59
|
+
refused input ids are excluded from the completion fingerprint and ride the plan file under a
|
|
60
|
+
bidirectional v2⇔member version gate (a pre-field worker parks loudly on a v2 plan instead of
|
|
61
|
+
settling wrong). WIDENING, named: refused products' edges no longer trip the supersession fuse.
|
|
62
|
+
- **C-3 instruction-form hard gate is unconditional**: a clean visible set refuses instruction-form
|
|
63
|
+
products by default (previously committed), naming the host escape hatch
|
|
64
|
+
`consolidation.allowInstructionProducts: true` (screened exactly-true; opens the clean arm only —
|
|
65
|
+
a marked set refuses with no opening knob).
|
|
66
|
+
- Registry reads are journal-aware (a committed journal wins; an unreadable journal throws
|
|
67
|
+
fail-closed instead of serving a clean-looking stale registry).
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- **`NOTICE_AUDIENCE` registry (closed)**: `ENGINE_NOTICE_CODES` is the 27-code catalog,
|
|
71
|
+
`NOTICE_AUDIENCE` answers for every member (8 user / 19 operator) in compile-time lockstep both
|
|
72
|
+
directions; `task.user_steer_undrained`/`task.user_followup_undrained` now carry `sessionId`
|
|
73
|
+
filled from the real run; catalog/code/audience types root-exported for downstream table diffing.
|
|
74
|
+
- **`AskRequest.hasBidiControls`** presence bit (additive): minted at the ask throat over
|
|
75
|
+
execution-snapshot args + tool preview for the directional-control class (U+061C/200E/200F/
|
|
76
|
+
202A-202E/2066-2069); presence-only (never `false`; absence covers budget exhaustion), zero
|
|
77
|
+
rewriting; the scan charges every character — values, object keys, Map keys, inherited names —
|
|
78
|
+
through ONE bounded reader (node budget + 1M code-unit allowance). Plus
|
|
79
|
+
**`renderUntrustedCommandText`** root export: the lossy DISPLAY baseline for raw command bytes
|
|
80
|
+
(strips Cf, folds controls to spaces, code-point cap with ellipsis; identity stays on raw).
|
|
81
|
+
- **Read faces**: `readConsolidationGateState` (present|absent|corrupt-with-detail; legacy face
|
|
82
|
+
byte-unchanged), `readConsolidationPlanFoldEvidence` (ok/absent/corrupt; applied-only edges),
|
|
83
|
+
`MemoryEngine.listMemoryScopes()`/File-backend `listScopes()` with the `supported` discriminator
|
|
84
|
+
(a backend that cannot enumerate answers `supported:false` with a reason, never `[]`).
|
|
85
|
+
- **Export tiers v1**: the export-surface snapshot carries a machine-readable `tiers` column
|
|
86
|
+
(stable/advanced/internal; self-declared heuristic) with a three-leg gate (every live export
|
|
87
|
+
tiered, no stale rows, live derivation ≡ frozen column).
|
|
88
|
+
- `OriginClearanceRow`/`OriginClearanceEvent` root type exports (server design/316 car-B compile
|
|
89
|
+
prerequisite). Export surface 1749 → 1758, all additive.
|
|
90
|
+
|
|
3
91
|
## 5.58.0 — 2026-08-24
|
|
4
92
|
|
|
5
93
|
### BREAKING (design/375 case A — per-segment batch consent; ships as one window, no compatibility arms)
|
package/dist/brain/anthropic.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {} from "../internal/llm.js";
|
|
2
2
|
import { BrainError } from "./errors.js";
|
|
3
|
+
import { startsWithInputTooLongAnchor } from "./input-too-long.js";
|
|
3
4
|
import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
|
|
4
5
|
import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js";
|
|
5
6
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
@@ -178,6 +179,8 @@ function anthropicStreamErrorCode(t) {
|
|
|
178
179
|
return "auth";
|
|
179
180
|
case "invalid_request_error":
|
|
180
181
|
return "invalid_request";
|
|
182
|
+
case "request_too_large":
|
|
183
|
+
return "invalid_request";
|
|
181
184
|
default:
|
|
182
185
|
return "server";
|
|
183
186
|
}
|
|
@@ -501,8 +504,13 @@ export function createAnthropicBrain(config = {}) {
|
|
|
501
504
|
break;
|
|
502
505
|
}
|
|
503
506
|
case "error": {
|
|
504
|
-
const
|
|
505
|
-
|
|
507
|
+
const rawError = evt.error;
|
|
508
|
+
const e = typeof rawError === "object" && rawError !== null && !Array.isArray(rawError)
|
|
509
|
+
? rawError
|
|
510
|
+
: undefined;
|
|
511
|
+
const inputTooLong = e?.type === "request_too_large" ||
|
|
512
|
+
(e?.type === "invalid_request_error" && typeof e.message === "string" && startsWithInputTooLongAnchor(e.message));
|
|
513
|
+
throw new BrainError(anthropicStreamErrorCode(e?.type), `anthropic stream error (${e?.type ?? "unknown"}): ${e?.message ?? ""}`, undefined, e === undefined ? undefined : inputTooLong ? { inputTooLong: true } : { inputTooLongRuledOut: true });
|
|
506
514
|
}
|
|
507
515
|
default:
|
|
508
516
|
break;
|
|
@@ -654,9 +662,11 @@ export function createAnthropicBrain(config = {}) {
|
|
|
654
662
|
(truncatedEmpty && !dynamicCut && !staticReasoningCut);
|
|
655
663
|
const errorKind = degenerate
|
|
656
664
|
? "degenerate"
|
|
657
|
-
: !safetyCut &&
|
|
658
|
-
? "
|
|
659
|
-
: undefined
|
|
665
|
+
: !safetyCut && contextWindowExceeded
|
|
666
|
+
? "input_too_long"
|
|
667
|
+
: !safetyCut && toolError === undefined && truncatedEmpty && !dynamicCut && !staticReasoningCut
|
|
668
|
+
? "length_empty"
|
|
669
|
+
: undefined;
|
|
660
670
|
const finalMessage = {
|
|
661
671
|
...partial,
|
|
662
672
|
content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],
|
package/dist/brain/errors.d.ts
CHANGED
|
@@ -34,7 +34,24 @@ export declare const IN_BAND_OUTPUT_CODES: ReadonlySet<BrainErrorCode>;
|
|
|
34
34
|
export declare class BrainError extends Error {
|
|
35
35
|
readonly code: BrainErrorCode;
|
|
36
36
|
readonly status?: number;
|
|
37
|
-
|
|
37
|
+
/** design/374 slice 2 — STRUCTURAL cause seat: the provider stated (in a provenance-checked
|
|
38
|
+
* diagnostic field or status, see `input-too-long.ts`) that the request INPUT exceeds its
|
|
39
|
+
* context limit. The engine's terminal-shell mint lifts it onto `AssistantMessage.errorKind:
|
|
40
|
+
* "input_too_long"`, which the loop's prompt-too-long classifier reads instead of regexing the
|
|
41
|
+
* human-facing message. Never inferred from `detail` prose after construction. */
|
|
42
|
+
readonly inputTooLong?: true;
|
|
43
|
+
/** The AUTHORITATIVE-NEGATIVE half (adversarial-review r1): the classifier RAN with provenance
|
|
44
|
+
* and this failure is NOT input-too-long (wrong status family, or the provider's own envelope
|
|
45
|
+
* states a different error). Lifted onto `AssistantMessage.inputTooLongRuledOut`, which
|
|
46
|
+
* suppresses the loop's prose fallback — a reflected "prompt is too long" inside an unrelated
|
|
47
|
+
* error body must not drive a context-shrinking recovery. Mutually exclusive with
|
|
48
|
+
* `inputTooLong`; BOTH absent = classification could not establish provenance ("unknown"),
|
|
49
|
+
* and the prose fallback keeps its historic reach. */
|
|
50
|
+
readonly inputTooLongRuledOut?: true;
|
|
51
|
+
constructor(code: BrainErrorCode, detail: string, status?: number, opts?: {
|
|
52
|
+
inputTooLong?: boolean;
|
|
53
|
+
inputTooLongRuledOut?: boolean;
|
|
54
|
+
});
|
|
38
55
|
}
|
|
39
56
|
/** Map an HTTP status to an error class. 401/403 = auth (don't retry); 429 = rate limit; 5xx = server. */
|
|
40
57
|
export declare function classifyHttp(status: number): BrainErrorCode;
|
package/dist/brain/errors.js
CHANGED
|
@@ -12,11 +12,17 @@ export const IN_BAND_OUTPUT_CODES = new Set(["stream_torn", "refusal", "length_e
|
|
|
12
12
|
export class BrainError extends Error {
|
|
13
13
|
code;
|
|
14
14
|
status;
|
|
15
|
-
|
|
15
|
+
inputTooLong;
|
|
16
|
+
inputTooLongRuledOut;
|
|
17
|
+
constructor(code, detail, status, opts) {
|
|
16
18
|
super(`[${code}] ${detail}`);
|
|
17
19
|
this.name = "BrainError";
|
|
18
20
|
this.code = code;
|
|
19
21
|
this.status = status;
|
|
22
|
+
if (opts?.inputTooLong === true)
|
|
23
|
+
this.inputTooLong = true;
|
|
24
|
+
else if (opts?.inputTooLongRuledOut === true)
|
|
25
|
+
this.inputTooLongRuledOut = true;
|
|
20
26
|
}
|
|
21
27
|
}
|
|
22
28
|
export function classifyHttp(status) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/374 slice 2 — TYPED input-too-long classification for provider refusals.
|
|
3
|
+
*
|
|
4
|
+
* "The request INPUT alone exceeds the model's context limit" used to be recognized only by the
|
|
5
|
+
* loop's prose regex over the human-facing `errorMessage` — a classifier that (a) cannot tell a
|
|
6
|
+
* provider's own diagnostic from caller text a gateway reflected, and (b) breaks the moment a
|
|
7
|
+
* provider rewords. This module classifies at the BRAIN boundary, where provenance is decidable,
|
|
8
|
+
* and the verdict rides `BrainError.inputTooLong` → `AssistantMessage.errorKind: "input_too_long"`
|
|
9
|
+
* (the same typed lane `degenerate`/`length_empty` already use).
|
|
10
|
+
*
|
|
11
|
+
* PROVENANCE DISCIPLINE (mirrors `context-overflow.ts`, the sibling classifier for the
|
|
12
|
+
* input+max_tokens overflow — a DIFFERENT error: there the input fits and lowering the output cap
|
|
13
|
+
* self-heals; here the input alone does not fit and only shrinking the input helps):
|
|
14
|
+
* - The only text source is the provider's diagnostic FIELD: `error.message` of a JSON object
|
|
15
|
+
* body whose `error` is itself an object (both the Anthropic `{type:"error",error:{…}}` and
|
|
16
|
+
* the OpenAI `{error:{…}}` envelopes reduce to this). A plain-text body, a string `error`, a
|
|
17
|
+
* top-level `message` — refused: each is a guess about gateway formatting that would trade a
|
|
18
|
+
* hypothetical classification for a field a gateway may fill with caller content.
|
|
19
|
+
* - Prose is matched ANCHORED at the start of that field only, against the conservative
|
|
20
|
+
* context-limit FAMILY below (case-INsensitive since the r2 family form — the family note owns
|
|
21
|
+
* the reasoning and the member list). A gateway that inlines the offending request
|
|
22
|
+
* ("validation failed; offending prompt: prompt is too long…") therefore cannot classify:
|
|
23
|
+
* caller text must never steer a retry.
|
|
24
|
+
* - Structural signals need no prose at all: OpenAI-family `error.code ===
|
|
25
|
+
* "context_length_exceeded"`, and HTTP 413 (the transport's own "request entity too large").
|
|
26
|
+
*
|
|
27
|
+
* Misclassification cost note (updated with the r1 three-state verdict — the original "a miss
|
|
28
|
+
* keeps exactly the pre-374 behavior" claim died with the binary form): inside a PARSED provider
|
|
29
|
+
* envelope a miss is an AUTHORITATIVE negative that SUPPRESSES the loop's prose fallback — which
|
|
30
|
+
* is why the family must carry every observed genuine opening (the r2/r3/r4 additions below);
|
|
31
|
+
* prose-only bodies keep the pre-374 fallback reach via "unknown". A FALSE HIT costs one bounded
|
|
32
|
+
* recovery attempt — but only on a 400/413/422 the request already failed on.
|
|
33
|
+
*/
|
|
34
|
+
/** True when the (provider-authored) diagnostic OPENS with the context-limit refusal family. */
|
|
35
|
+
export declare function startsWithInputTooLongAnchor(diagnostic: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Three-state classification verdict (adversarial-review r1: a binary answer conflated "the
|
|
38
|
+
* provider said something ELSE" with "we could not read the provider", and the loop's prose
|
|
39
|
+
* fallback then re-opened the reflected-text hole for exactly the errors this module HAD
|
|
40
|
+
* examined):
|
|
41
|
+
* - `"yes"` — provenance-checked input-too-long; the caller stamps the typed cause.
|
|
42
|
+
* - `"no"` — the classification is AUTHORITATIVE-NEGATIVE: either the status can never carry
|
|
43
|
+
* this refusal (5xx, auth, rate limit — reflected prose in those bodies must not
|
|
44
|
+
* drive a context-shrinking recovery), or the provider's own diagnostic envelope
|
|
45
|
+
* parsed and states a different error. The caller marks the terminal shell
|
|
46
|
+
* ruled-out, which SUPPRESSES the loop's prose fallback for this message.
|
|
47
|
+
* - `"unknown"` — a PTL-capable status whose body established no provenance (text/plain gateway,
|
|
48
|
+
* truncated read): nothing is stamped either way, and the loop's historic prose
|
|
49
|
+
* fallback keeps working for gateways that speak prose only.
|
|
50
|
+
*/
|
|
51
|
+
export type InputTooLongVerdict = "yes" | "no" | "unknown";
|
|
52
|
+
/**
|
|
53
|
+
* Classify an HTTP refusal as input-too-long. `body` is the (bounded) error-body text as read;
|
|
54
|
+
* a truncated read that no longer parses as JSON answers `"unknown"` (413 excepted — the status
|
|
55
|
+
* alone is the provider's statement).
|
|
56
|
+
*/
|
|
57
|
+
export declare function classifyInputTooLong(status: number, body: string): InputTooLongVerdict;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const INPUT_TOO_LONG_ANCHORED_RE = /^(?:prompt is too long|input is too long|this model's maximum context length is |context.{0,3}length|context window (?:limit|exceeded)|reached its context window|too many tokens|maximum context|input length and `max_tokens` exceed context limit|(?:the )?request(?: \([^)]{0,40}\))? exceeds|(?:the )?input token count(?: \([^)]{0,40}\))? exceeds|requested tokens(?: \([^)]{0,40}\))? exceed|your input exceeds|(?:the )?number of tokens in the request exceeds)/i;
|
|
2
|
+
const INPUT_TOO_LONG_STATUSES = new Set([400, 413, 422]);
|
|
3
|
+
export function startsWithInputTooLongAnchor(diagnostic) {
|
|
4
|
+
return INPUT_TOO_LONG_ANCHORED_RE.test(diagnostic.trimStart());
|
|
5
|
+
}
|
|
6
|
+
function providerErrorObject(body) {
|
|
7
|
+
const trimmed = body.trim();
|
|
8
|
+
if (!trimmed.startsWith("{"))
|
|
9
|
+
return undefined;
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(trimmed);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
18
|
+
return undefined;
|
|
19
|
+
const err = parsed.error;
|
|
20
|
+
if (typeof err !== "object" || err === null || Array.isArray(err))
|
|
21
|
+
return undefined;
|
|
22
|
+
return err;
|
|
23
|
+
}
|
|
24
|
+
export function classifyInputTooLong(status, body) {
|
|
25
|
+
if (!INPUT_TOO_LONG_STATUSES.has(status))
|
|
26
|
+
return "no";
|
|
27
|
+
if (status === 413)
|
|
28
|
+
return "yes";
|
|
29
|
+
const err = providerErrorObject(body);
|
|
30
|
+
if (err === undefined)
|
|
31
|
+
return "unknown";
|
|
32
|
+
if (err.code === "context_length_exceeded")
|
|
33
|
+
return "yes";
|
|
34
|
+
return typeof err.message === "string" && startsWithInputTooLongAnchor(err.message) ? "yes" : "no";
|
|
35
|
+
}
|
|
@@ -174,7 +174,13 @@ export declare function adjudicateDerivedRoute(args: {
|
|
|
174
174
|
*/
|
|
175
175
|
export declare function sameRouteIdentity(a: Pick<Model, "id" | "baseUrl">, b: Pick<Model, "id" | "baseUrl">): boolean;
|
|
176
176
|
/** The `route.fallback_to_primary` announcement — one shared shape for every derived-leg seat, so
|
|
177
|
-
* a consumer can rely on `detail.{seat, from, to, cause}` regardless of which seat fell back.
|
|
177
|
+
* a consumer can rely on `detail.{seat, from, to, cause}` regardless of which seat fell back.
|
|
178
|
+
*
|
|
179
|
+
* `sessionId` (#433, additive): the swap happened while resolving ONE run's seats, so the run is
|
|
180
|
+
* what an operator correlates the line against ("which task ran on the fallback model"). The
|
|
181
|
+
* AUDIENCE stays `"operator"` — the fix is a credential/catalog fix, and no end user can act on it;
|
|
182
|
+
* attribution is a routing key, not an entitlement. Omitted when a caller has no session in hand,
|
|
183
|
+
* never fabricated. */
|
|
178
184
|
export declare function fallbackToPrimaryNotice(args: {
|
|
179
185
|
seat: string;
|
|
180
186
|
from: string;
|
|
@@ -182,6 +188,7 @@ export declare function fallbackToPrimaryNotice(args: {
|
|
|
182
188
|
verdict: Extract<RouteAdjudication, {
|
|
183
189
|
ok: false;
|
|
184
190
|
}>;
|
|
191
|
+
sessionId?: string;
|
|
185
192
|
}): EngineNotice;
|
|
186
193
|
/** The auth carriers of a header bag, canonicalized (case-fold name → value, sorted) — the
|
|
187
194
|
* Model-visible credential fingerprint used by catalog-change disclosure (a same-name entry whose
|
|
@@ -173,7 +173,14 @@ export function fallbackToPrimaryNotice(args) {
|
|
|
173
173
|
code: "route.fallback_to_primary",
|
|
174
174
|
message: `derived model "${args.from}" (${args.seat}) is not routable (${args.verdict.code}) — ` +
|
|
175
175
|
`falling back to the primary model "${args.to}". ${args.verdict.detail.fixHint}`,
|
|
176
|
-
detail: {
|
|
176
|
+
detail: {
|
|
177
|
+
seat: args.seat,
|
|
178
|
+
from: args.from,
|
|
179
|
+
to: args.to,
|
|
180
|
+
cause: args.verdict.code,
|
|
181
|
+
fixHint: args.verdict.detail.fixHint,
|
|
182
|
+
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
183
|
+
},
|
|
177
184
|
};
|
|
178
185
|
}
|
|
179
186
|
export function authCarrierFingerprint(headers) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAssistantMessageEventStream, } from "../internal/llm.js";
|
|
2
2
|
import { FLOOR_OUTPUT_TOKENS, parseContextOverflow, planOutputCapAdjustment } from "./context-overflow.js";
|
|
3
3
|
import { BrainError, classifyConnectFailure, classifyHttp, describeNetworkError } from "./errors.js";
|
|
4
|
+
import { classifyInputTooLong } from "./input-too-long.js";
|
|
4
5
|
import { FAST_MAX_BACKOFF_MS, providerWaitHint, retryBackoffMs } from "./retry.js";
|
|
5
6
|
import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
6
7
|
import { createConnectController, resolveStallTimeoutMs } from "./timeout.js";
|
|
@@ -258,6 +259,12 @@ export function runStreamingBrain(args) {
|
|
|
258
259
|
const errorMsg = emptyAssistant(model);
|
|
259
260
|
errorMsg.stopReason = aborted ? "aborted" : "error";
|
|
260
261
|
errorMsg.errorMessage = err instanceof Error ? err.message : String(err);
|
|
262
|
+
if (!aborted && err instanceof BrainError) {
|
|
263
|
+
if (err.inputTooLong === true)
|
|
264
|
+
errorMsg.errorKind = "input_too_long";
|
|
265
|
+
else if (err.inputTooLongRuledOut === true)
|
|
266
|
+
errorMsg.inputTooLongRuledOut = true;
|
|
267
|
+
}
|
|
261
268
|
errorMsg.usageMissing = true;
|
|
262
269
|
out.push({ type: "error", reason: aborted ? "aborted" : "error", error: errorMsg });
|
|
263
270
|
})
|
|
@@ -429,7 +436,8 @@ export function runStreamingBrain(args) {
|
|
|
429
436
|
throw new BrainError("network", describeNetworkError(netErr));
|
|
430
437
|
const detail = overflowBody ?? (r ? (await readErrorBody(r, signal)).text : "");
|
|
431
438
|
const status = r?.status ?? 0;
|
|
432
|
-
|
|
439
|
+
const itl = classifyInputTooLong(status, detail);
|
|
440
|
+
throw new BrainError(classifyHttp(status), `${httpLabel} HTTP ${status || "ERR"}: ${detail.slice(0, 500)}${overflowDeclineNote}`, status, itl === "yes" ? { inputTooLong: true } : itl === "no" ? { inputTooLongRuledOut: true } : undefined);
|
|
433
441
|
}
|
|
434
442
|
finally {
|
|
435
443
|
cc.abort();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatFileOperations, formatPersistedOutputRefs, prepareCompaction, shouldCompact, } from "../internal/harness.js";
|
|
2
2
|
import { fileArgPath } from "../tools/fs/safety.js";
|
|
3
|
-
import { contextEditFrontier } from "./context-edit.js";
|
|
3
|
+
import { contextEditFrontier, resolveTriggerWindow } from "./context-edit.js";
|
|
4
4
|
import { selectCompactionEpoch } from "../prompt-assembly/epoch.js";
|
|
5
5
|
import { SEMA_DEFAULT_PACK } from "../prompt-assembly/packs/sema-default.js";
|
|
6
6
|
import { brainToRuntime } from "./runtime.js";
|
|
@@ -81,7 +81,7 @@ export async function maybeCompact(opts) {
|
|
|
81
81
|
const structuralTokens = ctx.messages.reduce((sum, m) => sum + estimateTokens(m, cpt), 0);
|
|
82
82
|
const anchorStale = est.usageTokens > 0 && est.tokens > structuralTokens * STALE_ANCHOR_STRUCTURAL_MARGIN + overhead;
|
|
83
83
|
const tokens = anchorStale || est.usageTokens === 0 ? structuralTokens + overhead : est.tokens;
|
|
84
|
-
const window = opts.model.
|
|
84
|
+
const window = resolveTriggerWindow(opts.model).window;
|
|
85
85
|
const windowKnown = Number.isFinite(window) && window > 0;
|
|
86
86
|
const windowField = opts.model.autoCompactTokens != null ? "autoCompactTokens" : opts.model.contextTokens != null ? "contextTokens" : "contextWindow";
|
|
87
87
|
const settings = sanitizeCompactionSettings(rawSettings, window);
|
|
@@ -135,19 +135,41 @@ export interface RiskDescriptor {
|
|
|
135
135
|
* answer that in the field. */
|
|
136
136
|
shellGateDoctrine?: "classify" | "always";
|
|
137
137
|
/** A REDACTED, length-capped ONE-LINE summary of the call (the command for a shell gate; a brief key-arg
|
|
138
|
-
* digest otherwise) for the inbox preview. NEUTRALIZED via
|
|
139
|
-
* + length-capped (it is
|
|
140
|
-
*
|
|
138
|
+
* digest otherwise) for the inbox preview. NEUTRALIZED via
|
|
139
|
+
* {@link import("./permission-rule-model.js").renderUntrustedCommandText} + length-capped (it is
|
|
140
|
+
* persisted + shown to a human inbox, so a crafted tool arg carrying a `</system-reminder>` variant /
|
|
141
|
+
* newline / huge string must NOT inject into the render or bloat storage — and, since #438, a
|
|
142
|
+
* directional/zero-width `\p{Cf}` marker cannot ride into the render either: the display baseline
|
|
143
|
+
* STRIPS the format class, so what the line shows is what the args carry, in the order they carry it).
|
|
144
|
+
* GENERATIONAL, like every mint-time seat: the summary is minted ONCE at park mint and persisted, so
|
|
145
|
+
* rows minted before the #438 render change keep their old bytes (which may carry `\p{Cf}`) forever —
|
|
146
|
+
* a consumer must not assume one byte-form across row generations, and the display obligation
|
|
147
|
+
* (render through the baseline, or screen) is the same for both. One benign visible consequence on
|
|
148
|
+
* new rows: an emoji ZWJ sequence in a command displays decomposed here (raw bytes stay in
|
|
149
|
+
* `pendingAction.args`/`toolInput` — this seat is DISPLAY, not identity).
|
|
141
150
|
* NEVER raw secrets / full args / env. Deterministic. Optional (omitted when nothing safe to summarize). */
|
|
142
151
|
summary?: string;
|
|
143
152
|
/** Best-effort file paths the action touches (fs-tool `path` args; shell parsing is deliberately NOT
|
|
144
|
-
* attempted — over-reaching a shell parse risks a wrong/forgeable path). Each path
|
|
153
|
+
* attempted — over-reaching a shell parse risks a wrong/forgeable path). Each path rendered through the
|
|
154
|
+
* same {@link import("./permission-rule-model.js").renderUntrustedCommandText} baseline as
|
|
155
|
+
* {@link summary} (a path name can carry the same reordering hazard as a command) + capped.
|
|
145
156
|
* Omitted when none derivable. */
|
|
146
157
|
touchedPaths?: string[];
|
|
147
158
|
/** #144 (additive): a persisted allow rule MATCHED this call but could not clear the mandated ask —
|
|
148
|
-
* the matched rule text (
|
|
149
|
-
*
|
|
150
|
-
* just outranked" instead of the person concluding
|
|
159
|
+
* the matched rule text (rendered through the same display baseline as {@link summary}, capped),
|
|
160
|
+
* threaded to the mint so the durable-park route carries the same disclosure the synchronous ask
|
|
161
|
+
* does. The inbox renders "their rule is alive, just outranked" instead of the person concluding
|
|
162
|
+
* their rule silently broke. (The baseline is a FAMILY-UNIFORMITY choice AND, on this seat, a
|
|
163
|
+
* disclosed one-face NARROWING: the feeding path is the permission-rule store's READ leg, which
|
|
164
|
+
* does not re-screen stored rule text through the validator — the bytes may be model-authored or
|
|
165
|
+
* a custom provider's, and a store-sourced string can legally carry `\p{Cf}`. For
|
|
166
|
+
* validator-screened text the render coincides with the previous `inlineUntrusted` treatment
|
|
167
|
+
* (the two differ only on the `\p{Cf}` class, and both fold the width-bearing controls it admits
|
|
168
|
+
* (a tab) identically); for a Cf carrier this durable seat STRIPS what the synchronous ask's
|
|
169
|
+
* `persistedRuleShadowed` preserves — same value, two byte-forms, diverging exactly on `\p{Cf}`,
|
|
170
|
+
* the durable face stripping MORE (the safe direction). This seat carries NO cross-face
|
|
171
|
+
* same-value contract (unlike `probeReason`/`probeCause` below), which is why the one-face move
|
|
172
|
+
* is legal — a byte-pinning consumer must read per face.) */
|
|
151
173
|
shadowedRule?: string;
|
|
152
174
|
/** backlog #239 (additive): WHY the reversibility probe did not clear this call, in the probe's own
|
|
153
175
|
* words — the cause the tighten acted on, which until now existed only inside the probe and left the
|
|
@@ -157,7 +179,14 @@ export interface RiskDescriptor {
|
|
|
157
179
|
* boundary, so it is DISPLAY/TRIAGE metadata only, never adjudication input.
|
|
158
180
|
*
|
|
159
181
|
* The UNSTRUCTURED face: a deployment's own probe writes prose here and this engine cannot say what it
|
|
160
|
-
* means. An engine-minted cause uses {@link probeCause} instead — see that field for why.
|
|
182
|
+
* means. An engine-minted cause uses {@link probeCause} instead — see that field for why.
|
|
183
|
+
*
|
|
184
|
+
* RESIDUAL, disclosed (#438): unlike the descriptor-owned display family above, this seat does
|
|
185
|
+
* NOT strip the `\p{Cf}` display-reordering class — a directional/zero-width marker in the
|
|
186
|
+
* probe's prose survives here byte-for-byte, because the seat carries a documented SAME-VALUE
|
|
187
|
+
* contract with its synchronous `AskRequest.probeReason` twin (one string, two faces) and a
|
|
188
|
+
* one-face re-render would break it. A consumer rendering this seat owes its own screen (or the
|
|
189
|
+
* descriptor family's display baseline). */
|
|
161
190
|
probeReason?: string;
|
|
162
191
|
/** backlog #239 (additive): the STRUCTURED cause, for a probe whose verdict this engine understands —
|
|
163
192
|
* the built-in shell probe is the first. Carries a machine-readable {@link ProbeCause.code} and the
|
|
@@ -178,7 +207,12 @@ export interface RiskDescriptor {
|
|
|
178
207
|
* command text. */
|
|
179
208
|
export interface ProbeCauseOperands {
|
|
180
209
|
/** The named entries. Each `inlineUntrusted`-neutralized and capped at {@link PROBE_CAUSE_PATH_MAX}
|
|
181
|
-
* INDIVIDUALLY, so one pathological entry costs only itself. Bounded in count by the minting probe.
|
|
210
|
+
* INDIVIDUALLY, so one pathological entry costs only itself. Bounded in count by the minting probe.
|
|
211
|
+
* RESIDUAL, disclosed (#438): `inlineUntrusted` does NOT strip the `\p{Cf}` display-reordering
|
|
212
|
+
* class the descriptor's own display family strips — a directional/zero-width marker in a path
|
|
213
|
+
* survives on this seat (cross-face byte parity with the synchronous `AskRequest.probeCause`
|
|
214
|
+
* twin; see that same-value contract for why a one-face re-render is off the table). A consumer
|
|
215
|
+
* rendering these entries owes its own screen (or the descriptor family's display baseline). */
|
|
182
216
|
readonly shown: readonly string[];
|
|
183
217
|
/** How many entries the family has in total — `shown.length` when nothing was elided. A non-negative
|
|
184
218
|
* integer; never a lie by omission. */
|
|
@@ -218,11 +252,6 @@ export interface ProbeCause {
|
|
|
218
252
|
* that sanitizes at the entry boundary and the descriptor builder that re-sanitizes at the persist
|
|
219
253
|
* boundary cannot cap the same value at two different lengths. */
|
|
220
254
|
export declare const PROBE_REASON_MAX = 200;
|
|
221
|
-
/** backlog #239: the per-ENTRY ceiling inside a {@link ProbeCause} operand family, sized like the
|
|
222
|
-
* descriptor's own {@link RiskDescriptor.touchedPaths} entries (the same thing on the same card). Per
|
|
223
|
-
* entry, not per cause: that is the whole point of the structured shape — one pathological path costs
|
|
224
|
-
* only itself, where a single joined string let it consume every other entry's room. */
|
|
225
|
-
export declare const PROBE_CAUSE_PATH_MAX = 200;
|
|
226
255
|
/**
|
|
227
256
|
* backlog #239: the ONE validator for a {@link ProbeCause} arriving from a probe.
|
|
228
257
|
*
|
|
@@ -346,11 +375,19 @@ export interface PendingSteerInput {
|
|
|
346
375
|
* degrade an inbox preview, never a permission outcome.
|
|
347
376
|
*
|
|
348
377
|
* **Redaction is load-bearing** (the `summary`/`touchedPaths` are PERSISTED + surfaced to a human inbox):
|
|
349
|
-
* every model-controlled value
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* `
|
|
378
|
+
* every model-controlled value in the DESCRIPTOR-OWNED display family (`summary` both arms,
|
|
379
|
+
* `touchedPaths`, `shadowedRule`) goes through
|
|
380
|
+
* {@link import("./permission-rule-model.js").renderUntrustedCommandText} — which STRIPS the `\p{Cf}`
|
|
381
|
+
* format class (directional controls / zero-width formatters, the display-reordering hazard the
|
|
382
|
+
* `hasBidiControls` bit warns about; #438) and then applies the shared inline form (folds CR/LF/Unicode
|
|
383
|
+
* separators to one space, defuses `</system-reminder>` variants + `<<<`/`>>>` fence sentinels) — AND a
|
|
384
|
+
* length cap, so a crafted arg carrying a break-out tag / newline / huge string / RLO can neither inject
|
|
385
|
+
* into the inbox render nor display as something other than what runs nor bloat the durable row.
|
|
386
|
+
* TWO SEATS ARE DELIBERATELY NOT IN THE FAMILY: `probeCause` and `probeReason` carry a documented
|
|
387
|
+
* SAME-VALUE contract with their synchronous `AskRequest` twins (their docs say so verbatim), so
|
|
388
|
+
* changing their bytes here alone would break that cross-face contract — they keep the shared
|
|
389
|
+
* `inlineUntrusted`/`normalizeProbeCause` treatment (a residual their docs disclose). NEVER dumps full
|
|
390
|
+
* args / env / secrets — only a bash command string or a short `name=value` digest of the SHOWN args.
|
|
354
391
|
*
|
|
355
392
|
* `summary` for a shell gate = the `command` string (capped 200 cp); otherwise a `name=value` digest of the
|
|
356
393
|
* call's top-level string/number/boolean args (each value capped), omitted when nothing safe to summarize.
|
|
@@ -728,6 +765,33 @@ export type PendingAction = {
|
|
|
728
765
|
* pre-1a.2 checkpoint (deserialized without it), in which case the resume skips the hash check.
|
|
729
766
|
*/
|
|
730
767
|
boundInputHash: string;
|
|
768
|
+
/**
|
|
769
|
+
* #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
|
|
770
|
+
* older reader ignores. Unlike that precedent seat — which resume DOES read, through its
|
|
771
|
+
* verify-then-adopt port — nothing reads THIS bit at all, per the echo-only clause below):
|
|
772
|
+
* PRESENCE ONLY — the row's execution
|
|
773
|
+
* payload carries at least one DIRECTIONAL format control. The durable twin of the synchronous
|
|
774
|
+
* {@link import("./tool-policy.js").AskRequest.hasBidiControls} (ONE contract, two faces — that
|
|
775
|
+
* field's doc owns the character class, the hazard and the warning-not-verdict posture; everything
|
|
776
|
+
* there applies here unchanged). What is stated per face:
|
|
777
|
+
*
|
|
778
|
+
* - JUDGED AT THE PARK MINT over THIS ROW's materialized payload: the fidelity-projected `args`
|
|
779
|
+
* snapshot (the bytes a human approves and a resume executes) plus the row's own `preview`
|
|
780
|
+
* projection. The row's bit describes the row's bytes — on a shape-narrowing backend (a Map key
|
|
781
|
+
* under a JSON codec) a marker the synchronous face detected can be GONE from the row, and the
|
|
782
|
+
* honest bit for the row is then ABSENT: the inbox renders the row's snapshot, and the bit
|
|
783
|
+
* agrees with what it renders. The two faces coincide whenever the projection is shape-preserving.
|
|
784
|
+
* - `true` or ABSENT, never `false` (absence = "not detected": clean, or the bounded scan did not
|
|
785
|
+
* reach it — a `false` would claim a proof the scan does not offer).
|
|
786
|
+
* - A DEPLOYMENT-WRITTEN park row (the design/144 convention's deployment half) may carry `true`
|
|
787
|
+
* (taken at its word — over-warning is the safe direction, and a writer with row access holds
|
|
788
|
+
* the args themselves already) or omit it (the summary projection then runs its own bounded
|
|
789
|
+
* backfill scan — see {@link CheckpointSummary.hasBidiControls}). A written `false` is
|
|
790
|
+
* out-of-contract and is read as ABSENT everywhere; it never reaches a projection.
|
|
791
|
+
* - ECHO-ONLY, like {@link CheckpointSummary.toolInput}: NO resume / gate / CAS /
|
|
792
|
+
* `winnerFromOutcome` path reads it — it is a display warning, never a control input.
|
|
793
|
+
*/
|
|
794
|
+
hasBidiControls?: true;
|
|
731
795
|
/** All tool-call ids in the suspending assistant message, in emission order. */
|
|
732
796
|
batchToolCallIds: string[];
|
|
733
797
|
/** Ids of calls already executed (results in the session) when the suspend fired — #1..k-1. */
|
|
@@ -1615,6 +1679,39 @@ export interface CheckpointSummary {
|
|
|
1615
1679
|
* contextually escape), ECHO-ONLY (no gate/CAS/resume reads it), and surfaces SHOULD show the
|
|
1616
1680
|
* bound raw args (`toolInput`/`pendingAction.args`) alongside — the preview never replaces them. */
|
|
1617
1681
|
preview?: unknown;
|
|
1682
|
+
/**
|
|
1683
|
+
* #438: PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1684
|
+
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
1685
|
+
* that doc owns the hazard). Only ever for a `tool_approval` pendingAction; OMITTED for every other
|
|
1686
|
+
* kind (the {@link toolCallId} ABSENCE-SHAPE contract applies: never `null`, never present-but-
|
|
1687
|
+
* `undefined`, never `false` — a re-projecting BFF must not materialize any of those on the way out).
|
|
1688
|
+
*
|
|
1689
|
+
* WHERE THE ANSWER COMES FROM: the row's own persisted bit when present (the park mint is the
|
|
1690
|
+
* authority — one look, one answer, never re-derived); else a bounded BACKFILL scan run AT PROJECTION
|
|
1691
|
+
* TIME over this projection's OWN bounded materials — the truncated {@link toolInput} string just
|
|
1692
|
+
* derived plus the row's `preview`, under a small per-row budget — so a pre-#438 row and a
|
|
1693
|
+
* deployment-written row still get a bit exactly where the inbox reads. The backfill judges THE BYTES
|
|
1694
|
+
* THIS PROJECTION HANDS THE INBOX: a marker buried past the `toolInput` truncation point (or past the
|
|
1695
|
+
* per-row scan budget) is honestly reported as absent — new-engine-minted rows carry the mint-time bit
|
|
1696
|
+
* (judged over the full parked args) for exactly that reason. The backfill NEVER writes back to the
|
|
1697
|
+
* row: `summarizeCheckpoint` stays a pure projection.
|
|
1698
|
+
*
|
|
1699
|
+
* HOW TO READ IT: `true` = core detected the class in this row's payload (or took a deployment row's
|
|
1700
|
+
* own `true` at its word — over-warning is the safe direction); render a visible "the text below can
|
|
1701
|
+
* display differently from what it runs" annotation. ABSENT = NOT DETECTED, never "clean" — it covers
|
|
1702
|
+
* clean, budget-exhausted, and a `false`-writing out-of-contract producer (read as absent, never
|
|
1703
|
+
* projected). The bit changes NO rendering obligation a surface already had: with or without it,
|
|
1704
|
+
* `toolInput` / `toolName` / a summary string go through
|
|
1705
|
+
* {@link import("./permission-rule-model.js").renderUntrustedCommandText} (or the surface's own
|
|
1706
|
+
* screen) before a human reads them; for the structured `args`/`preview` the FIRST-CHOICE display
|
|
1707
|
+
* material is the bounded projections core already minted (`toolInput`, the descriptor `summary`) —
|
|
1708
|
+
* do not re-serialize the whole args for a list render, and a drill-in that must show everything owes
|
|
1709
|
+
* itself a TOTAL, bounded traversal (cycle-safe, per-type output forms, throwing getters to a
|
|
1710
|
+
* placeholder, depth/node/char budgets with a truncation marker — never "full `JSON.stringify` then
|
|
1711
|
+
* truncate", which does all the work and takes all the throws before the cap). ECHO-ONLY, like
|
|
1712
|
+
* {@link sourceTaskId}: a warning to a person, never a control input — resume/gate/CAS never read it.
|
|
1713
|
+
*/
|
|
1714
|
+
hasBidiControls?: true;
|
|
1618
1715
|
}
|
|
1619
1716
|
/**
|
|
1620
1717
|
* design/80 assistant-scheduler seam #1: project a single PENDING {@link Checkpoint} to its lightweight
|