@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/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,20 @@ were written.
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **`system/init` now lists what this build can do.** A new optional
|
|
13
|
+
`capabilities: string[]` field, carrying `["interrupt"]` for this release.
|
|
14
|
+
|
|
15
|
+
A wrapper cannot tell by observation whether an interrupt will settle
|
|
16
|
+
gracefully or kill the process outright, and guessing wrong costs the user
|
|
17
|
+
their conversation. Rather than have callers compare version numbers — which
|
|
18
|
+
breaks on forks, prereleases, and version shapes nobody anticipated — the
|
|
19
|
+
build now says so itself, in an event that already arrives on every start.
|
|
20
|
+
Absent on older builds, and absence should be read as "no", never as
|
|
21
|
+
"probably".
|
|
22
|
+
|
|
23
|
+
Additive, so `schemaVersion` does not move. Consumers that do not know the
|
|
24
|
+
field ignore it, exactly as the schema contract asks.
|
|
25
|
+
|
|
12
26
|
- **The composer understands pasting.** Until now it did not have the concept at
|
|
13
27
|
all: every byte the terminal delivered on stdin was a keystroke, and a
|
|
14
28
|
clipboard block is thousands of bytes arriving in an unpredictable number of
|
|
@@ -58,6 +72,29 @@ were written.
|
|
|
58
72
|
|
|
59
73
|
### Fixed
|
|
60
74
|
|
|
75
|
+
- **`{"type":"interrupt"}` now really does settle the run — on every platform.**
|
|
76
|
+
The stdin interrupt frame used to raise a SIGINT at our own process. On POSIX
|
|
77
|
+
that reaches the runner's terminator and the run winds down properly: it
|
|
78
|
+
breaks out of the loop, persists the session, and emits `result` with the
|
|
79
|
+
usage and cost. On Windows, Node does not deliver a self-directed SIGINT to
|
|
80
|
+
its own handlers — the process simply terminated with exit code 1, having run
|
|
81
|
+
none of it.
|
|
82
|
+
|
|
83
|
+
For anyone using `aragon exec` interactively that difference was invisible,
|
|
84
|
+
because an interrupt there is usually the last thing that happens. It is not
|
|
85
|
+
invisible to a **wrapper** that interrupts a turn and then respawns on the same
|
|
86
|
+
`--session-id` to keep the conversation going: on Windows it silently got back
|
|
87
|
+
a session missing everything since the previous save. `exec/index.ts` has
|
|
88
|
+
carried a comment about that caller for some time ("PERSIST FIRST, ANNOUNCE
|
|
89
|
+
SECOND … a caller that reads `result` and immediately respawns with the same
|
|
90
|
+
`--session-id` must not race the writer"), so the intent was there; the path
|
|
91
|
+
just could not be reached on Windows.
|
|
92
|
+
|
|
93
|
+
The frame now calls the runner directly instead of going through a signal.
|
|
94
|
+
POSIX behaviour is byte-for-byte unchanged — the same code runs, with the same
|
|
95
|
+
`stopReason`, the same exit code 130, and the same two-second window in which a
|
|
96
|
+
second signal still forces an immediate exit.
|
|
97
|
+
|
|
61
98
|
- **A pasted API key is stored correctly again.** Ink broadcasts each stdin chunk
|
|
62
99
|
to every mounted input handler, so the settings screen, the question overlay
|
|
63
100
|
and the plan-review feedback field all see what the composer sees. Without
|
package/README.md
CHANGED
|
@@ -413,6 +413,7 @@ signal ends the run (`130` SIGINT, `143` SIGTERM, `129` SIGHUP).
|
|
|
413
413
|
| `/retry [show\|on\|off\|max <n>]` | API retry: the effective ladder, the on/off switch, the retry count. Takes effect **in the running session**, children included |
|
|
414
414
|
| `/fast [on\|off\|model <id>\|provider <id>\|same\|review <n\|off>\|delegate on\|off]` | Fast model tier: status and this session's totals, the on/off switch, which model, the review cadence |
|
|
415
415
|
| `/compact [status\|on\|off\|threshold <n>\|keep <n>\|history\|show <n>\|<instructions>]` | Context compaction: occupancy and this session's totals, the on/off switch, when it fires, how much it keeps. With free text, compacts now and tells the summarizer what to pay attention to |
|
|
416
|
+
| `/context` | What the context gauge is showing: occupancy split into measured and estimated, the window and **where it came from**, how stale the measurement is, compaction's actual state, and what the session-spend readout includes |
|
|
416
417
|
| `/bg [list\|logs <id> [n]\|stop <id\|all>\|status]` | Background services: what is running, its log tail, and how to stop it. `list` is the default |
|
|
417
418
|
| `/update [status\|now\|skip\|off]` | Auto-update: what is running, what is available, where it came from, when it looks again. `now` forces a check past the throttle; `skip` declines the offered version until a newer one appears |
|
|
418
419
|
| `/theme <auto\|warm\|cool\|light>` | Switch the color theme live (persisted) |
|
|
@@ -799,7 +800,8 @@ ask — and the agent writes the finished plan as its final message in markdown.
|
|
|
799
800
|
For work with three or more distinct steps the agent keeps a **visible plan**:
|
|
800
801
|
it writes a checklist with the `todo_write` tool, marks exactly one item as in
|
|
801
802
|
progress, and ticks it off before starting the next. In full-screen mode that
|
|
802
|
-
list is rendered as a right-hand rail,
|
|
803
|
+
list is rendered as a right-hand rail, targeting 15% of the terminal width
|
|
804
|
+
(14–36 columns, including its separator and padding).
|
|
803
805
|
|
|
804
806
|
```
|
|
805
807
|
transcript ... | TODO 2/7
|
|
@@ -807,8 +809,7 @@ list is rendered as a right-hand rail, about a fifth of the terminal wide.
|
|
|
807
809
|
|
|
|
808
810
|
| v 1 Read the reducer
|
|
809
811
|
| v 2 Design the store
|
|
810
|
-
| > 3 Wiring the rail
|
|
811
|
-
| AppShell
|
|
812
|
+
| > 3 Wiring the rail...
|
|
812
813
|
| o 4 Add the panel-rows test
|
|
813
814
|
| o 5 Update the README
|
|
814
815
|
| +2 more
|
|
@@ -832,7 +833,7 @@ not also tell the model may change it — except you, saying so:
|
|
|
832
833
|
conversation has just been replaced.
|
|
833
834
|
- A finished plan is dropped at the START of your next message, so you still get
|
|
834
835
|
to see `7/7 done`. An unfinished one survives "continue" / "now do the rest",
|
|
835
|
-
and is dropped after three unrelated turns rather than holding
|
|
836
|
+
and is dropped after three unrelated turns rather than holding part of the
|
|
836
837
|
screen for the rest of the session.
|
|
837
838
|
|
|
838
839
|
### When a run ends with steps left
|
|
@@ -888,11 +889,19 @@ planning discipline and hides the column — which is what you want with a scree
|
|
|
888
889
|
reader, and what `--no-todo-panel` does for a whole session. Under `-p` the plan
|
|
889
890
|
is written to stderr as `[todo] 3/7 <step>` lines, suppressed by `--quiet`.
|
|
890
891
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
892
|
+
右栏从 76 列开始显示,正文至少保留 62 列;100 列终端使用 15 列右栏。
|
|
893
|
+
已有任务时,团队面板按实际行数占用空间,补全菜单的边框和提示也计入预算。
|
|
894
|
+
例如 100×20、团队占 8 行时,TODO 仍以 4 行紧凑视图显示。
|
|
895
|
+
|
|
896
|
+
剩余 3–5 行时保留标题、完成计数、当前任务与合并提示 `-A +B`:
|
|
897
|
+
`-9 +10` 表示上方隐藏 9 项、下方隐藏 10 项。6 行起使用常规视图;
|
|
898
|
+
所有任务各占一行,全文仍可在正文任务卡片中查看。进度条需要至少 20 列、8 行。
|
|
899
|
+
输入增长导致空间不足时团队先折叠为摘要;菜单不足 3 行时隐藏,其候选不会拦截输入。
|
|
900
|
+
|
|
901
|
+
低于 76 列、共享区域不足 3 行、打开 overlay、显式关闭面板、没有任务或
|
|
902
|
+
inline 模式时不显示右栏。低于 12 行使用原有小终端提示;扩大窗口后自动恢复。
|
|
903
|
+
inline 仍使用上文的单行任务条。`/todo status` 中的 `Panel: on` 仅表示配置开启,
|
|
904
|
+
实际可见性还取决于数据和窗口空间;可以通过该命令区分未生成计划与暂时隐藏。
|
|
896
905
|
|
|
897
906
|
## Built-in tools
|
|
898
907
|
|
|
@@ -1211,6 +1220,84 @@ it for a background advisory is how an unrequested call ends up competing with
|
|
|
1211
1220
|
you for a rate-limited provider's quota. The review's own wall-clock bound is
|
|
1212
1221
|
20 s.
|
|
1213
1222
|
|
|
1223
|
+
## The context gauge
|
|
1224
|
+
|
|
1225
|
+
The right end of the status bar answers one question: **how full is the context,
|
|
1226
|
+
out of how much.**
|
|
1227
|
+
|
|
1228
|
+
```
|
|
1229
|
+
[####----] 43% 86.0k/200.0k total 1.2M^ 48.0k v $3.21 12 tok/s 1m02s
|
|
1230
|
+
^ ^ ^
|
|
1231
|
+
| | +-- session spend, prompt side / output side
|
|
1232
|
+
| +-- occupied / window
|
|
1233
|
+
+-- the two together, as a percentage
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
The readouts drop as the terminal narrows, in that order of priority:
|
|
1237
|
+
|
|
1238
|
+
| Columns | Right cluster |
|
|
1239
|
+
|---|---|
|
|
1240
|
+
| >= 96 | `[####----] 43% 86.0k/200.0k total 1.2M^ 48.0k v $3.21` |
|
|
1241
|
+
| 72-95 | `[####----] 43% 86.0k/200.0k $3.21` |
|
|
1242
|
+
| 60-71 | `[####----] 43% $3.21` |
|
|
1243
|
+
| < 60 | `43% $3.21` |
|
|
1244
|
+
|
|
1245
|
+
**Two different markers, because there are two different things to doubt.**
|
|
1246
|
+
|
|
1247
|
+
- `~` sits on the **percentage** and means the *numerator* is partly a guess:
|
|
1248
|
+
either no completed turn has reported usage yet (right after `/resume`, or
|
|
1249
|
+
right after a compaction), or tool results have been appended since the last
|
|
1250
|
+
measurement and their size is estimated.
|
|
1251
|
+
- `?` sits on the **window** and means the *denominator* is a guess: the model is
|
|
1252
|
+
not in the built-in table, so a flat 128 000 was substituted. See
|
|
1253
|
+
`contextWindow` below.
|
|
1254
|
+
|
|
1255
|
+
**The percentage keeps moving during a turn.** Every tool result nudges it,
|
|
1256
|
+
whether or not compaction is enabled — it is not a compaction feature, and a
|
|
1257
|
+
session started with `--no-compaction` gets the same live reading. After
|
|
1258
|
+
`/resume` the gauge shows the restored session's real occupancy immediately,
|
|
1259
|
+
without sending anything.
|
|
1260
|
+
|
|
1261
|
+
### `/context`
|
|
1262
|
+
|
|
1263
|
+
The gauge is a bar and a rounded percentage; `/context` is the full answer, at
|
|
1264
|
+
any width, in any mode:
|
|
1265
|
+
|
|
1266
|
+
```
|
|
1267
|
+
Context
|
|
1268
|
+
Occupancy 43% 86.2k of 200.0k [measured 85.1k + 1.1k estimated]
|
|
1269
|
+
Window 200000 from the model table
|
|
1270
|
+
Since measured 1.1k estimated tokens appended since the last provider-reported usage
|
|
1271
|
+
Compaction on - triggers at 90% (amber at 75%), 2 this session, 118.0k reclaimed
|
|
1272
|
+
Session spend 1.2M in (incl. 940.0k cache read, 12.0k cache write) / 48.2k out, $3.21
|
|
1273
|
+
includes subagent, fast-tier and compaction spend,
|
|
1274
|
+
not just this conversation
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
Two lines are worth reading closely. **`Window`** always names where the number
|
|
1278
|
+
came from — the model table, your own `contextWindow`, or the invented
|
|
1279
|
+
placeholder — because those are indistinguishable everywhere else. And
|
|
1280
|
+
**`Compaction`** reports what is actually true of this session, including
|
|
1281
|
+
`off - not registered for this session (started with --no-compaction)`; it never
|
|
1282
|
+
promises a threshold that nothing will act on.
|
|
1283
|
+
|
|
1284
|
+
### `contextWindow`
|
|
1285
|
+
|
|
1286
|
+
When the model is not in the built-in table — a custom `baseUrl`, a self-hosted
|
|
1287
|
+
id, something released last week — the denominator is a **fabricated** 128 000
|
|
1288
|
+
and the percentage is meaningless. Correct it:
|
|
1289
|
+
|
|
1290
|
+
```bash
|
|
1291
|
+
aragon config set contextWindow 1000000 # persisted; prints "Set contextWindow = 1000000"
|
|
1292
|
+
aragon config set contextWindow auto # back to the model table / placeholder
|
|
1293
|
+
ARAGON_CONTEXT_WINDOW=1000000 aragon # one session
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
Values are clamped to `[8000, 5000000]`; `auto` (the default) is stored as
|
|
1297
|
+
`null`. Setting it removes the `?` marker, because you have asserted the number —
|
|
1298
|
+
and `/context` still shows that it came from you, which is what makes a wrong
|
|
1299
|
+
value findable.
|
|
1300
|
+
|
|
1214
1301
|
## Context compaction
|
|
1215
1302
|
|
|
1216
1303
|
A long agentic run fills the model's context window. Before this existed, the
|
|
@@ -1307,7 +1394,10 @@ Three surfaces, each answering something the others cannot:
|
|
|
1307
1394
|
- **As state** — a `compacting` chip in the status bar, and the context gauge's
|
|
1308
1395
|
colours aligned to your own thresholds. The gauge **falls immediately** after a
|
|
1309
1396
|
compaction rather than waiting for the next turn, with a `~` while the value is
|
|
1310
|
-
derived rather than measured.
|
|
1397
|
+
derived rather than measured. The colours follow your configured thresholds
|
|
1398
|
+
whenever compaction is **enabled**, even if no summarizer model currently
|
|
1399
|
+
resolves; after `/compact off` they fall back to the generic 60 / 85, because
|
|
1400
|
+
colouring by a rescue that is not coming is worse than not colouring at all.
|
|
1311
1401
|
- **As a record** — a transcript card with the before/after message and token
|
|
1312
1402
|
counts, the summarizer, the duration, and the summary itself. `Ctrl+O` expands
|
|
1313
1403
|
it.
|
|
@@ -1320,7 +1410,9 @@ self-disabled, or the ladder ran out.
|
|
|
1320
1410
|
|
|
1321
1411
|
`/compact status` is the guaranteed surface: the chip drops on a narrow terminal
|
|
1322
1412
|
and the card scrolls away, but the command answers at any width, in any mode,
|
|
1323
|
-
after the fact.
|
|
1413
|
+
after the fact. For the gauge specifically, [`/context`](#the-context-gauge) is
|
|
1414
|
+
the equivalent — and it works in a session that never registered compaction at
|
|
1415
|
+
all.
|
|
1324
1416
|
|
|
1325
1417
|
### When the RECENT turns are what does not fit
|
|
1326
1418
|
|
|
@@ -1701,6 +1793,8 @@ env / `.env` → CLI flags**.
|
|
|
1701
1793
|
reasoning blocks), `ARAGON_LIVE_TOOL_OUTPUT` (`0` keeps a running tool card to
|
|
1702
1794
|
one line),
|
|
1703
1795
|
`ARAGON_MAX_TOKENS` (a number, or `auto` / `0` for the per-model ceiling),
|
|
1796
|
+
`ARAGON_CONTEXT_WINDOW` (a number, or `auto`; the denominator the context
|
|
1797
|
+
gauge measures against),
|
|
1704
1798
|
`ARAGON_THEME`,
|
|
1705
1799
|
`ARAGON_FULLSCREEN`, `ARAGON_PLAN` (`1` starts in plan mode),
|
|
1706
1800
|
`ARAGON_TEAM` (`0` disables team subagents), `ARAGON_TEAM_MAX` (fan-out width),
|
|
@@ -1728,6 +1822,7 @@ env / `.env` → CLI flags**.
|
|
|
1728
1822
|
| Config key | Default | Meaning |
|
|
1729
1823
|
| --- | --- | --- |
|
|
1730
1824
|
| `maxTokens` | `64000` | Output token cap, clamped to `[256, 200000]`. `null` means **auto** — use each model's own ceiling, never above 64000. Absent means the default. See [Output token limits](#output-token-limits). |
|
|
1825
|
+
| `contextWindow` | `null` | The window the context gauge measures against, clamped to `[8000, 5000000]`. `null` means **auto** — the built-in model table, or a fabricated 128000 for a model it has never seen. Set it when the gauge shows `?` on the denominator. See [The context gauge](#the-context-gauge). |
|
|
1731
1826
|
| `showThinking` | `false` | Draw the reasoning the model returns. `thinkingLevel` is the effort the provider is asked to **spend**; this is whether the terminal **shows** it. Off by default: a settled turn that thought leaves one muted `thought for 12s` row in its place, so nothing is hidden silently. |
|
|
1732
1827
|
| `liveToolOutput` | `true` | Draw up to eight sanitised rows of a **running** tool's output on its card, plus a `no output for Ns` row when the child goes quiet. On by default, unlike `showThinking`: this ADDS the information a long `bash` call otherwise hides, and its cost is bounded by construction — eight rows per call, sixteen calls, whatever the command emits. Turn it off and the card is a single `running` row again, with no store allocated and no recorder attached. |
|
|
1733
1828
|
| `fullscreen` | `true` | Use the full-screen TUI (still subject to the automatic downgrades). Setting it to `false` opts out permanently; leaving it `true` is *not* a force — only `--fullscreen` / `ARAGON_FULLSCREEN=1` override the heuristics. |
|
|
@@ -1805,6 +1900,8 @@ ARAGON_MAX_TOKENS=32000 aragon # env layer, between the flag and the file
|
|
|
1805
1900
|
aragon config set maxTokens 32000 # persisted; prints "Set maxTokens = 32000"
|
|
1806
1901
|
aragon config set maxTokens auto # persisted as null
|
|
1807
1902
|
aragon config set maxTokens default # back to 64000
|
|
1903
|
+
aragon config set contextWindow 1000000 # the gauge's denominator
|
|
1904
|
+
aragon config set contextWindow auto # back to the model table
|
|
1808
1905
|
aragon config get maxTokens # "64000" | "auto"
|
|
1809
1906
|
```
|
|
1810
1907
|
|
|
@@ -18,8 +18,9 @@ import type { TeamEvent, TeamSnapshot } from '../team/types.js';
|
|
|
18
18
|
import { ProcSupervisor } from '../proc/supervisor.js';
|
|
19
19
|
import type { ProcEventListener, ServiceSnapshot } from '../proc/types.js';
|
|
20
20
|
import { type FastStatus } from '../fast/wiring.js';
|
|
21
|
-
import type { FastEventListener } from '../fast/types.js';
|
|
22
|
-
import
|
|
21
|
+
import type { FastEventListener, FastSnapshot } from '../fast/types.js';
|
|
22
|
+
import { ContextMeter, type ContextUsageListener } from '../compaction/meter.js';
|
|
23
|
+
import type { CompactionEventListener, CompactionSnapshot, ContextUsageSnapshot } from '../compaction/types.js';
|
|
23
24
|
import type { TodoEventListener, TodoSnapshot } from '../todo/types.js';
|
|
24
25
|
import { type HumanInputGate } from '../tools/human-input.js';
|
|
25
26
|
import { SkillService, type ApprovalGate } from '../skills/service.js';
|
|
@@ -178,6 +179,7 @@ export declare class AgentController {
|
|
|
178
179
|
private readonly outputListeners;
|
|
179
180
|
private readonly fast;
|
|
180
181
|
private readonly compaction;
|
|
182
|
+
private readonly contextMeter;
|
|
181
183
|
private readonly procs;
|
|
182
184
|
/**
|
|
183
185
|
* The host's notice channel, kept so `prompt()`'s totality has somewhere to
|
|
@@ -612,6 +614,41 @@ export declare class AgentController {
|
|
|
612
614
|
* `subscribeTodos` already set.
|
|
613
615
|
*/
|
|
614
616
|
subscribeFast(listener: FastEventListener): () => void;
|
|
617
|
+
/**
|
|
618
|
+
* The FIFTH thin forwarder: the current fast-tier snapshot, or `null` when
|
|
619
|
+
* this session never registered a tier at all
|
|
620
|
+
* (web-use-tier-cooperation-and-control-closure §4.2.1).
|
|
621
|
+
*
|
|
622
|
+
* `null` and "a snapshot with `live: false`" are different answers and callers
|
|
623
|
+
* depend on the difference: the first means "there is no tier here", the
|
|
624
|
+
* second means "there is one and it is not usable right now".
|
|
625
|
+
*
|
|
626
|
+
* Deliberately NOT folded into the existing `review_end` event: that shape is
|
|
627
|
+
* consumed by `App` too, and this round promised to be purely additive.
|
|
628
|
+
*/
|
|
629
|
+
fastSnapshot(): FastSnapshot | null;
|
|
630
|
+
/**
|
|
631
|
+
* Subscribe to occupancy publications. Returns an unsubscribe function.
|
|
632
|
+
*
|
|
633
|
+
* THE SOLE UPSTREAM OF `ViewState.context` (I-1). The gauge used to have two
|
|
634
|
+
* writers - `turnEnd` and a `contextTokensEstimated` dispatch fired from two
|
|
635
|
+
* places in `App` - which is the entire cause of P0-1: both wrote in the same
|
|
636
|
+
* synchronous fan-out and the second one won, restoring the pre-compaction
|
|
637
|
+
* figure one statement after the bar had correctly fallen. One writer makes
|
|
638
|
+
* that class of bug unrepresentable rather than merely fixed.
|
|
639
|
+
*/
|
|
640
|
+
subscribeContextUsage(listener: ContextUsageListener): () => void;
|
|
641
|
+
/**
|
|
642
|
+
* Occupancy right now, re-measuring if the history has moved.
|
|
643
|
+
*
|
|
644
|
+
* `/context` READS THIS AND NOT `getCompactionSnapshot().pressure` (RV-8). The
|
|
645
|
+
* latter returns `offCompactionSnapshot()`'s hardcoded zero pressure when
|
|
646
|
+
* compaction was never registered, so a report built on it would read 0 % for
|
|
647
|
+
* precisely the sessions this feature exists to fix.
|
|
648
|
+
*/
|
|
649
|
+
getContextUsage(): ContextUsageSnapshot;
|
|
650
|
+
/** The meter itself, for the wiring's single-instance assertion and tests. */
|
|
651
|
+
getContextMeter(): ContextMeter;
|
|
615
652
|
/** Whether a `ContextManager` exists at all. Fixed at construction. */
|
|
616
653
|
isCompactionRegistered(): boolean;
|
|
617
654
|
/** Whether it will actually fire. Flipped live by `/compact on|off`. */
|
package/dist/agent/controller.js
CHANGED
|
@@ -28,6 +28,7 @@ import { addSignalHook } from '../logging/install.js';
|
|
|
28
28
|
import { describeFastTierProblem, fastProviderOf, resolveFastTier } from '../fast/resolve.js';
|
|
29
29
|
import { FastWiring, offFastStatus } from '../fast/wiring.js';
|
|
30
30
|
import { CompactionWiring, offCompactionSnapshot } from '../compaction/wiring.js';
|
|
31
|
+
import { ContextMeter } from '../compaction/meter.js';
|
|
31
32
|
import { TodoStore } from '../todo/store.js';
|
|
32
33
|
import { createTodoTool } from '../todo/todo-tool.js';
|
|
33
34
|
import { buildTodoBlock } from '../todo/prompt.js';
|
|
@@ -187,6 +188,23 @@ export class AgentController {
|
|
|
187
188
|
// -----------------------------------------------------------------------
|
|
188
189
|
compaction;
|
|
189
190
|
// -----------------------------------------------------------------------
|
|
191
|
+
// Context occupancy (context-usage-gauge-accuracy §3.2 / W1)
|
|
192
|
+
//
|
|
193
|
+
// CONSTRUCTED UNCONDITIONALLY, unlike its neighbour, and that asymmetry IS the
|
|
194
|
+
// feature. `compaction` above is `null` for a session started with
|
|
195
|
+
// `--no-compaction` - and before this field existed, that took the only
|
|
196
|
+
// measuring code in the process with it: such a session's gauge had exactly
|
|
197
|
+
// one sample per turn (the provider's `turn_end` usage) and read `0 %` after a
|
|
198
|
+
// `/resume`. Occupancy is not a compaction concern; it is what compaction
|
|
199
|
+
// READS.
|
|
200
|
+
//
|
|
201
|
+
// IT COSTS A HEADLESS HOST NOTHING (I-11). `aragon exec` builds an
|
|
202
|
+
// `AgentController` too, and nothing there reads this number; the meter arms
|
|
203
|
+
// no timer while it has no subscribers, so the extra object is a few scalars
|
|
204
|
+
// and a `Set`.
|
|
205
|
+
// -----------------------------------------------------------------------
|
|
206
|
+
contextMeter;
|
|
207
|
+
// -----------------------------------------------------------------------
|
|
190
208
|
// Background services (background-service-supervision §3.3 / §3.6)
|
|
191
209
|
//
|
|
192
210
|
// CONSTRUCTED UNCONDITIONALLY, and that is the one thing about this field that
|
|
@@ -500,6 +518,28 @@ export class AgentController {
|
|
|
500
518
|
modelId: config.model,
|
|
501
519
|
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
502
520
|
};
|
|
521
|
+
// --- Context occupancy, part 1: BEFORE the Agent, UNCONDITIONALLY ------
|
|
522
|
+
//
|
|
523
|
+
// Built here for the same C-12 reason the wiring is - lazy `getMessages` /
|
|
524
|
+
// `getSystemPrompt` closures, `attach` after `new Agent` - and built for
|
|
525
|
+
// EVERY session, including one with compaction off, because that is the one
|
|
526
|
+
// whose gauge was broken (P1-4 / P0-2).
|
|
527
|
+
this.contextMeter = new ContextMeter({
|
|
528
|
+
getMessages: () => this.agent.state.messages,
|
|
529
|
+
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
530
|
+
getModelInfo: () => this.getModelInfo(),
|
|
531
|
+
// ONE PREDICATE FOR "IS THE DENOMINATOR REAL". `isPricedModel` is
|
|
532
|
+
// `modelRegistry.getModel(...) !== undefined`, and `getModelInfoFor` falls
|
|
533
|
+
// back to `buildRuntimeModel` - a 128k placeholder AND a zero cost table -
|
|
534
|
+
// for exactly the models it answers `false` for. The two are the same
|
|
535
|
+
// question about the same static table, not a coincidence.
|
|
536
|
+
isWindowKnown: () => this.isPricedModel({
|
|
537
|
+
providerId: this.config.provider,
|
|
538
|
+
modelId: this.config.model,
|
|
539
|
+
}),
|
|
540
|
+
// LIVE, so a settings-screen edit moves the denominator without a relaunch.
|
|
541
|
+
getWindowOverride: () => this.config.contextWindow,
|
|
542
|
+
});
|
|
503
543
|
// --- Context compaction, part 1: BEFORE the Agent ----------------------
|
|
504
544
|
//
|
|
505
545
|
// It needs no agent reference to EXIST — only live config, the key
|
|
@@ -517,6 +557,9 @@ export class AgentController {
|
|
|
517
557
|
getMessages: () => this.agent.state.messages,
|
|
518
558
|
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
519
559
|
notify: (level, text) => deps.notify?.(level, text),
|
|
560
|
+
// ONE METER PER PROCESS (R-1). The wiring forwards it to the compactor,
|
|
561
|
+
// so the trigger and the gauge cannot read two different numbers.
|
|
562
|
+
meter: this.contextMeter,
|
|
520
563
|
})
|
|
521
564
|
: null;
|
|
522
565
|
this.agent = new Agent({
|
|
@@ -567,6 +610,15 @@ export class AgentController {
|
|
|
567
610
|
// subscribed could not run before `new Agent(...)`, and the port has to be
|
|
568
611
|
// inside its constructor argument (C-12 / P0-2).
|
|
569
612
|
this.compaction?.attach((listener) => this.agent.subscribe(listener));
|
|
613
|
+
// --- Context occupancy, part 2: after the Agent exists -----------------
|
|
614
|
+
//
|
|
615
|
+
// ORDER RELATIVE TO THE LINE ABOVE IS DELIBERATELY IRRELEVANT (I-9 / T7).
|
|
616
|
+
// The append direction is order-free because the meter marks itself dirty
|
|
617
|
+
// and re-measures on read; the replace direction is order-free because the
|
|
618
|
+
// splice is announced from `settlePending`'s own synchronous code rather
|
|
619
|
+
// than from a listener. If a future change makes this ordering matter, the
|
|
620
|
+
// fix is at those two sites, not here.
|
|
621
|
+
this.contextMeter.attach((listener) => this.agent.subscribe(listener));
|
|
570
622
|
// --- Fast tier, part 2: after the Agent exists --------------------------
|
|
571
623
|
//
|
|
572
624
|
// `FastWiring` subscribes to the lead's event stream, so it cannot be built
|
|
@@ -1207,6 +1259,7 @@ export class AgentController {
|
|
|
1207
1259
|
this.teamRuntime?.dispose();
|
|
1208
1260
|
this.fast?.dispose();
|
|
1209
1261
|
this.compaction?.dispose();
|
|
1262
|
+
this.contextMeter.dispose();
|
|
1210
1263
|
// REAP BEFORE RELEASING THE HOOK, in that order: disposal is a teardown, and
|
|
1211
1264
|
// a service that outlives it has nothing left that could ever stop it.
|
|
1212
1265
|
this.procs.reapSync();
|
|
@@ -1337,6 +1390,13 @@ export class AgentController {
|
|
|
1337
1390
|
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1338
1391
|
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1339
1392
|
this.compaction?.onConfigChanged();
|
|
1393
|
+
// THE DENOMINATOR MOVED, NOT THE HISTORY (context-usage-gauge-accuracy §3.4).
|
|
1394
|
+
// A re-measure, deliberately NOT a reset: the measured base is still a true
|
|
1395
|
+
// statement about the messages, and throwing it away would swap a correct
|
|
1396
|
+
// numerator for a whole-history estimate on a common operation. The
|
|
1397
|
+
// numerator's calibration is one turn stale until the next `turn_end`
|
|
1398
|
+
// (RV-13), which is the trade this call names.
|
|
1399
|
+
this.contextMeter.onWindowChanged();
|
|
1340
1400
|
}
|
|
1341
1401
|
setThinkingLevel(level) {
|
|
1342
1402
|
this.config = { ...this.config, thinkingLevel: level };
|
|
@@ -1435,7 +1495,14 @@ export class AgentController {
|
|
|
1435
1495
|
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
1436
1496
|
// was CLEARED, not appended to, so the prefix length the last measurement
|
|
1437
1497
|
// covered indexes an array that no longer exists.
|
|
1498
|
+
//
|
|
1499
|
+
// BOTH LINES, AND THE SECOND IS NOT REDUNDANT (context-usage-gauge-accuracy
|
|
1500
|
+
// I-9). The wiring is `null` for a `--no-compaction` session, so it is the
|
|
1501
|
+
// meter call that guarantees a re-measure - deleting it "to de-duplicate"
|
|
1502
|
+
// reintroduces P0-2 for exactly the sessions this feature was written for. A
|
|
1503
|
+
// deep reset is idempotent, so both firing costs nothing.
|
|
1438
1504
|
this.compaction?.onHistoryReplaced();
|
|
1505
|
+
this.contextMeter.onHistoryReplaced();
|
|
1439
1506
|
// A new conversation has no loaded skills, so the "already loaded earlier
|
|
1440
1507
|
// in this conversation" hint would otherwise start lying (P2-9).
|
|
1441
1508
|
this.skills.getRegistry().clearActive();
|
|
@@ -1455,7 +1522,15 @@ export class AgentController {
|
|
|
1455
1522
|
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
1456
1523
|
// `estimateAppendedTokens` bounds-checks as well, so a MISSED site costs
|
|
1457
1524
|
// accuracy and never correctness; that belt does not make this call optional.
|
|
1525
|
+
//
|
|
1526
|
+
// THE SECOND LINE IS THE P0-2 FIX. Before it, `/resume` of a 180k-token
|
|
1527
|
+
// session left the gauge and `/compact status` reading 0 %: nothing
|
|
1528
|
+
// re-measured and nothing published, so the mount-time "empty history"
|
|
1529
|
+
// reading stood until the next completed turn. The DEEP reset also drops
|
|
1530
|
+
// `estimateOffset`, because `/resume` can change the model in the same
|
|
1531
|
+
// breath (I-8).
|
|
1458
1532
|
this.compaction?.onHistoryReplaced();
|
|
1533
|
+
this.contextMeter.onHistoryReplaced();
|
|
1459
1534
|
}
|
|
1460
1535
|
getMessages() {
|
|
1461
1536
|
return this.agent.state.messages;
|
|
@@ -1571,6 +1646,56 @@ export class AgentController {
|
|
|
1571
1646
|
return () => { };
|
|
1572
1647
|
return this.fast.subscribe(listener);
|
|
1573
1648
|
}
|
|
1649
|
+
/**
|
|
1650
|
+
* The FIFTH thin forwarder: the current fast-tier snapshot, or `null` when
|
|
1651
|
+
* this session never registered a tier at all
|
|
1652
|
+
* (web-use-tier-cooperation-and-control-closure §4.2.1).
|
|
1653
|
+
*
|
|
1654
|
+
* `null` and "a snapshot with `live: false`" are different answers and callers
|
|
1655
|
+
* depend on the difference: the first means "there is no tier here", the
|
|
1656
|
+
* second means "there is one and it is not usable right now".
|
|
1657
|
+
*
|
|
1658
|
+
* Deliberately NOT folded into the existing `review_end` event: that shape is
|
|
1659
|
+
* consumed by `App` too, and this round promised to be purely additive.
|
|
1660
|
+
*/
|
|
1661
|
+
fastSnapshot() {
|
|
1662
|
+
return this.fast?.snapshot() ?? null;
|
|
1663
|
+
}
|
|
1664
|
+
// -----------------------------------------------------------------------
|
|
1665
|
+
// Context occupancy (context-usage-gauge-accuracy §4.2) — THREE FORWARDERS
|
|
1666
|
+
//
|
|
1667
|
+
// ALL THREE ARE AVAILABLE UNCONDITIONALLY, which is the whole difference
|
|
1668
|
+
// between this block and the compaction one below it: there is no
|
|
1669
|
+
// `?? offSomething()` fallback here, because the meter always exists.
|
|
1670
|
+
// -----------------------------------------------------------------------
|
|
1671
|
+
/**
|
|
1672
|
+
* Subscribe to occupancy publications. Returns an unsubscribe function.
|
|
1673
|
+
*
|
|
1674
|
+
* THE SOLE UPSTREAM OF `ViewState.context` (I-1). The gauge used to have two
|
|
1675
|
+
* writers - `turnEnd` and a `contextTokensEstimated` dispatch fired from two
|
|
1676
|
+
* places in `App` - which is the entire cause of P0-1: both wrote in the same
|
|
1677
|
+
* synchronous fan-out and the second one won, restoring the pre-compaction
|
|
1678
|
+
* figure one statement after the bar had correctly fallen. One writer makes
|
|
1679
|
+
* that class of bug unrepresentable rather than merely fixed.
|
|
1680
|
+
*/
|
|
1681
|
+
subscribeContextUsage(listener) {
|
|
1682
|
+
return this.contextMeter.subscribe(listener);
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Occupancy right now, re-measuring if the history has moved.
|
|
1686
|
+
*
|
|
1687
|
+
* `/context` READS THIS AND NOT `getCompactionSnapshot().pressure` (RV-8). The
|
|
1688
|
+
* latter returns `offCompactionSnapshot()`'s hardcoded zero pressure when
|
|
1689
|
+
* compaction was never registered, so a report built on it would read 0 % for
|
|
1690
|
+
* precisely the sessions this feature exists to fix.
|
|
1691
|
+
*/
|
|
1692
|
+
getContextUsage() {
|
|
1693
|
+
return this.contextMeter.currentUsage();
|
|
1694
|
+
}
|
|
1695
|
+
/** The meter itself, for the wiring's single-instance assertion and tests. */
|
|
1696
|
+
getContextMeter() {
|
|
1697
|
+
return this.contextMeter;
|
|
1698
|
+
}
|
|
1574
1699
|
// -----------------------------------------------------------------------
|
|
1575
1700
|
// Context compaction (context-auto-compaction §3.2.1) — THIN FORWARDERS
|
|
1576
1701
|
// -----------------------------------------------------------------------
|
package/dist/agent/reducer.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { AgentEvent, ModelCost, ToolResult, TokenUsage } from '@aragon-agen
|
|
|
14
14
|
import type { AgentMode } from './agent-mode.js';
|
|
15
15
|
import type { DispatchOutcome, SubagentRun, SubagentSpec, TeamSnapshot } from '../team/types.js';
|
|
16
16
|
import type { FastReview, FastSnapshot } from '../fast/types.js';
|
|
17
|
-
import type { CompactionMode, CompactionRecord, CompactionSnapshot, CompactionUiTrigger } from '../compaction/types.js';
|
|
17
|
+
import type { CompactionMode, CompactionRecord, CompactionSnapshot, CompactionUiTrigger, ContextUsageSnapshot } from '../compaction/types.js';
|
|
18
18
|
import type { TodoItem, TodoSnapshot } from '../todo/types.js';
|
|
19
19
|
import type { ServiceSnapshot, ServiceStatus } from '../proc/types.js';
|
|
20
20
|
import type { RetryPhase } from './retry-view.js';
|
|
@@ -329,6 +329,16 @@ export type Entry = {
|
|
|
329
329
|
export interface UsageTotal {
|
|
330
330
|
inputTokens: number;
|
|
331
331
|
outputTokens: number;
|
|
332
|
+
/**
|
|
333
|
+
* Cache read hits (context-usage-gauge-accuracy §3.6 / P1-3).
|
|
334
|
+
*
|
|
335
|
+
* `occupiedTokens` counts them and `computeCost` PRICES them, so a session
|
|
336
|
+
* total that omits them makes the status bar's `^` read lower than the `$`
|
|
337
|
+
* next to it - on the same row, from the same turns. That was three units on
|
|
338
|
+
* one line, and this field plus its sibling are what collapse them to one.
|
|
339
|
+
*/
|
|
340
|
+
cacheReadTokens: number;
|
|
341
|
+
cacheWriteTokens: number;
|
|
332
342
|
costUsd: number;
|
|
333
343
|
}
|
|
334
344
|
/**
|
|
@@ -349,8 +359,20 @@ export interface ViewState {
|
|
|
349
359
|
entries: Entry[];
|
|
350
360
|
status: 'idle' | 'running';
|
|
351
361
|
usageTotal: UsageTotal;
|
|
352
|
-
/**
|
|
353
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Context occupancy, as published by `AgentController`'s `ContextMeter`.
|
|
364
|
+
*
|
|
365
|
+
* ITS ONLY WRITER IS `case 'contextUsage'` (I-1), and that is the structural
|
|
366
|
+
* half of the P0-1 fix. It replaced `contextTokens` + `contextTokensEstimated`,
|
|
367
|
+
* which between them had THREE writing branches (`turnEnd`,
|
|
368
|
+
* `contextTokensEstimated`, `resetConversation`) fed by two competing
|
|
369
|
+
* upstreams. Two writers in one synchronous fan-out means the later one wins,
|
|
370
|
+
* and the later one was the stale `snapshot` carrying the PRE-compaction
|
|
371
|
+
* figure - so the bar fell and bounced straight back, every time, with nothing
|
|
372
|
+
* logging a fault. `context-one-writer.test.ts` scans this file to keep it
|
|
373
|
+
* that way.
|
|
374
|
+
*/
|
|
375
|
+
context: ContextUsageSnapshot;
|
|
354
376
|
overlay: Overlay;
|
|
355
377
|
thinkingVisible: boolean;
|
|
356
378
|
/** Ephemeral acks; auto-dismissed by App (spec §3.9). */
|
|
@@ -408,16 +430,6 @@ export interface ViewState {
|
|
|
408
430
|
* totals. `null` is what leaves an ordinary session's status bar unchanged.
|
|
409
431
|
*/
|
|
410
432
|
compaction: CompactionSnapshot | null;
|
|
411
|
-
/**
|
|
412
|
-
* Whether `contextTokens` is currently a DERIVED figure rather than a measured
|
|
413
|
-
* one (§6.2 / P1-11).
|
|
414
|
-
*
|
|
415
|
-
* Set by `compactionEnd`, cleared by the next `turnEnd`. The status bar widens
|
|
416
|
-
* its leading `~` to cover it: today `~` means "the window is a guess", and
|
|
417
|
-
* this makes it also mean "the occupancy is a guess". Honesty about a guess is
|
|
418
|
-
* cheaper than a support thread about a wrong percentage.
|
|
419
|
-
*/
|
|
420
|
-
contextTokensEstimated: boolean;
|
|
421
433
|
seq: number;
|
|
422
434
|
/** The `kind: 'team'` entry the live dispatch is writing into. */
|
|
423
435
|
teamEntryId?: string;
|
|
@@ -648,15 +660,17 @@ export type ViewAction = {
|
|
|
648
660
|
snapshot: CompactionSnapshot;
|
|
649
661
|
}
|
|
650
662
|
/**
|
|
651
|
-
*
|
|
663
|
+
* The occupancy reading, from `AgentController.subscribeContextUsage`.
|
|
652
664
|
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
665
|
+
* THE ONLY ACTION THAT WRITES `state.context`, AND IT IS DISPATCHED FROM
|
|
666
|
+
* EXACTLY ONE PLACE (I-1 / T11). It replaced `contextTokensEstimated`, which
|
|
667
|
+
* `App` fired from two branches of the compaction stream while `turnEnd` wrote
|
|
668
|
+
* the same field from the agent stream - three writers, two upstreams, one
|
|
669
|
+
* number, and the P0-1 bounce as the result.
|
|
656
670
|
*/
|
|
657
671
|
| {
|
|
658
|
-
type: '
|
|
659
|
-
|
|
672
|
+
type: 'contextUsage';
|
|
673
|
+
snapshot: ContextUsageSnapshot;
|
|
660
674
|
} | {
|
|
661
675
|
type: 'serviceStart';
|
|
662
676
|
service: ServiceSnapshot;
|