@aragon-agent/cli 0.6.2 → 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 +202 -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 +224 -5
- 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 +28 -1
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/runner.js +42 -0
- 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/persist.js +19 -0
- 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 +294 -13
- package/dist/ui/Composer.d.ts +13 -1
- package/dist/ui/Composer.js +2 -2
- 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 +6 -1
- 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 +27 -1
- package/dist/ui/use-startup-notices.js +43 -11
- package/dist/ui/use-wheel-routing.js +8 -0
- package/package.json +4 -2
package/dist/agent/controller.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* from the event stream by the reducer (TUI) and the headless writer.
|
|
10
10
|
*/
|
|
11
11
|
import { Agent, initProviders, ModelRegistry, } from '@aragon-agent/core';
|
|
12
|
-
import { clampFastConfig, clampRetryConfig, clampTeamConfig, clampTodoConfig, isAdapterProvider, toRetryPolicy, } from '../config/schema.js';
|
|
12
|
+
import { clampCompactionConfig, clampFastConfig, clampRetryConfig, clampTeamConfig, clampTodoConfig, isAdapterProvider, toRetryPolicy, } from '../config/schema.js';
|
|
13
13
|
import { makeGetApiKey } from '../config/load.js';
|
|
14
14
|
import { updatePersistedConfig } from '../config/store.js';
|
|
15
15
|
import { createBuiltinTools } from '../tools/index.js';
|
|
@@ -22,6 +22,7 @@ import { buildTeamBlock } from '../team/prompt.js';
|
|
|
22
22
|
import { buildFastBlock } from '../fast/prompt.js';
|
|
23
23
|
import { describeFastTierProblem, fastProviderOf, resolveFastTier } from '../fast/resolve.js';
|
|
24
24
|
import { FastWiring, offFastStatus } from '../fast/wiring.js';
|
|
25
|
+
import { CompactionWiring, offCompactionSnapshot } from '../compaction/wiring.js';
|
|
25
26
|
import { TodoStore } from '../todo/store.js';
|
|
26
27
|
import { createTodoTool } from '../todo/todo-tool.js';
|
|
27
28
|
import { buildTodoBlock } from '../todo/prompt.js';
|
|
@@ -153,6 +154,24 @@ export class AgentController {
|
|
|
153
154
|
// property, and `<fast_tier>` is never spliced (I-2).
|
|
154
155
|
// -----------------------------------------------------------------------
|
|
155
156
|
fast;
|
|
157
|
+
// -----------------------------------------------------------------------
|
|
158
|
+
// Context compaction (context-auto-compaction §3.2.1)
|
|
159
|
+
//
|
|
160
|
+
// ONE FIELD AND THIN FORWARDERS, the `fast` shape one feature later, and for
|
|
161
|
+
// the same budget reason (C-16): every piece of state — the guards, the
|
|
162
|
+
// ladder, the manual queue, the session totals — lives in
|
|
163
|
+
// `compaction/wiring.ts` and `compaction/compactor.ts`.
|
|
164
|
+
//
|
|
165
|
+
// `null` when `compaction.enabled` was false at construction, which is the
|
|
166
|
+
// byte-identity branch: no manager is passed to `new Agent({...})`, nothing is
|
|
167
|
+
// subscribed, no registry is allocated, and the engine loop's checkpoint is one
|
|
168
|
+
// `if` against a field that is genuinely absent (AC-1).
|
|
169
|
+
//
|
|
170
|
+
// CONSTRUCTED ABOVE `new Agent({...})` AND ATTACHED BELOW IT (C-12 / P0-2).
|
|
171
|
+
// The port has to be inside the Agent's constructor argument and the wiring has
|
|
172
|
+
// to subscribe to the Agent, and this class cannot do both in one step.
|
|
173
|
+
// -----------------------------------------------------------------------
|
|
174
|
+
compaction;
|
|
156
175
|
/**
|
|
157
176
|
* Set SYNCHRONOUSLY by `abort()` before `agent.abort()` (§3.5.4a guard 1).
|
|
158
177
|
* Cleared at the next `agent_start`.
|
|
@@ -217,6 +236,11 @@ export class AgentController {
|
|
|
217
236
|
this.teamRuntime = this.teamRegistered
|
|
218
237
|
? new TeamRuntime({
|
|
219
238
|
getConfig: () => this.config,
|
|
239
|
+
// Read LIVE through the wiring, exactly as `resolveTier` below is: the
|
|
240
|
+
// closure is only ever called from inside a dispatch, and
|
|
241
|
+
// `childFactory()` re-reads `compaction.subagents` on every call so a
|
|
242
|
+
// settings-screen edit reaches the next dispatch.
|
|
243
|
+
contextManagerFor: (req) => this.compaction?.childFactory()?.(req),
|
|
220
244
|
// Read LIVE through the wiring, which does not exist yet — the closure
|
|
221
245
|
// is only ever called from inside a dispatch, long after construction.
|
|
222
246
|
resolveTier: (tier) => this.fast
|
|
@@ -379,6 +403,25 @@ export class AgentController {
|
|
|
379
403
|
modelId: config.model,
|
|
380
404
|
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
381
405
|
};
|
|
406
|
+
// --- Context compaction, part 1: BEFORE the Agent ----------------------
|
|
407
|
+
//
|
|
408
|
+
// It needs no agent reference to EXIST — only live config, the key
|
|
409
|
+
// resolvers, the model-info lookup and a notifier — which is exactly what
|
|
410
|
+
// makes the C-12 split work. `getMessages` / `getSystemPrompt` are LAZY for
|
|
411
|
+
// the same reason: `this.agent` is assigned on the very next statement, and
|
|
412
|
+
// they are only ever called from inside an event handler.
|
|
413
|
+
this.compaction = config.compaction.enabled
|
|
414
|
+
? new CompactionWiring({
|
|
415
|
+
getConfig: () => this.config,
|
|
416
|
+
hasKey: (id) => this.hasApiKey(id),
|
|
417
|
+
getApiKey: (id) => this.resolveKey(id),
|
|
418
|
+
getModelInfoFor: (ref) => this.getModelInfoFor(ref),
|
|
419
|
+
isPricedModel: (ref) => this.isPricedModel(ref),
|
|
420
|
+
getMessages: () => this.agent.state.messages,
|
|
421
|
+
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
422
|
+
notify: (level, text) => deps.notify?.(level, text),
|
|
423
|
+
})
|
|
424
|
+
: null;
|
|
382
425
|
this.agent = new Agent({
|
|
383
426
|
systemPrompt: this.composeSystemPrompt(),
|
|
384
427
|
model,
|
|
@@ -389,6 +432,13 @@ export class AgentController {
|
|
|
389
432
|
// without rebuilding the Agent — the closure reads the current config.
|
|
390
433
|
getApiKey: (id) => this.resolveKey(id),
|
|
391
434
|
maxTokens: config.maxTokens,
|
|
435
|
+
// SPREAD CONDITIONALLY, never `contextManager: this.compaction?.manager()`.
|
|
436
|
+
// With the feature off the options bag has no such key, so the loop's gate
|
|
437
|
+
// tests a field that is genuinely `undefined` rather than a property
|
|
438
|
+
// holding `undefined` — which is what makes AC-1's byte-identity claim
|
|
439
|
+
// provable by construction rather than by inspection. `createBuiltinTools`
|
|
440
|
+
// above spreads `permission` for the identical reason (P1-2 / AC-28).
|
|
441
|
+
...(this.compaction ? { contextManager: this.compaction.manager() } : {}),
|
|
392
442
|
timeouts: {
|
|
393
443
|
// Timeout invariant (R1): idle ≥ tool so long tool runs are not killed.
|
|
394
444
|
toolTimeout: config.toolTimeoutMs,
|
|
@@ -408,6 +458,18 @@ export class AgentController {
|
|
|
408
458
|
},
|
|
409
459
|
},
|
|
410
460
|
});
|
|
461
|
+
// --- Context compaction, part 2: AFTER the Agent exists -----------------
|
|
462
|
+
//
|
|
463
|
+
// `attach` is what lets the wiring see `compaction_end` — the ENGINE's
|
|
464
|
+
// `applied` verdict, which the compactor cannot know because validation
|
|
465
|
+
// happens after `compact()` returns — and `agent_start`, which resets the
|
|
466
|
+
// per-run cap. It is idempotent, and `dispose()` unsubscribes.
|
|
467
|
+
//
|
|
468
|
+
// A SEPARATE CALL RATHER THAN A `subscribe` DEP LIKE `FastWiring`'s, and that
|
|
469
|
+
// is the constraint that makes the whole split work: a constructor that
|
|
470
|
+
// subscribed could not run before `new Agent(...)`, and the port has to be
|
|
471
|
+
// inside its constructor argument (C-12 / P0-2).
|
|
472
|
+
this.compaction?.attach((listener) => this.agent.subscribe(listener));
|
|
411
473
|
// --- Fast tier, part 2: after the Agent exists --------------------------
|
|
412
474
|
//
|
|
413
475
|
// `FastWiring` subscribes to the lead's event stream, so it cannot be built
|
|
@@ -670,6 +732,11 @@ export class AgentController {
|
|
|
670
732
|
// nothing ever clears (RV-1).
|
|
671
733
|
this.abortRequested = true;
|
|
672
734
|
this.fast?.abort();
|
|
735
|
+
// The engine also races `ctx.signal` inside `runCompaction`, so this is the
|
|
736
|
+
// same DELIBERATE REDUNDANCY the `teamRuntime` line below records: it makes
|
|
737
|
+
// "Esc cancels an in-flight summarization" a property of this class rather
|
|
738
|
+
// than a consequence of the compactor honouring a signal three modules away.
|
|
739
|
+
this.compaction?.abort();
|
|
673
740
|
this.agent.abort();
|
|
674
741
|
// DELIBERATE REDUNDANCY (§3.8). Aborting the lead already aborts the
|
|
675
742
|
// in-flight `task` call's `ctx.signal`, which the runtime listens to — that
|
|
@@ -870,6 +937,7 @@ export class AgentController {
|
|
|
870
937
|
dispose() {
|
|
871
938
|
this.teamRuntime?.dispose();
|
|
872
939
|
this.fast?.dispose();
|
|
940
|
+
this.compaction?.dispose();
|
|
873
941
|
}
|
|
874
942
|
// -----------------------------------------------------------------------
|
|
875
943
|
// Todo planning (todo-plan-execution §3.6a)
|
|
@@ -991,6 +1059,10 @@ export class AgentController {
|
|
|
991
1059
|
// another vendor's gateway — and `<fast_tier>` would go on naming the model
|
|
992
1060
|
// it used to resolve to.
|
|
993
1061
|
this.fast?.onConfigChanged();
|
|
1062
|
+
// The summarizer resolves from LIVE config too — it may BE the fast tier —
|
|
1063
|
+
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1064
|
+
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1065
|
+
this.compaction?.onConfigChanged();
|
|
994
1066
|
}
|
|
995
1067
|
setThinkingLevel(level) {
|
|
996
1068
|
this.config = { ...this.config, thinkingLevel: level };
|
|
@@ -1010,6 +1082,10 @@ export class AgentController {
|
|
|
1010
1082
|
// Rule 5 of §3.2 reads `apiKeys`, so a key edit is a tier event: this is the
|
|
1011
1083
|
// path that turns `no_key` back into a working tier without a relaunch.
|
|
1012
1084
|
this.fast?.onConfigChanged();
|
|
1085
|
+
// The summarizer resolves from LIVE config too — it may BE the fast tier —
|
|
1086
|
+
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1087
|
+
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1088
|
+
this.compaction?.onConfigChanged();
|
|
1013
1089
|
}
|
|
1014
1090
|
setCwd(cwd) {
|
|
1015
1091
|
this.cwd = cwd;
|
|
@@ -1082,6 +1158,10 @@ export class AgentController {
|
|
|
1082
1158
|
}
|
|
1083
1159
|
clearMessages() {
|
|
1084
1160
|
this.agent.clearMessages();
|
|
1161
|
+
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
1162
|
+
// was CLEARED, not appended to, so the prefix length the last measurement
|
|
1163
|
+
// covered indexes an array that no longer exists.
|
|
1164
|
+
this.compaction?.onHistoryReplaced();
|
|
1085
1165
|
// A new conversation has no loaded skills, so the "already loaded earlier
|
|
1086
1166
|
// in this conversation" hint would otherwise start lying (P2-9).
|
|
1087
1167
|
this.skills.getRegistry().clearActive();
|
|
@@ -1098,6 +1178,10 @@ export class AgentController {
|
|
|
1098
1178
|
}
|
|
1099
1179
|
replaceMessages(messages) {
|
|
1100
1180
|
this.agent.replaceMessages(messages);
|
|
1181
|
+
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
1182
|
+
// `estimateAppendedTokens` bounds-checks as well, so a MISSED site costs
|
|
1183
|
+
// accuracy and never correctness; that belt does not make this call optional.
|
|
1184
|
+
this.compaction?.onHistoryReplaced();
|
|
1101
1185
|
}
|
|
1102
1186
|
getMessages() {
|
|
1103
1187
|
return this.agent.state.messages;
|
|
@@ -1191,6 +1275,10 @@ export class AgentController {
|
|
|
1191
1275
|
// was never registered) nothing has to be rebuilt, because no block is
|
|
1192
1276
|
// spliced either way.
|
|
1193
1277
|
this.fast?.onConfigChanged();
|
|
1278
|
+
// The summarizer resolves from LIVE config too — it may BE the fast tier —
|
|
1279
|
+
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1280
|
+
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1281
|
+
this.compaction?.onConfigChanged();
|
|
1194
1282
|
return fast;
|
|
1195
1283
|
}
|
|
1196
1284
|
/** Everything `/fast status`, the settings line and the chip read. */
|
|
@@ -1209,6 +1297,117 @@ export class AgentController {
|
|
|
1209
1297
|
return () => { };
|
|
1210
1298
|
return this.fast.subscribe(listener);
|
|
1211
1299
|
}
|
|
1300
|
+
// -----------------------------------------------------------------------
|
|
1301
|
+
// Context compaction (context-auto-compaction §3.2.1) — THIN FORWARDERS
|
|
1302
|
+
// -----------------------------------------------------------------------
|
|
1303
|
+
/** Whether a `ContextManager` exists at all. Fixed at construction. */
|
|
1304
|
+
isCompactionRegistered() {
|
|
1305
|
+
return this.compaction !== null;
|
|
1306
|
+
}
|
|
1307
|
+
/** Whether it will actually fire. Flipped live by `/compact on|off`. */
|
|
1308
|
+
isCompactionEnabled() {
|
|
1309
|
+
return this.compaction?.isEnabled() === true;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Flip the live switch.
|
|
1313
|
+
*
|
|
1314
|
+
* A NO-OP WHEN NOTHING IS REGISTERED, and `/compact` reports that honestly by
|
|
1315
|
+
* checking `isCompactionRegistered()` first — the `/fast on` and `/team on`
|
|
1316
|
+
* shape. `enabled` decides whether the wiring EXISTS, which happens once at
|
|
1317
|
+
* construction and cannot be undone: a session that started with no manager
|
|
1318
|
+
* would have to be given one mid-flight, and the `Agent` has no setter for it
|
|
1319
|
+
* on purpose (§3.2).
|
|
1320
|
+
*/
|
|
1321
|
+
setCompactionEnabled(enabled) {
|
|
1322
|
+
this.compaction?.setEnabled(enabled);
|
|
1323
|
+
}
|
|
1324
|
+
getCompactionConfig() {
|
|
1325
|
+
return this.config.compaction;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Adopt a new compaction config for this session.
|
|
1329
|
+
*
|
|
1330
|
+
* THE `onConfigChanged` CALL IS NOT OPTIONAL, for the reason `setFastConfig`
|
|
1331
|
+
* records: a persist without it reports success and changes nothing visible
|
|
1332
|
+
* until relaunch, because the chip and `/compact status` read a snapshot.
|
|
1333
|
+
*/
|
|
1334
|
+
setCompactionConfig(patch) {
|
|
1335
|
+
const compaction = clampCompactionConfig({ ...this.config.compaction, ...patch });
|
|
1336
|
+
this.config = { ...this.config, compaction };
|
|
1337
|
+
this.compaction?.onConfigChanged();
|
|
1338
|
+
return compaction;
|
|
1339
|
+
}
|
|
1340
|
+
/** Everything `/compact status`, the settings row and the chip read. */
|
|
1341
|
+
getCompactionSnapshot() {
|
|
1342
|
+
return this.compaction ? this.compaction.snapshot() : offCompactionSnapshot();
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* The `ModelRef` the next compaction would summarize with, or `null`.
|
|
1346
|
+
*
|
|
1347
|
+
* IT IS CARRIED, NEVER RE-DERIVED, and that is the whole reason it exists. The
|
|
1348
|
+
* summarizer is the fast tier when `useFastTier` is on AND the tier resolves,
|
|
1349
|
+
* and the session's own model otherwise — a combination no caller can
|
|
1350
|
+
* reconstruct from `CliConfig` without re-implementing `resolveFastTier`, and
|
|
1351
|
+
* one that a plausible guess (`fast.enabled ? fast.provider : provider`) gets
|
|
1352
|
+
* WRONG whenever `fast.enabled` is true and `compaction.useFastTier` is false.
|
|
1353
|
+
* Pricing the spend against the wrong provider looks up a model the static
|
|
1354
|
+
* table has never seen, which prices it at `$0.00` — the C-11 / RV-4 lie, on
|
|
1355
|
+
* the one feature that spends money without being asked (AC-15).
|
|
1356
|
+
*/
|
|
1357
|
+
getCompactionSummarizerRef() {
|
|
1358
|
+
return this.compaction?.summarizerRef() ?? null;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* This run's archive key, or `null` when compaction is unregistered
|
|
1362
|
+
* (context-auto-compaction-hardening §3.5 / W4).
|
|
1363
|
+
*
|
|
1364
|
+
* `/compact history` and `/compact show` scope themselves to it, so a second
|
|
1365
|
+
* `aragon` in another terminal is invisible here rather than interleaved with
|
|
1366
|
+
* this one's compactions.
|
|
1367
|
+
*/
|
|
1368
|
+
getCompactionRunId() {
|
|
1369
|
+
return this.compaction?.archiveRunId() ?? null;
|
|
1370
|
+
}
|
|
1371
|
+
/** Ask for a compaction at the next turn boundary (§4.4 / D-17). */
|
|
1372
|
+
queueCompaction(instructions) {
|
|
1373
|
+
this.compaction?.queueManual(instructions);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* The IDLE `/compact` path (§4.4 / D-25).
|
|
1377
|
+
*
|
|
1378
|
+
* IT VALIDATES BEFORE IT SPLICES. `CompactionWiring.compactNow` runs
|
|
1379
|
+
* `validateHistory` itself and refuses on failure, because with no loop running
|
|
1380
|
+
* the engine's own gate is unreachable and D-4 declares that gate
|
|
1381
|
+
* unbypassable. `replaceMessages` validates NOTHING.
|
|
1382
|
+
*/
|
|
1383
|
+
async compactNow(instructions) {
|
|
1384
|
+
if (!this.compaction)
|
|
1385
|
+
return { ok: false, reason: 'not_registered' };
|
|
1386
|
+
const controller = new AbortController();
|
|
1387
|
+
const outcome = await this.compaction.compactNow({
|
|
1388
|
+
messages: this.agent.state.messages,
|
|
1389
|
+
systemPrompt: this.agent.state.systemPrompt,
|
|
1390
|
+
model: this.agent.state.model,
|
|
1391
|
+
signal: controller.signal,
|
|
1392
|
+
...(instructions ? { instructions } : {}),
|
|
1393
|
+
});
|
|
1394
|
+
if (!outcome.ok)
|
|
1395
|
+
return { ok: false, reason: outcome.reason };
|
|
1396
|
+
this.agent.replaceMessages(outcome.messages);
|
|
1397
|
+
return { ok: true };
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Subscribe to the CLI-local compaction event stream.
|
|
1401
|
+
*
|
|
1402
|
+
* Returns a no-op unsubscribe when compaction was off at construction, so
|
|
1403
|
+
* every caller can subscribe unconditionally — the contract `subscribeTeam`,
|
|
1404
|
+
* `subscribeTodos` and `subscribeFast` already set.
|
|
1405
|
+
*/
|
|
1406
|
+
subscribeCompaction(listener) {
|
|
1407
|
+
if (!this.compaction)
|
|
1408
|
+
return () => { };
|
|
1409
|
+
return this.compaction.subscribe(listener);
|
|
1410
|
+
}
|
|
1212
1411
|
/** List usable tools (name + description) for the /tools command. */
|
|
1213
1412
|
listTools() {
|
|
1214
1413
|
return this.tools;
|
package/dist/agent/headless.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { PreflightResult } from './controller.js';
|
|
|
9
9
|
import type { TeamEvent } from '../team/types.js';
|
|
10
10
|
import type { TodoEvent, TodoSnapshot } from '../todo/types.js';
|
|
11
11
|
import type { FastEvent } from '../fast/types.js';
|
|
12
|
+
import type { CompactionEvent } from '../compaction/types.js';
|
|
12
13
|
import { type FollowThroughMode } from '../todo/follow-through.js';
|
|
13
14
|
/** The minimal controller surface headless mode needs (real or stubbed). */
|
|
14
15
|
export interface HeadlessController {
|
|
@@ -43,6 +44,13 @@ export interface HeadlessController {
|
|
|
43
44
|
* byte-identical to today's.
|
|
44
45
|
*/
|
|
45
46
|
subscribeFast?(listener: (event: FastEvent) => void): () => void;
|
|
47
|
+
/**
|
|
48
|
+
* The CLI-local compaction stream — OPTIONAL, for the identical reason the
|
|
49
|
+
* three above are (context-auto-compaction §4.7). A controller with no
|
|
50
|
+
* `ContextManager` has no compaction events, and `runHeadless` calls it with
|
|
51
|
+
* `?.`, which keeps a `--no-compaction` headless run byte-identical to today's.
|
|
52
|
+
*/
|
|
53
|
+
subscribeCompaction?(listener: (event: CompactionEvent) => void): () => void;
|
|
46
54
|
/**
|
|
47
55
|
* The current plan — OPTIONAL, for the identical reason the two subscriptions
|
|
48
56
|
* above are (todo-plan-followthrough §3.6 rule 2). ABSENT MEANS THE
|
package/dist/agent/headless.js
CHANGED
|
@@ -167,6 +167,52 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
167
167
|
total.inputTokens += event.usage.inputTokens;
|
|
168
168
|
total.outputTokens += event.usage.outputTokens;
|
|
169
169
|
});
|
|
170
|
+
// Context compaction (context-auto-compaction §4.7).
|
|
171
|
+
//
|
|
172
|
+
// TWO STDERR LINES, AND UNLIKE THE FAST TIER THEY ARE WRITTEN (D-12's
|
|
173
|
+
// reasoning does not carry over). A review is an unrequested second opinion
|
|
174
|
+
// that changes nothing; a compaction PERMANENTLY DROPS MESSAGES from the
|
|
175
|
+
// conversation, and a `-p` run whose middle third vanished with no line
|
|
176
|
+
// anywhere saying so is not a compact run, it is an unexplained one — the same
|
|
177
|
+
// argument the retry lines above make for a three-minute pause.
|
|
178
|
+
//
|
|
179
|
+
// stderr, never stdout: stdout is the machine-readable answer and has to stay
|
|
180
|
+
// parseable by `aragon -p | jq`. Not `--quiet`-gated, for the reason the retry
|
|
181
|
+
// line is not.
|
|
182
|
+
//
|
|
183
|
+
// The summary text is NOT written (D-18): it can be tens of thousands of
|
|
184
|
+
// characters, and no consumer of `-p` asked for it.
|
|
185
|
+
const unsubscribeCompaction = controller.subscribeCompaction?.((event) => {
|
|
186
|
+
if (event.type === 'usage') {
|
|
187
|
+
total.inputTokens += event.usage.inputTokens;
|
|
188
|
+
total.outputTokens += event.usage.outputTokens;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (event.type !== 'compaction_end')
|
|
192
|
+
return;
|
|
193
|
+
const r = event.record;
|
|
194
|
+
if (!r.applied) {
|
|
195
|
+
err.write(`\n[compaction] skipped: ${r.reason ?? 'unknown'}\n`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const secs = (r.durationMs / 1000).toFixed(1);
|
|
199
|
+
const how = r.mode === 'truncated'
|
|
200
|
+
? ' WITHOUT a summary (summarize failed)'
|
|
201
|
+
: r.mode === 'relieved'
|
|
202
|
+
? ' by clipping the retained turns (nothing could be dropped)'
|
|
203
|
+
: '';
|
|
204
|
+
err.write(`\n[compaction] ${formatTokens(r.tokensBefore)} -> ${formatTokens(r.tokensAfter)} tokens, ` +
|
|
205
|
+
`${r.messagesBefore} -> ${r.messagesAfter} messages${how} (${secs}s)\n`);
|
|
206
|
+
// ONE MORE LINE, AND ONLY WHEN RELIEF FIRED (§4.5). A clip inside the turns
|
|
207
|
+
// the run is about to continue from is a real data loss, and a `-p` run whose
|
|
208
|
+
// recent tool output was silently shortened is an unexplained run - the same
|
|
209
|
+
// argument the compaction lines above make for a vanished middle third.
|
|
210
|
+
if (r.tailRelief) {
|
|
211
|
+
err.write(`[compaction] clipped ${r.tailRelief.messages} tool ` +
|
|
212
|
+
`${r.tailRelief.messages === 1 ? 'result' : 'results'} in the retained turns ` +
|
|
213
|
+
`(${formatTokens(r.tailRelief.charsRemoved)} chars)\n`);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
170
216
|
// --- The bounded continuation loop (todo-plan-followthrough §3.6). --------
|
|
171
217
|
//
|
|
172
218
|
// THE THREE `unsubscribe` CALLS STAY OUTSIDE IT (rule 4). The loop wraps the
|
|
@@ -227,6 +273,7 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
227
273
|
unsubscribeTeam?.();
|
|
228
274
|
unsubscribeTodos?.();
|
|
229
275
|
unsubscribeFast?.();
|
|
276
|
+
unsubscribeCompaction?.();
|
|
230
277
|
}
|
|
231
278
|
// Ensure a trailing newline so piped output is well-formed.
|
|
232
279
|
if (wroteText)
|
package/dist/agent/reducer.d.ts
CHANGED
|
@@ -14,6 +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
18
|
import type { TodoItem, TodoSnapshot } from '../todo/types.js';
|
|
18
19
|
import type { RetryPhase } from './retry-view.js';
|
|
19
20
|
import type { FilePatch } from '../tools/patch.js';
|
|
@@ -222,6 +223,57 @@ export type Entry = {
|
|
|
222
223
|
turn: number;
|
|
223
224
|
durationMs?: number;
|
|
224
225
|
live: boolean;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* One context compaction (context-auto-compaction §5.5). ONE CARD PER
|
|
229
|
+
* COMPACTION, rewritten once when the call returns — like the fast card, and
|
|
230
|
+
* unlike the todo card's per-turn checklist.
|
|
231
|
+
*
|
|
232
|
+
* JSON-SERIALIZABLE by construction, so `/save` and `/resume` need no format
|
|
233
|
+
* change.
|
|
234
|
+
*
|
|
235
|
+
* `live: true` is normalized away ON LOAD, in `session/persist.ts`, next to
|
|
236
|
+
* `team.active`, `todo.live`, the retry card's phase and `fast.live`, and for
|
|
237
|
+
* the identical reason (C-8): `Transcript`'s settled boundary is MONOTONIC, so
|
|
238
|
+
* an entry that never settles never reaches `<Static>` and is re-rendered on
|
|
239
|
+
* every frame for the rest of the session.
|
|
240
|
+
*
|
|
241
|
+
* EVERY STRING ON THIS CARD DESCRIBES THE HISTORY, NEVER THE SCREEN (P2-9).
|
|
242
|
+
* `/clear` empties the transcript view but not `messages`, so a compaction can
|
|
243
|
+
* legitimately fire against a visually empty transcript and report
|
|
244
|
+
* "112 -> 9 messages" with nine visible rows.
|
|
245
|
+
*/
|
|
246
|
+
| {
|
|
247
|
+
id: string;
|
|
248
|
+
kind: 'compaction';
|
|
249
|
+
/** 1-based within the session, so the card can name itself. */
|
|
250
|
+
index: number;
|
|
251
|
+
trigger: CompactionUiTrigger;
|
|
252
|
+
mode: CompactionMode;
|
|
253
|
+
applied: boolean;
|
|
254
|
+
reason?: string;
|
|
255
|
+
messagesBefore: number;
|
|
256
|
+
messagesAfter: number;
|
|
257
|
+
tokensBefore: number;
|
|
258
|
+
tokensAfter: number;
|
|
259
|
+
summary?: string;
|
|
260
|
+
model: string;
|
|
261
|
+
durationMs?: number;
|
|
262
|
+
/**
|
|
263
|
+
* When the live card opened, so the elapsed counter has an origin
|
|
264
|
+
* (context-auto-compaction-hardening §3.6 / W5).
|
|
265
|
+
*
|
|
266
|
+
* JSON-SERIALIZABLE like every other field here, and harmless on reload:
|
|
267
|
+
* `session/persist.ts` normalizes `live` to false, and a settled card
|
|
268
|
+
* renders `durationMs` rather than an elapsed figure.
|
|
269
|
+
*/
|
|
270
|
+
startedAt?: number;
|
|
271
|
+
/** Set when tail relief clipped the retained turns (W2). */
|
|
272
|
+
tailRelief?: {
|
|
273
|
+
messages: number;
|
|
274
|
+
charsRemoved: number;
|
|
275
|
+
};
|
|
276
|
+
live: boolean;
|
|
225
277
|
};
|
|
226
278
|
export interface UsageTotal {
|
|
227
279
|
inputTokens: number;
|
|
@@ -296,6 +348,25 @@ export interface ViewState {
|
|
|
296
348
|
* what leaves an ordinary session's status bar byte-identical (AC-4).
|
|
297
349
|
*/
|
|
298
350
|
fast: FastSnapshot | null;
|
|
351
|
+
/**
|
|
352
|
+
* The live compaction projection, or `null` when the feature is off for this
|
|
353
|
+
* session (context-auto-compaction §5.5).
|
|
354
|
+
*
|
|
355
|
+
* NEVER PERSISTED — `entries` is the history and the settled cards are the
|
|
356
|
+
* record. It drives the status chip, the gauge's marks and `/compact status`'s
|
|
357
|
+
* totals. `null` is what leaves an ordinary session's status bar unchanged.
|
|
358
|
+
*/
|
|
359
|
+
compaction: CompactionSnapshot | null;
|
|
360
|
+
/**
|
|
361
|
+
* Whether `contextTokens` is currently a DERIVED figure rather than a measured
|
|
362
|
+
* one (§6.2 / P1-11).
|
|
363
|
+
*
|
|
364
|
+
* Set by `compactionEnd`, cleared by the next `turnEnd`. The status bar widens
|
|
365
|
+
* its leading `~` to cover it: today `~` means "the window is a guess", and
|
|
366
|
+
* this makes it also mean "the occupancy is a guess". Honesty about a guess is
|
|
367
|
+
* cheaper than a support thread about a wrong percentage.
|
|
368
|
+
*/
|
|
369
|
+
contextTokensEstimated: boolean;
|
|
299
370
|
seq: number;
|
|
300
371
|
/** The `kind: 'team'` entry the live dispatch is writing into. */
|
|
301
372
|
teamEntryId?: string;
|
|
@@ -305,6 +376,8 @@ export interface ViewState {
|
|
|
305
376
|
retryEntryId?: string;
|
|
306
377
|
/** The `kind: 'fast'` entry the in-flight review is writing into. */
|
|
307
378
|
fastEntryId?: string;
|
|
379
|
+
/** The `kind: 'compaction'` entry the in-flight compaction is writing into. */
|
|
380
|
+
compactionEntryId?: string;
|
|
308
381
|
streamingId?: string;
|
|
309
382
|
turnProduced: boolean;
|
|
310
383
|
errorNoticed: boolean;
|
|
@@ -497,6 +570,42 @@ export type ViewAction = {
|
|
|
497
570
|
} | {
|
|
498
571
|
type: 'fastTier';
|
|
499
572
|
snapshot: FastSnapshot;
|
|
573
|
+
} | {
|
|
574
|
+
type: 'compactionStart';
|
|
575
|
+
index: number;
|
|
576
|
+
trigger: CompactionUiTrigger;
|
|
577
|
+
model: string;
|
|
578
|
+
} | {
|
|
579
|
+
type: 'compactionEnd';
|
|
580
|
+
record: CompactionRecord;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Compaction spend.
|
|
584
|
+
*
|
|
585
|
+
* `costDelta` IS PRECOMPUTED BY THE CALLER, exactly as `teamUsage`'s and
|
|
586
|
+
* `fastUsage`'s are: the reducer stays pure and cost-table-free. It matters
|
|
587
|
+
* MORE here than for either of those, because the summarizer may be a
|
|
588
|
+
* different model from the lead AND from the fast tier, so a reducer that
|
|
589
|
+
* computed its own would have to know about three price tables.
|
|
590
|
+
*/
|
|
591
|
+
| {
|
|
592
|
+
type: 'compactionUsage';
|
|
593
|
+
usage: TokenUsage;
|
|
594
|
+
costDelta: number;
|
|
595
|
+
} | {
|
|
596
|
+
type: 'compactionSnapshot';
|
|
597
|
+
snapshot: CompactionSnapshot;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Overwrite the gauge with a DERIVED occupancy (§6.2).
|
|
601
|
+
*
|
|
602
|
+
* Separate from `compactionEnd` because the value is not always available at
|
|
603
|
+
* the same instant, and because the flag it sets — `contextTokensEstimated` —
|
|
604
|
+
* has to be clearable by the next `turnEnd` independently.
|
|
605
|
+
*/
|
|
606
|
+
| {
|
|
607
|
+
type: 'contextTokensEstimated';
|
|
608
|
+
tokens: number;
|
|
500
609
|
};
|
|
501
610
|
export declare const STORED_PREVIEW_CHARS = 8000;
|
|
502
611
|
export declare const STORED_PREVIEW_LINES = 200;
|
|
@@ -532,6 +641,17 @@ export interface PatchSource {
|
|
|
532
641
|
* it — every existing test, and headless mode — produces byte-identical actions.
|
|
533
642
|
*/
|
|
534
643
|
export declare function reduceEvent(event: AgentEvent, cost?: ModelCost, patches?: PatchSource): ViewAction[];
|
|
644
|
+
/**
|
|
645
|
+
* The opening words of the `context_overflow` banner.
|
|
646
|
+
*
|
|
647
|
+
* A SHARED CONSTANT RATHER THAN TWO LITERALS, because `compactionStart` matches
|
|
648
|
+
* on it to rewrite the notice a successful reactive recovery would otherwise
|
|
649
|
+
* leave behind (P1-4). Two spellings that must agree, with nothing checking that
|
|
650
|
+
* they do, is how that rewrite silently stops matching — at which point the red
|
|
651
|
+
* "start over" banner comes back and only on the recovery path, which is the one
|
|
652
|
+
* place nobody re-tests.
|
|
653
|
+
*/
|
|
654
|
+
export declare const CONTEXT_OVERFLOW_NOTICE_PREFIX = "Context length exceeded";
|
|
535
655
|
/** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
|
|
536
656
|
export declare function formatStreamError(error: Error): string;
|
|
537
657
|
export declare function viewReducer(state: ViewState, action: ViewAction): ViewState;
|