@aragon-agent/cli 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +321 -0
- package/README.md +297 -2
- package/dist/agent/controller.d.ts +193 -1
- package/dist/agent/controller.js +475 -2
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +189 -0
- package/dist/agent/reducer.js +301 -2
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +297 -16
- package/dist/commands/builtins.js +165 -24
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +118 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +355 -0
- package/dist/compaction/compactor.d.ts +302 -0
- package/dist/compaction/compactor.js +810 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +215 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +264 -0
- package/dist/compaction/wiring.js +577 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +5 -2
- package/dist/config/env.js +140 -3
- package/dist/config/load.d.ts +42 -0
- package/dist/config/load.js +101 -1
- package/dist/config/schema.d.ts +244 -0
- package/dist/config/schema.js +170 -0
- package/dist/config/store.js +37 -1
- package/dist/config/ui-state.d.ts +28 -1
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/runner.js +42 -0
- package/dist/input/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +53 -1
- package/dist/logging/install.js +81 -1
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +55 -1
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +62 -2
- package/dist/ui/App.js +546 -16
- package/dist/ui/Composer.d.ts +66 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +82 -3
- package/dist/ui/PromptInput.js +232 -49
- package/dist/ui/StatusBar.d.ts +59 -0
- package/dist/ui/StatusBar.js +16 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +61 -3
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +44 -9
- package/dist/ui/layout/budget.js +42 -10
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +85 -1
- package/dist/ui/overlays/HelpOverlay.js +9 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +74 -2
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +38 -0
- package/dist/ui/screen.js +113 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +72 -0
- package/dist/ui/use-startup-notices.d.ts +27 -1
- package/dist/ui/use-startup-notices.js +43 -11
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/dist/ui/use-wheel-routing.js +8 -0
- package/package.json +4 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -42
- package/dist/input/stdin-mouse-filter.js +0 -153
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceCard — one supervised background service in the transcript
|
|
3
|
+
* (background-service-supervision §3.8).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`. `ui/` is inside
|
|
6
|
+
* `glyphs.test.ts::inScope`, so a literal here fails the scan.
|
|
7
|
+
*
|
|
8
|
+
* NO ANIMATED SPINNER, IN ANY STATE (D-14 / P2-7). That is STRICTER than
|
|
9
|
+
* `single-spinner-while-running` D-1, which suppresses animation only while the
|
|
10
|
+
* activity line is mounted — and a service can sit at `starting` while the agent
|
|
11
|
+
* is IDLE, where D-1's "one owner on screen" argument does not reach because
|
|
12
|
+
* there is no owner on screen. A card that animated only when the agent happened
|
|
13
|
+
* to be idle would be the worst of both. It uses `glyphs.spinnerStill`
|
|
14
|
+
* throughout and still accepts `reducedMotion` for consistency with
|
|
15
|
+
* `EntryView`'s comparator.
|
|
16
|
+
*
|
|
17
|
+
* TWO SHAPES, ONE COMPONENT. `terminal: true` is the one-row record a service's
|
|
18
|
+
* exit appends (D-11); everything else is the live card. They share a component
|
|
19
|
+
* because they share a colour ladder and a status vocabulary, and splitting them
|
|
20
|
+
* would be two places to keep those in step.
|
|
21
|
+
*/
|
|
22
|
+
import React from 'react';
|
|
23
|
+
import type { Theme } from '../theme.js';
|
|
24
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
25
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
26
|
+
import type { ServiceStatus } from '../../proc/types.js';
|
|
27
|
+
export interface ServiceCardProps {
|
|
28
|
+
serviceId: string;
|
|
29
|
+
command: string;
|
|
30
|
+
status: ServiceStatus;
|
|
31
|
+
url?: string;
|
|
32
|
+
port?: number;
|
|
33
|
+
exitCode: number | null;
|
|
34
|
+
startedAt: number;
|
|
35
|
+
readyAt?: number;
|
|
36
|
+
endedAt?: number;
|
|
37
|
+
rows: readonly string[];
|
|
38
|
+
/** The one-row record a terminal transition appends. */
|
|
39
|
+
terminal?: boolean;
|
|
40
|
+
killIncomplete?: boolean;
|
|
41
|
+
expanded?: boolean;
|
|
42
|
+
/** Accepted for comparator consistency; this card never animates (D-14). */
|
|
43
|
+
reducedMotion?: boolean;
|
|
44
|
+
theme: Theme;
|
|
45
|
+
caps: TermCapabilities;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The card's colour.
|
|
49
|
+
*
|
|
50
|
+
* `running` is `toolRunning` rather than the accent: it is alive and listening
|
|
51
|
+
* on nothing, which is a real third state and not a lesser `ready`. A clean
|
|
52
|
+
* `exited(0)` is muted, because a command that finished on purpose is not a
|
|
53
|
+
* failure.
|
|
54
|
+
*/
|
|
55
|
+
export declare function serviceCardColor(props: Pick<ServiceCardProps, 'status' | 'exitCode'>, theme: Theme): string | undefined;
|
|
56
|
+
/** The status glyph, by the same ladder as the colour. */
|
|
57
|
+
export declare function serviceGlyph(props: Pick<ServiceCardProps, 'status' | 'exitCode'>, glyphs: ReturnType<typeof pickGlyphs>): string;
|
|
58
|
+
declare function ServiceCardImpl(props: ServiceCardProps): React.ReactElement;
|
|
59
|
+
/**
|
|
60
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
61
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
62
|
+
* props below are stable references on a settled card, and `theme` / `caps` are
|
|
63
|
+
* `useMemo`d in `App.tsx` (I-L2-1).
|
|
64
|
+
*/
|
|
65
|
+
export declare const ServiceCard: React.MemoExoticComponent<typeof ServiceCardImpl>;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ServiceCard — one supervised background service in the transcript
|
|
4
|
+
* (background-service-supervision §3.8).
|
|
5
|
+
*
|
|
6
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`. `ui/` is inside
|
|
7
|
+
* `glyphs.test.ts::inScope`, so a literal here fails the scan.
|
|
8
|
+
*
|
|
9
|
+
* NO ANIMATED SPINNER, IN ANY STATE (D-14 / P2-7). That is STRICTER than
|
|
10
|
+
* `single-spinner-while-running` D-1, which suppresses animation only while the
|
|
11
|
+
* activity line is mounted — and a service can sit at `starting` while the agent
|
|
12
|
+
* is IDLE, where D-1's "one owner on screen" argument does not reach because
|
|
13
|
+
* there is no owner on screen. A card that animated only when the agent happened
|
|
14
|
+
* to be idle would be the worst of both. It uses `glyphs.spinnerStill`
|
|
15
|
+
* throughout and still accepts `reducedMotion` for consistency with
|
|
16
|
+
* `EntryView`'s comparator.
|
|
17
|
+
*
|
|
18
|
+
* TWO SHAPES, ONE COMPONENT. `terminal: true` is the one-row record a service's
|
|
19
|
+
* exit appends (D-11); everything else is the live card. They share a component
|
|
20
|
+
* because they share a colour ladder and a status vocabulary, and splitting them
|
|
21
|
+
* would be two places to keep those in step.
|
|
22
|
+
*/
|
|
23
|
+
import React from 'react';
|
|
24
|
+
import { Box, Text } from 'ink';
|
|
25
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
26
|
+
import { railBorderProps } from '../layout/Gutter.js';
|
|
27
|
+
import { formatDuration } from '../../agent/usage.js';
|
|
28
|
+
/** Tail rows the collapsed card draws. Mirrored as a NUMBER in `virtual-window.ts`. */
|
|
29
|
+
const COLLAPSED_TAIL_ROWS = 2;
|
|
30
|
+
/** Tail rows the expanded card draws — the snapshot's own cap. */
|
|
31
|
+
const EXPANDED_TAIL_ROWS = 8;
|
|
32
|
+
/**
|
|
33
|
+
* The card's colour.
|
|
34
|
+
*
|
|
35
|
+
* `running` is `toolRunning` rather than the accent: it is alive and listening
|
|
36
|
+
* on nothing, which is a real third state and not a lesser `ready`. A clean
|
|
37
|
+
* `exited(0)` is muted, because a command that finished on purpose is not a
|
|
38
|
+
* failure.
|
|
39
|
+
*/
|
|
40
|
+
export function serviceCardColor(props, theme) {
|
|
41
|
+
switch (props.status) {
|
|
42
|
+
case 'starting':
|
|
43
|
+
return theme.muted;
|
|
44
|
+
case 'ready':
|
|
45
|
+
return theme.accent;
|
|
46
|
+
case 'running':
|
|
47
|
+
return theme.toolRunning;
|
|
48
|
+
case 'failed':
|
|
49
|
+
return theme.toolError;
|
|
50
|
+
case 'exited':
|
|
51
|
+
return props.exitCode === 0 ? theme.muted : theme.toolError;
|
|
52
|
+
case 'stopped':
|
|
53
|
+
return theme.muted;
|
|
54
|
+
default:
|
|
55
|
+
return theme.muted;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** The status glyph, by the same ladder as the colour. */
|
|
59
|
+
export function serviceGlyph(props, glyphs) {
|
|
60
|
+
switch (props.status) {
|
|
61
|
+
case 'starting':
|
|
62
|
+
return glyphs.toolPending;
|
|
63
|
+
case 'ready':
|
|
64
|
+
return glyphs.toolDone;
|
|
65
|
+
case 'running':
|
|
66
|
+
return glyphs.toolRunning;
|
|
67
|
+
case 'failed':
|
|
68
|
+
return glyphs.toolError;
|
|
69
|
+
case 'exited':
|
|
70
|
+
return props.exitCode === 0 ? glyphs.toolDone : glyphs.toolError;
|
|
71
|
+
case 'stopped':
|
|
72
|
+
return glyphs.toolPending;
|
|
73
|
+
default:
|
|
74
|
+
return glyphs.toolPending;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** The right-hand status clause: what happened, and how long it took. */
|
|
78
|
+
function statusClause(props, dot) {
|
|
79
|
+
const { status, exitCode, startedAt, readyAt, endedAt } = props;
|
|
80
|
+
if (status === 'ready') {
|
|
81
|
+
const took = readyAt === undefined ? '' : `${dot}${formatDuration(readyAt - startedAt)}`;
|
|
82
|
+
return `ready${took}`;
|
|
83
|
+
}
|
|
84
|
+
if (status === 'running')
|
|
85
|
+
return 'running';
|
|
86
|
+
if (status === 'starting')
|
|
87
|
+
return 'starting';
|
|
88
|
+
const lived = endedAt === undefined ? '' : `${dot}${formatDuration(endedAt - startedAt)}`;
|
|
89
|
+
if (status === 'stopped')
|
|
90
|
+
return `stopped${lived}`;
|
|
91
|
+
if (status === 'failed')
|
|
92
|
+
return `failed${lived}`;
|
|
93
|
+
return `exited${exitCode === null ? '' : ` code ${exitCode}`}${lived}`;
|
|
94
|
+
}
|
|
95
|
+
function ServiceCardImpl(props) {
|
|
96
|
+
const { serviceId, command, url, rows, expanded, terminal, theme, caps } = props;
|
|
97
|
+
const glyphs = pickGlyphs(caps);
|
|
98
|
+
const color = serviceCardColor(props, theme);
|
|
99
|
+
const dot = ` ${glyphs.midDot} `;
|
|
100
|
+
const clause = statusClause(props, dot);
|
|
101
|
+
// THE TERMINAL RECORD IS ONE ROW, ALWAYS. It is a SECOND event at a SECOND
|
|
102
|
+
// time — the only thing `<Static>` permits once the live card is printed — so
|
|
103
|
+
// it names what happened and nothing else (D-11).
|
|
104
|
+
if (terminal) {
|
|
105
|
+
return (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: color, wrap: "truncate", children: ["service ", serviceId, " ", clause, props.killIncomplete ? ' (may have left a detached child)' : ''] }) }));
|
|
106
|
+
}
|
|
107
|
+
const shown = rows.slice(-(expanded ? EXPANDED_TAIL_ROWS : COLLAPSED_TAIL_ROWS));
|
|
108
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: theme.accent, bold: true, children: ["service ", serviceId] }), _jsxs(Text, { color: theme.muted, wrap: "truncate", children: [' ', command] }), _jsxs(Text, { color: color, wrap: "truncate", children: [dot, clause] })] }), url !== undefined && (_jsxs(Box, { flexDirection: "row", flexShrink: 0, paddingLeft: 1, children: [_jsx(Text, { color: theme.primary, children: url }), _jsxs(Text, { color: theme.muted, children: [dot, "ctrl+c stop", dot, "ctrl+o log"] })] })), shown.length > 0 && (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: shown.map((line, index) => (_jsx(Text, { color: theme.muted, wrap: "truncate", children: line }, index))) }))] }));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
112
|
+
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
113
|
+
* props below are stable references on a settled card, and `theme` / `caps` are
|
|
114
|
+
* `useMemo`d in `App.tsx` (I-L2-1).
|
|
115
|
+
*/
|
|
116
|
+
export const ServiceCard = React.memo(ServiceCardImpl);
|
|
@@ -3,15 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The role glyph, the gutter rail and the inter-entry spacing all belong to
|
|
5
5
|
* `EntryFrame` now (§4.3), so this component renders content and nothing else.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* IT DOES TAKE `caps` AS OF tui-paste-handling (G7): the row cap below needs one
|
|
8
|
+
* glyph, and a hardcoded ellipsis is the exact failure `glyphs.ts` exists to
|
|
9
|
+
* prevent. That is the only reason the earlier "it has no glyph of its own left
|
|
10
|
+
* to degrade" note no longer holds.
|
|
8
11
|
*/
|
|
9
12
|
import React from 'react';
|
|
10
13
|
import type { Theme } from '../theme.js';
|
|
11
|
-
|
|
14
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
15
|
+
declare function UserEntryImpl({ text, theme, caps, }: {
|
|
12
16
|
text: string;
|
|
13
17
|
theme: Theme;
|
|
18
|
+
caps: TermCapabilities;
|
|
14
19
|
}): React.ReactElement;
|
|
20
|
+
/**
|
|
21
|
+
* Rows this component renders for a given text.
|
|
22
|
+
*
|
|
23
|
+
* EXPORTED SO `virtual-window.ts` AND THIS FILE CANNOT DISAGREE (I-13). The
|
|
24
|
+
* render cap and the height estimate are ONE number; an over-estimated entry is
|
|
25
|
+
* never mounted, so it is never measured, so the estimate never self-corrects --
|
|
26
|
+
* which makes that particular disagreement permanent and silent.
|
|
27
|
+
*/
|
|
28
|
+
export declare function userEntryRenderedRows(lineCount: number): number;
|
|
15
29
|
/**
|
|
16
30
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
17
31
|
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
@@ -1,16 +1,45 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Render a user message entry.
|
|
4
4
|
*
|
|
5
5
|
* The role glyph, the gutter rail and the inter-entry spacing all belong to
|
|
6
6
|
* `EntryFrame` now (§4.3), so this component renders content and nothing else.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* IT DOES TAKE `caps` AS OF tui-paste-handling (G7): the row cap below needs one
|
|
9
|
+
* glyph, and a hardcoded ellipsis is the exact failure `glyphs.ts` exists to
|
|
10
|
+
* prevent. That is the only reason the earlier "it has no glyph of its own left
|
|
11
|
+
* to degrade" note no longer holds.
|
|
9
12
|
*/
|
|
10
13
|
import React from 'react';
|
|
11
14
|
import { Box, Text } from 'ink';
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
16
|
+
import { USER_ENTRY_MAX_ROWS } from '../composer-limits.js';
|
|
17
|
+
function UserEntryImpl({ text, theme, caps, }) {
|
|
18
|
+
const glyphs = pickGlyphs(caps);
|
|
19
|
+
const all = text.split('\n');
|
|
20
|
+
// A submitted 5 000-line paste otherwise asks Yoga to lay out 5 000 nodes
|
|
21
|
+
// inside a measured entry -- the hazard `render-cache.ts:37` already records
|
|
22
|
+
// in the same words ("a multi-megabyte 'code block' is almost always a pasted
|
|
23
|
+
// log"). The full text stays in `ViewState` and in the exit transcript, which
|
|
24
|
+
// is deliberately NOT capped: a replay a user pipes into an issue must be
|
|
25
|
+
// complete.
|
|
26
|
+
const hidden = Math.max(0, all.length - USER_ENTRY_MAX_ROWS);
|
|
27
|
+
const shown = hidden > 0 ? all.slice(0, USER_ENTRY_MAX_ROWS) : all;
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", children: [shown.map((line, i) => (_jsx(Text, { color: theme.user, bold: i === 0, children: line }, i))), hidden > 0 && (
|
|
29
|
+
// The tail NAMES the number of hidden rows rather than hiding them
|
|
30
|
+
// silently, which is what makes the cap honest (D-9 / R-10).
|
|
31
|
+
_jsx(Text, { color: theme.muted, children: `${glyphs.ellipsis} +${hidden} more lines` }))] }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rows this component renders for a given text.
|
|
35
|
+
*
|
|
36
|
+
* EXPORTED SO `virtual-window.ts` AND THIS FILE CANNOT DISAGREE (I-13). The
|
|
37
|
+
* render cap and the height estimate are ONE number; an over-estimated entry is
|
|
38
|
+
* never mounted, so it is never measured, so the estimate never self-corrects --
|
|
39
|
+
* which makes that particular disagreement permanent and silent.
|
|
40
|
+
*/
|
|
41
|
+
export function userEntryRenderedRows(lineCount) {
|
|
42
|
+
return lineCount > USER_ENTRY_MAX_ROWS ? USER_ENTRY_MAX_ROWS + 1 : lineCount;
|
|
14
43
|
}
|
|
15
44
|
/**
|
|
16
45
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
@@ -58,6 +58,31 @@ export interface FrameDifferOptions {
|
|
|
58
58
|
rows: () => number | undefined;
|
|
59
59
|
/** Raised once, on the 0→1 edge of `fallbacks` (§5.6). */
|
|
60
60
|
onFirstFallback?: () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Raw frame lines in, lines to PAINT out. Identity when absent, and absent is
|
|
63
|
+
* the default — a session without drag-select never allocates a second array.
|
|
64
|
+
*
|
|
65
|
+
* Called AFTER the geometry stand-down and BEFORE the cache comparison, so a
|
|
66
|
+
* frame this module refuses to address never reaches the selection layer, and
|
|
67
|
+
* the diff below still compares what is actually on the screen (I-2).
|
|
68
|
+
*
|
|
69
|
+
* It MUST return the same number of lines it was given; a decorator that
|
|
70
|
+
* changes the count would desynchronise absolute addressing, so the result is
|
|
71
|
+
* discarded if it does.
|
|
72
|
+
*/
|
|
73
|
+
decorate?: (lines: string[]) => string[];
|
|
74
|
+
/**
|
|
75
|
+
* Raised whenever the cache is dropped — a foreign write, a resize, a geometry
|
|
76
|
+
* stand-down, an explicit `invalidate()`.
|
|
77
|
+
*
|
|
78
|
+
* P1-7: on every one of those paths the RAW Ink frame reaches the screen
|
|
79
|
+
* verbatim, so any highlight is wiped off the terminal while the selection
|
|
80
|
+
* controller still believes it is painted and its mirror still holds the rows
|
|
81
|
+
* of the last DECORATED frame. A release would then copy text that is not on
|
|
82
|
+
* screen. I-4's "by construction" argument has exactly this hole, and one
|
|
83
|
+
* callback closes it: the controller clears the selection.
|
|
84
|
+
*/
|
|
85
|
+
onInvalidate?: () => void;
|
|
61
86
|
}
|
|
62
87
|
export interface FrameWriterStats {
|
|
63
88
|
/** Frames the differ EMITTED — `framesDiffed + framesFull`, never pass-throughs. */
|
|
@@ -78,6 +103,15 @@ export interface FrameWriterStats {
|
|
|
78
103
|
* to stdout behind Ink's back" and is what raises the §5.6 notice.
|
|
79
104
|
*/
|
|
80
105
|
fallbacks: number;
|
|
106
|
+
/**
|
|
107
|
+
* Self-initiated repaints — `repaint()` calls that emitted at least one row.
|
|
108
|
+
*
|
|
109
|
+
* A COUNTER OF ITS OWN, deliberately kept out of `framesTotal` /
|
|
110
|
+
* `framesDiffed` (P2-6). Those count frames INK PRODUCED, and folding a drag's
|
|
111
|
+
* highlight repaints into them would make `/perf`'s frame rate a function of
|
|
112
|
+
* how much the user moved the mouse.
|
|
113
|
+
*/
|
|
114
|
+
repaints: number;
|
|
81
115
|
}
|
|
82
116
|
export interface FrameDiffer {
|
|
83
117
|
/**
|
|
@@ -91,5 +125,17 @@ export interface FrameDiffer {
|
|
|
91
125
|
invalidate(): void;
|
|
92
126
|
/** Single owner of the counters (P2-2); the writer re-exports this. */
|
|
93
127
|
stats(): FrameWriterStats;
|
|
128
|
+
/**
|
|
129
|
+
* Re-run `decorate` over the LAST RAW FRAME and emit only the rows whose paint
|
|
130
|
+
* changed; `''` when there is nothing safe to do.
|
|
131
|
+
*
|
|
132
|
+
* This is what makes a drag cost 1-3 addressed rows instead of a React commit
|
|
133
|
+
* and a whole-tree Yoga layout (D-6). `''` is a REAL state rather than an error
|
|
134
|
+
* — after an `invalidate()` there is no cache to diff against and no origin to
|
|
135
|
+
* address from — and the caller's fallback is `App`'s existing `redrawNonce`:
|
|
136
|
+
* ask for a React redraw, the next commit runs `decorate`, and the highlight
|
|
137
|
+
* appears one frame later instead of never.
|
|
138
|
+
*/
|
|
139
|
+
repaint(): string;
|
|
94
140
|
}
|
|
95
141
|
export declare function createFrameDiffer(options: FrameDifferOptions): FrameDiffer;
|
package/dist/ui/frame-differ.js
CHANGED
|
@@ -92,8 +92,23 @@ function countErases(prefix) {
|
|
|
92
92
|
return prefix.split(ERASE_LINE).length - 1;
|
|
93
93
|
}
|
|
94
94
|
export function createFrameDiffer(options) {
|
|
95
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Last frame AS PAINTED — what is actually on the screen.
|
|
97
|
+
*
|
|
98
|
+
* `null` = no cache, next frame is a full repaint. THE DIFF COMPARES PAINTED
|
|
99
|
+
* LINES (I-2): diffing raw against a painted screen would leave the highlight
|
|
100
|
+
* burned into rows the next frame never repaints, because those rows' RAW text
|
|
101
|
+
* did not change.
|
|
102
|
+
*/
|
|
96
103
|
let prev = null;
|
|
104
|
+
/**
|
|
105
|
+
* The same frame UNDECORATED — `repaint()`'s input.
|
|
106
|
+
*
|
|
107
|
+
* Two caches rather than one, because a repaint has to re-run `decorate` over
|
|
108
|
+
* the ORIGINAL rows: painting an already-painted row would nest the selection
|
|
109
|
+
* escapes and the highlight would never come off.
|
|
110
|
+
*/
|
|
111
|
+
let rawPrev = null;
|
|
97
112
|
/**
|
|
98
113
|
* Whether any chunk has been seen at all.
|
|
99
114
|
*
|
|
@@ -111,9 +126,37 @@ export function createFrameDiffer(options) {
|
|
|
111
126
|
linesWritten: 0,
|
|
112
127
|
bytesWritten: 0,
|
|
113
128
|
fallbacks: 0,
|
|
129
|
+
repaints: 0,
|
|
114
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* Re-entrancy guard for `onInvalidate`. The listener clears the selection,
|
|
133
|
+
* which asks for a repaint, which can reach `invalidate()` again; one flag is
|
|
134
|
+
* cheaper than reasoning about how deep that can go.
|
|
135
|
+
*/
|
|
136
|
+
let notifying = false;
|
|
115
137
|
const invalidate = () => {
|
|
116
138
|
prev = null;
|
|
139
|
+
rawPrev = null;
|
|
140
|
+
if (notifying || !options.onInvalidate)
|
|
141
|
+
return;
|
|
142
|
+
notifying = true;
|
|
143
|
+
try {
|
|
144
|
+
options.onInvalidate();
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
notifying = false;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* `decorate`, with the line-count contract enforced. A decorator that returned
|
|
152
|
+
* a different number of rows would shift every absolute address below it for
|
|
153
|
+
* the rest of the session, so its answer is discarded rather than trusted.
|
|
154
|
+
*/
|
|
155
|
+
const applyDecorate = (lines) => {
|
|
156
|
+
if (!options.decorate)
|
|
157
|
+
return lines;
|
|
158
|
+
const painted = options.decorate(lines);
|
|
159
|
+
return painted.length === lines.length ? painted : lines;
|
|
117
160
|
};
|
|
118
161
|
/**
|
|
119
162
|
* Write `chunk` verbatim — today's behaviour, and the only failure mode.
|
|
@@ -132,8 +175,9 @@ export function createFrameDiffer(options) {
|
|
|
132
175
|
return null;
|
|
133
176
|
};
|
|
134
177
|
const wrapSync = (payload) => options.sync ? SYNC_BEGIN + payload + SYNC_END : payload;
|
|
135
|
-
const emit = (payload, lines, addressed, full) => {
|
|
178
|
+
const emit = (payload, lines, raw, addressed, full) => {
|
|
136
179
|
prev = lines;
|
|
180
|
+
rawPrev = raw;
|
|
137
181
|
counters.framesTotal += 1;
|
|
138
182
|
if (full)
|
|
139
183
|
counters.framesFull += 1;
|
|
@@ -175,16 +219,16 @@ export function createFrameDiffer(options) {
|
|
|
175
219
|
* is no unchanged content that has to survive, and full repaints are rare by
|
|
176
220
|
* construction — one per session, one per resize, one per invalidation.
|
|
177
221
|
*/
|
|
178
|
-
const fullRepaint = (lines) => {
|
|
222
|
+
const fullRepaint = (lines, raw) => {
|
|
179
223
|
const payload = cursorTo(1) +
|
|
180
224
|
SGR_RESET +
|
|
181
225
|
ERASE_DOWN +
|
|
182
226
|
lines.join('\r\n') +
|
|
183
227
|
'\r\n' +
|
|
184
228
|
cursorTo(lines.length + 1);
|
|
185
|
-
return emit(wrapSync(payload), lines, lines.length, true);
|
|
229
|
+
return emit(wrapSync(payload), lines, raw, lines.length, true);
|
|
186
230
|
};
|
|
187
|
-
const diffRepaint = (lines, previous) => {
|
|
231
|
+
const diffRepaint = (lines, raw, previous) => {
|
|
188
232
|
const out = [];
|
|
189
233
|
for (let i = 0; i < lines.length; i += 1) {
|
|
190
234
|
// I-4: paint, THEN clear the tail. Never `ERASE_LINE` first.
|
|
@@ -196,10 +240,45 @@ export function createFrameDiffer(options) {
|
|
|
196
240
|
// on an identical frame, so a chunk that reaches here differs by at least one
|
|
197
241
|
// line; the only way to observe zero is a cache that disagrees with the
|
|
198
242
|
// screen, which is the state `invalidate()` exists to prevent.
|
|
243
|
+
//
|
|
244
|
+
// With a `decorate` hook it stops being unreachable in one direction and
|
|
245
|
+
// stays correct: a frame whose RAW rows changed only where the highlight
|
|
246
|
+
// already covers them paints identically, and writing nothing is right.
|
|
199
247
|
if (out.length === 0)
|
|
200
|
-
return emit('', lines, 0, false);
|
|
248
|
+
return emit('', lines, raw, 0, false);
|
|
201
249
|
out.push(cursorTo(lines.length + 1)); // I-5
|
|
202
|
-
return emit(wrapSync(out.join('')), lines, out.length - 1, false);
|
|
250
|
+
return emit(wrapSync(out.join('')), lines, raw, out.length - 1, false);
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Re-paint the last frame's rows through `decorate` again, emitting only what
|
|
254
|
+
* changed. Called when the SELECTION moved rather than when Ink produced a
|
|
255
|
+
* frame — hence its own counter and no touch of the frame ones (P2-6).
|
|
256
|
+
*
|
|
257
|
+
* It carries the same DEC 2026 sync envelope and parks on row `H + 1` as every
|
|
258
|
+
* other batch, because it is addressed exactly like a diff frame and a
|
|
259
|
+
* half-applied highlight tears the same way a half-applied frame does.
|
|
260
|
+
*/
|
|
261
|
+
const repaint = () => {
|
|
262
|
+
if (prev === null || rawPrev === null)
|
|
263
|
+
return '';
|
|
264
|
+
const rows = usableRows();
|
|
265
|
+
if (rows === undefined)
|
|
266
|
+
return ''; // I-9 — geometry we cannot address safely
|
|
267
|
+
if (rawPrev.length + 1 > rows)
|
|
268
|
+
return '';
|
|
269
|
+
const painted = applyDecorate(rawPrev);
|
|
270
|
+
const out = [];
|
|
271
|
+
for (let i = 0; i < painted.length; i += 1) {
|
|
272
|
+
if (painted[i] !== prev[i]) {
|
|
273
|
+
out.push(cursorTo(i + 1) + SGR_RESET + painted[i] + SGR_RESET + ERASE_TO_EOL);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
prev = painted;
|
|
277
|
+
if (out.length === 0)
|
|
278
|
+
return '';
|
|
279
|
+
out.push(cursorTo(painted.length + 1)); // I-5
|
|
280
|
+
counters.repaints += 1;
|
|
281
|
+
return wrapSync(out.join(''));
|
|
203
282
|
};
|
|
204
283
|
const transform = (chunk) => {
|
|
205
284
|
const firstChunk = !seenAnyChunk;
|
|
@@ -217,14 +296,19 @@ export function createFrameDiffer(options) {
|
|
|
217
296
|
const lines = body.slice(0, -1).split('\n');
|
|
218
297
|
if (lines.length + 1 > rows)
|
|
219
298
|
return passThrough(false); // I-9
|
|
220
|
-
|
|
221
|
-
|
|
299
|
+
// AFTER the geometry stand-down, BEFORE the cache comparison: a frame this
|
|
300
|
+
// module refuses to address never reaches the selection layer, and what is
|
|
301
|
+
// compared below is what the screen will hold.
|
|
302
|
+
const painted = applyDecorate(lines);
|
|
303
|
+
if (prev === null || prev.length !== painted.length || matched.erased !== prev.length + 1) {
|
|
304
|
+
return fullRepaint(painted, lines);
|
|
222
305
|
}
|
|
223
|
-
return diffRepaint(lines, prev);
|
|
306
|
+
return diffRepaint(painted, lines, prev);
|
|
224
307
|
};
|
|
225
308
|
return {
|
|
226
309
|
transform,
|
|
227
310
|
invalidate,
|
|
311
|
+
repaint,
|
|
228
312
|
stats: () => ({ ...counters }),
|
|
229
313
|
};
|
|
230
314
|
}
|
package/dist/ui/gauge.d.ts
CHANGED
|
@@ -17,8 +17,23 @@ export interface Gauge {
|
|
|
17
17
|
/** Clamped percentage 0–100. */
|
|
18
18
|
pct: number;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Where the bar changes colour, as percentages.
|
|
22
|
+
*
|
|
23
|
+
* The defaults are the values this function hardcoded before context compaction
|
|
24
|
+
* existed, so every existing call site and test is byte-identical (§6.2).
|
|
25
|
+
*/
|
|
26
|
+
export interface GaugeMarks {
|
|
27
|
+
/** Amber at or above this. */
|
|
28
|
+
warn: number;
|
|
29
|
+
/** Red strictly above this. */
|
|
30
|
+
high: number;
|
|
31
|
+
}
|
|
20
32
|
/**
|
|
21
33
|
* Build a `width`-cell gauge for `pct` (0–100). Uses block glyphs on Unicode
|
|
22
34
|
* terminals and `#`/`-` otherwise (e.g. `[######----]`).
|
|
35
|
+
*
|
|
36
|
+
* `marks` is OPTIONAL and defaults to today's values, so omitting it produces
|
|
37
|
+
* byte-identical output to the pre-compaction build.
|
|
23
38
|
*/
|
|
24
|
-
export declare function buildGauge(pct: number, width: number, theme: Theme, caps: TermCapabilities): Gauge;
|
|
39
|
+
export declare function buildGauge(pct: number, width: number, theme: Theme, caps: TermCapabilities, marks?: GaugeMarks): Gauge;
|
package/dist/ui/gauge.js
CHANGED
|
@@ -4,19 +4,38 @@
|
|
|
4
4
|
* has no Unicode. Kept out of the `.tsx` so it is unit-testable without Ink.
|
|
5
5
|
*/
|
|
6
6
|
import { pickGlyphs } from './glyphs.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const DEFAULT_MARKS = { warn: 60, high: 85 };
|
|
8
|
+
/**
|
|
9
|
+
* Pick the fill colour by threshold: `< warn` low · `>= warn` mid · `> high` high.
|
|
10
|
+
*
|
|
11
|
+
* THE TWO OPERATORS ARE PRESERVED EXACTLY (`> high`, `>= warn`), and that is not
|
|
12
|
+
* cosmetic: changing either would move the default 85 or 60 boundary and break
|
|
13
|
+
* the byte-identity claim that makes the `marks` parameter a free change.
|
|
14
|
+
*
|
|
15
|
+
* WHAT RED MEANS, PRECISELY (P2-4). The compaction trigger is
|
|
16
|
+
* `ratio >= threshold` (INCLUSIVE) while this fill is `pct > high` (EXCLUSIVE),
|
|
17
|
+
* so with `high = threshold` any occupancy that would render red has ALREADY
|
|
18
|
+
* triggered compaction, and the gauge repaints downward in the same frame. The
|
|
19
|
+
* red band is therefore only ever OBSERVABLE when compaction did not or could
|
|
20
|
+
* not run — the feature is off, it self-disabled, or the failure ladder ended at
|
|
21
|
+
* its last rung. That is a genuinely more useful meaning than "imminent", and it
|
|
22
|
+
* is the one the README and the settings screen state.
|
|
23
|
+
*/
|
|
24
|
+
function thresholdColor(pct, theme, marks) {
|
|
25
|
+
if (pct > marks.high)
|
|
10
26
|
return theme.gauge.high;
|
|
11
|
-
if (pct >=
|
|
27
|
+
if (pct >= marks.warn)
|
|
12
28
|
return theme.gauge.mid;
|
|
13
29
|
return theme.gauge.low;
|
|
14
30
|
}
|
|
15
31
|
/**
|
|
16
32
|
* Build a `width`-cell gauge for `pct` (0–100). Uses block glyphs on Unicode
|
|
17
33
|
* terminals and `#`/`-` otherwise (e.g. `[######----]`).
|
|
34
|
+
*
|
|
35
|
+
* `marks` is OPTIONAL and defaults to today's values, so omitting it produces
|
|
36
|
+
* byte-identical output to the pre-compaction build.
|
|
18
37
|
*/
|
|
19
|
-
export function buildGauge(pct, width, theme, caps) {
|
|
38
|
+
export function buildGauge(pct, width, theme, caps, marks = DEFAULT_MARKS) {
|
|
20
39
|
const clamped = Math.max(0, Math.min(100, Math.round(pct)));
|
|
21
40
|
const cells = Math.max(1, width);
|
|
22
41
|
const filledCells = Math.max(0, Math.min(cells, Math.round((clamped / 100) * cells)));
|
|
@@ -29,7 +48,7 @@ export function buildGauge(pct, width, theme, caps) {
|
|
|
29
48
|
return {
|
|
30
49
|
filled: full.repeat(filledCells),
|
|
31
50
|
empty: empty.repeat(cells - filledCells),
|
|
32
|
-
fillColor: thresholdColor(clamped, theme),
|
|
51
|
+
fillColor: thresholdColor(clamped, theme, marks),
|
|
33
52
|
trackColor: theme.gauge.track,
|
|
34
53
|
pct: clamped,
|
|
35
54
|
};
|
package/dist/ui/glyphs.d.ts
CHANGED
|
@@ -117,6 +117,17 @@ export interface Glyphs {
|
|
|
117
117
|
* scan fails the build if either spelling appears anywhere else.
|
|
118
118
|
*/
|
|
119
119
|
retry: string;
|
|
120
|
+
/**
|
|
121
|
+
* Context compaction (context-auto-compaction §6.3). The rail marker on the
|
|
122
|
+
* compaction card.
|
|
123
|
+
*
|
|
124
|
+
* HERE RATHER THAN IN `CompactionCard`, for the reason this file's header
|
|
125
|
+
* gives: a literal in a component bypasses `pickGlyphs` entirely and shows
|
|
126
|
+
* mojibake on a legacy console whatever the capability probe said.
|
|
127
|
+
* `glyphs.test.ts`'s static scan fails the build if either spelling appears
|
|
128
|
+
* anywhere else — and `src/compaction/**` is inside that scan's scope too.
|
|
129
|
+
*/
|
|
130
|
+
compaction: string;
|
|
120
131
|
/**
|
|
121
132
|
* Ink `borderStyle` name for every boxed region (composer, completion popup,
|
|
122
133
|
* overlay frame).
|
package/dist/ui/glyphs.js
CHANGED
|
@@ -65,6 +65,7 @@ const UNICODE_GLYPHS = {
|
|
|
65
65
|
todoActive: '▸',
|
|
66
66
|
todoDone: '✔',
|
|
67
67
|
retry: '↻',
|
|
68
|
+
compaction: '⤓',
|
|
68
69
|
tool: {
|
|
69
70
|
read_file: '▤',
|
|
70
71
|
write_file: '✎',
|
|
@@ -127,6 +128,7 @@ const ASCII_GLYPHS = {
|
|
|
127
128
|
todoActive: '>',
|
|
128
129
|
todoDone: '[x]',
|
|
129
130
|
retry: '[r]',
|
|
131
|
+
compaction: '[c]',
|
|
130
132
|
tool: {
|
|
131
133
|
read_file: '=',
|
|
132
134
|
write_file: '+',
|