@sema-agent/core 5.61.0 → 5.63.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 (61) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/agents/subagent.d.ts +12 -2
  3. package/dist/agents/subagent.js +3 -2
  4. package/dist/brain/open-responses.js +8 -3
  5. package/dist/brain/openai.js +4 -4
  6. package/dist/brain/stream-engine.d.ts +13 -2
  7. package/dist/brain/stream-engine.js +3 -3
  8. package/dist/core/auto-compaction.d.ts +6 -4
  9. package/dist/core/auto-compaction.js +3 -0
  10. package/dist/core/auto-mode-prompt-assets.js +1 -1
  11. package/dist/core/checkpoint-store.d.ts +36 -4
  12. package/dist/core/checkpoint-store.js +1 -0
  13. package/dist/core/context-edit.d.ts +36 -29
  14. package/dist/core/context-edit.js +3 -3
  15. package/dist/core/governance-codes.d.ts +1 -1
  16. package/dist/core/governance-codes.js +2 -0
  17. package/dist/core/hooks.d.ts +86 -4
  18. package/dist/core/hooks.js +3 -3
  19. package/dist/core/memory-engine/engine.d.ts +11 -0
  20. package/dist/core/memory-engine/engine.js +29 -3
  21. package/dist/core/memory-engine/index.d.ts +1 -1
  22. package/dist/core/memory-engine/origin-clearance.d.ts +28 -0
  23. package/dist/core/park-selfcheck.js +2 -0
  24. package/dist/core/pricing.d.ts +24 -0
  25. package/dist/core/pricing.js +18 -0
  26. package/dist/core/runner/prepare-config-doors.d.ts +36 -2
  27. package/dist/core/runner/prepare-config-doors.js +66 -8
  28. package/dist/core/runner/prepare-task.d.ts +113 -12
  29. package/dist/core/runner/prepare-task.js +239 -131
  30. package/dist/core/runner/runtask.d.ts +7 -0
  31. package/dist/core/runner/runtask.js +325 -95
  32. package/dist/core/runner/turn-attachments.d.ts +137 -5
  33. package/dist/core/runner/turn-attachments.js +25 -2
  34. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  35. package/dist/core/tool-errors.d.ts +2 -1
  36. package/dist/core/tool-policy.d.ts +27 -0
  37. package/dist/core/trace.d.ts +5 -4
  38. package/dist/core/types.d.ts +163 -29
  39. package/dist/core/untrusted-text.d.ts +5 -4
  40. package/dist/core/untrusted-text.js +8 -0
  41. package/dist/core/usage-window-store.d.ts +109 -8
  42. package/dist/core/usage-window-store.js +79 -12
  43. package/dist/engine/harness/agent-harness.js +20 -5
  44. package/dist/engine/harness/types.d.ts +38 -0
  45. package/dist/engine/loop/agent-loop.js +20 -1
  46. package/dist/engine/loop/types.d.ts +41 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  49. package/dist/orchestration/workflow-types.d.ts +48 -1
  50. package/dist/orchestration/workflow-types.js +12 -4
  51. package/dist/orchestration/workflow.d.ts +14 -3
  52. package/dist/orchestration/workflow.js +44 -19
  53. package/dist/prompt-assembly/event-registry.js +2 -0
  54. package/dist/prompts/default.js +1 -1
  55. package/dist/server/http.d.ts +1 -1
  56. package/dist/stores/file/usage-window-store.d.ts +1 -1
  57. package/dist/stores/file/usage-window-store.js +27 -6
  58. package/dist/tools/loop-tick.js +1 -1
  59. package/dist/tools/scheduler-tools.js +9 -1
  60. package/package.json +1 -1
  61. package/test/export-surface.snapshot.json +3 -1
@@ -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
@@ -2658,9 +2658,12 @@ export interface TaskSpec {
2658
2658
  * Scope caveat (design/141 examples 批实测): `instructions` rides the WHOLE-TURN summarization
2659
2659
  * request only; a split-turn cut point (mid-turn prefix summarization) uses the engine-owned
2660
2660
  * turn-prefix prompt and does not carry it (same A2 boundary as `RunnerDeps.summaryProvider`).
2661
- * `withinTask: false` keeps end-of-task compaction but disables the within-task (turn-boundary)
2662
- * trigger — the escape hatch for the §25 (A) control-flow change; the system prompt then stops
2663
- * claiming mid-task summarization (§6.3 honesty).
2661
+ * `withinTask: false` keeps end-of-task compaction but disables the ROUTINE within-task
2662
+ * (turn-boundary) trigger — the escape hatch for the §25 (A) control-flow change; the system
2663
+ * prompt then stops claiming ROUTINE mid-task summarization (§6.3 honesty). It does NOT silence
2664
+ * the recovery-class forced lanes (design/374 slice 3): a prompt-too-long recovery or the guard
2665
+ * chain's arm B may still legitimately compact WITHIN the task — error/pressure recovery is not
2666
+ * a routine boundary pass, so a within-task compaction under this flag is contract-conforming.
2664
2667
  * `attachWorkingFiles` (LONGRUN-2; **default ON since 2026-07-03** — CC 198 hard-codes its
2665
2668
  * post-compact file restore, and LONGRUN-2 measured ≈2.3 extra read round-trips per compaction
2666
2669
  * without it): after each compaction, re-read the task's most recently READ files (CC
@@ -2823,6 +2826,51 @@ export interface TaskSpec {
2823
2826
  * that judgment shaped the DEFAULT-OFF posture; an opted-in deployment chooses CC parity.
2824
2827
  */
2825
2828
  budgetUsd?: true;
2829
+ /**
2830
+ * RB-318 (ruled 2026-08-26) — `total_tokens_reminder` (CC 2.1.245 producer `MPs`, renderer `Oje`):
2831
+ * the TOKEN twin of {@link budgetUsd}. One line per collected boundary,
2832
+ * `<total_tokens>N tokens left</total_tokens>`, CC-verbatim bytes.
2833
+ *
2834
+ * Activation, CC-exact in shape and BYOM-honest in substance: opted in here AND — for the two
2835
+ * COUNTDOWN arms — the task carries a token ceiling (`limits.maxTokens`, or a resource-slice
2836
+ * allocation's remainder). No ceiling ⇒ permanently silent, because core does not know and will not
2837
+ * guess your model's context window: the number it publishes is the one it already ENFORCES
2838
+ * (`stats.tokens` against that ceiling), so the readout can never disagree with an eventual
2839
+ * `limits.max_tokens_exceeded`. The two CONSTANT arms (`infinite` / `fixed`) read no measurement at
2840
+ * all and need no ceiling.
2841
+ *
2842
+ * Cadence, CC-exact: no threshold ladder and no throttle — every collected boundary carries it, and
2843
+ * the "progression" is the numbers advancing with spend. Recorded deviation (shared with
2844
+ * {@link budgetUsd}, and for the same reason): the lane only rides boundaries whose turn resolved
2845
+ * ≥1 tool call, so a boundary steer never EXTENDS a run that reached its natural end.
2846
+ *
2847
+ * Default OFF like every other member of this family. NOTE: this relaxes the same design/74 "no
2848
+ * budget language reaches the model" default that {@link budgetUsd} does — an opted-in deployment
2849
+ * chooses CC parity (where, since 2.1.245, this readout is on by default).
2850
+ */
2851
+ totalTokensReminder?: true;
2852
+ /**
2853
+ * RB-318 — which arm of the readout, CC 2.1.245's closed set (`off` / `infinite` / `fixed` /
2854
+ * `countdown` / `padded-countdown`); absent ⇒ CC's own default `"padded-countdown"`.
2855
+ *
2856
+ * - `countdown` — remaining = ceiling − spend, raw.
2857
+ * - `padded-countdown` — the same, through a monotone floor, so the number NEVER JUMPS BACK
2858
+ * within one engine leg (CC's per-agent smoothing floor; ONE `runTask` invocation is the
2859
+ * epoch). A durable-resume leg is a NEW epoch by design: the slice window, the spend
2860
+ * coordinate and the floor all restart, so the resumed leg's readout RE-ANCHORS to its own
2861
+ * real remaining — which may sit above the prior leg's last readout (the fresh slice window
2862
+ * is a genuine new allowance; CC's own re-anchor arm counts a new task epoch down from the
2863
+ * full budget again). Carrying the floor across legs would publish "0 tokens left" against a
2864
+ * window the run genuinely still holds — a frozen falsehood, deliberately not done.
2865
+ * - `off` — silences the lane while leaving it wired, for a deployment that resolves the mode from
2866
+ * its own env/settings the way CC does (core reads no env).
2867
+ * - `infinite` / `fixed` — CC's two constant arms: the literal `Infinite`, and the constant
2868
+ * 5000000. They publish no measurement (that is what they are for) and need no ceiling.
2869
+ *
2870
+ * A value outside the set is REFUSED at prepare (`config.attachment_invalid`), never folded to the
2871
+ * default — a near-miss spelling must not silently publish a different readout than the one asked for.
2872
+ */
2873
+ totalTokensReminderMode?: "off" | "infinite" | "fixed" | "countdown" | "padded-countdown";
2826
2874
  /** Post-compact background-task restatement — DEFAULT ON since 5.12.0 (boolean, not `true`:
2827
2875
  * explicit `false` is the opt-out; same contract as the listing family below). CC hard-codes
2828
2876
  * this behavior, and the opt-in default left every non-shell host (server-driven runs) with a
@@ -3123,8 +3171,10 @@ export interface TaskResult {
3123
3171
  *
3124
3172
  * 1.37+ terminal codes use a **dotted namespace** so a caller can prefix-match a whole class:
3125
3173
  * `"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`/…)
3174
+ * `"limits.max_walltime_exceeded"` / `"config.limit_invalid"` / `"config.limit_unknown_key"` /
3175
+ * `"config.attachment_invalid"` (RB-318 — a mode-valued `TaskSpec.attachments` member outside its
3176
+ * closed set, refused at the same door as the limits; e.g. a near-miss `totalTokensReminderMode`
3177
+ * spelling) (e.g. `errorCode.startsWith("limits.")`). 1.36 brain codes (`auth`/`network`/`rate_limit`/…)
3128
3178
  * and `"conflict"` remain flat (unchanged, to avoid breaking existing consumers).
3129
3179
  *
3130
3180
  * design/164 件四/件五 added two codes for the EXTERNAL stop causes — neither is a `limits.` code,
@@ -3137,6 +3187,14 @@ export interface TaskResult {
3137
3187
  * the wait hint rides the thrown error's `retryAfterMs` (delivered through `RunnerDeps.onError`) and
3138
3188
  * the message text. Retrying before the window frees will be refused again.
3139
3189
  * Their config-time siblings are `"config.env_lifetime_invalid"` / `"config.usage_window_invalid"`.
3190
+ * A governance window with a MONEY ceiling adds two more, both of which say "the ceiling could not be
3191
+ * evaluated" rather than "the ceiling was reached" — neither is retryable without a config change:
3192
+ * - `"config.usage_window_unpriced"` — a `UsageWindow.maxCostUsd` over a run with no cost figure (no
3193
+ * `RunnerDeps.pricing` entry and no `Model.cost`). Raised at the door for the run's own model, and at
3194
+ * the accounting point when a mid-run model switch loses the price table.
3195
+ * - `"usage_window.store_cost_unanswered"` — the wired ledger does not carry the money arm: it either
3196
+ * dropped the cost supplied to a charge, or answered a $ window without the cost it holds. Both mean
3197
+ * the ceiling was never evaluated (typically a store or decorator that predates the cost arm).
3140
3198
  */
3141
3199
  errorCode?: string;
3142
3200
  /**
@@ -3446,7 +3504,8 @@ export interface TaskResult {
3446
3504
  * · `defused` (an MCP/web segment's exact-mark bytes were rewritten — the lane's one sanctioned
3447
3505
  * byte change, always paired with a `marked` disclosure),
3448
3506
  * · `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
3507
+ * subset is `task-notification` / `new-diagnostics` / `user_memory` / `skills` /
3508
+ * `total_tokens`; `scope` is
3450
3509
  * fenced but not disclosed, since `<scope>…</scope>` is also an ordinary build-file element.
3451
3510
  * That family carries no mark, so its sentence is positional rather than byte-testable. It
3452
3511
  * rides ON the reminder copy when both families hit, so `envelope` can be bumped alongside
@@ -3670,8 +3729,15 @@ export interface ToolActivity {
3670
3729
  export type HumanInputSource = "objective" | "steer" | "next_turn" | "wake" | "external" | "system";
3671
3730
  /** design/171 §6.2 — how a human input was disposed of when its event was emitted:
3672
3731
  * `"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";
3732
+ * boundary; `"parked_for_wake"` = parked for the session's next run (torn-down lane);
3733
+ * `"blocked"` (design/373 §4.3, additive closed-set add, consumers named in the ship post) =
3734
+ * a PARKED entry was withheld at resume redelivery — the screen runs on EVERY resume kind that
3735
+ * drains parked steers (policy_ask / dry_run_review / plan_review / resource_limit, wake
3736
+ * included) — by the deployment's `userPromptSubmit` screen (block verdict, or a fail-closed
3737
+ * non-answer/crash): the row was consumed, the frame never reached the model, and the sibling
3738
+ * `steering.parked_input_blocked` notice names the same inputId — "park 时收下、redeliver 时被筛"
3739
+ * is auditable, never a silent disappearance. */
3740
+ export type HumanInputDelivery = "applied" | "queued" | "parked_for_wake" | "blocked";
3675
3741
  /**
3676
3742
  * The fleet-task kinds a DELEGATED run can honestly claim, derived from (never a second spelling of)
3677
3743
  * {@link TaskNotificationPayload}'s `task_type` vocabulary — the same axis a consumer already keys
@@ -3684,6 +3750,32 @@ export type DelegationTaskType = Extract<TaskNotificationPayload["task_type"], "
3684
3750
  export type TaskEvent = ({
3685
3751
  type: "text_delta";
3686
3752
  delta: string;
3753
+ } & TaskEventIdentity) | ({
3754
+ /**
3755
+ * #447 — the assistant's streaming PROSE SEGMENT is COMPLETE: the model closed the text content
3756
+ * block whose bytes just streamed as `text_delta`s. This is the explicit segment boundary a
3757
+ * REMOTE consumer needs so it never has to guess segment ends from wire silence (the idle-flush
3758
+ * heuristic this retires cut one slow-model reply into N fragments). CC-aligned: CC's agent
3759
+ * stream yields each finished content block as its own assistant-message unit at the provider's
3760
+ * `content_block_stop` — block completion IS the segmentation signal there; a single-process
3761
+ * consumer reads it off the provider stream natively, and this event is that same boundary
3762
+ * surfaced on the TaskEvent wire (CC pays the byte cost of re-carrying the block at the stop;
3763
+ * so does this event's `content`).
3764
+ *
3765
+ * `content` = the authoritative FULL text of the completed segment (byte-equal to that
3766
+ * segment's accumulated deltas, from the brain's own accumulation) — a consumer commits the
3767
+ * segment from it instead of trusting its own delta stitching. UNTRUSTED model output for
3768
+ * display only, same contract as `text_delta`. Emitted only for a segment that holds bytes: an
3769
+ * empty text block closes silently (a boundary with no segment would render phantom rows).
3770
+ *
3771
+ * Additive + ignorable. HONEST ABSENCE: the frame exists only when the serving Brain reports
3772
+ * block ends (`text_end` on its event stream — all three first-party brains do; a custom brain
3773
+ * that never emits them yields a wire without this frame). A consumer treats per-segment
3774
+ * presence as the signal and falls back to its own heuristic only on streams that carry none —
3775
+ * absence is "unreported", never "the segment did not end".
3776
+ */
3777
+ type: "text_end";
3778
+ content: string;
3687
3779
  } & TaskEventIdentity) | ({
3688
3780
  type: "reasoning_delta";
3689
3781
  delta: string;
@@ -4064,7 +4156,7 @@ export type TaskEvent = ({
4064
4156
  * repo-controlled and must not enter the event telemetry plane through this echo.
4065
4157
  */
4066
4158
  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";
4159
+ 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
4160
  preview: string;
4069
4161
  } & TaskEventIdentity) | ({
4070
4162
  /**
@@ -4417,6 +4509,23 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
4417
4509
  * identity (`inputId` idempotency) includes the NORMALIZED tier: the same text replayed under
4418
4510
  * the same id at a different tier refuses `steering.duplicate_input_id` — an "idempotent
4419
4511
  * success" that silently skipped the interrupt would be a disposition lie.
4512
+ *
4513
+ * design/373 §4.3 (D2, ruling of 2026-08-24) — a deployment `userPromptSubmit` hook SCREENS this verb:
4514
+ * the steer face is a SERVICE entrance (third-party callers reach a running run through it), so
4515
+ * the deployment's prompt filter sits at the entrance, `ctx.source:"steer"` + `ctx.inputId` +
4516
+ * `ctx.actor` discriminated. Chain position: domain validation → liveness → `inputId` replay
4517
+ * short-circuit → screen → accept/enqueue → (now) interrupt — an idempotent replay of an ACCEPTED
4518
+ * id answers success WITHOUT re-running the hook (a re-run could answer differently and
4519
+ * retro-falsify the standing receipt). Hook `block` ⇒ **typed throw `steering.blocked_by_hook`**
4520
+ * (message carries the hook's own bounded reason); a hook timeout / cancellation / crash refuses
4521
+ * with the SAME code, fail-closed (a screen that did not answer has not cleared the input —
4522
+ * message discriminates the cause; the crash also reaches `onError` phase:"hook"). A blocked call
4523
+ * was never accepted: no `human_input` frame, no undrained account, and the `inputId` stays
4524
+ * UNBOOKED (retry freely, changed content included). `additionalContext` ⇒ prepended to the
4525
+ * delivered frame as the engine's own reminder (never inside the untrusted mid-turn frame). The
4526
+ * replay identity is over the CALLER's bytes — hook output never enters it. Engine-authored
4527
+ * frames (task notifications, diagnostics) and external `notify()` text are OUT of the screen's
4528
+ * domain (notifications are sanitized DATA, not prompts — the notify contract's ruling).
4420
4529
  */
4421
4530
  steer(text: string, options?: {
4422
4531
  trusted?: boolean;
@@ -5063,6 +5172,17 @@ export interface EngineNotice {
5063
5172
  * implemented, so the unhonored-knob disclosure it carried has no referent; consumers must
5064
5173
  * judge ladder support by VERSION, never by that code's absence.
5065
5174
  *
5175
+ * - `"steering.parked_input_blocked"` (design/373 §4.3) — a PARKED steer entry was withheld when
5176
+ * a resume redelivered it (any resume kind that drains parked steers — wake included) by the
5177
+ * deployment's `userPromptSubmit` screen (block verdict, or a fail-closed non-answer/crash):
5178
+ * the row was consumed with the checkpoint, the frame never reached the model, and the resume
5179
+ * itself proceeds (a blocked instruction must never wedge a wake). The lifecycle half of the
5180
+ * same fact is the entry's own `human_input` account with `delivery: "blocked"` — the two share
5181
+ * the inputId, so "accepted at park, screened at redelivery" is auditable end to end. Audience
5182
+ * `"user"` (the person whose instruction was withheld is the one entitled to re-issue it);
5183
+ * `detail: { inputId?, sessionId, taskId? }` — `inputId` is the parked entry's stored
5184
+ * correlation key.
5185
+ *
5066
5186
  * - `"memory.session_polluted"` (design/178 §3, #324a; message mode-aware since design/336) —
5067
5187
  * this session's memory crossed into the one-way externally-exposed state (a tool classified
5068
5188
  * as an external content source was invoked, directly or through a delegated child). Under
@@ -5807,11 +5927,17 @@ export interface RunnerDeps {
5807
5927
  * `SendMessageToolOptions.admission`; read per call (a value change governs the next message). */
5808
5928
  peerAdmission?: Partial<import("../agents/peer-admission.js").PeerAdmissionConfig>;
5809
5929
  /**
5810
- * design/164 件五 — DEPLOYMENT-level usage governance: token allowances that span TASKS, evaluated per
5930
+ * design/164 件五 — DEPLOYMENT-level usage governance: allowances that span TASKS, evaluated per
5811
5931
  * principal (or once for the whole deployment when a task declares none). A different axis from
5812
5932
  * `TaskSpec.limits`, which is the allowance ONE task asked for — an operator granting "N tokens per 5
5813
5933
  * hours" cannot express it as a task limit, because nothing stops the next task from asking again.
5814
5934
  *
5935
+ * A window carries a TOKEN ceiling and, optionally, a MONEY ceiling (`UsageWindow.maxCostUsd`, absolute
5936
+ * USD — the `TaskLimits.maxCostUsd` quantity one governance level up). The two are independent and
5937
+ * either one binds. A $ ceiling requires a PRICED run: a task whose model has neither a {@link pricing}
5938
+ * entry nor a `Model.cost` declaration is refused at the door (`config.usage_window_unpriced`) rather
5939
+ * than charged the fabricated 0 an unpriced run would otherwise file into an operator's ceiling.
5940
+ *
5815
5941
  * Unset (the default) ⇒ NO governance: no ledger is read or written and no task can be refused for
5816
5942
  * usage. When set, every window is evaluated at two moments:
5817
5943
  * - **entry** (before the first model call of a fresh task): an exhausted window REFUSES the task with
@@ -5828,7 +5954,7 @@ export interface RunnerDeps {
5828
5954
  * would take the deployment down instead of telling the operator).
5829
5955
  */
5830
5956
  usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
5831
- /** design/164 件五 — the cross-task token ledger {@link usageWindows} is evaluated against. Core bundles
5957
+ /** design/164 件五 — the cross-task usage ledger {@link usageWindows} is evaluated against. Core bundles
5832
5958
  * `InMemoryUsageWindowStore` (process-local) and `FileUsageWindowStore` (restart-surviving); a fleet
5833
5959
  * deployment plugs a database behind the same two-method seam. Ignored when `usageWindows` is unset. */
5834
5960
  usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
@@ -6075,8 +6201,11 @@ export interface RunnerDeps {
6075
6201
  /**
6076
6202
  * Default in-process hooks for all tasks (design/37) — the FULL lifecycle seam of the `Hooks`
6077
6203
  * 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
6204
+ * `postToolUse` (rewrite output + inject context), `userPromptSubmit` (design/373 §4.3: screens
6205
+ * EVERY user-lane entrance the objective, a live `TaskStream.steer`, a wake resume's message,
6206
+ * and a parked steer's redelivery, discriminated by `ctx.source`; a hook that blocks
6207
+ * unconditionally refuses steers/wakes too — see the ⚠️ WIDENED INVOCATION SET banner on
6208
+ * {@link import("./hooks.js").Hooks.userPromptSubmit}), plus `stop` (push back when the run would otherwise end and continue
6080
6209
  * it), `postToolUseFailure` / `postToolBatch` / `permissionDenied` (failure, batch-boundary and
6081
6210
  * deny observers), `preCompact` / `postCompact` (compaction gate + observer), `stopFailure`
6082
6211
  * (API-error terminal observer) and the `preToolUseObservational` declaration flag — each member's
@@ -6219,22 +6348,26 @@ export interface RunnerDeps {
6219
6348
  */
6220
6349
  toolResultThresholdChars?: number;
6221
6350
  /**
6222
- * design/374 — microCompact machine-alignment knobs (EXPERIMENTAL until the slice-3 default
6223
- * flip; both default OFF so a deployment that never touches this bag runs the pre-374 machine
6224
- * byte for byte).
6351
+ * design/374 — microCompact machine-alignment knobs. Since the slice-3 default flip BOTH knobs
6352
+ * default ON (`machine: "cc"`, `clearOnRejection: true`); the pre-374 behavior is the explicit
6353
+ * opt-out `{ machine: "legacy", clearOnRejection: false }`.
6225
6354
  *
6226
6355
  * - `machine`: which stale-tool-result clearing machine the request pipeline runs —
6227
- * `"legacy"` (default; the historical keep-3 / clear-to-budget machine) or `"cc"` (the CC
6228
- * 2.1.223 rejection-leg form: keep 5, ≥20k minimum-savings gate, one deep clear beyond the
6229
- * keep window, CC marker bytes). ⚠️ Read the P-form warning on
6230
- * {@link import("./context-edit.js").ContextEditMachine} before selecting `"cc"`: until the
6231
- * slice-3 fallback re-ordering ships, the 20k gate sits in front of the only reduction while
6232
- * the message-dropping guard trim still backstops opting in is accepting that trade.
6356
+ * `"cc"` (default: the CC 2.1.223 rejection-leg form keep 5, ≥20k minimum-savings gate, one
6357
+ * deep clear beyond the keep window, CC marker bytes), `"legacy"` (the historical keep-3 /
6358
+ * clear-to-budget machine, kept as the compatibility opt-out; it also keeps the pre-374
6359
+ * backstop order — guard trim as the ordinary second line), or `"off"` (no proactive frontier
6360
+ * clearing at all — the D-2 off switch; the unified machine then gets its ONE shot at the
6361
+ * blocking point instead, the slice-3 arm A, trace trigger `"blocking"`). The pre-flip P-form
6362
+ * warning on {@link import("./context-edit.js").ContextEditMachine} is resolved: the slice-3
6363
+ * fallback re-ordering shipped with this default (blocking-point re-run → in-turn forced
6364
+ * compaction behind the adopt seam → trim demoted to the disaster-only last resort), so an
6365
+ * under-20k refusal no longer falls straight into a message-dropping trim.
6233
6366
  * - `clearOnRejection` (MC-R, slice 2): on a provider input-too-long rejection, run ONE cheap
6234
6367
  * deterministic clear over the rejected projection (same cc machine, savings ≥20k or nothing)
6235
- * and retry inside the turn BEFORE the forced-compaction recovery. Default false (X2: the
6236
- * machinery lands dark; the default flips together with the machine in slice 3). Independent
6237
- * of `machine` — an enabled MC-R always clears in the cc form (the rejection arm has no
6368
+ * and retry inside the turn BEFORE the forced-compaction recovery. Default true (flipped with
6369
+ * the machine default in slice 3; X2's dark-landing clause is spent). Independent of
6370
+ * `machine` — an enabled MC-R always clears in the cc form (the rejection arm has no
6238
6371
  * budget coordinate for the legacy incremental form to stop at). BUDGET ACCOUNTING (design/374
6239
6372
  * §3.2.1, stated here because it is otherwise invisible to a deployment): a successful MC-R
6240
6373
  * clear-and-retry SPENDS one attempt of the shared prompt-too-long recovery budget (default 2
@@ -6246,10 +6379,11 @@ export interface RunnerDeps {
6246
6379
  * A declaration outside the closed vocabulary (a `machine` string not in the union, a
6247
6380
  * non-boolean `clearOnRejection` — JSON/env-derived config the type cannot guard) refuses the
6248
6381
  * whole prepare loudly (`code: "config.microcompact_invalid"`, no silent re-default): folding it
6249
- * would run the pre-374 machine while the deployment believes it opted in.
6382
+ * would silently run the DEFAULT machine while the deployment believes its declaration took
6383
+ * effect.
6250
6384
  */
6251
6385
  microCompact?: {
6252
- machine?: "legacy" | "cc";
6386
+ machine?: "off" | "legacy" | "cc";
6253
6387
  clearOnRejection?: boolean;
6254
6388
  };
6255
6389
  /**
@@ -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",
@@ -13,9 +13,9 @@
13
13
  * backends equivalent by construction rather than by two hand-written copies that drift.
14
14
  */
15
15
  /**
16
- * One governed window. Both fields are allowances, not hints: `windowMs` is the window's WIDTH and
17
- * `maxTokens` the tokens it admits. `anchor` picks between the two window shapes that actually exist in
18
- * the wild:
16
+ * One governed window. Its fields are allowances, not hints: `windowMs` is the window's WIDTH and
17
+ * `maxTokens` / `maxCostUsd` the two CEILINGS it admits work under. `anchor` picks between the two window
18
+ * shapes that actually exist in the wild:
19
19
  * - `"first-use"` — the window OPENS at the key's first charge and lasts `windowMs`; when it lapses with
20
20
  * no further use, the next charge opens a fresh one. This is the "5 hours from when you started"
21
21
  * shape: bursty use is admitted at full width, and an idle key is never penalized for old traffic.
@@ -29,6 +29,28 @@ export interface UsageWindow {
29
29
  /** Tokens the window admits before it is exhausted. Must be finite and >= 0; `0` is a real, always-full
30
30
  * window (an operator lock-out), not "unset". */
31
31
  maxTokens: number;
32
+ /**
33
+ * OPTIONAL second ceiling: the MONEY the window admits before it is exhausted, in absolute USD — the
34
+ * same quantity and unit `TaskLimits.maxCostUsd` names, one governance level up (a task's own allowance
35
+ * vs the allowance an operator grants a principal across tasks). Must be finite and >= 0; `0` is a real,
36
+ * always-full window, exactly as it is for `maxTokens`.
37
+ *
38
+ * The two ceilings are INDEPENDENT and both bind: a window is exhausted when EITHER its tokens reach
39
+ * `maxTokens` or its cost reaches `maxCostUsd` — whichever fills first. Absent (the default, and every
40
+ * pre-existing deployment) means THIS window governs tokens only — its own reading never asks for or
41
+ * enforces a cost. Whether cost is tracked/recorded at all is a DEPLOYMENT-level fact, not a
42
+ * per-window one (the `UsageSlot.costMicroUsd` / `UsageBucketRow` in-presence condition: "while the
43
+ * deployment governs at least one `maxCostUsd` window"): in a MIXED declaration the shared ledger
44
+ * rows carry cost under every window's bucket, and only a deployment with NO $ window anywhere keeps
45
+ * its rows byte-identical to the pre-cost-arm shape.
46
+ *
47
+ * **Requires a PRICED run.** Cost is not a number the engine can invent: a run whose model has neither a
48
+ * `RunnerDeps.pricing` entry nor a `Model.cost` declaration produces NO cost figure at all (not a zero),
49
+ * so a $ ceiling over it is unevaluable and the task is refused at the door
50
+ * (`config.usage_window_unpriced`) rather than charged a fabricated 0 that would let the ceiling
51
+ * silently stop applying.
52
+ */
53
+ maxCostUsd?: number;
32
54
  /** Which of the two window shapes above this is. */
33
55
  anchor: "first-use" | "rolling";
34
56
  }
@@ -36,6 +58,19 @@ export interface UsageWindow {
36
58
  export interface UsageSlot {
37
59
  at: number;
38
60
  tokens: number;
61
+ /** The charge's cost in integer micro-USD. **In-presence condition:** written only while the deployment
62
+ * governs at least one {@link UsageWindow.maxCostUsd} window AND the charge had a cost figure at all — a
63
+ * token-only deployment's ledger rows are byte-for-byte what they were before the cost arm existed, and
64
+ * so is the row for spend nothing could price. Absent therefore reads as "no money recorded here": a row
65
+ * written BEFORE a $ window was declared, or one whose model had no price table, contributes 0 to the
66
+ * money lane rather than back-dating a number nobody recorded. */
67
+ costMicroUsd?: number;
68
+ /** This charge's spend could NOT be priced (see {@link UsageWindowStore.charge}'s `null` arm).
69
+ * **In-presence condition:** only ever `true`, only on a row written while a $ window was governed and
70
+ * nothing could price the spend. It is what keeps "no money here" apart from "no money spent here": a
71
+ * window holding one of these reports a money figure that is a LOWER BOUND, and says so
72
+ * ({@link UsageWindowReading.costUnknown}). */
73
+ costUnknown?: true;
39
74
  }
40
75
  /** The OPEN first-use window for one `windowMs` on one key. Rows for other widths coexist, so a
41
76
  * deployment governing 5h and 7d windows keeps one row per width rather than one blended counter. */
@@ -46,6 +81,11 @@ export interface UsageBucketRow {
46
81
  openedAt: number;
47
82
  /** Tokens charged into it since it opened. */
48
83
  tokens: number;
84
+ /** Integer micro-USD charged into it since it opened. Same in-presence condition as {@link UsageSlot.costMicroUsd}. */
85
+ costMicroUsd?: number;
86
+ /** Some spend in this open window could not be priced. Same meaning and in-presence condition as
87
+ * {@link UsageSlot.costUnknown}; sticky for the life of the open bucket. */
88
+ costUnknown?: true;
49
89
  }
50
90
  /** Everything one key's governance state consists of: the rolling lane's slots and the first-use lane's
51
91
  * open buckets. Persisted verbatim by every backend (plain JSON, no methods). */
@@ -59,7 +99,20 @@ export interface UsageWindowReading {
59
99
  window: UsageWindow;
60
100
  /** Tokens the window currently holds. */
61
101
  tokens: number;
62
- /** `tokens >= window.maxTokens` the window admits no further work. */
102
+ /** Integer micro-USD the window currently holds. **In-presence condition:** present exactly when
103
+ * `window.maxCostUsd !== undefined` — a reading for a token-only window is byte-identical to what it
104
+ * was before the cost arm existed, and a MISSING value on a $ window means the store did not evaluate
105
+ * the ceiling at all (refused loudly by {@link usageRetryAfterMs}, never read as an open ceiling). */
106
+ costMicroUsd?: number;
107
+ /** The window holds spend nothing could price, so `costMicroUsd` is a LOWER BOUND rather than the total.
108
+ * **In-presence condition:** only ever `true`, and only on a window that declares `maxCostUsd`. The
109
+ * ceiling still binds on what IS countable (an under-count can only postpone exhaustion, never invent
110
+ * it), and the engine announces the gap on every run that reads it — the alternative, treating the
111
+ * whole window as exhausted until the unpriced charge ages out, would lock a key out for as long as
112
+ * `windowMs` over one degraded turn, which is a larger outage than the gap it answers. */
113
+ costUnknown?: true;
114
+ /** The window admits no further work: `tokens >= window.maxTokens`, OR (when the window declares one)
115
+ * `costMicroUsd >= window.maxCostUsd` — the two ceilings are independent and either one binds. */
63
116
  exhausted: boolean;
64
117
  /** Ms until this window next frees capacity: for `first-use`, when the open window lapses; for
65
118
  * `rolling`, when its OLDEST in-window slot ages out (which frees that slot's tokens, not
@@ -82,8 +135,19 @@ export interface UsageWindowStore {
82
135
  * needs it to know which first-use buckets to keep open and how far back the rolling lane must
83
136
  * remember, so the ledger stays bounded instead of growing for the life of the deployment.
84
137
  * Called once per accounting point; never with a negative or non-finite `tokens`.
138
+ *
139
+ * `costMicroUsd` is the same charge's MONEY, in integer micro-USD, or `null` when the spend is real but
140
+ * NOTHING could price it. **In-presence condition:** one of the two is supplied exactly when the
141
+ * deployment governs at least one {@link UsageWindow.maxCostUsd} window — a token-only deployment is
142
+ * called with the pre-cost-arm argument list and must behave identically.
143
+ *
144
+ * A store that governs $ windows MUST FORWARD it (into `chargeUsageRecord`, or into its own equivalent)
145
+ * and answer {@link UsageWindowReading.costMicroUsd} from what it persisted. A decorator written against
146
+ * the pre-cost-arm signature drops the argument silently — which is why the arithmetic refuses an absent
147
+ * cost outright (`usage_window.store_cost_unanswered`) instead of folding it to a 0 that would leave the
148
+ * money ceiling open forever with nothing able to notice.
85
149
  */
86
- charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
150
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[], costMicroUsd?: number | null): Promise<void>;
87
151
  /** Read `key`'s state for each window as of `now`, in the order the windows were given. */
88
152
  read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
89
153
  }
@@ -99,6 +163,10 @@ export declare const EMPTY_USAGE_WINDOW_RECORD: UsageWindowRecord;
99
163
  * would govern a deployment by a number nobody chose. Refuses with `config.usage_window_invalid`.
100
164
  */
101
165
  export declare function resolveUsageWindows(windows: readonly UsageWindow[] | undefined): readonly UsageWindow[] | undefined;
166
+ /** Does the deployment govern MONEY at all? The one predicate that decides whether a cost figure is asked
167
+ * of the engine, written into the ledger, or answered in a reading — so a token-only deployment stays
168
+ * byte-for-byte what it was before the cost arm existed. */
169
+ export declare function windowsGovernCost(windows: readonly UsageWindow[]): boolean;
102
170
  /**
103
171
  * Fold one charge into a key's record and return the NEW record (pure — the input is never mutated, so a
104
172
  * store that keeps records in a Map cannot be corrupted by a half-applied charge).
@@ -107,8 +175,23 @@ export declare function resolveUsageWindows(windows: readonly UsageWindow[] | un
107
175
  * can never be read again, and a first-use bucket for a width the deployment no longer governs can never
108
176
  * be read again either, so both are dropped here. A record therefore stays bounded by (charges within the
109
177
  * widest rolling window) + (one row per governed first-use width).
178
+ *
179
+ * `costMicroUsd` rides the same fold as `tokens` (integer micro-USD, never a float USD: the ledger sums
180
+ * thousands of charges and a float would accumulate error into a money ceiling), and it is a THREE-state
181
+ * argument while the deployment governs a $ window:
182
+ * - a NUMBER — the charge's cost, folded into the money lane;
183
+ * - `null` — the tokens are known but their cost is NOT (an unpriced model served the spend). The row is
184
+ * written with its tokens and NO cost field: the token ceiling stays exact, and the money lane counts
185
+ * nothing rather than a fabricated 0. The engine refuses such a run anyway — this keeps the ledger
186
+ * honest about the spend that already happened before the refusal;
187
+ * - `undefined` — REFUSED. Core always supplies one of the two above when a $ window is governed, so an
188
+ * absent argument means some store in the chain (a pre-cost-arm decorator, most likely) dropped it, and
189
+ * folding it to 0 would leave the money ceiling permanently open with nothing able to detect it.
190
+ * With NO $ window governed the argument is ignored entirely and rows keep their pre-cost-arm bytes.
191
+ * Rows that already carry a cost keep it, so removing and re-adding a $ window neither loses the history
192
+ * nor rewrites rows that predate it.
110
193
  */
111
- export declare function chargeUsageRecord(record: UsageWindowRecord, tokens: number, at: number, windows: readonly UsageWindow[]): UsageWindowRecord;
194
+ export declare function chargeUsageRecord(record: UsageWindowRecord, tokens: number, at: number, windows: readonly UsageWindow[], costMicroUsd?: number | null): UsageWindowRecord;
112
195
  /**
113
196
  * Read a key's record against the governed windows as of `now` (pure). The `first-use` lane reads the open
114
197
  * bucket; the `rolling` lane sums the slots inside `(now − windowMs, now]`.
@@ -119,8 +202,26 @@ export declare function readUsageRecord(record: UsageWindowRecord, windows: read
119
202
  * window would admit work again. `undefined` when nothing is exhausted. The MAXIMUM (not the minimum) of
120
203
  * the exhausted windows' hints — resuming when the shortest one clears would immediately re-suspend on
121
204
  * the longer one.
205
+ *
206
+ * It is also the ONE choke point every governance read in the engine goes through, so the integrity
207
+ * checks live here, in two arms:
208
+ * - **coverage** (when the caller supplies the declared `windows`): one reading per declared window,
209
+ * in the order the windows were given, with the window echoed verbatim on the fields that name the
210
+ * allowance (`windowMs`/`anchor`/`maxTokens`/`maxCostUsd`). The per-reading arm below is
211
+ * structurally blind to a reading that is MISSING — a decorator store that maps only the windows
212
+ * it recognizes, reorders the answer, or echoes a normalized copy with `maxCostUsd` stripped would
213
+ * otherwise un-govern a declared ceiling with zero symptoms. Every engine read passes its windows
214
+ * through; the parameter stays optional so a bare-readings caller keeps the pre-coverage face.
215
+ * - **per-reading**: a reading for a window that DECLARES `maxCostUsd` while carrying no
216
+ * `costMicroUsd` means the store never evaluated the money ceiling — its `exhausted` verdict then
217
+ * covers the token axis alone.
218
+ * Both arms refuse with ONE code (`usage_window.store_cost_unanswered`) rather than read as an open
219
+ * ceiling — an unanswered window and an answered-without-cost window are the same fact ("the store
220
+ * never proved it evaluated the declared ceiling"): a governance ceiling that silently stops applying
221
+ * is worse than one that never existed, which is the same posture the file backend takes when its
222
+ * ledger is unreadable.
122
223
  */
123
- export declare function usageRetryAfterMs(readings: readonly UsageWindowReading[]): number | undefined;
224
+ export declare function usageRetryAfterMs(readings: readonly UsageWindowReading[], windows?: readonly UsageWindow[]): number | undefined;
124
225
  /**
125
226
  * Process-local {@link UsageWindowStore} reference implementation. Governs correctly within ONE Runner
126
227
  * process and loses its ledger on restart — the right choice for tests and single-process deployments,
@@ -129,6 +230,6 @@ export declare function usageRetryAfterMs(readings: readonly UsageWindowReading[
129
230
  */
130
231
  export declare class InMemoryUsageWindowStore implements UsageWindowStore {
131
232
  private readonly records;
132
- charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
233
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[], costMicroUsd?: number | null): Promise<void>;
133
234
  read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
134
235
  }