@sema-agent/core 5.59.0 → 5.60.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 +61 -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/stream-engine.js +9 -1
- package/dist/core/auto-compaction.js +2 -2
- package/dist/core/checkpoint-store.d.ts +172 -25
- 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 +1 -1
- package/dist/core/governance-codes.js +5 -0
- package/dist/core/locked-config.d.ts +36 -4
- package/dist/core/locked-config.js +34 -1
- package/dist/core/mcp.d.ts +4 -5
- package/dist/core/mcp.js +10 -6
- 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 +5 -6
- 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/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 +107 -8
- package/dist/core/runner/runtask.js +97 -9
- package/dist/core/store-contracts/checkpoint-store-contract.js +32 -0
- package/dist/core/tool-policy.d.ts +32 -10
- package/dist/core/tool-policy.js +3 -3
- package/dist/core/tools.js +1 -1
- package/dist/core/trace.d.ts +36 -0
- package/dist/core/types.d.ts +127 -2
- 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 +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/fs-bash.js +1 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.60.1 — 2026-08-25
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Resume-leg usage-governance holes (#449, P0/P1)**: a resume no longer burns a full model turn
|
|
7
|
+
when the usage window is exhausted — an owed row (approval/review decision, wake, parked steer,
|
|
8
|
+
bg snapshot) refuses PRE-CAS with the new `resume.usage_window_exhausted` CheckpointError
|
|
9
|
+
(`detail.retryAfterMs`; the row stays pending, zero consumption, the same token redeems after the
|
|
10
|
+
window); a bare resource_limit continue re-suspends at turn 0 through the same platform-limit leg
|
|
11
|
+
the turn boundary uses (fresh checkpoint, new `resumeAfterMs`, zero model calls). The resume
|
|
12
|
+
ledger key now falls back to the checkpoint-pinned principal (`resource_limit` mints stamp
|
|
13
|
+
`cp.principal`) — a caller that omits principal no longer silently charges the GLOBAL bucket and
|
|
14
|
+
collects a fresh quota; supplying a DIFFERENT principal refuses loudly
|
|
15
|
+
(`resume.principal_mismatch`, pre-CAS, row pending). NARROWING, named: mismatched-principal
|
|
16
|
+
resumes previously proceeded under the wrong identity.
|
|
17
|
+
- **covering-ask audit arm (#450, core half)**: the prepare-time policy-name audit gains ask-slot
|
|
18
|
+
coverage pins (the exemption for `mcp__<server>__*` spellings now proves it speaks the truth
|
|
19
|
+
against a live in-engine ask lane); `isNamespacedCoveringRuleName`/`namespacedRuleNameCovers`
|
|
20
|
+
root-exported (additive) so out-of-engine name-set publishers can self-check the covering grammar
|
|
21
|
+
(the fifth-publisher fail-open shape stays pinned as-is until the server-side consumer lands its
|
|
22
|
+
fix). Export surface 1758 → 1760.
|
|
23
|
+
|
|
24
|
+
## 5.60.0 — 2026-08-25
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **CC clearing machine + micro-compact recovery (design/374 slices 1a/1b/2/4; every default byte-identical)**:
|
|
28
|
+
`ContextEditOptions.machine: "legacy" | "cc"` selects the clearing machine — `cc` is CC's rejection-leg
|
|
29
|
+
form carried whole (keep 5, a >=20000-token minimum-savings gate with the field-exact `Sby` estimator,
|
|
30
|
+
one deep clear, a window base over ALL compactable occurrences, five cleared-predicate forms);
|
|
31
|
+
`RunnerDeps.microCompact { machine, clearOnRejection }` (both default off). A typed `input_too_long`
|
|
32
|
+
cause classifies real provider context refusals (position-0 anchored family over the parsed envelope
|
|
33
|
+
diagnostic; reflected copies stay ruled out) and feeds an MC-R recovery arm at the head of the
|
|
34
|
+
recovery chain (attempt-1 latch; plan over the rejected projection; a projection LEDGER — occurrence
|
|
35
|
+
keys (toolCallId, timestamp, dup ordinal), new-machine writes only — is the clear's sole survival
|
|
36
|
+
channel across per-request rebuilds). `resolveTriggerWindow` becomes the one trigger-window source
|
|
37
|
+
(four consumers); a clamped window announces `config.autocompact_window_clamped` once per task.
|
|
38
|
+
Trace frames `context.mc_clear`/`context.mc_null`. Nine CC divergences registered (cc-parity ledger).
|
|
39
|
+
- **Durable park rows carry the bidi twin (design/377, #438)**: `PendingAction.tool_approval.hasBidiControls?: true`
|
|
40
|
+
minted at park over the fidelity-projected args snapshot + preview; `CheckpointSummary.hasBidiControls`
|
|
41
|
+
projected, bit-less legacy rows BACKFILLED at projection from their own bounded materials (32K units /
|
|
42
|
+
512 nodes per row; never written back; a stored `false` reads as absent). Checkpoint version unchanged.
|
|
43
|
+
- **MCP entry-level content-trust declaration (design/378, #439; default byte-identical)**:
|
|
44
|
+
`McpServerSpec.contentOrigin?: "local" | "execution" | "external"` (the existing ToolContentOrigin
|
|
45
|
+
vocabulary), minted once at intake from a frozen copy; `memory.content_class_declared` announces one
|
|
46
|
+
per-value line per declared entry per prepared leg (explicit `"external"` announces too — it
|
|
47
|
+
forecloses the trustedTools exemption); malformed declarations refuse at preparation
|
|
48
|
+
(`config.mcp_content_class`).
|
|
49
|
+
|
|
50
|
+
### Changed — narrowings (named)
|
|
51
|
+
- **Inbox display family goes Cf-safe (two byte generations)**: descriptor-owned `RiskDescriptor` seats
|
|
52
|
+
(`summary` both arms, `touchedPaths`, `shadowedRule`) render through `renderUntrustedCommandText` —
|
|
53
|
+
newly-minted rows strip the `\p{Cf}` format class (emoji ZWJ sequences display decomposed); persisted
|
|
54
|
+
rows keep their old bytes forever. Byte-pinning consumers must read per row generation.
|
|
55
|
+
- **Confirm-face refusal messages stop interpolating raw command bytes (#430)**: >120-code-point
|
|
56
|
+
commands cap with an ellipsis, control bytes render `\uXXXX`; short clean commands byte-identical.
|
|
57
|
+
Two `record_malformed` damage sentences escape record-sourced values.
|
|
58
|
+
- **Vocabulary-outside `declared` content origins now classify `external`** (previously escaped marking
|
|
59
|
+
silently — a fail-open hole closed; untyped host-mounted tools with garbage declarations are the
|
|
60
|
+
affected shape).
|
|
61
|
+
- **`approvalRecordDamageOf` gains the reverse integrity clause (#441)**: an orphan candidate row
|
|
62
|
+
(belonging to no offer) answers `record_malformed` at all three consent entries.
|
|
63
|
+
|
|
3
64
|
## 5.59.0 — 2026-08-24
|
|
4
65
|
|
|
5
66
|
### BREAKING (narrow, named)
|
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
|
+
}
|
|
@@ -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. */
|
|
@@ -1199,7 +1263,7 @@ export declare const FACE_CHECKPOINT_VERSION = 9;
|
|
|
1199
1263
|
* (binding human/irreversible_ask), v4 (a pre-164 row, refused only when it carries the retired
|
|
1200
1264
|
* wall-clock allocation), v5 (token-allocation-bearing resource/approval), v6 (org-admission freeze
|
|
1201
1265
|
* state), v7 (F-012 constraint-chain / delegation-provenance enforcement state), v8 (a
|
|
1202
|
-
* non-budgetable `realApproval` gate bit with its org-origin resume semantics)
|
|
1266
|
+
* non-budgetable `realApproval` gate bit with its org-origin resume semantics), v9 (the
|
|
1203
1267
|
* design/199 read-face section: resolved face + deny-set additions, folded stricter-wins on
|
|
1204
1268
|
* resume). */
|
|
1205
1269
|
export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 9;
|
|
@@ -1487,11 +1551,21 @@ export interface Checkpoint {
|
|
|
1487
1551
|
sourceTaskId?: string;
|
|
1488
1552
|
/**
|
|
1489
1553
|
* design S1d (source-tag persistence): the issuing task's authenticated end-user {@link
|
|
1490
|
-
* import("./types.js").TaskSpec.principal} (design/62), stamped at the APPROVAL suspend mint
|
|
1491
|
-
*
|
|
1492
|
-
* import("./tool-policy.js").AskRequest}.principal. Caller-set
|
|
1493
|
-
* {@link sourceTaskId} it is **
|
|
1494
|
-
* reads it
|
|
1554
|
+
* import("./types.js").TaskSpec.principal} (design/62), stamped at the APPROVAL suspend mint — and, since
|
|
1555
|
+
* #449 G2, at the `resource_limit` mint too — so an aggregating inbox can attribute a paused checkpoint
|
|
1556
|
+
* per-user — the durable analog of {@link import("./tool-policy.js").AskRequest}.principal. Caller-set
|
|
1557
|
+
* (never a tool/worker arg), and like {@link sourceTaskId} it is **NEVER a gate input**: no gate / CAS /
|
|
1558
|
+
* winner path reads it. The RESUME ENTRY however does read it as the row's identity record (#449 G2):
|
|
1559
|
+
* a resume config that omits `principal` inherits this value for the resumed leg (usage-ledger bucket,
|
|
1560
|
+
* later mints' scope derivation, ask attribution — a dropped field must not silently move the leg onto
|
|
1561
|
+
* the SHARED usage bucket), and a resume config carrying a DIFFERENT non-empty principal is refused
|
|
1562
|
+
* pre-CAS (`resume.principal_mismatch`). Absent ⇒ no principal (a legacy checkpoint, an unauthenticated
|
|
1563
|
+
* task, or a gate kind that predates the stamp). Rolling-deployment note (#449 G2 residual): the field
|
|
1564
|
+
* is NOT version-gated — an older worker accepts a principal-bearing row and keeps its historic
|
|
1565
|
+
* behavior (no fallback, no mismatch refusal); the identity-continuity guarantee holds once the fleet
|
|
1566
|
+
* is on enforcing workers. Version-gating it is a recorded design candidate: the lattice's newest rung
|
|
1567
|
+
* (design/199) encodes "v>=9 ⟺ face section present", so ANY new version above it must rewrite that
|
|
1568
|
+
* guard's window form and add its own presence invariant — a lattice-wide change, not a stamp.
|
|
1495
1569
|
*/
|
|
1496
1570
|
principal?: string;
|
|
1497
1571
|
/**
|
|
@@ -1615,6 +1689,39 @@ export interface CheckpointSummary {
|
|
|
1615
1689
|
* contextually escape), ECHO-ONLY (no gate/CAS/resume reads it), and surfaces SHOULD show the
|
|
1616
1690
|
* bound raw args (`toolInput`/`pendingAction.args`) alongside — the preview never replaces them. */
|
|
1617
1691
|
preview?: unknown;
|
|
1692
|
+
/**
|
|
1693
|
+
* #438: PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1694
|
+
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
1695
|
+
* that doc owns the hazard). Only ever for a `tool_approval` pendingAction; OMITTED for every other
|
|
1696
|
+
* kind (the {@link toolCallId} ABSENCE-SHAPE contract applies: never `null`, never present-but-
|
|
1697
|
+
* `undefined`, never `false` — a re-projecting BFF must not materialize any of those on the way out).
|
|
1698
|
+
*
|
|
1699
|
+
* WHERE THE ANSWER COMES FROM: the row's own persisted bit when present (the park mint is the
|
|
1700
|
+
* authority — one look, one answer, never re-derived); else a bounded BACKFILL scan run AT PROJECTION
|
|
1701
|
+
* TIME over this projection's OWN bounded materials — the truncated {@link toolInput} string just
|
|
1702
|
+
* derived plus the row's `preview`, under a small per-row budget — so a pre-#438 row and a
|
|
1703
|
+
* deployment-written row still get a bit exactly where the inbox reads. The backfill judges THE BYTES
|
|
1704
|
+
* THIS PROJECTION HANDS THE INBOX: a marker buried past the `toolInput` truncation point (or past the
|
|
1705
|
+
* per-row scan budget) is honestly reported as absent — new-engine-minted rows carry the mint-time bit
|
|
1706
|
+
* (judged over the full parked args) for exactly that reason. The backfill NEVER writes back to the
|
|
1707
|
+
* row: `summarizeCheckpoint` stays a pure projection.
|
|
1708
|
+
*
|
|
1709
|
+
* HOW TO READ IT: `true` = core detected the class in this row's payload (or took a deployment row's
|
|
1710
|
+
* own `true` at its word — over-warning is the safe direction); render a visible "the text below can
|
|
1711
|
+
* display differently from what it runs" annotation. ABSENT = NOT DETECTED, never "clean" — it covers
|
|
1712
|
+
* clean, budget-exhausted, and a `false`-writing out-of-contract producer (read as absent, never
|
|
1713
|
+
* projected). The bit changes NO rendering obligation a surface already had: with or without it,
|
|
1714
|
+
* `toolInput` / `toolName` / a summary string go through
|
|
1715
|
+
* {@link import("./permission-rule-model.js").renderUntrustedCommandText} (or the surface's own
|
|
1716
|
+
* screen) before a human reads them; for the structured `args`/`preview` the FIRST-CHOICE display
|
|
1717
|
+
* material is the bounded projections core already minted (`toolInput`, the descriptor `summary`) —
|
|
1718
|
+
* do not re-serialize the whole args for a list render, and a drill-in that must show everything owes
|
|
1719
|
+
* itself a TOTAL, bounded traversal (cycle-safe, per-type output forms, throwing getters to a
|
|
1720
|
+
* placeholder, depth/node/char budgets with a truncation marker — never "full `JSON.stringify` then
|
|
1721
|
+
* truncate", which does all the work and takes all the throws before the cap). ECHO-ONLY, like
|
|
1722
|
+
* {@link sourceTaskId}: a warning to a person, never a control input — resume/gate/CAS never read it.
|
|
1723
|
+
*/
|
|
1724
|
+
hasBidiControls?: true;
|
|
1618
1725
|
}
|
|
1619
1726
|
/**
|
|
1620
1727
|
* design/80 assistant-scheduler seam #1: project a single PENDING {@link Checkpoint} to its lightweight
|
|
@@ -1715,6 +1822,23 @@ export declare class CheckpointError extends Error {
|
|
|
1715
1822
|
* thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
|
|
1716
1823
|
* stays `pending`); re-resume with the full original chain. */
|
|
1717
1824
|
| "resume.parent_constraint_mismatch"
|
|
1825
|
+
/** #449 G1 (codex r2, pre-CAS entrance): a deployment usage-governance window for this resume's
|
|
1826
|
+
* ledger key (the config's principal, else the row's recorded one, else the shared bucket) is
|
|
1827
|
+
* EXHAUSTED, and the row still OWES a delivery a re-mint cannot carry (an approval/review
|
|
1828
|
+
* decision, a wake, parked steers, a background-task obituary snapshot). Refused BEFORE the CAS,
|
|
1829
|
+
* so nothing is consumed and nothing is unpinned — the SAME token with the SAME decision is
|
|
1830
|
+
* redeemable once the window frees (`detail.retryAfterMs` carries the wait). A bare
|
|
1831
|
+
* `resource_limit` continue with nothing parked does NOT take this refusal: it proceeds and
|
|
1832
|
+
* re-suspends at turn 0 into a FRESH checkpoint carrying the new wait hint. */
|
|
1833
|
+
| "resume.usage_window_exhausted"
|
|
1834
|
+
/** #449 G2 (identity continuity): the resume config carries a NON-EMPTY `principal` that differs from
|
|
1835
|
+
* the {@link Checkpoint.principal} recorded at the suspend mint. `ResumeTaskConfig`'s contract is
|
|
1836
|
+
* "the same TaskSpec the run suspended with", and the principal keys real lanes on the resumed leg
|
|
1837
|
+
* (the usage-governance ledger bucket, later mints' scope derivation, session-rule reads, ask
|
|
1838
|
+
* attribution) — silently running the leg under a different identity is the bad-value direction this
|
|
1839
|
+
* refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
|
|
1840
|
+
* original principal, or omit the field to inherit the recorded one. */
|
|
1841
|
+
| "resume.principal_mismatch"
|
|
1718
1842
|
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1719
1843
|
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1720
1844
|
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
@@ -1742,6 +1866,9 @@ export declare class CheckpointError extends Error {
|
|
|
1742
1866
|
* discriminant. */
|
|
1743
1867
|
readonly detail?: {
|
|
1744
1868
|
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
1869
|
+
/** #449 G1 — on `resume.usage_window_exhausted` ONLY: how long until the binding window frees
|
|
1870
|
+
* (the same wait a suspended row carries as `gate.resumeAfterMs`). Absent on every other code. */
|
|
1871
|
+
retryAfterMs?: number;
|
|
1745
1872
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1746
1873
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
1747
1874
|
* damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
|
|
@@ -1844,6 +1971,23 @@ export declare class CheckpointError extends Error {
|
|
|
1844
1971
|
* thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
|
|
1845
1972
|
* stays `pending`); re-resume with the full original chain. */
|
|
1846
1973
|
| "resume.parent_constraint_mismatch"
|
|
1974
|
+
/** #449 G1 (codex r2, pre-CAS entrance): a deployment usage-governance window for this resume's
|
|
1975
|
+
* ledger key (the config's principal, else the row's recorded one, else the shared bucket) is
|
|
1976
|
+
* EXHAUSTED, and the row still OWES a delivery a re-mint cannot carry (an approval/review
|
|
1977
|
+
* decision, a wake, parked steers, a background-task obituary snapshot). Refused BEFORE the CAS,
|
|
1978
|
+
* so nothing is consumed and nothing is unpinned — the SAME token with the SAME decision is
|
|
1979
|
+
* redeemable once the window frees (`detail.retryAfterMs` carries the wait). A bare
|
|
1980
|
+
* `resource_limit` continue with nothing parked does NOT take this refusal: it proceeds and
|
|
1981
|
+
* re-suspends at turn 0 into a FRESH checkpoint carrying the new wait hint. */
|
|
1982
|
+
| "resume.usage_window_exhausted"
|
|
1983
|
+
/** #449 G2 (identity continuity): the resume config carries a NON-EMPTY `principal` that differs from
|
|
1984
|
+
* the {@link Checkpoint.principal} recorded at the suspend mint. `ResumeTaskConfig`'s contract is
|
|
1985
|
+
* "the same TaskSpec the run suspended with", and the principal keys real lanes on the resumed leg
|
|
1986
|
+
* (the usage-governance ledger bucket, later mints' scope derivation, session-rule reads, ask
|
|
1987
|
+
* attribution) — silently running the leg under a different identity is the bad-value direction this
|
|
1988
|
+
* refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
|
|
1989
|
+
* original principal, or omit the field to inherit the recorded one. */
|
|
1990
|
+
| "resume.principal_mismatch"
|
|
1847
1991
|
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1848
1992
|
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1849
1993
|
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
@@ -1871,6 +2015,9 @@ export declare class CheckpointError extends Error {
|
|
|
1871
2015
|
* discriminant. */
|
|
1872
2016
|
detail?: {
|
|
1873
2017
|
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
2018
|
+
/** #449 G1 — on `resume.usage_window_exhausted` ONLY: how long until the binding window frees
|
|
2019
|
+
* (the same wait a suspended row carries as `gate.resumeAfterMs`). Absent on every other code. */
|
|
2020
|
+
retryAfterMs?: number;
|
|
1874
2021
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1875
2022
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
1876
2023
|
* damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
|