@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
package/dist/agent/reducer.js
CHANGED
|
@@ -11,11 +11,25 @@
|
|
|
11
11
|
* synthesizes a generic error notice (a swallowed throw is never a no-op).
|
|
12
12
|
*/
|
|
13
13
|
import process from 'node:process';
|
|
14
|
-
import {
|
|
15
|
-
import { computeCost } from './usage.js';
|
|
14
|
+
import { emptyContextUsage } from '../compaction/meter.js';
|
|
15
|
+
import { addUsage, computeCost } from './usage.js';
|
|
16
16
|
import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
|
|
17
17
|
/** Default auto-dismiss window for a toast. */
|
|
18
18
|
export const DEFAULT_TOAST_TTL_MS = 2500;
|
|
19
|
+
/**
|
|
20
|
+
* The zero session total.
|
|
21
|
+
*
|
|
22
|
+
* ONE LITERAL FOR TWO SITES (`initialViewState` and `resetConversation`). They
|
|
23
|
+
* used to spell the object out twice, which is how `cacheReadTokens` would have
|
|
24
|
+
* been added to one of them and not the other.
|
|
25
|
+
*/
|
|
26
|
+
const EMPTY_USAGE_TOTAL = {
|
|
27
|
+
inputTokens: 0,
|
|
28
|
+
outputTokens: 0,
|
|
29
|
+
cacheReadTokens: 0,
|
|
30
|
+
cacheWriteTokens: 0,
|
|
31
|
+
costUsd: 0,
|
|
32
|
+
};
|
|
19
33
|
/**
|
|
20
34
|
* The initial view state, optionally seeded from config.
|
|
21
35
|
*
|
|
@@ -33,8 +47,8 @@ export function initialViewState(seed = {}) {
|
|
|
33
47
|
return {
|
|
34
48
|
entries: [],
|
|
35
49
|
status: 'idle',
|
|
36
|
-
usageTotal:
|
|
37
|
-
|
|
50
|
+
usageTotal: EMPTY_USAGE_TOTAL,
|
|
51
|
+
context: emptyContextUsage(),
|
|
38
52
|
overlay: null,
|
|
39
53
|
thinkingVisible: seed.thinkingVisible ?? false,
|
|
40
54
|
toasts: [],
|
|
@@ -48,7 +62,6 @@ export function initialViewState(seed = {}) {
|
|
|
48
62
|
retry: null,
|
|
49
63
|
fast: null,
|
|
50
64
|
compaction: null,
|
|
51
|
-
contextTokensEstimated: false,
|
|
52
65
|
seq: 0,
|
|
53
66
|
teamEntryId: undefined,
|
|
54
67
|
todoEntryId: undefined,
|
|
@@ -610,22 +623,12 @@ export function viewReducer(state, action) {
|
|
|
610
623
|
...state,
|
|
611
624
|
streamingId: undefined,
|
|
612
625
|
turnProduced: true,
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
|
|
619
|
-
// number from the one the user is watching.
|
|
620
|
-
contextTokens: occupiedTokens(action.usage),
|
|
621
|
-
// A MEASUREMENT SUPERSEDES A DERIVATION (§6.2). `compactionEnd` writes an
|
|
622
|
-
// estimate so the bar falls immediately; this is where it stops being one.
|
|
623
|
-
contextTokensEstimated: false,
|
|
624
|
-
usageTotal: {
|
|
625
|
-
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
626
|
-
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
627
|
-
costUsd: state.usageTotal.costUsd + action.costDelta,
|
|
628
|
-
},
|
|
626
|
+
// `contextTokens` IS NOT WRITTEN HERE ANY MORE (I-1). The occupancy this
|
|
627
|
+
// turn produced reaches the view through `ContextMeter`, which subscribes
|
|
628
|
+
// to the same `turn_end` and publishes the measured pressure - window,
|
|
629
|
+
// percentage and approximation markers included, none of which this
|
|
630
|
+
// branch ever had. Two branches writing one number is what P0-1 was.
|
|
631
|
+
usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta),
|
|
629
632
|
// The turn produced an answer, so whatever it had to retry, it recovered.
|
|
630
633
|
...settleRetryCard(state, entries, 'recovered'),
|
|
631
634
|
};
|
|
@@ -763,8 +766,13 @@ export function viewReducer(state, action) {
|
|
|
763
766
|
droppedEntries: 0,
|
|
764
767
|
streamingId: undefined,
|
|
765
768
|
expandedToolIds: {},
|
|
766
|
-
|
|
767
|
-
|
|
769
|
+
// `context` IS DELIBERATELY UNTOUCHED (I-1). `builtins.ts` calls
|
|
770
|
+
// `controller.clearMessages()` BEFORE dispatching this, and that marks
|
|
771
|
+
// the meter dirty and schedules a publication; zeroing here would make
|
|
772
|
+
// the final state `0` rather than the meter's real answer - which for an
|
|
773
|
+
// emptied conversation is the system prompt's own couple of percent, not
|
|
774
|
+
// nothing. `usageTotal` DOES reset: that is session spend, not occupancy.
|
|
775
|
+
usageTotal: EMPTY_USAGE_TOTAL,
|
|
768
776
|
teamEntryId: undefined,
|
|
769
777
|
// `/reset` clears `messages`, so the belief is gone and the list goes
|
|
770
778
|
// with it — the other half of I-2. `controller.clearMessages()` clears
|
|
@@ -774,10 +782,6 @@ export function viewReducer(state, action) {
|
|
|
774
782
|
retryEntryId: undefined,
|
|
775
783
|
retry: null,
|
|
776
784
|
compactionEntryId: undefined,
|
|
777
|
-
// `contextTokens` is zeroed above, so the flag that says it is a
|
|
778
|
-
// DERIVATION has to go with it — otherwise a fresh conversation renders
|
|
779
|
-
// `~0%` and the tilde is a lie about a number that is simply zero.
|
|
780
|
-
contextTokensEstimated: false,
|
|
781
785
|
};
|
|
782
786
|
case 'setOverlay':
|
|
783
787
|
return { ...state, overlay: action.overlay };
|
|
@@ -860,18 +864,11 @@ export function viewReducer(state, action) {
|
|
|
860
864
|
}
|
|
861
865
|
case 'teamUsage':
|
|
862
866
|
// `usageTotal` AND NOTHING ELSE (§3.9 / AC-15). In particular NOT
|
|
863
|
-
// `
|
|
867
|
+
// `context`: that gauge shows the LEAD's context occupancy against
|
|
864
868
|
// the model's window, and folding five children into it would read 180%
|
|
865
869
|
// on a perfectly healthy session. Child spend is real money and has to
|
|
866
870
|
// appear in the cost readout; it is not the lead's context.
|
|
867
|
-
return {
|
|
868
|
-
...state,
|
|
869
|
-
usageTotal: {
|
|
870
|
-
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
871
|
-
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
872
|
-
costUsd: state.usageTotal.costUsd + action.costDelta,
|
|
873
|
-
},
|
|
874
|
-
};
|
|
871
|
+
return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
|
|
875
872
|
case 'teamEnd': {
|
|
876
873
|
const { outcome } = action;
|
|
877
874
|
const entries = state.teamEntryId
|
|
@@ -991,17 +988,10 @@ export function viewReducer(state, action) {
|
|
|
991
988
|
}
|
|
992
989
|
case 'fastUsage':
|
|
993
990
|
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` (§3.6 / AC-18). In
|
|
994
|
-
// particular NOT `
|
|
991
|
+
// particular NOT `context`: that gauge shows the LEAD's context
|
|
995
992
|
// occupancy against the lead model's window, and a review is a separate
|
|
996
993
|
// conversation with a different model entirely.
|
|
997
|
-
return {
|
|
998
|
-
...state,
|
|
999
|
-
usageTotal: {
|
|
1000
|
-
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
1001
|
-
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
1002
|
-
costUsd: state.usageTotal.costUsd + action.costDelta,
|
|
1003
|
-
},
|
|
1004
|
-
};
|
|
994
|
+
return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
|
|
1005
995
|
case 'fastTier':
|
|
1006
996
|
return { ...state, fast: action.snapshot };
|
|
1007
997
|
// --- Context compaction (context-auto-compaction §5.5) ----------------
|
|
@@ -1128,40 +1118,41 @@ export function viewReducer(state, action) {
|
|
|
1128
1118
|
}
|
|
1129
1119
|
case 'compactionUsage':
|
|
1130
1120
|
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
|
|
1131
|
-
// particular NOT `
|
|
1121
|
+
// particular NOT `context`: that gauge shows the LEAD's context
|
|
1132
1122
|
// occupancy against the lead model's window, and a summarization is a
|
|
1133
1123
|
// separate conversation with a possibly different model entirely.
|
|
1134
|
-
return {
|
|
1135
|
-
...state,
|
|
1136
|
-
usageTotal: {
|
|
1137
|
-
inputTokens: state.usageTotal.inputTokens + action.usage.inputTokens,
|
|
1138
|
-
outputTokens: state.usageTotal.outputTokens + action.usage.outputTokens,
|
|
1139
|
-
costUsd: state.usageTotal.costUsd + action.costDelta,
|
|
1140
|
-
},
|
|
1141
|
-
};
|
|
1124
|
+
return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
|
|
1142
1125
|
case 'compactionSnapshot':
|
|
1143
1126
|
return { ...state, compaction: action.snapshot };
|
|
1144
1127
|
/**
|
|
1145
|
-
* THE
|
|
1128
|
+
* THE ONE WRITER OF `state.context` (I-1).
|
|
1146
1129
|
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
* the
|
|
1130
|
+
* THE IDENTITY SHORT-CIRCUIT BELOW IS LOAD-BEARING, NOT A MICRO-OPTIMISATION
|
|
1131
|
+
* (§5.2 / R-3). The meter publishes on a 400 ms tick throughout a turn and
|
|
1132
|
+
* most of those ticks measure the same number - the history did not change
|
|
1133
|
+
* between two tool calls of the same size, or the percentage rounded to the
|
|
1134
|
+
* same integer. Returning the SAME state object lets React skip the whole
|
|
1135
|
+
* subtree; returning a fresh one would make a permanently visible row into a
|
|
1136
|
+
* 2.5 Hz re-render source and walk the render governor up its ladder for no
|
|
1137
|
+
* visible change at all.
|
|
1152
1138
|
*
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
* never counts them, so writing the raw figure would make the bar fall too
|
|
1156
|
-
* far and then visibly climb again on the next `turn_end` for no reason the
|
|
1157
|
-
* user can see.
|
|
1139
|
+
* `ContextUsageSnapshot` carries no timestamp for exactly this reason: a
|
|
1140
|
+
* field that moves every tick would make this comparison never hold.
|
|
1158
1141
|
*/
|
|
1159
|
-
case '
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1142
|
+
case 'contextUsage': {
|
|
1143
|
+
const prev = state.context;
|
|
1144
|
+
const next = action.snapshot;
|
|
1145
|
+
if (prev.occupied === next.occupied &&
|
|
1146
|
+
prev.window === next.window &&
|
|
1147
|
+
prev.pct === next.pct &&
|
|
1148
|
+
prev.source === next.source &&
|
|
1149
|
+
prev.deltaTokens === next.deltaTokens &&
|
|
1150
|
+
prev.windowKnown === next.windowKnown &&
|
|
1151
|
+
prev.windowOverridden === next.windowOverridden) {
|
|
1152
|
+
return state;
|
|
1153
|
+
}
|
|
1154
|
+
return { ...state, context: next };
|
|
1155
|
+
}
|
|
1165
1156
|
// --- API retry (llm-api-retry-backoff §6.4) ---------------------------
|
|
1166
1157
|
case 'retryScheduled': {
|
|
1167
1158
|
const snapshot = {
|
package/dist/agent/usage.d.ts
CHANGED
|
@@ -3,8 +3,42 @@
|
|
|
3
3
|
* (which is expressed in USD per 1M tokens). See spec §6.2 status bar.
|
|
4
4
|
*/
|
|
5
5
|
import type { ModelCost, TokenUsage } from '@aragon-agent/core';
|
|
6
|
+
import type { UsageTotal } from './reducer.js';
|
|
6
7
|
/** Compute the USD cost of a single `TokenUsage` given a model's cost table. */
|
|
7
8
|
export declare function computeCost(usage: TokenUsage, cost?: ModelCost): number;
|
|
9
|
+
/**
|
|
10
|
+
* Fold one `TokenUsage` into a running session total
|
|
11
|
+
* (context-usage-gauge-accuracy §3.6 / W5).
|
|
12
|
+
*
|
|
13
|
+
* THE ONE ADDITION SITE, and collapsing four hand-written copies into it is the
|
|
14
|
+
* point rather than a tidy-up. The reducer had the same three lines in four
|
|
15
|
+
* branches - `turnEnd`, `teamUsage`, `fastUsage`, `compactionUsage` - and all
|
|
16
|
+
* four silently omitted the cache terms, so the status bar's `^` read LOWER than
|
|
17
|
+
* the `$` beside it on any session behind a caching gateway. A fifth source
|
|
18
|
+
* added later would have made the identical omission; now it cannot.
|
|
19
|
+
*
|
|
20
|
+
* `costDelta` IS PRECOMPUTED BY THE CALLER, exactly as the four branches already
|
|
21
|
+
* required: the summarizer and the fast tier may be different models with
|
|
22
|
+
* different price tables, and a reducer that priced its own would have to know
|
|
23
|
+
* about three of them.
|
|
24
|
+
*/
|
|
25
|
+
export declare function addUsage(total: UsageTotal, usage: TokenUsage, costDelta: number): UsageTotal;
|
|
26
|
+
/**
|
|
27
|
+
* The number the status bar's `^` shows: the PROMPT side of the session total.
|
|
28
|
+
*
|
|
29
|
+
* DELIBERATELY THE SAME TERMS AS `occupiedTokens` MINUS `outputTokens`
|
|
30
|
+
* (`compaction/pressure.ts`). Anthropic reports `input_tokens` EXCLUDING cached
|
|
31
|
+
* tokens and puts them in two separate fields, so `inputTokens` alone
|
|
32
|
+
* under-reports for anyone behind a caching gateway - while `computeCost` above
|
|
33
|
+
* has always priced all three. That is the "three units on one row" defect
|
|
34
|
+
* (P1-3): the percentage counted cache, the dollars counted cache, and the arrow
|
|
35
|
+
* did not.
|
|
36
|
+
*
|
|
37
|
+
* `$` IS STILL NOT THE SAME TOTAL, and the distinction is worth keeping straight
|
|
38
|
+
* (RV-14): `computeCost` also prices OUTPUT tokens. What this function asserts is
|
|
39
|
+
* narrower and exact - both cache terms are counted, on both sides.
|
|
40
|
+
*/
|
|
41
|
+
export declare function promptTokensOf(total: UsageTotal): number;
|
|
8
42
|
/** Format a USD amount for the status bar (`$0.0000`, or `$1.23` when large). */
|
|
9
43
|
export declare function formatCost(usd: number): string;
|
|
10
44
|
/** Format a token count compactly (`1.2k`, `3.4M`). */
|
package/dist/agent/usage.js
CHANGED
|
@@ -12,6 +12,49 @@ export function computeCost(usage, cost) {
|
|
|
12
12
|
perM(usage.cacheReadTokens ?? 0, cost.cacheRead) +
|
|
13
13
|
perM(usage.cacheWriteTokens ?? 0, cost.cacheWrite));
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Fold one `TokenUsage` into a running session total
|
|
17
|
+
* (context-usage-gauge-accuracy §3.6 / W5).
|
|
18
|
+
*
|
|
19
|
+
* THE ONE ADDITION SITE, and collapsing four hand-written copies into it is the
|
|
20
|
+
* point rather than a tidy-up. The reducer had the same three lines in four
|
|
21
|
+
* branches - `turnEnd`, `teamUsage`, `fastUsage`, `compactionUsage` - and all
|
|
22
|
+
* four silently omitted the cache terms, so the status bar's `^` read LOWER than
|
|
23
|
+
* the `$` beside it on any session behind a caching gateway. A fifth source
|
|
24
|
+
* added later would have made the identical omission; now it cannot.
|
|
25
|
+
*
|
|
26
|
+
* `costDelta` IS PRECOMPUTED BY THE CALLER, exactly as the four branches already
|
|
27
|
+
* required: the summarizer and the fast tier may be different models with
|
|
28
|
+
* different price tables, and a reducer that priced its own would have to know
|
|
29
|
+
* about three of them.
|
|
30
|
+
*/
|
|
31
|
+
export function addUsage(total, usage, costDelta) {
|
|
32
|
+
return {
|
|
33
|
+
inputTokens: total.inputTokens + usage.inputTokens,
|
|
34
|
+
outputTokens: total.outputTokens + usage.outputTokens,
|
|
35
|
+
cacheReadTokens: total.cacheReadTokens + (usage.cacheReadTokens ?? 0),
|
|
36
|
+
cacheWriteTokens: total.cacheWriteTokens + (usage.cacheWriteTokens ?? 0),
|
|
37
|
+
costUsd: total.costUsd + costDelta,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The number the status bar's `^` shows: the PROMPT side of the session total.
|
|
42
|
+
*
|
|
43
|
+
* DELIBERATELY THE SAME TERMS AS `occupiedTokens` MINUS `outputTokens`
|
|
44
|
+
* (`compaction/pressure.ts`). Anthropic reports `input_tokens` EXCLUDING cached
|
|
45
|
+
* tokens and puts them in two separate fields, so `inputTokens` alone
|
|
46
|
+
* under-reports for anyone behind a caching gateway - while `computeCost` above
|
|
47
|
+
* has always priced all three. That is the "three units on one row" defect
|
|
48
|
+
* (P1-3): the percentage counted cache, the dollars counted cache, and the arrow
|
|
49
|
+
* did not.
|
|
50
|
+
*
|
|
51
|
+
* `$` IS STILL NOT THE SAME TOTAL, and the distinction is worth keeping straight
|
|
52
|
+
* (RV-14): `computeCost` also prices OUTPUT tokens. What this function asserts is
|
|
53
|
+
* narrower and exact - both cache terms are counted, on both sides.
|
|
54
|
+
*/
|
|
55
|
+
export function promptTokensOf(total) {
|
|
56
|
+
return total.inputTokens + total.cacheReadTokens + total.cacheWriteTokens;
|
|
57
|
+
}
|
|
15
58
|
/** Format a USD amount for the status bar (`$0.0000`, or `$1.23` when large). */
|
|
16
59
|
export function formatCost(usd) {
|
|
17
60
|
if (usd <= 0)
|
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS,
|
|
|
27
27
|
import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
28
28
|
import { getLogger } from './logging/logger.js';
|
|
29
29
|
import { runLogsCommand } from './logging/cli-commands.js';
|
|
30
|
-
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SCROLL_RESUME_MS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
30
|
+
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampContextWindow, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, isAutoToken, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SCROLL_RESUME_MS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
31
31
|
import { makeGetApiKey } from './config/load.js';
|
|
32
32
|
import { AgentController, DENY_ALL_APPROVAL } from './agent/controller.js';
|
|
33
33
|
import { runSkillsCommand } from './skills/cli-commands.js';
|
|
@@ -153,6 +153,11 @@ function toFlags(opts) {
|
|
|
153
153
|
fastModel: opts.fastModel,
|
|
154
154
|
fastProvider: opts.fastProvider,
|
|
155
155
|
fastReview: opts.fastReview,
|
|
156
|
+
// …and again for `--fast-delegate` / `--no-fast-delegate`, one feature
|
|
157
|
+
// later — NINE features, one trap. It is `fast.delegate`'s only channel:
|
|
158
|
+
// there is no `ARAGON_FAST_DELEGATE`, so missing this line leaves a wrapper
|
|
159
|
+
// with no way at all to say "do not delegate on this run".
|
|
160
|
+
fastDelegate: opts.fastDelegate,
|
|
156
161
|
// `--verbose` is just a shorthand for `--log-level debug`; an explicit
|
|
157
162
|
// `--log-level` still wins, so the two can be combined without surprise.
|
|
158
163
|
logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
|
|
@@ -924,6 +929,11 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
924
929
|
'baseUrl',
|
|
925
930
|
'thinkingLevel',
|
|
926
931
|
'maxTokens',
|
|
932
|
+
// context-usage-gauge-accuracy §3.6. Listed here AND given its own branch in
|
|
933
|
+
// `runConfigSet` below - a key present here but absent there falls through
|
|
934
|
+
// every case, writes nothing, and still prints `Set contextWindow = 1000000`
|
|
935
|
+
// (P1-2).
|
|
936
|
+
'contextWindow',
|
|
927
937
|
'theme',
|
|
928
938
|
'confirmTools',
|
|
929
939
|
'toolTimeoutMs',
|
|
@@ -1020,6 +1030,30 @@ function runConfigSetMaxTokens(value) {
|
|
|
1020
1030
|
const merged = updatePersistedConfig({ maxTokens: stored });
|
|
1021
1031
|
process.stdout.write(`Set maxTokens = ${merged.maxTokens ?? 'auto'}\n`);
|
|
1022
1032
|
}
|
|
1033
|
+
/**
|
|
1034
|
+
* `aragon config set contextWindow <n|auto>`.
|
|
1035
|
+
*
|
|
1036
|
+
* ITS OWN BRANCH, AHEAD OF THE SWITCH, for the reason `maxTokens` has one: it
|
|
1037
|
+
* must echo the STORED value rather than the typed one, so
|
|
1038
|
+
* `config set contextWindow 99999999` reports the clamped 5000000 instead of a
|
|
1039
|
+
* number that was never written. It also REJECTS nonsense rather than clamping
|
|
1040
|
+
* it - a non-numeric argument here is almost always a typo for a number, and
|
|
1041
|
+
* storing AUTO for `20O000` would quietly discard the user's intent.
|
|
1042
|
+
*/
|
|
1043
|
+
function runConfigSetContextWindow(value) {
|
|
1044
|
+
const trimmed = value.trim();
|
|
1045
|
+
const auto = isAutoToken(trimmed);
|
|
1046
|
+
if (!auto && !/^\d+$/.test(trimmed)) {
|
|
1047
|
+
process.stderr.write(`Invalid contextWindow "${value}". Use a number of tokens or "auto".\n`);
|
|
1048
|
+
process.exitCode = 2;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
// `null` GOES IN UNCLAMPED (RV-12). Running AUTO through the clamp yields
|
|
1052
|
+
// 8000 and removes "auto" from the config permanently.
|
|
1053
|
+
const stored = auto ? null : clampContextWindow(trimmed, null);
|
|
1054
|
+
const merged = updatePersistedConfig({ contextWindow: stored });
|
|
1055
|
+
process.stdout.write(`Set contextWindow = ${merged.contextWindow ?? 'auto'}\n`);
|
|
1056
|
+
}
|
|
1023
1057
|
function runConfigSet(key, value) {
|
|
1024
1058
|
if (!CONFIG_SET_KEYS.has(key)) {
|
|
1025
1059
|
process.stderr.write(`Unknown config key "${key}". Known keys: ${[...CONFIG_SET_KEYS].join(', ')}.\n`);
|
|
@@ -1040,6 +1074,11 @@ function runConfigSet(key, value) {
|
|
|
1040
1074
|
runConfigSetMaxTokens(value);
|
|
1041
1075
|
return;
|
|
1042
1076
|
}
|
|
1077
|
+
// The second tri-state key, and it needs its own branch for the same reason.
|
|
1078
|
+
if (key === 'contextWindow') {
|
|
1079
|
+
runConfigSetContextWindow(value);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1043
1082
|
const teamPatch = applyTeamConfigSet(key, value);
|
|
1044
1083
|
if (teamPatch) {
|
|
1045
1084
|
// The written value is echoed from the CLAMPED result rather than from the
|
|
@@ -1491,6 +1530,14 @@ function buildProgram() {
|
|
|
1491
1530
|
.option('--fast-model <id>', 'The fast model id (implies nothing about --fast)')
|
|
1492
1531
|
.option('--fast-provider <id>', 'Provider for the fast model (default: the main provider)')
|
|
1493
1532
|
.option('--fast-review <n|off>', 'Turns between automatic fast reviews, or "off"')
|
|
1533
|
+
// BOTH FORMS, POSITIVE FIRST, and here it matters more than anywhere above:
|
|
1534
|
+
// `fast.delegate` is PERSISTED and defaults to TRUE, so declaring only
|
|
1535
|
+
// `--no-fast-delegate` would make commander default `opts.fastDelegate` to
|
|
1536
|
+
// `true` and force `delegate: true` on EVERY run that passed no flag at all
|
|
1537
|
+
// — silently overwriting a user's stored `false`. Same polarity, same trap
|
|
1538
|
+
// and same fix as `--update` / `--compaction`.
|
|
1539
|
+
.option('--fast-delegate', 'Let the fast tier take mechanical sub-steps (the default)')
|
|
1540
|
+
.option('--no-fast-delegate', 'Run every sub-step on the main model')
|
|
1494
1541
|
// BOTH FORMS, positive first, for the reason recorded seven times above —
|
|
1495
1542
|
// and this is the pair whose omission would be worst. `update.mode` is
|
|
1496
1543
|
// PERSISTED and defaults to `'auto'`, so a lone `--no-update` would make
|
|
@@ -17,6 +17,7 @@ import { getLogger } from '../logging/logger.js';
|
|
|
17
17
|
import { copyText } from '../ui/clipboard.js';
|
|
18
18
|
import { perfCommand } from './perf.js';
|
|
19
19
|
import { runCompactCommand } from '../compaction/command.js';
|
|
20
|
+
import { runContextCommand } from '../compaction/context-command.js';
|
|
20
21
|
/**
|
|
21
22
|
* `/fast status` — THE GUARANTEED REPORTING SURFACE for the tier (§4.4 / R-8).
|
|
22
23
|
*
|
|
@@ -809,6 +810,14 @@ const COMMANDS = [
|
|
|
809
810
|
description: 'Context compaction: status | on | off | threshold <n> | keep <n> | <instructions>',
|
|
810
811
|
run: (ctx) => runCompactCommand(ctx),
|
|
811
812
|
},
|
|
813
|
+
{
|
|
814
|
+
// REGISTRATION ONLY, for the reason `/compact` records one entry up: this
|
|
815
|
+
// file is past the 1000-line guideline and the report has five sections.
|
|
816
|
+
// The body is in `compaction/context-command.ts`.
|
|
817
|
+
name: 'context',
|
|
818
|
+
description: 'Show context occupancy, the window it is measured against, and session spend',
|
|
819
|
+
run: (ctx) => runContextCommand(ctx),
|
|
820
|
+
},
|
|
812
821
|
{
|
|
813
822
|
name: 'expand',
|
|
814
823
|
// DELIBERATELY LEFT TOOL-ONLY, and the asymmetry with Ctrl+O is a decision
|
|
@@ -12,6 +12,7 @@
|
|
|
12
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
13
|
import type { CliConfig } from '../config/schema.js';
|
|
14
14
|
import type { NoticeLevel } from '../agent/reducer.js';
|
|
15
|
+
import { ContextMeter } from './meter.js';
|
|
15
16
|
import type { CompactionEvent, Pressure } from './types.js';
|
|
16
17
|
export interface CompactorDeps {
|
|
17
18
|
/** Read LIVE on every call — the controller replaces the object on mutation. */
|
|
@@ -42,6 +43,24 @@ export interface CompactorDeps {
|
|
|
42
43
|
complete: (providerId: string, request: LLMRequest) => Promise<AssistantMessage>;
|
|
43
44
|
emit: (event: CompactionEvent) => void;
|
|
44
45
|
notify: (level: NoticeLevel, text: string) => void;
|
|
46
|
+
/**
|
|
47
|
+
* The ONE owner of occupancy measurement (context-usage-gauge-accuracy W1).
|
|
48
|
+
*
|
|
49
|
+
* THE COMPACTOR NO LONGER MEASURES. It used to hold `estimateOffset`,
|
|
50
|
+
* `measuredPrefixLength` and `lastPressure` itself, which locked the only
|
|
51
|
+
* measuring code in the process behind `compaction.enabled` - and a session
|
|
52
|
+
* that turned compaction OFF is exactly the one whose gauge then had a single
|
|
53
|
+
* sample per turn and nothing else. Reading a meter the controller owns makes
|
|
54
|
+
* "the trigger and the gauge read one number" a STRUCTURAL fact rather than a
|
|
55
|
+
* discipline (§3.3).
|
|
56
|
+
*
|
|
57
|
+
* OPTIONAL SO EVERY EXISTING CONSTRUCTION SITE COMPILES (R-1), and lazily
|
|
58
|
+
* replaced by a private one built from the deps already here. PRODUCTION MUST
|
|
59
|
+
* INJECT: `context-gauge-wiring.test.ts` asserts one instance per process
|
|
60
|
+
* (`controller.getContextMeter()` is the same object the wiring forwards), and
|
|
61
|
+
* two meters in one session means two answers to one question.
|
|
62
|
+
*/
|
|
63
|
+
meter?: ContextMeter;
|
|
45
64
|
/**
|
|
46
65
|
* Publish the occupancy this checkpoint measured (hardening §3.2.4 / DH-15).
|
|
47
66
|
*
|
|
@@ -96,18 +115,15 @@ export declare class Compactor implements ContextManager {
|
|
|
96
115
|
private selfDisabledReason;
|
|
97
116
|
private pendingManual;
|
|
98
117
|
private generation;
|
|
99
|
-
/** Calibration for every ESTIMATED figure (D-23). Recomputed on every turn_end. */
|
|
100
|
-
private estimateOffset;
|
|
101
|
-
/** The last pressure computed, so the status bar and `/compact status` agree. */
|
|
102
|
-
private lastPressure;
|
|
103
118
|
/**
|
|
104
|
-
* The
|
|
119
|
+
* The fallback meter, built on first use when no host injected one (R-1).
|
|
105
120
|
*
|
|
106
|
-
* `
|
|
107
|
-
*
|
|
108
|
-
*
|
|
121
|
+
* `estimateOffset`, `measuredPrefixLength` and `lastPressure` used to be
|
|
122
|
+
* FIELDS HERE. They moved to `ContextMeter` wholesale
|
|
123
|
+
* (context-usage-gauge-accuracy W1), because keeping them here is what put the
|
|
124
|
+
* only measurement in the process behind `compaction.enabled`.
|
|
109
125
|
*/
|
|
110
|
-
private
|
|
126
|
+
private ownMeter;
|
|
111
127
|
/**
|
|
112
128
|
* The messages the last splice dropped, held ONLY until the wiring collects
|
|
113
129
|
* them for the archive (W4 / RV-3).
|
|
@@ -129,21 +145,17 @@ export declare class Compactor implements ContextManager {
|
|
|
129
145
|
/** `agent_start` — the per-run cap resets, the session totals do not. */
|
|
130
146
|
onRunStart(): void;
|
|
131
147
|
/**
|
|
132
|
-
* `turn_end`
|
|
148
|
+
* `turn_end` - recompute the calibration offset (D-23).
|
|
133
149
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
onTurnEnd(usage: TokenUsage, messages: readonly Message[], systemPrompt: string): void;
|
|
138
|
-
/**
|
|
139
|
-
* Any host-side history mutation that is NOT an append (hardening §3.2.3).
|
|
150
|
+
* A THIN FORWARDER since context-usage-gauge-accuracy W1. The meter subscribes
|
|
151
|
+
* to `turn_end` itself, so BOTH PATHS MUST BE IDEMPOTENT: this one recomputes
|
|
152
|
+
* exactly what the other one did from the same inputs, which it is.
|
|
140
153
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* costs accuracy - the number degrades to round 1's - and never correctness.
|
|
154
|
+
* `messages` / `systemPrompt` stay in the signature and are ignored: the meter
|
|
155
|
+
* reads them from its own lazy deps, and every existing caller and test keeps
|
|
156
|
+
* compiling.
|
|
145
157
|
*/
|
|
146
|
-
|
|
158
|
+
onTurnEnd(usage: TokenUsage, _messages: readonly Message[], _systemPrompt: string): void;
|
|
147
159
|
/**
|
|
148
160
|
* The dropped messages, released as they are returned (W4 / RV-3).
|
|
149
161
|
*
|
|
@@ -255,15 +267,29 @@ export declare class Compactor implements ContextManager {
|
|
|
255
267
|
private noteNoProgress;
|
|
256
268
|
private finish;
|
|
257
269
|
/**
|
|
258
|
-
* The pressure the trigger and the gauge both read.
|
|
270
|
+
* The pressure the trigger and the gauge both read - MEASURED NOW.
|
|
259
271
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
272
|
+
* A FORWARDER since W1: the decision point supplies its own authoritative
|
|
273
|
+
* `lastUsage` (the probe's), and the meter owns everything else. `messages` /
|
|
274
|
+
* `systemPrompt` stay in the signature and are ignored, because the meter
|
|
275
|
+
* reads the LIVE history through its own deps - which is strictly better than
|
|
276
|
+
* a caller that could pass `[]` and get a 0 % reading (the old default here).
|
|
262
277
|
*/
|
|
263
278
|
measure(probe: Pick<CompactionProbe, 'lastUsage'> & {
|
|
264
279
|
messages?: readonly Message[];
|
|
265
280
|
systemPrompt?: string;
|
|
266
281
|
}): Pressure;
|
|
282
|
+
/**
|
|
283
|
+
* The last pressure PUBLISHED. NEVER re-measured - `lastPublished`, not
|
|
284
|
+
* `current` (I-10 / RV-3).
|
|
285
|
+
*
|
|
286
|
+
* `finish()` below reads this for `tokensBefore`, which is the sole upstream of
|
|
287
|
+
* `tokensReclaimed`, the card's "reclaimed N tokens" and `/compact status`'s
|
|
288
|
+
* session total. All three want the occupancy BEFORE the splice. Point this at
|
|
289
|
+
* `meter.current()` and the moment the meter is dirty - which after a splice it
|
|
290
|
+
* always is - `tokensBefore` becomes the POST-compaction figure, `reclaimed`
|
|
291
|
+
* collapses to 0, and the card renders that with nothing logging a fault.
|
|
292
|
+
*/
|
|
267
293
|
lastMeasured(): Pressure | null;
|
|
268
294
|
/** Written by the wiring once the engine has reported its verdict. */
|
|
269
295
|
recordApplied(tokensBefore: number, tokensAfter: number): void;
|
|
@@ -295,6 +321,16 @@ export declare class Compactor implements ContextManager {
|
|
|
295
321
|
* entire run, and the spend is on the card with the summarizer's own price.
|
|
296
322
|
*/
|
|
297
323
|
resolveSummarizer(): SummarizerChoice | null;
|
|
324
|
+
/**
|
|
325
|
+
* The meter this compactor reads, injected or private (R-1).
|
|
326
|
+
*
|
|
327
|
+
* MEMOIZED, because a fresh meter on every call would have no `estimateOffset`
|
|
328
|
+
* and no cached pressure - `lastMeasured()` would answer `null` forever and
|
|
329
|
+
* `tokensReclaimed` would sit at 0. The fallback exists so the ten existing
|
|
330
|
+
* `compaction-*.test.ts` construction sites compile unchanged; production
|
|
331
|
+
* injects, and `context-gauge-wiring.test.ts` asserts the single instance.
|
|
332
|
+
*/
|
|
333
|
+
private meter;
|
|
298
334
|
private mainRef;
|
|
299
335
|
private modelInfo;
|
|
300
336
|
private effectiveMaxOutputTokens;
|