@aragon-agent/cli 0.6.3 → 0.6.7

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 (138) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/README.md +203 -13
  3. package/dist/agent/controller.d.ts +158 -2
  4. package/dist/agent/controller.js +400 -1
  5. package/dist/agent/reducer.d.ts +102 -19
  6. package/dist/agent/reducer.js +162 -71
  7. package/dist/agent/system-prompt.d.ts +14 -0
  8. package/dist/agent/system-prompt.js +2 -0
  9. package/dist/agent/usage.d.ts +34 -0
  10. package/dist/agent/usage.js +43 -0
  11. package/dist/cli.js +132 -23
  12. package/dist/commands/builtins.js +101 -1
  13. package/dist/compaction/child.js +5 -0
  14. package/dist/compaction/command.js +14 -1
  15. package/dist/compaction/compactor.d.ts +94 -24
  16. package/dist/compaction/compactor.js +247 -98
  17. package/dist/compaction/context-command.d.ts +33 -0
  18. package/dist/compaction/context-command.js +166 -0
  19. package/dist/compaction/meter.d.ts +251 -0
  20. package/dist/compaction/meter.js +428 -0
  21. package/dist/compaction/pressure.d.ts +6 -0
  22. package/dist/compaction/pressure.js +4 -0
  23. package/dist/compaction/types.d.ts +77 -0
  24. package/dist/compaction/wiring.d.ts +56 -0
  25. package/dist/compaction/wiring.js +107 -25
  26. package/dist/config/cli-commands.js +5 -0
  27. package/dist/config/env.d.ts +1 -1
  28. package/dist/config/env.js +63 -2
  29. package/dist/config/load.d.ts +28 -0
  30. package/dist/config/load.js +83 -1
  31. package/dist/config/schema.d.ts +129 -0
  32. package/dist/config/schema.js +69 -0
  33. package/dist/config/store.js +47 -1
  34. package/dist/exec/events.d.ts +138 -1
  35. package/dist/exec/events.js +42 -0
  36. package/dist/exec/index.js +43 -6
  37. package/dist/exec/progress-reporter.d.ts +23 -0
  38. package/dist/exec/progress-reporter.js +88 -0
  39. package/dist/exec/runner.d.ts +34 -0
  40. package/dist/exec/runner.js +137 -25
  41. package/dist/fast/reviewer.d.ts +19 -1
  42. package/dist/fast/reviewer.js +22 -0
  43. package/dist/fast/types.d.ts +15 -0
  44. package/dist/fast/wiring.d.ts +10 -1
  45. package/dist/fast/wiring.js +18 -1
  46. package/dist/input/limits.d.ts +72 -0
  47. package/dist/input/limits.js +63 -0
  48. package/dist/input/paste-parse.d.ts +71 -0
  49. package/dist/input/paste-parse.js +95 -0
  50. package/dist/input/stdin-filter.d.ts +81 -0
  51. package/dist/input/stdin-filter.js +379 -0
  52. package/dist/logging/install.d.ts +2 -0
  53. package/dist/logging/install.js +53 -1
  54. package/dist/proc/classify.d.ts +52 -0
  55. package/dist/proc/classify.js +143 -0
  56. package/dist/proc/kill-tree.d.ts +42 -0
  57. package/dist/proc/kill-tree.js +119 -0
  58. package/dist/proc/limits.d.ts +84 -0
  59. package/dist/proc/limits.js +84 -0
  60. package/dist/proc/log-ring.d.ts +68 -0
  61. package/dist/proc/log-ring.js +102 -0
  62. package/dist/proc/prompt.d.ts +24 -0
  63. package/dist/proc/prompt.js +49 -0
  64. package/dist/proc/readiness.d.ts +102 -0
  65. package/dist/proc/readiness.js +219 -0
  66. package/dist/proc/supervisor.d.ts +103 -0
  67. package/dist/proc/supervisor.js +465 -0
  68. package/dist/proc/types.d.ts +175 -0
  69. package/dist/proc/types.js +15 -0
  70. package/dist/session/persist.js +36 -1
  71. package/dist/todo/limits.d.ts +4 -17
  72. package/dist/todo/limits.js +4 -17
  73. package/dist/todo/panel-rows.d.ts +8 -6
  74. package/dist/todo/panel-rows.js +19 -6
  75. package/dist/tools/bash-tool.d.ts +51 -2
  76. package/dist/tools/bash-tool.js +321 -51
  77. package/dist/tools/fs-tools.d.ts +17 -0
  78. package/dist/tools/index.d.ts +55 -2
  79. package/dist/tools/index.js +39 -1
  80. package/dist/tools/proc-tools.d.ts +25 -0
  81. package/dist/tools/proc-tools.js +102 -0
  82. package/dist/ui/App.d.ts +15 -2
  83. package/dist/ui/App.js +330 -69
  84. package/dist/ui/AutocompletePopup.d.ts +3 -1
  85. package/dist/ui/AutocompletePopup.js +9 -11
  86. package/dist/ui/BottomStatusRow.d.ts +1 -1
  87. package/dist/ui/Composer.d.ts +56 -1
  88. package/dist/ui/Composer.js +50 -6
  89. package/dist/ui/PromptInput.d.ts +52 -3
  90. package/dist/ui/PromptInput.js +201 -55
  91. package/dist/ui/StatusBar.d.ts +40 -15
  92. package/dist/ui/StatusBar.js +51 -9
  93. package/dist/ui/TeamPanel.d.ts +3 -1
  94. package/dist/ui/TeamPanel.js +13 -9
  95. package/dist/ui/TodoPanel.d.ts +5 -5
  96. package/dist/ui/TodoPanel.js +56 -24
  97. package/dist/ui/Transcript.js +29 -1
  98. package/dist/ui/UpdateLine.d.ts +1 -1
  99. package/dist/ui/capabilities.js +5 -5
  100. package/dist/ui/composer-limits.d.ts +31 -0
  101. package/dist/ui/composer-limits.js +50 -0
  102. package/dist/ui/composer-rows.d.ts +73 -0
  103. package/dist/ui/composer-rows.js +201 -0
  104. package/dist/ui/editor-reducer.d.ts +37 -0
  105. package/dist/ui/editor-reducer.js +88 -12
  106. package/dist/ui/entries/ServiceCard.d.ts +66 -0
  107. package/dist/ui/entries/ServiceCard.js +116 -0
  108. package/dist/ui/entries/UserEntry.d.ts +17 -3
  109. package/dist/ui/entries/UserEntry.js +34 -5
  110. package/dist/ui/exit-snapshot.d.ts +9 -0
  111. package/dist/ui/layout/ScrollViewport.d.ts +1 -1
  112. package/dist/ui/layout/autocomplete.d.ts +18 -0
  113. package/dist/ui/layout/autocomplete.js +26 -0
  114. package/dist/ui/layout/budget.d.ts +34 -6
  115. package/dist/ui/layout/budget.js +32 -7
  116. package/dist/ui/layout/rail.d.ts +4 -53
  117. package/dist/ui/layout/rail.js +10 -62
  118. package/dist/ui/layout/team-panel.d.ts +18 -0
  119. package/dist/ui/layout/team-panel.js +22 -0
  120. package/dist/ui/layout/todo-layout.d.ts +24 -0
  121. package/dist/ui/layout/todo-layout.js +27 -0
  122. package/dist/ui/layout/virtual-window.js +51 -1
  123. package/dist/ui/overlays/HelpOverlay.js +3 -0
  124. package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
  125. package/dist/ui/overlays/QuestionOverlay.js +5 -1
  126. package/dist/ui/overlays/SettingsScreen.js +14 -1
  127. package/dist/ui/paste-frames.d.ts +87 -0
  128. package/dist/ui/paste-frames.js +112 -0
  129. package/dist/ui/paste-tokens.d.ts +90 -0
  130. package/dist/ui/paste-tokens.js +147 -0
  131. package/dist/ui/screen.d.ts +16 -0
  132. package/dist/ui/screen.js +45 -2
  133. package/dist/ui/selection/selection-controller.d.ts +1 -1
  134. package/dist/ui/transcript-text.js +55 -2
  135. package/dist/ui/use-wheel-routing.d.ts +1 -1
  136. package/package.json +2 -2
  137. package/dist/input/stdin-mouse-filter.d.ts +0 -47
  138. package/dist/input/stdin-mouse-filter.js +0 -153
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The `/context` slash command (context-usage-gauge-accuracy §3.7 / W6).
3
+ *
4
+ * ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope, and this
5
+ * text lands in a transcript `notice` - produced outside `src/ui/**`, where no
6
+ * terminal capabilities are available, so it must be safe on a legacy `cmd.exe`.
7
+ *
8
+ * IT LIVES HERE RATHER THAN IN `builtins.ts` FOR THE REASON `/compact` DOES
9
+ * (C-16 / P1-12): `CLAUDE.md` caps a source file at 1000 lines and `builtins.ts`
10
+ * is already past it. `builtins.ts` gets the registration and nothing else.
11
+ *
12
+ * THIS IS THE GUARANTEED REPORTING SURFACE FOR OCCUPANCY. The status bar drops
13
+ * readouts on a narrow terminal, the gauge is a bar and a rounded percentage,
14
+ * and neither can say WHERE the denominator came from or WHAT the session total
15
+ * includes. Every question a user asks after distrusting the bar is answered
16
+ * here, at any width, in any mode - which is also why the honesty rules below
17
+ * are not decoration.
18
+ */
19
+ import { formatCost, formatTokens, promptTokensOf } from '../agent/usage.js';
20
+ /**
21
+ * `/context` - occupancy, denominator, freshness, compaction, session spend.
22
+ *
23
+ * THE OCCUPANCY COMES FROM `controller.getContextUsage()` AND NOWHERE ELSE
24
+ * (RV-8). The obvious alternative, `getCompactionSnapshot().pressure`, returns
25
+ * `offCompactionSnapshot()`'s HARDCODED ZERO pressure whenever compaction was
26
+ * never registered - so a report built on it would read `0%` for exactly the
27
+ * sessions this whole feature exists to fix. `/compact status` gets away with
28
+ * reading it only because it returns early on that branch and never prints an
29
+ * occupancy line at all.
30
+ */
31
+ export function formatContextReport(ctx) {
32
+ const controller = ctx.controller;
33
+ const usage = controller.getContextUsage();
34
+ const lines = ['Context'];
35
+ lines.push(` Occupancy ${occupancyLine(usage)}`);
36
+ lines.push(` Window ${windowLine(usage)}`);
37
+ lines.push(` Since measured ${freshnessLine(usage)}`);
38
+ lines.push(` Compaction ${compactionLine(ctx)}`);
39
+ lines.push(` Session spend ${spendLine(ctx)}`);
40
+ // THE SENTENCE THAT MAKES THE LINE ABOVE HONEST (D-7 / N-3). The session
41
+ // total folds in subagents, the fast tier and compaction's own summarization
42
+ // calls - three other models' tokens in a figure sitting next to the lead's
43
+ // occupancy. Splitting it into four counters would need four units of
44
+ // provenance argument for one line of output; SAYING what it contains costs
45
+ // one line and answers the same question.
46
+ lines.push(' includes subagent, fast-tier and compaction spend,');
47
+ lines.push(' not just this conversation');
48
+ return lines.join('\n');
49
+ }
50
+ /**
51
+ * `43% 86.2k of 200k [measured 85.1k + 1.1k estimated]`
52
+ *
53
+ * THE BREAKDOWN IS THE POINT. "43%" alone cannot be checked by a user who thinks
54
+ * it is wrong; naming how much of it is a provider measurement and how much is
55
+ * our own guess about messages appended since tells them whether to distrust the
56
+ * number or the model.
57
+ */
58
+ function occupancyLine(usage) {
59
+ const approximate = usage.source === 'estimate' || usage.deltaTokens > 0;
60
+ const pct = `${approximate || !usage.windowKnown ? '~' : ''}${usage.pct}%`;
61
+ const measured = Math.max(0, usage.occupied - usage.deltaTokens);
62
+ const breakdown = usage.source === 'estimate'
63
+ ? `[estimated ${formatTokens(usage.occupied)}]`
64
+ : usage.deltaTokens > 0
65
+ ? `[measured ${formatTokens(measured)} + ${formatTokens(usage.deltaTokens)} estimated]`
66
+ : `[measured ${formatTokens(measured)}]`;
67
+ return `${pct} ${formatTokens(usage.occupied)} of ${formatTokens(usage.window)} ${breakdown}`;
68
+ }
69
+ /**
70
+ * Where the denominator came from. THREE SOURCES, ALL NAMED.
71
+ *
72
+ * THE PLACEHOLDER BRANCH IS THE ONE THAT MATTERS. `buildRuntimeModel` returns a
73
+ * flat 128000 for any model the static table has never seen - a custom
74
+ * `baseUrl`, a self-hosted id, a model released last week - and before this
75
+ * command there was nothing that would admit the number was invented. A user
76
+ * reading `128000` next to their 1M-token model concludes the gauge is broken;
77
+ * they are right, and this line tells them which key fixes it.
78
+ */
79
+ function windowLine(usage) {
80
+ if (usage.windowOverridden) {
81
+ return `${usage.window} from contextWindow in config.json (overrides the model table)`;
82
+ }
83
+ if (!usage.windowKnown) {
84
+ return (`${usage.window} PLACEHOLDER - this model is not in the table; ` +
85
+ 'set contextWindow to correct it');
86
+ }
87
+ return `${usage.window} from the model table`;
88
+ }
89
+ /**
90
+ * How stale the measured half is.
91
+ *
92
+ * `deltaTokens` IS A TOKEN COUNT, NOT A MESSAGE COUNT, and this line says so in
93
+ * tokens for that reason: the meter estimates the appended SLICE, and turning
94
+ * that back into "4 messages" would be a second derivation of something nobody
95
+ * recorded.
96
+ */
97
+ function freshnessLine(usage) {
98
+ if (usage.source === 'estimate') {
99
+ return 'no provider-reported usage yet - the whole figure is an estimate';
100
+ }
101
+ if (usage.deltaTokens <= 0) {
102
+ return 'nothing appended since the last provider-reported usage';
103
+ }
104
+ return `${formatTokens(usage.deltaTokens)} estimated tokens appended since the last provider-reported usage`;
105
+ }
106
+ /**
107
+ * The compaction state, IN FIVE FORMS (RV-8).
108
+ *
109
+ * A SINGLE FORM WOULD BE A LIE FOUR TIMES OUT OF FIVE. The obvious version of
110
+ * this line - "on - triggers at 90% (amber at 75%)" - is printed unconditionally
111
+ * only if you forget that compaction has four other observable states, and the
112
+ * first of them is `aragon --no-compaction`, where that sentence promises a
113
+ * rescue that cannot come. A report that exists to be trusted does not get to
114
+ * have one branch.
115
+ *
116
+ * THE PREDICATES ARE THE ONES `formatCompactionStatus` ALREADY USES
117
+ * (`compaction/command.ts`), not a second set: two ladders over the same three
118
+ * booleans will eventually disagree, and the disagreement would be between two
119
+ * commands the user runs precisely because they already distrust something.
120
+ */
121
+ function compactionLine(ctx) {
122
+ const controller = ctx.controller;
123
+ if (!controller.isCompactionRegistered()) {
124
+ return 'off - not registered for this session (started with --no-compaction)';
125
+ }
126
+ const snapshot = controller.getCompactionSnapshot();
127
+ if (snapshot.selfDisabled) {
128
+ const reason = snapshot.selfDisabledReason ?? 'no progress';
129
+ return `self-disabled (${reason}) - no compaction will run until /compact on`;
130
+ }
131
+ if (!controller.isCompactionEnabled()) {
132
+ return 'off for this session - /compact on re-enables it';
133
+ }
134
+ const config = controller.getCompactionConfig();
135
+ const thresholds = `triggers at ${Math.round(config.threshold * 100)}% ` +
136
+ `(amber at ${Math.round(config.warnThreshold * 100)}%)`;
137
+ if (!snapshot.live) {
138
+ return `on, but no summarizer model resolves - ${thresholds}`;
139
+ }
140
+ const count = snapshot.compactions;
141
+ return (`on - ${thresholds}, ${count} this session, ` +
142
+ `${formatTokens(snapshot.tokensReclaimed)} reclaimed`);
143
+ }
144
+ /**
145
+ * `1.24M in (incl. 940k cache read, 12k cache write) / 48.2k out, $3.21`
146
+ *
147
+ * THE CACHE PARENTHETICAL IS OMITTED WHEN BOTH TERMS ARE ZERO, which is every
148
+ * direct connection - this CLI never sends `cache_control`, so the terms are
149
+ * non-zero only behind a gateway that caches on the user's behalf. Those are
150
+ * exactly the users for whom the pre-feature `^` under-reported (P1-3), and the
151
+ * clause is how they can see that it no longer does.
152
+ */
153
+ function spendLine(ctx) {
154
+ const total = ctx.state.usageTotal;
155
+ const cached = total.cacheReadTokens + total.cacheWriteTokens;
156
+ const parenthetical = cached > 0
157
+ ? ` (incl. ${formatTokens(total.cacheReadTokens)} cache read, ` +
158
+ `${formatTokens(total.cacheWriteTokens)} cache write)`
159
+ : '';
160
+ return (`${formatTokens(promptTokensOf(total))} in${parenthetical} / ` +
161
+ `${formatTokens(total.outputTokens)} out, ${formatCost(total.costUsd)}`);
162
+ }
163
+ /** `/context` takes no arguments; anything after it is ignored. */
164
+ export function runContextCommand(ctx) {
165
+ ctx.notify('info', formatContextReport(ctx));
166
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * `ContextMeter` - the ONE owner of "how full is the context right now"
3
+ * (context-usage-gauge-accuracy §3.2 / W1).
4
+ *
5
+ * ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope
6
+ * (`glyphs.test.ts::inScope`). If this module is ever moved to a new tree, the
7
+ * scanner's hardcoded directory list must be widened IN THE SAME COMMIT - a
8
+ * scanner that silently stops scanning is worse than no scanner, and this
9
+ * package has paid for that edit eleven times (I-7).
10
+ *
11
+ * IT LIVES HERE BUT IT IS NOT PART OF COMPACTION, and that is deliberate. The
12
+ * whole point of this class is that occupancy is measurable when compaction is
13
+ * OFF: `AgentController` builds a `CompactionWiring` only when
14
+ * `compaction.enabled` is true, so a measurement owned by the compactor is
15
+ * unreachable for exactly the sessions that most need a working gauge. It sits
16
+ * in this directory because `pressure.ts` - the arithmetic it drives - already
17
+ * does, and because moving it would cost a scanner edit for no gain (R-10).
18
+ *
19
+ * THE STATE MACHINE IN ONE PARAGRAPH. Every mutation of the history marks the
20
+ * meter DIRTY; every read that finds it dirty re-measures SYNCHRONOUSLY. That
21
+ * ordering is what removes the subscription-order dependency in the APPEND
22
+ * direction: it does not matter whether this class or the compaction wiring
23
+ * hears `tool_execution_end` first, because whoever reads gets a number that
24
+ * includes the appended message either way (I-2). It does NOT remove the
25
+ * dependency in the REPLACE direction - a splice does not announce itself to a
26
+ * listener that has not run yet - which is why the splice marks this dirty from
27
+ * `CompactionWiring.settlePending`'s own synchronous code (I-9), and why
28
+ * `current()` carries a structural belt for a missed invalidation site.
29
+ */
30
+ import type { AgentEvent, Message, ModelInfo, TokenUsage } from '@aragon-agent/core';
31
+ import type { ContextUsageSnapshot, Pressure } from './types.js';
32
+ /**
33
+ * How long a scheduled publish waits.
34
+ *
35
+ * IT MUST BE A DELAY, NOT ZERO, AND NOT A SYNCHRONOUS MEASUREMENT (I-3). The
36
+ * loop emits `tool_execution_end` BEFORE it pushes the `tool_result` message, so
37
+ * anything that measures inside that event measures a history missing the single
38
+ * largest message of the turn - silently, and every turn. A `setTimeout` is
39
+ * necessarily later than that synchronous push.
40
+ *
41
+ * 400 ms also caps the publish rate at 2.5 Hz, which together with the reducer's
42
+ * identity short-circuit keeps a permanently visible row from becoming a
43
+ * re-render source (R-3).
44
+ */
45
+ export declare const CONTEXT_METER_TICK_MS = 400;
46
+ export interface ContextMeterDeps {
47
+ /**
48
+ * The engine's live history and system prompt.
49
+ *
50
+ * LAZY, and they must be: the controller constructs this before the `Agent`
51
+ * exists, so a closure that touches `this.agent` is only valid from inside a
52
+ * call made later.
53
+ */
54
+ getMessages(): readonly Message[];
55
+ getSystemPrompt(): string;
56
+ /** `ModelInfo` for the MAIN model. Re-read on every measurement - `/model` exists. */
57
+ getModelInfo(): ModelInfo;
58
+ /** Whether the STATIC model table knows this model, i.e. the window is real. */
59
+ isWindowKnown(): boolean;
60
+ /**
61
+ * The user's `contextWindow` override, or `null` for AUTO. Re-read on every
62
+ * measurement, so a live settings edit moves the denominator without a relaunch.
63
+ */
64
+ getWindowOverride(): number | null;
65
+ }
66
+ export type ContextUsageListener = (usage: ContextUsageSnapshot) => void;
67
+ /**
68
+ * The projection `ViewState` and `/context` read (§4.1).
69
+ *
70
+ * THE ONLY CONVERTER between `Pressure` and `ContextUsageSnapshot`. Two of them
71
+ * would eventually round the percentage differently, and "the status bar says 43
72
+ * and `/context` says 44" is the exact class of disagreement this whole feature
73
+ * exists to remove.
74
+ */
75
+ export declare function toContextUsage(p: Pressure): ContextUsageSnapshot;
76
+ /** The reading a meter with nothing measured yet reports. */
77
+ export declare function emptyContextUsage(): ContextUsageSnapshot;
78
+ export declare class ContextMeter {
79
+ private readonly deps;
80
+ private readonly listeners;
81
+ private unsubscribe;
82
+ private timer;
83
+ private disposed;
84
+ /**
85
+ * The authoritative usage of the last completed turn.
86
+ *
87
+ * Its presence is what selects `computePressure`'s MEASURED branch, so
88
+ * dropping it is how every reset degrades to an estimate rather than to a
89
+ * number describing a history that no longer exists.
90
+ */
91
+ private lastUsage;
92
+ /**
93
+ * The systematic difference between what `estimatePromptTokens` counts and
94
+ * what the provider bills (D-23) - chiefly the tool schemas.
95
+ *
96
+ * SURVIVES A SPLICE AND NOT A SESSION CHANGE (I-8). See `onHistorySpliced`.
97
+ */
98
+ private estimateOffset;
99
+ /** `messages.length` at the `turn_end` that produced `lastUsage`. */
100
+ private measuredPrefixLength;
101
+ /** The last pressure PUBLISHED. Never re-derived on read - see `lastPublished`. */
102
+ private last;
103
+ /** Whether the history has changed since the last measurement. */
104
+ private dirty;
105
+ constructor(deps: ContextMeterDeps);
106
+ /**
107
+ * Occupancy right now. FOR THE SCREEN.
108
+ *
109
+ * Re-measures synchronously when dirty (I-2), and publishes when it does - the
110
+ * gauge has to fall in the SAME FRAME as the compaction that caused it (AC-1),
111
+ * and the caller that triggers this re-measurement is `CompactionWiring`'s own
112
+ * synchronous `snapshot()` inside `settlePending`. Leaving the publish to the
113
+ * scheduled tick instead would be worse than slow: the tick fires only when
114
+ * `dirty`, and this call is what clears it.
115
+ *
116
+ * NEVER THROWS. A meter that can throw is a status bar that can take the
117
+ * process down.
118
+ */
119
+ current(): Pressure;
120
+ /**
121
+ * The last pressure this meter PUBLISHED. FOR THE BOOKS. Never re-measures.
122
+ *
123
+ * `Compactor.lastMeasured()` forwards here and not to `current()`, and the
124
+ * distinction is load-bearing (I-10): `compactor.ts`'s `tokensBefore` is the
125
+ * sole upstream of `tokensReclaimed`, the card's "reclaimed N tokens" line and
126
+ * `/compact status`'s session total. It wants the occupancy BEFORE the splice.
127
+ * Wired to `current()` it would re-measure the already-spliced history, report
128
+ * `reclaimed 0`, and render that number without a single log line about it.
129
+ */
130
+ lastPublished(): Pressure | null;
131
+ /** The published projection, or a zeroed one before the first measurement. */
132
+ currentUsage(): ContextUsageSnapshot;
133
+ /**
134
+ * Measure with the caller's own authoritative usage. FOR A DECISION POINT.
135
+ *
136
+ * `Compactor.shouldCompact` has a `CompactionProbe.lastUsage` the meter may
137
+ * not have seen yet, so it supplies it rather than trusting the field here.
138
+ */
139
+ measureWith(lastUsage: TokenUsage | undefined): Pressure;
140
+ /**
141
+ * `turn_end` - the one moment in the whole flow that is FULLY measured.
142
+ *
143
+ * The loop emits it BEFORE pushing the assistant message, so the history right
144
+ * now is exactly the history the reported `usage` was billed for: the offset
145
+ * and the prefix length describe the same array by construction, and
146
+ * `deltaTokens` is 0. Published immediately for that reason - there is no
147
+ * better number to wait for.
148
+ */
149
+ onTurnEnd(usage: TokenUsage): void;
150
+ /**
151
+ * A compaction spliced the history (I-8, the SHALLOW reset).
152
+ *
153
+ * `estimateOffset` IS KEPT, and that is the whole reason this method is not
154
+ * `onHistoryReplaced`. The offset describes "what this toolset costs on this
155
+ * model above what the estimator counts"; a splice changes neither the toolset
156
+ * nor the model, so it is still valid - and it is the only calibration the
157
+ * estimate branch has until the next `turn_end`. Dropping it makes the gauge
158
+ * fall several thousand tokens too far and then visibly climb back on the next
159
+ * turn, for no reason the user can see (I-5).
160
+ *
161
+ * IDEMPOTENT. Both `settlePending` (the authority, I-9) and this meter's own
162
+ * `compaction_end` handler reach it, and the second one must cost nothing.
163
+ */
164
+ onHistorySpliced(): void;
165
+ /**
166
+ * The history came from somewhere else entirely (I-8, the DEEP reset):
167
+ * `/clear`, `/reset`, `/resume`.
168
+ *
169
+ * `estimateOffset` GOES TOO, unlike the shallow reset above. `/resume` can
170
+ * change the model in the same breath (`builtins.ts`'s restore path does), and
171
+ * an offset carried across that is a calibration constant applied to something
172
+ * it never calibrated. The first reading afterwards is therefore a bare
173
+ * estimate - biased LOW, and corrected by the very next `turn_end`. Biased low
174
+ * and self-correcting beats biased high and permanent.
175
+ */
176
+ onHistoryReplaced(): void;
177
+ /**
178
+ * Only the DENOMINATOR moved (`/model`, a `contextWindow` edit).
179
+ *
180
+ * DELIBERATELY NOT A RESET. The measured base is still a true statement about
181
+ * the history, so throwing it away would trade a correct numerator for a whole
182
+ * history estimate and produce a visible jump on a common operation. The
183
+ * numerator's calibration is one turn stale until the next `turn_end`
184
+ * refreshes it, which is the trade this method names (RV-13).
185
+ */
186
+ onWindowChanged(): void;
187
+ /**
188
+ * Something was appended. Mark dirty, arrange a throttled publish, MEASURE
189
+ * NOTHING (I-3).
190
+ */
191
+ scheduleTick(): void;
192
+ /** Publish to this meter's own subscribers. Returns an unsubscribe function. */
193
+ subscribe(listener: ContextUsageListener): () => void;
194
+ /**
195
+ * Subscribe to the lead agent's stream. IDEMPOTENT.
196
+ *
197
+ * THIS TABLE IS NOT THE SOURCE OF COMPACTION CORRECTNESS (I-9). The splice is
198
+ * marked dirty by `CompactionWiring.settlePending` inside its own synchronous
199
+ * code, BEFORE it emits either event, so a reader is correct regardless of
200
+ * which listener the emitter reaches first. The `compaction_end` row below is
201
+ * a second, idempotent path that serves the manual `/compact` route and any
202
+ * future splicer.
203
+ */
204
+ attach(subscribe: (listener: (event: AgentEvent) => void) => () => void): void;
205
+ dispose(): void;
206
+ private onAgentEvent;
207
+ /**
208
+ * The structural belt for a missed invalidation site (I-9).
209
+ *
210
+ * A recorded prefix that is LONGER than the history it indexes describes an
211
+ * array that no longer exists. Rather than merely zeroing the delta - which is
212
+ * what `estimateAppendedTokens` does, keeping the wrong measured base - this
213
+ * drops the base too and forces the estimate branch.
214
+ *
215
+ * THE COMPARISON IS STRICT, AND THAT BOUNDARY IS LOAD-BEARING (IF-1). The
216
+ * design document wrote `length <= measuredPrefixLength`, which is off by one:
217
+ * `turn_end` is emitted BEFORE the assistant message is pushed, so the instant
218
+ * `onTurnEnd` records the prefix the history is EXACTLY that long. An
219
+ * inclusive test therefore discards a measurement one statement after taking
220
+ * it - but only when this meter's listener runs before the compaction wiring's,
221
+ * because the wiring's `snapshot()` is what reads in that window. That is a
222
+ * silent, subscription-order-dependent fallback to the estimate branch on
223
+ * every single turn, which is the exact class of bug this feature exists to
224
+ * remove. A measurement taken at length N describes every history of length
225
+ * >= N; only a SHORTER one is impossible.
226
+ *
227
+ * Returns whether anything was discarded.
228
+ */
229
+ private discardStalePrefix;
230
+ /**
231
+ * Measure, cache, clear the dirty flag, publish. The ONE write path.
232
+ *
233
+ * Every public mutator funnels through here so "what was published" and "what
234
+ * is cached" cannot drift, which is what makes `lastPublished()` a meaningful
235
+ * answer rather than a stale one.
236
+ */
237
+ private measureAndPublish;
238
+ private compute;
239
+ /**
240
+ * Where the denominator comes from, and whether it can be trusted.
241
+ *
242
+ * AN OVERRIDE MAKES THE WINDOW KNOWN (I-6): the user asserted it, so the `~`
243
+ * that means "this denominator is a guess" has no business staying on the
244
+ * percentage. `windowOverridden` travels separately so `/context` can still
245
+ * name the source - a hand-typed window that is wrong is exactly the case that
246
+ * most needs to be visible.
247
+ */
248
+ private resolveWindow;
249
+ private readSystemPrompt;
250
+ private publish;
251
+ }