@sema-agent/core 5.34.0 → 5.36.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 +104 -0
- package/dist/agents/subagent.js +29 -2
- package/dist/core/auto-compaction.d.ts +23 -0
- package/dist/core/auto-compaction.js +8 -0
- package/dist/core/checkpoint-store.d.ts +49 -4
- package/dist/core/context-guard.d.ts +41 -0
- package/dist/core/context-guard.js +76 -0
- package/dist/core/hooks.d.ts +98 -3
- package/dist/core/hooks.js +146 -8
- package/dist/core/memory-engine/engine.js +1 -1
- package/dist/core/park-selfcheck.d.ts +161 -0
- package/dist/core/park-selfcheck.js +251 -0
- package/dist/core/runner/assemble-result.d.ts +3 -0
- package/dist/core/runner/assemble-result.js +3 -0
- package/dist/core/runner/git-status-frame.d.ts +219 -0
- package/dist/core/runner/git-status-frame.js +212 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
- package/dist/core/runner/prepare-task.d.ts +28 -4
- package/dist/core/runner/prepare-task.js +86 -52
- package/dist/core/runner/runtask.d.ts +6 -1
- package/dist/core/runner/runtask.js +330 -19
- package/dist/core/task-registry-agent.d.ts +15 -0
- package/dist/core/task-registry-agent.js +9 -0
- package/dist/core/task-registry.d.ts +3 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +125 -0
- package/dist/core/tool-policy.js +35 -2
- package/dist/core/types.d.ts +98 -9
- package/dist/engine/harness/types.d.ts +65 -1
- package/dist/engine/harness/types.js +20 -0
- package/dist/engine/session/import-validate.js +10 -1
- package/dist/engine/session/session.d.ts +37 -1
- package/dist/engine/session/session.js +56 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/internal/harness.d.ts +2 -0
- package/dist/internal/harness.js +2 -0
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompts/default.d.ts +20 -7
- package/dist/prompts/default.js +2 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.36.0 — 2026-08-15
|
|
4
|
+
|
|
5
|
+
No BREAKING changes. Upgrade note: the system-prompt shape change resets provider prompt caches
|
|
6
|
+
ONCE on upgrade — the point of the feature is that they stop resetting after that.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Env-tail prefix stability (#254): the turn-dynamic git facts (branch line, working-tree line,
|
|
11
|
+
status snapshot) leave the system prompt for a value-keyed `git_status` turn frame. The system
|
|
12
|
+
prompt stays byte-stable across legs while the working tree moves, so provider prefix caches
|
|
13
|
+
survive leg/resume/fork boundaries (live-verified cache hits past the static-section watermark).
|
|
14
|
+
The frame re-sends only when its `(kind, hash)` changes; kind flips announce a tombstone once
|
|
15
|
+
("may be stale" / "no longer a git repository"); recovery re-asserts loudly. Compaction re-asserts
|
|
16
|
+
the current frame beside the summary on all three landing paths (boundary / PTL recovery /
|
|
17
|
+
end-of-task, one single-source hook). Announcement mirrors are branch-authoritative with a
|
|
18
|
+
transcript-side newest-frame arbiter: the transcript is the truth, a mirror that cannot be audited
|
|
19
|
+
degrades to one conservative re-announcement.
|
|
20
|
+
- Wire/durable additions for #254: `steering_injected.source` gains `git_status` (preview is a
|
|
21
|
+
CONSTANT phrase per frame kind — repo text never enters the event face); durable session entry
|
|
22
|
+
`git_announcement`, compaction `details.gitAnnouncement`, `CheckpointState.gitAnnouncement` (all
|
|
23
|
+
additive, no version bump — absent fields resume with one conservative re-announcement); `Session`
|
|
24
|
+
gains two OPTIONAL members (`appendGitAnnouncement`/`getGitAnnouncement`); event-registry row
|
|
25
|
+
`git_status`. Existing sessions record one `legacy_migration` pin on first resume (env probe
|
|
26
|
+
sentinel `probe-env@2`).
|
|
27
|
+
- New `TaskResult.errorCode`: `irreducible_core_over_budget` — the request budget cannot fit even
|
|
28
|
+
the compaction summaries plus the DEGRADED git frame; the run fails loudly instead of shipping a
|
|
29
|
+
dishonest request. The frame has a trim-protected replace-by-key slot with exactly one
|
|
30
|
+
deterministic degrade shrink; H4 probe failures degrade to a two-line branch+dirty frame instead
|
|
31
|
+
of silently omitting the section; the probe sentinel mis-split arm is loud.
|
|
32
|
+
- Fleet row generation out-line (#258): `task_progress` ticks and `BackgroundChildEvent` spawn/tick
|
|
33
|
+
frames from background-agent rows carry `seq` — the row's stop-cycle generation (fresh spawn = 1,
|
|
34
|
+
every launched revival bumps it), one axis with `TaskNotificationPayload.seq` — so a fleet
|
|
35
|
+
consumer can tell a LATE first frame from a REVIVED cycle's frame. BCE terminal frames fall back
|
|
36
|
+
to the registry generation when the notify-mirror carrier is absent (one run speaks one number;
|
|
37
|
+
the notification payload's design/144 seq contract is unchanged). Absence stays a fact: sync
|
|
38
|
+
children, workflow agents and top-level runs carry nothing — never read absence as "cycle 1".
|
|
39
|
+
Hardening shipped with it: a parked-born resume speaks ONE generation end to end (the consume
|
|
40
|
+
flip's target), a retained wake realigns forward past a foreign tier-3 cycle's durable advance,
|
|
41
|
+
and BCE ticks always stamp the ENCLOSING row's generation.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- `buildEnvironmentContext` no longer renders gitBranch/gitDirty/gitSnapshot (fields remain as
|
|
46
|
+
data; the snapshot rides the `git_status` frame). Pre-call budget estimates now include the frame
|
|
47
|
+
bytes (an informed tightening). Frame bodies clamp astral-safe (U+FFFD at a split code point).
|
|
48
|
+
- Merged-code rescan hardening (18 findings across two rounds, all verified then disposed): the
|
|
49
|
+
degraded-shrink replacement inserts via thunk (a branch named with `$&`/`$$` — legal ref bytes —
|
|
50
|
+
no longer expands match context into the frame or silently renames itself; same class swept to
|
|
51
|
+
`buildMemoryInstruction`); the irreducible core charges the frame segment + summaries only, and a
|
|
52
|
+
trim-dropped carrier is never resurrected — the guard re-inserts the frame SEGMENT as its own
|
|
53
|
+
engine-minted request-view message, so the trim's adjudication stands and the request stays
|
|
54
|
+
servable; the listing-replay scan strips positive git frames before parsing (a commit subject
|
|
55
|
+
spelling a roster header can no longer reset the announced set); the degraded frame joins the
|
|
56
|
+
request-lossy disclosure; old-checkpoint resume compat re-pinned honestly (one conservative
|
|
57
|
+
re-announcement); the PTL-recovery landing path gained real coverage.
|
|
58
|
+
- `probeParkRoundTrip` JSDoc names its write side loudly (diagnostic probe, not a health-poll
|
|
59
|
+
body).
|
|
60
|
+
|
|
61
|
+
## 5.35.0 — 2026-08-15
|
|
62
|
+
|
|
63
|
+
No BREAKING changes. One deliberate fail-closed tighten and one loosening-direction fix are called
|
|
64
|
+
out under Changed; everything else is additive.
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- `TaskStream.steer` takes an optional `inputId` dedup key (additive; absent ⇒ byte-identical
|
|
69
|
+
behavior). The live steer leg becomes idempotent the way the parked leg already was: an identical
|
|
70
|
+
payload under a seen key is a no-op (nothing injected, no second ledger frame), a DIFFERENT payload
|
|
71
|
+
under a seen key is refused loudly (`steering.duplicate_input_id`, the parked leg's code), and the
|
|
72
|
+
caller's key rides the `human_input` frame verbatim (previously a fresh uuid was always minted, so
|
|
73
|
+
a replayed injection was indistinguishable from an idempotent hit in the receipt). Validation runs
|
|
74
|
+
before liveness (empty/overlong/reserved-sentinel values are refused, never silently treated as
|
|
75
|
+
"no key"). Dedup scope is the live stream's lifetime — narrower than the parked queue's persisted
|
|
76
|
+
window; the contract states the asymmetry rather than pretending parity.
|
|
77
|
+
- `AskRequest.ruleEvidence` (design/252 G-2): an ask carries the rule-provenance evidence behind it —
|
|
78
|
+
the org snapshot `revision`, the org rule text, and the matched personal rule's add dots — each
|
|
79
|
+
member present as a value or as a NAMED absence (`not_wired` / `not_adjudicated` / `unavailable` /
|
|
80
|
+
`no_match` / `not_reported`), so "nothing governed this", "the source was unreadable" and "the lane
|
|
81
|
+
was skipped" stop sharing one wire shape.
|
|
82
|
+
- Settlement approver attribution (design/252 G-7): both settlement lanes accept an optional
|
|
83
|
+
`approver` identity, screened (length/control-characters/lone-surrogates, refused not truncated)
|
|
84
|
+
and TRANSCRIBED — never adjudicated — onto the settled call's `tool_end` frame beside `settledBy`.
|
|
85
|
+
- `probeParkRoundTrip` + `durableParkGapOf` (design/252 G-6): a park-lane liveness self-check (file a
|
|
86
|
+
synthetic checkpoint, read it back, enumerate, fence) plus the static two-halves reading that turns
|
|
87
|
+
a missing-session resume symptom into a named absent seat. `ParkSelfCheckResult.casLossObserved`
|
|
88
|
+
(additive) marks the one green arm a CAS-mis-reporting backend would pass through.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- Fail-closed tighten: a `persistedRules.admits()` answer that is neither a string nor an object no
|
|
93
|
+
longer clears an ask. Previously any non-`undefined` answer cleared it — "`null` admits this call"
|
|
94
|
+
was an allow built on an unreadable answer.
|
|
95
|
+
- The org-verdict normalizer reads ANY non-array carrier (class instances and callables included —
|
|
96
|
+
the structural contract admits them) with own-data field reads; refusing the carrier outright
|
|
97
|
+
could fold a structurally valid org DENY into `unavailable`, i.e. from un-approvable into a
|
|
98
|
+
real-approval ask a person could clear. Inherited members still never authorize; revoked proxies
|
|
99
|
+
are contained; foreign array methods (an overridden or species-substituted `.map`/`.filter`) no
|
|
100
|
+
longer run anywhere on the evidence path.
|
|
101
|
+
- The park probe sweeps behind a late-committing timed-out `put` on either settle, and the
|
|
102
|
+
approver screen refuses oversized values before materializing a code-point array.
|
|
103
|
+
- Dev-surface: the live-gate recipe and examples switch the reference live model to
|
|
104
|
+
`deepseek-v4-flash` (operator directive; prior cache/behavior figures quoted against
|
|
105
|
+
`deepseek-v4-pro` re-anchor on next measurement).
|
|
106
|
+
|
|
3
107
|
## 5.34.0 — 2026-08-15
|
|
4
108
|
|
|
5
109
|
No BREAKING changes. Two behavior-surface additions (both additive seats), one packaging addition,
|
package/dist/agents/subagent.js
CHANGED
|
@@ -544,6 +544,11 @@ export function createSubagentResume(deps) {
|
|
|
544
544
|
: "resume unavailable: the agent's registry row no longer exists (terminal GC) — relaunch a new agent instead.", revived.reason === "still_running" ? "steering.still_running" : revived.reason === "recycling" ? "resume.row_recycling" : "resume.row_gone");
|
|
545
545
|
}
|
|
546
546
|
reviveCycle = revived.cycle;
|
|
547
|
+
if (deps.registry !== undefined && deps.taskId !== undefined) {
|
|
548
|
+
const settledSeq = deps.registry.backgroundAgentCycleSeq(deps.taskId);
|
|
549
|
+
if (settledSeq !== undefined && settledSeq > entry.cycleSeq)
|
|
550
|
+
entry.cycleSeq = settledSeq;
|
|
551
|
+
}
|
|
547
552
|
if (deps.bgSink !== undefined) {
|
|
548
553
|
reviveEmit = (event) => {
|
|
549
554
|
try {
|
|
@@ -560,6 +565,7 @@ export function createSubagentResume(deps) {
|
|
|
560
565
|
...(deps.rowScope !== undefined ? { scope: deps.rowScope } : {}),
|
|
561
566
|
description: `${deps.rowDescription ?? "sub-agent"} (resumed ${marker})`,
|
|
562
567
|
...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
|
|
568
|
+
seq: entry.cycleSeq,
|
|
563
569
|
...(deps.rowName !== undefined ? { name: deps.rowName } : {}),
|
|
564
570
|
sessionId: entry.childSessionId,
|
|
565
571
|
transcriptId: entry.childSessionId,
|
|
@@ -585,6 +591,7 @@ export function createSubagentResume(deps) {
|
|
|
585
591
|
transcriptId: entry.childSessionId,
|
|
586
592
|
sessionId: entry.childSessionId,
|
|
587
593
|
...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
|
|
594
|
+
seq: entry.cycleSeq,
|
|
588
595
|
progressTaskId: entry.childSessionId,
|
|
589
596
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
590
597
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -593,6 +600,7 @@ export function createSubagentResume(deps) {
|
|
|
593
600
|
}, deps.registry !== undefined && deps.taskId !== undefined ? () => deps.registry.noteBackgroundAgentActivity(deps.taskId) : undefined);
|
|
594
601
|
stream = childRunner.runTaskStream(resumeSpec, undefined, {
|
|
595
602
|
...entry.internalsSnapshot,
|
|
603
|
+
cycleSeq: entry.cycleSeq,
|
|
596
604
|
...(true
|
|
597
605
|
? {
|
|
598
606
|
onForwardEvent: (e) => {
|
|
@@ -612,6 +620,7 @@ export function createSubagentResume(deps) {
|
|
|
612
620
|
sessionId: entry.childSessionId,
|
|
613
621
|
transcriptId: entry.childSessionId,
|
|
614
622
|
...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
|
|
623
|
+
seq: entry.cycleSeq,
|
|
615
624
|
progressTaskId: e.taskId,
|
|
616
625
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
617
626
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
@@ -1955,6 +1964,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1955
1964
|
bg.registry.attachAgentTerminalNotifier(taskId, () => {
|
|
1956
1965
|
const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
|
|
1957
1966
|
const completionIdReap = bg.registry.getCompletionId(taskId);
|
|
1967
|
+
const seqReap = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
1958
1968
|
const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - forkBgHangAt);
|
|
1959
1969
|
sinkEmit({
|
|
1960
1970
|
kind: "terminal",
|
|
@@ -1964,6 +1974,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1964
1974
|
transcriptId: forkedId,
|
|
1965
1975
|
parentToolCallId: ctx.toolCallId,
|
|
1966
1976
|
status: "killed",
|
|
1977
|
+
...(seqReap !== undefined ? { seq: seqReap } : {}),
|
|
1967
1978
|
stoppedBy: stoppedByReap,
|
|
1968
1979
|
summary: summaryReap,
|
|
1969
1980
|
resumable: false,
|
|
@@ -1992,6 +2003,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1992
2003
|
catch {
|
|
1993
2004
|
}
|
|
1994
2005
|
};
|
|
2006
|
+
const bgForkCycleSeq = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
1995
2007
|
sinkEmit({
|
|
1996
2008
|
kind: "spawn",
|
|
1997
2009
|
taskId,
|
|
@@ -2000,6 +2012,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2000
2012
|
...(bgScope !== undefined ? { scope: bgScope } : {}),
|
|
2001
2013
|
description: shortDesc,
|
|
2002
2014
|
agentType: spawnAgentType,
|
|
2015
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2003
2016
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2004
2017
|
sessionId: forkedId,
|
|
2005
2018
|
transcriptId: forkedId,
|
|
@@ -2028,6 +2041,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2028
2041
|
parentToolCallId: ctx.toolCallId,
|
|
2029
2042
|
progressTaskId: forkedId,
|
|
2030
2043
|
agentType: spawnAgentType,
|
|
2044
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2031
2045
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2032
2046
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2033
2047
|
usage: { toolUses: toolStarts },
|
|
@@ -2039,6 +2053,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2039
2053
|
...(forkQuestionStrip !== undefined ? forkQuestionStrip.internalsFlag : {}),
|
|
2040
2054
|
onNotifyInjectorReady: s2ForkNotifyReady,
|
|
2041
2055
|
delegationTaskType: "background_agent",
|
|
2056
|
+
...(bgForkCycleSeq !== undefined ? { cycleSeq: bgForkCycleSeq } : {}),
|
|
2042
2057
|
onForwardEvent: (e) => {
|
|
2043
2058
|
try {
|
|
2044
2059
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2056,6 +2071,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2056
2071
|
sessionId: forkedId,
|
|
2057
2072
|
transcriptId: forkedId,
|
|
2058
2073
|
parentToolCallId: ctx.toolCallId,
|
|
2074
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2059
2075
|
progressTaskId: e.taskId,
|
|
2060
2076
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
2061
2077
|
agentType: spawnAgentType,
|
|
@@ -2072,6 +2088,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2072
2088
|
...(forkQuestionStrip !== undefined ? forkQuestionStrip.internalsFlag : {}),
|
|
2073
2089
|
onNotifyInjectorReady: s2ForkNotifyReady,
|
|
2074
2090
|
delegationTaskType: "background_agent",
|
|
2091
|
+
...(bgForkCycleSeq !== undefined ? { cycleSeq: bgForkCycleSeq } : {}),
|
|
2075
2092
|
onForwardEvent: (e) => {
|
|
2076
2093
|
try {
|
|
2077
2094
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2147,6 +2164,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2147
2164
|
...(stillbornFork ? {} : { transcriptId: forkTranscriptId }),
|
|
2148
2165
|
parentToolCallId: ctx.toolCallId,
|
|
2149
2166
|
status: settledBg,
|
|
2167
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2150
2168
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
2151
2169
|
summary: forkTerminalSummary,
|
|
2152
2170
|
...residualFork,
|
|
@@ -2204,6 +2222,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2204
2222
|
transcriptId: forkedId,
|
|
2205
2223
|
parentToolCallId: ctx.toolCallId,
|
|
2206
2224
|
status: settledBg,
|
|
2225
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2207
2226
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
2208
2227
|
summary: summaryBg,
|
|
2209
2228
|
...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
|
|
@@ -2420,9 +2439,11 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2420
2439
|
const bgStartedAt = Date.now();
|
|
2421
2440
|
stepRecorder.lockTo(bgChildSessionId);
|
|
2422
2441
|
bg.registry.bindBackgroundAgentSession(taskId, bgChildSessionId);
|
|
2442
|
+
const bgCycleSeq = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
2423
2443
|
bg.registry.attachAgentTerminalNotifier(taskId, () => {
|
|
2424
2444
|
const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
|
|
2425
2445
|
const completionIdReap = bg.registry.getCompletionId(taskId);
|
|
2446
|
+
const seqReap = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
2426
2447
|
const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - bgStartedAt);
|
|
2427
2448
|
sinkEmit({
|
|
2428
2449
|
kind: "terminal",
|
|
@@ -2432,6 +2453,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2432
2453
|
transcriptId: bgChildSessionId,
|
|
2433
2454
|
parentToolCallId: ctx.toolCallId,
|
|
2434
2455
|
status: "killed",
|
|
2456
|
+
...(seqReap !== undefined ? { seq: seqReap } : {}),
|
|
2435
2457
|
stoppedBy: stoppedByReap,
|
|
2436
2458
|
summary: summaryReap,
|
|
2437
2459
|
resumable: false,
|
|
@@ -2459,6 +2481,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2459
2481
|
...(bgScope !== undefined ? { scope: bgScope } : {}),
|
|
2460
2482
|
description: reviveRow !== undefined ? `${shortDesc} (revived)` : shortDesc,
|
|
2461
2483
|
agentType: spawnAgentType,
|
|
2484
|
+
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2462
2485
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2463
2486
|
sessionId: bgChildSessionId,
|
|
2464
2487
|
transcriptId: bgChildSessionId,
|
|
@@ -2531,6 +2554,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2531
2554
|
parentToolCallId: ctx.toolCallId,
|
|
2532
2555
|
progressTaskId: bgChildSessionId,
|
|
2533
2556
|
agentType: spawnAgentType,
|
|
2557
|
+
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2534
2558
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2535
2559
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2536
2560
|
usage: { toolUses: toolStarts },
|
|
@@ -2542,6 +2566,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2542
2566
|
...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
|
|
2543
2567
|
onNotifyInjectorReady: s2NotifyReady,
|
|
2544
2568
|
delegationTaskType: "background_agent",
|
|
2569
|
+
...(bgCycleSeq !== undefined ? { cycleSeq: bgCycleSeq } : {}),
|
|
2545
2570
|
onForwardEvent: (e) => {
|
|
2546
2571
|
try {
|
|
2547
2572
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2559,6 +2584,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2559
2584
|
sessionId: bgChildSessionId,
|
|
2560
2585
|
transcriptId: bgChildSessionId,
|
|
2561
2586
|
parentToolCallId: ctx.toolCallId,
|
|
2587
|
+
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2562
2588
|
progressTaskId: e.taskId,
|
|
2563
2589
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
2564
2590
|
agentType: spawnAgentType,
|
|
@@ -2575,6 +2601,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2575
2601
|
...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
|
|
2576
2602
|
onNotifyInjectorReady: s2NotifyReady,
|
|
2577
2603
|
delegationTaskType: "background_agent",
|
|
2604
|
+
...(bgCycleSeq !== undefined ? { cycleSeq: bgCycleSeq } : {}),
|
|
2578
2605
|
onForwardEvent: (e) => {
|
|
2579
2606
|
try {
|
|
2580
2607
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2858,7 +2885,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2858
2885
|
...(child.sessionId ? { sessionId: child.sessionId, transcriptId: child.sessionId } : {}),
|
|
2859
2886
|
parentToolCallId: ctx.toolCallId,
|
|
2860
2887
|
status: settled,
|
|
2861
|
-
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2888
|
+
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2862
2889
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
2863
2890
|
summary: bgTerminalSummary,
|
|
2864
2891
|
...residual,
|
|
@@ -2948,7 +2975,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2948
2975
|
transcriptId: bgChildSessionId,
|
|
2949
2976
|
parentToolCallId: ctx.toolCallId,
|
|
2950
2977
|
status: settled,
|
|
2951
|
-
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2978
|
+
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2952
2979
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
2953
2980
|
summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + (settled === "failed" ? errorKindClause(errClassBgReject) : ""),
|
|
2954
2981
|
...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
|
|
@@ -323,6 +323,29 @@ export interface MaybeCompactOptions {
|
|
|
323
323
|
path: string;
|
|
324
324
|
content: string;
|
|
325
325
|
}>, preserveReadState?: ReadonlyArray<string>) => void;
|
|
326
|
+
/**
|
|
327
|
+
* env-tail migration — the git-status lane's compaction interaction (two-phase receipt). The git
|
|
328
|
+
* frame lives on the MESSAGE plane, does not enter the summary, and the projection strips it from
|
|
329
|
+
* serialized history — so a landed compaction would silently lose the model's git view while the
|
|
330
|
+
* announced hash keeps suppressing every re-send. When present:
|
|
331
|
+
* - `pending` is restated on the compaction entry's `details.gitAnnouncement` with
|
|
332
|
+
* `pending: true` in the SAME CAS as the new baseline (the promptEpoch/announcedListings
|
|
333
|
+
* restatement posture) — any reader between the landing and the re-assert receipt sees an
|
|
334
|
+
* OWED announcement, never a false "announced";
|
|
335
|
+
* - `land()` is invoked immediately after the landed baseline, BEFORE any context rebuild or
|
|
336
|
+
* retry (all three routes — turn-boundary auto, prompt-too-long recovery, end-of-task finish
|
|
337
|
+
* — funnel through this one append): it re-appends the current frame and writes the
|
|
338
|
+
* announced mirror once the append receipt is in hand. `land` contains its own failures (a
|
|
339
|
+
* failed re-assert leaves the pending restatement authoritative — the next boundary/leg
|
|
340
|
+
* re-announces); a throw here must never fail a LANDED compaction, so the call is belted.
|
|
341
|
+
*/
|
|
342
|
+
gitRestate?: {
|
|
343
|
+
pending: {
|
|
344
|
+
kind: "full" | "degraded" | "unavailable" | "non-repo";
|
|
345
|
+
hash: string;
|
|
346
|
+
};
|
|
347
|
+
land: () => Promise<void>;
|
|
348
|
+
};
|
|
326
349
|
/**
|
|
327
350
|
* Fixed per-request prompt overhead (system prompt + tool schemas, in tokens), applied ONLY in the
|
|
328
351
|
* anchor-less regime (design/64 §26.7, search [91] fourth candidate): when no assistant in the
|
|
@@ -446,7 +446,15 @@ export async function maybeCompact(opts) {
|
|
|
446
446
|
promptEpoch: restatedEpoch,
|
|
447
447
|
...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
|
|
448
448
|
...(opts.activeTools !== undefined && opts.activeTools.length > 0 ? { activeTools: [...opts.activeTools] } : {}),
|
|
449
|
+
...(opts.gitRestate !== undefined ? { gitAnnouncement: { kind: opts.gitRestate.pending.kind, hash: opts.gitRestate.pending.hash, pending: true } } : {}),
|
|
449
450
|
}, false);
|
|
451
|
+
if (opts.gitRestate !== undefined) {
|
|
452
|
+
try {
|
|
453
|
+
await opts.gitRestate.land();
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
}
|
|
457
|
+
}
|
|
450
458
|
try {
|
|
451
459
|
opts.onApplied?.(attachedComplete, excludedReadStatePreserveKeys);
|
|
452
460
|
}
|
|
@@ -577,6 +577,31 @@ export type ResumeOutcome = {
|
|
|
577
577
|
* with the backends and belongs to its own change, not to this one.
|
|
578
578
|
*/
|
|
579
579
|
settledBy?: import("./tool-policy.js").ApprovalSettledBy;
|
|
580
|
+
/**
|
|
581
|
+
* design/252 G-7 — WHOSE settlement: the identifier the approval channel reports for the party
|
|
582
|
+
* that ended this wait, transcribed onto the resolved call's `tool_end` frame beside
|
|
583
|
+
* {@link settledBy}.
|
|
584
|
+
*
|
|
585
|
+
* Core AUTHENTICATES NOTHING here. On the durable lane the identity work is entirely the
|
|
586
|
+
* deployment's — its approval card, its signature check, its operator console decide who this
|
|
587
|
+
* was; a library holding no identity surface has nothing to check the claim against and does not
|
|
588
|
+
* pretend otherwise. What core owns is that the claim is well-formed: a plain string, bounded,
|
|
589
|
+
* free of control characters, refused pre-CAS (`checkpoint.invalid_outcome`) otherwise, like
|
|
590
|
+
* every other out-of-domain field on this arm. Refused rather than dropped, because a settlement
|
|
591
|
+
* that silently lost its attributed party reads as unattributed — the misreport this seat exists
|
|
592
|
+
* to end.
|
|
593
|
+
*
|
|
594
|
+
* SCOPE — like {@link settledBy}, this rides the SETTLEMENT and not the persisted winner:
|
|
595
|
+
* `winnerFromOutcome` records what an `env_failed` replay must reproduce exactly, and who
|
|
596
|
+
* reported a settlement is not part of that authorization. A replay states its own attribution
|
|
597
|
+
* and is not held to the first one; binding it into the durable row would also make a re-supply
|
|
598
|
+
* that merely omits the field fail closed on the recovery path. Carrying it into the row is a
|
|
599
|
+
* store-shape change owned jointly with the backends and belongs to its own change.
|
|
600
|
+
*
|
|
601
|
+
* OPTIONAL and additive: absent means the caller named nobody, which is not the same as "nobody
|
|
602
|
+
* approved this" and must not be read as one.
|
|
603
|
+
*/
|
|
604
|
+
approver?: string;
|
|
580
605
|
}
|
|
581
606
|
/** design/74: continue a resource-suspended run with the next slice's allowance. NO decision payload and
|
|
582
607
|
* NO budget figure — the allowance is computed from {@link Checkpoint.resourceLedger}, so money never
|
|
@@ -983,6 +1008,22 @@ export interface CheckpointState {
|
|
|
983
1008
|
skills?: string[];
|
|
984
1009
|
models?: string[];
|
|
985
1010
|
};
|
|
1011
|
+
/**
|
|
1012
|
+
* The git-status frame's announced `(kind, hash)` state at suspend (env-tail migration: the
|
|
1013
|
+
* run-loop-maintained mirror, advanced at every frame receipt). The LOWEST rung of the resume
|
|
1014
|
+
* read ladder — the session branch walk is the authority (a nearest PENDING mirror there, or an
|
|
1015
|
+
* announced mirror whose frame entry is off the active branch, forces a re-announce regardless of
|
|
1016
|
+
* this field); the checkpoint seeds only when the branch carries no mirror at all, and its
|
|
1017
|
+
* `entryId` must itself sit on the active branch to count as announced. Schema ADDITION
|
|
1018
|
+
* (additive, old readers ignore it — the Q5 posture): an older checkpoint without the field re-announces
|
|
1019
|
+
* conservatively — the frame is a tail append, so a duplicate costs bytes, never a prefix break.
|
|
1020
|
+
*/
|
|
1021
|
+
gitAnnouncement?: {
|
|
1022
|
+
kind: "full" | "degraded" | "unavailable" | "non-repo";
|
|
1023
|
+
hash: string;
|
|
1024
|
+
entryId?: string;
|
|
1025
|
+
pending?: true;
|
|
1026
|
+
};
|
|
986
1027
|
/**
|
|
987
1028
|
* design/180 R-4 (R-2 cross-process variant) — the armed delegation child's MONOTONIC runtime-provenance
|
|
988
1029
|
* aggregate at suspend ({@link import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate}:
|
|
@@ -1631,12 +1672,14 @@ export declare class CheckpointError extends Error {
|
|
|
1631
1672
|
* action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
1632
1673
|
* decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
|
|
1633
1674
|
* `"settledBy"` (the settlement source is outside the three-word vocabulary: re-decide naming one
|
|
1634
|
-
* of them, or omit the field)
|
|
1675
|
+
* of them, or omit the field), or — design/252 G-7 — `"approver"` (the attribution is not a
|
|
1676
|
+
* well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
|
|
1677
|
+
* it). The throw
|
|
1635
1678
|
* sites always knew which arm fired; this carries that fact instead of dropping it (ruled
|
|
1636
1679
|
* 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
|
|
1637
1680
|
* discriminant. */
|
|
1638
1681
|
readonly detail?: {
|
|
1639
|
-
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
|
|
1682
|
+
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
1640
1683
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1641
1684
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
1642
1685
|
* damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
|
|
@@ -1752,12 +1795,14 @@ export declare class CheckpointError extends Error {
|
|
|
1752
1795
|
* action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
1753
1796
|
* decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
|
|
1754
1797
|
* `"settledBy"` (the settlement source is outside the three-word vocabulary: re-decide naming one
|
|
1755
|
-
* of them, or omit the field)
|
|
1798
|
+
* of them, or omit the field), or — design/252 G-7 — `"approver"` (the attribution is not a
|
|
1799
|
+
* well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
|
|
1800
|
+
* it). The throw
|
|
1756
1801
|
* sites always knew which arm fired; this carries that fact instead of dropping it (ruled
|
|
1757
1802
|
* 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
|
|
1758
1803
|
* discriminant. */
|
|
1759
1804
|
detail?: {
|
|
1760
|
-
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
|
|
1805
|
+
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
1761
1806
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1762
1807
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
1763
1808
|
* damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
|
|
@@ -53,6 +53,47 @@ export declare function dropOrphanToolResults(messages: AgentMessage[]): {
|
|
|
53
53
|
nextRole: string;
|
|
54
54
|
}>;
|
|
55
55
|
};
|
|
56
|
+
/** R2-3 (falsification round 2) — does the message's ENGINE-AUTHORED region cover [start, start+len)?
|
|
57
|
+
* The carrier scan must never crown a counterfeit: a tool result or a user paste can reproduce the
|
|
58
|
+
* wrapped frame bytes verbatim, but only ENGINE metadata (engineMinted whole-message, the
|
|
59
|
+
* enginePrefixChars head, or an engineSegments range) proves the engine put them there. */
|
|
60
|
+
export declare function engineRegionCovers(m: AgentMessage, start: number, len: number): boolean;
|
|
61
|
+
/** Outcome of {@link protectGitFrame} — `over_budget` means the irreducible core (summaries + the
|
|
62
|
+
* protected frame carrier) exceeds the budget even after the one-shot degraded shrink: the caller
|
|
63
|
+
* must fail the request LOUDLY (`irreducible_core_over_budget`) instead of pretending the trim
|
|
64
|
+
* succeeded or looping compaction/re-assertion. */
|
|
65
|
+
export type GitFrameGuardOutcome = {
|
|
66
|
+
messages: AgentMessage[];
|
|
67
|
+
action: "kept" | "absent" | "reinserted" | "shrunk";
|
|
68
|
+
} | {
|
|
69
|
+
action: "over_budget";
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* env-tail migration — the git frame's UNCLIPPABLE-CORE protection over a trimmed request view.
|
|
73
|
+
* The frame's "value unchanged ⇒ never re-sent" contract makes a request-only drop uniquely
|
|
74
|
+
* dangerous for it: the commit receipt has already landed, so a trim that cuts the carrier makes
|
|
75
|
+
* the announced hash suppress every future re-send while the model never saw the frame. Rules:
|
|
76
|
+
* - REPLACE-BY-KEY SLOT: exactly ONE carrier is protected — the newest message whose flat text CONTAINS
|
|
77
|
+
* `protectedText` (the ENGINE-HELD wrapped frame segment; set at delivery/re-assert AND
|
|
78
|
+
* re-seeded on value-unchanged legs, so the protection survives legs that send nothing).
|
|
79
|
+
* Older frames trim like ordinary history.
|
|
80
|
+
* - Re-insert a dropped carrier after the leading summaries/trim-notice (its original relative
|
|
81
|
+
* position is always ≥ there — the carrier predates every kept tail message).
|
|
82
|
+
* - IRREDUCIBLE-CORE arc: when the core (always-kept summaries + the carrier) alone exceeds the
|
|
83
|
+
* budget, substitute the carrier's frame segment with the deterministic DEGRADED rendering —
|
|
84
|
+
* exactly once per run (`alreadyShrunk` latch, request-view only; the run loop re-announces the
|
|
85
|
+
* shrunk view through its own receipt machinery). Still over after the shrink ⇒ `over_budget`.
|
|
86
|
+
* - A total overshoot whose core FITS is left alone (turn-alignment overshoot is a pre-existing,
|
|
87
|
+
* tolerated trade — validity beats budget; this guard must not convert it into a failure).
|
|
88
|
+
*/
|
|
89
|
+
export declare function protectGitFrame(before: AgentMessage[], trimmed: AgentMessage[], budgetTokens: number, frame: {
|
|
90
|
+
protectedText: string;
|
|
91
|
+
substitute?: {
|
|
92
|
+
find: string;
|
|
93
|
+
replace: string;
|
|
94
|
+
};
|
|
95
|
+
}, // protectedText = the wrapped frame SEGMENT (see the carrier scan below)
|
|
96
|
+
charsPerToken?: number): GitFrameGuardOutcome;
|
|
56
97
|
/** Budget the in-task guard targets for a model — CC-aligned headroom numbers with a 0.85
|
|
57
98
|
* small-window floor, sema-owned drop-oldest action (see {@link GUARD_HEADROOM_TOKENS}); always
|
|
58
99
|
* above the compaction/clearStale frontier. Dual-window semantics: deliberately the PHYSICAL
|
|
@@ -114,6 +114,82 @@ export function dropOrphanToolResults(messages) {
|
|
|
114
114
|
}
|
|
115
115
|
return { messages: out, dropped };
|
|
116
116
|
}
|
|
117
|
+
export function engineRegionCovers(m, start, len) {
|
|
118
|
+
const meta = m;
|
|
119
|
+
if (meta.engineMinted === true)
|
|
120
|
+
return true;
|
|
121
|
+
if (Array.isArray(meta.engineSegments))
|
|
122
|
+
return meta.engineSegments.some((seg) => seg.start <= start && start + len <= seg.end);
|
|
123
|
+
if (typeof meta.enginePrefixChars === "number")
|
|
124
|
+
return start + len <= meta.enginePrefixChars;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
function singleText(m) {
|
|
128
|
+
const c = m.content;
|
|
129
|
+
if (typeof c === "string")
|
|
130
|
+
return c;
|
|
131
|
+
if (Array.isArray(c) && c.length === 1) {
|
|
132
|
+
const b = c[0];
|
|
133
|
+
if (b?.type === "text" && typeof b.text === "string")
|
|
134
|
+
return b.text;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
export function protectGitFrame(before, trimmed, budgetTokens, frame, charsPerToken) {
|
|
139
|
+
const cpt = charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
140
|
+
let carrier;
|
|
141
|
+
for (let i = before.length - 1; i >= 0; i--) {
|
|
142
|
+
const candidate = before[i];
|
|
143
|
+
if (candidate === undefined || messageRole(candidate) !== "user")
|
|
144
|
+
continue;
|
|
145
|
+
const text = singleText(candidate);
|
|
146
|
+
if (text === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
const at = text.indexOf(frame.protectedText);
|
|
149
|
+
if (at === -1 || !engineRegionCovers(candidate, at, frame.protectedText.length))
|
|
150
|
+
continue;
|
|
151
|
+
carrier = candidate;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (carrier === undefined)
|
|
155
|
+
return { messages: trimmed, action: "absent" };
|
|
156
|
+
let out = trimmed;
|
|
157
|
+
let action = "kept";
|
|
158
|
+
if (!out.includes(carrier)) {
|
|
159
|
+
carrier = { role: "user", content: [{ type: "text", text: frame.protectedText }], timestamp: carrier.timestamp ?? Date.now(), engineMinted: true };
|
|
160
|
+
let at = 0;
|
|
161
|
+
for (; at < out.length; at++) {
|
|
162
|
+
const head = out[at];
|
|
163
|
+
if (head === undefined || (!isSummary(head) && singleText(head) !== CONTEXT_TRIM_NOTICE))
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
out = [...out.slice(0, at), carrier, ...out.slice(at)];
|
|
167
|
+
action = "reinserted";
|
|
168
|
+
}
|
|
169
|
+
const segTokens = (text) => estimateTokens({ role: "user", content: [{ type: "text", text }] }, cpt);
|
|
170
|
+
const coreTokens = (view, frameText) => {
|
|
171
|
+
let t = segTokens(frameText);
|
|
172
|
+
for (const m of view)
|
|
173
|
+
if (isSummary(m))
|
|
174
|
+
t += estimateTokens(m, cpt);
|
|
175
|
+
return t;
|
|
176
|
+
};
|
|
177
|
+
if (coreTokens(out, frame.protectedText) <= budgetTokens)
|
|
178
|
+
return { messages: out, action };
|
|
179
|
+
if (frame.substitute !== undefined) {
|
|
180
|
+
const text = singleText(carrier);
|
|
181
|
+
const shrunkText = text.replace(frame.substitute.find, () => frame.substitute.replace);
|
|
182
|
+
if (shrunkText !== text) {
|
|
183
|
+
const shrunkCarrier = { ...carrier, content: [{ type: "text", text: shrunkText }] };
|
|
184
|
+
const idx = out.indexOf(carrier);
|
|
185
|
+
out = [...out.slice(0, idx), shrunkCarrier, ...out.slice(idx + 1)];
|
|
186
|
+
if (coreTokens(out, frame.substitute.replace) <= budgetTokens)
|
|
187
|
+
return { messages: out, action: "shrunk" };
|
|
188
|
+
return { action: "over_budget" };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { action: "over_budget" };
|
|
192
|
+
}
|
|
117
193
|
export function guardBudget(model) {
|
|
118
194
|
const window = model.contextTokens ?? model.contextWindow;
|
|
119
195
|
if (!Number.isFinite(window) || window <= 0) {
|