@aragon-agent/cli 0.6.1 → 0.6.3
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 +216 -0
- package/README.md +241 -18
- package/dist/agent/agent-mode.d.ts +39 -0
- package/dist/agent/agent-mode.js +36 -0
- package/dist/agent/controller.d.ts +74 -1
- package/dist/agent/controller.js +200 -1
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +120 -0
- package/dist/agent/reducer.js +201 -2
- package/dist/cli.js +297 -6
- package/dist/commands/builtins.js +73 -23
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +113 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +342 -0
- package/dist/compaction/compactor.d.ts +268 -0
- package/dist/compaction/compactor.js +689 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +179 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +255 -0
- package/dist/compaction/wiring.js +556 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +92 -2
- package/dist/config/load.d.ts +31 -0
- package/dist/config/load.js +63 -1
- package/dist/config/schema.d.ts +159 -0
- package/dist/config/schema.js +137 -0
- package/dist/config/store.js +17 -1
- package/dist/config/ui-state.d.ts +44 -6
- package/dist/config/ui-state.js +21 -12
- package/dist/diagnostics/doctor.js +41 -1
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/index.js +8 -4
- package/dist/exec/runner.js +44 -2
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/stdin-mouse-filter.d.ts +8 -3
- package/dist/logging/install.d.ts +51 -1
- package/dist/logging/install.js +28 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/cli-commands.js +23 -1
- package/dist/session/persist.js +19 -0
- package/dist/session/store.d.ts +18 -0
- package/dist/session/store.js +59 -2
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +48 -1
- package/dist/ui/App.js +317 -17
- package/dist/ui/Composer.d.ts +26 -1
- package/dist/ui/Composer.js +6 -6
- package/dist/ui/PromptInput.d.ts +34 -1
- package/dist/ui/PromptInput.js +54 -3
- package/dist/ui/StatusBar.d.ts +40 -0
- package/dist/ui/StatusBar.js +13 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +32 -2
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +10 -3
- package/dist/ui/layout/budget.js +10 -3
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +34 -0
- package/dist/ui/overlays/HelpOverlay.js +18 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +60 -1
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/screen.d.ts +22 -0
- package/dist/ui/screen.js +70 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +23 -0
- package/dist/ui/use-startup-notices.d.ts +62 -5
- package/dist/ui/use-startup-notices.js +81 -17
- package/dist/ui/use-wheel-routing.js +8 -0
- package/dist/ui/win-vt-force.d.ts +136 -0
- package/dist/ui/win-vt-force.js +237 -0
- package/package.json +4 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/compact` slash command and its status report
|
|
3
|
+
* (context-auto-compaction §4.4).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope, and this
|
|
6
|
+
* text lands in a transcript `notice` — produced outside `src/ui/**`, where no
|
|
7
|
+
* terminal capabilities are available, so it must be safe on a legacy `cmd.exe`.
|
|
8
|
+
*
|
|
9
|
+
* IT LIVES HERE RATHER THAN IN `builtins.ts`, AND THAT IS A BUDGET DECISION
|
|
10
|
+
* RATHER THAN TASTE (C-16 / P1-12). `CLAUDE.md` caps a source file at 1000 lines,
|
|
11
|
+
* `builtins.ts` is already past it, and `/compact` has eight forms plus the
|
|
12
|
+
* longest status report in the package. `builtins.ts` gets the registration and nothing
|
|
13
|
+
* else — the same split `controller.ts` argues for `FastWiring`, one layer out.
|
|
14
|
+
*/
|
|
15
|
+
import type { CommandContext } from '../commands/registry.js';
|
|
16
|
+
/**
|
|
17
|
+
* `/compact status` — THE GUARANTEED REPORTING SURFACE (§6.5 / AC-17).
|
|
18
|
+
*
|
|
19
|
+
* The chip drops on a narrow terminal, the activity row is suppressed under an
|
|
20
|
+
* overlay, and the card scrolls away. This command answers every question about
|
|
21
|
+
* the feature's state at any width, in any mode, including after the fact.
|
|
22
|
+
*
|
|
23
|
+
* IT READS THE SAME NUMBERS THE STATUS BAR DOES, through one `occupiedTokens`
|
|
24
|
+
* (§3.4.1 / R-11): a user watching 78 % on the bar who is told 91 % here would
|
|
25
|
+
* conclude one of the two is lying, and they would be right.
|
|
26
|
+
*
|
|
27
|
+
* `unknown (no price table for <model>)` RATHER THAN A CURRENCY AMOUNT when the
|
|
28
|
+
* static table has never seen the summarizer (the C-11 / RV-4 lesson from the
|
|
29
|
+
* fast tier). `$0.00` on a feature that is spending money is the same class of
|
|
30
|
+
* lie as pricing a Haiku child at Sonnet rates — and it matters MORE here,
|
|
31
|
+
* because the DEFAULT summarizer is the session's own frontier model.
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatCompactionStatus(ctx: CommandContext): string;
|
|
34
|
+
/**
|
|
35
|
+
* The `/compact` handler.
|
|
36
|
+
*
|
|
37
|
+
* `/compact <free text>` is the Claude Code affordance and is therefore the
|
|
38
|
+
* FALL-THROUGH rather than a subcommand: anything that is not one of the seven
|
|
39
|
+
* verbs is instructions for the summarizer.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runCompactCommand(ctx: CommandContext): Promise<void>;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/compact` slash command and its status report
|
|
3
|
+
* (context-auto-compaction §4.4).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope, and this
|
|
6
|
+
* text lands in a transcript `notice` — produced outside `src/ui/**`, where no
|
|
7
|
+
* terminal capabilities are available, so it must be safe on a legacy `cmd.exe`.
|
|
8
|
+
*
|
|
9
|
+
* IT LIVES HERE RATHER THAN IN `builtins.ts`, AND THAT IS A BUDGET DECISION
|
|
10
|
+
* RATHER THAN TASTE (C-16 / P1-12). `CLAUDE.md` caps a source file at 1000 lines,
|
|
11
|
+
* `builtins.ts` is already past it, and `/compact` has eight forms plus the
|
|
12
|
+
* longest status report in the package. `builtins.ts` gets the registration and nothing
|
|
13
|
+
* else — the same split `controller.ts` argues for `FastWiring`, one layer out.
|
|
14
|
+
*/
|
|
15
|
+
import { parseThresholdInput } from '../config/schema.js';
|
|
16
|
+
import { formatCost, formatTokens } from '../agent/usage.js';
|
|
17
|
+
import { computeCost } from '../agent/usage.js';
|
|
18
|
+
import { COMPACTION_LIMITS } from './limits.js';
|
|
19
|
+
import { findArchive, listArchives } from './archive.js';
|
|
20
|
+
/**
|
|
21
|
+
* `/compact status` — THE GUARANTEED REPORTING SURFACE (§6.5 / AC-17).
|
|
22
|
+
*
|
|
23
|
+
* The chip drops on a narrow terminal, the activity row is suppressed under an
|
|
24
|
+
* overlay, and the card scrolls away. This command answers every question about
|
|
25
|
+
* the feature's state at any width, in any mode, including after the fact.
|
|
26
|
+
*
|
|
27
|
+
* IT READS THE SAME NUMBERS THE STATUS BAR DOES, through one `occupiedTokens`
|
|
28
|
+
* (§3.4.1 / R-11): a user watching 78 % on the bar who is told 91 % here would
|
|
29
|
+
* conclude one of the two is lying, and they would be right.
|
|
30
|
+
*
|
|
31
|
+
* `unknown (no price table for <model>)` RATHER THAN A CURRENCY AMOUNT when the
|
|
32
|
+
* static table has never seen the summarizer (the C-11 / RV-4 lesson from the
|
|
33
|
+
* fast tier). `$0.00` on a feature that is spending money is the same class of
|
|
34
|
+
* lie as pricing a Haiku child at Sonnet rates — and it matters MORE here,
|
|
35
|
+
* because the DEFAULT summarizer is the session's own frontier model.
|
|
36
|
+
*/
|
|
37
|
+
export function formatCompactionStatus(ctx) {
|
|
38
|
+
const controller = ctx.controller;
|
|
39
|
+
const snapshot = controller.getCompactionSnapshot();
|
|
40
|
+
const config = controller.getCompactionConfig();
|
|
41
|
+
const lines = [];
|
|
42
|
+
if (!controller.isCompactionRegistered()) {
|
|
43
|
+
lines.push('Auto-compaction: not registered for this session (started with --no-compaction).');
|
|
44
|
+
lines.push(` Saved setting: compaction.enabled = ${config.enabled}`);
|
|
45
|
+
lines.push(' A full context window will end the run, as it did before this feature existed.');
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
const state = snapshot.selfDisabled
|
|
49
|
+
? `self-disabled (${snapshot.selfDisabledReason ?? 'no progress'})`
|
|
50
|
+
: controller.isCompactionEnabled()
|
|
51
|
+
? snapshot.live
|
|
52
|
+
? 'on'
|
|
53
|
+
: 'on, but no summarizer model resolves'
|
|
54
|
+
: 'off for this session';
|
|
55
|
+
lines.push(`Auto-compaction: ${state}`);
|
|
56
|
+
const p = snapshot.pressure;
|
|
57
|
+
const pct = Math.round(p.ratio * 100);
|
|
58
|
+
const approx = p.source === 'estimate' || !p.windowKnown ? '~' : '';
|
|
59
|
+
lines.push(` Occupancy: ${approx}${pct}% (${formatTokens(p.occupied)} of ` +
|
|
60
|
+
`${formatTokens(p.contextWindow)}${p.windowKnown ? '' : ', window unknown'}) ` +
|
|
61
|
+
`[${p.source === 'usage' ? 'measured' : 'estimated'}]`);
|
|
62
|
+
lines.push(` Triggers at: ${Math.round(config.threshold * 100)}% ` +
|
|
63
|
+
`(amber at ${Math.round(config.warnThreshold * 100)}%), ` +
|
|
64
|
+
`or when headroom drops below one full response`);
|
|
65
|
+
lines.push(` Keeps: the original task + ${config.keepRecentTurns} recent turns, verbatim`);
|
|
66
|
+
lines.push(` Summarizer: ${snapshot.model || '(none)'}` +
|
|
67
|
+
(config.useFastTier ? ' (fast tier preferred)' : ' (fast tier not used)'));
|
|
68
|
+
lines.push(` On summarize failure: ${config.onFailure}`);
|
|
69
|
+
lines.push(` This session: ${snapshot.compactions} ` +
|
|
70
|
+
`${snapshot.compactions === 1 ? 'compaction' : 'compactions'}` +
|
|
71
|
+
(snapshot.generation > 0 ? ` (generation ${snapshot.generation})` : '') +
|
|
72
|
+
`, ${formatTokens(snapshot.tokensReclaimed)} tokens reclaimed`);
|
|
73
|
+
const cost = snapshot.pricingUnknown
|
|
74
|
+
? `unknown (no price table for ${snapshot.model || 'the summarizer'})`
|
|
75
|
+
: formatCost(compactionCost(ctx));
|
|
76
|
+
lines.push(` Compaction spend: ${formatTokens(snapshot.usage.inputTokens)} in / ` +
|
|
77
|
+
`${formatTokens(snapshot.usage.outputTokens)} out, ${cost}`);
|
|
78
|
+
// TWO MORE LINES, AND ONLY WHEN THEY APPLY (hardening §4.4). A clip inside the
|
|
79
|
+
// retained turns is a real data loss and `/compact status` is the GUARANTEED
|
|
80
|
+
// reporting surface, so it has to be sayable here as well as on a card that
|
|
81
|
+
// may have scrolled away.
|
|
82
|
+
const relief = totalTailRelief(ctx);
|
|
83
|
+
if (relief) {
|
|
84
|
+
lines.push(` Tail relief ${relief.messages} tool ` +
|
|
85
|
+
`${relief.messages === 1 ? 'result' : 'results'} clipped ` +
|
|
86
|
+
`(${formatTokens(relief.charsRemoved)} chars) in ` +
|
|
87
|
+
`${relief.compactions} ${relief.compactions === 1 ? 'compaction' : 'compactions'}`);
|
|
88
|
+
}
|
|
89
|
+
const archives = archiveSummary(ctx);
|
|
90
|
+
if (archives)
|
|
91
|
+
lines.push(` Archive ${archives}`);
|
|
92
|
+
if (snapshot.inFlight)
|
|
93
|
+
lines.push(' A compaction is running right now.');
|
|
94
|
+
if (controller.isCompactionRegistered() && !controller.isCompactionEnabled()) {
|
|
95
|
+
lines.push(' Turn it back on with /compact on.');
|
|
96
|
+
}
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The summarizer's own price, never the lead's.
|
|
101
|
+
*
|
|
102
|
+
* THE REF IS ASKED FOR, NOT RECONSTRUCTED. Deriving the provider from config
|
|
103
|
+
* (`fast.enabled ? fast.provider : provider`) is wrong exactly when
|
|
104
|
+
* `fast.enabled` is true and `compaction.useFastTier` is false: the summarizer is
|
|
105
|
+
* then the MAIN model looked up under the FAST provider, which the static table
|
|
106
|
+
* has never seen, so `getModelInfoFor` returns a runtime model costed at zero and
|
|
107
|
+
* this line reports `$0.00` for a priced model. `pricingUnknown` cannot catch it
|
|
108
|
+
* either — the compactor sets that flag from the ref it actually used, which IS
|
|
109
|
+
* priced. `getCompactionSummarizerRef()` carries the answer instead.
|
|
110
|
+
*/
|
|
111
|
+
function compactionCost(ctx) {
|
|
112
|
+
const snapshot = ctx.controller.getCompactionSnapshot();
|
|
113
|
+
const ref = ctx.controller.getCompactionSummarizerRef();
|
|
114
|
+
if (!ref)
|
|
115
|
+
return 0;
|
|
116
|
+
return computeCost(snapshot.usage, ctx.controller.getModelInfoFor(ref).cost);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The session's clipped-tool-result total, or `null` when relief never fired.
|
|
120
|
+
*
|
|
121
|
+
* READ FROM THE TRANSCRIPT ENTRIES rather than from a counter on the snapshot,
|
|
122
|
+
* because relief is rare enough that a session-total field would be a number
|
|
123
|
+
* carried on every snapshot for a line almost nobody sees. The entries are
|
|
124
|
+
* already in view state and already carry it.
|
|
125
|
+
*/
|
|
126
|
+
function totalTailRelief(ctx) {
|
|
127
|
+
let messages = 0;
|
|
128
|
+
let charsRemoved = 0;
|
|
129
|
+
let compactions = 0;
|
|
130
|
+
for (const entry of ctx.state.entries) {
|
|
131
|
+
if (entry.kind !== 'compaction' || !entry.tailRelief)
|
|
132
|
+
continue;
|
|
133
|
+
messages += entry.tailRelief.messages;
|
|
134
|
+
charsRemoved += entry.tailRelief.charsRemoved;
|
|
135
|
+
compactions += 1;
|
|
136
|
+
}
|
|
137
|
+
return compactions === 0 ? null : { messages, charsRemoved, compactions };
|
|
138
|
+
}
|
|
139
|
+
/** `N files in <dir>` for this run, or `null` when there are none. */
|
|
140
|
+
function archiveSummary(ctx) {
|
|
141
|
+
const runId = ctx.controller.getCompactionRunId();
|
|
142
|
+
if (!runId)
|
|
143
|
+
return null;
|
|
144
|
+
if (!ctx.controller.getCompactionConfig().archive)
|
|
145
|
+
return null;
|
|
146
|
+
const listing = listArchives(runId);
|
|
147
|
+
if (listing.entries.length === 0)
|
|
148
|
+
return null;
|
|
149
|
+
return `${listing.entries.length} ${listing.entries.length === 1 ? 'file' : 'files'} in ${listing.dir}`;
|
|
150
|
+
}
|
|
151
|
+
/** One `/compact history` row. */
|
|
152
|
+
function archiveRow(entry) {
|
|
153
|
+
const at = new Date(entry.createdAt);
|
|
154
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
155
|
+
const clock = `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`;
|
|
156
|
+
return (` #${entry.index} ${clock} ${entry.trigger.padEnd(8)} ` +
|
|
157
|
+
`${formatTokens(entry.tokensBefore)} -> ${formatTokens(entry.tokensAfter)} ` +
|
|
158
|
+
`${entry.droppedCount} messages ${entry.fileName}`);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* `/compact history` - THIS RUN's archives, newest first.
|
|
162
|
+
*
|
|
163
|
+
* THE LISTING IS `runId`-SCOPED (RV-6). `<home>/compaction` is shared by every
|
|
164
|
+
* `aragon` on the machine, so an unscoped listing would present a neighbouring
|
|
165
|
+
* session's compactions as this one's and `#2` would be ambiguous the moment two
|
|
166
|
+
* runs both reached their second compaction. Other runs' files are COUNTED in one
|
|
167
|
+
* trailing line rather than listed, because the alternative is a user concluding
|
|
168
|
+
* their own compactions went missing.
|
|
169
|
+
*/
|
|
170
|
+
function formatCompactionHistory(ctx) {
|
|
171
|
+
const runId = ctx.controller.getCompactionRunId();
|
|
172
|
+
if (!runId) {
|
|
173
|
+
return 'Compaction is not registered for this session (started with --no-compaction).';
|
|
174
|
+
}
|
|
175
|
+
const listing = listArchives(runId);
|
|
176
|
+
if (listing.entries.length === 0) {
|
|
177
|
+
return ctx.controller.getCompactionConfig().archive
|
|
178
|
+
? 'No compactions have been archived yet.'
|
|
179
|
+
: 'No compaction archives for this session. (compaction.archive is off)';
|
|
180
|
+
}
|
|
181
|
+
const lines = [
|
|
182
|
+
`compaction history (${listing.entries.length} ` +
|
|
183
|
+
`${listing.entries.length === 1 ? 'archive' : 'archives'}, ${listing.dir})`,
|
|
184
|
+
'',
|
|
185
|
+
...listing.entries.map(archiveRow),
|
|
186
|
+
];
|
|
187
|
+
if (listing.otherRuns > 0) {
|
|
188
|
+
lines.push('');
|
|
189
|
+
lines.push(` (${listing.otherRuns} more ${listing.otherRuns === 1 ? 'archive' : 'archives'} ` +
|
|
190
|
+
'from earlier runs in this directory)');
|
|
191
|
+
}
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* `/compact show <n>` - metadata, the stored summary, and the path.
|
|
196
|
+
*
|
|
197
|
+
* `<n>` IS THE COMPACTION INDEX WITHIN THIS RUN - the `#n` `/compact history`
|
|
198
|
+
* printed, which is also the `index` field in the document. Those are the same
|
|
199
|
+
* number by construction: the listing is `runId`-scoped and `index` is unique
|
|
200
|
+
* within a run, so there is no second numbering to confuse it with.
|
|
201
|
+
*
|
|
202
|
+
* IT NEVER PRINTS MESSAGE BODIES (DH-9). A single archive can be megabytes and
|
|
203
|
+
* the transcript is not a pager; the path is the useful output.
|
|
204
|
+
*/
|
|
205
|
+
function formatCompactionShow(ctx, raw) {
|
|
206
|
+
const runId = ctx.controller.getCompactionRunId();
|
|
207
|
+
if (!runId) {
|
|
208
|
+
return 'Compaction is not registered for this session (started with --no-compaction).';
|
|
209
|
+
}
|
|
210
|
+
const n = Number.parseInt(raw.trim(), 10);
|
|
211
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
212
|
+
return 'Usage: /compact show <n> (see /compact history)';
|
|
213
|
+
const entry = findArchive(runId, n);
|
|
214
|
+
if (!entry)
|
|
215
|
+
return `No archive #${n}. Try /compact history.`;
|
|
216
|
+
const lines = [
|
|
217
|
+
`compaction #${entry.index} (${entry.trigger}, ${entry.mode})`,
|
|
218
|
+
` When: ${new Date(entry.createdAt).toISOString()}`,
|
|
219
|
+
` Tokens: ${formatTokens(entry.tokensBefore)} -> ${formatTokens(entry.tokensAfter)}`,
|
|
220
|
+
` Dropped: ${entry.droppedCount} messages${entry.clipped ? ' (archive body clipped to fit)' : ''}`,
|
|
221
|
+
` Model: ${entry.model || '(none)'}`,
|
|
222
|
+
];
|
|
223
|
+
if (entry.tailRelief) {
|
|
224
|
+
lines.push(` Clipped: ${entry.tailRelief.messages} tool results in the retained turns ` +
|
|
225
|
+
`(${formatTokens(entry.tailRelief.charsRemoved)} chars)`);
|
|
226
|
+
}
|
|
227
|
+
lines.push(` File: ${entry.path}`);
|
|
228
|
+
if (entry.summary) {
|
|
229
|
+
lines.push('');
|
|
230
|
+
lines.push(entry.summary);
|
|
231
|
+
}
|
|
232
|
+
return lines.join('\n');
|
|
233
|
+
}
|
|
234
|
+
const USAGE = 'Usage: /compact [status | on | off | threshold <n> | keep <n> | history | show <n> | <instructions>]';
|
|
235
|
+
/**
|
|
236
|
+
* The `/compact` handler.
|
|
237
|
+
*
|
|
238
|
+
* `/compact <free text>` is the Claude Code affordance and is therefore the
|
|
239
|
+
* FALL-THROUGH rather than a subcommand: anything that is not one of the seven
|
|
240
|
+
* verbs is instructions for the summarizer.
|
|
241
|
+
*/
|
|
242
|
+
export async function runCompactCommand(ctx) {
|
|
243
|
+
const arg = ctx.args.trim();
|
|
244
|
+
const [rawVerb, value] = arg.split(/\s+/, 2);
|
|
245
|
+
const verb = (rawVerb ?? '').toLowerCase();
|
|
246
|
+
const controller = ctx.controller;
|
|
247
|
+
if (verb === 'status') {
|
|
248
|
+
ctx.notify('info', formatCompactionStatus(ctx));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (verb === 'on' || verb === 'off') {
|
|
252
|
+
const enabled = verb === 'on';
|
|
253
|
+
const applied = controller.setCompactionConfig({ enabled });
|
|
254
|
+
ctx.persistConfig({ compaction: { enabled } });
|
|
255
|
+
if (!controller.isCompactionRegistered()) {
|
|
256
|
+
// The honest branch, and the whole point of splitting by what was decided
|
|
257
|
+
// at construction. `enabled` decides whether the wiring EXISTS, and a
|
|
258
|
+
// session that started without one cannot be given one mid-flight: the
|
|
259
|
+
// `Agent` takes the port in its constructor and has no setter for it
|
|
260
|
+
// (§3.2). `/fast on` and `/team on` each record the same shape.
|
|
261
|
+
ctx.notify('info', enabled
|
|
262
|
+
? 'Compaction is off for this session (started with --no-compaction). Saved for next launch.'
|
|
263
|
+
: 'Compaction is already off for this session. Saved for next launch.');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
controller.setCompactionEnabled(applied.enabled);
|
|
267
|
+
ctx.toast('success', `Auto-compaction ${enabled ? 'on' : 'off'}.`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (verb === 'threshold') {
|
|
271
|
+
const raw = (value ?? '').trim();
|
|
272
|
+
const parsed = raw ? parseThresholdInput(raw) : null;
|
|
273
|
+
if (parsed === null) {
|
|
274
|
+
ctx.notify('warn', 'Usage: /compact threshold <0.5-0.95> (also accepts 90%)');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const applied = controller.setCompactionConfig({ threshold: parsed });
|
|
278
|
+
ctx.persistConfig({
|
|
279
|
+
compaction: { threshold: applied.threshold },
|
|
280
|
+
});
|
|
281
|
+
// The APPLIED value, not the requested one: `clampCompactionConfig` may have
|
|
282
|
+
// moved it, and reporting what was asked for would make the clamp invisible.
|
|
283
|
+
ctx.toast('success', `Compaction threshold set to ${Math.round(applied.threshold * 100)}%.`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (verb === 'keep') {
|
|
287
|
+
const n = Number.parseInt((value ?? '').trim(), 10);
|
|
288
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
289
|
+
ctx.notify('warn', 'Usage: /compact keep <1-20>');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const applied = controller.setCompactionConfig({ keepRecentTurns: n });
|
|
293
|
+
ctx.persistConfig({
|
|
294
|
+
compaction: { keepRecentTurns: applied.keepRecentTurns },
|
|
295
|
+
});
|
|
296
|
+
ctx.toast('success', `Keeping ${applied.keepRecentTurns} recent turns verbatim.`);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// BEFORE THE FREE-TEXT FALL-THROUGH, so a user cannot accidentally summarize
|
|
300
|
+
// with the word "history".
|
|
301
|
+
if (verb === 'history') {
|
|
302
|
+
ctx.notify('info', formatCompactionHistory(ctx));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (verb === 'show') {
|
|
306
|
+
ctx.notify('info', formatCompactionShow(ctx, value ?? ''));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (verb === 'help' || verb === '?') {
|
|
310
|
+
ctx.notify('info', USAGE);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// --- Compact now, with `arg` (possibly empty) as instructions -------------
|
|
314
|
+
if (!controller.isCompactionRegistered()) {
|
|
315
|
+
ctx.notify('info', 'Compaction is not registered for this session (started with --no-compaction).');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (!controller.isCompactionEnabled()) {
|
|
319
|
+
ctx.notify('warn', 'Auto-compaction is off. Turn it on with /compact on, then try again.');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const instructions = arg.length > 0 ? arg.slice(0, COMPACTION_LIMITS.summaryMaxChars) : undefined;
|
|
323
|
+
if (ctx.state.status === 'running') {
|
|
324
|
+
// QUEUED, NOT RACED (D-17). Replacing `messages` under a live loop is a race;
|
|
325
|
+
// a flag `shouldCompact` honours at the next turn boundary is not, because
|
|
326
|
+
// the boundary is the one moment the engine is single-threaded with respect
|
|
327
|
+
// to the history, by construction.
|
|
328
|
+
controller.queueCompaction(instructions);
|
|
329
|
+
ctx.toast('info', 'Will compact before the next turn.');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
ctx.toast('info', 'Compacting context...');
|
|
333
|
+
const outcome = await controller.compactNow(instructions);
|
|
334
|
+
if (outcome.ok) {
|
|
335
|
+
ctx.toast('success', 'Context compacted.');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// THE SAME `invalid_history: <reason>` VOCABULARY THE ENGINE PRODUCES (D-25 /
|
|
339
|
+
// P1-7), so the card, the log and `/compact status` cannot tell the idle path
|
|
340
|
+
// apart from the in-loop one.
|
|
341
|
+
ctx.notify('warn', `Compaction did not run: ${outcome.reason ?? 'unknown'}`);
|
|
342
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Compactor` — the `ContextManager` implementation (context-auto-compaction
|
|
3
|
+
* §3.4 / §3.6 / §3.7 / §3.8).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* THIS IS THE POLICY HALF OF THE FEATURE. The engine owns exactly one thing -
|
|
8
|
+
* structural validity - and every other decision is here: when to fire, what to
|
|
9
|
+
* keep, which model summarizes, what the prompt asks for, what happens when the
|
|
10
|
+
* call fails, and when to stop spending.
|
|
11
|
+
*/
|
|
12
|
+
import { type Message, type ModelInfo, type ModelRef, type TokenUsage, type AssistantMessage, type CompactionContext, type CompactionOutcome, type CompactionProbe, type ContextManager, type LLMRequest } from '@aragon-agent/core';
|
|
13
|
+
import type { CliConfig } from '../config/schema.js';
|
|
14
|
+
import type { NoticeLevel } from '../agent/reducer.js';
|
|
15
|
+
import type { CompactionEvent, Pressure } from './types.js';
|
|
16
|
+
export interface CompactorDeps {
|
|
17
|
+
/** Read LIVE on every call — the controller replaces the object on mutation. */
|
|
18
|
+
getConfig: () => CliConfig;
|
|
19
|
+
hasKey: (providerId: string) => boolean;
|
|
20
|
+
getApiKey: (providerId: string) => string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The engine's live history and system prompt.
|
|
23
|
+
*
|
|
24
|
+
* `shouldCompact` NEEDS THEM, and that is the whole reason they are here.
|
|
25
|
+
* `CompactionProbe` carries `messageCount` but not the messages — the port is
|
|
26
|
+
* synchronous and cheap by design (D-3) — so the ESTIMATE fallback (§3.4.2)
|
|
27
|
+
* has nothing to measure unless the host supplies it. Estimating over an empty
|
|
28
|
+
* array yields 0 %, which is exactly the naive answer §3.4.2 exists to reject:
|
|
29
|
+
* the first request of a resumed 180 k-token session is the most dangerous
|
|
30
|
+
* moment in this feature's life, `lastUsage` is `undefined` there by
|
|
31
|
+
* definition, and a trigger that reads 0 % never fires.
|
|
32
|
+
*
|
|
33
|
+
* LAZY, and they must be: the wiring is constructed BEFORE the `Agent` (C-12),
|
|
34
|
+
* so a closure that touches it is only valid from inside a call the loop makes.
|
|
35
|
+
*/
|
|
36
|
+
getMessages: () => readonly Message[];
|
|
37
|
+
getSystemPrompt: () => string;
|
|
38
|
+
/** `ModelInfo` for ANY model, so the summarizer's own window and price resolve. */
|
|
39
|
+
getModelInfoFor: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => ModelInfo;
|
|
40
|
+
isPricedModel: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => boolean;
|
|
41
|
+
/** The compactor's OWN fail-fast transport (D-10). Supplied by the wiring. */
|
|
42
|
+
complete: (providerId: string, request: LLMRequest) => Promise<AssistantMessage>;
|
|
43
|
+
emit: (event: CompactionEvent) => void;
|
|
44
|
+
notify: (level: NoticeLevel, text: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Publish the occupancy this checkpoint measured (hardening §3.2.4 / DH-15).
|
|
47
|
+
*
|
|
48
|
+
* REPORTING IS NOT A SIDE EFFECT OF DECIDING; it is the other half of R-11.
|
|
49
|
+
* `shouldCompact` measures ONCE, ABOVE the guards, and calls this
|
|
50
|
+
* unconditionally on every enabled checkpoint - so the gauge and the trigger
|
|
51
|
+
* keep reading one number even on the turns a guard declines to compact.
|
|
52
|
+
* Optional, so a `Compactor` built without a UI (the child manager, W3) passes
|
|
53
|
+
* nothing and the call is one `?.` per turn.
|
|
54
|
+
*/
|
|
55
|
+
onPressure?: (pressure: Pressure) => void;
|
|
56
|
+
}
|
|
57
|
+
/** Per-instance bounds a child manager tightens (hardening §3.4.2 mechanism B). */
|
|
58
|
+
export interface CompactorOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Guard 2's ceiling for THIS compactor.
|
|
61
|
+
*
|
|
62
|
+
* A FIELD RATHER THAN A DIRECT `COMPACTION_LIMITS` READ, because a child gets a
|
|
63
|
+
* tighter one (W3 / DH-6) and `maxPerRun` is STRUCTURAL, not policy - there is
|
|
64
|
+
* no config path to it by design (`limits.ts` header). Defaults to the lead's
|
|
65
|
+
* value, so every existing construction site is unchanged.
|
|
66
|
+
*/
|
|
67
|
+
maxPerRun?: number;
|
|
68
|
+
}
|
|
69
|
+
/** The summarizer the next compaction would use. */
|
|
70
|
+
export interface SummarizerChoice {
|
|
71
|
+
ref: ModelRef;
|
|
72
|
+
/** Whether the fast tier supplied it, for `/compact status` and the card. */
|
|
73
|
+
fromFastTier: boolean;
|
|
74
|
+
}
|
|
75
|
+
export declare class Compactor implements ContextManager {
|
|
76
|
+
private readonly deps;
|
|
77
|
+
private readonly log;
|
|
78
|
+
private compactionsThisRun;
|
|
79
|
+
private lastCompactionTurn;
|
|
80
|
+
private compactions;
|
|
81
|
+
private tokensReclaimed;
|
|
82
|
+
private sessionUsage;
|
|
83
|
+
private pricingUnknown;
|
|
84
|
+
private inFlight;
|
|
85
|
+
private consecutiveNoProgress;
|
|
86
|
+
private selfDisabled;
|
|
87
|
+
private selfDisabledReason;
|
|
88
|
+
private pendingManual;
|
|
89
|
+
private generation;
|
|
90
|
+
/** Calibration for every ESTIMATED figure (D-23). Recomputed on every turn_end. */
|
|
91
|
+
private estimateOffset;
|
|
92
|
+
/** The last pressure computed, so the status bar and `/compact status` agree. */
|
|
93
|
+
private lastPressure;
|
|
94
|
+
/**
|
|
95
|
+
* The history LENGTH the measurement in `lastUsage` covered (hardening W1).
|
|
96
|
+
*
|
|
97
|
+
* `undefined` DISABLES THE DELTA and reproduces round 1's arithmetic exactly.
|
|
98
|
+
* Every host-side mutation that is not an append must clear it - see
|
|
99
|
+
* `invalidateMeasurement`.
|
|
100
|
+
*/
|
|
101
|
+
private measuredPrefixLength;
|
|
102
|
+
/**
|
|
103
|
+
* The messages the last splice dropped, held ONLY until the wiring collects
|
|
104
|
+
* them for the archive (W4 / RV-3).
|
|
105
|
+
*
|
|
106
|
+
* NOT ON `CompactionRecord`: that record is dispatched into the reducer and
|
|
107
|
+
* retained in view state for the card's lifetime, so putting a megabyte of
|
|
108
|
+
* history on it would park the dropped conversation in React state - precisely
|
|
109
|
+
* the memory this compaction just reclaimed.
|
|
110
|
+
*
|
|
111
|
+
* SET ONLY WHEN ARCHIVING IS ON, so with the key off the slice is never
|
|
112
|
+
* retained and W4 costs nothing.
|
|
113
|
+
*/
|
|
114
|
+
private lastDropped;
|
|
115
|
+
private callAbort;
|
|
116
|
+
private callAbortReason;
|
|
117
|
+
/** Guard 2's ceiling for this instance (hardening §3.4.2 mechanism B / DH-16). */
|
|
118
|
+
private readonly maxPerRun;
|
|
119
|
+
constructor(deps: CompactorDeps, opts?: CompactorOptions);
|
|
120
|
+
/** `agent_start` — the per-run cap resets, the session totals do not. */
|
|
121
|
+
onRunStart(): void;
|
|
122
|
+
/**
|
|
123
|
+
* `turn_end` — recompute the calibration offset (D-23).
|
|
124
|
+
*
|
|
125
|
+
* RE-MEASURED EVERY TURN, not once, so it tracks a toolset that changed
|
|
126
|
+
* mid-session (`/tools`, a skill that registers one).
|
|
127
|
+
*/
|
|
128
|
+
onTurnEnd(usage: TokenUsage, messages: readonly Message[], systemPrompt: string): void;
|
|
129
|
+
/**
|
|
130
|
+
* Any host-side history mutation that is NOT an append (hardening §3.2.3).
|
|
131
|
+
*
|
|
132
|
+
* `/clear`, `/resume`, and the splice itself: after any of them a recorded
|
|
133
|
+
* index means nothing, and the delta would be measured against the wrong
|
|
134
|
+
* array. `estimateAppendedTokens` bounds-checks as well, so a MISSED call here
|
|
135
|
+
* costs accuracy - the number degrades to round 1's - and never correctness.
|
|
136
|
+
*/
|
|
137
|
+
invalidateMeasurement(): void;
|
|
138
|
+
/**
|
|
139
|
+
* The dropped messages, released as they are returned (W4 / RV-3).
|
|
140
|
+
*
|
|
141
|
+
* `take` RATHER THAN `get`: the reference is dropped on read, so a wiring that
|
|
142
|
+
* declines to archive (key off, or `applied: false`) cannot pin it and a second
|
|
143
|
+
* read cannot double-write. `settlePending` calls this on EVERY verdict and
|
|
144
|
+
* discards on the ones that do not archive, which is what guarantees the array
|
|
145
|
+
* cannot outlive one compaction regardless of which branch was taken.
|
|
146
|
+
*/
|
|
147
|
+
takeLastDropped(): Message[] | null;
|
|
148
|
+
/** Cancel an in-flight summarization (Esc, `dispose`). */
|
|
149
|
+
abort(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Ask for a compaction at the next turn boundary.
|
|
152
|
+
*
|
|
153
|
+
* THE BOUNDARY IS THE ONLY MOMENT THE ENGINE IS SINGLE-THREADED WITH RESPECT
|
|
154
|
+
* TO THE HISTORY, by construction. Replacing `messages` under a live loop is a
|
|
155
|
+
* race; a flag `shouldCompact` honours there is not.
|
|
156
|
+
*/
|
|
157
|
+
queueManual(instructions?: string): void;
|
|
158
|
+
hasPendingManual(): boolean;
|
|
159
|
+
clearPendingManual(): void;
|
|
160
|
+
/**
|
|
161
|
+
* SYNCHRONOUS AND CHEAP. Two deliberate effects: the pressure cache, and
|
|
162
|
+
* `deps.onPressure` (hardening DH-15).
|
|
163
|
+
*
|
|
164
|
+
* THE MEASUREMENT IS TAKEN ONCE, ABOVE THE GUARDS, AND REPORTED
|
|
165
|
+
* UNCONDITIONALLY. Below them it is skipped on five paths — the per-run cap,
|
|
166
|
+
* the cooldown, manual, overflow and guard 4 — and TWO OF THOSE NEVER CLEAR
|
|
167
|
+
* WITHIN A RUN. A gauge fed from the measurement would therefore go dark
|
|
168
|
+
* exactly when compaction is exhausted and the window is still filling, which
|
|
169
|
+
* is the one state where the corrected number is the user's only warning. It
|
|
170
|
+
* would silently revert to the measured base — the very figure W1 exists
|
|
171
|
+
* because it under-reports.
|
|
172
|
+
*
|
|
173
|
+
* `!config.enabled` STAYS THE FIRST STATEMENT, so a session that turned the
|
|
174
|
+
* feature off pays nothing at all and AC-1's byte-identity claim is untouched.
|
|
175
|
+
*/
|
|
176
|
+
shouldCompact(probe: CompactionProbe): boolean;
|
|
177
|
+
compact(ctx: CompactionContext): Promise<CompactionOutcome>;
|
|
178
|
+
private runCompaction;
|
|
179
|
+
/** Tail relief against this session's window (hardening §3.3.3 / W2). */
|
|
180
|
+
private relieveInPlace;
|
|
181
|
+
/**
|
|
182
|
+
* The failure ladder (§3.7).
|
|
183
|
+
*
|
|
184
|
+
* Rung 1 is the fast tier (or the main model); rung 2 is ONE retry on the MAIN
|
|
185
|
+
* model when rung 1 used the fast tier, because "a fast model that cannot
|
|
186
|
+
* summarize" is the most likely single failure. Rungs 3 and 4 are the caller's,
|
|
187
|
+
* because they depend on `onFailure`.
|
|
188
|
+
*/
|
|
189
|
+
private runLadder;
|
|
190
|
+
private callOnce;
|
|
191
|
+
/**
|
|
192
|
+
* Guard 3 — the progress requirement (§3.8).
|
|
193
|
+
*
|
|
194
|
+
* ADVISORY ON OVERFLOW, NEVER A VETO (D-24 / P1-13). The arithmetic that makes
|
|
195
|
+
* this guard sensible for the proactive trigger INVERTS on the reactive one: a
|
|
196
|
+
* pressure compaction that reclaims little has spent money to buy two turns and
|
|
197
|
+
* should stop, while an overflow compaction that reclaims little is the
|
|
198
|
+
* difference between a continuing run and a dead one, because the provider has
|
|
199
|
+
* already refused the request.
|
|
200
|
+
*
|
|
201
|
+
* The projection is ESTIMATED — no usage exists for a history that has not been
|
|
202
|
+
* sent yet — and is therefore CALIBRATED with `estimateOffset` (D-23).
|
|
203
|
+
*
|
|
204
|
+
* IT TAKES THE *UI* TRIGGER, not `ctx.trigger`. A queued `/compact` reaches the
|
|
205
|
+
* loop as an ordinary `pressure` checkpoint (that is what the queue IS), so
|
|
206
|
+
* charging its result to guard 4's counter would let a user who types
|
|
207
|
+
* `/compact` twice on a short conversation self-disable the proactive trigger
|
|
208
|
+
* for the rest of the session — and be told, wrongly, that their recent turns
|
|
209
|
+
* exceed the threshold. `uiTrigger` is the only value that knows the difference.
|
|
210
|
+
*/
|
|
211
|
+
private checkProgress;
|
|
212
|
+
/**
|
|
213
|
+
* Guard 4 — stuck detection (§3.8).
|
|
214
|
+
*
|
|
215
|
+
* PRESSURE ONLY, AND `pressure` HERE MEANS THE *UI* TRIGGER. A session that
|
|
216
|
+
* self-disables still survives an overflow rather than dying at the next
|
|
217
|
+
* request, and if the tail really is too large the overflow attempt is the one
|
|
218
|
+
* that can do something about it (D-24). A `manual` compaction is excluded for
|
|
219
|
+
* a different reason: this guard exists to stop UNREQUESTED spend, and
|
|
220
|
+
* `/compact` on a conversation with nothing worth dropping is a user who asked
|
|
221
|
+
* — twice — not a runaway loop.
|
|
222
|
+
*/
|
|
223
|
+
private noteNoProgress;
|
|
224
|
+
private finish;
|
|
225
|
+
/**
|
|
226
|
+
* The pressure the trigger and the gauge both read.
|
|
227
|
+
*
|
|
228
|
+
* CACHED on every evaluation, so `/compact status` reports the same number the
|
|
229
|
+
* last decision used rather than recomputing one that has since moved.
|
|
230
|
+
*/
|
|
231
|
+
measure(probe: Pick<CompactionProbe, 'lastUsage'> & {
|
|
232
|
+
messages?: readonly Message[];
|
|
233
|
+
systemPrompt?: string;
|
|
234
|
+
}): Pressure;
|
|
235
|
+
lastMeasured(): Pressure | null;
|
|
236
|
+
/** Written by the wiring once the engine has reported its verdict. */
|
|
237
|
+
recordApplied(tokensBefore: number, tokensAfter: number): void;
|
|
238
|
+
sessionTotals(): {
|
|
239
|
+
compactions: number;
|
|
240
|
+
tokensReclaimed: number;
|
|
241
|
+
usage: TokenUsage;
|
|
242
|
+
pricingUnknown: boolean;
|
|
243
|
+
inFlight: boolean;
|
|
244
|
+
selfDisabled: boolean;
|
|
245
|
+
selfDisabledReason?: string;
|
|
246
|
+
generation: number;
|
|
247
|
+
};
|
|
248
|
+
/** `/compact on` after a self-disable — the user overriding the guard. */
|
|
249
|
+
clearSelfDisable(): void;
|
|
250
|
+
isSelfDisabled(): boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Which model summarizes (§3.6.4).
|
|
253
|
+
*
|
|
254
|
+
* SAY THE DEFAULT OUT LOUD (P2-8): FOR ALMOST EVERYONE THIS IS THE MAIN MODEL.
|
|
255
|
+
* `resolveFastTier` returns `{ok: false, reason: 'disabled'}` on
|
|
256
|
+
* `fast.enabled !== true` alone, and `fast.enabled` defaults to `false` — so
|
|
257
|
+
* `useFastTier: true` describes a PREFERENCE, not the common path, and the
|
|
258
|
+
* out-of-the-box first compaction is a ~30 k-token call on the session's own
|
|
259
|
+
* model. Three things make that the right trade rather than a hidden cost: it
|
|
260
|
+
* happens at most `maxPerRun` times per run, the alternative is losing the
|
|
261
|
+
* entire run, and the spend is on the card with the summarizer's own price.
|
|
262
|
+
*/
|
|
263
|
+
resolveSummarizer(): SummarizerChoice | null;
|
|
264
|
+
private mainRef;
|
|
265
|
+
private modelInfo;
|
|
266
|
+
private effectiveMaxOutputTokens;
|
|
267
|
+
private noteUsage;
|
|
268
|
+
}
|