@vincemakes/kiso-tui-cells 0.7.0 → 0.8.0

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.
@@ -27,6 +27,54 @@
27
27
  export type PanelFlavor = "approval" | "simple";
28
28
  export type PanelPhase = "options" | "rule" | "amend";
29
29
  export type PanelSel = 0 | 1 | 2 | 3;
30
+ /** One option of a question: the label the human picks, plus an
31
+ * optional one-line description (the model's own words). */
32
+ export interface AskOption {
33
+ readonly label: string;
34
+ readonly description?: string;
35
+ }
36
+ /** One question: 2-4 options, single- or multi-select, and an optional
37
+ * ≤12-cell header (the panel's title when present — the schema caps
38
+ * it so the title never fights the counter for the row). */
39
+ export interface AskQuestion {
40
+ readonly question: string;
41
+ readonly header?: string;
42
+ readonly options: readonly AskOption[];
43
+ readonly multiSelect?: boolean;
44
+ }
45
+ /** The whole ask_user call: 1-4 questions, walked in order. */
46
+ export interface AskSpec {
47
+ readonly questions: readonly AskQuestion[];
48
+ }
49
+ /** One answered question — the three shapes the tool_result carries:
50
+ * a single choice, a multi-select list, or the typed-in answer. */
51
+ export type AskAnswer = {
52
+ readonly q: string;
53
+ readonly choice: string;
54
+ } | {
55
+ readonly q: string;
56
+ readonly choices: readonly string[];
57
+ } | {
58
+ readonly q: string;
59
+ readonly custom: string;
60
+ };
61
+ /** The ask's outcome: every question answered, or the decline — an
62
+ * HONEST recorded outcome that names what was skipped, never silence. */
63
+ export type AskResult = {
64
+ readonly answers: readonly AskAnswer[];
65
+ } | {
66
+ readonly declined: readonly string[];
67
+ };
68
+ /** The ask panel's runtime state — the editor owns and advances it,
69
+ * the compositor reads it. `picks` and `custom` are per question, so
70
+ * a walk back (←) shows what was already chosen. */
71
+ export interface AskRuntime {
72
+ readonly qIndex: number;
73
+ readonly cursor: number;
74
+ readonly picks: readonly (readonly number[])[];
75
+ readonly custom: readonly (string | null)[];
76
+ readonly phase: "options" | "custom";
77
+ }
30
78
  /** The ALWAYS-verbose args (the panel's body): the untruncated diff
31
79
  * (edit/write), or the full text (shell = the command line, other =
32
80
  * the pretty-printed JSON). The CLI composes them UNTRUNCATED — the
@@ -64,6 +112,10 @@ export interface PanelView {
64
112
  /** The fallback question — the y/n text for the dock-less path
65
113
  * (a TTY without a dock, or a pipe). */
66
114
  readonly fallbackQuestion: string;
115
+ /** KC3.5: the questions, when this view is an ASK. Present = the
116
+ * panel renders the ask block and the editor routes the ask keys;
117
+ * absent = the approval/simple panel, unchanged. */
118
+ readonly ask?: AskSpec;
67
119
  }
68
120
  export type PanelVerdict = {
69
121
  readonly action: "allow";
@@ -76,6 +128,13 @@ export type PanelVerdict = {
76
128
  readonly reason: string;
77
129
  } | {
78
130
  readonly action: "cancel";
131
+ }
132
+ /** KC3.5: the ask's own verdict — the answers (or the decline) the
133
+ * cli hands back to the tool. Only ask views ever produce it, so
134
+ * the approval path's switch is untouched. */
135
+ | {
136
+ readonly action: "answers";
137
+ readonly result: AskResult;
79
138
  };
80
139
  /** The bound panel state the compositor reads — the editor owns the
81
140
  * phase/selection state machine and the key routing; the compositor
@@ -84,6 +143,8 @@ export interface PanelState {
84
143
  readonly view: PanelView;
85
144
  readonly phase: PanelPhase;
86
145
  readonly sel: PanelSel;
146
+ /** KC3.5: the ask's walk — present exactly when `view.ask` is. */
147
+ readonly ask?: AskRuntime;
87
148
  }
88
149
  /** The block's rows — EXACTLY the preview's frame shape, the gutter at
89
150
  * the left edge (the preview's two-space mock indent is its own
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { SPINNER, foldLine, visibleWidth, bodySpacing, Container, cellComponent,
10
10
  export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
11
11
  export { pendingQueueRows } from "./components.js";
12
12
  export { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
13
- export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, type PanelArgs, type PanelFlavor, type PanelPhase, type PanelSel, type PanelState, type PanelVerdict, type PanelView, } from "./approval-panel.js";
13
+ export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, type PanelArgs, type PanelFlavor, type PanelPhase, type PanelSel, type PanelState, type PanelVerdict, type PanelView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, } from "./approval-panel.js";
14
14
  export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact, } from "./strings.js";
15
+ export { extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
15
16
  export { bannerLines, COLOR_OFF, COLOR_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
package/dist/index.js CHANGED
@@ -23,4 +23,6 @@ export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWi
23
23
  // listing/view/note, the uncertain execution's view. The flow stays in
24
24
  // the cli; what the human reads is presentation.
25
25
  export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, } from "./strings.js";
26
+ // KC3.5: the interrupted-ask copy and the extracted /help table.
27
+ export { extensionsBannerText, helpRows, unansweredAskView } from "./strings.js";
26
28
  export { bannerLines, COLOR_OFF, COLOR_ON, colorInlineCode, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, } from "./render.js";
package/dist/strings.d.ts CHANGED
@@ -58,3 +58,50 @@ export declare function projectUntrustedNote(count: number, root: string): strin
58
58
  * question because it reaches the terminal as raw text there; the
59
59
  * panel's own rows are escaped by the panel renderer. */
60
60
  export declare function uncertainView(name: string, executionId: string): PanelView;
61
+ /**
62
+ * KC3.5 — the SAME uncertainty gate, said honestly for an ask_user call.
63
+ *
64
+ * "Did the interrupted execution apply?" is the right question for a
65
+ * side effect and the wrong one for a question: nothing applied, the
66
+ * human simply never answered. The COPY special-cases ask_user; the
67
+ * mechanism does not — the verdict still maps to the runtime's own
68
+ * rerun/abandoned resolution, whose error-fill text is untouched.
69
+ *
70
+ * (The round's ① probe pinned why this surface exists at all: the
71
+ * shipped recovery blocks on a started-unreported execution regardless
72
+ * of idempotency, so an interrupted ask meets this gate on the way
73
+ * back. Re-asking is safe — that is what "1 re-ask" says out loud.)
74
+ */
75
+ export declare function unansweredAskView(executionId: string): PanelView;
76
+ /** An extension as the banner names it — the live `connecting` flag is
77
+ * the MCP bridge's in-flight state ("mcp (connecting…)"). Structural on
78
+ * purpose: the runtime's KisoExtension satisfies it without this
79
+ * package importing the runtime. */
80
+ export interface BannerExtension {
81
+ readonly name: string;
82
+ readonly connecting?: boolean;
83
+ }
84
+ /**
85
+ * KC3.5 slice ⓪ (the extraction) — the `[N extensions: …]` banner text:
86
+ * the built-in column, then the user-level names, then the project-level
87
+ * ones marked `project:`.
88
+ *
89
+ * A pure function of three name lists, so what the banner SAYS is
90
+ * testable without a terminal — which matters this round, because the
91
+ * count is where the ask's TTY gate becomes visible: an interactive
92
+ * session reads `built-in: mcp, skills, subagent, ask` and a piped one
93
+ * reads `built-in: mcp, skills, subagent`, from this one composition.
94
+ */
95
+ export declare function extensionsBannerText(builtIn: readonly BannerExtension[], user: readonly BannerExtension[], project: readonly BannerExtension[]): string;
96
+ /**
97
+ * KC3.5 slice ⓪ (the extraction) — the /help command table.
98
+ *
99
+ * The rows were eight bodyLog calls in the CLI's dispatcher; they are
100
+ * presentation, and presentation belongs here (the KC3 §1 pattern —
101
+ * the FLOW, which is "print these on the chain, then re-prompt", stays
102
+ * in dispatch.ts). The last row carries its own newline exactly as it
103
+ * did inline: bodyLog splits on \n, so `exit` and `keys` land as two
104
+ * rows from one call — the shape the KC1/KC2/KC3 gestures were added
105
+ * to, unchanged.
106
+ */
107
+ export declare function helpRows(): string[];
package/dist/strings.js CHANGED
@@ -79,3 +79,79 @@ export function uncertainView(name, executionId) {
79
79
  fallbackQuestion: `⚠ interrupted execution: ${escapeTerminal(name)} (${executionId}) — did it apply? (y)es / (n)o `,
80
80
  };
81
81
  }
82
+ /**
83
+ * KC3.5 — the SAME uncertainty gate, said honestly for an ask_user call.
84
+ *
85
+ * "Did the interrupted execution apply?" is the right question for a
86
+ * side effect and the wrong one for a question: nothing applied, the
87
+ * human simply never answered. The COPY special-cases ask_user; the
88
+ * mechanism does not — the verdict still maps to the runtime's own
89
+ * rerun/abandoned resolution, whose error-fill text is untouched.
90
+ *
91
+ * (The round's ① probe pinned why this surface exists at all: the
92
+ * shipped recovery blocks on a started-unreported execution regardless
93
+ * of idempotency, so an interrupted ask meets this gate on the way
94
+ * back. Re-asking is safe — that is what "1 re-ask" says out loud.)
95
+ */
96
+ export function unansweredAskView(executionId) {
97
+ return {
98
+ flavor: "simple",
99
+ name: "unanswered question",
100
+ title: `ask_user (${executionId})`,
101
+ speaker: "kiso",
102
+ statusText: "▸ unanswered question",
103
+ args: { kind: "text", lines: [executionId] },
104
+ ruleOverride: "an unanswered question was interrupted — ask it again? — 1 re-ask · 3 drop",
105
+ fallbackQuestion: `⚠ an unanswered question was interrupted (${executionId}) — ask it again? (y)es / (n)o `,
106
+ };
107
+ }
108
+ /**
109
+ * KC3.5 slice ⓪ (the extraction) — the `[N extensions: …]` banner text:
110
+ * the built-in column, then the user-level names, then the project-level
111
+ * ones marked `project:`.
112
+ *
113
+ * A pure function of three name lists, so what the banner SAYS is
114
+ * testable without a terminal — which matters this round, because the
115
+ * count is where the ask's TTY gate becomes visible: an interactive
116
+ * session reads `built-in: mcp, skills, subagent, ask` and a piped one
117
+ * reads `built-in: mcp, skills, subagent`, from this one composition.
118
+ */
119
+ export function extensionsBannerText(builtIn, user, project) {
120
+ const total = builtIn.length + user.length + project.length;
121
+ if (total === 0)
122
+ return "";
123
+ const label = (e) => (e.connecting === true ? `${e.name} (connecting…)` : e.name);
124
+ const parts = [];
125
+ if (builtIn.length > 0)
126
+ parts.push(`built-in: ${builtIn.map(label).join(", ")}`);
127
+ if (user.length > 0)
128
+ parts.push(user.map(label).join(", "));
129
+ if (project.length > 0)
130
+ parts.push(`project: ${project.map(label).join(", ")}`);
131
+ return ` · [${total} extension${total === 1 ? "" : "s"}: ${parts.join(" · ")}]`;
132
+ }
133
+ /**
134
+ * KC3.5 slice ⓪ (the extraction) — the /help command table.
135
+ *
136
+ * The rows were eight bodyLog calls in the CLI's dispatcher; they are
137
+ * presentation, and presentation belongs here (the KC3 §1 pattern —
138
+ * the FLOW, which is "print these on the chain, then re-prompt", stays
139
+ * in dispatch.ts). The last row carries its own newline exactly as it
140
+ * did inline: bodyLog splits on \n, so `exit` and `keys` land as two
141
+ * rows from one call — the shape the KC1/KC2/KC3 gestures were added
142
+ * to, unchanged.
143
+ */
144
+ export function helpRows() {
145
+ const p = palette();
146
+ const cmd = (name, desc) => `${p.bold}${name}${p.reset} ${desc}`;
147
+ return [
148
+ cmd("/help", "print this list of commands"),
149
+ cmd("/think", "show the last full thinking block"),
150
+ cmd("/last", "show the most recent tool call's input and output"),
151
+ cmd("/status", "show session id, event count, and context estimate"),
152
+ cmd("/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"),
153
+ cmd("/model", "list model profiles; /model <name|provider/model> switches"),
154
+ cmd("/compact", "summarize the older conversation to free context"),
155
+ `${cmd("exit", "leave the session")}\n${cmd("keys", "enter sends · ctrl+J newline (shift+enter where encoded) · esc stops the run · alt+⏎ stops it and sends this instead · @ files · 1-4 answers an ask")}`,
156
+ ];
157
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tui-cells",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "kiso tui-cells — the components cell renderer (components, diff, width, the render slice). Zero runtime dependencies: input is data, output is bytes.",
5
5
  "type": "module",
6
6
  "license": "MIT",