@sema-agent/core 5.61.0 → 5.62.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/brain/open-responses.js +8 -3
  3. package/dist/brain/openai.js +4 -4
  4. package/dist/brain/stream-engine.d.ts +13 -2
  5. package/dist/brain/stream-engine.js +3 -3
  6. package/dist/core/auto-mode-prompt-assets.js +1 -1
  7. package/dist/core/checkpoint-store.d.ts +36 -4
  8. package/dist/core/checkpoint-store.js +1 -0
  9. package/dist/core/governance-codes.d.ts +1 -1
  10. package/dist/core/governance-codes.js +2 -0
  11. package/dist/core/hooks.d.ts +83 -4
  12. package/dist/core/hooks.js +3 -3
  13. package/dist/core/park-selfcheck.js +2 -0
  14. package/dist/core/pricing.d.ts +24 -0
  15. package/dist/core/pricing.js +18 -0
  16. package/dist/core/runner/prepare-config-doors.d.ts +34 -0
  17. package/dist/core/runner/prepare-config-doors.js +55 -0
  18. package/dist/core/runner/prepare-task.d.ts +46 -7
  19. package/dist/core/runner/prepare-task.js +77 -42
  20. package/dist/core/runner/runtask.d.ts +7 -0
  21. package/dist/core/runner/runtask.js +198 -13
  22. package/dist/core/runner/turn-attachments.d.ts +137 -5
  23. package/dist/core/runner/turn-attachments.js +25 -2
  24. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  25. package/dist/core/tool-errors.d.ts +2 -1
  26. package/dist/core/tool-policy.d.ts +27 -0
  27. package/dist/core/types.d.ts +138 -12
  28. package/dist/core/untrusted-text.d.ts +5 -4
  29. package/dist/core/untrusted-text.js +8 -0
  30. package/dist/core/usage-window-store.d.ts +109 -8
  31. package/dist/core/usage-window-store.js +79 -12
  32. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  33. package/dist/orchestration/workflow.d.ts +2 -2
  34. package/dist/prompt-assembly/event-registry.js +2 -0
  35. package/dist/server/http.d.ts +1 -1
  36. package/dist/stores/file/usage-window-store.d.ts +1 -1
  37. package/dist/stores/file/usage-window-store.js +27 -6
  38. package/dist/tools/loop-tick.js +1 -1
  39. package/dist/tools/scheduler-tools.js +9 -1
  40. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { McpDroppedTool } from "../mcp.js";
2
+ import type { TaskSpec } from "../types.js";
2
3
  import { type SkillListingEntry } from "./synthetic-tools.js";
3
4
  /**
4
5
  * design/133 §R3 — turn-boundary attachment seam: the PURE producer layer (CC `getContextAttachments`
@@ -28,6 +29,44 @@ export declare const TOOL_SEARCH_REMINDER_CONFIG: {
28
29
  readonly EVERY_N_TURNS: 15;
29
30
  readonly MAX_NAMES: 10;
30
31
  };
32
+ /**
33
+ * RB-318 — the `total_tokens_reminder` mode set, CC 2.1.245-VERBATIM and in CC's declaration order
34
+ * (bundle `R9o`; the settings schema declares the same five for its `totalTokensReminder` key).
35
+ * Each arm names what the rendered number IS, per CC's own schema prose:
36
+ * - `off` — the lane is silent. Kept as a member rather than folded into the opt-in flag because it
37
+ * is the DEPLOYMENT-RESOLVED kill switch (CC resolves the mode from env
38
+ * `CLAUDE_CODE_TOTAL_TOKENS_REMINDER` → settings → gate; sema keeps env resolution at the shell and
39
+ * carries only the resolved mode — the {@link AttachmentInputs.config.todoReminderMode} contract
40
+ * verbatim, where "wired on" and "silenced by policy" are also two independent bits).
41
+ * - `infinite` — the literal text `Infinite`.
42
+ * - `fixed` — the constant {@link TOTAL_TOKENS_FIXED_ARM_VALUE}. Together with `infinite` these are
43
+ * CC's two CONSTANT arms: they consume no measurement at all, which is exactly why they exist
44
+ * (they are the controls the countdown arms are measured against).
45
+ * - `countdown` — the live remaining budget, raw.
46
+ * - `padded-countdown` — the same, smoothed by a monotone floor so the number never jumps back.
47
+ */
48
+ export declare const TOTAL_TOKENS_REMINDER_MODES: readonly ["off", "infinite", "fixed", "countdown", "padded-countdown"];
49
+ /**
50
+ * RB-318 rider — the closed set is spelled TWICE (the public `TaskSpec.attachments.
51
+ * totalTokensReminderMode` literal union and this array), and only the TaskSpec→array direction had
52
+ * a compile chain (runtask's AttachmentInputs assignment). The config door widens this array to
53
+ * `readonly string[]` before `includes`, so a member added HERE and never to the public union used
54
+ * to compile clean, pass the door, and render — while the public type declared it illegal. Both
55
+ * directions now refuse at compile time (the governance-codes `satisfies` lockstep form):
56
+ * - array ⊆ TaskSpec — the `satisfies` on the declaration above;
57
+ * - TaskSpec ⊆ array — the conditional below collapses the exported type to `never` (the default-
58
+ * mode constant two lines down errors immediately) when the public union grows an unlisted member.
59
+ */
60
+ type TaskSpecTotalTokensReminderMode = NonNullable<NonNullable<TaskSpec["attachments"]>["totalTokensReminderMode"]>;
61
+ export type TotalTokensReminderMode = [TaskSpecTotalTokensReminderMode] extends [(typeof TOTAL_TOKENS_REMINDER_MODES)[number]] ? (typeof TOTAL_TOKENS_REMINDER_MODES)[number] : never;
62
+ /** RB-318 — CC 2.1.245's own default ("Defaults to padded-countdown"). The 2026-07-30 registration of
63
+ * this lane rested on the 2.1.220 premise "OFF by default even in 220"; 245 flipped it, which is the
64
+ * fact that reopened the ruling. Applies only ONCE the deployment has opted the lane in. */
65
+ export declare const TOTAL_TOKENS_REMINDER_DEFAULT_MODE: TotalTokensReminderMode;
66
+ /** RB-318 — CC's `fixed` arm constant (bundle `A9o`). A fixed anchor is the POINT of that arm (the
67
+ * number is meant not to move), so it is copied as the constant it is rather than re-pointed at a
68
+ * declared ceiling — a deployment that wants its own number picks a countdown arm instead. */
69
+ export declare const TOTAL_TOKENS_FIXED_ARM_VALUE = 5000000;
31
70
  /** F5: per-boundary stat scan upper bound — at most this many most-recently-read files are stat'ed. */
32
71
  export declare const CHANGED_FILES_MAX = 20;
33
72
  /**
@@ -39,7 +78,7 @@ export declare const CHANGED_FILES_MAX = 20;
39
78
  export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
40
79
  /** Per-boundary payload byte cap over the whole bundle (weak-model context economics). */
41
80
  export declare const ATTACHMENT_BYTE_CAP: number;
42
- export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
81
+ export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "total_tokens_reminder" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
43
82
  /** G1 续批 — one agent type on the delegation tool's roster (ToolSpec.agentListing, filled by
44
83
  * createSubagentTool: defined agents + the built-in fork when offered). */
45
84
  export interface AgentListingEntry {
@@ -164,6 +203,33 @@ export interface AttachmentState {
164
203
  * agent frame permanently starving the skills frame (each frame individually fits the cap by
165
204
  * construction — agent roster ≤ cap by deployment size, skills block ≤ SKILLS_BLOCK_MAX_BYTES). */
166
205
  skillsListingStarved?: boolean;
206
+ /**
207
+ * RB-318 — the `padded-countdown` arm's SMOOTHING FLOOR: the highest usage figure this run has
208
+ * already published against. CC's `cumulativeUsed` (bundle: `Math.max(storedFloor, rolledOver +
209
+ * used − anchor)`, stored back) exists so the countdown never RUNS BACKWARDS when the live usage
210
+ * coordinate drops under it — a model that watched its budget shrink and then saw it grow again
211
+ * learns the number is noise.
212
+ *
213
+ * Two of CC's three maps are structurally degenerate here and that is a mapping, not a loss:
214
+ * - the per-agent KEY — CC's tracker is session-scoped and keyed by `agentId ?? "main"` because one
215
+ * CC session hosts the main agent and its subagents; a sema {@link AttachmentState} is already
216
+ * per-RUN, and a delegated child is its own run with its own state, so this scalar IS that slot.
217
+ * - the ANCHOR (`reanchorTaskBudget`, re-armed on every regular user prompt so a new user task
218
+ * counts down from the full budget again) — a sema LEG is one task epoch, so the anchor is fixed
219
+ * at 0 for the leg's whole life and the epoch reset is the next `runTask` invocation — a
220
+ * durable-resume leg INCLUDED (it re-enters through `runTask`, rebuilds this state, re-derives
221
+ * its slice window and restarts the spend coordinate, so its countdown re-anchors to the new
222
+ * leg's real remaining — the CC re-anchor arm, not a floor violation; the floor deliberately
223
+ * does NOT ride the checkpoint, since a carried floor against a fresh slice window would freeze
224
+ * the readout at "0 tokens left" over an allowance the run genuinely holds). Same lifecycle
225
+ * reasoning as the plan_mode lane's `countPlanModeAttachmentsSinceLastExit` note.
226
+ * - the ROLLOVER (`rollOverContext`, which adds each compacted-away context back so the count keeps
227
+ * advancing) is likewise not owed: sema feeds a CUMULATIVE spend coordinate (`stats.tokens`),
228
+ * which a compaction never rewinds, where CC feeds the live context size.
229
+ * `undefined` = the padded arm has not rendered yet (an OFF lane, or a raw-countdown one, never
230
+ * allocates it).
231
+ */
232
+ totalTokensFloor?: number;
167
233
  }
168
234
  /**
169
235
  * A1-R — the date_change lane's OWN state, deliberately NOT an
@@ -314,6 +380,25 @@ export interface AttachmentInputs {
314
380
  used: number;
315
381
  total: number;
316
382
  };
383
+ /** RB-318 total_tokens_reminder lane (裁 B, 2026-08-26) — the TOKEN twin of {@link budgetUsd}, fed
384
+ * from the same place for the same reason: `used` is the run's cumulative token spend
385
+ * (`stats.tokens`) and `total` is the ceiling the budget gate enforces against it
386
+ * (`rs.budget.maxTokensWindow` = min(`limits.maxTokens`, the cross-slice allocation's remainder)),
387
+ * so a model-visible countdown can never disagree with an eventual `limits.max_tokens_exceeded`.
388
+ * Core neither estimates nor discovers this pair: no ceiling declared ⇒ absent ⇒ the countdown arms
389
+ * are silent (CC's own `maxBudgetUsd === void 0 → []` posture on the sibling lane).
390
+ *
391
+ * COORDINATE NOTE (candidate divergence registration): CC's two countdown arms read two DIFFERENT
392
+ * sources — `countdown` the live remaining context window, `padded-countdown` a synthetic task
393
+ * budget (its default 15_000_000 is far larger than any real window). sema carries ONE pair, the
394
+ * enforced spend coordinate, and lets the mode pick the arithmetic over it: publishing a context
395
+ * window would mean publishing a figure core cannot make true (BYOM — the window, the tokenizer and
396
+ * the accounting belong to the deployment's model), while the enforced ceiling is the one token
397
+ * number core owns end to end. */
398
+ totalTokens?: {
399
+ used: number;
400
+ total: number;
401
+ };
317
402
  config: {
318
403
  todoReminder: boolean;
319
404
  /** 口径③ — CC `r2o()` kill-switch parity (pretty.js:479167-479171; consumed by both producers at
@@ -349,6 +434,36 @@ export interface AttachmentInputs {
349
434
  * through a workflow governance baseline (`WorkflowGovernanceBaseline.base`, which explicitly
350
435
  * snapshots `maxCostUsd` and every other governance field) reaches the lane. */
351
436
  budgetUsd?: boolean;
437
+ /**
438
+ * RB-318 (裁 B, 2026-08-26) — `total_tokens_reminder` (CC 2.1.245 producer `MPs`, renderer `Oje`,
439
+ * registered in the SHARED producer group beside `budget_usd`, so a subagent carrying its own
440
+ * ceiling sees it too — the RB-311 attribution verbatim). Opt-in, default OFF like every other 133
441
+ * member: an un-opted deployment renders no reminder frame — the ATTACHMENT lane itself is
442
+ * byte-identical to the pre-lane engine. (Scoped deliberately to this lane: registering
443
+ * `<total_tokens>` in ENGINE_ENVELOPES changed the SANITIZE/DISCLOSURE faces for every
444
+ * deployment, opted or not — untrusted text carrying that spelling is now defused on the fenced
445
+ * lanes and envelope-shaped external data trips the design/319 disclosure tail. That is the
446
+ * defensive direction and unconditional by design: the envelope family must be un-forgeable
447
+ * whether or not anyone renders the real frame.)
448
+ *
449
+ * WHY IT EXISTS NOW. The 2026-07-30 registration refused it as a product-face question and rested
450
+ * on "OFF by default even in 220". 245 flips exactly that premise — its settings schema reads
451
+ * "Defaults to padded-countdown", i.e. upstream now publishes a token countdown to the model by
452
+ * default. The BYOM honesty objection is answered by SHAPE rather than by dropping it: core
453
+ * estimates nothing. The numbers are the ones the engine already enforces against
454
+ * ({@link AttachmentInputs.totalTokens}), the ceiling is the deployment's own declared
455
+ * `limits.maxTokens`, and with no ceiling declared the countdown arms simply never fire.
456
+ *
457
+ * The arm is chosen by {@link totalTokensReminderMode} (default {@link
458
+ * TOTAL_TOKENS_REMINDER_DEFAULT_MODE}); `off` there silences the lane without un-wiring it.
459
+ */
460
+ totalTokensReminder?: boolean;
461
+ /** RB-318 — the RESOLVED mode (see {@link TOTAL_TOKENS_REMINDER_MODES}). CC resolves it from env /
462
+ * settings / a server gate; sema keeps env resolution at the deployment shell and carries only the
463
+ * resolved value, exactly like {@link todoReminderMode}. Absent ⇒ {@link
464
+ * TOTAL_TOKENS_REMINDER_DEFAULT_MODE}. A value outside the closed set is refused at the config
465
+ * door (`config.attachment_invalid`), never folded to the default. */
466
+ totalTokensReminderMode?: TotalTokensReminderMode;
352
467
  /** G1: post-compact background-task announce (opt-in, default OFF like the 133 members). */
353
468
  backgroundTasks?: boolean;
354
469
  /** G1: deferred-tool materialization announce (design/36 setTools delta → boundary notice). */
@@ -441,10 +556,11 @@ export interface AttachmentInputs {
441
556
  * done HERE because the caller only invokes this once per boundary after its injection gate passed —
442
557
  * splitting "decide" from "mark sent" would just invite a drift between the two.
443
558
  *
444
- * Returns attachments in EVIDENCE-STRENGTH order (todo/task → plan_mode budget_usd →
445
- * background_taskstools_deltaagent_listingskills_listingmcp_instructionschanged_files), which is also the §R3 MED-6
446
- * truncation order read backwards: under the 8KB cap changed_files is sacrificed first, the todo/task
447
- * list last. The G1/续批 members are registry/engine ground truth (strong), but the
559
+ * Returns attachments in EVIDENCE-STRENGTH order (todo/task → tool_search_usage_reminder
560
+ * plan_modebudget_usdtotal_tokens_reminderbackground_taskstools_deltaagent_listing
561
+ * skills_listing mcp_instructions mcp_dropped_tools changed_files), which is also the §R3
562
+ * MED-6 truncation order read backwards: under the 8KB cap changed_files is sacrificed first, the
563
+ * todo/task list last. The G1/续批 members are registry/engine ground truth (strong), but the
448
564
  * todo/plan lane keeps priority: it carries the run's own work-tracking state, which the model can
449
565
  * least afford to lose.
450
566
  */
@@ -528,6 +644,21 @@ export declare function commitInstructionsChange(state: InstructionsChangeState,
528
644
  contentHash: string | null;
529
645
  }>): void;
530
646
  export declare function renderBudgetUsd(used: number, total: number): string;
647
+ /**
648
+ * RB-318 — CC 2.1.245 `Oje` VERBATIM: the whole body is the one-line envelope
649
+ * `<total_tokens>N tokens left</total_tokens>`, where N is the arm's own substitution — the literal
650
+ * `Infinite`, the {@link TOTAL_TOKENS_FIXED_ARM_VALUE} constant, or the remaining count clamped at
651
+ * zero (CC's `Math.max(0, …)`: an over-spent run reads `0 tokens left`, never a negative number).
652
+ *
653
+ * `remaining` is ignored by the two constant arms — the caller passes whatever it has (the producer
654
+ * passes 0), matching CC, whose own producer computes `0` for them.
655
+ *
656
+ * The `<total_tokens>` tag is an ENGINE-MINTED authority envelope: it is registered in the
657
+ * `ENGINE_ENVELOPES` census (untrusted-text.ts) and is in the fenced family, so the same spelling
658
+ * arriving inside untrusted content is defused rather than read as this readout. Its own body needs no
659
+ * escaping in return — every byte here is engine copy or a finite number.
660
+ */
661
+ export declare function renderTotalTokensReminder(mode: TotalTokensReminderMode, remaining: number): string;
531
662
  /**
532
663
  * SR-7 — CC 2.1.198 VERBATIM inner text (bundle pretty.js:698391-698398, `F6c`): the aggregated
533
664
  * orphaned-background-task notice a restarted/resumed leg injects ONCE (CC's own `<system-reminder>`
@@ -829,3 +960,4 @@ export declare function selectMcpDroppedBatch<T extends McpDroppedTool>(entries:
829
960
  * is unchanged: this is a pure function of (string, number).
830
961
  */
831
962
  export declare function clipToBytes(s: string, maxBytes: number): string;
963
+ export {};
@@ -15,15 +15,21 @@ export const TOOL_SEARCH_REMINDER_CONFIG = {
15
15
  EVERY_N_TURNS: 15,
16
16
  MAX_NAMES: 10,
17
17
  };
18
+ export const TOTAL_TOKENS_REMINDER_MODES = ["off", "infinite", "fixed", "countdown", "padded-countdown"];
19
+ export const TOTAL_TOKENS_REMINDER_DEFAULT_MODE = "padded-countdown";
20
+ export const TOTAL_TOKENS_FIXED_ARM_VALUE = 5_000_000;
18
21
  export const CHANGED_FILES_MAX = 20;
19
22
  export const CHANGED_FILES_MTIME_EPS_MS = 2000;
20
23
  export const ATTACHMENT_BYTE_CAP = 8 * 1024;
21
24
  const PROJECTION_ITEMS_MAX = 50;
22
25
  const PROJECTION_CONTENT_MAX = 80;
23
26
  const ATTACHMENT_TAGS_DEFAULT = [...SHELLED_BODY_ENVELOPE_TAGS];
24
- const ATTACHMENT_TAGS_SKILLS_OWNER = SHELLED_BODY_ENVELOPE_TAGS.filter((t) => t !== "skills");
27
+ const ATTACHMENT_TAGS_OWNED = {
28
+ skills_listing: SHELLED_BODY_ENVELOPE_TAGS.filter((t) => t !== "skills"),
29
+ total_tokens_reminder: SHELLED_BODY_ENVELOPE_TAGS.filter((t) => t !== "total_tokens"),
30
+ };
25
31
  export function attachmentEnvelopeTags(source) {
26
- return source === "skills_listing" ? ATTACHMENT_TAGS_SKILLS_OWNER : ATTACHMENT_TAGS_DEFAULT;
32
+ return ATTACHMENT_TAGS_OWNED[source] ?? ATTACHMENT_TAGS_DEFAULT;
27
33
  }
28
34
  export const INSTRUCTIONS_CHANGE_BYTE_CAP = 512;
29
35
  export function createAttachmentState() {
@@ -166,6 +172,19 @@ export function collectDueAttachments(state, inp) {
166
172
  if (inp.config.budgetUsd && inp.budgetUsd !== undefined) {
167
173
  (out ??= []).push({ source: "budget_usd", body: renderBudgetUsd(inp.budgetUsd.used, inp.budgetUsd.total) });
168
174
  }
175
+ if (inp.config.totalTokensReminder === true) {
176
+ const mode = inp.config.totalTokensReminderMode ?? TOTAL_TOKENS_REMINDER_DEFAULT_MODE;
177
+ if (mode === "infinite" || mode === "fixed") {
178
+ (out ??= []).push({ source: "total_tokens_reminder", body: renderTotalTokensReminder(mode, 0) });
179
+ }
180
+ else if ((mode === "countdown" || mode === "padded-countdown") && inp.totalTokens !== undefined) {
181
+ const { used, total } = inp.totalTokens;
182
+ if (Number.isFinite(used) && Number.isFinite(total) && used >= 0 && total >= 0) {
183
+ const effectiveUsed = mode === "padded-countdown" ? (state.totalTokensFloor = Math.max(state.totalTokensFloor ?? 0, used)) : used;
184
+ (out ??= []).push({ source: "total_tokens_reminder", body: renderTotalTokensReminder(mode, total - effectiveUsed) });
185
+ }
186
+ }
187
+ }
169
188
  if (inp.config.backgroundTasks && state.postCompactPending) {
170
189
  state.postCompactPending = false;
171
190
  if (inp.backgroundTasks !== undefined && inp.backgroundTasks.length > 0) {
@@ -362,6 +381,10 @@ export function commitInstructionsChange(state, announced) {
362
381
  export function renderBudgetUsd(used, total) {
363
382
  return `USD budget: $${used}/$${total}; $${total - used} remaining`;
364
383
  }
384
+ export function renderTotalTokensReminder(mode, remaining) {
385
+ const shown = mode === "infinite" ? "Infinite" : mode === "fixed" ? TOTAL_TOKENS_FIXED_ARM_VALUE : Math.max(0, remaining);
386
+ return `<total_tokens>${shown} tokens left</total_tokens>`;
387
+ }
365
388
  export function renderOrphanedBackgroundTasks(tasks) {
366
389
  const safe = (t) => sanitizeUntrustedText(t, SHELLED_BODY_ENVELOPE_TAGS);
367
390
  return (`The container was restarted. The following background tasks were running and are now stopped:\n` +
@@ -212,6 +212,25 @@ export async function checkpointStoreContract(make, runAssertion) {
212
212
  const stored = (await store.get(bitless.token)).pendingAction;
213
213
  assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
214
214
  });
215
+ run("#457 previewWithheld three-form matrix: a row's spelling survives + projects; absent stays absent; an out-of-contract spelling reads as absent", async () => {
216
+ const store = make();
217
+ const oversize = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-oversize" });
218
+ oversize.pendingAction.previewWithheld = "oversize";
219
+ const unavailable = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-unavailable" });
220
+ unavailable.pendingAction.previewWithheld = "unavailable";
221
+ const absent = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-absent" });
222
+ const bogus = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-bogus" });
223
+ bogus.pendingAction.previewWithheld = "shrugged";
224
+ for (const cp of [oversize, unavailable, absent, bogus])
225
+ await store.put(cp.token, cp);
226
+ const back = (await store.get(oversize.token)).pendingAction;
227
+ assert.equal(back.previewWithheld, "oversize", "the row's previewWithheld must survive the round-trip verbatim");
228
+ const byId = new Map((await store.listByScope("tenant-a")).map((s) => [s.sessionId, s]));
229
+ assert.equal(byId.get("pw-oversize")?.previewWithheld, "oversize");
230
+ assert.equal(byId.get("pw-unavailable")?.previewWithheld, "unavailable");
231
+ assert.equal("previewWithheld" in byId.get("pw-absent"), false, "absent must project the key OMITTED, not null/empty");
232
+ assert.equal("previewWithheld" in byId.get("pw-bogus"), false, "an out-of-contract spelling must read as absent, never project verbatim");
233
+ });
215
234
  await settle();
216
235
  }
217
236
  const sortByToken = (a, b) => a.token.localeCompare(b.token);
@@ -118,7 +118,8 @@ export type WorkerErrorClass = "budget" | "limit" | "output" | "suspend" | "revi
118
118
  * The `limits.` namespace splits into the `budget` and `limit` classes by exact code — see
119
119
  * {@link EXACT_CODE_CLASS}. Operation-level dotted codes that are surfaced to a *caller* and never become a
120
120
  * task outcome are deliberately OUT of scope: `steering.*` (`steering.not_running`/`steering.invalid_content`/
121
- * `steering.duplicate_input_id`, rejected to the `steer()` caller) and `mcp.*` (`mcp.server_unavailable`, an `onWarn` warning code) — neither
121
+ * `steering.duplicate_input_id`/`steering.blocked_by_hook`, rejected to the `steer()` caller the fourth also
122
+ * rides the resume face as a `CheckpointError` code) and `mcp.*` (`mcp.server_unavailable`, an `onWarn` warning code) — neither
122
123
  * reaches `TaskResult.errorCode`, so a caller will never pass them here. A genuinely unmapped code → `"unknown"` (which therefore means
123
124
  * "known-but-foreign or no code", e.g. a leaked fs/Node code, NOT "an OUR terminal class we forgot to add").
124
125
  */
@@ -853,6 +853,33 @@ export interface AskRequest {
853
853
  * with contextual escaping and show {@link args} alongside — the preview can misrepresent the
854
854
  * executable action and never replaces args. Never adjudication input. */
855
855
  readonly preview?: unknown;
856
+ /**
857
+ * #457 ② — PRESENT ⇔ this tool DECLARES an approval preview and the gate could not present a usable
858
+ * one, naming which way it failed: `"oversize"` (produced, but past the 16KiB display bound — the
859
+ * truncation stub still rides {@link preview}) or `"unavailable"` (the projection threw, or its value
860
+ * has no JSON serialization, so nothing was produced at all).
861
+ *
862
+ * ABSENT is the ordinary world and covers three cases that are NOT withholdings: the tool declares no
863
+ * preview, the projection declined to speak for these args, and the projection succeeded. A surface
864
+ * must therefore read presence, never absence: absence is not a claim that the preview is faithful.
865
+ *
866
+ * A DISCLOSURE, not a verdict and not a transform — the same posture as {@link hasBidiControls}. Core
867
+ * refuses nothing and narrows nothing on account of it: {@link args} and {@link boundInputHash} are
868
+ * always carried, so unlike a surface whose only content IS the projection, a core approval card can
869
+ * still show the person exactly what will run. What the bit buys is the honest sentence a card owes
870
+ * when it is rendering less than the tool meant it to — and the ground for a surface that chooses to
871
+ * withhold its own PERSISTENT "allow and stop asking me this" affordance while the action cannot be
872
+ * reviewed as intended (upstream's form: a withheld preview vetoes the standing row, leaving one-time
873
+ * approval only). Core deliberately does NOT apply that veto to {@link ruleOffers} itself — core's
874
+ * offers speak for shell COMMAND text, which rides `args` and is shown whatever the preview did.
875
+ *
876
+ * Filled at the ask MINT sites that resolve a preview (the synchronous gate leg), not at `resolveAsk`
877
+ * — the chokepoint receives an already-built request and has no tool table to project from. The
878
+ * durable park leg's twin is `PendingAction.tool_approval.previewWithheld` on the row, judged over
879
+ * that row's own fidelity-projected args snapshot; the two faces can honestly differ exactly as the
880
+ * bidi bit's two faces can.
881
+ */
882
+ readonly previewWithheld?: "oversize" | "unavailable";
856
883
  /** Canonical digest of {@link args} as presented in THIS ask, computed by the engine's one
857
884
  * `boundInputHashOf` — the same digest a durable park binds its checkpoint to, so an aggregating
858
885
  * approver can reconcile a synchronous ask row against a parked checkpoint row for the same call
@@ -1032,7 +1032,7 @@ export interface ToolExecuteContext {
1032
1032
  * is a DISPLAY channel ONLY — the child stream is NEVER merged into the parent's MODEL context, and nothing
1033
1033
  * security-relevant consumes a forwarded event. Present ONLY when the deployment opted in. The tool-ctx wrapper
1034
1034
  * passes `task_progress` unconditionally and — when the deployment sets `forwardSubagentEvents: true` — the
1035
- * transcript classes too (text_delta/reasoning_delta/tool_start/tool_end); other event types never cross it.
1035
+ * transcript classes too (text_delta/text_end/reasoning_delta/tool_start/tool_end); other event types never cross it.
1036
1036
  * The delegation lane's OWN tap is trusted and forwards the child's FULL event stream (bg frames tagged
1037
1037
  * with bgAgentId). ⚠️ Forwarded ticks are UNTRUSTED display hints — any
1038
1038
  * tool holding this ctx could self-declare one, so a consumer validates `parentTaskId` against its known runs.
@@ -2543,7 +2543,7 @@ export interface TaskSpec {
2543
2543
  /**
2544
2544
  * Subagent viewing pane (dogfood finding 2026-07-03): widen the opt-in display sink
2545
2545
  * (`RunInternals.onForwardEvent`) from `task_progress`-only to a SUBAGENT's live CONTENT events
2546
- * (`text_delta` / `reasoning_delta` / `tool_start` / `tool_end`), so a UI can render a delegated
2546
+ * (`text_delta` / `text_end` (#447) / `reasoning_delta` / `tool_start` / `tool_end`), so a UI can render a delegated
2547
2547
  * child's transcript live. Default OFF (progress-only, prior behavior). The child stream is still
2548
2548
  * NEVER merged into the parent's model context — this is purely a render channel; forwarded events
2549
2549
  * carry `parentToolCallId` (attribution) and the same UNTRUSTED-RAW contract as the main stream's
@@ -2823,6 +2823,51 @@ export interface TaskSpec {
2823
2823
  * that judgment shaped the DEFAULT-OFF posture; an opted-in deployment chooses CC parity.
2824
2824
  */
2825
2825
  budgetUsd?: true;
2826
+ /**
2827
+ * RB-318 (ruled 2026-08-26) — `total_tokens_reminder` (CC 2.1.245 producer `MPs`, renderer `Oje`):
2828
+ * the TOKEN twin of {@link budgetUsd}. One line per collected boundary,
2829
+ * `<total_tokens>N tokens left</total_tokens>`, CC-verbatim bytes.
2830
+ *
2831
+ * Activation, CC-exact in shape and BYOM-honest in substance: opted in here AND — for the two
2832
+ * COUNTDOWN arms — the task carries a token ceiling (`limits.maxTokens`, or a resource-slice
2833
+ * allocation's remainder). No ceiling ⇒ permanently silent, because core does not know and will not
2834
+ * guess your model's context window: the number it publishes is the one it already ENFORCES
2835
+ * (`stats.tokens` against that ceiling), so the readout can never disagree with an eventual
2836
+ * `limits.max_tokens_exceeded`. The two CONSTANT arms (`infinite` / `fixed`) read no measurement at
2837
+ * all and need no ceiling.
2838
+ *
2839
+ * Cadence, CC-exact: no threshold ladder and no throttle — every collected boundary carries it, and
2840
+ * the "progression" is the numbers advancing with spend. Recorded deviation (shared with
2841
+ * {@link budgetUsd}, and for the same reason): the lane only rides boundaries whose turn resolved
2842
+ * ≥1 tool call, so a boundary steer never EXTENDS a run that reached its natural end.
2843
+ *
2844
+ * Default OFF like every other member of this family. NOTE: this relaxes the same design/74 "no
2845
+ * budget language reaches the model" default that {@link budgetUsd} does — an opted-in deployment
2846
+ * chooses CC parity (where, since 2.1.245, this readout is on by default).
2847
+ */
2848
+ totalTokensReminder?: true;
2849
+ /**
2850
+ * RB-318 — which arm of the readout, CC 2.1.245's closed set (`off` / `infinite` / `fixed` /
2851
+ * `countdown` / `padded-countdown`); absent ⇒ CC's own default `"padded-countdown"`.
2852
+ *
2853
+ * - `countdown` — remaining = ceiling − spend, raw.
2854
+ * - `padded-countdown` — the same, through a monotone floor, so the number NEVER JUMPS BACK
2855
+ * within one engine leg (CC's per-agent smoothing floor; ONE `runTask` invocation is the
2856
+ * epoch). A durable-resume leg is a NEW epoch by design: the slice window, the spend
2857
+ * coordinate and the floor all restart, so the resumed leg's readout RE-ANCHORS to its own
2858
+ * real remaining — which may sit above the prior leg's last readout (the fresh slice window
2859
+ * is a genuine new allowance; CC's own re-anchor arm counts a new task epoch down from the
2860
+ * full budget again). Carrying the floor across legs would publish "0 tokens left" against a
2861
+ * window the run genuinely still holds — a frozen falsehood, deliberately not done.
2862
+ * - `off` — silences the lane while leaving it wired, for a deployment that resolves the mode from
2863
+ * its own env/settings the way CC does (core reads no env).
2864
+ * - `infinite` / `fixed` — CC's two constant arms: the literal `Infinite`, and the constant
2865
+ * 5000000. They publish no measurement (that is what they are for) and need no ceiling.
2866
+ *
2867
+ * A value outside the set is REFUSED at prepare (`config.attachment_invalid`), never folded to the
2868
+ * default — a near-miss spelling must not silently publish a different readout than the one asked for.
2869
+ */
2870
+ totalTokensReminderMode?: "off" | "infinite" | "fixed" | "countdown" | "padded-countdown";
2826
2871
  /** Post-compact background-task restatement — DEFAULT ON since 5.12.0 (boolean, not `true`:
2827
2872
  * explicit `false` is the opt-out; same contract as the listing family below). CC hard-codes
2828
2873
  * this behavior, and the opt-in default left every non-shell host (server-driven runs) with a
@@ -3123,8 +3168,10 @@ export interface TaskResult {
3123
3168
  *
3124
3169
  * 1.37+ terminal codes use a **dotted namespace** so a caller can prefix-match a whole class:
3125
3170
  * `"limits.max_tokens_exceeded"` / `"limits.max_cost_exceeded"` / `"limits.max_turns_exceeded"` /
3126
- * `"limits.max_walltime_exceeded"` / `"config.limit_invalid"` / `"config.limit_unknown_key"`
3127
- * (e.g. `errorCode.startsWith("limits.")`). 1.36 brain codes (`auth`/`network`/`rate_limit`/…)
3171
+ * `"limits.max_walltime_exceeded"` / `"config.limit_invalid"` / `"config.limit_unknown_key"` /
3172
+ * `"config.attachment_invalid"` (RB-318 — a mode-valued `TaskSpec.attachments` member outside its
3173
+ * closed set, refused at the same door as the limits; e.g. a near-miss `totalTokensReminderMode`
3174
+ * spelling) (e.g. `errorCode.startsWith("limits.")`). 1.36 brain codes (`auth`/`network`/`rate_limit`/…)
3128
3175
  * and `"conflict"` remain flat (unchanged, to avoid breaking existing consumers).
3129
3176
  *
3130
3177
  * design/164 件四/件五 added two codes for the EXTERNAL stop causes — neither is a `limits.` code,
@@ -3137,6 +3184,14 @@ export interface TaskResult {
3137
3184
  * the wait hint rides the thrown error's `retryAfterMs` (delivered through `RunnerDeps.onError`) and
3138
3185
  * the message text. Retrying before the window frees will be refused again.
3139
3186
  * Their config-time siblings are `"config.env_lifetime_invalid"` / `"config.usage_window_invalid"`.
3187
+ * A governance window with a MONEY ceiling adds two more, both of which say "the ceiling could not be
3188
+ * evaluated" rather than "the ceiling was reached" — neither is retryable without a config change:
3189
+ * - `"config.usage_window_unpriced"` — a `UsageWindow.maxCostUsd` over a run with no cost figure (no
3190
+ * `RunnerDeps.pricing` entry and no `Model.cost`). Raised at the door for the run's own model, and at
3191
+ * the accounting point when a mid-run model switch loses the price table.
3192
+ * - `"usage_window.store_cost_unanswered"` — the wired ledger does not carry the money arm: it either
3193
+ * dropped the cost supplied to a charge, or answered a $ window without the cost it holds. Both mean
3194
+ * the ceiling was never evaluated (typically a store or decorator that predates the cost arm).
3140
3195
  */
3141
3196
  errorCode?: string;
3142
3197
  /**
@@ -3446,7 +3501,8 @@ export interface TaskResult {
3446
3501
  * · `defused` (an MCP/web segment's exact-mark bytes were rewritten — the lane's one sanctioned
3447
3502
  * byte change, always paired with a `marked` disclosure),
3448
3503
  * · `envelope` (text shaped like one of the engine's OTHER authority envelopes — the DISCLOSED
3449
- * subset is `task-notification` / `new-diagnostics` / `user_memory` / `skills`; `scope` is
3504
+ * subset is `task-notification` / `new-diagnostics` / `user_memory` / `skills` /
3505
+ * `total_tokens`; `scope` is
3450
3506
  * fenced but not disclosed, since `<scope>…</scope>` is also an ordinary build-file element.
3451
3507
  * That family carries no mark, so its sentence is positional rather than byte-testable. It
3452
3508
  * rides ON the reminder copy when both families hit, so `envelope` can be bumped alongside
@@ -3670,8 +3726,15 @@ export interface ToolActivity {
3670
3726
  export type HumanInputSource = "objective" | "steer" | "next_turn" | "wake" | "external" | "system";
3671
3727
  /** design/171 §6.2 — how a human input was disposed of when its event was emitted:
3672
3728
  * `"applied"` = delivered into a model turn; `"queued"` = accepted, awaiting the next turn
3673
- * boundary; `"parked_for_wake"` = parked for the session's next run (torn-down lane). */
3674
- export type HumanInputDelivery = "applied" | "queued" | "parked_for_wake";
3729
+ * boundary; `"parked_for_wake"` = parked for the session's next run (torn-down lane);
3730
+ * `"blocked"` (design/373 §4.3, additive closed-set add, consumers named in the ship post) =
3731
+ * a PARKED entry was withheld at resume redelivery — the screen runs on EVERY resume kind that
3732
+ * drains parked steers (policy_ask / dry_run_review / plan_review / resource_limit, wake
3733
+ * included) — by the deployment's `userPromptSubmit` screen (block verdict, or a fail-closed
3734
+ * non-answer/crash): the row was consumed, the frame never reached the model, and the sibling
3735
+ * `steering.parked_input_blocked` notice names the same inputId — "park 时收下、redeliver 时被筛"
3736
+ * is auditable, never a silent disappearance. */
3737
+ export type HumanInputDelivery = "applied" | "queued" | "parked_for_wake" | "blocked";
3675
3738
  /**
3676
3739
  * The fleet-task kinds a DELEGATED run can honestly claim, derived from (never a second spelling of)
3677
3740
  * {@link TaskNotificationPayload}'s `task_type` vocabulary — the same axis a consumer already keys
@@ -3684,6 +3747,32 @@ export type DelegationTaskType = Extract<TaskNotificationPayload["task_type"], "
3684
3747
  export type TaskEvent = ({
3685
3748
  type: "text_delta";
3686
3749
  delta: string;
3750
+ } & TaskEventIdentity) | ({
3751
+ /**
3752
+ * #447 — the assistant's streaming PROSE SEGMENT is COMPLETE: the model closed the text content
3753
+ * block whose bytes just streamed as `text_delta`s. This is the explicit segment boundary a
3754
+ * REMOTE consumer needs so it never has to guess segment ends from wire silence (the idle-flush
3755
+ * heuristic this retires cut one slow-model reply into N fragments). CC-aligned: CC's agent
3756
+ * stream yields each finished content block as its own assistant-message unit at the provider's
3757
+ * `content_block_stop` — block completion IS the segmentation signal there; a single-process
3758
+ * consumer reads it off the provider stream natively, and this event is that same boundary
3759
+ * surfaced on the TaskEvent wire (CC pays the byte cost of re-carrying the block at the stop;
3760
+ * so does this event's `content`).
3761
+ *
3762
+ * `content` = the authoritative FULL text of the completed segment (byte-equal to that
3763
+ * segment's accumulated deltas, from the brain's own accumulation) — a consumer commits the
3764
+ * segment from it instead of trusting its own delta stitching. UNTRUSTED model output for
3765
+ * display only, same contract as `text_delta`. Emitted only for a segment that holds bytes: an
3766
+ * empty text block closes silently (a boundary with no segment would render phantom rows).
3767
+ *
3768
+ * Additive + ignorable. HONEST ABSENCE: the frame exists only when the serving Brain reports
3769
+ * block ends (`text_end` on its event stream — all three first-party brains do; a custom brain
3770
+ * that never emits them yields a wire without this frame). A consumer treats per-segment
3771
+ * presence as the signal and falls back to its own heuristic only on streams that carry none —
3772
+ * absence is "unreported", never "the segment did not end".
3773
+ */
3774
+ type: "text_end";
3775
+ content: string;
3687
3776
  } & TaskEventIdentity) | ({
3688
3777
  type: "reasoning_delta";
3689
3778
  delta: string;
@@ -4064,7 +4153,7 @@ export type TaskEvent = ({
4064
4153
  * repo-controlled and must not enter the event telemetry plane through this echo.
4065
4154
  */
4066
4155
  type: "steering_injected";
4067
- source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification" | "git_status";
4156
+ source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "total_tokens_reminder" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification" | "git_status";
4068
4157
  preview: string;
4069
4158
  } & TaskEventIdentity) | ({
4070
4159
  /**
@@ -4417,6 +4506,23 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
4417
4506
  * identity (`inputId` idempotency) includes the NORMALIZED tier: the same text replayed under
4418
4507
  * the same id at a different tier refuses `steering.duplicate_input_id` — an "idempotent
4419
4508
  * success" that silently skipped the interrupt would be a disposition lie.
4509
+ *
4510
+ * design/373 §4.3 (D2, ruling of 2026-08-24) — a deployment `userPromptSubmit` hook SCREENS this verb:
4511
+ * the steer face is a SERVICE entrance (third-party callers reach a running run through it), so
4512
+ * the deployment's prompt filter sits at the entrance, `ctx.source:"steer"` + `ctx.inputId` +
4513
+ * `ctx.actor` discriminated. Chain position: domain validation → liveness → `inputId` replay
4514
+ * short-circuit → screen → accept/enqueue → (now) interrupt — an idempotent replay of an ACCEPTED
4515
+ * id answers success WITHOUT re-running the hook (a re-run could answer differently and
4516
+ * retro-falsify the standing receipt). Hook `block` ⇒ **typed throw `steering.blocked_by_hook`**
4517
+ * (message carries the hook's own bounded reason); a hook timeout / cancellation / crash refuses
4518
+ * with the SAME code, fail-closed (a screen that did not answer has not cleared the input —
4519
+ * message discriminates the cause; the crash also reaches `onError` phase:"hook"). A blocked call
4520
+ * was never accepted: no `human_input` frame, no undrained account, and the `inputId` stays
4521
+ * UNBOOKED (retry freely, changed content included). `additionalContext` ⇒ prepended to the
4522
+ * delivered frame as the engine's own reminder (never inside the untrusted mid-turn frame). The
4523
+ * replay identity is over the CALLER's bytes — hook output never enters it. Engine-authored
4524
+ * frames (task notifications, diagnostics) and external `notify()` text are OUT of the screen's
4525
+ * domain (notifications are sanitized DATA, not prompts — the notify contract's ruling).
4420
4526
  */
4421
4527
  steer(text: string, options?: {
4422
4528
  trusted?: boolean;
@@ -5063,6 +5169,17 @@ export interface EngineNotice {
5063
5169
  * implemented, so the unhonored-knob disclosure it carried has no referent; consumers must
5064
5170
  * judge ladder support by VERSION, never by that code's absence.
5065
5171
  *
5172
+ * - `"steering.parked_input_blocked"` (design/373 §4.3) — a PARKED steer entry was withheld when
5173
+ * a resume redelivered it (any resume kind that drains parked steers — wake included) by the
5174
+ * deployment's `userPromptSubmit` screen (block verdict, or a fail-closed non-answer/crash):
5175
+ * the row was consumed with the checkpoint, the frame never reached the model, and the resume
5176
+ * itself proceeds (a blocked instruction must never wedge a wake). The lifecycle half of the
5177
+ * same fact is the entry's own `human_input` account with `delivery: "blocked"` — the two share
5178
+ * the inputId, so "accepted at park, screened at redelivery" is auditable end to end. Audience
5179
+ * `"user"` (the person whose instruction was withheld is the one entitled to re-issue it);
5180
+ * `detail: { inputId?, sessionId, taskId? }` — `inputId` is the parked entry's stored
5181
+ * correlation key.
5182
+ *
5066
5183
  * - `"memory.session_polluted"` (design/178 §3, #324a; message mode-aware since design/336) —
5067
5184
  * this session's memory crossed into the one-way externally-exposed state (a tool classified
5068
5185
  * as an external content source was invoked, directly or through a delegated child). Under
@@ -5807,11 +5924,17 @@ export interface RunnerDeps {
5807
5924
  * `SendMessageToolOptions.admission`; read per call (a value change governs the next message). */
5808
5925
  peerAdmission?: Partial<import("../agents/peer-admission.js").PeerAdmissionConfig>;
5809
5926
  /**
5810
- * design/164 件五 — DEPLOYMENT-level usage governance: token allowances that span TASKS, evaluated per
5927
+ * design/164 件五 — DEPLOYMENT-level usage governance: allowances that span TASKS, evaluated per
5811
5928
  * principal (or once for the whole deployment when a task declares none). A different axis from
5812
5929
  * `TaskSpec.limits`, which is the allowance ONE task asked for — an operator granting "N tokens per 5
5813
5930
  * hours" cannot express it as a task limit, because nothing stops the next task from asking again.
5814
5931
  *
5932
+ * A window carries a TOKEN ceiling and, optionally, a MONEY ceiling (`UsageWindow.maxCostUsd`, absolute
5933
+ * USD — the `TaskLimits.maxCostUsd` quantity one governance level up). The two are independent and
5934
+ * either one binds. A $ ceiling requires a PRICED run: a task whose model has neither a {@link pricing}
5935
+ * entry nor a `Model.cost` declaration is refused at the door (`config.usage_window_unpriced`) rather
5936
+ * than charged the fabricated 0 an unpriced run would otherwise file into an operator's ceiling.
5937
+ *
5815
5938
  * Unset (the default) ⇒ NO governance: no ledger is read or written and no task can be refused for
5816
5939
  * usage. When set, every window is evaluated at two moments:
5817
5940
  * - **entry** (before the first model call of a fresh task): an exhausted window REFUSES the task with
@@ -5828,7 +5951,7 @@ export interface RunnerDeps {
5828
5951
  * would take the deployment down instead of telling the operator).
5829
5952
  */
5830
5953
  usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
5831
- /** design/164 件五 — the cross-task token ledger {@link usageWindows} is evaluated against. Core bundles
5954
+ /** design/164 件五 — the cross-task usage ledger {@link usageWindows} is evaluated against. Core bundles
5832
5955
  * `InMemoryUsageWindowStore` (process-local) and `FileUsageWindowStore` (restart-surviving); a fleet
5833
5956
  * deployment plugs a database behind the same two-method seam. Ignored when `usageWindows` is unset. */
5834
5957
  usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
@@ -6075,8 +6198,11 @@ export interface RunnerDeps {
6075
6198
  /**
6076
6199
  * Default in-process hooks for all tasks (design/37) — the FULL lifecycle seam of the `Hooks`
6077
6200
  * interface, not just the tool-call trio: `preToolUse` (rewrite/restrict args + inject context),
6078
- * `postToolUse` (rewrite output + inject context), `userPromptSubmit` (block/inject before the
6079
- * objective becomes a message), plus `stop` (push back when the run would otherwise end and continue
6201
+ * `postToolUse` (rewrite output + inject context), `userPromptSubmit` (design/373 §4.3: screens
6202
+ * EVERY user-lane entrance the objective, a live `TaskStream.steer`, a wake resume's message,
6203
+ * and a parked steer's redelivery, discriminated by `ctx.source`; a hook that blocks
6204
+ * unconditionally refuses steers/wakes too — see the ⚠️ WIDENED INVOCATION SET banner on
6205
+ * {@link import("./hooks.js").Hooks.userPromptSubmit}), plus `stop` (push back when the run would otherwise end and continue
6080
6206
  * it), `postToolUseFailure` / `postToolBatch` / `permissionDenied` (failure, batch-boundary and
6081
6207
  * deny observers), `preCompact` / `postCompact` (compaction gate + observer), `stopFailure`
6082
6208
  * (API-error terminal observer) and the `preToolUseObservational` declaration flag — each member's
@@ -100,10 +100,11 @@ export declare const FENCED_LANE_ENVELOPE_TAGS: readonly string[];
100
100
  *
101
101
  * These are not fences. They render deployment/server/model-supplied strings into a body the run loop
102
102
  * then wraps in engine authority, so a forged envelope inside one is laundered by the wrapper. The
103
- * wrapper itself cannot blanket-neutralize the family — it also shells the one body that legitimately
104
- * IS an envelope (`buildSkillsBlock`'s `<skills>` fence) so containment is expressed as OWNERSHIP:
105
- * this full set for every body that owns nothing, minus its own tag for the one that does (see
106
- * `attachmentEnvelopeTags` in turn-attachments.ts).
103
+ * wrapper itself cannot blanket-neutralize the family — it also shells bodies that legitimately ARE
104
+ * envelopes (TWO owners today: `buildSkillsBlock`'s `<skills>` fence and `renderTotalTokensReminder`'s
105
+ * `<total_tokens>` body) so containment is expressed as OWNERSHIP: this full set for every body that
106
+ * owns nothing, minus its own tag for each one that does (single source: `ATTACHMENT_TAGS_OWNED` in
107
+ * turn-attachments.ts, which the per-source `attachmentEnvelopeTags` derivation reads).
107
108
  *
108
109
  * Allocated ONCE so the sanitizer's memoized break-out regex is keyed by a stable value.
109
110
  */
@@ -48,6 +48,14 @@ export const ENGINE_ENVELOPES = Object.freeze([
48
48
  fenced: true,
49
49
  disclosed: true,
50
50
  },
51
+ {
52
+ tag: "total_tokens",
53
+ kind: "authority",
54
+ mint: "core/runner/turn-attachments.ts renderTotalTokensReminder (RB-318, CC 2.1.245-verbatim body)",
55
+ guard: "the body has no untrusted seat at all — every byte is engine copy or a finite number the producer validated; the fence below keeps the same spelling arriving from OUTSIDE from being read as this readout",
56
+ fenced: true,
57
+ disclosed: true,
58
+ },
51
59
  {
52
60
  tag: "working-file",
53
61
  kind: "framing",