@aragon-agent/cli 0.6.2 → 0.6.4
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 +321 -0
- package/README.md +297 -2
- package/dist/agent/controller.d.ts +193 -1
- package/dist/agent/controller.js +475 -2
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +189 -0
- package/dist/agent/reducer.js +301 -2
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +297 -16
- package/dist/commands/builtins.js +165 -24
- 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 +118 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +355 -0
- package/dist/compaction/compactor.d.ts +302 -0
- package/dist/compaction/compactor.js +810 -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 +215 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +264 -0
- package/dist/compaction/wiring.js +577 -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 +5 -2
- package/dist/config/env.js +140 -3
- package/dist/config/load.d.ts +42 -0
- package/dist/config/load.js +101 -1
- package/dist/config/schema.d.ts +244 -0
- package/dist/config/schema.js +170 -0
- package/dist/config/store.js +37 -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/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +53 -1
- package/dist/logging/install.js +81 -1
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +55 -1
- 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/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +62 -2
- package/dist/ui/App.js +546 -16
- package/dist/ui/Composer.d.ts +66 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +82 -3
- package/dist/ui/PromptInput.js +232 -49
- package/dist/ui/StatusBar.d.ts +59 -0
- package/dist/ui/StatusBar.js +16 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +61 -3
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- 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 +44 -9
- package/dist/ui/layout/budget.js +42 -10
- 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 +85 -1
- package/dist/ui/overlays/HelpOverlay.js +9 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +74 -2
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +38 -0
- package/dist/ui/screen.js +113 -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 +72 -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.d.ts +1 -1
- package/dist/ui/use-wheel-routing.js +8 -0
- package/package.json +4 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -42
- package/dist/input/stdin-mouse-filter.js +0 -153
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';
|
|
@@ -20,8 +20,14 @@ import { TeamHumanQueue } from '../team/human-queue.js';
|
|
|
20
20
|
import { createTaskTool } from '../team/task-tool.js';
|
|
21
21
|
import { buildTeamBlock } from '../team/prompt.js';
|
|
22
22
|
import { buildFastBlock } from '../fast/prompt.js';
|
|
23
|
+
import { ProcSupervisor } from '../proc/supervisor.js';
|
|
24
|
+
import { buildBackgroundServicesBlock } from '../proc/prompt.js';
|
|
25
|
+
import { PROC_LIMITS } from '../proc/limits.js';
|
|
26
|
+
import { makeBashKill, makeBashOutput } from '../tools/proc-tools.js';
|
|
27
|
+
import { addSignalHook } from '../logging/install.js';
|
|
23
28
|
import { describeFastTierProblem, fastProviderOf, resolveFastTier } from '../fast/resolve.js';
|
|
24
29
|
import { FastWiring, offFastStatus } from '../fast/wiring.js';
|
|
30
|
+
import { CompactionWiring, offCompactionSnapshot } from '../compaction/wiring.js';
|
|
25
31
|
import { TodoStore } from '../todo/store.js';
|
|
26
32
|
import { createTodoTool } from '../todo/todo-tool.js';
|
|
27
33
|
import { buildTodoBlock } from '../todo/prompt.js';
|
|
@@ -32,6 +38,15 @@ import { createNodeSkillHost } from '../skills/node-host.js';
|
|
|
32
38
|
import { SkillService } from '../skills/service.js';
|
|
33
39
|
import { createSkillTools } from '../skills/tools.js';
|
|
34
40
|
import { buildSystemPrompt } from './system-prompt.js';
|
|
41
|
+
/**
|
|
42
|
+
* Extra budget, on top of `PROC_LIMITS.killGraceMs`, that `prompt()` allows a
|
|
43
|
+
* force-stopped engine to unwind in before it starts a new run anyway (I-8).
|
|
44
|
+
*
|
|
45
|
+
* `bash`'s own kill grace is what a wedged foreground command costs; this covers
|
|
46
|
+
* the loop's own unwinding on top of it. Waiting FOREVER is not an option - a
|
|
47
|
+
* tool that never settles is exactly why rung two exists.
|
|
48
|
+
*/
|
|
49
|
+
const ENGINE_UNWIND_GRACE_MS = 3000;
|
|
35
50
|
/** Approval gate used whenever there is provably no human to ask (headless). */
|
|
36
51
|
export const DENY_ALL_APPROVAL = {
|
|
37
52
|
canPrompt: () => false,
|
|
@@ -153,6 +168,66 @@ export class AgentController {
|
|
|
153
168
|
// property, and `<fast_tier>` is never spliced (I-2).
|
|
154
169
|
// -----------------------------------------------------------------------
|
|
155
170
|
fast;
|
|
171
|
+
// -----------------------------------------------------------------------
|
|
172
|
+
// Context compaction (context-auto-compaction §3.2.1)
|
|
173
|
+
//
|
|
174
|
+
// ONE FIELD AND THIN FORWARDERS, the `fast` shape one feature later, and for
|
|
175
|
+
// the same budget reason (C-16): every piece of state — the guards, the
|
|
176
|
+
// ladder, the manual queue, the session totals — lives in
|
|
177
|
+
// `compaction/wiring.ts` and `compaction/compactor.ts`.
|
|
178
|
+
//
|
|
179
|
+
// `null` when `compaction.enabled` was false at construction, which is the
|
|
180
|
+
// byte-identity branch: no manager is passed to `new Agent({...})`, nothing is
|
|
181
|
+
// subscribed, no registry is allocated, and the engine loop's checkpoint is one
|
|
182
|
+
// `if` against a field that is genuinely absent (AC-1).
|
|
183
|
+
//
|
|
184
|
+
// CONSTRUCTED ABOVE `new Agent({...})` AND ATTACHED BELOW IT (C-12 / P0-2).
|
|
185
|
+
// The port has to be inside the Agent's constructor argument and the wiring has
|
|
186
|
+
// to subscribe to the Agent, and this class cannot do both in one step.
|
|
187
|
+
// -----------------------------------------------------------------------
|
|
188
|
+
compaction;
|
|
189
|
+
// -----------------------------------------------------------------------
|
|
190
|
+
// Background services (background-service-supervision §3.3 / §3.6)
|
|
191
|
+
//
|
|
192
|
+
// CONSTRUCTED UNCONDITIONALLY, and that is the one thing about this field that
|
|
193
|
+
// is easy to get wrong (P1-6 / D-9). `cfg.bash.background` gates the SERVICE
|
|
194
|
+
// half - the `background` tool param, `bash_output`/`bash_kill`, the prompt
|
|
195
|
+
// block, the status chip - and NEVER the foreground half, because G2 ("bash
|
|
196
|
+
// always settles") and G3 ("Esc, Esc always works, and the session stays
|
|
197
|
+
// usable") are promised unconditionally. Gating the whole supervisor on the
|
|
198
|
+
// flag would make rung two of the Esc ladder a silent no-op for every user who
|
|
199
|
+
// turned background services off.
|
|
200
|
+
//
|
|
201
|
+
// With the flag off it holds foreground pids, nothing else, and allocates no
|
|
202
|
+
// timers.
|
|
203
|
+
// -----------------------------------------------------------------------
|
|
204
|
+
procs;
|
|
205
|
+
/**
|
|
206
|
+
* The host's notice channel, kept so `prompt()`'s totality has somewhere to
|
|
207
|
+
* report to (I-8). Every other consumer reads `deps.notify` through a closure
|
|
208
|
+
* built in the constructor; this one is called from a method, which is why it
|
|
209
|
+
* needs a field.
|
|
210
|
+
*/
|
|
211
|
+
notifyHostFn;
|
|
212
|
+
/** Whether the SERVICE half is on: tools, prompt block, `background` param. */
|
|
213
|
+
bashBackgroundRegistered;
|
|
214
|
+
/** Release the exit reaper. Called by `dispose()`. */
|
|
215
|
+
releaseSignalHook;
|
|
216
|
+
/**
|
|
217
|
+
* Bumped by `forceStop()`; captured by `App` when it subscribes to a run.
|
|
218
|
+
*
|
|
219
|
+
* THE VIEW AND THE ENGINE DELIBERATELY DISAGREE AFTER A FORCE-STOP (D-10 /
|
|
220
|
+
* I-7). Rung two dispatches `runEnd` locally so the composer is usable
|
|
221
|
+
* immediately, but the engine keeps unwinding and keeps EMITTING -
|
|
222
|
+
* `turn_start`, `tool_call_start`, `turn_end`, `agent_end` - and
|
|
223
|
+
* `runStart`/`turnStart` would set `status: 'running'` again, bouncing the
|
|
224
|
+
* view straight back out of the `idle` the user was just given and appending
|
|
225
|
+
* entries to a transcript they believe is finished. `App` drops every event
|
|
226
|
+
* whose generation is stale, which is the same shape as `abortRequested`
|
|
227
|
+
* above and exists for the same reason: the loop legitimately emits after an
|
|
228
|
+
* abort lands.
|
|
229
|
+
*/
|
|
230
|
+
runGen = 0;
|
|
156
231
|
/**
|
|
157
232
|
* Set SYNCHRONOUSLY by `abort()` before `agent.abort()` (§3.5.4a guard 1).
|
|
158
233
|
* Cleared at the next `agent_start`.
|
|
@@ -217,6 +292,11 @@ export class AgentController {
|
|
|
217
292
|
this.teamRuntime = this.teamRegistered
|
|
218
293
|
? new TeamRuntime({
|
|
219
294
|
getConfig: () => this.config,
|
|
295
|
+
// Read LIVE through the wiring, exactly as `resolveTier` below is: the
|
|
296
|
+
// closure is only ever called from inside a dispatch, and
|
|
297
|
+
// `childFactory()` re-reads `compaction.subagents` on every call so a
|
|
298
|
+
// settings-screen edit reaches the next dispatch.
|
|
299
|
+
contextManagerFor: (req) => this.compaction?.childFactory()?.(req),
|
|
220
300
|
// Read LIVE through the wiring, which does not exist yet — the closure
|
|
221
301
|
// is only ever called from inside a dispatch, long after construction.
|
|
222
302
|
resolveTier: (tier) => this.fast
|
|
@@ -266,6 +346,19 @@ export class AgentController {
|
|
|
266
346
|
// reason the field's own comment gives: the binding below is spread
|
|
267
347
|
// conditionally on it (§3.1.3 / P1-6).
|
|
268
348
|
this.toolOutputs = config.liveToolOutput ? createToolOutputStore() : null;
|
|
349
|
+
// ALSO ABOVE `createBuiltinTools`, and UNCONDITIONAL - see the field's own
|
|
350
|
+
// comment. `readyTimeoutMs` is read through a closure rather than captured,
|
|
351
|
+
// so a live config change reaches the next service rather than the next
|
|
352
|
+
// launch of the CLI.
|
|
353
|
+
this.procs = new ProcSupervisor({ readyTimeoutMs: () => this.config.bash.readyTimeoutMs });
|
|
354
|
+
this.notifyHostFn = deps.notify;
|
|
355
|
+
this.bashBackgroundRegistered = config.bash.background;
|
|
356
|
+
// THE EXIT REAPER (G5 / P1-2 / R-10). An APPEND-ONLY hook rather than a
|
|
357
|
+
// second `setSignalTerminator`: that is a single slot `cli.tsx` already owns
|
|
358
|
+
// for the alternate-screen restore, and replacing it is the documented bug
|
|
359
|
+
// that leaves the user staring at a blank alternate screen. `reapSync` is
|
|
360
|
+
// the only entry point it calls, and it is synchronous end to end (I-9).
|
|
361
|
+
this.releaseSignalHook = addSignalHook(() => this.procs.reapSync());
|
|
269
362
|
this.tools = createBuiltinTools({
|
|
270
363
|
getCwd: () => this.cwd,
|
|
271
364
|
// OWNED HERE, NOT IN `cli.tsx` (D-18): `createBuiltinTools` is called from
|
|
@@ -282,6 +375,28 @@ export class AgentController {
|
|
|
282
375
|
...(this.toolOutputs
|
|
283
376
|
? { recordOutput: (id, chunk) => this.emitToolOutput('lead', id, chunk) }
|
|
284
377
|
: {}),
|
|
378
|
+
// THE SERVICE HALF, AND ONLY THE SERVICE HALF (P1-6 / I-2). With
|
|
379
|
+
// `bash.background: false` the options bag has no `procs` key at all, so
|
|
380
|
+
// `createBuiltinTools` adds no property to `deps`, `bash` gets its default
|
|
381
|
+
// `{}` second argument, and the tool is the pre-feature one: no
|
|
382
|
+
// `background` in its schema, the pre-feature description string, no
|
|
383
|
+
// supervisor call. The FOREGROUND half of `this.procs` is still live, and
|
|
384
|
+
// `forceStop()` still reaches it.
|
|
385
|
+
...(this.bashBackgroundRegistered
|
|
386
|
+
? {
|
|
387
|
+
procs: this.procs,
|
|
388
|
+
// Read LIVE for the reason every other closure in this bag is:
|
|
389
|
+
// `aragon config set bash.autoBackground false` must take effect in
|
|
390
|
+
// the running session.
|
|
391
|
+
autoBackground: () => this.config.bash.autoBackground,
|
|
392
|
+
startupSettleMs: () => this.config.bash.startupSettleMs,
|
|
393
|
+
// D-15 / P1-8. `planInteractive` is the SAME signal `planTools`
|
|
394
|
+
// already uses to decide that no overlay can be rendered, which is
|
|
395
|
+
// exactly the question being asked here: can a card be drawn, and is
|
|
396
|
+
// there a user who could keep a service alive?
|
|
397
|
+
interactive: this.planInteractive,
|
|
398
|
+
}
|
|
399
|
+
: {}),
|
|
285
400
|
confirmTools: config.confirmTools,
|
|
286
401
|
// Routed through the same watchdog pause as the plan tools. This is a
|
|
287
402
|
// pre-existing bug being fixed in passing: a `--confirm` user who took
|
|
@@ -321,6 +436,12 @@ export class AgentController {
|
|
|
321
436
|
}),
|
|
322
437
|
]
|
|
323
438
|
: [],
|
|
439
|
+
// THROUGH THE FACTORY for the reason `teamTools` records below (P0-2), and
|
|
440
|
+
// empty when background services were off at construction - which leaves
|
|
441
|
+
// the array byte-identical to the pre-feature build (I-2).
|
|
442
|
+
procTools: this.bashBackgroundRegistered
|
|
443
|
+
? [makeBashOutput({ supervisor: this.procs }), makeBashKill({ supervisor: this.procs })]
|
|
444
|
+
: [],
|
|
324
445
|
// THROUGH THE FACTORY, NEVER APPENDED AFTERWARDS (D-15 / P0-1).
|
|
325
446
|
// `tools.test.ts::C7` asserts `HOST_TOOL_NAMES` equals what
|
|
326
447
|
// `createBuiltinTools` produces, so appending `task` here would turn that
|
|
@@ -379,6 +500,25 @@ export class AgentController {
|
|
|
379
500
|
modelId: config.model,
|
|
380
501
|
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
381
502
|
};
|
|
503
|
+
// --- Context compaction, part 1: BEFORE the Agent ----------------------
|
|
504
|
+
//
|
|
505
|
+
// It needs no agent reference to EXIST — only live config, the key
|
|
506
|
+
// resolvers, the model-info lookup and a notifier — which is exactly what
|
|
507
|
+
// makes the C-12 split work. `getMessages` / `getSystemPrompt` are LAZY for
|
|
508
|
+
// the same reason: `this.agent` is assigned on the very next statement, and
|
|
509
|
+
// they are only ever called from inside an event handler.
|
|
510
|
+
this.compaction = config.compaction.enabled
|
|
511
|
+
? new CompactionWiring({
|
|
512
|
+
getConfig: () => this.config,
|
|
513
|
+
hasKey: (id) => this.hasApiKey(id),
|
|
514
|
+
getApiKey: (id) => this.resolveKey(id),
|
|
515
|
+
getModelInfoFor: (ref) => this.getModelInfoFor(ref),
|
|
516
|
+
isPricedModel: (ref) => this.isPricedModel(ref),
|
|
517
|
+
getMessages: () => this.agent.state.messages,
|
|
518
|
+
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
519
|
+
notify: (level, text) => deps.notify?.(level, text),
|
|
520
|
+
})
|
|
521
|
+
: null;
|
|
382
522
|
this.agent = new Agent({
|
|
383
523
|
systemPrompt: this.composeSystemPrompt(),
|
|
384
524
|
model,
|
|
@@ -389,6 +529,13 @@ export class AgentController {
|
|
|
389
529
|
// without rebuilding the Agent — the closure reads the current config.
|
|
390
530
|
getApiKey: (id) => this.resolveKey(id),
|
|
391
531
|
maxTokens: config.maxTokens,
|
|
532
|
+
// SPREAD CONDITIONALLY, never `contextManager: this.compaction?.manager()`.
|
|
533
|
+
// With the feature off the options bag has no such key, so the loop's gate
|
|
534
|
+
// tests a field that is genuinely `undefined` rather than a property
|
|
535
|
+
// holding `undefined` — which is what makes AC-1's byte-identity claim
|
|
536
|
+
// provable by construction rather than by inspection. `createBuiltinTools`
|
|
537
|
+
// above spreads `permission` for the identical reason (P1-2 / AC-28).
|
|
538
|
+
...(this.compaction ? { contextManager: this.compaction.manager() } : {}),
|
|
392
539
|
timeouts: {
|
|
393
540
|
// Timeout invariant (R1): idle ≥ tool so long tool runs are not killed.
|
|
394
541
|
toolTimeout: config.toolTimeoutMs,
|
|
@@ -408,6 +555,18 @@ export class AgentController {
|
|
|
408
555
|
},
|
|
409
556
|
},
|
|
410
557
|
});
|
|
558
|
+
// --- Context compaction, part 2: AFTER the Agent exists -----------------
|
|
559
|
+
//
|
|
560
|
+
// `attach` is what lets the wiring see `compaction_end` — the ENGINE's
|
|
561
|
+
// `applied` verdict, which the compactor cannot know because validation
|
|
562
|
+
// happens after `compact()` returns — and `agent_start`, which resets the
|
|
563
|
+
// per-run cap. It is idempotent, and `dispose()` unsubscribes.
|
|
564
|
+
//
|
|
565
|
+
// A SEPARATE CALL RATHER THAN A `subscribe` DEP LIKE `FastWiring`'s, and that
|
|
566
|
+
// is the constraint that makes the whole split work: a constructor that
|
|
567
|
+
// subscribed could not run before `new Agent(...)`, and the port has to be
|
|
568
|
+
// inside its constructor argument (C-12 / P0-2).
|
|
569
|
+
this.compaction?.attach((listener) => this.agent.subscribe(listener));
|
|
411
570
|
// --- Fast tier, part 2: after the Agent exists --------------------------
|
|
412
571
|
//
|
|
413
572
|
// `FastWiring` subscribes to the lead's event stream, so it cannot be built
|
|
@@ -550,6 +709,12 @@ export class AgentController {
|
|
|
550
709
|
review: this.config.fast.review,
|
|
551
710
|
})
|
|
552
711
|
: '',
|
|
712
|
+
// ONE FLAG, unlike team/todo's two: there is no live on/off switch for
|
|
713
|
+
// background services - `bash.background` is read once at construction,
|
|
714
|
+
// and the tools it registers cannot be unregistered (the `team.enabled` /
|
|
715
|
+
// `todo.enabled` rule). So the block is present exactly when the tools are,
|
|
716
|
+
// and can never advertise a capability the model does not have.
|
|
717
|
+
backgroundBlock: this.bashBackgroundRegistered ? buildBackgroundServicesBlock() : '',
|
|
553
718
|
// Read from the FIELD on every rebuild, for the reason the field's own
|
|
554
719
|
// comment gives. Empty everywhere but `aragon exec`, and `buildSystemPrompt`
|
|
555
720
|
// splices it conditionally, so every existing prompt is byte-identical.
|
|
@@ -638,7 +803,64 @@ export class AgentController {
|
|
|
638
803
|
* underlying promise so headless mode can await completion. `agent.prompt()`
|
|
639
804
|
* never rejects, so callers must not treat resolution as success.
|
|
640
805
|
*/
|
|
641
|
-
prompt(text) {
|
|
806
|
+
async prompt(text) {
|
|
807
|
+
// I-8 - THIS METHOD NEVER REJECTS, AND THAT HAS TO BE A PROPERTY OF THE
|
|
808
|
+
// METHOD RATHER THAN A COMMENT ABOVE IT (P0-1).
|
|
809
|
+
//
|
|
810
|
+
// `App` calls this as `void controller.prompt(...)`, and
|
|
811
|
+
// `logging/install.ts` routes an `unhandledRejection` into `handleFatal`,
|
|
812
|
+
// which EXITS THE PROCESS. `Agent.prompt()` throws SYNCHRONOUSLY while
|
|
813
|
+
// `Agent.running` is true - so before the force-stop ladder existed this was
|
|
814
|
+
// merely nearly-true (the view was only ever idle when the engine was), and
|
|
815
|
+
// rung two makes the view idle while the engine is still unwinding. Without
|
|
816
|
+
// the wait and the guard below, "Esc, Esc, then type" would kill the CLI on
|
|
817
|
+
// the very screen this feature exists to rescue.
|
|
818
|
+
//
|
|
819
|
+
// TWO PARTS, BOTH REQUIRED. First WAIT the engine out, bounded, so the
|
|
820
|
+
// common case (the engine unwinds in a few hundred ms) starts a real run
|
|
821
|
+
// rather than reporting a failure. Then swallow anything that still escapes
|
|
822
|
+
// and surface it through the existing notify channel, because a message that
|
|
823
|
+
// cannot be started must produce a visible sentence - never a crash, and
|
|
824
|
+
// never silence.
|
|
825
|
+
if (this.agent.state.isRunning) {
|
|
826
|
+
this.abort();
|
|
827
|
+
await this.waitForEngineIdle();
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
await this.startPrompt(text);
|
|
831
|
+
}
|
|
832
|
+
catch (err) {
|
|
833
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
834
|
+
getLogger().warn('agent', 'prompt_failed', { reason });
|
|
835
|
+
this.notifyHostFn?.('error', `Could not start the run: ${reason}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Wait for the engine to unwind, bounded.
|
|
840
|
+
*
|
|
841
|
+
* BOUNDED BECAUSE THE UNBOUNDED CASE IS THE ONE THIS FEATURE IS ABOUT: a tool
|
|
842
|
+
* that never settles is exactly why rung two exists, and `Agent.waitForIdle()`
|
|
843
|
+
* alone would hang here for as long as it hangs there. The budget is the two
|
|
844
|
+
* graces a wedged `bash` can legitimately spend, plus the executor's own.
|
|
845
|
+
*/
|
|
846
|
+
waitForEngineIdle() {
|
|
847
|
+
const budget = PROC_LIMITS.killGraceMs + ENGINE_UNWIND_GRACE_MS;
|
|
848
|
+
return new Promise((resolve) => {
|
|
849
|
+
let done = false;
|
|
850
|
+
const finish = () => {
|
|
851
|
+
if (done)
|
|
852
|
+
return;
|
|
853
|
+
done = true;
|
|
854
|
+
clearTimeout(timer);
|
|
855
|
+
resolve();
|
|
856
|
+
};
|
|
857
|
+
const timer = setTimeout(finish, budget);
|
|
858
|
+
timer.unref?.();
|
|
859
|
+
void this.agent.waitForIdle().then(finish, finish);
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
/** The pre-round body of `prompt`, unchanged apart from being private. */
|
|
863
|
+
startPrompt(text) {
|
|
642
864
|
// A new user message is a new intent, so the previous turn's tool ceiling
|
|
643
865
|
// expires here and anything a slash command queued is promoted (D-G1 / D-G2).
|
|
644
866
|
// This and `steer()` below are the ONLY two places a user message enters the
|
|
@@ -670,6 +892,11 @@ export class AgentController {
|
|
|
670
892
|
// nothing ever clears (RV-1).
|
|
671
893
|
this.abortRequested = true;
|
|
672
894
|
this.fast?.abort();
|
|
895
|
+
// The engine also races `ctx.signal` inside `runCompaction`, so this is the
|
|
896
|
+
// same DELIBERATE REDUNDANCY the `teamRuntime` line below records: it makes
|
|
897
|
+
// "Esc cancels an in-flight summarization" a property of this class rather
|
|
898
|
+
// than a consequence of the compactor honouring a signal three modules away.
|
|
899
|
+
this.compaction?.abort();
|
|
673
900
|
this.agent.abort();
|
|
674
901
|
// DELIBERATE REDUNDANCY (§3.8). Aborting the lead already aborts the
|
|
675
902
|
// in-flight `task` call's `ctx.signal`, which the runtime listens to — that
|
|
@@ -679,6 +906,115 @@ export class AgentController {
|
|
|
679
906
|
// `App.tsx` records for `cancelPendingHuman()` on `agent_end`.
|
|
680
907
|
this.teamRuntime?.abortAll();
|
|
681
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Rung two of the Esc ladder: stop the AGENT, whatever it is blocked on
|
|
911
|
+
* (§3.6 / G3).
|
|
912
|
+
*
|
|
913
|
+
* FOUR STEPS, AND THE FOURTH IS AN ABSENCE.
|
|
914
|
+
*
|
|
915
|
+
* 1. `abort()` - everything it does today, unchanged.
|
|
916
|
+
* 2. `killForeground('force')` - hard-kill every tracked foreground `bash`
|
|
917
|
+
* child. THIS is what actually unblocks a wedged `await tool.execute`;
|
|
918
|
+
* an abort signal a tool ignores is advisory, and rung two exists for
|
|
919
|
+
* precisely the tools that ignore it.
|
|
920
|
+
* 3. `runGen += 1` - so `App` can drop the events the engine will keep
|
|
921
|
+
* emitting while it unwinds (I-7). Without it the view bounces straight
|
|
922
|
+
* back out of the `idle` the user was just given.
|
|
923
|
+
* 4. IT DOES NOT TOUCH BACKGROUND SERVICES. `Esc` interrupts the AGENT;
|
|
924
|
+
* `Ctrl+C` stops the SERVICES. That asymmetry is the user's own two
|
|
925
|
+
* sentences and it is exactly the kind of thing a later reader will
|
|
926
|
+
* "fix" by making this kill everything - which would mean a user who
|
|
927
|
+
* pressed Esc twice to stop a runaway turn also lost the dev server they
|
|
928
|
+
* had been working against for an hour. It is invariant I-4.
|
|
929
|
+
*
|
|
930
|
+
* UNCONDITIONAL ON `bash.background` (P1-6 / D-9). The supervisor is always
|
|
931
|
+
* constructed; with services off its foreground registry is all there is, and
|
|
932
|
+
* that is the half this method uses.
|
|
933
|
+
*/
|
|
934
|
+
forceStop() {
|
|
935
|
+
this.abort();
|
|
936
|
+
const killed = this.procs.killForeground('force');
|
|
937
|
+
this.runGen += 1;
|
|
938
|
+
getLogger().info('agent', 'force_stop', { generation: this.runGen, killed });
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* The current run generation (I-7).
|
|
942
|
+
*
|
|
943
|
+
* `App` captures it when it subscribes to a run and drops every engine event
|
|
944
|
+
* whose generation is stale. Same shape as the `abortRequested` guard the fast
|
|
945
|
+
* reviewer already uses, and for the same reason: the loop legitimately emits
|
|
946
|
+
* after an abort lands.
|
|
947
|
+
*/
|
|
948
|
+
get runGeneration() {
|
|
949
|
+
return this.runGen;
|
|
950
|
+
}
|
|
951
|
+
// -----------------------------------------------------------------------
|
|
952
|
+
// Background services (background-service-supervision §3.5 / §3.6)
|
|
953
|
+
// -----------------------------------------------------------------------
|
|
954
|
+
/** Whether `bash_output` / `bash_kill` are in `this.tools`. Fixed at construction. */
|
|
955
|
+
isBackgroundRegistered() {
|
|
956
|
+
return this.bashBackgroundRegistered;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Subscribe to the CLI-local `ProcEvent` stream.
|
|
960
|
+
*
|
|
961
|
+
* Returns a live unsubscribe even when services are off, because the
|
|
962
|
+
* supervisor exists either way - the contract `subscribeTeam` /
|
|
963
|
+
* `subscribeTodos` / `subscribeFast` / `subscribeToolOutput` already set. With
|
|
964
|
+
* services off nothing is ever emitted, so an unconditional subscription in
|
|
965
|
+
* `App` costs one `Set` entry.
|
|
966
|
+
*/
|
|
967
|
+
subscribeProc(listener) {
|
|
968
|
+
return this.procs.subscribe(listener);
|
|
969
|
+
}
|
|
970
|
+
getServiceSnapshot(id) {
|
|
971
|
+
return this.procs.get(id);
|
|
972
|
+
}
|
|
973
|
+
listServices() {
|
|
974
|
+
return this.procs.list();
|
|
975
|
+
}
|
|
976
|
+
/** `starting | ready | running`. Drives the status chip and the Ctrl+C rung. */
|
|
977
|
+
liveServiceCount() {
|
|
978
|
+
return this.procs.liveCount();
|
|
979
|
+
}
|
|
980
|
+
readServiceLog(id, since) {
|
|
981
|
+
return this.procs.read(id, since);
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Stop one service, or every service when `id` is `'all'`.
|
|
985
|
+
*
|
|
986
|
+
* The GRACEFUL ladder (`SIGTERM` -> `SIGKILL` after `stopGraceMs`), because
|
|
987
|
+
* this path has a live event loop to run it on. `reapServicesSync` is the
|
|
988
|
+
* other one, and it is the only one an exit path may use.
|
|
989
|
+
*/
|
|
990
|
+
stopService(id) {
|
|
991
|
+
return this.procs.stop(id);
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Rung one of `Ctrl+C`: stop every live service (§3.6 / G4).
|
|
995
|
+
*
|
|
996
|
+
* `force: true` means `reapSync()` SEMANTICS - immediate `SIGKILL`, no
|
|
997
|
+
* `stopGraceMs` - and is what `doExit` uses, because `doExit` awaits nothing
|
|
998
|
+
* and must not start (§3.6 step 3). The graceful ladder belongs to `Ctrl+C`,
|
|
999
|
+
* `bash_kill` and `/bg stop`.
|
|
1000
|
+
*/
|
|
1001
|
+
stopAllServices(opts = {}) {
|
|
1002
|
+
if (opts.force) {
|
|
1003
|
+
this.procs.reapSync();
|
|
1004
|
+
return Promise.resolve(this.procs.list());
|
|
1005
|
+
}
|
|
1006
|
+
return this.procs.stop('all');
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* The synchronous reaper (I-9).
|
|
1010
|
+
*
|
|
1011
|
+
* Exposed so `doExit` can call it without awaiting anything. It is also what
|
|
1012
|
+
* the signal hook registered in the constructor calls, and it is idempotent -
|
|
1013
|
+
* which it has to be, since a normal quit reaches it twice.
|
|
1014
|
+
*/
|
|
1015
|
+
reapServicesSync() {
|
|
1016
|
+
this.procs.reapSync();
|
|
1017
|
+
}
|
|
682
1018
|
steer(text) {
|
|
683
1019
|
// Mid-run interjection: promote the queue, keep what is already in force
|
|
684
1020
|
// (D-G3). Clearing here would make "ask a follow-up" the way around the
|
|
@@ -870,6 +1206,12 @@ export class AgentController {
|
|
|
870
1206
|
dispose() {
|
|
871
1207
|
this.teamRuntime?.dispose();
|
|
872
1208
|
this.fast?.dispose();
|
|
1209
|
+
this.compaction?.dispose();
|
|
1210
|
+
// REAP BEFORE RELEASING THE HOOK, in that order: disposal is a teardown, and
|
|
1211
|
+
// a service that outlives it has nothing left that could ever stop it.
|
|
1212
|
+
this.procs.reapSync();
|
|
1213
|
+
this.releaseSignalHook();
|
|
1214
|
+
this.procs.dispose();
|
|
873
1215
|
}
|
|
874
1216
|
// -----------------------------------------------------------------------
|
|
875
1217
|
// Todo planning (todo-plan-execution §3.6a)
|
|
@@ -991,6 +1333,10 @@ export class AgentController {
|
|
|
991
1333
|
// another vendor's gateway — and `<fast_tier>` would go on naming the model
|
|
992
1334
|
// it used to resolve to.
|
|
993
1335
|
this.fast?.onConfigChanged();
|
|
1336
|
+
// The summarizer resolves from LIVE config too — it may BE the fast tier —
|
|
1337
|
+
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1338
|
+
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1339
|
+
this.compaction?.onConfigChanged();
|
|
994
1340
|
}
|
|
995
1341
|
setThinkingLevel(level) {
|
|
996
1342
|
this.config = { ...this.config, thinkingLevel: level };
|
|
@@ -1010,6 +1356,10 @@ export class AgentController {
|
|
|
1010
1356
|
// Rule 5 of §3.2 reads `apiKeys`, so a key edit is a tier event: this is the
|
|
1011
1357
|
// path that turns `no_key` back into a working tier without a relaunch.
|
|
1012
1358
|
this.fast?.onConfigChanged();
|
|
1359
|
+
// The summarizer resolves from LIVE config too — it may BE the fast tier —
|
|
1360
|
+
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1361
|
+
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1362
|
+
this.compaction?.onConfigChanged();
|
|
1013
1363
|
}
|
|
1014
1364
|
setCwd(cwd) {
|
|
1015
1365
|
this.cwd = cwd;
|
|
@@ -1082,6 +1432,10 @@ export class AgentController {
|
|
|
1082
1432
|
}
|
|
1083
1433
|
clearMessages() {
|
|
1084
1434
|
this.agent.clearMessages();
|
|
1435
|
+
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
1436
|
+
// was CLEARED, not appended to, so the prefix length the last measurement
|
|
1437
|
+
// covered indexes an array that no longer exists.
|
|
1438
|
+
this.compaction?.onHistoryReplaced();
|
|
1085
1439
|
// A new conversation has no loaded skills, so the "already loaded earlier
|
|
1086
1440
|
// in this conversation" hint would otherwise start lying (P2-9).
|
|
1087
1441
|
this.skills.getRegistry().clearActive();
|
|
@@ -1098,6 +1452,10 @@ export class AgentController {
|
|
|
1098
1452
|
}
|
|
1099
1453
|
replaceMessages(messages) {
|
|
1100
1454
|
this.agent.replaceMessages(messages);
|
|
1455
|
+
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
1456
|
+
// `estimateAppendedTokens` bounds-checks as well, so a MISSED site costs
|
|
1457
|
+
// accuracy and never correctness; that belt does not make this call optional.
|
|
1458
|
+
this.compaction?.onHistoryReplaced();
|
|
1101
1459
|
}
|
|
1102
1460
|
getMessages() {
|
|
1103
1461
|
return this.agent.state.messages;
|
|
@@ -1191,6 +1549,10 @@ export class AgentController {
|
|
|
1191
1549
|
// was never registered) nothing has to be rebuilt, because no block is
|
|
1192
1550
|
// spliced either way.
|
|
1193
1551
|
this.fast?.onConfigChanged();
|
|
1552
|
+
// The summarizer resolves from LIVE config too — it may BE the fast tier —
|
|
1553
|
+
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1554
|
+
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1555
|
+
this.compaction?.onConfigChanged();
|
|
1194
1556
|
return fast;
|
|
1195
1557
|
}
|
|
1196
1558
|
/** Everything `/fast status`, the settings line and the chip read. */
|
|
@@ -1209,6 +1571,117 @@ export class AgentController {
|
|
|
1209
1571
|
return () => { };
|
|
1210
1572
|
return this.fast.subscribe(listener);
|
|
1211
1573
|
}
|
|
1574
|
+
// -----------------------------------------------------------------------
|
|
1575
|
+
// Context compaction (context-auto-compaction §3.2.1) — THIN FORWARDERS
|
|
1576
|
+
// -----------------------------------------------------------------------
|
|
1577
|
+
/** Whether a `ContextManager` exists at all. Fixed at construction. */
|
|
1578
|
+
isCompactionRegistered() {
|
|
1579
|
+
return this.compaction !== null;
|
|
1580
|
+
}
|
|
1581
|
+
/** Whether it will actually fire. Flipped live by `/compact on|off`. */
|
|
1582
|
+
isCompactionEnabled() {
|
|
1583
|
+
return this.compaction?.isEnabled() === true;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Flip the live switch.
|
|
1587
|
+
*
|
|
1588
|
+
* A NO-OP WHEN NOTHING IS REGISTERED, and `/compact` reports that honestly by
|
|
1589
|
+
* checking `isCompactionRegistered()` first — the `/fast on` and `/team on`
|
|
1590
|
+
* shape. `enabled` decides whether the wiring EXISTS, which happens once at
|
|
1591
|
+
* construction and cannot be undone: a session that started with no manager
|
|
1592
|
+
* would have to be given one mid-flight, and the `Agent` has no setter for it
|
|
1593
|
+
* on purpose (§3.2).
|
|
1594
|
+
*/
|
|
1595
|
+
setCompactionEnabled(enabled) {
|
|
1596
|
+
this.compaction?.setEnabled(enabled);
|
|
1597
|
+
}
|
|
1598
|
+
getCompactionConfig() {
|
|
1599
|
+
return this.config.compaction;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Adopt a new compaction config for this session.
|
|
1603
|
+
*
|
|
1604
|
+
* THE `onConfigChanged` CALL IS NOT OPTIONAL, for the reason `setFastConfig`
|
|
1605
|
+
* records: a persist without it reports success and changes nothing visible
|
|
1606
|
+
* until relaunch, because the chip and `/compact status` read a snapshot.
|
|
1607
|
+
*/
|
|
1608
|
+
setCompactionConfig(patch) {
|
|
1609
|
+
const compaction = clampCompactionConfig({ ...this.config.compaction, ...patch });
|
|
1610
|
+
this.config = { ...this.config, compaction };
|
|
1611
|
+
this.compaction?.onConfigChanged();
|
|
1612
|
+
return compaction;
|
|
1613
|
+
}
|
|
1614
|
+
/** Everything `/compact status`, the settings row and the chip read. */
|
|
1615
|
+
getCompactionSnapshot() {
|
|
1616
|
+
return this.compaction ? this.compaction.snapshot() : offCompactionSnapshot();
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* The `ModelRef` the next compaction would summarize with, or `null`.
|
|
1620
|
+
*
|
|
1621
|
+
* IT IS CARRIED, NEVER RE-DERIVED, and that is the whole reason it exists. The
|
|
1622
|
+
* summarizer is the fast tier when `useFastTier` is on AND the tier resolves,
|
|
1623
|
+
* and the session's own model otherwise — a combination no caller can
|
|
1624
|
+
* reconstruct from `CliConfig` without re-implementing `resolveFastTier`, and
|
|
1625
|
+
* one that a plausible guess (`fast.enabled ? fast.provider : provider`) gets
|
|
1626
|
+
* WRONG whenever `fast.enabled` is true and `compaction.useFastTier` is false.
|
|
1627
|
+
* Pricing the spend against the wrong provider looks up a model the static
|
|
1628
|
+
* table has never seen, which prices it at `$0.00` — the C-11 / RV-4 lie, on
|
|
1629
|
+
* the one feature that spends money without being asked (AC-15).
|
|
1630
|
+
*/
|
|
1631
|
+
getCompactionSummarizerRef() {
|
|
1632
|
+
return this.compaction?.summarizerRef() ?? null;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* This run's archive key, or `null` when compaction is unregistered
|
|
1636
|
+
* (context-auto-compaction-hardening §3.5 / W4).
|
|
1637
|
+
*
|
|
1638
|
+
* `/compact history` and `/compact show` scope themselves to it, so a second
|
|
1639
|
+
* `aragon` in another terminal is invisible here rather than interleaved with
|
|
1640
|
+
* this one's compactions.
|
|
1641
|
+
*/
|
|
1642
|
+
getCompactionRunId() {
|
|
1643
|
+
return this.compaction?.archiveRunId() ?? null;
|
|
1644
|
+
}
|
|
1645
|
+
/** Ask for a compaction at the next turn boundary (§4.4 / D-17). */
|
|
1646
|
+
queueCompaction(instructions) {
|
|
1647
|
+
this.compaction?.queueManual(instructions);
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* The IDLE `/compact` path (§4.4 / D-25).
|
|
1651
|
+
*
|
|
1652
|
+
* IT VALIDATES BEFORE IT SPLICES. `CompactionWiring.compactNow` runs
|
|
1653
|
+
* `validateHistory` itself and refuses on failure, because with no loop running
|
|
1654
|
+
* the engine's own gate is unreachable and D-4 declares that gate
|
|
1655
|
+
* unbypassable. `replaceMessages` validates NOTHING.
|
|
1656
|
+
*/
|
|
1657
|
+
async compactNow(instructions) {
|
|
1658
|
+
if (!this.compaction)
|
|
1659
|
+
return { ok: false, reason: 'not_registered' };
|
|
1660
|
+
const controller = new AbortController();
|
|
1661
|
+
const outcome = await this.compaction.compactNow({
|
|
1662
|
+
messages: this.agent.state.messages,
|
|
1663
|
+
systemPrompt: this.agent.state.systemPrompt,
|
|
1664
|
+
model: this.agent.state.model,
|
|
1665
|
+
signal: controller.signal,
|
|
1666
|
+
...(instructions ? { instructions } : {}),
|
|
1667
|
+
});
|
|
1668
|
+
if (!outcome.ok)
|
|
1669
|
+
return { ok: false, reason: outcome.reason };
|
|
1670
|
+
this.agent.replaceMessages(outcome.messages);
|
|
1671
|
+
return { ok: true };
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Subscribe to the CLI-local compaction event stream.
|
|
1675
|
+
*
|
|
1676
|
+
* Returns a no-op unsubscribe when compaction was off at construction, so
|
|
1677
|
+
* every caller can subscribe unconditionally — the contract `subscribeTeam`,
|
|
1678
|
+
* `subscribeTodos` and `subscribeFast` already set.
|
|
1679
|
+
*/
|
|
1680
|
+
subscribeCompaction(listener) {
|
|
1681
|
+
if (!this.compaction)
|
|
1682
|
+
return () => { };
|
|
1683
|
+
return this.compaction.subscribe(listener);
|
|
1684
|
+
}
|
|
1212
1685
|
/** List usable tools (name + description) for the /tools command. */
|
|
1213
1686
|
listTools() {
|
|
1214
1687
|
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
|