@aragon-agent/cli 0.6.3 → 0.6.7
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 +142 -0
- package/README.md +203 -13
- package/dist/agent/controller.d.ts +158 -2
- package/dist/agent/controller.js +400 -1
- package/dist/agent/reducer.d.ts +102 -19
- package/dist/agent/reducer.js +162 -71
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +132 -23
- package/dist/commands/builtins.js +101 -1
- package/dist/compaction/child.js +5 -0
- package/dist/compaction/command.js +14 -1
- package/dist/compaction/compactor.d.ts +94 -24
- package/dist/compaction/compactor.js +247 -98
- 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 +77 -0
- package/dist/compaction/wiring.d.ts +56 -0
- package/dist/compaction/wiring.js +107 -25
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +63 -2
- package/dist/config/load.d.ts +28 -0
- package/dist/config/load.js +83 -1
- package/dist/config/schema.d.ts +129 -0
- package/dist/config/schema.js +69 -0
- package/dist/config/store.js +47 -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/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +2 -0
- package/dist/logging/install.js +53 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +36 -1
- 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/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/App.d.ts +15 -2
- package/dist/ui/App.js +330 -69
- 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 +56 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +52 -3
- package/dist/ui/PromptInput.js +201 -55
- package/dist/ui/StatusBar.d.ts +40 -15
- package/dist/ui/StatusBar.js +51 -9
- 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 +29 -1
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/layout/ScrollViewport.d.ts +1 -1
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +34 -6
- package/dist/ui/layout/budget.js +32 -7
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- 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 +24 -0
- package/dist/ui/layout/todo-layout.js +27 -0
- package/dist/ui/layout/virtual-window.js +51 -1
- package/dist/ui/overlays/HelpOverlay.js +3 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.js +14 -1
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +16 -0
- package/dist/ui/screen.js +45 -2
- package/dist/ui/selection/selection-controller.d.ts +1 -1
- package/dist/ui/transcript-text.js +55 -2
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -47
- package/dist/input/stdin-mouse-filter.js +0 -153
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Status bar (spec §4.8). Width-aware, borderless, EXACTLY one row: a left
|
|
3
3
|
* cluster (run status, model, thinking level) and a right cluster (context
|
|
4
|
-
* gauge,
|
|
5
|
-
* Clusters drop out gracefully on narrow terminals.
|
|
4
|
+
* gauge, occupancy pair, session total, cost, tokens/sec + elapsed while
|
|
5
|
+
* running, off-bottom `↑N`). Clusters drop out gracefully on narrow terminals.
|
|
6
|
+
*
|
|
7
|
+
* THE RIGHT CLUSTER'S WIDTH LADDER (context-usage-gauge-accuracy §3.6):
|
|
8
|
+
*
|
|
9
|
+
* >= 96 [####....] 43% 86.0k/200.0k total 1.2M^ 48.0k v $3.21 12 tok/s 1m02s
|
|
10
|
+
* 72-95 [####....] 43% 86.0k/200.0k $3.21
|
|
11
|
+
* 60-71 [####....] 43% $3.21
|
|
12
|
+
* < 60 43% $3.21
|
|
6
13
|
*
|
|
7
14
|
* Dropping the round border here buys back 3 rows of a 24-row terminal — an
|
|
8
15
|
* eighth of the screen was being spent framing a single line of text.
|
|
@@ -11,14 +18,23 @@ import React from 'react';
|
|
|
11
18
|
import type { Theme } from './theme.js';
|
|
12
19
|
import type { TermCapabilities } from './capabilities.js';
|
|
13
20
|
import type { UsageTotal } from '../agent/reducer.js';
|
|
21
|
+
import type { ContextUsageSnapshot } from '../compaction/types.js';
|
|
14
22
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
15
23
|
interface StatusBarProps {
|
|
16
24
|
model: string;
|
|
17
25
|
provider: string;
|
|
18
26
|
usageTotal: UsageTotal;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Context occupancy, as one object (context-usage-gauge-accuracy §4.3).
|
|
29
|
+
*
|
|
30
|
+
* IT REPLACED FOUR PARALLEL PROPS - `contextTokens`, `contextWindow`,
|
|
31
|
+
* `contextWindowKnown`, `contextEstimated`. Four props describing one reading
|
|
32
|
+
* are four chances for a caller to update three of them, which is the props-
|
|
33
|
+
* layer form of the "many writers" defect this feature removed from the
|
|
34
|
+
* reducer. `usageTotal` stays separate because it genuinely IS another
|
|
35
|
+
* quantity: session spend, not occupancy.
|
|
36
|
+
*/
|
|
37
|
+
context: ContextUsageSnapshot;
|
|
22
38
|
status: 'idle' | 'running';
|
|
23
39
|
elapsedMs: number;
|
|
24
40
|
thinkingLevel: string;
|
|
@@ -88,6 +104,25 @@ interface StatusBarProps {
|
|
|
88
104
|
done: number;
|
|
89
105
|
total: number;
|
|
90
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Live background services (background-service-supervision §3.8), or absent
|
|
109
|
+
* when none are running — an ordinary session's bar is then unchanged.
|
|
110
|
+
*
|
|
111
|
+
* LEFT CLUSTER, after the todo chip, and it DEGRADES rather than hiding below
|
|
112
|
+
* `PROC_LIMITS.statusCompactCols`. That is the `todoActive` / `teamActive`
|
|
113
|
+
* ladder rather than `compactionActive`'s hide-below-threshold one, and the
|
|
114
|
+
* choice is deliberate: `compact` and `fast` describe something the CLI is
|
|
115
|
+
* doing to itself, whereas this is a PROCESS ON THE USER'S MACHINE HOLDING A
|
|
116
|
+
* PORT. A narrow terminal is a reason to shorten it, never a reason to stop
|
|
117
|
+
* saying it.
|
|
118
|
+
*
|
|
119
|
+
* `live` counts `starting | ready | running`. The transcript records events
|
|
120
|
+
* about a service (D-11); this is where "what is running right now" lives,
|
|
121
|
+
* alongside `/bg` and `bash_output`.
|
|
122
|
+
*/
|
|
123
|
+
servicesActive?: {
|
|
124
|
+
live: number;
|
|
125
|
+
};
|
|
91
126
|
/**
|
|
92
127
|
* Live retry state (llm-api-retry-backoff §6.6), or absent when nothing is
|
|
93
128
|
* retrying — an ordinary session's bar is then unchanged.
|
|
@@ -168,16 +203,6 @@ interface StatusBarProps {
|
|
|
168
203
|
warn: number;
|
|
169
204
|
high: number;
|
|
170
205
|
};
|
|
171
|
-
/**
|
|
172
|
-
* Whether `contextTokens` is a DERIVED figure rather than a measured one.
|
|
173
|
-
*
|
|
174
|
-
* It WIDENS THE EXISTING `~`, it does not add a second marker. Today that
|
|
175
|
-
* prefix means "the window is a guess" (`contextWindowKnown`); with compaction
|
|
176
|
-
* it also has to mean "the occupancy is a guess", because the bar is written
|
|
177
|
-
* from an estimate for one turn after every compaction so it can fall
|
|
178
|
-
* immediately (§6.2 / AC-5).
|
|
179
|
-
*/
|
|
180
|
-
contextEstimated?: boolean;
|
|
181
206
|
}
|
|
182
207
|
export declare function StatusBar(props: StatusBarProps): React.ReactElement;
|
|
183
208
|
export {};
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useStdout } from 'ink';
|
|
3
3
|
import { pickGlyphs } from './glyphs.js';
|
|
4
|
-
import { formatCost, formatTokens, formatDuration } from '../agent/usage.js';
|
|
4
|
+
import { formatCost, formatTokens, formatDuration, promptTokensOf } from '../agent/usage.js';
|
|
5
5
|
import { buildGauge } from './gauge.js';
|
|
6
6
|
import { MODE_LABEL } from '../agent/agent-mode.js';
|
|
7
7
|
import { formatRetryChip } from '../agent/retry-view.js';
|
|
@@ -9,8 +9,29 @@ import { FAST_LIMITS } from '../fast/limits.js';
|
|
|
9
9
|
import { COMPACTION_LIMITS } from '../compaction/limits.js';
|
|
10
10
|
import { TEAM_LIMITS } from '../team/limits.js';
|
|
11
11
|
import { TODO_LIMITS } from '../todo/limits.js';
|
|
12
|
+
import { PROC_LIMITS } from '../proc/limits.js';
|
|
12
13
|
/** Below this the `eco` chip costs the context gauge more than it is worth. */
|
|
13
14
|
const ECO_MIN_COLS = 72;
|
|
15
|
+
/**
|
|
16
|
+
* Where `86k/200k` starts being shown (context-usage-gauge-accuracy §3.6 / W5).
|
|
17
|
+
*
|
|
18
|
+
* IT TAKES THE COLUMNS THE SESSION TOTAL USED TO HAVE, and the ordering is the
|
|
19
|
+
* point rather than a layout accident (D-6). "How full am I, out of how much" is
|
|
20
|
+
* the question a user asks the gauge; "what has this session cost in tokens" is
|
|
21
|
+
* a different one, and putting the second in front of the first spends the
|
|
22
|
+
* scarce columns on the number the user is more likely to MISREAD - the total
|
|
23
|
+
* looks like an occupancy until you notice it exceeds the window.
|
|
24
|
+
*/
|
|
25
|
+
const ABSOLUTE_PAIR_MIN_COLS = 72;
|
|
26
|
+
/**
|
|
27
|
+
* Where the session total returns, now prefixed.
|
|
28
|
+
*
|
|
29
|
+
* `total ` IS AN ASCII WORD, NOT A SIGMA. `ui/**` is inside the glyph scanner's
|
|
30
|
+
* scope, so a new symbol would need an entry in `glyphs.ts` with an ASCII
|
|
31
|
+
* fallback for a legacy `cmd.exe` - a real cost for a three-character prefix
|
|
32
|
+
* that reads worse.
|
|
33
|
+
*/
|
|
34
|
+
const SESSION_TOTAL_MIN_COLS = 96;
|
|
14
35
|
/**
|
|
15
36
|
* I-5 — DO NOT "CLEAN THIS UP". Ink short-circuits identical output at TWO
|
|
16
37
|
* gates (`ink.js:132` and `log-update.js:13`), so a fixed frame whose React
|
|
@@ -30,23 +51,42 @@ function redrawChar(nonce) {
|
|
|
30
51
|
return nonce % 2 === 0 ? ' ' : NBSP;
|
|
31
52
|
}
|
|
32
53
|
export function StatusBar(props) {
|
|
33
|
-
const { model, provider, usageTotal,
|
|
54
|
+
const { model, provider, usageTotal, context, status, elapsedMs, thinkingLevel, tokPerSec, theme, caps, scrolledLines = 0, redrawNonce = 0, agentMode = 'build', pendingAgentMode = null, teamActive, todoActive, servicesActive, retryActive, ecoRung = 0, fastActive, compactionActive, gaugeMarks, } = props;
|
|
34
55
|
const { stdout } = useStdout();
|
|
35
56
|
const cols = stdout?.columns ?? 80;
|
|
36
57
|
const running = status === 'running';
|
|
37
58
|
const glyphs = pickGlyphs(caps);
|
|
38
|
-
|
|
59
|
+
// THE PERCENTAGE IS NOT RECOMPUTED HERE ANY MORE. It arrives already divided
|
|
60
|
+
// and clamped by the same code the compaction trigger reads, which is the
|
|
61
|
+
// whole of AC-2: a user watching 78 % and a `/compact status` reporting 91 %
|
|
62
|
+
// would rightly conclude one of them is lying.
|
|
63
|
+
const pct = context.pct;
|
|
39
64
|
// `gaugeMarks` is spread rather than passed as `undefined`, so a session with
|
|
40
65
|
// compaction off takes `buildGauge`'s own default and the bar is byte-identical
|
|
41
66
|
// to the pre-feature build.
|
|
42
67
|
const gauge = gaugeMarks
|
|
43
68
|
? buildGauge(pct, cols < 72 ? 8 : 12, theme, caps, gaugeMarks)
|
|
44
69
|
: buildGauge(pct, cols < 72 ? 8 : 12, theme, caps);
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
// `~` BELONGS TO THE NUMERATOR, `?` TO THE DENOMINATOR (P2-7). One glyph used
|
|
71
|
+
// to carry three meanings - the window is a guess, the occupancy is a guess,
|
|
72
|
+
// the occupancy contains an appended-message estimate - and a user could not
|
|
73
|
+
// tell from it which half to distrust. The `~` keeps every case it had: an
|
|
74
|
+
// unknown window still produces it, so a session that never touches the new
|
|
75
|
+
// absolute pair sees the identical string it saw before.
|
|
76
|
+
const occupancyApproximate = context.source === 'estimate' || context.deltaTokens > 0;
|
|
77
|
+
const pctLabel = `${context.windowKnown && !occupancyApproximate ? '' : '~'}${gauge.pct}%`;
|
|
78
|
+
// `?` ON THE WINDOW, AND ONLY ON THE NEW READOUT. `windowKnown` is false
|
|
79
|
+
// exactly when `buildRuntimeModel` invented the 128k placeholder, and saying
|
|
80
|
+
// `128k?` is the difference between a denominator the user can act on and one
|
|
81
|
+
// they will assume came from their model card.
|
|
82
|
+
const windowLabel = `${formatTokens(context.window)}${context.windowKnown ? '' : '?'}`;
|
|
83
|
+
const absolutePair = `${formatTokens(context.occupied)}/${windowLabel}`;
|
|
84
|
+
// THE PROMPT-SIDE TOTAL, NOT `inputTokens` (P1-3 / AC-11). Anthropic reports
|
|
85
|
+
// `input_tokens` EXCLUDING cached tokens, so the old expression under-reported
|
|
86
|
+
// for anyone behind a caching gateway - on the same row as a `$` that has
|
|
87
|
+
// always priced the cache. Any session with cache hits sees this number grow,
|
|
88
|
+
// and that is the fix rather than a regression.
|
|
89
|
+
const tokens = `total ${formatTokens(promptTokensOf(usageTotal))}${glyphs.arrowUp} ${formatTokens(usageTotal.outputTokens)}${glyphs.arrowDown}`;
|
|
50
90
|
const statusGlyph = running ? theme.symbols.toolRunning : theme.symbols.toolPending;
|
|
51
91
|
// Nothing at all in build mode with nothing pending: the default posture is
|
|
52
92
|
// not news, and a permanent word here would be furniture.
|
|
@@ -66,5 +106,7 @@ export function StatusBar(props) {
|
|
|
66
106
|
? `agents ${teamActive.running}/${teamActive.total}`
|
|
67
107
|
: `[${teamActive.running}]`] })), retryActive && (_jsxs(Text, { color: theme.noticeWarn, bold: true, children: [' ', formatRetryChip(retryActive, cols)] })), todoActive && (_jsxs(Text, { color: theme.accent, children: [' ', cols >= TODO_LIMITS.statusCompactCols
|
|
68
108
|
? `todo ${todoActive.done}/${todoActive.total}`
|
|
69
|
-
: `[${todoActive.done}/${todoActive.total}]`] })),
|
|
109
|
+
: `[${todoActive.done}/${todoActive.total}]`] })), servicesActive && (_jsxs(Text, { color: theme.accent, children: [' ', cols >= PROC_LIMITS.statusCompactCols
|
|
110
|
+
? `svc ${servicesActive.live}`
|
|
111
|
+
: `[${servicesActive.live}]`] })), cols >= 60 && (_jsxs(Text, { color: theme.muted, children: [' ', provider, ":", model] })), thinkingLevel !== 'off' && cols >= 72 && (_jsxs(Text, { color: theme.accent, children: [" think:", thinkingLevel] }))] }), _jsxs(Box, { flexDirection: "row", children: [compactionActive && cols >= COMPACTION_LIMITS.statusCompactCols && (_jsxs(Text, { color: compactionActive.inFlight ? theme.accent : theme.muted, children: [compactionActive.inFlight ? 'compacting' : 'compact', ' '] })), fastActive && cols >= FAST_LIMITS.statusCompactCols && (_jsxs(Text, { color: theme.muted, children: ["fast", fastActive.inFlight ? '*' : '', ' '] })), ecoRung > 0 && cols >= ECO_MIN_COLS && (_jsxs(Text, { color: theme.muted, children: ["eco", ' '] })), scrolledLines > 0 && (_jsxs(Text, { color: theme.noticeWarn, children: [glyphs.arrowUp, scrolledLines, ' '] })), cols >= 60 && (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: "[" }), _jsx(Text, { color: gauge.fillColor, children: gauge.filled }), _jsx(Text, { color: gauge.trackColor, children: gauge.empty }), _jsx(Text, { color: theme.muted, children: "] " })] })), _jsx(Text, { color: theme.muted, children: pctLabel }), cols >= ABSOLUTE_PAIR_MIN_COLS && (_jsxs(Text, { color: theme.muted, children: [" ", absolutePair] })), cols >= SESSION_TOTAL_MIN_COLS && _jsxs(Text, { color: theme.muted, children: [" ", tokens] }), _jsxs(Text, { color: theme.muted, children: [" ", formatCost(usageTotal.costUsd)] }), running && (_jsxs(Text, { color: theme.muted, children: [tokPerSec > 0 && cols >= 72 ? ` ${tokPerSec} tok/s` : '', " ", formatDuration(elapsedMs)] }))] })] }));
|
|
70
112
|
}
|
package/dist/ui/TeamPanel.d.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import React from 'react';
|
|
20
20
|
import type { Theme } from './theme.js';
|
|
21
21
|
import type { TermCapabilities } from './capabilities.js';
|
|
22
|
+
import { type TeamPanelLayout } from './layout/team-panel.js';
|
|
22
23
|
import type { SubagentRun, TeamSnapshot } from '../team/types.js';
|
|
23
24
|
export interface TeamPanelProps {
|
|
24
25
|
snapshot: TeamSnapshot;
|
|
@@ -35,6 +36,7 @@ export interface TeamPanelProps {
|
|
|
35
36
|
caps: TermCapabilities;
|
|
36
37
|
/** Injectable clock so the panel test does not depend on wall time. */
|
|
37
38
|
now?: number;
|
|
39
|
+
layout?: TeamPanelLayout;
|
|
38
40
|
}
|
|
39
41
|
export declare function activityBudget(cols: number): number;
|
|
40
42
|
/**
|
|
@@ -55,4 +57,4 @@ export declare function activityBudget(cols: number): number;
|
|
|
55
57
|
* columns and break at 80.
|
|
56
58
|
*/
|
|
57
59
|
export declare function activityLine(run: SubagentRun, cols: number): string;
|
|
58
|
-
export declare function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, }: TeamPanelProps): React.ReactElement;
|
|
60
|
+
export declare function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }: TeamPanelProps): React.ReactElement | null;
|
package/dist/ui/TeamPanel.js
CHANGED
|
@@ -5,7 +5,7 @@ import { pickGlyphs } from './glyphs.js';
|
|
|
5
5
|
import { formatDuration } from '../agent/usage.js';
|
|
6
6
|
import { describeToolActivity, sanitizeActivity } from '../team/activity.js';
|
|
7
7
|
import { TEAM_LIMITS } from '../team/limits.js';
|
|
8
|
-
import {
|
|
8
|
+
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
9
9
|
function isRunning(run) {
|
|
10
10
|
return run.phase === 'starting' || run.phase === 'thinking' || run.phase === 'tool' ||
|
|
11
11
|
run.phase === 'waiting';
|
|
@@ -101,24 +101,28 @@ export function activityLine(run, cols) {
|
|
|
101
101
|
return 'aborted';
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, }) {
|
|
104
|
+
export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }) {
|
|
105
|
+
const projection = layout ?? buildTeamPanelLayout({
|
|
106
|
+
snapshot, terminalRows: rows, availableRows: Infinity,
|
|
107
|
+
});
|
|
108
|
+
if (projection.rowCount === 0)
|
|
109
|
+
return null;
|
|
105
110
|
const glyphs = pickGlyphs(caps);
|
|
106
111
|
const at = now ?? Date.now();
|
|
107
112
|
const running = snapshot.runs.filter(isRunning).length;
|
|
108
113
|
const done = snapshot.runs.filter(isSettled).length;
|
|
109
114
|
const elapsed = formatDuration(Math.max(0, at - snapshot.startedAt));
|
|
110
115
|
const header = (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { wrap: "truncate", color: theme.accent, bold: true, children: [glyphs.teamAgent, " team", ' ', _jsxs(Text, { color: theme.muted, children: [running, " running ", glyphs.midDot, " ", done, " done ", glyphs.midDot, " ", elapsed] })] }), snapshot.messageCount > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [glyphs.teamMail, " ", snapshot.messageCount] }))] }));
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
if (rows < TEAM_LIMITS.panelCollapseRows) {
|
|
116
|
+
// Short terminals or a long draft leave only the shared header projection,
|
|
117
|
+
// which still answers whether anything is running and for how long.
|
|
118
|
+
if (projection.collapsed) {
|
|
115
119
|
return _jsx(Box, { flexDirection: "column", flexShrink: 0, children: header });
|
|
116
120
|
}
|
|
117
121
|
// RANKED, not sliced by array index (F-2). The slot pool hands out indices in
|
|
118
122
|
// order, so `slice(0, 5)` showed the five children that finished FIRST and hid
|
|
119
123
|
// everything still working behind `+N more` - at exactly the fan-out widths
|
|
120
124
|
// the requirement permits.
|
|
121
|
-
const { visible, hiddenTotal, hiddenRunning } =
|
|
125
|
+
const { visible, hiddenTotal, hiddenRunning } = projection;
|
|
122
126
|
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [header, visible.map((run) => {
|
|
123
127
|
const color = statusColor(run, theme);
|
|
124
128
|
// One spinner per RUNNING row, and the row cap is 5 precisely because
|
|
@@ -126,8 +130,8 @@ export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, no
|
|
|
126
130
|
// `reducedMotion` and a non-Unicode terminal fall back to the same
|
|
127
131
|
// static marker, exactly as `AssistantEntry` and `ToolCard` already do.
|
|
128
132
|
const animate = isRunning(run) && !reducedMotion && caps.unicode;
|
|
129
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
|
|
130
|
-
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject] }))] }));
|
|
133
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
|
|
134
|
+
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
|
|
131
135
|
}
|
|
132
136
|
function marker(run, glyphs) {
|
|
133
137
|
if (run.phase === 'done')
|
package/dist/ui/TodoPanel.d.ts
CHANGED
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* stop being a rail and start eating viewport height, growing and shrinking the
|
|
15
15
|
* transcript every time the list changed length.
|
|
16
16
|
*
|
|
17
|
-
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is
|
|
17
|
+
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is 14-36
|
|
18
18
|
* columns wide and the system prompt's last line is "Respond in the language the
|
|
19
19
|
* user writes in", so CJK item text in an 18-column rail is the NORMAL case
|
|
20
20
|
* rather than the edge case. Every text cell is `wrap="truncate"` and Ink's
|
|
21
21
|
* truncation is display-width aware, so nothing can overflow the box — and NO
|
|
22
|
-
* ARITHMETIC HERE MAY BUDGET USER TEXT BY `.length`. The
|
|
23
|
-
* `padStart(2)` cells below are ASCII
|
|
22
|
+
* ARITHMETIC HERE MAY BUDGET USER TEXT BY `.length`. The count string and
|
|
23
|
+
* `padStart(2)` cells below are ASCII counts and indices, which is why they are
|
|
24
24
|
* safe; a "characters that fit" calculation over `content` would not be. That is
|
|
25
25
|
* the whole of the lesson `TeamPanel`'s `WIDE_CHAR_ALLOWANCE` records at length,
|
|
26
26
|
* and the mitigation here is to have no such budget at all.
|
|
@@ -34,7 +34,7 @@ export interface TodoPanelProps {
|
|
|
34
34
|
/** From `todoRailWidth(cols)`. The rail's whole column budget, separator included. */
|
|
35
35
|
width: number;
|
|
36
36
|
/**
|
|
37
|
-
* From `
|
|
37
|
+
* From `buildTodoRailLayout` after actual team/menu rows are subtracted.
|
|
38
38
|
* (I-10 / P1-3). The gate that mounts this component and the number it lays
|
|
39
39
|
* out against are ONE value computed once in `App`: a panel that mounts on one
|
|
40
40
|
* number and lays out against another renders nothing and reports nothing.
|
|
@@ -46,4 +46,4 @@ export interface TodoPanelProps {
|
|
|
46
46
|
theme: Theme;
|
|
47
47
|
caps: TermCapabilities;
|
|
48
48
|
}
|
|
49
|
-
export declare function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }: TodoPanelProps): React.ReactElement;
|
|
49
|
+
export declare function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }: TodoPanelProps): React.ReactElement | null;
|
package/dist/ui/TodoPanel.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* TodoPanel — the right rail (todo-plan-execution §6.1).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`; a literal here bypasses the
|
|
6
|
+
* capability probe and shows mojibake on a legacy console (`glyphs.ts` header).
|
|
7
|
+
*
|
|
8
|
+
* MOUNTED ONLY WHEN A LIST EXISTS. `App` passes `null` the rest of the time and
|
|
9
|
+
* this component is not rendered at all, so a session that never plans has zero
|
|
10
|
+
* columns and zero layout shift (R-g's "no furniture when idle" / AC-24).
|
|
11
|
+
*
|
|
12
|
+
* IT MUST BE MOUNTED IN `AppShell`'s MIDDLE BAND, NEVER IN THE BOTTOM CHROME
|
|
13
|
+
* (C-6 / I-4). This is a COLUMN BESIDE the viewport, and the middle band is the
|
|
14
|
+
* only row-wrapped region there is; moved down into the bottom chrome it would
|
|
15
|
+
* stop being a rail and start eating viewport height, growing and shrinking the
|
|
16
|
+
* transcript every time the list changed length.
|
|
17
|
+
*
|
|
18
|
+
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is 14-36
|
|
19
|
+
* columns wide and the system prompt's last line is "Respond in the language the
|
|
20
|
+
* user writes in", so CJK item text in an 18-column rail is the NORMAL case
|
|
21
|
+
* rather than the edge case. Every text cell is `wrap="truncate"` and Ink's
|
|
22
|
+
* truncation is display-width aware, so nothing can overflow the box — and NO
|
|
23
|
+
* ARITHMETIC HERE MAY BUDGET USER TEXT BY `.length`. The count string and
|
|
24
|
+
* `padStart(2)` cells below are ASCII counts and indices, which is why they are
|
|
25
|
+
* safe; a "characters that fit" calculation over `content` would not be. That is
|
|
26
|
+
* the whole of the lesson `TeamPanel`'s `WIDE_CHAR_ALLOWANCE` records at length,
|
|
27
|
+
* and the mitigation here is to have no such budget at all.
|
|
28
|
+
*/
|
|
29
|
+
import { useMemo } from 'react';
|
|
2
30
|
import { Box, Text } from 'ink';
|
|
3
31
|
import Spinner from 'ink-spinner';
|
|
4
32
|
import { pickGlyphs } from './glyphs.js';
|
|
@@ -6,7 +34,7 @@ import { buildGauge } from './gauge.js';
|
|
|
6
34
|
import { railBorderProps } from './layout/Gutter.js';
|
|
7
35
|
import { TODO_RAIL_INDEX_MIN_COLS } from './layout/rail.js';
|
|
8
36
|
import { TODO_LIMITS } from '../todo/limits.js';
|
|
9
|
-
import { selectTodoRows } from '../todo/panel-rows.js';
|
|
37
|
+
import { selectCompactTodoRows, selectTodoRows } from '../todo/panel-rows.js';
|
|
10
38
|
/** Header row + the blank row under the gauge. */
|
|
11
39
|
const FIXED_ROWS = 2;
|
|
12
40
|
/** The gauge is the one purely decorative element, so it is the first to go. */
|
|
@@ -19,8 +47,11 @@ const MARKER_COLS = 3;
|
|
|
19
47
|
/** Index cell: two digits plus the space that separates it from the text. */
|
|
20
48
|
const INDEX_COLS = 3;
|
|
21
49
|
function marker(item, glyphs) {
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
// Ink's tokenizer otherwise treats the bare emoji-capable check as one cell,
|
|
51
|
+
// while string-width counts two, adding an extra visible space to each row.
|
|
52
|
+
if (item.status === 'completed') {
|
|
53
|
+
return glyphs.todoDone.length === 1 ? `${glyphs.todoDone}\uFE0F` : glyphs.todoDone;
|
|
54
|
+
}
|
|
24
55
|
if (item.status === 'in_progress')
|
|
25
56
|
return glyphs.todoActive;
|
|
26
57
|
return glyphs.todoPending;
|
|
@@ -34,41 +65,42 @@ function rowColor(item, theme) {
|
|
|
34
65
|
}
|
|
35
66
|
export function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }) {
|
|
36
67
|
const glyphs = pickGlyphs(caps);
|
|
68
|
+
// Ink reapplies all four Yoga borders when the borderStyle object changes,
|
|
69
|
+
// but unchanged borderTop/Bottom=false props are not reapplied on that update.
|
|
70
|
+
const border = useMemo(() => railBorderProps(glyphs.railVertical), [glyphs.railVertical]);
|
|
71
|
+
if (snapshot.items.length === 0 || rows < TODO_LIMITS.panelMinRows)
|
|
72
|
+
return null;
|
|
73
|
+
const compact = rows < TODO_LIMITS.panelFullRows;
|
|
37
74
|
const showGauge = width >= GAUGE_MIN_COLS && rows >= GAUGE_MIN_ROWS;
|
|
38
|
-
const showIndex = width >= TODO_RAIL_INDEX_MIN_COLS;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
75
|
+
const showIndex = !compact && width >= TODO_RAIL_INDEX_MIN_COLS;
|
|
76
|
+
const compactFooter = compact && snapshot.items.length > rows - 1;
|
|
77
|
+
const itemRows = compact ? rows - 1 - Number(compactFooter)
|
|
78
|
+
: rows - FIXED_ROWS - Number(showGauge);
|
|
79
|
+
const { visible, hiddenAbove, hiddenBelow } = compact
|
|
80
|
+
? selectCompactTodoRows(snapshot.items, itemRows)
|
|
81
|
+
: selectTodoRows(snapshot.items, itemRows);
|
|
41
82
|
const pct = snapshot.total > 0 ? Math.round((snapshot.doneCount / snapshot.total) * 100) : 0;
|
|
42
83
|
const gauge = buildGauge(pct, Math.max(4, width - CHROME_COLS - 2), theme, caps);
|
|
43
84
|
const complete = snapshot.doneCount === snapshot.total;
|
|
85
|
+
const countText = `${snapshot.doneCount}/${snapshot.total}`;
|
|
86
|
+
const showDone = complete
|
|
87
|
+
&& width - CHROME_COLS >= 'TODO '.length + countText.length + ' done'.length;
|
|
88
|
+
const count = `${countText}${showDone ? ' done' : ''}`;
|
|
44
89
|
return (
|
|
45
90
|
// `flexShrink={0}` IS LOAD-BEARING (P1-4). Ink's `Box.defaultProps` is
|
|
46
91
|
// `{ flexWrap:'nowrap', flexDirection:'row', flexGrow:0, flexShrink:1 }`, so
|
|
47
92
|
// a `width={n}` box inside a shrinking row is SQUEEZED under pressure — and
|
|
48
93
|
// every piece of arithmetic in `rail.ts` silently becomes a suggestion at
|
|
49
94
|
// exactly the narrow widths where the guarantee matters. `ScrollIndicator`
|
|
50
|
-
// spells the same pair one level down.
|
|
51
|
-
//
|
|
52
|
-
_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, overflow: "hidden", paddingLeft: 1, ...
|
|
95
|
+
// spells the same pair one level down. Height uses the same row budget as
|
|
96
|
+
// selection, so content cannot silently enlarge the middle band.
|
|
97
|
+
_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, height: rows, overflow: "hidden", paddingLeft: 1, ...border, borderColor: theme.border, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: "TODO" }) }), _jsx(Box, { width: count.length, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: complete ? theme.toolDone : theme.muted, children: count }) })] }), showGauge && (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: gauge.fillColor, children: gauge.filled }), _jsx(Text, { color: gauge.trackColor, children: gauge.empty })] })), !compact && _jsx(Text, { children: " " }), !compact && hiddenAbove > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenAbove, " above"] })), visible.map(({ item, index }) => {
|
|
53
98
|
const active = item.status === 'in_progress';
|
|
54
99
|
// The spinner is gated on `running`: a spinner while the agent is idle
|
|
55
100
|
// is a lie about the state of the world, and this panel outlives the run
|
|
56
101
|
// that filled it. Reduced motion and a non-Unicode terminal fall back to
|
|
57
102
|
// the same static marker, exactly as `AssistantEntry` and `TeamPanel` do.
|
|
58
103
|
const animate = active && running && !reducedMotion && caps.unicode;
|
|
59
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, width: MARKER_COLS, children: _jsx(Text, { color: rowColor(item, theme), bold: active, children: animate ? _jsx(Spinner, { type: "dots" }) : marker(item, glyphs) }) }), showIndex && (_jsx(Box, { flexShrink: 0, width: INDEX_COLS, children: _jsx(Text, { color: theme.muted, children: String(index + 1).padStart(2) }) })), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap:
|
|
60
|
-
}), hiddenBelow > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenBelow, " more"] }))] }));
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Keep the wrapping row inside `TODO_LIMITS.activeWrapRows`.
|
|
64
|
-
*
|
|
65
|
-
* A CHARACTER BUDGET IS NOT A COLUMN BUDGET (P2-3), so this is deliberately
|
|
66
|
-
* generous rather than exact: it exists to stop a maximum-length `activeForm`
|
|
67
|
-
* from claiming five rows of an 18-column rail, and Ink's display-width-aware
|
|
68
|
-
* truncation is what actually keeps the row inside the box. Erring long costs
|
|
69
|
-
* nothing — the surrounding `overflow="hidden"` clips it.
|
|
70
|
-
*/
|
|
71
|
-
function clampWrapped(text, width) {
|
|
72
|
-
const budget = Math.max(1, width - CHROME_COLS) * TODO_LIMITS.activeWrapRows;
|
|
73
|
-
return text.length > budget ? text.slice(0, budget) : text;
|
|
104
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, width: MARKER_COLS, children: _jsx(Text, { color: rowColor(item, theme), bold: active, children: animate ? _jsx(Spinner, { type: "dots" }) : marker(item, glyphs) }) }), showIndex && (_jsx(Box, { flexShrink: 0, width: INDEX_COLS, children: _jsx(Text, { color: theme.muted, children: String(index + 1).padStart(2) }) })), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: rowColor(item, theme), bold: active, children: active ? item.activeForm : item.content }) })] }, `${index}-${item.content}`));
|
|
105
|
+
}), compactFooter && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: ["-", hiddenAbove, " +", hiddenBelow] })), !compact && hiddenBelow > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenBelow, " more"] }))] }, glyphs.railVertical));
|
|
74
106
|
}
|
package/dist/ui/Transcript.js
CHANGED
|
@@ -40,6 +40,7 @@ import { TodoCard, todoCardColor } from './entries/TodoCard.js';
|
|
|
40
40
|
import { RetryCard, retryCardColor } from './entries/RetryCard.js';
|
|
41
41
|
import { FastCard, fastCardColor } from './entries/FastCard.js';
|
|
42
42
|
import { CompactionCard, compactionCardColor } from './entries/CompactionCard.js';
|
|
43
|
+
import { ServiceCard, serviceCardColor, serviceGlyph } from './entries/ServiceCard.js';
|
|
43
44
|
import { MeasuredEntry } from './layout/MeasuredEntry.js';
|
|
44
45
|
import { useViewportGeometry } from './layout/viewport-geometry.js';
|
|
45
46
|
import { estimateEntryRows, heightKey, selectWindow, VIRTUAL_LIMITS, } from './layout/virtual-window.js';
|
|
@@ -107,6 +108,31 @@ export function computeSettledCount(entries, expandedToolIds) {
|
|
|
107
108
|
// which is the other half.
|
|
108
109
|
if (e.kind === 'compaction' && e.live)
|
|
109
110
|
break;
|
|
111
|
+
/**
|
|
112
|
+
* A SERVICE BLOCKS ONLY WHILE IT IS `starting` (D-11 / P0-4).
|
|
113
|
+
*
|
|
114
|
+
* Every clause above is bounded by an OPERATION — a dispatch, a turn, one
|
|
115
|
+
* LLM call, one countdown — which is why each of them can safely pin this
|
|
116
|
+
* prefix scan: the wait is short and it ends. A service is bounded by the
|
|
117
|
+
* USER'S INTENT. A dev server left up for an hour is the normal case, not
|
|
118
|
+
* the pathological one, so treating it like the other five would pin the
|
|
119
|
+
* boundary at the card and re-render the whole transcript from there on
|
|
120
|
+
* EVERY FRAME, FOREVER, on exactly the long sessions where it is least
|
|
121
|
+
* likely to be noticed.
|
|
122
|
+
*
|
|
123
|
+
* `starting` is different, and it is different for the reason that makes
|
|
124
|
+
* every other clause here legitimate: it is bounded by `readyTimeoutMs`,
|
|
125
|
+
* after which the supervisor calls the service `running` whatever happened.
|
|
126
|
+
*
|
|
127
|
+
* The cost of the rest is that a printed card stops mirroring live state —
|
|
128
|
+
* which is what a transcript IS. A terminal transition appends a NEW one-row
|
|
129
|
+
* entry rather than rewriting a card `<Static>` has already drawn, and live
|
|
130
|
+
* state lives in the status chip, `/bg` and `bash_output`. Weakening this to
|
|
131
|
+
* "any non-terminal service blocks" is the change D-11 exists to prevent;
|
|
132
|
+
* see condition 5 of the design's review verdict before making it.
|
|
133
|
+
*/
|
|
134
|
+
if (e.kind === 'service' && e.status === 'starting')
|
|
135
|
+
break;
|
|
110
136
|
count = i + 1;
|
|
111
137
|
}
|
|
112
138
|
return count;
|
|
@@ -148,7 +174,7 @@ function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion,
|
|
|
148
174
|
const frame = (glyph, color, children) => (_jsx(EntryFrame, { glyph: glyph, color: color, continuation: glyphs.railVertical, separation: separation, spacerColor: theme.border, children: children }));
|
|
149
175
|
switch (entry.kind) {
|
|
150
176
|
case 'user':
|
|
151
|
-
return frame(glyphs.user, theme.user, _jsx(UserEntry, { text: entry.text, theme: theme }));
|
|
177
|
+
return frame(glyphs.user, theme.user, _jsx(UserEntry, { text: entry.text, theme: theme, caps: caps }));
|
|
152
178
|
case 'assistant': {
|
|
153
179
|
// While text streams, the spinner IS the role marker. Braille dots are
|
|
154
180
|
// both an animation and a Unicode-only glyph, so reduced motion and an
|
|
@@ -208,6 +234,8 @@ function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion,
|
|
|
208
234
|
elapsedMs: entry.live && nowSec !== undefined && entry.startedAt !== undefined
|
|
209
235
|
? Math.max(0, nowSec * 1000 - entry.startedAt)
|
|
210
236
|
: undefined, live: entry.live, expanded: expanded, reducedMotion: reducedMotion, theme: theme, caps: caps }));
|
|
237
|
+
case 'service':
|
|
238
|
+
return frame(serviceGlyph(entry, glyphs), serviceCardColor(entry, theme), _jsx(ServiceCard, { serviceId: entry.serviceId, command: entry.command, status: entry.status, url: entry.url, port: entry.port, exitCode: entry.exitCode, startedAt: entry.startedAt, readyAt: entry.readyAt, endedAt: entry.endedAt, rows: entry.rows, terminal: entry.terminal, killIncomplete: entry.killIncomplete, expanded: expanded, reducedMotion: reducedMotion, theme: theme, caps: caps }));
|
|
211
239
|
case 'notice':
|
|
212
240
|
return frame(noticeSymbol(entry.level, theme), noticeColor(entry.level, theme), _jsx(Text, { color: noticeColor(entry.level, theme), children: entry.text }));
|
|
213
241
|
default:
|
package/dist/ui/UpdateLine.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* still truthy. Taking that branch would emit a box of ZERO rows - the frame
|
|
10
10
|
* keeps its fixed height, Yoga hands the row to the transcript, and
|
|
11
11
|
* `viewportRows()` goes on reporting the old number to `ScrollViewport`,
|
|
12
|
-
* `selectWindow`, `overlayMaxRows`, `popupMaxRows` and `
|
|
12
|
+
* `selectWindow`, `overlayMaxRows`, `popupMaxRows` and `buildTodoRailLayout`. The
|
|
13
13
|
* transcript would gain a row while the updater is idle and lose it the moment
|
|
14
14
|
* it has news. Presence is the CALLER's decision, through
|
|
15
15
|
* `shouldRenderUpdateLine`, exactly as `App.tsx` already decides the activity
|
package/dist/ui/capabilities.js
CHANGED
|
@@ -15,10 +15,6 @@ function detectColorLevel(env) {
|
|
|
15
15
|
return 0;
|
|
16
16
|
if (env.TERM === 'dumb')
|
|
17
17
|
return 0;
|
|
18
|
-
// A truecolor-capable terminal advertises itself.
|
|
19
|
-
const colorterm = env.COLORTERM?.toLowerCase();
|
|
20
|
-
if (colorterm === 'truecolor' || colorterm === '24bit')
|
|
21
|
-
return 3;
|
|
22
18
|
// FORCE_COLOR is an explicit override of the auto-detected depth.
|
|
23
19
|
const force = env.FORCE_COLOR;
|
|
24
20
|
if (force !== undefined) {
|
|
@@ -30,6 +26,10 @@ function detectColorLevel(env) {
|
|
|
30
26
|
return 3;
|
|
31
27
|
return 1; // '1' | 'true' | '' → at least basic color
|
|
32
28
|
}
|
|
29
|
+
// A truecolor-capable terminal advertises itself.
|
|
30
|
+
const colorterm = env.COLORTERM?.toLowerCase();
|
|
31
|
+
if (colorterm === 'truecolor' || colorterm === '24bit')
|
|
32
|
+
return 3;
|
|
33
33
|
// Fall back to chalk's own platform detection.
|
|
34
34
|
const level = chalk.level;
|
|
35
35
|
return level >= 0 && level <= 3 ? level : 0;
|
|
@@ -42,7 +42,7 @@ function detectUnicode(env) {
|
|
|
42
42
|
if (env.WT_SESSION)
|
|
43
43
|
return true; // Windows Terminal
|
|
44
44
|
const program = env.TERM_PROGRAM;
|
|
45
|
-
if (program === 'vscode' || program === 'iTerm.app' || program === 'Apple_Terminal')
|
|
45
|
+
if (program === 'aragonmesh' || program === 'vscode' || program === 'iTerm.app' || program === 'Apple_Terminal')
|
|
46
46
|
return true;
|
|
47
47
|
// Legacy cmd.exe (no TERM, no WT_SESSION) and unknown terminals fall back to ASCII.
|
|
48
48
|
return false;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer and transcript row ceilings (tui-paste-handling section 5.5 / 5.6).
|
|
3
|
+
*
|
|
4
|
+
* Structural, not policy (D-10): the only user-facing key this feature adds is
|
|
5
|
+
* the boolean `paste`. Everything here is a bound that keeps the frame's row
|
|
6
|
+
* arithmetic true, and every one of them is switchable by returning a larger
|
|
7
|
+
* number rather than by a config flag.
|
|
8
|
+
*/
|
|
9
|
+
/** At or below BOTH of these a paste is inserted verbatim rather than collapsed. */
|
|
10
|
+
export declare const PASTE_INLINE_MAX_LINES = 6;
|
|
11
|
+
export declare const PASTE_INLINE_MAX_CHARS = 400;
|
|
12
|
+
/**
|
|
13
|
+
* Rows one submitted user message may RENDER in the transcript, before the
|
|
14
|
+
* `... +N more lines` tail (G7).
|
|
15
|
+
*
|
|
16
|
+
* `ui/layout/virtual-window.ts` imports this rather than duplicating the number,
|
|
17
|
+
* unlike `DIFF_COLLAPSED_LINES` / `TOOL_LIVE_TAIL_ROWS` beside it: I-13 makes the
|
|
18
|
+
* render cap and the height estimate THE SAME NUMBER, and there is no
|
|
19
|
+
* import-cycle reason here for them to be two.
|
|
20
|
+
*/
|
|
21
|
+
export declare const USER_ENTRY_MAX_ROWS = 40;
|
|
22
|
+
/**
|
|
23
|
+
* Ceiling on the composer's DRAFT rows at a given terminal height.
|
|
24
|
+
*
|
|
25
|
+
* Non-decreasing in `terminalRows`. Returning `Infinity` reduces the whole
|
|
26
|
+
* height-bound layer to the rung below it (section 12, S4), which is what makes
|
|
27
|
+
* that stage revertible on its own.
|
|
28
|
+
*/
|
|
29
|
+
export declare function draftMaxRows(terminalRows: number): number;
|
|
30
|
+
/** The terminal height at which `draftMaxRows` steps from 3 to 6. Pinned by test. */
|
|
31
|
+
export declare const DRAFT_MAX_ROWS_HINT_TIER = 20;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer and transcript row ceilings (tui-paste-handling section 5.5 / 5.6).
|
|
3
|
+
*
|
|
4
|
+
* Structural, not policy (D-10): the only user-facing key this feature adds is
|
|
5
|
+
* the boolean `paste`. Everything here is a bound that keeps the frame's row
|
|
6
|
+
* arithmetic true, and every one of them is switchable by returning a larger
|
|
7
|
+
* number rather than by a config flag.
|
|
8
|
+
*/
|
|
9
|
+
/** At or below BOTH of these a paste is inserted verbatim rather than collapsed. */
|
|
10
|
+
export const PASTE_INLINE_MAX_LINES = 6;
|
|
11
|
+
export const PASTE_INLINE_MAX_CHARS = 400;
|
|
12
|
+
/**
|
|
13
|
+
* Rows one submitted user message may RENDER in the transcript, before the
|
|
14
|
+
* `... +N more lines` tail (G7).
|
|
15
|
+
*
|
|
16
|
+
* `ui/layout/virtual-window.ts` imports this rather than duplicating the number,
|
|
17
|
+
* unlike `DIFF_COLLAPSED_LINES` / `TOOL_LIVE_TAIL_ROWS` beside it: I-13 makes the
|
|
18
|
+
* render cap and the height estimate THE SAME NUMBER, and there is no
|
|
19
|
+
* import-cycle reason here for them to be two.
|
|
20
|
+
*/
|
|
21
|
+
export const USER_ENTRY_MAX_ROWS = 40;
|
|
22
|
+
/**
|
|
23
|
+
* The terminal height at which the composer's hint row starts to fit.
|
|
24
|
+
*
|
|
25
|
+
* DELIBERATELY A SECOND COPY of `ui/layout/budget.ts::HINT_MIN_ROWS` rather than
|
|
26
|
+
* an import: `budget.ts` calls `draftMaxRows` to clamp its composer term, so an
|
|
27
|
+
* import in this direction would close an ESM cycle whose only protection is
|
|
28
|
+
* that neither side reads the other at module scope -- true today, and a
|
|
29
|
+
* confusing TDZ crash the first time someone hoists a use. `budget.test.ts`
|
|
30
|
+
* asserts the two numbers are equal, so they cannot drift in silence.
|
|
31
|
+
*/
|
|
32
|
+
const DRAFT_TIER_MIN_ROWS = 20;
|
|
33
|
+
/**
|
|
34
|
+
* Ceiling on the composer's DRAFT rows at a given terminal height.
|
|
35
|
+
*
|
|
36
|
+
* Non-decreasing in `terminalRows`. Returning `Infinity` reduces the whole
|
|
37
|
+
* height-bound layer to the rung below it (section 12, S4), which is what makes
|
|
38
|
+
* that stage revertible on its own.
|
|
39
|
+
*/
|
|
40
|
+
export function draftMaxRows(terminalRows) {
|
|
41
|
+
if (!Number.isFinite(terminalRows))
|
|
42
|
+
return 3;
|
|
43
|
+
if (terminalRows >= 30)
|
|
44
|
+
return 10;
|
|
45
|
+
if (terminalRows >= DRAFT_TIER_MIN_ROWS)
|
|
46
|
+
return 6;
|
|
47
|
+
return 3;
|
|
48
|
+
}
|
|
49
|
+
/** The terminal height at which `draftMaxRows` steps from 3 to 6. Pinned by test. */
|
|
50
|
+
export const DRAFT_MAX_ROWS_HINT_TIER = DRAFT_TIER_MIN_ROWS;
|