@sema-agent/core 5.60.1 → 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.
- package/CHANGELOG.md +125 -0
- package/dist/agents/subagent.d.ts +4 -2
- package/dist/agents/subagent.js +9 -9
- package/dist/brain/open-responses.js +8 -3
- package/dist/brain/openai.js +4 -4
- package/dist/brain/stream-engine.d.ts +13 -2
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +36 -4
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -2
- package/dist/core/hooks.d.ts +83 -4
- package/dist/core/hooks.js +3 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +19 -1
- package/dist/core/memory-engine/consolidation-driver.js +75 -3
- package/dist/core/memory-engine/consolidation.d.ts +52 -5
- package/dist/core/memory-engine/consolidation.js +3 -1
- package/dist/core/memory-engine/distiller.d.ts +89 -1
- package/dist/core/memory-engine/distiller.js +94 -5
- package/dist/core/memory-engine/engine.d.ts +8 -0
- package/dist/core/memory-engine/engine.js +51 -8
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/pricing.d.ts +24 -0
- package/dist/core/pricing.js +18 -0
- package/dist/core/runner/prepare-config-doors.d.ts +34 -0
- package/dist/core/runner/prepare-config-doors.js +55 -0
- package/dist/core/runner/prepare-task.d.ts +52 -10
- package/dist/core/runner/prepare-task.js +77 -42
- package/dist/core/runner/runtask.d.ts +7 -0
- package/dist/core/runner/runtask.js +254 -38
- package/dist/core/runner/turn-attachments.d.ts +137 -5
- package/dist/core/runner/turn-attachments.js +25 -2
- package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
- package/dist/core/task-notification.d.ts +50 -23
- package/dist/core/task-notification.js +20 -4
- package/dist/core/tool-errors.d.ts +2 -1
- package/dist/core/tool-policy.d.ts +27 -0
- package/dist/core/types.d.ts +214 -31
- package/dist/core/untrusted-text.d.ts +5 -4
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/usage-window-store.d.ts +109 -8
- package/dist/core/usage-window-store.js +79 -12
- package/dist/engine/harness/agent-harness.d.ts +58 -2
- package/dist/engine/harness/agent-harness.js +115 -5
- package/dist/engine/loop/agent-loop.js +153 -15
- package/dist/engine/loop/types.d.ts +32 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +9 -4
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.js +2 -0
- package/dist/server/http.d.ts +1 -1
- package/dist/stores/file/usage-window-store.d.ts +1 -1
- package/dist/stores/file/usage-window-store.js +27 -6
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +3 -3
- package/dist/tools/monitor.js +1 -1
- package/dist/tools/scheduler-tools.js +9 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +7 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -4177,6 +4266,12 @@ export type TaskEvent = ({
|
|
|
4177
4266
|
*/
|
|
4178
4267
|
type: "task_notification";
|
|
4179
4268
|
notification: TaskNotificationPayload;
|
|
4269
|
+
/** design/373 §3.6 — the frame's injection tier as DELIVERED (additive; consumers tolerate
|
|
4270
|
+
* absence): on the live lane, the tier the routing used (`next`/`now` → steer lane, `later`
|
|
4271
|
+
* → followUp lane); on the turn-open redelivery lane, the tier the park CARRIED. ABSENT is a
|
|
4272
|
+
* fact, not a default: a pre-373 producer event, or a parked record whose tier was not
|
|
4273
|
+
* carried (the terminal sweep's re-pend) — never read absence as `"later"`. */
|
|
4274
|
+
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
4180
4275
|
} & TaskEventIdentity) | ({
|
|
4181
4276
|
/**
|
|
4182
4277
|
* design/99 MF-10 — a SUBAGENT PROGRESS TICK: the delegated sub-run's usage ACCRUING
|
|
@@ -4380,11 +4475,60 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
|
|
|
4380
4475
|
* into this run's resume prompt) is NOT in it and WOULD inject again. A deployment that needs
|
|
4381
4476
|
* cross-leg or cross-process idempotency owns that half (its own key ledger), the same division
|
|
4382
4477
|
* of labor `notify`'s park-window dedup states.
|
|
4478
|
+
*
|
|
4479
|
+
* design/373 (additive) — `priority` is the steer face's injection tier, default `"next"`
|
|
4480
|
+
* (= the pre-373 behavior, byte-identical):
|
|
4481
|
+
* - `"next"` — the running turn's next boundary (unchanged);
|
|
4482
|
+
* - `"later"` — never folded into work in progress: delivered at the run's natural
|
|
4483
|
+
* would-otherwise-stop seat (its own closing turn); a run that ends before that seat re-parks
|
|
4484
|
+
* the input via the existing durable/undrained lanes (receipt semantics unchanged — "queued"
|
|
4485
|
+
* still means accepted-at-this-moment, never "consumed"); a `"later"` steer rides the followUp
|
|
4486
|
+
* lane, so its stranded account is `task.user_followup_undrained` (the accounts are LANE-keyed
|
|
4487
|
+
* — same two-key split the settled frame uses);
|
|
4488
|
+
* - `"now"` — `"next"`'s delivery guarantee PLUS the boundary is manufactured early: the frame
|
|
4489
|
+
* takes the queue's class head and, when a turn is in flight, the engine CUTS that turn at a
|
|
4490
|
+
* reconciled boundary (finished tool calls keep their real results; never-started ones settle
|
|
4491
|
+
* as paired interrupted results; the CC-verbatim interruption marker lands) and the run
|
|
4492
|
+
* CONTINUES with this input — never the run-level `interrupt()`. Best-effort accelerator:
|
|
4493
|
+
* with nothing in flight (idle tail, between turns) delivery is exactly `"next"`'s. Every
|
|
4494
|
+
* real cut is announced (`task.turn_interrupted` notice) and counts toward `maxTurns` —
|
|
4495
|
+
* deliberately (the hard cap IS the interrupt storm's bound), which means a `now` issued at
|
|
4496
|
+
* the run's FINAL allowed turn spends the remaining budget on the cut and the run then ends
|
|
4497
|
+
* at the limit before the steer's own turn: the input takes the terminal lanes like any
|
|
4498
|
+
* accepted-but-undelivered steer (durable park when a resource-suspend seat is eligible —
|
|
4499
|
+
* redeemed on resume; otherwise the loud `task.user_steer_undrained` account). Same for the
|
|
4500
|
+
* other terminal gates (budget/walltime) firing at the cut's own `turn_end`. The receipt
|
|
4501
|
+
* never promised consumption; no budget is reserved for the interruptor.
|
|
4502
|
+
* ⚠️ POWER FACE (§5-4b): passing `priority` through to a third-party caller GRANTS it the power
|
|
4503
|
+
* to cut this run's in-flight turns (cancelling that turn's in-flight tool work). A deployment
|
|
4504
|
+
* relaying steer must gate this option at its own face — relay IS authorization.
|
|
4505
|
+
* An unknown value throws typed `steering.invalid_content` (bad-value loudness). The replay
|
|
4506
|
+
* identity (`inputId` idempotency) includes the NORMALIZED tier: the same text replayed under
|
|
4507
|
+
* the same id at a different tier refuses `steering.duplicate_input_id` — an "idempotent
|
|
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).
|
|
4383
4526
|
*/
|
|
4384
4527
|
steer(text: string, options?: {
|
|
4385
4528
|
trusted?: boolean;
|
|
4386
4529
|
actor?: ActorAssertion;
|
|
4387
4530
|
inputId?: string;
|
|
4531
|
+
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
4388
4532
|
}): Promise<void>;
|
|
4389
4533
|
/**
|
|
4390
4534
|
* design/144 §2 — inject an EXTERNAL structured event into this run's task-notification lane, as a
|
|
@@ -4392,19 +4536,37 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
|
|
|
4392
4536
|
* the payload (`task_type` is cast to `"external"` — a caller can never impersonate an internal lane)
|
|
4393
4537
|
* and every field is sanitized as UNTRUSTED data on the model face. Delivery rides the EXISTING
|
|
4394
4538
|
* notification machinery, so the semantics are identical to internal producers:
|
|
4395
|
-
* -
|
|
4396
|
-
*
|
|
4397
|
-
*
|
|
4398
|
-
*
|
|
4539
|
+
* - design/373 — the injection LADDER is live on this lane (BREAKING vs the flat era, named):
|
|
4540
|
+
* `"next"` = the running turn's next boundary (mid-work included — the previous behavior of
|
|
4541
|
+
* every frame, now this tier's); `"later"` = never folded into work in progress — delivered at
|
|
4542
|
+
* the run's natural would-otherwise-stop seat (its own closing turn), and a run that ends
|
|
4543
|
+
* before that seat parks the frame per session for the NEXT run's turn-open (delivery then
|
|
4544
|
+
* depends on a next run happening — a recorded structural honesty note, this engine owns no
|
|
4545
|
+
* idle process); `"now"` is REFUSED typed (`notify.invalid_priority`) — turn-interrupt
|
|
4546
|
+
* authority belongs to the caller-provenance steer face, never to the notification lane
|
|
4547
|
+
* (callers wanting earliest-boundary delivery say `"next"`). ⚠️ The parameterless DEFAULT is
|
|
4548
|
+
* `"later"` (the CC pending-notification default): an omitted priority moved from
|
|
4549
|
+
* next-boundary to the closing seat with this design — callers that need boundary delivery
|
|
4550
|
+
* must say `"next"` explicitly. NOTE for the flat era's callers: `"later"` was silently inert
|
|
4551
|
+
* then (delivered at the boundary with no runtime signal saying otherwise) — the version is
|
|
4552
|
+
* the ONLY discriminator of which behavior a build has. Delivery order within a tier is
|
|
4553
|
+
* ARRIVAL order; consecutive buffered notifications drain as one boundary batch. `priority`
|
|
4554
|
+
* rides the park path (pend records carry it; the turn-open batch is priority-major) and the
|
|
4555
|
+
* uplink path;
|
|
4399
4556
|
* - duplicate events dedup on `task_id:status[:seq]` — pass a fresh `seq` per repeat event. The
|
|
4400
4557
|
* external lane keys in its OWN dedup domain (design/144 X1): an external event can never collide
|
|
4401
|
-
* with (or pre-occupy) an internal lane's key, and vice versa
|
|
4558
|
+
* with (or pre-occupy) an internal lane's key, and vice versa. The key deliberately excludes
|
|
4559
|
+
* `priority` (tier = delivery metadata, not event identity): a repeat under an in-flight key
|
|
4560
|
+
* folds WHATEVER tier it names — the standing entry keeps the FIRST accept's tier — so
|
|
4561
|
+
* re-tiering requires a fresh `seq` (a new occurrence), never a same-key resend;
|
|
4402
4562
|
* - a notify landing AFTER the run ended PARKS per session and the session's NEXT run delivers it
|
|
4403
4563
|
* (unlike {@link steer}, a finished task does not reject — the park lane is the contract). The park
|
|
4404
4564
|
* leg dedups on the same key (design/144 X4) with an HONEST WINDOW LIMIT: the dedup set spans ONE
|
|
4405
4565
|
* parked batch — it does not reach back across the live-queue/park boundary, and it resets when the
|
|
4406
4566
|
* next run drains the batch (a repeat sent after that drain is deliverable again);
|
|
4407
|
-
* Throws typed `notify.invalid_payload` on a malformed input
|
|
4567
|
+
* Throws typed `notify.invalid_payload` on a malformed input, `notify.invalid_priority` on
|
|
4568
|
+
* `priority: "now"` (see above — a KNOWN value this lane refuses, distinct from an unknown one),
|
|
4569
|
+
* and `notify.not_running` when the run
|
|
4408
4570
|
* never built its notification lane (prepare failed). Everything upstream of the event reaching this
|
|
4409
4571
|
* process — transport, webhook/daemon wiring, inbound authn, durable parking for idle sessions — is
|
|
4410
4572
|
* the DEPLOYMENT half (design/43: zero daemon semantics in core).
|
|
@@ -4992,19 +5154,31 @@ export interface EngineNotice {
|
|
|
4992
5154
|
* — what is announced is the remainder that could NOT be carried, so a fully-migrated park says
|
|
4993
5155
|
* nothing at all and a queue-full / no-longer-pending row still says exactly what was lost.
|
|
4994
5156
|
*
|
|
4995
|
-
* - `"task.
|
|
4996
|
-
*
|
|
4997
|
-
*
|
|
4998
|
-
*
|
|
4999
|
-
*
|
|
5000
|
-
*
|
|
5001
|
-
*
|
|
5002
|
-
*
|
|
5003
|
-
*
|
|
5004
|
-
*
|
|
5005
|
-
*
|
|
5006
|
-
*
|
|
5007
|
-
*
|
|
5157
|
+
* - `"task.turn_interrupted"` (design/373) — a caller-provenance steer with `priority: "now"`
|
|
5158
|
+
* ACTUALLY CUT the running turn: the in-flight provider stream / tool batch was aborted at a
|
|
5159
|
+
* manufactured boundary (finished tool calls keep their real results, never-started ones
|
|
5160
|
+
* settle as paired interrupted results, the interruption marker lands) and the run CONTINUES
|
|
5161
|
+
* with the steer at the queue head. One notice per REAL cut (a `now` that found nothing in
|
|
5162
|
+
* flight, or whose frame already rode the imminent boundary, announces nothing — no false
|
|
5163
|
+
* interrupt claims); every cut turn counts toward `maxTurns`, so an interrupt storm's cost is
|
|
5164
|
+
* bounded and each of its cuts is on the record. `detail: { inputId, sessionId, actorId?,
|
|
5165
|
+
* taskId? }` — `inputId` is the steer's own correlation key (the `human_input` frame's id),
|
|
5166
|
+
* `actorId` the caller's asserted identity when one rode the steer. Audience `"user"`: the
|
|
5167
|
+
* person whose input forced the boundary is the one entitled to see that it landed.
|
|
5168
|
+
* RETIRED here (BREAKING, named): `"task.injection_priority_unimplemented"` — the ladder is
|
|
5169
|
+
* implemented, so the unhonored-knob disclosure it carried has no referent; consumers must
|
|
5170
|
+
* judge ladder support by VERSION, never by that code's absence.
|
|
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.
|
|
5008
5182
|
*
|
|
5009
5183
|
* - `"memory.session_polluted"` (design/178 §3, #324a; message mode-aware since design/336) —
|
|
5010
5184
|
* this session's memory crossed into the one-way externally-exposed state (a tool classified
|
|
@@ -5750,11 +5924,17 @@ export interface RunnerDeps {
|
|
|
5750
5924
|
* `SendMessageToolOptions.admission`; read per call (a value change governs the next message). */
|
|
5751
5925
|
peerAdmission?: Partial<import("../agents/peer-admission.js").PeerAdmissionConfig>;
|
|
5752
5926
|
/**
|
|
5753
|
-
* design/164 件五 — DEPLOYMENT-level usage governance:
|
|
5927
|
+
* design/164 件五 — DEPLOYMENT-level usage governance: allowances that span TASKS, evaluated per
|
|
5754
5928
|
* principal (or once for the whole deployment when a task declares none). A different axis from
|
|
5755
5929
|
* `TaskSpec.limits`, which is the allowance ONE task asked for — an operator granting "N tokens per 5
|
|
5756
5930
|
* hours" cannot express it as a task limit, because nothing stops the next task from asking again.
|
|
5757
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
|
+
*
|
|
5758
5938
|
* Unset (the default) ⇒ NO governance: no ledger is read or written and no task can be refused for
|
|
5759
5939
|
* usage. When set, every window is evaluated at two moments:
|
|
5760
5940
|
* - **entry** (before the first model call of a fresh task): an exhausted window REFUSES the task with
|
|
@@ -5771,7 +5951,7 @@ export interface RunnerDeps {
|
|
|
5771
5951
|
* would take the deployment down instead of telling the operator).
|
|
5772
5952
|
*/
|
|
5773
5953
|
usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
|
|
5774
|
-
/** design/164 件五 — the cross-task
|
|
5954
|
+
/** design/164 件五 — the cross-task usage ledger {@link usageWindows} is evaluated against. Core bundles
|
|
5775
5955
|
* `InMemoryUsageWindowStore` (process-local) and `FileUsageWindowStore` (restart-surviving); a fleet
|
|
5776
5956
|
* deployment plugs a database behind the same two-method seam. Ignored when `usageWindows` is unset. */
|
|
5777
5957
|
usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
|
|
@@ -6018,8 +6198,11 @@ export interface RunnerDeps {
|
|
|
6018
6198
|
/**
|
|
6019
6199
|
* Default in-process hooks for all tasks (design/37) — the FULL lifecycle seam of the `Hooks`
|
|
6020
6200
|
* interface, not just the tool-call trio: `preToolUse` (rewrite/restrict args + inject context),
|
|
6021
|
-
* `postToolUse` (rewrite output + inject context), `userPromptSubmit` (
|
|
6022
|
-
*
|
|
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
|
|
6023
6206
|
* it), `postToolUseFailure` / `postToolBatch` / `permissionDenied` (failure, batch-boundary and
|
|
6024
6207
|
* deny observers), `preCompact` / `postCompact` (compaction gate + observer), `stopFailure`
|
|
6025
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
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* `
|
|
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.
|
|
17
|
-
* `maxTokens` the
|
|
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
|
-
/**
|
|
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
|
}
|