@aragon-agent/cli 0.5.6 → 0.5.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 +356 -0
- package/README.md +1429 -1105
- package/dist/agent/coalesce.d.ts +13 -0
- package/dist/agent/coalesce.js +50 -11
- package/dist/agent/controller.d.ts +141 -2
- package/dist/agent/controller.js +396 -4
- package/dist/agent/entry-limits.d.ts +65 -0
- package/dist/agent/entry-limits.js +133 -0
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +253 -3
- package/dist/agent/reducer.js +553 -28
- package/dist/agent/retry-view.d.ts +68 -0
- package/dist/agent/retry-view.js +95 -0
- package/dist/agent/system-prompt.d.ts +11 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +122 -4
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +313 -2
- package/dist/commands/perf.d.ts +45 -0
- package/dist/commands/perf.js +78 -0
- package/dist/config/cli-commands.d.ts +53 -2
- package/dist/config/cli-commands.js +136 -3
- package/dist/config/env.d.ts +6 -1
- package/dist/config/env.js +131 -1
- package/dist/config/load.d.ts +63 -0
- package/dist/config/load.js +125 -2
- package/dist/config/schema.d.ts +273 -10
- package/dist/config/schema.js +250 -7
- package/dist/config/store.js +20 -1
- package/dist/fast/critique.d.ts +43 -0
- package/dist/fast/critique.js +62 -0
- package/dist/fast/digest.d.ts +42 -0
- package/dist/fast/digest.js +85 -0
- package/dist/fast/frames.d.ts +99 -0
- package/dist/fast/frames.js +180 -0
- package/dist/fast/limits.d.ts +98 -0
- package/dist/fast/limits.js +98 -0
- package/dist/fast/prompt.d.ts +72 -0
- package/dist/fast/prompt.js +94 -0
- package/dist/fast/resolve.d.ts +43 -0
- package/dist/fast/resolve.js +105 -0
- package/dist/fast/review-call.d.ts +99 -0
- package/dist/fast/review-call.js +130 -0
- package/dist/fast/reviewer.d.ts +248 -0
- package/dist/fast/reviewer.js +664 -0
- package/dist/fast/types.d.ts +128 -0
- package/dist/fast/types.js +14 -0
- package/dist/fast/wiring.d.ts +170 -0
- package/dist/fast/wiring.js +316 -0
- package/dist/logging/install.d.ts +15 -1
- package/dist/logging/install.js +37 -4
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +57 -1
- package/dist/team/normalize.d.ts +26 -1
- package/dist/team/normalize.js +23 -3
- package/dist/team/prompt.d.ts +11 -0
- package/dist/team/prompt.js +10 -0
- package/dist/team/report.d.ts +18 -0
- package/dist/team/report.js +27 -1
- package/dist/team/runtime.d.ts +13 -2
- package/dist/team/runtime.js +33 -6
- package/dist/team/subagent.d.ts +19 -1
- package/dist/team/subagent.js +72 -10
- package/dist/team/task-tool.d.ts +26 -0
- package/dist/team/task-tool.js +48 -4
- package/dist/team/types.d.ts +80 -1
- package/dist/tools/bash-tool.js +19 -3
- package/dist/tools/diff.d.ts +25 -5
- package/dist/tools/diff.js +72 -41
- package/dist/tools/file-change-store.d.ts +34 -0
- package/dist/tools/file-change-store.js +57 -0
- package/dist/tools/fs-tools.d.ts +26 -0
- package/dist/tools/fs-tools.js +62 -5
- package/dist/tools/index.d.ts +31 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch.d.ts +115 -0
- package/dist/tools/patch.js +453 -0
- package/dist/tools/terminal-output.d.ts +107 -0
- package/dist/tools/terminal-output.js +198 -0
- package/dist/tools/tool-output-store.d.ts +68 -0
- package/dist/tools/tool-output-store.js +102 -0
- package/dist/ui/ActivityLine.d.ts +48 -0
- package/dist/ui/ActivityLine.js +22 -0
- package/dist/ui/App.d.ts +1 -4
- package/dist/ui/App.js +357 -44
- package/dist/ui/BottomStatusRow.d.ts +43 -0
- package/dist/ui/BottomStatusRow.js +14 -0
- package/dist/ui/Composer.d.ts +1 -6
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Markdown.d.ts +7 -1
- package/dist/ui/Markdown.js +15 -48
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +50 -0
- package/dist/ui/StatusBar.js +7 -3
- package/dist/ui/TeamPanel.d.ts +5 -4
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/TodoPanel.d.ts +5 -4
- package/dist/ui/Transcript.d.ts +80 -5
- package/dist/ui/Transcript.js +179 -11
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +38 -1
- package/dist/ui/entries/AssistantEntry.js +34 -3
- package/dist/ui/entries/DiffView.d.ts +67 -0
- package/dist/ui/entries/DiffView.js +103 -0
- package/dist/ui/entries/FastCard.d.ts +53 -0
- package/dist/ui/entries/FastCard.js +87 -0
- package/dist/ui/entries/RetryCard.d.ts +56 -0
- package/dist/ui/entries/RetryCard.js +60 -0
- package/dist/ui/entries/TeamCard.d.ts +9 -1
- package/dist/ui/entries/TeamCard.js +28 -2
- package/dist/ui/entries/TodoCard.d.ts +9 -1
- package/dist/ui/entries/TodoCard.js +23 -1
- package/dist/ui/entries/ToolCard.d.ts +50 -1
- package/dist/ui/entries/ToolCard.js +104 -7
- package/dist/ui/entries/ToolPreview.d.ts +2 -2
- package/dist/ui/entries/UserEntry.d.ts +9 -1
- package/dist/ui/entries/UserEntry.js +17 -1
- package/dist/ui/exit-snapshot.d.ts +7 -0
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/AppShell.d.ts +13 -22
- package/dist/ui/layout/AppShell.js +3 -38
- package/dist/ui/layout/MeasuredEntry.d.ts +21 -0
- package/dist/ui/layout/MeasuredEntry.js +30 -0
- package/dist/ui/layout/ScrollViewport.js +8 -2
- package/dist/ui/layout/viewport-geometry.d.ts +28 -0
- package/dist/ui/layout/viewport-geometry.js +27 -0
- package/dist/ui/layout/virtual-window.d.ts +100 -0
- package/dist/ui/layout/virtual-window.js +308 -0
- package/dist/ui/live-clamp.d.ts +42 -0
- package/dist/ui/live-clamp.js +74 -0
- package/dist/ui/markdown-blocks.d.ts +25 -1
- package/dist/ui/markdown-blocks.js +43 -0
- package/dist/ui/overlays/HelpOverlay.js +16 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
- package/dist/ui/overlays/SettingsScreen.js +147 -1
- package/dist/ui/render-cache.d.ts +81 -0
- package/dist/ui/render-cache.js +195 -0
- package/dist/ui/render-governor.d.ts +54 -0
- package/dist/ui/render-governor.js +78 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +32 -0
- package/dist/ui/use-height-store.d.ts +31 -0
- package/dist/ui/use-height-store.js +76 -0
- package/dist/ui/use-render-governor.d.ts +39 -0
- package/dist/ui/use-render-governor.js +57 -0
- package/dist/ui/use-startup-notices.d.ts +9 -1
- package/dist/ui/use-startup-notices.js +35 -1
- package/dist/ui/use-wheel-routing.d.ts +16 -32
- package/dist/ui/use-wheel-routing.js +21 -38
- package/package.json +2 -2
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded view state (tui-render-performance L1 / R5).
|
|
3
|
+
*
|
|
4
|
+
* `ViewState.entries` used to grow without any ceiling: `argsRaw`, assistant
|
|
5
|
+
* `text` and `thinking` all concatenated forever, and the entry array was never
|
|
6
|
+
* trimmed. That makes the per-frame cost of the renderer proportional to the
|
|
7
|
+
* total number of characters the session has ever produced, because Ink's
|
|
8
|
+
* `renderNodeToOutput` walks and measures every mounted text node whether or not
|
|
9
|
+
* `overflow: hidden` clips it.
|
|
10
|
+
*
|
|
11
|
+
* Two bounds live here, both pure and React-free:
|
|
12
|
+
* - `appendBounded` caps one field by eliding its MIDDLE, and
|
|
13
|
+
* - `trimEntries` ring-trims the entry array itself.
|
|
14
|
+
*
|
|
15
|
+
* ASCII ONLY. This module is produced outside `src/ui/**`, where no terminal
|
|
16
|
+
* capabilities are available, so the elision marker has to be safe on a legacy
|
|
17
|
+
* `cmd.exe` -- the same rule `PREVIEW_TRUNCATION_MARK` in `reducer.ts` records.
|
|
18
|
+
*/
|
|
19
|
+
import { DEFAULT_TRANSCRIPT_RETAIN } from '../config/schema.js';
|
|
20
|
+
/**
|
|
21
|
+
* The per-field ceilings.
|
|
22
|
+
*
|
|
23
|
+
* `text` is the generous one on purpose: a 256 KiB answer is already far past
|
|
24
|
+
* anything a terminal can usefully show, and the middle is what gets elided, so
|
|
25
|
+
* both the opening and the live cursor survive. `argsRaw` is the tight one --
|
|
26
|
+
* it is a streamed JSON fragment rendered as a 60-character card summary, so
|
|
27
|
+
* anything past a few KiB is invisible by construction.
|
|
28
|
+
*/
|
|
29
|
+
export const ENTRY_LIMITS = {
|
|
30
|
+
text: 262_144,
|
|
31
|
+
thinking: 65_536,
|
|
32
|
+
argsRaw: 16_384,
|
|
33
|
+
};
|
|
34
|
+
/** Marker inserted at the elision point. ASCII -- produced outside `src/ui/**`. */
|
|
35
|
+
export const ELISION_MARK_PREFIX = '\n[... ';
|
|
36
|
+
export const ELISION_MARK_SUFFIX = ' characters elided ...]\n';
|
|
37
|
+
/** Matches a marker this module wrote, so re-elision collapses rather than nests. */
|
|
38
|
+
const ELISION_RE = /\n\[\.\.\. (\d+) characters elided \.\.\.\]\n/;
|
|
39
|
+
/** Fixed cost of the marker, before the digits of the count. */
|
|
40
|
+
const MARKER_FIXED = ELISION_MARK_PREFIX.length + ELISION_MARK_SUFFIX.length;
|
|
41
|
+
/**
|
|
42
|
+
* Fraction of the cap reserved for the HEAD. The tail gets the rest because that
|
|
43
|
+
* is where the cursor is; the head exists so the answer still starts where the
|
|
44
|
+
* model started it.
|
|
45
|
+
*/
|
|
46
|
+
const HEAD_FRACTION = 4;
|
|
47
|
+
/** Undo one elision, so a second one merges into it rather than nesting. */
|
|
48
|
+
function splitElided(prev) {
|
|
49
|
+
const m = ELISION_RE.exec(prev);
|
|
50
|
+
if (!m)
|
|
51
|
+
return { head: '', elided: 0, tail: prev };
|
|
52
|
+
const index = m.index;
|
|
53
|
+
return {
|
|
54
|
+
head: prev.slice(0, index),
|
|
55
|
+
elided: Number.parseInt(m[1], 10) || 0,
|
|
56
|
+
tail: prev.slice(index + m[0].length),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Append `delta` to `prev`, keeping the result at or below `cap` by eliding the
|
|
61
|
+
* MIDDLE. Head and tail are both preserved: the head is the answer's opening,
|
|
62
|
+
* the tail is where the cursor is. Idempotent -- re-eliding an already elided
|
|
63
|
+
* string collapses the two markers into one.
|
|
64
|
+
*
|
|
65
|
+
* The returned string is NEVER longer than `cap` (AC-7). The marker's own digit
|
|
66
|
+
* count is bounded before the tail budget is computed, so the arithmetic cannot
|
|
67
|
+
* overshoot by a digit the way a naive `cap - marker.length` would.
|
|
68
|
+
*/
|
|
69
|
+
export function appendBounded(prev, delta, cap) {
|
|
70
|
+
if (!Number.isFinite(cap) || cap <= 0)
|
|
71
|
+
return '';
|
|
72
|
+
const split = splitElided(prev);
|
|
73
|
+
let head = split.head;
|
|
74
|
+
let tail = split.tail + delta;
|
|
75
|
+
let elided = split.elided;
|
|
76
|
+
// Fast path: nothing has ever been elided and it still fits. This is the path
|
|
77
|
+
// every ordinary answer takes, so it must allocate nothing beyond the concat.
|
|
78
|
+
if (elided === 0 && tail.length <= cap)
|
|
79
|
+
return tail;
|
|
80
|
+
const headBudget = Math.max(0, Math.min(Math.floor(cap / HEAD_FRACTION), cap - MARKER_FIXED - 1));
|
|
81
|
+
if (elided === 0) {
|
|
82
|
+
// First elision: the head has to be carved out of the accumulated text.
|
|
83
|
+
head = tail.slice(0, headBudget);
|
|
84
|
+
tail = tail.slice(headBudget);
|
|
85
|
+
}
|
|
86
|
+
else if (head.length > headBudget) {
|
|
87
|
+
// A later `cap` reduction can leave the stored head oversized; charge the
|
|
88
|
+
// difference to the elision count rather than silently keeping it.
|
|
89
|
+
elided += head.length - headBudget;
|
|
90
|
+
head = head.slice(0, headBudget);
|
|
91
|
+
}
|
|
92
|
+
if (head.length + MARKER_FIXED >= cap) {
|
|
93
|
+
// Degenerate cap (only reachable from a hand-written test): keep the tail.
|
|
94
|
+
return tail.slice(Math.max(0, tail.length - cap));
|
|
95
|
+
}
|
|
96
|
+
// `elided + tail.length` is an UPPER BOUND on the final count, so its digit
|
|
97
|
+
// count is an upper bound on the marker's width. Budgeting against the bound
|
|
98
|
+
// is what makes the `<= cap` guarantee hold without a second pass.
|
|
99
|
+
const digitBound = String(elided + tail.length).length;
|
|
100
|
+
const markerBound = MARKER_FIXED + digitBound;
|
|
101
|
+
const tailKeep = Math.max(0, Math.min(tail.length, cap - head.length - markerBound));
|
|
102
|
+
const total = elided + (tail.length - tailKeep);
|
|
103
|
+
return `${head}${ELISION_MARK_PREFIX}${total}${ELISION_MARK_SUFFIX}${tail.slice(tail.length - tailKeep)}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Ring-trim to `retain` entries; returns the SAME array reference when no-op.
|
|
107
|
+
*
|
|
108
|
+
* The identity return is load-bearing: the reducer spreads this result into a
|
|
109
|
+
* new `ViewState` on every append, and a fresh array on every action would
|
|
110
|
+
* defeat the `React.memo` boundaries L2 installs on the transcript.
|
|
111
|
+
*/
|
|
112
|
+
export function trimEntries(entries, retain) {
|
|
113
|
+
const keep = Math.max(1, Math.floor(retain));
|
|
114
|
+
if (entries.length <= keep)
|
|
115
|
+
return { entries, dropped: 0 };
|
|
116
|
+
return { entries: entries.slice(entries.length - keep), dropped: entries.length - keep };
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// The resolved retain ceiling
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
/**
|
|
122
|
+
* `viewReducer` is pure and takes no config, so the resolved `transcriptRetain`
|
|
123
|
+
* reaches it through a module-level singleton -- the shape `prompt-history.ts`
|
|
124
|
+
* already uses for `setHistoryEnabled`. `load.ts` is the ONLY writer.
|
|
125
|
+
*/
|
|
126
|
+
let resolvedRetain = DEFAULT_TRANSCRIPT_RETAIN;
|
|
127
|
+
export function setEntryRetain(value) {
|
|
128
|
+
if (Number.isFinite(value) && value > 0)
|
|
129
|
+
resolvedRetain = Math.floor(value);
|
|
130
|
+
}
|
|
131
|
+
export function entryRetain() {
|
|
132
|
+
return resolvedRetain;
|
|
133
|
+
}
|
package/dist/agent/headless.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { AgentEvent, ModelInfo } from '@aragon-agent/core';
|
|
|
8
8
|
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
|
+
import type { FastEvent } from '../fast/types.js';
|
|
11
12
|
import { type FollowThroughMode } from '../todo/follow-through.js';
|
|
12
13
|
/** The minimal controller surface headless mode needs (real or stubbed). */
|
|
13
14
|
export interface HeadlessController {
|
|
@@ -35,6 +36,13 @@ export interface HeadlessController {
|
|
|
35
36
|
* keeps a `--no-todo` headless run byte-identical to today's.
|
|
36
37
|
*/
|
|
37
38
|
subscribeTodos?(listener: (event: TodoEvent) => void): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* The CLI-local fast-tier stream — OPTIONAL, for the identical reason the two
|
|
41
|
+
* above are (fast-model-tier §3.9). A controller with no tier has no fast
|
|
42
|
+
* events, and `runHeadless` calls it with `?.`, which keeps a default `-p` run
|
|
43
|
+
* byte-identical to today's.
|
|
44
|
+
*/
|
|
45
|
+
subscribeFast?(listener: (event: FastEvent) => void): () => void;
|
|
38
46
|
/**
|
|
39
47
|
* The current plan — OPTIONAL, for the identical reason the two subscriptions
|
|
40
48
|
* above are (todo-plan-followthrough §3.6 rule 2). ABSENT MEANS THE
|
package/dist/agent/headless.js
CHANGED
|
@@ -21,6 +21,8 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
21
21
|
let sawTurnEnd = false;
|
|
22
22
|
let wroteText = false;
|
|
23
23
|
const total = { inputTokens: 0, outputTokens: 0 };
|
|
24
|
+
/** Retries seen this run, so the recovery line can name a count. */
|
|
25
|
+
let retryCount = 0;
|
|
24
26
|
const unsubscribe = controller.subscribe((event) => {
|
|
25
27
|
switch (event.type) {
|
|
26
28
|
case 'message_update': {
|
|
@@ -33,6 +35,25 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
33
35
|
errored = true;
|
|
34
36
|
err.write(`\n${formatStreamError(se.error)}\n`);
|
|
35
37
|
}
|
|
38
|
+
else if (se.type === 'retry_scheduled') {
|
|
39
|
+
// STDERR, NEVER STDOUT (llm-api-retry-backoff §6.7). stdout is the
|
|
40
|
+
// machine-readable answer and has to stay parseable by
|
|
41
|
+
// `aragon -p | jq`; a retry line on it would corrupt every consumer.
|
|
42
|
+
// Not `--quiet`-gated, unlike the tool lines: a three-minute pause with
|
|
43
|
+
// no explanation anywhere is not a "compact" run, it is a hang.
|
|
44
|
+
retryCount = se.attempt;
|
|
45
|
+
// The em dash is a literal because `agent/headless.ts` is the CLI's one
|
|
46
|
+
// documented glyph-scanner exemption: `aragon -p` does no capability
|
|
47
|
+
// probe, so there is no `Glyphs` set to read here.
|
|
48
|
+
err.write(`\n[retry ${se.attempt}/${se.maxRetries}] ${se.errorType} — ` +
|
|
49
|
+
`waiting ${Math.round(se.delayMs / 1000)}s\n`);
|
|
50
|
+
}
|
|
51
|
+
else if (se.type === 'stream_restart') {
|
|
52
|
+
// The partial answer already on stdout is about to be replayed. Saying so
|
|
53
|
+
// is the only way a piped consumer can tell a duplicated paragraph from
|
|
54
|
+
// a model that repeated itself.
|
|
55
|
+
err.write(`[retry] discarding a partial response and restarting\n`);
|
|
56
|
+
}
|
|
36
57
|
break;
|
|
37
58
|
}
|
|
38
59
|
case 'tool_execution_start':
|
|
@@ -46,6 +67,10 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
46
67
|
break;
|
|
47
68
|
case 'turn_end':
|
|
48
69
|
sawTurnEnd = true;
|
|
70
|
+
if (retryCount > 0) {
|
|
71
|
+
err.write(`[retry] recovered after ${retryCount} ${retryCount === 1 ? 'retry' : 'retries'}\n`);
|
|
72
|
+
retryCount = 0;
|
|
73
|
+
}
|
|
49
74
|
total.inputTokens += event.usage.inputTokens;
|
|
50
75
|
total.outputTokens += event.usage.outputTokens;
|
|
51
76
|
break;
|
|
@@ -128,6 +153,20 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
128
153
|
lastActive = activeIndex;
|
|
129
154
|
err.write(`[todo] ${activeIndex + 1}/${total} ${items[activeIndex]?.content ?? ''}\n`);
|
|
130
155
|
});
|
|
156
|
+
// Fast-tier review spend must reach the `[usage]` footer, or `-p` under-reports
|
|
157
|
+
// by however much the reviewer consumed (§3.6).
|
|
158
|
+
//
|
|
159
|
+
// NOTHING IS WRITTEN TO STDOUT, AND NOTHING TO STDERR EITHER (D-12). stdout is
|
|
160
|
+
// the answer channel and is piped into other tools; stderr in `-p` carries the
|
|
161
|
+
// tool trace, and an unrequested second opinion is not a tool the user asked
|
|
162
|
+
// for. The reviews are in the log records either way, which is where a `-p`
|
|
163
|
+
// user goes to ask what happened.
|
|
164
|
+
const unsubscribeFast = controller.subscribeFast?.((event) => {
|
|
165
|
+
if (event.type !== 'usage')
|
|
166
|
+
return;
|
|
167
|
+
total.inputTokens += event.usage.inputTokens;
|
|
168
|
+
total.outputTokens += event.usage.outputTokens;
|
|
169
|
+
});
|
|
131
170
|
// --- The bounded continuation loop (todo-plan-followthrough §3.6). --------
|
|
132
171
|
//
|
|
133
172
|
// THE THREE `unsubscribe` CALLS STAY OUTSIDE IT (rule 4). The loop wraps the
|
|
@@ -187,6 +226,7 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
187
226
|
unsubscribe();
|
|
188
227
|
unsubscribeTeam?.();
|
|
189
228
|
unsubscribeTodos?.();
|
|
229
|
+
unsubscribeFast?.();
|
|
190
230
|
}
|
|
191
231
|
// Ensure a trailing newline so piped output is well-formed.
|
|
192
232
|
if (wroteText)
|
package/dist/agent/reducer.d.ts
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
import type { AgentEvent, ModelCost, ToolResult, TokenUsage } from '@aragon-agent/core';
|
|
14
14
|
import type { AgentMode } from './agent-mode.js';
|
|
15
15
|
import type { DispatchOutcome, SubagentRun, SubagentSpec, TeamSnapshot } from '../team/types.js';
|
|
16
|
+
import type { FastReview, FastSnapshot } from '../fast/types.js';
|
|
16
17
|
import type { TodoItem, TodoSnapshot } from '../todo/types.js';
|
|
18
|
+
import type { RetryPhase } from './retry-view.js';
|
|
19
|
+
import type { FilePatch } from '../tools/patch.js';
|
|
17
20
|
export type NoticeLevel = 'info' | 'warn' | 'error';
|
|
18
21
|
export type ToastLevel = NoticeLevel | 'success';
|
|
19
22
|
export type Overlay = null | 'settings' | 'model' | 'help' | 'confirm' | 'question' | 'plan';
|
|
@@ -40,6 +43,21 @@ export type Entry = {
|
|
|
40
43
|
streaming: boolean;
|
|
41
44
|
aborted?: boolean;
|
|
42
45
|
usage?: TokenUsage;
|
|
46
|
+
/**
|
|
47
|
+
* Epoch ms the turn started reasoning (agent-activity-presentation §3.1.2).
|
|
48
|
+
* Set by `thinkingStart`, cleared by `streamRestart`.
|
|
49
|
+
*/
|
|
50
|
+
thinkingStartedAt?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Wall-clock reasoning time, SEALED ONCE — at the first text delta, or at
|
|
53
|
+
* turn/run end for a turn that thought and then called a tool without
|
|
54
|
+
* emitting any text.
|
|
55
|
+
*
|
|
56
|
+
* With thinking hidden this is the only thing the collapsed marker can say
|
|
57
|
+
* that the user could not otherwise know, which is the whole argument for
|
|
58
|
+
* having a marker at all (§1.2).
|
|
59
|
+
*/
|
|
60
|
+
thinkingMs?: number;
|
|
43
61
|
} | {
|
|
44
62
|
id: string;
|
|
45
63
|
kind: 'tool';
|
|
@@ -52,6 +70,44 @@ export type Entry = {
|
|
|
52
70
|
preview?: string;
|
|
53
71
|
durationMs?: number;
|
|
54
72
|
isError?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The structured file diff (agent-activity-presentation §3.3), or absent.
|
|
75
|
+
*
|
|
76
|
+
* IMMUTABLE AND WRITTEN ONCE, by `toolExecEnd`. `ToolCard` is `React.memo`'d
|
|
77
|
+
* with the DEFAULT comparator, so this object is only free while its
|
|
78
|
+
* reference is stable — which it is, because the reducer never rebuilds it
|
|
79
|
+
* and `mapEntry` preserves identity for untouched entries. A future action
|
|
80
|
+
* that recomputed or normalized a patch would turn that memo boundary into
|
|
81
|
+
* a no-op WITH NOTHING FAILING (P2-9).
|
|
82
|
+
*
|
|
83
|
+
* Needs no clause in `normalizeLoadedEntries` (D-9): the four that exist
|
|
84
|
+
* are all about a LIVE flag that would never settle, and a patch is settled
|
|
85
|
+
* by construction.
|
|
86
|
+
*/
|
|
87
|
+
patch?: FilePatch;
|
|
88
|
+
/**
|
|
89
|
+
* The sanitised live tail while the tool runs
|
|
90
|
+
* (agent-activity-presentation-live §3.3.1). Cleared at settle (D-23), and
|
|
91
|
+
* cleared again by EVERY path that reaches `status: 'idle'` (D-35).
|
|
92
|
+
*
|
|
93
|
+
* REDUCER STATE RATHER THAN A STORE READ DURING RENDER, and that is the
|
|
94
|
+
* load-bearing decision of the round (D-22): `entryRevision` is what tells
|
|
95
|
+
* the height cache an entry's rendered output changed, and an entry mutated
|
|
96
|
+
* outside `ViewState` keeps its revision and freezes at a stale height AND
|
|
97
|
+
* a stale subtree with nothing anywhere reporting it (I-L3-1).
|
|
98
|
+
*/
|
|
99
|
+
live?: readonly string[];
|
|
100
|
+
/**
|
|
101
|
+
* Monotonic counter -- the revision term for a NON-APPEND mutation (D-22).
|
|
102
|
+
*
|
|
103
|
+
* A COUNTER AND NOT `live.length`: a fixed-size tail evicting its oldest
|
|
104
|
+
* row while appending a new one of the same width leaves the joined length
|
|
105
|
+
* unchanged, which is exactly the lossy case `virtual-window.ts` says the
|
|
106
|
+
* reducer cannot produce today and that this round introduces.
|
|
107
|
+
*/
|
|
108
|
+
liveSeq?: number;
|
|
109
|
+
/** Epoch ms of the last output row, for the stall row (§3.3.4). */
|
|
110
|
+
lastOutputAt?: number;
|
|
55
111
|
} | {
|
|
56
112
|
id: string;
|
|
57
113
|
kind: 'notice';
|
|
@@ -107,12 +163,85 @@ export type Entry = {
|
|
|
107
163
|
* card this session produced.
|
|
108
164
|
*/
|
|
109
165
|
interrupted?: boolean;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The turn's retry story (llm-api-retry-backoff §6.4). ONE per turn, REWRITTEN
|
|
169
|
+
* IN PLACE — ten retries rendered as ten notices would bury the transcript in
|
|
170
|
+
* the one moment the user most needs it legible.
|
|
171
|
+
*
|
|
172
|
+
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
173
|
+
*
|
|
174
|
+
* `phase: 'waiting' | 'retrying'` is normalized away ON LOAD, in
|
|
175
|
+
* `session/persist.ts`, exactly as the team entry's `active` and the todo
|
|
176
|
+
* card's `live` are (R-10): `Transcript`'s settled boundary is MONOTONIC, so a
|
|
177
|
+
* card that never settles never reaches `<Static>` and is re-rendered on every
|
|
178
|
+
* frame for the rest of the session.
|
|
179
|
+
*/
|
|
180
|
+
| {
|
|
181
|
+
id: string;
|
|
182
|
+
kind: 'retry';
|
|
183
|
+
/** 1-based retry index of the LATEST attempt this card has seen. */
|
|
184
|
+
attempt: number;
|
|
185
|
+
maxRetries: number;
|
|
186
|
+
errorType: string;
|
|
187
|
+
message: string;
|
|
188
|
+
delayMs: number;
|
|
189
|
+
/** Epoch ms the next attempt fires; the UI ticks against this. */
|
|
190
|
+
resumeAt?: number;
|
|
191
|
+
phase: RetryPhase;
|
|
192
|
+
startedAt: number;
|
|
193
|
+
/** Filled on settle. */
|
|
194
|
+
totalRetries?: number;
|
|
195
|
+
elapsedMs?: number;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* One fast-model review (fast-model-tier §5.4). ONE CARD PER REVIEW, never
|
|
199
|
+
* rewritten across reviews — a critique is a single event, unlike the todo
|
|
200
|
+
* card's per-turn checklist.
|
|
201
|
+
*
|
|
202
|
+
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
203
|
+
*
|
|
204
|
+
* `live: true` is normalized away ON LOAD, in `session/persist.ts`, next to
|
|
205
|
+
* `team.active`, `todo.live` and the retry card's phase, and for the identical
|
|
206
|
+
* reason (C-5): `Transcript`'s settled boundary is MONOTONIC, so an entry that
|
|
207
|
+
* never settles never reaches `<Static>` and is re-rendered on every frame for
|
|
208
|
+
* the rest of the session. The reducer never legitimately sees a stale-live
|
|
209
|
+
* entry — only the file does.
|
|
210
|
+
*/
|
|
211
|
+
| {
|
|
212
|
+
id: string;
|
|
213
|
+
kind: 'fast';
|
|
214
|
+
/** 1-based within the session, so the card can name itself. */
|
|
215
|
+
reviewIndex: number;
|
|
216
|
+
model: string;
|
|
217
|
+
status: 'running' | 'ok' | 'advice' | 'empty' | 'failed' | 'dropped';
|
|
218
|
+
/** Present only for `advice`. */
|
|
219
|
+
text?: string;
|
|
220
|
+
/** Failure / drop reason, shown as one muted line. */
|
|
221
|
+
detail?: string;
|
|
222
|
+
turn: number;
|
|
223
|
+
durationMs?: number;
|
|
224
|
+
live: boolean;
|
|
110
225
|
};
|
|
111
226
|
export interface UsageTotal {
|
|
112
227
|
inputTokens: number;
|
|
113
228
|
outputTokens: number;
|
|
114
229
|
costUsd: number;
|
|
115
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* The ephemeral retry projection held in `ViewState.retry` (§9).
|
|
233
|
+
*
|
|
234
|
+
* Only the two IN-FLIGHT phases exist here: once a card settles the projection is
|
|
235
|
+
* `null`, because the settled card IS the history and a chip that reported a
|
|
236
|
+
* finished retry would never go away.
|
|
237
|
+
*/
|
|
238
|
+
export interface RetrySnapshot {
|
|
239
|
+
attempt: number;
|
|
240
|
+
maxRetries: number;
|
|
241
|
+
resumeAt?: number;
|
|
242
|
+
phase: 'waiting' | 'retrying';
|
|
243
|
+
errorType: string;
|
|
244
|
+
}
|
|
116
245
|
export interface ViewState {
|
|
117
246
|
entries: Entry[];
|
|
118
247
|
status: 'idle' | 'running';
|
|
@@ -150,18 +279,64 @@ export interface ViewState {
|
|
|
150
279
|
* build (AC-24).
|
|
151
280
|
*/
|
|
152
281
|
todos: TodoSnapshot | null;
|
|
282
|
+
/**
|
|
283
|
+
* The live retry projection, or `null` when nothing is retrying.
|
|
284
|
+
*
|
|
285
|
+
* NEVER PERSISTED — `entries` is the history and the settled card is the
|
|
286
|
+
* record. It drives the status-bar chip and the 1 Hz tick, and it is nulled on
|
|
287
|
+
* every settle path. Same contract as `team`.
|
|
288
|
+
*/
|
|
289
|
+
retry: RetrySnapshot | null;
|
|
290
|
+
/**
|
|
291
|
+
* The live fast-tier projection, or `null` when the tier is off for this
|
|
292
|
+
* session (fast-model-tier §5.2).
|
|
293
|
+
*
|
|
294
|
+
* NEVER PERSISTED — `entries` is the history and the settled cards are the
|
|
295
|
+
* record. It drives the status chip and `/fast status`'s totals. `null` is
|
|
296
|
+
* what leaves an ordinary session's status bar byte-identical (AC-4).
|
|
297
|
+
*/
|
|
298
|
+
fast: FastSnapshot | null;
|
|
153
299
|
seq: number;
|
|
154
300
|
/** The `kind: 'team'` entry the live dispatch is writing into. */
|
|
155
301
|
teamEntryId?: string;
|
|
156
302
|
/** The `kind: 'todo'` entry this turn is writing into. */
|
|
157
303
|
todoEntryId?: string;
|
|
304
|
+
/** The `kind: 'retry'` entry this turn is writing into. */
|
|
305
|
+
retryEntryId?: string;
|
|
306
|
+
/** The `kind: 'fast'` entry the in-flight review is writing into. */
|
|
307
|
+
fastEntryId?: string;
|
|
158
308
|
streamingId?: string;
|
|
159
309
|
turnProduced: boolean;
|
|
160
310
|
errorNoticed: boolean;
|
|
161
311
|
/** The current run was aborted by the user (suppresses the failure guard). */
|
|
162
312
|
aborted: boolean;
|
|
313
|
+
/**
|
|
314
|
+
* Entries removed by `transcriptRetain` (tui-render-performance L1).
|
|
315
|
+
*
|
|
316
|
+
* Rendered by `transcript-text` and reported by `/perf`; NEVER PERSISTED —
|
|
317
|
+
* `SavedSession` keeps `{ model, messages, entries, todos }` exactly as it is,
|
|
318
|
+
* so `/save` and `/resume` need no format change and old files load unchanged.
|
|
319
|
+
*/
|
|
320
|
+
droppedEntries: number;
|
|
321
|
+
}
|
|
322
|
+
export interface ViewStateSeed {
|
|
323
|
+
/** Start with thinking blocks expanded. Defaults to FALSE, the product default. */
|
|
324
|
+
thinkingVisible?: boolean;
|
|
163
325
|
}
|
|
164
|
-
|
|
326
|
+
/**
|
|
327
|
+
* The initial view state, optionally seeded from config.
|
|
328
|
+
*
|
|
329
|
+
* THE DEFAULT IS `false`, NOT `true`, AND THAT IS THE WHOLE SAFETY ARGUMENT
|
|
330
|
+
* (D-2). There is exactly one production call site — `App`'s `useReducer`, which
|
|
331
|
+
* passes `cfg.showThinking`. Every other caller is a test. A future call site
|
|
332
|
+
* that forgets the seed fails CLOSED, toward the product default, rather than
|
|
333
|
+
* silently restoring the reasoning firehose for one code path with nothing
|
|
334
|
+
* reporting it.
|
|
335
|
+
*
|
|
336
|
+
* The seed is a PARAMETER rather than a post-mount dispatch so that `/resume`
|
|
337
|
+
* cannot flash a restored thinking block on frame 1.
|
|
338
|
+
*/
|
|
339
|
+
export declare function initialViewState(seed?: ViewStateSeed): ViewState;
|
|
165
340
|
export type ViewAction = {
|
|
166
341
|
type: 'submit';
|
|
167
342
|
text: string;
|
|
@@ -199,6 +374,22 @@ export type ViewAction = {
|
|
|
199
374
|
isError: boolean;
|
|
200
375
|
duration: number;
|
|
201
376
|
preview: string;
|
|
377
|
+
/** A fully-built, immutable diff, so the reducer stays pure (§3.3.7). */
|
|
378
|
+
patch?: FilePatch;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* A running tool's live tail (agent-activity-presentation-live §3.3.1).
|
|
382
|
+
*
|
|
383
|
+
* `rows` IS THE WHOLE TAIL, not an increment: the store replaces it on every
|
|
384
|
+
* chunk, which is what lets `mergeDeltas` keep only the last action per
|
|
385
|
+
* `toolCallId` (D-30). `at` is read in `App`'s listener rather than here, so
|
|
386
|
+
* this reducer stays pure.
|
|
387
|
+
*/
|
|
388
|
+
| {
|
|
389
|
+
type: 'toolOutputDelta';
|
|
390
|
+
toolCallId: string;
|
|
391
|
+
rows: readonly string[];
|
|
392
|
+
at: number;
|
|
202
393
|
} | {
|
|
203
394
|
type: 'turnEnd';
|
|
204
395
|
usage: TokenUsage;
|
|
@@ -258,6 +449,54 @@ export type ViewAction = {
|
|
|
258
449
|
snapshot: TodoSnapshot;
|
|
259
450
|
} | {
|
|
260
451
|
type: 'todoCleared';
|
|
452
|
+
} | {
|
|
453
|
+
type: 'retryScheduled';
|
|
454
|
+
attempt: number;
|
|
455
|
+
maxRetries: number;
|
|
456
|
+
delayMs: number;
|
|
457
|
+
resumeAt: number;
|
|
458
|
+
errorType: string;
|
|
459
|
+
message: string;
|
|
460
|
+
} | {
|
|
461
|
+
type: 'retryAttempt';
|
|
462
|
+
attempt: number;
|
|
463
|
+
maxRetries: number;
|
|
464
|
+
} | {
|
|
465
|
+
type: 'streamRestart';
|
|
466
|
+
discardedToolCallIds: string[];
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* The 1 Hz countdown tick. NO STATE OF ITS OWN — it bumps `seq` so the frame
|
|
470
|
+
* re-renders and the card recomputes `resumeAt - Date.now()`. `App` runs the
|
|
471
|
+
* interval ONLY while `state.retry?.phase === 'waiting'`, so an idle session has
|
|
472
|
+
* no timer at all.
|
|
473
|
+
*/
|
|
474
|
+
| {
|
|
475
|
+
type: 'retryTick';
|
|
476
|
+
} | {
|
|
477
|
+
type: 'fastStart';
|
|
478
|
+
index: number;
|
|
479
|
+
turn: number;
|
|
480
|
+
model: string;
|
|
481
|
+
} | {
|
|
482
|
+
type: 'fastEnd';
|
|
483
|
+
review: FastReview;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Review spend.
|
|
487
|
+
*
|
|
488
|
+
* `costDelta` IS PRECOMPUTED BY THE CALLER, exactly as `teamUsage`'s is: the
|
|
489
|
+
* reducer stays pure and cost-table-free, which is the property that makes it
|
|
490
|
+
* testable — and the fast tier has a DIFFERENT cost table from the lead, so a
|
|
491
|
+
* reducer that computed its own would have to know about both (§3.6 / R-7).
|
|
492
|
+
*/
|
|
493
|
+
| {
|
|
494
|
+
type: 'fastUsage';
|
|
495
|
+
usage: TokenUsage;
|
|
496
|
+
costDelta: number;
|
|
497
|
+
} | {
|
|
498
|
+
type: 'fastTier';
|
|
499
|
+
snapshot: FastSnapshot;
|
|
261
500
|
};
|
|
262
501
|
export declare const STORED_PREVIEW_CHARS = 8000;
|
|
263
502
|
export declare const STORED_PREVIEW_LINES = 200;
|
|
@@ -276,12 +515,23 @@ export declare const PREVIEW_TRUNCATION_MARK = "...";
|
|
|
276
515
|
* over-promises how much more is expandable.
|
|
277
516
|
*/
|
|
278
517
|
export declare function buildToolPreview(result: ToolResult): string;
|
|
518
|
+
/**
|
|
519
|
+
* Where a `FilePatch` is collected from on `tool_execution_end`.
|
|
520
|
+
*
|
|
521
|
+
* Supplying one makes `reduceEvent` CONSUME-ONCE rather than pure, which is safe
|
|
522
|
+
* because `App.tsx` calls it exactly once per event and `mergeDeltas` only ever
|
|
523
|
+
* merges `textDelta` / `thinkingDelta`, never `toolExecEnd`.
|
|
524
|
+
*/
|
|
525
|
+
export interface PatchSource {
|
|
526
|
+
take(toolCallId: string): FilePatch | undefined;
|
|
527
|
+
}
|
|
279
528
|
/**
|
|
280
529
|
* Map one core `AgentEvent` to view actions. `cost` (the active model's cost
|
|
281
530
|
* table) lets `turn_end` accumulate USD cost; omit it in tests that only assert
|
|
282
|
-
* token totals.
|
|
531
|
+
* token totals. `patches` is the CLI-local diff side channel (§3.3.7); omitting
|
|
532
|
+
* it — every existing test, and headless mode — produces byte-identical actions.
|
|
283
533
|
*/
|
|
284
|
-
export declare function reduceEvent(event: AgentEvent, cost?: ModelCost): ViewAction[];
|
|
534
|
+
export declare function reduceEvent(event: AgentEvent, cost?: ModelCost, patches?: PatchSource): ViewAction[];
|
|
285
535
|
/** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
|
|
286
536
|
export declare function formatStreamError(error: Error): string;
|
|
287
537
|
export declare function viewReducer(state: ViewState, action: ViewAction): ViewState;
|