@aragon-agent/cli 0.6.4 → 0.6.8
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 +163 -37
- package/dist/agent/controller.d.ts +49 -8
- package/dist/agent/controller.js +160 -48
- package/dist/agent/headless.d.ts +2 -1
- package/dist/agent/prompt-options.d.ts +15 -0
- package/dist/agent/prompt-options.js +1 -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 +131 -54
- package/dist/commands/builtins.js +12 -1
- package/dist/commands/registry.d.ts +2 -3
- 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/input/interrupt-gesture.d.ts +18 -0
- package/dist/input/interrupt-gesture.js +19 -0
- package/dist/input/pointer-router.d.ts +11 -0
- package/dist/input/pointer-router.js +14 -0
- 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/todo/store.d.ts +8 -13
- package/dist/todo/store.js +13 -14
- package/dist/todo/types.d.ts +2 -1
- package/dist/ui/App.d.ts +2 -0
- package/dist/ui/App.js +233 -183
- 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 +13 -5
- package/dist/ui/Composer.js +7 -11
- package/dist/ui/PromptCaret.d.ts +11 -0
- package/dist/ui/PromptCaret.js +31 -0
- package/dist/ui/PromptInput.d.ts +12 -6
- package/dist/ui/PromptInput.js +50 -16
- package/dist/ui/StatusBar.d.ts +22 -15
- package/dist/ui/StatusBar.js +61 -16
- 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/Transcript.js +1 -0
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-rows.js +50 -9
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/frame-differ.js +2 -19
- package/dist/ui/frame-observer.d.ts +17 -0
- package/dist/ui/frame-observer.js +81 -0
- package/dist/ui/frame-parser.d.ts +7 -0
- package/dist/ui/frame-parser.js +18 -0
- package/dist/ui/layout/AppShell.d.ts +7 -52
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/ScrollIndicator.d.ts +4 -3
- package/dist/ui/layout/ScrollIndicator.js +4 -4
- package/dist/ui/layout/ScrollViewport.d.ts +13 -66
- package/dist/ui/layout/ScrollViewport.js +183 -185
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +3 -51
- package/dist/ui/layout/budget.js +4 -53
- package/dist/ui/layout/document-layout.d.ts +13 -0
- package/dist/ui/layout/document-layout.js +11 -0
- package/dist/ui/layout/follow-state.d.ts +1 -0
- package/dist/ui/layout/follow-state.js +1 -3
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- package/dist/ui/layout/scroll-indicator.d.ts +1 -1
- package/dist/ui/layout/scroll-indicator.js +3 -2
- package/dist/ui/layout/scrollbar-geometry.d.ts +17 -0
- package/dist/ui/layout/scrollbar-geometry.js +13 -0
- 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 +25 -0
- package/dist/ui/layout/todo-layout.js +28 -0
- package/dist/ui/layout/viewport-geometry.d.ts +1 -0
- package/dist/ui/layout/viewport-geometry.js +1 -0
- package/dist/ui/layout/virtual-window.d.ts +1 -0
- package/dist/ui/layout/virtual-window.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +5 -2
- package/dist/ui/screen.d.ts +1 -1
- package/dist/ui/screen.js +4 -6
- package/dist/ui/scrollbar-controller.d.ts +30 -0
- package/dist/ui/scrollbar-controller.js +124 -0
- package/dist/ui/transcript-text.js +6 -2
- package/dist/ui/use-wheel-routing.d.ts +2 -0
- package/dist/ui/use-wheel-routing.js +6 -0
- package/package.json +2 -2
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';
|
|
@@ -44,7 +45,7 @@ import { buildSystemPrompt } from './system-prompt.js';
|
|
|
44
45
|
*
|
|
45
46
|
* `bash`'s own kill grace is what a wedged foreground command costs; this covers
|
|
46
47
|
* 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
|
|
48
|
+
* tool that never settles is exactly why the force-stop rung exists.
|
|
48
49
|
*/
|
|
49
50
|
const ENGINE_UNWIND_GRACE_MS = 3000;
|
|
50
51
|
/** Approval gate used whenever there is provably no human to ask (headless). */
|
|
@@ -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
|
|
@@ -228,6 +246,7 @@ export class AgentController {
|
|
|
228
246
|
* abort lands.
|
|
229
247
|
*/
|
|
230
248
|
runGen = 0;
|
|
249
|
+
startupSequence = 0;
|
|
231
250
|
/**
|
|
232
251
|
* Set SYNCHRONOUSLY by `abort()` before `agent.abort()` (§3.5.4a guard 1).
|
|
233
252
|
* Cleared at the next `agent_start`.
|
|
@@ -500,6 +519,28 @@ export class AgentController {
|
|
|
500
519
|
modelId: config.model,
|
|
501
520
|
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
502
521
|
};
|
|
522
|
+
// --- Context occupancy, part 1: BEFORE the Agent, UNCONDITIONALLY ------
|
|
523
|
+
//
|
|
524
|
+
// Built here for the same C-12 reason the wiring is - lazy `getMessages` /
|
|
525
|
+
// `getSystemPrompt` closures, `attach` after `new Agent` - and built for
|
|
526
|
+
// EVERY session, including one with compaction off, because that is the one
|
|
527
|
+
// whose gauge was broken (P1-4 / P0-2).
|
|
528
|
+
this.contextMeter = new ContextMeter({
|
|
529
|
+
getMessages: () => this.agent.state.messages,
|
|
530
|
+
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
531
|
+
getModelInfo: () => this.getModelInfo(),
|
|
532
|
+
// ONE PREDICATE FOR "IS THE DENOMINATOR REAL". `isPricedModel` is
|
|
533
|
+
// `modelRegistry.getModel(...) !== undefined`, and `getModelInfoFor` falls
|
|
534
|
+
// back to `buildRuntimeModel` - a 128k placeholder AND a zero cost table -
|
|
535
|
+
// for exactly the models it answers `false` for. The two are the same
|
|
536
|
+
// question about the same static table, not a coincidence.
|
|
537
|
+
isWindowKnown: () => this.isPricedModel({
|
|
538
|
+
providerId: this.config.provider,
|
|
539
|
+
modelId: this.config.model,
|
|
540
|
+
}),
|
|
541
|
+
// LIVE, so a settings-screen edit moves the denominator without a relaunch.
|
|
542
|
+
getWindowOverride: () => this.config.contextWindow,
|
|
543
|
+
});
|
|
503
544
|
// --- Context compaction, part 1: BEFORE the Agent ----------------------
|
|
504
545
|
//
|
|
505
546
|
// It needs no agent reference to EXIST — only live config, the key
|
|
@@ -517,6 +558,9 @@ export class AgentController {
|
|
|
517
558
|
getMessages: () => this.agent.state.messages,
|
|
518
559
|
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
519
560
|
notify: (level, text) => deps.notify?.(level, text),
|
|
561
|
+
// ONE METER PER PROCESS (R-1). The wiring forwards it to the compactor,
|
|
562
|
+
// so the trigger and the gauge cannot read two different numbers.
|
|
563
|
+
meter: this.contextMeter,
|
|
520
564
|
})
|
|
521
565
|
: null;
|
|
522
566
|
this.agent = new Agent({
|
|
@@ -567,6 +611,15 @@ export class AgentController {
|
|
|
567
611
|
// subscribed could not run before `new Agent(...)`, and the port has to be
|
|
568
612
|
// inside its constructor argument (C-12 / P0-2).
|
|
569
613
|
this.compaction?.attach((listener) => this.agent.subscribe(listener));
|
|
614
|
+
// --- Context occupancy, part 2: after the Agent exists -----------------
|
|
615
|
+
//
|
|
616
|
+
// ORDER RELATIVE TO THE LINE ABOVE IS DELIBERATELY IRRELEVANT (I-9 / T7).
|
|
617
|
+
// The append direction is order-free because the meter marks itself dirty
|
|
618
|
+
// and re-measures on read; the replace direction is order-free because the
|
|
619
|
+
// splice is announced from `settlePending`'s own synchronous code rather
|
|
620
|
+
// than from a listener. If a future change makes this ordering matter, the
|
|
621
|
+
// fix is at those two sites, not here.
|
|
622
|
+
this.contextMeter.attach((listener) => this.agent.subscribe(listener));
|
|
570
623
|
// --- Fast tier, part 2: after the Agent exists --------------------------
|
|
571
624
|
//
|
|
572
625
|
// `FastWiring` subscribes to the lead's event stream, so it cannot be built
|
|
@@ -803,43 +856,33 @@ export class AgentController {
|
|
|
803
856
|
* underlying promise so headless mode can await completion. `agent.prompt()`
|
|
804
857
|
* never rejects, so callers must not treat resolution as success.
|
|
805
858
|
*/
|
|
806
|
-
async prompt(text) {
|
|
807
|
-
//
|
|
808
|
-
|
|
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
|
-
}
|
|
859
|
+
async prompt(text, options = {}) {
|
|
860
|
+
// Capture BEFORE stopping: synchronous abort listeners may cancel this request.
|
|
861
|
+
const request = ++this.startupSequence;
|
|
829
862
|
try {
|
|
830
|
-
|
|
863
|
+
if (this.agent.state.isRunning) {
|
|
864
|
+
this.stopEngine();
|
|
865
|
+
await this.waitForEngineIdle();
|
|
866
|
+
}
|
|
867
|
+
if (request !== this.startupSequence) {
|
|
868
|
+
return { status: 'not-started', reason: 'cancelled' };
|
|
869
|
+
}
|
|
870
|
+
if (this.agent.state.isRunning)
|
|
871
|
+
throw new Error('The previous run is still stopping.');
|
|
872
|
+
return await this.startPrompt(text, options, request);
|
|
831
873
|
}
|
|
832
874
|
catch (err) {
|
|
833
875
|
const reason = err instanceof Error ? err.message : String(err);
|
|
834
876
|
getLogger().warn('agent', 'prompt_failed', { reason });
|
|
835
877
|
this.notifyHostFn?.('error', `Could not start the run: ${reason}`);
|
|
878
|
+
return { status: 'not-started', reason: 'failed' };
|
|
836
879
|
}
|
|
837
880
|
}
|
|
838
881
|
/**
|
|
839
882
|
* Wait for the engine to unwind, bounded.
|
|
840
883
|
*
|
|
841
884
|
* BOUNDED BECAUSE THE UNBOUNDED CASE IS THE ONE THIS FEATURE IS ABOUT: a tool
|
|
842
|
-
* that never settles is exactly why rung
|
|
885
|
+
* that never settles is exactly why the force-stop rung exists, and `Agent.waitForIdle()`
|
|
843
886
|
* alone would hang here for as long as it hangs there. The budget is the two
|
|
844
887
|
* graces a wedged `bash` can legitimately spend, plus the executor's own.
|
|
845
888
|
*/
|
|
@@ -859,31 +902,25 @@ export class AgentController {
|
|
|
859
902
|
void this.agent.waitForIdle().then(finish, finish);
|
|
860
903
|
});
|
|
861
904
|
}
|
|
862
|
-
/**
|
|
863
|
-
startPrompt(text) {
|
|
864
|
-
// A new user message is a new intent, so the previous turn's tool ceiling
|
|
865
|
-
// expires here and anything a slash command queued is promoted (D-G1 / D-G2).
|
|
866
|
-
// This and `steer()` below are the ONLY two places a user message enters the
|
|
867
|
-
// engine, which is what makes a per-turn scope implementable at all (FG9).
|
|
905
|
+
/** Prepare before clearing the plan, then enter the engine without yielding. */
|
|
906
|
+
async startPrompt(text, options, request) {
|
|
868
907
|
if (this.skillsEnabled)
|
|
869
908
|
this.skills.beginUserTurn();
|
|
870
|
-
// The ask-round budget is per USER TURN and resets here for the same reason
|
|
871
|
-
// the skill frame does. `steer()` deliberately does NOT reset it: a user
|
|
872
|
-
// steering "actually, use Postgres" into a running plan is answering the
|
|
873
|
-
// questions, not buying four more rounds of them.
|
|
874
909
|
this.askRounds = 0;
|
|
875
|
-
// Same asymmetry, same reason (§3.5): a completed plan is dropped HERE
|
|
876
|
-
// rather than at `agent_end`, so the user gets their `7/7 done` moment, and
|
|
877
|
-
// an unfinished one survives "continue" / "now do the rest" but not four
|
|
878
|
-
// unrelated turns.
|
|
879
|
-
this.todos?.beginUserTurn();
|
|
880
|
-
// The fast reviewer is asked whether the run is on track FOR THIS (§3.5.2).
|
|
881
|
-
// Without it the reviewer judges progress without being told the goal, which
|
|
882
|
-
// is the one input it cannot reconstruct from the event stream.
|
|
883
910
|
this.fast?.setGoal(text);
|
|
884
|
-
|
|
911
|
+
if (request !== this.startupSequence) {
|
|
912
|
+
return { status: 'not-started', reason: 'cancelled' };
|
|
913
|
+
}
|
|
914
|
+
this.todos?.beginUserTurn(options.todoPolicy);
|
|
915
|
+
await this.agent.prompt(text);
|
|
916
|
+
return { status: 'finished' };
|
|
885
917
|
}
|
|
918
|
+
/** Cancel pending startup as well as any engine run already in progress. */
|
|
886
919
|
abort() {
|
|
920
|
+
this.startupSequence += 1;
|
|
921
|
+
this.stopEngine();
|
|
922
|
+
}
|
|
923
|
+
stopEngine() {
|
|
887
924
|
// SET BEFORE `agent.abort()`, SYNCHRONOUSLY — guard 1 of §3.5.4a. The loop
|
|
888
925
|
// still emits the batch's final `tool_execution_end` (`agent-loop.ts:249`)
|
|
889
926
|
// after an Esc lands inside the last tool's `await`, and the reviewer's
|
|
@@ -907,7 +944,7 @@ export class AgentController {
|
|
|
907
944
|
this.teamRuntime?.abortAll();
|
|
908
945
|
}
|
|
909
946
|
/**
|
|
910
|
-
*
|
|
947
|
+
* Final rung of the Esc ladder: stop the AGENT, whatever it is blocked on
|
|
911
948
|
* (§3.6 / G3).
|
|
912
949
|
*
|
|
913
950
|
* FOUR STEPS, AND THE FOURTH IS AN ABSENCE.
|
|
@@ -915,7 +952,7 @@ export class AgentController {
|
|
|
915
952
|
* 1. `abort()` - everything it does today, unchanged.
|
|
916
953
|
* 2. `killForeground('force')` - hard-kill every tracked foreground `bash`
|
|
917
954
|
* child. THIS is what actually unblocks a wedged `await tool.execute`;
|
|
918
|
-
* an abort signal a tool ignores is advisory, and rung
|
|
955
|
+
* an abort signal a tool ignores is advisory, and the force-stop rung exists for
|
|
919
956
|
* precisely the tools that ignore it.
|
|
920
957
|
* 3. `runGen += 1` - so `App` can drop the events the engine will keep
|
|
921
958
|
* emitting while it unwinds (I-7). Without it the view bounces straight
|
|
@@ -924,7 +961,7 @@ export class AgentController {
|
|
|
924
961
|
* `Ctrl+C` stops the SERVICES. That asymmetry is the user's own two
|
|
925
962
|
* sentences and it is exactly the kind of thing a later reader will
|
|
926
963
|
* "fix" by making this kill everything - which would mean a user who
|
|
927
|
-
*
|
|
964
|
+
* confirmed a force-stop of a runaway turn also lost the dev server they
|
|
928
965
|
* had been working against for an hour. It is invariant I-4.
|
|
929
966
|
*
|
|
930
967
|
* UNCONDITIONAL ON `bash.background` (P1-6 / D-9). The supervisor is always
|
|
@@ -1207,6 +1244,7 @@ export class AgentController {
|
|
|
1207
1244
|
this.teamRuntime?.dispose();
|
|
1208
1245
|
this.fast?.dispose();
|
|
1209
1246
|
this.compaction?.dispose();
|
|
1247
|
+
this.contextMeter.dispose();
|
|
1210
1248
|
// REAP BEFORE RELEASING THE HOOK, in that order: disposal is a teardown, and
|
|
1211
1249
|
// a service that outlives it has nothing left that could ever stop it.
|
|
1212
1250
|
this.procs.reapSync();
|
|
@@ -1337,6 +1375,13 @@ export class AgentController {
|
|
|
1337
1375
|
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1338
1376
|
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1339
1377
|
this.compaction?.onConfigChanged();
|
|
1378
|
+
// THE DENOMINATOR MOVED, NOT THE HISTORY (context-usage-gauge-accuracy §3.4).
|
|
1379
|
+
// A re-measure, deliberately NOT a reset: the measured base is still a true
|
|
1380
|
+
// statement about the messages, and throwing it away would swap a correct
|
|
1381
|
+
// numerator for a whole-history estimate on a common operation. The
|
|
1382
|
+
// numerator's calibration is one turn stale until the next `turn_end`
|
|
1383
|
+
// (RV-13), which is the trade this call names.
|
|
1384
|
+
this.contextMeter.onWindowChanged();
|
|
1340
1385
|
}
|
|
1341
1386
|
setThinkingLevel(level) {
|
|
1342
1387
|
this.config = { ...this.config, thinkingLevel: level };
|
|
@@ -1432,10 +1477,18 @@ export class AgentController {
|
|
|
1432
1477
|
}
|
|
1433
1478
|
clearMessages() {
|
|
1434
1479
|
this.agent.clearMessages();
|
|
1480
|
+
this.startupSequence += 1;
|
|
1435
1481
|
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
1436
1482
|
// was CLEARED, not appended to, so the prefix length the last measurement
|
|
1437
1483
|
// covered indexes an array that no longer exists.
|
|
1484
|
+
//
|
|
1485
|
+
// BOTH LINES, AND THE SECOND IS NOT REDUNDANT (context-usage-gauge-accuracy
|
|
1486
|
+
// I-9). The wiring is `null` for a `--no-compaction` session, so it is the
|
|
1487
|
+
// meter call that guarantees a re-measure - deleting it "to de-duplicate"
|
|
1488
|
+
// reintroduces P0-2 for exactly the sessions this feature was written for. A
|
|
1489
|
+
// deep reset is idempotent, so both firing costs nothing.
|
|
1438
1490
|
this.compaction?.onHistoryReplaced();
|
|
1491
|
+
this.contextMeter.onHistoryReplaced();
|
|
1439
1492
|
// A new conversation has no loaded skills, so the "already loaded earlier
|
|
1440
1493
|
// in this conversation" hint would otherwise start lying (P2-9).
|
|
1441
1494
|
this.skills.getRegistry().clearActive();
|
|
@@ -1452,10 +1505,19 @@ export class AgentController {
|
|
|
1452
1505
|
}
|
|
1453
1506
|
replaceMessages(messages) {
|
|
1454
1507
|
this.agent.replaceMessages(messages);
|
|
1508
|
+
this.startupSequence += 1;
|
|
1455
1509
|
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
1456
1510
|
// `estimateAppendedTokens` bounds-checks as well, so a MISSED site costs
|
|
1457
1511
|
// accuracy and never correctness; that belt does not make this call optional.
|
|
1512
|
+
//
|
|
1513
|
+
// THE SECOND LINE IS THE P0-2 FIX. Before it, `/resume` of a 180k-token
|
|
1514
|
+
// session left the gauge and `/compact status` reading 0 %: nothing
|
|
1515
|
+
// re-measured and nothing published, so the mount-time "empty history"
|
|
1516
|
+
// reading stood until the next completed turn. The DEEP reset also drops
|
|
1517
|
+
// `estimateOffset`, because `/resume` can change the model in the same
|
|
1518
|
+
// breath (I-8).
|
|
1458
1519
|
this.compaction?.onHistoryReplaced();
|
|
1520
|
+
this.contextMeter.onHistoryReplaced();
|
|
1459
1521
|
}
|
|
1460
1522
|
getMessages() {
|
|
1461
1523
|
return this.agent.state.messages;
|
|
@@ -1571,6 +1633,56 @@ export class AgentController {
|
|
|
1571
1633
|
return () => { };
|
|
1572
1634
|
return this.fast.subscribe(listener);
|
|
1573
1635
|
}
|
|
1636
|
+
/**
|
|
1637
|
+
* The FIFTH thin forwarder: the current fast-tier snapshot, or `null` when
|
|
1638
|
+
* this session never registered a tier at all
|
|
1639
|
+
* (web-use-tier-cooperation-and-control-closure §4.2.1).
|
|
1640
|
+
*
|
|
1641
|
+
* `null` and "a snapshot with `live: false`" are different answers and callers
|
|
1642
|
+
* depend on the difference: the first means "there is no tier here", the
|
|
1643
|
+
* second means "there is one and it is not usable right now".
|
|
1644
|
+
*
|
|
1645
|
+
* Deliberately NOT folded into the existing `review_end` event: that shape is
|
|
1646
|
+
* consumed by `App` too, and this round promised to be purely additive.
|
|
1647
|
+
*/
|
|
1648
|
+
fastSnapshot() {
|
|
1649
|
+
return this.fast?.snapshot() ?? null;
|
|
1650
|
+
}
|
|
1651
|
+
// -----------------------------------------------------------------------
|
|
1652
|
+
// Context occupancy (context-usage-gauge-accuracy §4.2) — THREE FORWARDERS
|
|
1653
|
+
//
|
|
1654
|
+
// ALL THREE ARE AVAILABLE UNCONDITIONALLY, which is the whole difference
|
|
1655
|
+
// between this block and the compaction one below it: there is no
|
|
1656
|
+
// `?? offSomething()` fallback here, because the meter always exists.
|
|
1657
|
+
// -----------------------------------------------------------------------
|
|
1658
|
+
/**
|
|
1659
|
+
* Subscribe to occupancy publications. Returns an unsubscribe function.
|
|
1660
|
+
*
|
|
1661
|
+
* THE SOLE UPSTREAM OF `ViewState.context` (I-1). The gauge used to have two
|
|
1662
|
+
* writers - `turnEnd` and a `contextTokensEstimated` dispatch fired from two
|
|
1663
|
+
* places in `App` - which is the entire cause of P0-1: both wrote in the same
|
|
1664
|
+
* synchronous fan-out and the second one won, restoring the pre-compaction
|
|
1665
|
+
* figure one statement after the bar had correctly fallen. One writer makes
|
|
1666
|
+
* that class of bug unrepresentable rather than merely fixed.
|
|
1667
|
+
*/
|
|
1668
|
+
subscribeContextUsage(listener) {
|
|
1669
|
+
return this.contextMeter.subscribe(listener);
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Occupancy right now, re-measuring if the history has moved.
|
|
1673
|
+
*
|
|
1674
|
+
* `/context` READS THIS AND NOT `getCompactionSnapshot().pressure` (RV-8). The
|
|
1675
|
+
* latter returns `offCompactionSnapshot()`'s hardcoded zero pressure when
|
|
1676
|
+
* compaction was never registered, so a report built on it would read 0 % for
|
|
1677
|
+
* precisely the sessions this feature exists to fix.
|
|
1678
|
+
*/
|
|
1679
|
+
getContextUsage() {
|
|
1680
|
+
return this.contextMeter.currentUsage();
|
|
1681
|
+
}
|
|
1682
|
+
/** The meter itself, for the wiring's single-instance assertion and tests. */
|
|
1683
|
+
getContextMeter() {
|
|
1684
|
+
return this.contextMeter;
|
|
1685
|
+
}
|
|
1574
1686
|
// -----------------------------------------------------------------------
|
|
1575
1687
|
// Context compaction (context-auto-compaction §3.2.1) — THIN FORWARDERS
|
|
1576
1688
|
// -----------------------------------------------------------------------
|
package/dist/agent/headless.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* derived from the event stream (R2), not from the resolved promise:
|
|
5
5
|
* 0 = success, 1 = agent error (incl. a silently-swallowed throw), 2 = config.
|
|
6
6
|
*/
|
|
7
|
+
import type { PromptOutcome } from './prompt-options.js';
|
|
7
8
|
import type { AgentEvent, ModelInfo } from '@aragon-agent/core';
|
|
8
9
|
import type { PreflightResult } from './controller.js';
|
|
9
10
|
import type { TeamEvent } from '../team/types.js';
|
|
@@ -15,7 +16,7 @@ import { type FollowThroughMode } from '../todo/follow-through.js';
|
|
|
15
16
|
export interface HeadlessController {
|
|
16
17
|
preflight(): PreflightResult;
|
|
17
18
|
subscribe(listener: (event: AgentEvent) => void): () => void;
|
|
18
|
-
prompt(text: string): Promise<void>;
|
|
19
|
+
prompt(text: string): Promise<void | PromptOutcome>;
|
|
19
20
|
getModelInfo(): ModelInfo;
|
|
20
21
|
/**
|
|
21
22
|
* The CLI-local team stream — OPTIONAL, deliberately (team-subagents §3.12 /
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Explicit TUI task boundary; omission retains legacy programmatic behavior. */
|
|
2
|
+
export interface PromptOptions {
|
|
3
|
+
todoPolicy?: 'new-task' | 'continue';
|
|
4
|
+
}
|
|
5
|
+
/** History recording is independent of whether this message continues a plan. */
|
|
6
|
+
export interface SubmitMessageOptions extends PromptOptions {
|
|
7
|
+
userInitiated?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Startup outcome only; engine events still describe the result of the run. */
|
|
10
|
+
export type PromptOutcome = {
|
|
11
|
+
status: 'finished';
|
|
12
|
+
} | {
|
|
13
|
+
status: 'not-started';
|
|
14
|
+
reason: 'cancelled' | 'failed';
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
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;
|