@aragon-agent/cli 0.6.7 → 0.6.9
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 +27 -0
- package/README.md +107 -77
- package/dist/agent/controller.d.ts +11 -7
- package/dist/agent/controller.js +35 -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 +7 -113
- package/dist/agent/reducer.js +0 -62
- package/dist/cli.js +111 -182
- package/dist/commands/builtins.js +3 -1
- package/dist/commands/perf.d.ts +32 -2
- package/dist/commands/perf.js +32 -2
- package/dist/commands/registry.d.ts +4 -5
- package/dist/config/env.js +0 -130
- package/dist/config/load.d.ts +0 -2
- package/dist/config/load.js +2 -31
- package/dist/config/schema.d.ts +1 -14
- package/dist/config/schema.js +0 -1
- package/dist/config/store.js +2 -0
- 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/logging/install.d.ts +2 -2
- package/dist/logging/install.js +2 -2
- package/dist/todo/follow-through.d.ts +0 -4
- package/dist/todo/follow-through.js +0 -4
- package/dist/todo/limits.d.ts +0 -10
- package/dist/todo/limits.js +0 -10
- package/dist/todo/prompt.d.ts +0 -15
- 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/ActivityLine.d.ts +26 -34
- package/dist/ui/ActivityLine.js +30 -19
- package/dist/ui/App.d.ts +3 -41
- package/dist/ui/App.js +223 -247
- package/dist/ui/BottomStatusRow.d.ts +1 -3
- package/dist/ui/BottomStatusRow.js +4 -8
- package/dist/ui/Composer.d.ts +38 -5
- package/dist/ui/Composer.js +45 -23
- package/dist/ui/Header.d.ts +2 -16
- package/dist/ui/Header.js +3 -18
- package/dist/ui/Logo.d.ts +1 -17
- package/dist/ui/Logo.js +0 -16
- package/dist/ui/ModeChip.d.ts +7 -18
- package/dist/ui/ModeChip.js +9 -0
- package/dist/ui/PromptCaret.d.ts +11 -0
- package/dist/ui/PromptCaret.js +31 -0
- package/dist/ui/PromptInput.d.ts +10 -33
- package/dist/ui/PromptInput.js +37 -20
- package/dist/ui/StatusBar.d.ts +1 -11
- package/dist/ui/StatusBar.js +14 -8
- package/dist/ui/ToastStack.d.ts +2 -18
- package/dist/ui/ToastStack.js +4 -11
- package/dist/ui/Transcript.d.ts +5 -71
- package/dist/ui/Transcript.js +10 -199
- package/dist/ui/composer-rows.js +50 -9
- package/dist/ui/entries/AssistantEntry.d.ts +0 -23
- package/dist/ui/entries/AssistantEntry.js +3 -4
- package/dist/ui/entries/DiffView.d.ts +1 -7
- package/dist/ui/entries/DiffView.js +2 -2
- package/dist/ui/entries/ToolCard.d.ts +0 -6
- package/dist/ui/entries/ToolCard.js +4 -13
- package/dist/ui/frame-differ.d.ts +13 -0
- package/dist/ui/frame-differ.js +37 -24
- 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/ink-runtime.d.ts +1 -0
- package/dist/ui/ink-runtime.js +21 -0
- package/dist/ui/layout/AppShell.d.ts +4 -58
- package/dist/ui/layout/AppShell.js +3 -6
- package/dist/ui/layout/OverlayFrame.d.ts +1 -25
- package/dist/ui/layout/OverlayFrame.js +0 -24
- package/dist/ui/layout/ScrollIndicator.d.ts +4 -3
- package/dist/ui/layout/ScrollIndicator.js +4 -4
- package/dist/ui/layout/ScrollViewport.d.ts +16 -66
- package/dist/ui/layout/ScrollViewport.js +195 -185
- 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/frame.d.ts +0 -22
- package/dist/ui/layout/frame.js +0 -36
- 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/todo-layout.d.ts +2 -3
- package/dist/ui/layout/todo-layout.js +9 -9
- package/dist/ui/layout/viewport-geometry.d.ts +2 -1
- package/dist/ui/layout/viewport-geometry.js +2 -1
- package/dist/ui/layout/virtual-window.d.ts +1 -0
- package/dist/ui/layout/virtual-window.js +1 -1
- package/dist/ui/live-clamp.d.ts +1 -29
- package/dist/ui/live-clamp.js +1 -29
- package/dist/ui/overlays/HelpOverlay.js +6 -2
- package/dist/ui/run-status-row.d.ts +61 -0
- package/dist/ui/run-status-row.js +70 -0
- 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.d.ts +0 -8
- package/dist/ui/transcript-text.js +0 -8
- package/dist/ui/use-startup-notices.d.ts +1 -57
- package/dist/ui/use-startup-notices.js +3 -54
- package/dist/ui/use-wheel-routing.d.ts +3 -1
- package/dist/ui/use-wheel-routing.js +6 -0
- package/package.json +2 -2
- package/dist/ui/TodoStrip.d.ts +0 -36
- package/dist/ui/TodoStrip.js +0 -32
package/dist/agent/controller.js
CHANGED
|
@@ -45,7 +45,7 @@ import { buildSystemPrompt } from './system-prompt.js';
|
|
|
45
45
|
*
|
|
46
46
|
* `bash`'s own kill grace is what a wedged foreground command costs; this covers
|
|
47
47
|
* the loop's own unwinding on top of it. Waiting FOREVER is not an option - a
|
|
48
|
-
* tool that never settles is exactly why rung
|
|
48
|
+
* tool that never settles is exactly why the force-stop rung exists.
|
|
49
49
|
*/
|
|
50
50
|
const ENGINE_UNWIND_GRACE_MS = 3000;
|
|
51
51
|
/** Approval gate used whenever there is provably no human to ask (headless). */
|
|
@@ -246,6 +246,7 @@ export class AgentController {
|
|
|
246
246
|
* abort lands.
|
|
247
247
|
*/
|
|
248
248
|
runGen = 0;
|
|
249
|
+
startupSequence = 0;
|
|
249
250
|
/**
|
|
250
251
|
* Set SYNCHRONOUSLY by `abort()` before `agent.abort()` (§3.5.4a guard 1).
|
|
251
252
|
* Cleared at the next `agent_start`.
|
|
@@ -855,43 +856,33 @@ export class AgentController {
|
|
|
855
856
|
* underlying promise so headless mode can await completion. `agent.prompt()`
|
|
856
857
|
* never rejects, so callers must not treat resolution as success.
|
|
857
858
|
*/
|
|
858
|
-
async prompt(text) {
|
|
859
|
-
//
|
|
860
|
-
|
|
861
|
-
//
|
|
862
|
-
// `App` calls this as `void controller.prompt(...)`, and
|
|
863
|
-
// `logging/install.ts` routes an `unhandledRejection` into `handleFatal`,
|
|
864
|
-
// which EXITS THE PROCESS. `Agent.prompt()` throws SYNCHRONOUSLY while
|
|
865
|
-
// `Agent.running` is true - so before the force-stop ladder existed this was
|
|
866
|
-
// merely nearly-true (the view was only ever idle when the engine was), and
|
|
867
|
-
// rung two makes the view idle while the engine is still unwinding. Without
|
|
868
|
-
// the wait and the guard below, "Esc, Esc, then type" would kill the CLI on
|
|
869
|
-
// the very screen this feature exists to rescue.
|
|
870
|
-
//
|
|
871
|
-
// TWO PARTS, BOTH REQUIRED. First WAIT the engine out, bounded, so the
|
|
872
|
-
// common case (the engine unwinds in a few hundred ms) starts a real run
|
|
873
|
-
// rather than reporting a failure. Then swallow anything that still escapes
|
|
874
|
-
// and surface it through the existing notify channel, because a message that
|
|
875
|
-
// cannot be started must produce a visible sentence - never a crash, and
|
|
876
|
-
// never silence.
|
|
877
|
-
if (this.agent.state.isRunning) {
|
|
878
|
-
this.abort();
|
|
879
|
-
await this.waitForEngineIdle();
|
|
880
|
-
}
|
|
859
|
+
async prompt(text, options = {}) {
|
|
860
|
+
// Capture BEFORE stopping: synchronous abort listeners may cancel this request.
|
|
861
|
+
const request = ++this.startupSequence;
|
|
881
862
|
try {
|
|
882
|
-
|
|
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);
|
|
883
873
|
}
|
|
884
874
|
catch (err) {
|
|
885
875
|
const reason = err instanceof Error ? err.message : String(err);
|
|
886
876
|
getLogger().warn('agent', 'prompt_failed', { reason });
|
|
887
877
|
this.notifyHostFn?.('error', `Could not start the run: ${reason}`);
|
|
878
|
+
return { status: 'not-started', reason: 'failed' };
|
|
888
879
|
}
|
|
889
880
|
}
|
|
890
881
|
/**
|
|
891
882
|
* Wait for the engine to unwind, bounded.
|
|
892
883
|
*
|
|
893
884
|
* BOUNDED BECAUSE THE UNBOUNDED CASE IS THE ONE THIS FEATURE IS ABOUT: a tool
|
|
894
|
-
* that never settles is exactly why rung
|
|
885
|
+
* that never settles is exactly why the force-stop rung exists, and `Agent.waitForIdle()`
|
|
895
886
|
* alone would hang here for as long as it hangs there. The budget is the two
|
|
896
887
|
* graces a wedged `bash` can legitimately spend, plus the executor's own.
|
|
897
888
|
*/
|
|
@@ -911,31 +902,25 @@ export class AgentController {
|
|
|
911
902
|
void this.agent.waitForIdle().then(finish, finish);
|
|
912
903
|
});
|
|
913
904
|
}
|
|
914
|
-
/**
|
|
915
|
-
startPrompt(text) {
|
|
916
|
-
// A new user message is a new intent, so the previous turn's tool ceiling
|
|
917
|
-
// expires here and anything a slash command queued is promoted (D-G1 / D-G2).
|
|
918
|
-
// This and `steer()` below are the ONLY two places a user message enters the
|
|
919
|
-
// 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) {
|
|
920
907
|
if (this.skillsEnabled)
|
|
921
908
|
this.skills.beginUserTurn();
|
|
922
|
-
// The ask-round budget is per USER TURN and resets here for the same reason
|
|
923
|
-
// the skill frame does. `steer()` deliberately does NOT reset it: a user
|
|
924
|
-
// steering "actually, use Postgres" into a running plan is answering the
|
|
925
|
-
// questions, not buying four more rounds of them.
|
|
926
909
|
this.askRounds = 0;
|
|
927
|
-
// Same asymmetry, same reason (§3.5): a completed plan is dropped HERE
|
|
928
|
-
// rather than at `agent_end`, so the user gets their `7/7 done` moment, and
|
|
929
|
-
// an unfinished one survives "continue" / "now do the rest" but not four
|
|
930
|
-
// unrelated turns.
|
|
931
|
-
this.todos?.beginUserTurn();
|
|
932
|
-
// The fast reviewer is asked whether the run is on track FOR THIS (§3.5.2).
|
|
933
|
-
// Without it the reviewer judges progress without being told the goal, which
|
|
934
|
-
// is the one input it cannot reconstruct from the event stream.
|
|
935
910
|
this.fast?.setGoal(text);
|
|
936
|
-
|
|
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' };
|
|
937
917
|
}
|
|
918
|
+
/** Cancel pending startup as well as any engine run already in progress. */
|
|
938
919
|
abort() {
|
|
920
|
+
this.startupSequence += 1;
|
|
921
|
+
this.stopEngine();
|
|
922
|
+
}
|
|
923
|
+
stopEngine() {
|
|
939
924
|
// SET BEFORE `agent.abort()`, SYNCHRONOUSLY — guard 1 of §3.5.4a. The loop
|
|
940
925
|
// still emits the batch's final `tool_execution_end` (`agent-loop.ts:249`)
|
|
941
926
|
// after an Esc lands inside the last tool's `await`, and the reviewer's
|
|
@@ -959,7 +944,7 @@ export class AgentController {
|
|
|
959
944
|
this.teamRuntime?.abortAll();
|
|
960
945
|
}
|
|
961
946
|
/**
|
|
962
|
-
*
|
|
947
|
+
* Final rung of the Esc ladder: stop the AGENT, whatever it is blocked on
|
|
963
948
|
* (§3.6 / G3).
|
|
964
949
|
*
|
|
965
950
|
* FOUR STEPS, AND THE FOURTH IS AN ABSENCE.
|
|
@@ -967,7 +952,7 @@ export class AgentController {
|
|
|
967
952
|
* 1. `abort()` - everything it does today, unchanged.
|
|
968
953
|
* 2. `killForeground('force')` - hard-kill every tracked foreground `bash`
|
|
969
954
|
* child. THIS is what actually unblocks a wedged `await tool.execute`;
|
|
970
|
-
* 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
|
|
971
956
|
* precisely the tools that ignore it.
|
|
972
957
|
* 3. `runGen += 1` - so `App` can drop the events the engine will keep
|
|
973
958
|
* emitting while it unwinds (I-7). Without it the view bounces straight
|
|
@@ -976,7 +961,7 @@ export class AgentController {
|
|
|
976
961
|
* `Ctrl+C` stops the SERVICES. That asymmetry is the user's own two
|
|
977
962
|
* sentences and it is exactly the kind of thing a later reader will
|
|
978
963
|
* "fix" by making this kill everything - which would mean a user who
|
|
979
|
-
*
|
|
964
|
+
* confirmed a force-stop of a runaway turn also lost the dev server they
|
|
980
965
|
* had been working against for an hour. It is invariant I-4.
|
|
981
966
|
*
|
|
982
967
|
* UNCONDITIONAL ON `bash.background` (P1-6 / D-9). The supervisor is always
|
|
@@ -1492,6 +1477,7 @@ export class AgentController {
|
|
|
1492
1477
|
}
|
|
1493
1478
|
clearMessages() {
|
|
1494
1479
|
this.agent.clearMessages();
|
|
1480
|
+
this.startupSequence += 1;
|
|
1495
1481
|
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
1496
1482
|
// was CLEARED, not appended to, so the prefix length the last measurement
|
|
1497
1483
|
// covered indexes an array that no longer exists.
|
|
@@ -1519,6 +1505,7 @@ export class AgentController {
|
|
|
1519
1505
|
}
|
|
1520
1506
|
replaceMessages(messages) {
|
|
1521
1507
|
this.agent.replaceMessages(messages);
|
|
1508
|
+
this.startupSequence += 1;
|
|
1522
1509
|
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
1523
1510
|
// `estimateAppendedTokens` bounds-checks as well, so a MISSED site costs
|
|
1524
1511
|
// accuracy and never correctness; that belt does not make this call optional.
|
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
|
@@ -115,20 +115,7 @@ export type Entry = {
|
|
|
115
115
|
kind: 'notice';
|
|
116
116
|
level: NoticeLevel;
|
|
117
117
|
text: string;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* A team dispatch (team-subagents §5.3). JSON-SERIALIZABLE by construction, so
|
|
121
|
-
* `/save` and `/resume` need no format change.
|
|
122
|
-
*
|
|
123
|
-
* `active: true` is normalized away ON LOAD, in `session/persist.ts`, not here
|
|
124
|
-
* (P1-5). A session saved mid-dispatch would otherwise resume with a card that
|
|
125
|
-
* claims to be running while nothing is: it spins forever, and because
|
|
126
|
-
* `Transcript`'s settled boundary is MONOTONIC an entry that never settles
|
|
127
|
-
* never reaches `<Static>` and is re-rendered on every frame for the rest of
|
|
128
|
-
* the session. The reducer never legitimately sees a stale-active entry — only
|
|
129
|
-
* the file does — which is why the fix belongs in the load path.
|
|
130
|
-
*/
|
|
131
|
-
| {
|
|
118
|
+
} | {
|
|
132
119
|
id: string;
|
|
133
120
|
kind: 'team';
|
|
134
121
|
dispatchId: string;
|
|
@@ -137,20 +124,7 @@ export type Entry = {
|
|
|
137
124
|
aborted: boolean;
|
|
138
125
|
durationMs?: number;
|
|
139
126
|
active: boolean;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* The turn's todo checklist (todo-plan-execution §3.8). ONE per user turn,
|
|
143
|
-
* rewritten in place — a 7-item list updated twice per item would otherwise be
|
|
144
|
-
* 14 near-identical tool cards in the one surface the user reads most.
|
|
145
|
-
*
|
|
146
|
-
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
147
|
-
*
|
|
148
|
-
* `live: true` is normalized away ON LOAD, in `session/persist.ts`, for the
|
|
149
|
-
* same reason the team entry's `active` is (C-5): `Transcript`'s settled
|
|
150
|
-
* boundary is MONOTONIC, so an entry that never settles never reaches
|
|
151
|
-
* `<Static>` and is re-rendered on every frame for the rest of the session.
|
|
152
|
-
*/
|
|
153
|
-
| {
|
|
127
|
+
} | {
|
|
154
128
|
id: string;
|
|
155
129
|
kind: 'todo';
|
|
156
130
|
items: TodoItem[];
|
|
@@ -165,21 +139,7 @@ export type Entry = {
|
|
|
165
139
|
* card this session produced.
|
|
166
140
|
*/
|
|
167
141
|
interrupted?: boolean;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* The turn's retry story (llm-api-retry-backoff §6.4). ONE per turn, REWRITTEN
|
|
171
|
-
* IN PLACE — ten retries rendered as ten notices would bury the transcript in
|
|
172
|
-
* the one moment the user most needs it legible.
|
|
173
|
-
*
|
|
174
|
-
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
175
|
-
*
|
|
176
|
-
* `phase: 'waiting' | 'retrying'` is normalized away ON LOAD, in
|
|
177
|
-
* `session/persist.ts`, exactly as the team entry's `active` and the todo
|
|
178
|
-
* card's `live` are (R-10): `Transcript`'s settled boundary is MONOTONIC, so a
|
|
179
|
-
* card that never settles never reaches `<Static>` and is re-rendered on every
|
|
180
|
-
* frame for the rest of the session.
|
|
181
|
-
*/
|
|
182
|
-
| {
|
|
142
|
+
} | {
|
|
183
143
|
id: string;
|
|
184
144
|
kind: 'retry';
|
|
185
145
|
/** 1-based retry index of the LATEST attempt this card has seen. */
|
|
@@ -195,22 +155,7 @@ export type Entry = {
|
|
|
195
155
|
/** Filled on settle. */
|
|
196
156
|
totalRetries?: number;
|
|
197
157
|
elapsedMs?: number;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* One fast-model review (fast-model-tier §5.4). ONE CARD PER REVIEW, never
|
|
201
|
-
* rewritten across reviews — a critique is a single event, unlike the todo
|
|
202
|
-
* card's per-turn checklist.
|
|
203
|
-
*
|
|
204
|
-
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
205
|
-
*
|
|
206
|
-
* `live: true` is normalized away ON LOAD, in `session/persist.ts`, next to
|
|
207
|
-
* `team.active`, `todo.live` and the retry card's phase, and for the identical
|
|
208
|
-
* reason (C-5): `Transcript`'s settled boundary is MONOTONIC, so an entry that
|
|
209
|
-
* never settles never reaches `<Static>` and is re-rendered on every frame for
|
|
210
|
-
* the rest of the session. The reducer never legitimately sees a stale-live
|
|
211
|
-
* entry — only the file does.
|
|
212
|
-
*/
|
|
213
|
-
| {
|
|
158
|
+
} | {
|
|
214
159
|
id: string;
|
|
215
160
|
kind: 'fast';
|
|
216
161
|
/** 1-based within the session, so the card can name itself. */
|
|
@@ -224,27 +169,7 @@ export type Entry = {
|
|
|
224
169
|
turn: number;
|
|
225
170
|
durationMs?: number;
|
|
226
171
|
live: boolean;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* One context compaction (context-auto-compaction §5.5). ONE CARD PER
|
|
230
|
-
* COMPACTION, rewritten once when the call returns — like the fast card, and
|
|
231
|
-
* unlike the todo card's per-turn checklist.
|
|
232
|
-
*
|
|
233
|
-
* JSON-SERIALIZABLE by construction, so `/save` and `/resume` need no format
|
|
234
|
-
* change.
|
|
235
|
-
*
|
|
236
|
-
* `live: true` is normalized away ON LOAD, in `session/persist.ts`, next to
|
|
237
|
-
* `team.active`, `todo.live`, the retry card's phase and `fast.live`, and for
|
|
238
|
-
* the identical reason (C-8): `Transcript`'s settled boundary is MONOTONIC, so
|
|
239
|
-
* an entry that never settles never reaches `<Static>` and is re-rendered on
|
|
240
|
-
* every frame for the rest of the session.
|
|
241
|
-
*
|
|
242
|
-
* EVERY STRING ON THIS CARD DESCRIBES THE HISTORY, NEVER THE SCREEN (P2-9).
|
|
243
|
-
* `/clear` empties the transcript view but not `messages`, so a compaction can
|
|
244
|
-
* legitimately fire against a visually empty transcript and report
|
|
245
|
-
* "112 -> 9 messages" with nine visible rows.
|
|
246
|
-
*/
|
|
247
|
-
| {
|
|
172
|
+
} | {
|
|
248
173
|
id: string;
|
|
249
174
|
kind: 'compaction';
|
|
250
175
|
/** 1-based within the session, so the card can name itself. */
|
|
@@ -275,29 +200,7 @@ export type Entry = {
|
|
|
275
200
|
charsRemoved: number;
|
|
276
201
|
};
|
|
277
202
|
live: boolean;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* One supervised background service (background-service-supervision §3.10).
|
|
281
|
-
*
|
|
282
|
-
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
283
|
-
*
|
|
284
|
-
* IT IS THE ONE LIVE CARD THAT DOES **NOT** BLOCK THE SETTLED BOUNDARY ONCE IT
|
|
285
|
-
* IS UP (D-11 / P0-4). Every other live clause in
|
|
286
|
-
* `Transcript.tsx::computeSettledCount` is bounded by an OPERATION — a
|
|
287
|
-
* dispatch, a turn, one LLM call, one countdown — and each carries a comment
|
|
288
|
-
* saying that an entry which never settles is re-rendered on every frame for
|
|
289
|
-
* the rest of the session. A service is bounded by the USER'S INTENT: a dev
|
|
290
|
-
* server left up for an hour is the normal case. So only `starting` blocks
|
|
291
|
-
* (bounded by `readyTimeoutMs`), a `ready`/`running` card commits its body to
|
|
292
|
-
* `<Static>`, and a later terminal transition appends a NEW one-row entry
|
|
293
|
-
* rather than trying to rewrite a card `<Static>` has already printed.
|
|
294
|
-
*
|
|
295
|
-
* `terminal: true` marks that second entry. `session/persist.ts` rewrites any
|
|
296
|
-
* restored non-terminal card to `stopped`, next to `team.active`, `todo.live`,
|
|
297
|
-
* the retry phase, `fast.live` and `compaction.live`, and for the identical
|
|
298
|
-
* reason.
|
|
299
|
-
*/
|
|
300
|
-
| {
|
|
203
|
+
} | {
|
|
301
204
|
id: string;
|
|
302
205
|
kind: 'service';
|
|
303
206
|
/** `s1`, `s2`, ... — the id the model quotes back. */
|
|
@@ -677,16 +580,7 @@ export type ViewAction = {
|
|
|
677
580
|
} | {
|
|
678
581
|
type: 'serviceUpdate';
|
|
679
582
|
service: ServiceSnapshot;
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* A service reached a terminal state.
|
|
683
|
-
*
|
|
684
|
-
* IT APPENDS A NEW ONE-ROW ENTRY rather than rewriting the card (D-11). The
|
|
685
|
-
* card may already have been printed into `<Static>`, and `<Static>` cannot
|
|
686
|
-
* un-print — so any design that rewrites a printed card is wrong whatever it
|
|
687
|
-
* claims. A second event at a second time is also simply more honest.
|
|
688
|
-
*/
|
|
689
|
-
| {
|
|
583
|
+
} | {
|
|
690
584
|
type: 'serviceEnd';
|
|
691
585
|
service: ServiceSnapshot;
|
|
692
586
|
};
|
package/dist/agent/reducer.js
CHANGED
|
@@ -382,31 +382,6 @@ function settleRetryCard(state, entries, phase) {
|
|
|
382
382
|
retryEntryId: undefined,
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
|
-
/**
|
|
386
|
-
* EVERY PATH THAT REACHES `status: 'idle'` RELEASES THE LIVE TAIL (D-35 / AC-40).
|
|
387
|
-
*
|
|
388
|
-
* `runEnd` finalizes only the streaming ASSISTANT entry and `abortMark` maps only
|
|
389
|
-
* assistant entries, so neither touches a tool entry stranded at
|
|
390
|
-
* `status: 'running'` -- the engine's own idle watchdog, a provider error mid-call
|
|
391
|
-
* and `Esc` all produce one. Today that costs a single spinner row. With a tail
|
|
392
|
-
* attached it costs a permanent multi-row card whose stall counter is frozen at
|
|
393
|
-
* its last value, because the 200 ms ticker that feeds `nowSec` is torn down the
|
|
394
|
-
* instant `state.status` leaves `running` -- and, worse, `computeSettledCount`
|
|
395
|
-
* breaks on a non-settled tool entry, so that card pins `Transcript`'s MONOTONIC
|
|
396
|
-
* settled boundary and re-renders the tail every frame for the rest of the
|
|
397
|
-
* session. The rule and the stakes are `settleRetryCard`'s, verbatim.
|
|
398
|
-
*
|
|
399
|
-
* THE STATUS IS DELIBERATELY NOT CHANGED. That is a different decision with
|
|
400
|
-
* different consequences for `bashBadge` and for the run's own bookkeeping, and
|
|
401
|
-
* it is not this round's to make. Only the tail is released.
|
|
402
|
-
*
|
|
403
|
-
* `liveSeq` is BUMPED rather than cleared, and that is not bookkeeping: clearing
|
|
404
|
-
* `live` is a non-append mutation, and with the status untouched nothing else in
|
|
405
|
-
* the tool revision moves -- so without the bump the height cache would keep
|
|
406
|
-
* serving the multi-row measurement for a card that now draws one row (I-L3-1).
|
|
407
|
-
* Entries with no tail are returned by identity, which is what keeps
|
|
408
|
-
* `EntryView`'s memo boundary intact for the rest of the transcript.
|
|
409
|
-
*/
|
|
410
385
|
function releaseLiveTails(entries) {
|
|
411
386
|
let touched = false;
|
|
412
387
|
const next = entries.map((e) => {
|
|
@@ -1218,22 +1193,6 @@ export function viewReducer(state, action) {
|
|
|
1218
1193
|
entries: mapEntry(state.entries, known, (e) => e.kind === 'retry' ? { ...e, phase: 'retrying', resumeAt: undefined } : e),
|
|
1219
1194
|
};
|
|
1220
1195
|
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Rewind the view for a mid-stream restart.
|
|
1223
|
-
*
|
|
1224
|
-
* THE ONLY ACTION IN THIS REDUCER THAT SHRINKS `entries`, and it is safe for
|
|
1225
|
-
* one specific reason (§6.4 / AC-22). `Transcript` clamps its settled boundary
|
|
1226
|
-
* with a MONOTONIC high-water mark: if `highWater` ever exceeded
|
|
1227
|
-
* `entries.length` the clamp would mark EVERYTHING settled — including the
|
|
1228
|
-
* still-streaming assistant entry — and `<Static>` would duplicate it.
|
|
1229
|
-
*
|
|
1230
|
-
* It cannot, and the ORDERING is why: the entries removed here are
|
|
1231
|
-
* `kind: 'tool'` cards created by `toolCallStart` AFTER `turnStart` created the
|
|
1232
|
-
* streaming assistant entry, so they sit strictly above it in the array; and
|
|
1233
|
-
* the boundary scan already breaks at that entry. Removing only entries above
|
|
1234
|
-
* the boundary is the invariant. A future action that removed a SETTLED entry
|
|
1235
|
-
* would break `<Static>` and nothing would report it.
|
|
1236
|
-
*/
|
|
1237
1196
|
case 'streamRestart': {
|
|
1238
1197
|
const discarded = new Set(action.discardedToolCallIds);
|
|
1239
1198
|
const entries = state.entries.filter((e) => !(e.kind === 'tool' && discarded.has(e.toolCallId)));
|
|
@@ -1326,18 +1285,6 @@ export function viewReducer(state, action) {
|
|
|
1326
1285
|
droppedEntries: appended.droppedEntries,
|
|
1327
1286
|
};
|
|
1328
1287
|
}
|
|
1329
|
-
/**
|
|
1330
|
-
* Rewrite the LIVE card in place.
|
|
1331
|
-
*
|
|
1332
|
-
* SAFE ONLY BECAUSE OF D-11'S OTHER HALF: a `ready` / `running` card has
|
|
1333
|
-
* reached `<Static>` and cannot be re-printed, so `serviceEnd` appends a new
|
|
1334
|
-
* entry instead of routing through here. What this handles is the window in
|
|
1335
|
-
* which the card is still in the live region — `starting`, and the one
|
|
1336
|
-
* transition out of it — plus the output tail while it is there.
|
|
1337
|
-
*
|
|
1338
|
-
* The lookup is by `serviceId` and SKIPS terminal entries, so a stray update
|
|
1339
|
-
* cannot rewrite the one-row record `serviceEnd` appended.
|
|
1340
|
-
*/
|
|
1341
1288
|
case 'serviceUpdate': {
|
|
1342
1289
|
const target = findServiceEntryId(state, action.service.id);
|
|
1343
1290
|
if (!target)
|
|
@@ -1347,15 +1294,6 @@ export function viewReducer(state, action) {
|
|
|
1347
1294
|
entries: mapEntry(state.entries, target, (e) => e.kind === 'service' ? { ...e, ...serviceFields(action.service) } : e),
|
|
1348
1295
|
};
|
|
1349
1296
|
}
|
|
1350
|
-
/**
|
|
1351
|
-
* A service reached a terminal state.
|
|
1352
|
-
*
|
|
1353
|
-
* TWO WRITES, AND BOTH ARE REQUIRED. The live card is settled in place so
|
|
1354
|
-
* `computeSettledCount` can move past it and `estimateEntryRows` stops
|
|
1355
|
-
* charging for a tail nothing will add to; the NEW one-row entry is what the
|
|
1356
|
-
* user actually reads, because the card may already be printed into
|
|
1357
|
-
* `<Static>` and `<Static>` cannot un-print (D-11 / R-13).
|
|
1358
|
-
*/
|
|
1359
1297
|
case 'serviceEnd': {
|
|
1360
1298
|
const target = findServiceEntryId(state, action.service.id);
|
|
1361
1299
|
const settled = target
|