@aragon-agent/cli 0.6.4 → 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.
- package/CHANGELOG.md +37 -0
- package/README.md +108 -11
- package/dist/agent/controller.d.ts +39 -2
- package/dist/agent/controller.js +125 -0
- package/dist/agent/reducer.d.ts +33 -19
- package/dist/agent/reducer.js +62 -71
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +48 -1
- package/dist/commands/builtins.js +9 -0
- package/dist/compaction/compactor.d.ts +60 -24
- package/dist/compaction/compactor.js +89 -61
- package/dist/compaction/context-command.d.ts +33 -0
- package/dist/compaction/context-command.js +166 -0
- package/dist/compaction/meter.d.ts +251 -0
- package/dist/compaction/meter.js +428 -0
- package/dist/compaction/pressure.d.ts +6 -0
- package/dist/compaction/pressure.js +4 -0
- package/dist/compaction/types.d.ts +41 -0
- package/dist/compaction/wiring.d.ts +47 -0
- package/dist/compaction/wiring.js +88 -27
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.js +15 -1
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +45 -1
- package/dist/config/schema.d.ts +44 -0
- package/dist/config/schema.js +36 -0
- package/dist/config/store.js +27 -1
- package/dist/exec/events.d.ts +138 -1
- package/dist/exec/events.js +42 -0
- package/dist/exec/index.js +43 -6
- package/dist/exec/progress-reporter.d.ts +23 -0
- package/dist/exec/progress-reporter.js +88 -0
- package/dist/exec/runner.d.ts +34 -0
- package/dist/exec/runner.js +137 -25
- package/dist/fast/reviewer.d.ts +19 -1
- package/dist/fast/reviewer.js +22 -0
- package/dist/fast/types.d.ts +15 -0
- package/dist/fast/wiring.d.ts +10 -1
- package/dist/fast/wiring.js +18 -1
- package/dist/todo/limits.d.ts +4 -17
- package/dist/todo/limits.js +4 -17
- package/dist/todo/panel-rows.d.ts +8 -6
- package/dist/todo/panel-rows.js +19 -6
- package/dist/ui/App.js +75 -63
- package/dist/ui/AutocompletePopup.d.ts +3 -1
- package/dist/ui/AutocompletePopup.js +9 -11
- package/dist/ui/BottomStatusRow.d.ts +1 -1
- package/dist/ui/Composer.d.ts +3 -1
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +19 -5
- package/dist/ui/StatusBar.d.ts +21 -15
- package/dist/ui/StatusBar.js +48 -9
- package/dist/ui/TeamPanel.d.ts +3 -1
- package/dist/ui/TeamPanel.js +13 -9
- package/dist/ui/TodoPanel.d.ts +5 -5
- package/dist/ui/TodoPanel.js +56 -24
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/layout/ScrollViewport.d.ts +1 -1
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- package/dist/ui/layout/team-panel.d.ts +18 -0
- package/dist/ui/layout/team-panel.js +22 -0
- package/dist/ui/layout/todo-layout.d.ts +24 -0
- package/dist/ui/layout/todo-layout.js +27 -0
- package/dist/ui/transcript-text.js +6 -2
- package/package.json +2 -2
|
@@ -17,7 +17,8 @@ import { attemptTailRelief, reliefSummary, tailBudgetTokens } from './tail-budge
|
|
|
17
17
|
import { buildDigest, isPriorSummaryBlock } from './digest.js';
|
|
18
18
|
import { buildAnchor, buildCompactedBlock, countProtectedPrefix, readGeneration, truncationBody, } from './summary-prompt.js';
|
|
19
19
|
import { accumulateUsage, errorText, runSummarizeCall } from './summarize-call.js';
|
|
20
|
-
import {
|
|
20
|
+
import { computePressure, shouldCompactAt } from './pressure.js';
|
|
21
|
+
import { ContextMeter } from './meter.js';
|
|
21
22
|
export class Compactor {
|
|
22
23
|
deps;
|
|
23
24
|
log = getLogger().child('compaction');
|
|
@@ -44,18 +45,15 @@ export class Compactor {
|
|
|
44
45
|
selfDisabledReason;
|
|
45
46
|
pendingManual = null;
|
|
46
47
|
generation = 0;
|
|
47
|
-
/** Calibration for every ESTIMATED figure (D-23). Recomputed on every turn_end. */
|
|
48
|
-
estimateOffset;
|
|
49
|
-
/** The last pressure computed, so the status bar and `/compact status` agree. */
|
|
50
|
-
lastPressure = null;
|
|
51
48
|
/**
|
|
52
|
-
* The
|
|
49
|
+
* The fallback meter, built on first use when no host injected one (R-1).
|
|
53
50
|
*
|
|
54
|
-
* `
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* `estimateOffset`, `measuredPrefixLength` and `lastPressure` used to be
|
|
52
|
+
* FIELDS HERE. They moved to `ContextMeter` wholesale
|
|
53
|
+
* (context-usage-gauge-accuracy W1), because keeping them here is what put the
|
|
54
|
+
* only measurement in the process behind `compaction.enabled`.
|
|
57
55
|
*/
|
|
58
|
-
|
|
56
|
+
ownMeter = null;
|
|
59
57
|
/**
|
|
60
58
|
* The messages the last splice dropped, held ONLY until the wiring collects
|
|
61
59
|
* them for the archive (W4 / RV-3).
|
|
@@ -87,28 +85,34 @@ export class Compactor {
|
|
|
87
85
|
this.lastCompactionTurn = -Infinity;
|
|
88
86
|
}
|
|
89
87
|
/**
|
|
90
|
-
* `turn_end`
|
|
88
|
+
* `turn_end` - recompute the calibration offset (D-23).
|
|
91
89
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
90
|
+
* A THIN FORWARDER since context-usage-gauge-accuracy W1. The meter subscribes
|
|
91
|
+
* to `turn_end` itself, so BOTH PATHS MUST BE IDEMPOTENT: this one recomputes
|
|
92
|
+
* exactly what the other one did from the same inputs, which it is.
|
|
93
|
+
*
|
|
94
|
+
* `messages` / `systemPrompt` stay in the signature and are ignored: the meter
|
|
95
|
+
* reads them from its own lazy deps, and every existing caller and test keeps
|
|
96
|
+
* compiling.
|
|
94
97
|
*/
|
|
95
|
-
onTurnEnd(usage,
|
|
96
|
-
this.
|
|
97
|
-
// THE SAME ARRAY `computeEstimateOffset` JUST MEASURED, so the two numbers
|
|
98
|
-
// describe the same request by construction (hardening §3.2.3).
|
|
99
|
-
this.measuredPrefixLength = messages.length;
|
|
98
|
+
onTurnEnd(usage, _messages, _systemPrompt) {
|
|
99
|
+
this.meter().onTurnEnd(usage);
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
*
|
|
101
|
+
/*
|
|
102
|
+
* `invalidateMeasurement()` IS GONE, DELIBERATELY (I-9 / RV-2 round two).
|
|
103
|
+
*
|
|
104
|
+
* It had exactly two production callers and their reset depths are now
|
|
105
|
+
* OPPOSITE: `CompactionWiring.onHistoryReplaced` (a `/clear`, `/reset` or
|
|
106
|
+
* `/resume`) must drop `estimateOffset`, while `settlePending`'s applied
|
|
107
|
+
* branch (a splice) must KEEP it - the toolset and the model are unchanged, so
|
|
108
|
+
* the calibration still holds and dropping it makes the gauge fall too far and
|
|
109
|
+
* climb back (I-5 / I-8). The two used to share a method only because the one
|
|
110
|
+
* field it cleared happened to be their intersection; that intersection no
|
|
111
|
+
* longer exists. Both call sites now name the depth they mean, on the meter.
|
|
103
112
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* array. `estimateAppendedTokens` bounds-checks as well, so a MISSED call here
|
|
107
|
-
* costs accuracy - the number degrades to round 1's - and never correctness.
|
|
113
|
+
* Reinstating a forwarder here would hide "which caller gets which depth"
|
|
114
|
+
* behind a name that mentions neither, and BOTH wrong choices are silent.
|
|
108
115
|
*/
|
|
109
|
-
invalidateMeasurement() {
|
|
110
|
-
this.measuredPrefixLength = undefined;
|
|
111
|
-
}
|
|
112
116
|
/**
|
|
113
117
|
* The dropped messages, released as they are returned (W4 / RV-3).
|
|
114
118
|
*
|
|
@@ -172,17 +176,13 @@ export class Compactor {
|
|
|
172
176
|
const config = this.deps.getConfig().compaction;
|
|
173
177
|
if (!config.enabled)
|
|
174
178
|
return false;
|
|
175
|
-
// THE
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
// The
|
|
180
|
-
//
|
|
181
|
-
const pressure = this.
|
|
182
|
-
...(probe.lastUsage ? { lastUsage: probe.lastUsage } : {}),
|
|
183
|
-
messages: this.deps.getMessages(),
|
|
184
|
-
systemPrompt: this.deps.getSystemPrompt(),
|
|
185
|
-
});
|
|
179
|
+
// THE METER READS THE LIVE HISTORY ITSELF (W1), so the estimate branch can no
|
|
180
|
+
// longer be handed an empty array by a caller that forgot - which used to be
|
|
181
|
+
// a 0 % reading on precisely the session that most needs a trigger, the
|
|
182
|
+
// resumed 180 k-token one where `lastUsage` is `undefined` by definition.
|
|
183
|
+
// The probe's own usage is still passed, because it is the authority for
|
|
184
|
+
// THIS checkpoint.
|
|
185
|
+
const pressure = this.meter().measureWith(probe.lastUsage);
|
|
186
186
|
this.deps.onPressure?.(pressure);
|
|
187
187
|
const manual = this.pendingManual !== null;
|
|
188
188
|
// Guard 2 — the per-run cap. BOTH TRIGGERS, HARD. It is the bound that makes
|
|
@@ -568,12 +568,17 @@ export class Compactor {
|
|
|
568
568
|
checkProgress(ctx, uiTrigger, after) {
|
|
569
569
|
const config = this.deps.getConfig().compaction;
|
|
570
570
|
const info = this.modelInfo();
|
|
571
|
+
// THE OFFSET NOW LIVES ON THE METER (W1), and it is read from the last
|
|
572
|
+
// PUBLISHED pressure rather than re-derived: `Pressure.estimateOffset` is
|
|
573
|
+
// carried on every measurement, so this is the same number the field used to
|
|
574
|
+
// hold, without the compactor owning a second copy of it.
|
|
575
|
+
const estimateOffset = this.meter().lastPublished()?.estimateOffset;
|
|
571
576
|
const projected = computePressure({
|
|
572
577
|
messages: after,
|
|
573
578
|
systemPrompt: ctx.systemPrompt,
|
|
574
579
|
contextWindow: info.contextWindow,
|
|
575
580
|
windowKnown: true,
|
|
576
|
-
...(
|
|
581
|
+
...(estimateOffset !== undefined ? { estimateOffset } : {}),
|
|
577
582
|
});
|
|
578
583
|
const target = config.threshold - COMPACTION_LIMITS.minReclaimRatio;
|
|
579
584
|
const madeProgress = projected.ratio < target;
|
|
@@ -619,7 +624,9 @@ export class Compactor {
|
|
|
619
624
|
finish(args, outcome, applied, keepRecentTurnsUsed, announced) {
|
|
620
625
|
const before = args.ctx.messages.length;
|
|
621
626
|
const after = outcome.action === 'replace' ? outcome.messages.length : before;
|
|
622
|
-
|
|
627
|
+
// THE OCCUPANCY BEFORE THE SPLICE (I-10). `lastMeasured()` is the pure cache
|
|
628
|
+
// read on purpose; anything that re-measures here reports 0 reclaimed.
|
|
629
|
+
const tokensBefore = this.lastMeasured()?.occupied ?? 0;
|
|
623
630
|
// `'relieved'` IS A CLI-ONLY WORD (RV-5 / DH-20). The port reports
|
|
624
631
|
// `'truncated'` for a relief-only compaction, which is accurate for the
|
|
625
632
|
// engine's narrower question; the card's `summarized N messages with
|
|
@@ -690,32 +697,30 @@ export class Compactor {
|
|
|
690
697
|
// Reporting helpers
|
|
691
698
|
// =========================================================================
|
|
692
699
|
/**
|
|
693
|
-
* The pressure the trigger and the gauge both read.
|
|
700
|
+
* The pressure the trigger and the gauge both read - MEASURED NOW.
|
|
694
701
|
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
702
|
+
* A FORWARDER since W1: the decision point supplies its own authoritative
|
|
703
|
+
* `lastUsage` (the probe's), and the meter owns everything else. `messages` /
|
|
704
|
+
* `systemPrompt` stay in the signature and are ignored, because the meter
|
|
705
|
+
* reads the LIVE history through its own deps - which is strictly better than
|
|
706
|
+
* a caller that could pass `[]` and get a 0 % reading (the old default here).
|
|
697
707
|
*/
|
|
698
708
|
measure(probe) {
|
|
699
|
-
|
|
700
|
-
const pressure = computePressure({
|
|
701
|
-
...(probe.lastUsage ? { lastUsage: probe.lastUsage } : {}),
|
|
702
|
-
messages: probe.messages ?? [],
|
|
703
|
-
systemPrompt: probe.systemPrompt ?? '',
|
|
704
|
-
contextWindow: info.contextWindow,
|
|
705
|
-
windowKnown: this.deps.isPricedModel({
|
|
706
|
-
providerId: this.mainRef().providerId,
|
|
707
|
-
modelId: this.mainRef().modelId,
|
|
708
|
-
}),
|
|
709
|
-
...(this.estimateOffset !== undefined ? { estimateOffset: this.estimateOffset } : {}),
|
|
710
|
-
...(this.measuredPrefixLength !== undefined
|
|
711
|
-
? { measuredPrefixLength: this.measuredPrefixLength }
|
|
712
|
-
: {}),
|
|
713
|
-
});
|
|
714
|
-
this.lastPressure = pressure;
|
|
715
|
-
return pressure;
|
|
709
|
+
return this.meter().measureWith(probe.lastUsage);
|
|
716
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* The last pressure PUBLISHED. NEVER re-measured - `lastPublished`, not
|
|
713
|
+
* `current` (I-10 / RV-3).
|
|
714
|
+
*
|
|
715
|
+
* `finish()` below reads this for `tokensBefore`, which is the sole upstream of
|
|
716
|
+
* `tokensReclaimed`, the card's "reclaimed N tokens" and `/compact status`'s
|
|
717
|
+
* session total. All three want the occupancy BEFORE the splice. Point this at
|
|
718
|
+
* `meter.current()` and the moment the meter is dirty - which after a splice it
|
|
719
|
+
* always is - `tokensBefore` becomes the POST-compaction figure, `reclaimed`
|
|
720
|
+
* collapses to 0, and the card renders that with nothing logging a fault.
|
|
721
|
+
*/
|
|
717
722
|
lastMeasured() {
|
|
718
|
-
return this.
|
|
723
|
+
return this.meter().lastPublished();
|
|
719
724
|
}
|
|
720
725
|
/** Written by the wiring once the engine has reported its verdict. */
|
|
721
726
|
recordApplied(tokensBefore, tokensAfter) {
|
|
@@ -768,6 +773,29 @@ export class Compactor {
|
|
|
768
773
|
return null;
|
|
769
774
|
return { ref, fromFastTier: false };
|
|
770
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* The meter this compactor reads, injected or private (R-1).
|
|
778
|
+
*
|
|
779
|
+
* MEMOIZED, because a fresh meter on every call would have no `estimateOffset`
|
|
780
|
+
* and no cached pressure - `lastMeasured()` would answer `null` forever and
|
|
781
|
+
* `tokensReclaimed` would sit at 0. The fallback exists so the ten existing
|
|
782
|
+
* `compaction-*.test.ts` construction sites compile unchanged; production
|
|
783
|
+
* injects, and `context-gauge-wiring.test.ts` asserts the single instance.
|
|
784
|
+
*/
|
|
785
|
+
meter() {
|
|
786
|
+
if (this.deps.meter)
|
|
787
|
+
return this.deps.meter;
|
|
788
|
+
if (!this.ownMeter) {
|
|
789
|
+
this.ownMeter = new ContextMeter({
|
|
790
|
+
getMessages: () => this.deps.getMessages(),
|
|
791
|
+
getSystemPrompt: () => this.deps.getSystemPrompt(),
|
|
792
|
+
getModelInfo: () => this.modelInfo(),
|
|
793
|
+
isWindowKnown: () => this.deps.isPricedModel(this.mainRef()),
|
|
794
|
+
getWindowOverride: () => this.deps.getConfig().contextWindow,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return this.ownMeter;
|
|
798
|
+
}
|
|
771
799
|
mainRef() {
|
|
772
800
|
const config = this.deps.getConfig();
|
|
773
801
|
return {
|
|
@@ -0,0 +1,33 @@
|
|
|
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 type { CommandContext } from '../commands/registry.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 declare function formatContextReport(ctx: CommandContext): string;
|
|
32
|
+
/** `/context` takes no arguments; anything after it is ignored. */
|
|
33
|
+
export declare function runContextCommand(ctx: CommandContext): void;
|
|
@@ -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
|
+
}
|