@vincemakes/kiso-tui-cells 0.1.41
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/dist/approval-panel.d.ts +109 -0
- package/dist/approval-panel.js +144 -0
- package/dist/components.d.ts +298 -0
- package/dist/components.js +979 -0
- package/dist/diff.d.ts +32 -0
- package/dist/diff.js +122 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +21 -0
- package/dist/render.d.ts +129 -0
- package/dist/render.js +295 -0
- package/dist/width.d.ts +21 -0
- package/dist/width.js +67 -0
- package/package.json +57 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W21 (the v8 approval round) — the approval panel: the bounded block
|
|
3
|
+
* that replaces the running tool's live window while a human-chain
|
|
4
|
+
* approval is pending (the v8 design §3.2). The panel is a VIEW only —
|
|
5
|
+
* the verdict mapping (bare No aborts, No+words continues, esc
|
|
6
|
+
* cancels, the allow-amend words ride the next turn) lives in the CLI,
|
|
7
|
+
* never here (the R3 chain ruling).
|
|
8
|
+
*
|
|
9
|
+
* The block's rows:
|
|
10
|
+
* - the rule line — the why-asked line, ONE row (never a fold):
|
|
11
|
+
* `<tool> needs approval — asked by <speaker> · <fix hint>`;
|
|
12
|
+
* - the title — the toolTarget rendering, ONE row;
|
|
13
|
+
* - the divider — "─ the full args — never truncated ─";
|
|
14
|
+
* - the ALWAYS-verbose args (shell = the full command; edit/write =
|
|
15
|
+
* the untruncated ± diff; other = the full JSON — nothing the human
|
|
16
|
+
* is asked to approve is ever cut), FOLDED at W−2 and capped at
|
|
17
|
+
* maxRows−6 with the "└ +N more rows" notice;
|
|
18
|
+
* - the numbered options — "1 Yes / 2 Yes, don't ask again for <tool>
|
|
19
|
+
* / 3 No" (the approval flavor) or "1 Yes / 3 No" (the simple
|
|
20
|
+
* flavor — the trust gate, the uncertain resolutions);
|
|
21
|
+
* - the affordance — the phase's key hint, ONE row;
|
|
22
|
+
* - the └ corner.
|
|
23
|
+
* The single-row lines CUT (never fold — the block's height is its row
|
|
24
|
+
* count, the W20 discipline — the #checked throw demands it); the args
|
|
25
|
+
* FOLD (a bounded block's body — content folds, metadata cuts).
|
|
26
|
+
*/
|
|
27
|
+
export type PanelFlavor = "approval" | "simple";
|
|
28
|
+
export type PanelPhase = "options" | "rule" | "amend";
|
|
29
|
+
export type PanelSel = 0 | 1 | 2 | 3;
|
|
30
|
+
/** The ALWAYS-verbose args (the panel's body): the untruncated diff
|
|
31
|
+
* (edit/write), or the full text (shell = the command line, other =
|
|
32
|
+
* the pretty-printed JSON). The CLI composes them UNTRUNCATED — the
|
|
33
|
+
* panel renders the expanded diff path (diffBody(diff, W, true)). */
|
|
34
|
+
export type PanelArgs = {
|
|
35
|
+
readonly kind: "diff";
|
|
36
|
+
readonly diff: import("./diff.js").DiffLine[] | null;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: "text";
|
|
39
|
+
readonly lines: readonly string[];
|
|
40
|
+
};
|
|
41
|
+
export interface PanelView {
|
|
42
|
+
/** The flavor — "approval" carries the option-2 rule ("Yes, don't
|
|
43
|
+
* ask again"), "simple" (the trust gate, the uncertain resolutions)
|
|
44
|
+
* carries only 1 Yes / 3 No. */
|
|
45
|
+
readonly flavor: PanelFlavor;
|
|
46
|
+
/** The tool name — the rule line's first word and the option-2
|
|
47
|
+
* rule prefill (the approval flavor). */
|
|
48
|
+
readonly name: string;
|
|
49
|
+
/** The title — the toolTarget rendering ("edit examples/foo.ts"). */
|
|
50
|
+
readonly title: string;
|
|
51
|
+
/** The rule line's "asked by" — the first non-abstain extension
|
|
52
|
+
* (the ask verdict's speaker). */
|
|
53
|
+
readonly speaker: string;
|
|
54
|
+
/** The fix hint per speaker (the v8 design §3.5 table). */
|
|
55
|
+
readonly hint?: string;
|
|
56
|
+
/** The options-phase status-left text — the CLI knows the context
|
|
57
|
+
* ("▸ run paused", the trust gate's line). */
|
|
58
|
+
readonly statusText: string;
|
|
59
|
+
/** The ALWAYS-verbose args — the full command/content/diff. */
|
|
60
|
+
readonly args: PanelArgs;
|
|
61
|
+
/** The simple flavor's full rule line (the trust/uncertain
|
|
62
|
+
* questions) — overrides the why-asked composition. */
|
|
63
|
+
readonly ruleOverride?: string;
|
|
64
|
+
/** The fallback question — the y/n text for the dock-less path
|
|
65
|
+
* (a TTY without a dock, or a pipe). */
|
|
66
|
+
readonly fallbackQuestion: string;
|
|
67
|
+
}
|
|
68
|
+
export type PanelVerdict = {
|
|
69
|
+
readonly action: "allow";
|
|
70
|
+
readonly reason: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly action: "allow-rule";
|
|
73
|
+
readonly rule: string;
|
|
74
|
+
} | {
|
|
75
|
+
readonly action: "deny";
|
|
76
|
+
readonly reason: string;
|
|
77
|
+
} | {
|
|
78
|
+
readonly action: "cancel";
|
|
79
|
+
};
|
|
80
|
+
/** The bound panel state the compositor reads — the editor owns the
|
|
81
|
+
* phase/selection state machine and the key routing; the compositor
|
|
82
|
+
* renders it (the block rows, the input lead, the status/hint). */
|
|
83
|
+
export interface PanelState {
|
|
84
|
+
readonly view: PanelView;
|
|
85
|
+
readonly phase: PanelPhase;
|
|
86
|
+
readonly sel: PanelSel;
|
|
87
|
+
}
|
|
88
|
+
/** The block's rows — EXACTLY the preview's frame shape, the gutter at
|
|
89
|
+
* the left edge (the preview's two-space mock indent is its own
|
|
90
|
+
* styling; the real rows sit at column 1, like every tool cell).
|
|
91
|
+
* maxRows caps the TOTAL (the args fold; the single-row lines cut). */
|
|
92
|
+
export declare function panelBlockRows(view: PanelView, phase: PanelPhase, sel: PanelSel, W: number, maxRows: number): string[];
|
|
93
|
+
/** The input row's lead for the panel's phase (the preview's chrome
|
|
94
|
+
* rows): the digit leads bold, the rule/feedback leads dim. The rule
|
|
95
|
+
* lead names the tool (the option-2 prefill), the amend lead the
|
|
96
|
+
* denial/allowance feedback ("the words ride the verdict"). */
|
|
97
|
+
export declare function panelLead(view: PanelView, phase: PanelPhase, sel: PanelSel): string;
|
|
98
|
+
/** The lead's plain text — the editor's reflow width (the line must
|
|
99
|
+
* fit the lead + the box's walls). */
|
|
100
|
+
export declare function panelLeadPlain(view: PanelView, phase: PanelPhase, sel: PanelSel): string;
|
|
101
|
+
export declare function panelLeadWidth(view: PanelView, phase: PanelPhase, sel: PanelSel): number;
|
|
102
|
+
/** The status row's left text while the panel is up — the phase, not
|
|
103
|
+
* the CLI's painting status (the compositor derives it from the panel
|
|
104
|
+
* state; the "▸ run paused" etc. ride the options phase). */
|
|
105
|
+
export declare function panelStatus(view: PanelView, phase: PanelPhase, sel: PanelSel): string;
|
|
106
|
+
/** The status row's right-aligned hint while the panel is up — the
|
|
107
|
+
* phase's keys. The approval flavor gains the tab-amend path; the
|
|
108
|
+
* simple flavor (the trust/uncertain gates) never does. */
|
|
109
|
+
export declare function panelAffordance(view: PanelView, phase: PanelPhase, sel: PanelSel): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W21 (the v8 approval round) — the approval panel: the bounded block
|
|
3
|
+
* that replaces the running tool's live window while a human-chain
|
|
4
|
+
* approval is pending (the v8 design §3.2). The panel is a VIEW only —
|
|
5
|
+
* the verdict mapping (bare No aborts, No+words continues, esc
|
|
6
|
+
* cancels, the allow-amend words ride the next turn) lives in the CLI,
|
|
7
|
+
* never here (the R3 chain ruling).
|
|
8
|
+
*
|
|
9
|
+
* The block's rows:
|
|
10
|
+
* - the rule line — the why-asked line, ONE row (never a fold):
|
|
11
|
+
* `<tool> needs approval — asked by <speaker> · <fix hint>`;
|
|
12
|
+
* - the title — the toolTarget rendering, ONE row;
|
|
13
|
+
* - the divider — "─ the full args — never truncated ─";
|
|
14
|
+
* - the ALWAYS-verbose args (shell = the full command; edit/write =
|
|
15
|
+
* the untruncated ± diff; other = the full JSON — nothing the human
|
|
16
|
+
* is asked to approve is ever cut), FOLDED at W−2 and capped at
|
|
17
|
+
* maxRows−6 with the "└ +N more rows" notice;
|
|
18
|
+
* - the numbered options — "1 Yes / 2 Yes, don't ask again for <tool>
|
|
19
|
+
* / 3 No" (the approval flavor) or "1 Yes / 3 No" (the simple
|
|
20
|
+
* flavor — the trust gate, the uncertain resolutions);
|
|
21
|
+
* - the affordance — the phase's key hint, ONE row;
|
|
22
|
+
* - the └ corner.
|
|
23
|
+
* The single-row lines CUT (never fold — the block's height is its row
|
|
24
|
+
* count, the W20 discipline — the #checked throw demands it); the args
|
|
25
|
+
* FOLD (a bounded block's body — content folds, metadata cuts).
|
|
26
|
+
*/
|
|
27
|
+
import { displayWidth } from "./width.js";
|
|
28
|
+
import { cutLine, diffBody, gutterFold, visibleWidth, widthCut } from "./components.js";
|
|
29
|
+
import { escapeTerminal, palette } from "./render.js";
|
|
30
|
+
/** The rule line's text — the why-asked line (the R3 chain): the tool
|
|
31
|
+
* name, the first non-abstain speaker, the fix hint (the §3.5 table,
|
|
32
|
+
* code-accented). The simple flavor carries the CLI's own question
|
|
33
|
+
* text instead (the trust gate, the uncertain resolutions) — their
|
|
34
|
+
* titles and args differ, the interaction is identical (§3.6). */
|
|
35
|
+
function panelRuleText(view) {
|
|
36
|
+
const p = palette();
|
|
37
|
+
if (view.ruleOverride !== undefined)
|
|
38
|
+
return escapeTerminal(view.ruleOverride);
|
|
39
|
+
const hint = view.hint;
|
|
40
|
+
const base = `${p.bold}${escapeTerminal(view.name)}${p.reset} ${p.dim}needs approval — asked by${p.reset} ${p.bold}${escapeTerminal(view.speaker)}${p.reset}`;
|
|
41
|
+
return hint ? `${base}${p.dim} ·${p.reset} ${p.code}${escapeTerminal(hint)}${p.reset}` : base;
|
|
42
|
+
}
|
|
43
|
+
/** The numbered options row — "1 Yes 2 Yes, don't ask again for
|
|
44
|
+
* <rule> 3 No" (approval) or "1 Yes 3 No" (simple). The row is a
|
|
45
|
+
* pure SPAN: the block prepends the gutter (the invariant ① test
|
|
46
|
+
* caught the double-gutter — the block and the row both emitted it).
|
|
47
|
+
* The option-2 rule name is the ONLY cuttable span: the row's fixed
|
|
48
|
+
* part (the 1/3 options + the separators + the option-2 prefix) is 45
|
|
49
|
+
* cells, so the name fits W−45 and cuts with a "…" at W−46 (the
|
|
50
|
+
* single-row discipline — the row never folds). */
|
|
51
|
+
function panelOptionsRow(view, sel, W) {
|
|
52
|
+
const p = palette();
|
|
53
|
+
const o1 = sel === 1 ? `${p.bold} 1 Yes${p.reset}` : ` 1 Yes`;
|
|
54
|
+
const o3 = sel === 3 ? `${p.bold} 3 No${p.reset}` : ` 3 No`;
|
|
55
|
+
if (view.flavor === "simple")
|
|
56
|
+
return `${o1} ${o3}`;
|
|
57
|
+
// the option-2 span: " 2 Yes, don't ask again for <name>" — the
|
|
58
|
+
// fixed part is 45 (the 1/3 options + the separators + the 28-cell
|
|
59
|
+
// prefix); the name cuts to W−46 + "…".
|
|
60
|
+
const name = escapeTerminal(view.name);
|
|
61
|
+
const budget = Math.max(1, W - 46);
|
|
62
|
+
const shown = visibleWidth(name) > Math.max(0, W - 45) ? `${widthCut(name, budget)}…` : name;
|
|
63
|
+
const o2 = sel === 2 ? `${p.bold} 2 Yes, don't ask again for ${shown}${p.reset}` : ` 2 Yes, don't ask again for ${shown}`;
|
|
64
|
+
return `${o1} ${o2} ${o3}`;
|
|
65
|
+
}
|
|
66
|
+
/** The block's rows — EXACTLY the preview's frame shape, the gutter at
|
|
67
|
+
* the left edge (the preview's two-space mock indent is its own
|
|
68
|
+
* styling; the real rows sit at column 1, like every tool cell).
|
|
69
|
+
* maxRows caps the TOTAL (the args fold; the single-row lines cut). */
|
|
70
|
+
export function panelBlockRows(view, phase, sel, W, maxRows) {
|
|
71
|
+
const p = palette();
|
|
72
|
+
const gutter = `${p.dim}│${p.reset} `;
|
|
73
|
+
const rows = [];
|
|
74
|
+
rows.push(`${gutter}${cutLine(panelRuleText(view), Math.max(1, W - 2))}`);
|
|
75
|
+
rows.push(`${gutter}${cutLine(`${p.bold}${escapeTerminal(view.title)}${p.reset}`, Math.max(1, W - 2))}`);
|
|
76
|
+
rows.push(`${cutLine(`${p.dim}─ the full args — never truncated ─${p.reset}`, Math.max(1, W - 2))}`);
|
|
77
|
+
// the args — the bounded block's body: fold, then cap. The └ cut is
|
|
78
|
+
// ONE row (the W20 discipline): when the args exceed the budget, one
|
|
79
|
+
// notice row carries the count and where the rest is (the event log).
|
|
80
|
+
const args = view.args.kind === "diff"
|
|
81
|
+
? diffBody(view.args.diff, W, true) // the expanded path — never the tool cell's capped copy
|
|
82
|
+
: view.args.lines.flatMap((line) => gutterFold(`${p.dim}│${p.reset} `, escapeTerminal(line), W));
|
|
83
|
+
const argsBudget = Math.max(1, maxRows - 6);
|
|
84
|
+
let shown;
|
|
85
|
+
if (args.length > argsBudget) {
|
|
86
|
+
const kept = Math.max(0, argsBudget - 1);
|
|
87
|
+
const n = args.length - kept;
|
|
88
|
+
shown = [...args.slice(0, kept), cutLine(`${p.dim}└ +${n} more rows — the full args are in the event log${p.reset}`, Math.max(1, W - 2))];
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
shown = args;
|
|
92
|
+
}
|
|
93
|
+
rows.push(...shown);
|
|
94
|
+
rows.push(`${gutter}${panelOptionsRow(view, sel, W)}`);
|
|
95
|
+
rows.push(`${gutter}${p.dim}${panelAffordance(view, phase, sel)}${p.reset}`);
|
|
96
|
+
rows.push(`${p.dim}└ ${p.reset}`);
|
|
97
|
+
return rows;
|
|
98
|
+
}
|
|
99
|
+
/** The input row's lead for the panel's phase (the preview's chrome
|
|
100
|
+
* rows): the digit leads bold, the rule/feedback leads dim. The rule
|
|
101
|
+
* lead names the tool (the option-2 prefill), the amend lead the
|
|
102
|
+
* denial/allowance feedback ("the words ride the verdict"). */
|
|
103
|
+
export function panelLead(view, phase, sel) {
|
|
104
|
+
const p = palette();
|
|
105
|
+
if (phase === "rule")
|
|
106
|
+
return `${p.dim}2 Yes, don't ask again for ${p.reset}`;
|
|
107
|
+
if (phase === "amend")
|
|
108
|
+
return `${p.dim}${sel === 3 ? "feedback (deny): " : "feedback (amend): "}${p.reset}`;
|
|
109
|
+
return `${p.bold}${view.flavor === "approval" ? "1-3> " : "1/3> "}${p.reset}`;
|
|
110
|
+
}
|
|
111
|
+
/** The lead's plain text — the editor's reflow width (the line must
|
|
112
|
+
* fit the lead + the box's walls). */
|
|
113
|
+
export function panelLeadPlain(view, phase, sel) {
|
|
114
|
+
if (phase === "rule")
|
|
115
|
+
return "2 Yes, don't ask again for ";
|
|
116
|
+
if (phase === "amend")
|
|
117
|
+
return sel === 3 ? "feedback (deny): " : "feedback (amend): ";
|
|
118
|
+
return view.flavor === "approval" ? "1-3> " : "1/3> ";
|
|
119
|
+
}
|
|
120
|
+
export function panelLeadWidth(view, phase, sel) {
|
|
121
|
+
return displayWidth(panelLeadPlain(view, phase, sel));
|
|
122
|
+
}
|
|
123
|
+
/** The status row's left text while the panel is up — the phase, not
|
|
124
|
+
* the CLI's painting status (the compositor derives it from the panel
|
|
125
|
+
* state; the "▸ run paused" etc. ride the options phase). */
|
|
126
|
+
export function panelStatus(view, phase, sel) {
|
|
127
|
+
if (phase === "rule")
|
|
128
|
+
return "▸ rule input";
|
|
129
|
+
if (phase === "amend")
|
|
130
|
+
return sel === 3 ? "▸ deny · the words become the tool_result" : "▸ amend · the words ride the verdict";
|
|
131
|
+
return view.statusText;
|
|
132
|
+
}
|
|
133
|
+
/** The status row's right-aligned hint while the panel is up — the
|
|
134
|
+
* phase's keys. The approval flavor gains the tab-amend path; the
|
|
135
|
+
* simple flavor (the trust/uncertain gates) never does. */
|
|
136
|
+
export function panelAffordance(view, phase, sel) {
|
|
137
|
+
if (phase === "rule")
|
|
138
|
+
return "enter commits · esc backs out";
|
|
139
|
+
if (phase === "amend")
|
|
140
|
+
return "enter sends";
|
|
141
|
+
if (view.flavor === "approval")
|
|
142
|
+
return sel === 0 ? "tab amend · esc cancel" : "enter sends · esc backs out";
|
|
143
|
+
return sel === 0 ? "esc cancel" : "enter sends";
|
|
144
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI v6 (ADR-0046) — the components: EVERY screen line's renderer.
|
|
3
|
+
* Extracted to tui-cells (ADR-0043 Amendment 4): the cell renderer
|
|
4
|
+
* leaves the tui for the 9th package; the tui's shims re-export it.
|
|
5
|
+
*
|
|
6
|
+
* Each component turns one piece of state into display lines (SGR
|
|
7
|
+
* included, raw — the compositor writes them verbatim). The folding
|
|
8
|
+
* lives HERE: every line a component returns must fit the terminal
|
|
9
|
+
* width — the compositor's crash-on-violation invariant backs it up
|
|
10
|
+
* (a component that forgets to fold CRASHES with a diagnostic, never
|
|
11
|
+
* silently truncates — the crash is the contract, not a symptom).
|
|
12
|
+
*
|
|
13
|
+
* The fold is SGR-AWARE: a line whose bold/dim span would straddle a
|
|
14
|
+
* fold boundary closes the span at the break and reopens it on the
|
|
15
|
+
* next row — the #16b contract (no literal "[2m" fragments) survives
|
|
16
|
+
* folding. displayWidth/charWidth (width.ts) are the width primitives
|
|
17
|
+
* (untouched); render.ts supplies the original text (palette, escape,
|
|
18
|
+
* tint, fold wording).
|
|
19
|
+
*/
|
|
20
|
+
import { foldThinking, foldResult, renderToolSummary, type ResumeMeta } from "./render.js";
|
|
21
|
+
/** The spinner glyphs, cycled by the compositor's on-demand tick. */
|
|
22
|
+
export declare const SPINNER: string[];
|
|
23
|
+
/** The frame context the compositor passes down — the pieces of time
|
|
24
|
+
* that make a live render non-deterministic (the running tool's glyph
|
|
25
|
+
* and elapsed). Everything else is a pure function of the cell. */
|
|
26
|
+
export interface FrameCtx {
|
|
27
|
+
readonly spinnerI: number;
|
|
28
|
+
readonly now: number;
|
|
29
|
+
/** The terminal height (rows) — the banner cell's tier input (W1:
|
|
30
|
+
* the tier table reads H, so a resize RE-TIERS instead of
|
|
31
|
+
* re-folding frozen rows). */
|
|
32
|
+
readonly height: number;
|
|
33
|
+
}
|
|
34
|
+
/** ONE screen line a component emits (raw, SGR included). */
|
|
35
|
+
export type RenderLine = string;
|
|
36
|
+
/**
|
|
37
|
+
* The fold — split a display-width line into ≤W rows, preserving SGR
|
|
38
|
+
* spans across the break: a span open at the break closes (reset) at
|
|
39
|
+
* the row's end and reopens on the next row. The rows are what the
|
|
40
|
+
* terminal's own soft-wrap would have produced — except the compositor
|
|
41
|
+
* folds FIRST, so the terminal never reflows a component's line (the
|
|
42
|
+
* #17 merge class cannot reach committed content).
|
|
43
|
+
*/
|
|
44
|
+
export declare function foldLine(line: string, W: number): string[];
|
|
45
|
+
/** The visible width of a rendered line (SGR stripped — the invariant
|
|
46
|
+
* the compositor enforces on every emitted line). */
|
|
47
|
+
export declare function visibleWidth(line: string): number;
|
|
48
|
+
/** A component: render the display lines for one piece of state. */
|
|
49
|
+
export interface Component {
|
|
50
|
+
render(width: number, ctx: FrameCtx): string[];
|
|
51
|
+
}
|
|
52
|
+
/** The W11 spacing formula — "a row gets one blank line above it when
|
|
53
|
+
* the row is itself a block, or when the previous sibling was taller
|
|
54
|
+
* than one row". One-row siblings pack tight; anything multi-row
|
|
55
|
+
* breathes on both sides. The FIRST cell never gets the blank (it sits
|
|
56
|
+
* at the body's top — the banner would otherwise start one row down).
|
|
57
|
+
* `prev` is the previous sibling's OWN rows (raw — a cell's own blank
|
|
58
|
+
* must never count toward its height). The blank is a JOIN artifact:
|
|
59
|
+
* the cell's own render stays blank-free, so per-cell accounting
|
|
60
|
+
* (heights, the fold cache) never sees a fake row. */
|
|
61
|
+
export declare function bodySpacing(prev: readonly string[] | null, rows: readonly string[]): string[];
|
|
62
|
+
/** The container — vertical concatenation with the W11 formula. No
|
|
63
|
+
* component decides its own spacing: every blank in the body is the
|
|
64
|
+
* container's. */
|
|
65
|
+
export declare class Container implements Component {
|
|
66
|
+
private readonly children;
|
|
67
|
+
constructor(children: Component[]);
|
|
68
|
+
render(width: number, ctx: FrameCtx): string[];
|
|
69
|
+
}
|
|
70
|
+
export type BodyCell = {
|
|
71
|
+
kind: "user";
|
|
72
|
+
text: string;
|
|
73
|
+
done: true;
|
|
74
|
+
turn: number;
|
|
75
|
+
} | {
|
|
76
|
+
kind: "thinking";
|
|
77
|
+
text: string;
|
|
78
|
+
done: boolean;
|
|
79
|
+
turn: number;
|
|
80
|
+
} | {
|
|
81
|
+
kind: "tool";
|
|
82
|
+
name: string;
|
|
83
|
+
input: string;
|
|
84
|
+
/** W15: the FULL input JSON (pretty-printed) — the display
|
|
85
|
+
* summary above is sliced at 60 chars; the expanded block's
|
|
86
|
+
* "--- input ---" section mirrors /last and needs it all. */
|
|
87
|
+
inputFull: string;
|
|
88
|
+
childRoles: string[];
|
|
89
|
+
state: "pending" | "approval" | "running" | "done";
|
|
90
|
+
isError: boolean;
|
|
91
|
+
resultText: string;
|
|
92
|
+
diff: import("./diff.js").DiffLine[] | null;
|
|
93
|
+
added: number;
|
|
94
|
+
removed: number;
|
|
95
|
+
startedAt: number | null;
|
|
96
|
+
doneAt: number | null;
|
|
97
|
+
done: boolean;
|
|
98
|
+
/** W15: the live-region expand toggle — while the cell is live
|
|
99
|
+
* the FULL body renders in place (the compositor owns those
|
|
100
|
+
* rows and redraws them); a committed cell can never toggle
|
|
101
|
+
* (history is never rewritten — ADR-0046). */
|
|
102
|
+
expanded: boolean;
|
|
103
|
+
/** W14: the turn boundary — the index of the turn record that
|
|
104
|
+
* created this cell (the fold-hold's owner; −1 when no turn
|
|
105
|
+
* exists yet — the pre-turn cells never hold). */
|
|
106
|
+
turn: number;
|
|
107
|
+
/** W13: the rolled-up group summary — set at COMMIT time when
|
|
108
|
+
* the head of an N > 2 same-tool run renders the group (the
|
|
109
|
+
* work order's claimed shape: "✓ read 5 files (2.4k lines,
|
|
110
|
+
* 1.1s)" + the target children). The members carry null — the
|
|
111
|
+
* compositor's rolled-heads bookkeeping renders them []. */
|
|
112
|
+
rolled: null | {
|
|
113
|
+
count: number;
|
|
114
|
+
lines: number;
|
|
115
|
+
elapsed: string;
|
|
116
|
+
targets: string[];
|
|
117
|
+
};
|
|
118
|
+
/** W19: a DENIED call's reason (the CLI extracted it from the
|
|
119
|
+
* result's "[Permission denied] " prefix, keyed on the "denied"
|
|
120
|
+
* tag). Non-null renders the pinned row — the full call name,
|
|
121
|
+
* the target, the reason in the W4 parentheses idiom, NO timing
|
|
122
|
+
* metadata (the call never ran). */
|
|
123
|
+
reason: string | null;
|
|
124
|
+
/** A5: the approval verdict — the permission_decided event bound
|
|
125
|
+
* into the cell (no free-standing ` approved` orphan row). The
|
|
126
|
+
* settled head row aggregates name + status + decidedBy in ONE
|
|
127
|
+
* row: a denied call's pinned row gains `· by <decidedBy>`; an
|
|
128
|
+
* extension-approved call's settled row gains `· approved by
|
|
129
|
+
* <decidedBy>` (the human approval needs no marker — the ⏸ →
|
|
130
|
+
* spinner → ✓ sequence told the story). Null until a decision
|
|
131
|
+
* lands (the auto-allowed calls never have one). */
|
|
132
|
+
verdict: {
|
|
133
|
+
decision: "approved" | "denied";
|
|
134
|
+
decidedBy?: string;
|
|
135
|
+
reason?: string;
|
|
136
|
+
} | null;
|
|
137
|
+
} | {
|
|
138
|
+
kind: "text";
|
|
139
|
+
text: string;
|
|
140
|
+
done: boolean;
|
|
141
|
+
} | {
|
|
142
|
+
kind: "notice";
|
|
143
|
+
text: string;
|
|
144
|
+
done: true;
|
|
145
|
+
} | {
|
|
146
|
+
kind: "banner";
|
|
147
|
+
version: string;
|
|
148
|
+
extensionsText: string;
|
|
149
|
+
resume: ResumeMeta[];
|
|
150
|
+
done: true;
|
|
151
|
+
} | {
|
|
152
|
+
kind: "raw";
|
|
153
|
+
lines: string[];
|
|
154
|
+
done: true;
|
|
155
|
+
} | {
|
|
156
|
+
kind: "terminal";
|
|
157
|
+
label: string;
|
|
158
|
+
line: string;
|
|
159
|
+
done: true;
|
|
160
|
+
} | {
|
|
161
|
+
kind: "checklist";
|
|
162
|
+
/** the model-authored header tail (parseChecklist's count line —
|
|
163
|
+
* chat.ts). The compositor's fixed "task" prefix rides BEFORE it
|
|
164
|
+
* (W20 naming ruling: never model-controlled). */
|
|
165
|
+
header: string;
|
|
166
|
+
items: {
|
|
167
|
+
text: string;
|
|
168
|
+
status: "pending" | "active" | "done";
|
|
169
|
+
}[];
|
|
170
|
+
/** W20: false while LIVE — the current turn's ONE in-place block
|
|
171
|
+
* (the commit loop only takes done cells, so it stays in the
|
|
172
|
+
* live region); true once SETTLED — endTurn committed it as the
|
|
173
|
+
* turn's one recap block. */
|
|
174
|
+
done: boolean;
|
|
175
|
+
/** W20: the LIVE block's ctrl+r toggle (W15) — the capped form
|
|
176
|
+
* flips to the full list in place. The settled render ignores
|
|
177
|
+
* it (already full). */
|
|
178
|
+
expanded: boolean;
|
|
179
|
+
/** W20: the wall clock of the block's FIRST call — the settled
|
|
180
|
+
* header's duration is clocked from here, compositor-side (the
|
|
181
|
+
* CLI stays unchanged). */
|
|
182
|
+
startedAt: number;
|
|
183
|
+
/** W20: the run's duration at the settle — the `2h 14m` form. */
|
|
184
|
+
durationSeconds: number;
|
|
185
|
+
turn: number;
|
|
186
|
+
};
|
|
187
|
+
declare const TOOL_SUMMARY_MAX = 60;
|
|
188
|
+
/** The component for one cell — the mapping table lives here so the
|
|
189
|
+
* compositor stays a pure writer. */
|
|
190
|
+
export declare function cellComponent(cell: BodyCell): Component;
|
|
191
|
+
/** W22: the pending-queue chips — queued user lines pre-render above
|
|
192
|
+
* the input row as the SAME UserMessage chip (undimmed: reverse video
|
|
193
|
+
* inverts the CURRENT colours), the dim `□` gutter marking the queued
|
|
194
|
+
* state (the gutter rides EVERY row — the gutterFold precedent: the
|
|
195
|
+
* left edge alone distinguishes the states). Each chip folds at W−3
|
|
196
|
+
* (the gutter's 2 cells), so a long line hard-folds INSIDE the chip
|
|
197
|
+
* and invariant ① holds on the band. */
|
|
198
|
+
export declare function pendingQueueRows(lines: readonly string[], W: number): string[];
|
|
199
|
+
/** Fold a line's CONTENT at W−2 and prefix EVERY row with the gutter
|
|
200
|
+
* (W2: a wrapped tool row keeps its state mark — the left edge alone
|
|
201
|
+
* distinguishes the states at --plain; the UserMessage rail precedent,
|
|
202
|
+
* v5 #16f). The gutter carries its own SGR (e.g. the bold ✓). W21:
|
|
203
|
+
* exported for the approval panel's text args (the same │ gutter). */
|
|
204
|
+
export declare function gutterFold(gutter: string, line: string, W: number): string[];
|
|
205
|
+
/** A6: the tool-header variant — ONE cut row, never a fold. A wide
|
|
206
|
+
* header (a long target path, a wordy denial reason) used to wrap
|
|
207
|
+
* through foldLine — every wrapped row repeated the gutter, the
|
|
208
|
+
* settled row grew past its previewed height. The header names the
|
|
209
|
+
* call — the ellipsis marks the cut, the body below still carries the
|
|
210
|
+
* full content. The budget: the gutter's own visible width + the
|
|
211
|
+
* ellipsis ride the row (the invariant ① cap holds). */
|
|
212
|
+
export declare function gutterCut(gutter: string, line: string, W: number): string[];
|
|
213
|
+
/** W13 — the rollup opt-in table: which tools collapse, and the count
|
|
214
|
+
* NOUN (read_file calls → "5 files", list_dir → "5 dirs", search_text
|
|
215
|
+
* → "5 matches"). Only these tools opt in — a shell burst is never
|
|
216
|
+
* rolled up (its rows carry meaning). The folded-turn line (W14) reuses
|
|
217
|
+
* the plurals for its other-tool terms ("2 dirs", "1 match"). */
|
|
218
|
+
export declare const ROLLUP_NOUN: Readonly<Record<string, string>>;
|
|
219
|
+
/** W14 — the folded-turn line: a whole QUIET turn (no text), once it is
|
|
220
|
+
* scrollback, becomes ONE line — the work order's claimed shape
|
|
221
|
+
* (`▞ thought 19s · 5 reads · no edits`), the counts accumulated at
|
|
222
|
+
* toolStart: read_file → "reads", edit_file → "edits", the other tools
|
|
223
|
+
* as first-call-order terms (the ROLLUP_NOUN plurals when the tool opts
|
|
224
|
+
* in, the verb + "s" otherwise).
|
|
225
|
+
* A9 (ruling R2, mock A): the user chip rides the fold — the human's
|
|
226
|
+
* words LEAD the one line, `▞ <chip> · thought 19s · 5 reads · no
|
|
227
|
+
* edits` — the chip the SAME SGR-7 bracket as the live user row (#16f,
|
|
228
|
+
* side pads included). The words take the fold's width budget: the
|
|
229
|
+
* metadata terms survive (the W14 metadata rule — they give way LAST),
|
|
230
|
+
* the words width-cut at the end with the honest "…" (never a silent
|
|
231
|
+
* truncate — invariant ① holds on the ONE row by construction). */
|
|
232
|
+
export declare function turnFold(t: {
|
|
233
|
+
words: string;
|
|
234
|
+
thoughtSeconds: number;
|
|
235
|
+
reads: number;
|
|
236
|
+
edits: number;
|
|
237
|
+
others: [string, number][];
|
|
238
|
+
}, W: number): string[];
|
|
239
|
+
/** The approval mini-diff (W7): capped at 12 folded rows — the head +
|
|
240
|
+
* the named middle (the renderer cut — what was cut, how to expand) +
|
|
241
|
+
* the tail. The rows are folded at the current width BEFORE the cap —
|
|
242
|
+
* the R1 measured bug: truncateDiff capped at 40 ENTRIES while the
|
|
243
|
+
* fold turned them into 73 SCREEN rows at W≤80 (a 44-row terminal's
|
|
244
|
+
* content cap is H−4 = 40 — the approval force-committed a third of
|
|
245
|
+
* the screen into scrollback inside one frame).
|
|
246
|
+
* W17: the cap is a ROW budget at every width — the └ cut is ONE line
|
|
247
|
+
* (a folded cut pushed the total past 12 at narrow widths), and below
|
|
248
|
+
* a floor of 3 SOURCE lines visible the head/tail pair is noise (each
|
|
249
|
+
* fragment a sliver of a long line): drop to the head only — the head
|
|
250
|
+
* takes the whole budget — and the └ row carries the rest.
|
|
251
|
+
* W21: exported for the approval panel — the expanded path renders
|
|
252
|
+
* the approval's ALWAYS-verbose args (never the capped copy). */
|
|
253
|
+
export declare function diffBody(diff: import("./diff.js").DiffLine[] | null, W: number, expanded?: boolean): string[];
|
|
254
|
+
/** W20 — the task block's fixed-window height: the whole live block
|
|
255
|
+
* (header + rows) in POST-FOLD screen rows at EVERY width: the header,
|
|
256
|
+
* the active row, up to 2 pending, the overflow-pending fold, the
|
|
257
|
+
* done-collapse. Every live row CUTS at W (never folds) — the block's
|
|
258
|
+
* height is its row count. */
|
|
259
|
+
export declare const CAP_TASK_LIVE = 6;
|
|
260
|
+
/** W20 — the live block's fixed-window row cut: an SGR-aware ONE-ROW
|
|
261
|
+
* truncation (foldLine wraps; a wrapped row would break the height
|
|
262
|
+
* cap — every live row is exactly one screen row at every width).
|
|
263
|
+
* A line that fits (≤ W) passes through whole; an overflow cuts the
|
|
264
|
+
* content at W−1 — the ellipsis's slot — and the ellipsis rides AFTER
|
|
265
|
+
* the reset (post-reset — the PTY needles' convention). The cut row
|
|
266
|
+
* never exceeds W (invariant ①). W21: exported for the approval
|
|
267
|
+
* panel's single-row lines (the rule line, the title, the divider,
|
|
268
|
+
* the options/affordance rows). */
|
|
269
|
+
export declare function cutLine(line: string, W: number): string;
|
|
270
|
+
/** W20 — the settled block's duration, the `2h 14m` form (the task
|
|
271
|
+
* narrative's long-horizon idiom): minutes+seconds under an hour,
|
|
272
|
+
* hours+minutes past it. */
|
|
273
|
+
export declare function formatDuration(totalSeconds: number): string;
|
|
274
|
+
/** The status container's row: the status text (+ the tail) with the
|
|
275
|
+
* right-aligned "/ commands · ↑ history" hint in the idle state —
|
|
276
|
+
* the hint CUT FIRST when the width is short (the #16g rule); when
|
|
277
|
+
* the STATUS ITSELF cannot fit, it cuts with a "…" — the last resort,
|
|
278
|
+
* enforced by invariant ① (the old code let the status soft-wrap).
|
|
279
|
+
* W21: the question param is gone — the old question slot retires; a
|
|
280
|
+
* pending approval's status IS the panel's (the compositor derives
|
|
281
|
+
* it from the bound panel state). */
|
|
282
|
+
export declare function statusLine(status: string, tail: string, W: number, hint?: string): string;
|
|
283
|
+
/** The display-width prefix of a plain (SGR-free) text. W21: exported
|
|
284
|
+
* for the approval panel's option-2 rule-name cut. */
|
|
285
|
+
export declare function widthCut(text: string, max: number): string;
|
|
286
|
+
/** W6 — the box: the chrome's top rail. The two ╌ dotted rows become
|
|
287
|
+
* a rounded box (the box already says "input lives here"); the rails
|
|
288
|
+
* stay dim, the width is still the full W (the box is a rail with
|
|
289
|
+
* corners — the menu/gap rows above and the status below are
|
|
290
|
+
* untouched). */
|
|
291
|
+
export declare function boxTop(W: number): string;
|
|
292
|
+
/** W6 — the box: the chrome's bottom rail. */
|
|
293
|
+
export declare function boxBottom(W: number): string;
|
|
294
|
+
/** The terminal label + rhythm gap (the pipe path's v2c bytes — the
|
|
295
|
+
* exact render the passthrough needs). */
|
|
296
|
+
export declare function terminalPipe(label: string, statusLineText: string): string;
|
|
297
|
+
/** The pipe-path pieces the passthrough reuses (byte-identical). */
|
|
298
|
+
export { foldThinking, foldResult, renderToolSummary, TOOL_SUMMARY_MAX };
|