@bastani/atomic 0.8.4-0 → 0.8.5-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.
- package/CHANGELOG.md +16 -0
- package/README.md +24 -23
- package/dist/builtin/intercom/README.md +5 -5
- package/dist/builtin/intercom/index.ts +1 -1
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/ui/compose.ts +19 -1
- package/dist/builtin/intercom/ui/session-list.ts +19 -1
- package/dist/builtin/mcp/README.md +3 -3
- package/dist/builtin/mcp/commands.ts +1 -1
- package/dist/builtin/mcp/host-html-template.ts +1 -1
- package/dist/builtin/mcp/mcp-panel.ts +14 -14
- package/dist/builtin/mcp/mcp-setup-panel.ts +4 -4
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/tool-result-renderer.ts +1 -1
- package/dist/builtin/subagents/README.md +3 -3
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/tui/render.ts +1844 -1062
- package/dist/builtin/web-access/README.md +1 -1
- package/dist/builtin/web-access/curator-page.ts +2 -2
- package/dist/builtin/web-access/index.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/README.md +34 -7
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +23 -4
- package/dist/builtin/workflows/builtin/ralph.ts +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/workflow/SKILL.md +75 -16
- package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +34 -11
- package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +111 -20
- package/dist/builtin/workflows/src/extension/discovery.ts +32 -4
- package/dist/builtin/workflows/src/extension/index.ts +347 -63
- package/dist/builtin/workflows/src/extension/render-call.ts +3 -1
- package/dist/builtin/workflows/src/extension/render-result.ts +7 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +4 -2
- package/dist/builtin/workflows/src/extension/wiring.ts +32 -8
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +36 -14
- package/dist/builtin/workflows/src/runs/background/runner.ts +2 -2
- package/dist/builtin/workflows/src/runs/background/status.ts +89 -0
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +338 -78
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +55 -7
- package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +146 -10
- package/dist/builtin/workflows/src/shared/store.ts +29 -0
- package/dist/builtin/workflows/src/shared/types.ts +25 -4
- package/dist/builtin/workflows/src/tui/graph-canvas.ts +69 -2
- package/dist/builtin/workflows/src/tui/graph-view.ts +97 -182
- package/dist/builtin/workflows/src/tui/header.ts +36 -20
- package/dist/builtin/workflows/src/tui/inline-form-card.ts +129 -46
- package/dist/builtin/workflows/src/tui/inline-form-editor.ts +111 -36
- package/dist/builtin/workflows/src/tui/inputs-picker.ts +311 -91
- package/dist/builtin/workflows/src/tui/layout.ts +1 -1
- package/dist/builtin/workflows/src/tui/node-card.ts +66 -37
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +20 -6
- package/dist/builtin/workflows/src/tui/prompt-card.ts +262 -85
- package/dist/builtin/workflows/src/tui/run-detail.ts +50 -31
- package/dist/builtin/workflows/src/tui/session-confirm.ts +21 -14
- package/dist/builtin/workflows/src/tui/session-picker.ts +35 -26
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +531 -960
- package/dist/builtin/workflows/src/tui/status-helpers.ts +6 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +8 -4
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -2
- package/dist/builtin/workflows/src/tui/switcher.ts +55 -25
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +33 -1
- package/dist/builtin/workflows/src/tui/workflow-list.ts +10 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -6
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -3
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -7
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +3 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +3 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +24 -12
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +28 -17
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +65 -28
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +13 -5
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
- package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.js +6 -6
- package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +7 -4
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +3 -2
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +3 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +2 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/todos.d.ts.map +1 -1
- package/dist/core/tools/todos.js +1 -1
- package/dist/core/tools/todos.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts +4 -3
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +3 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +3 -3
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +13 -3
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +2 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +2 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +47 -5
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +5 -5
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +7 -7
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +8 -8
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +3 -3
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +10 -12
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +3 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/working-status.d.ts +25 -0
- package/dist/modes/interactive/components/working-status.d.ts.map +1 -0
- package/dist/modes/interactive/components/working-status.js +28 -0
- package/dist/modes/interactive/components/working-status.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +8 -7
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +8 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +5 -5
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/development.md +2 -2
- package/docs/extensions.md +7 -7
- package/docs/packages.md +11 -8
- package/docs/quickstart.md +2 -2
- package/docs/rpc.md +1 -1
- package/docs/sdk.md +14 -11
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +10 -10
- package/docs/settings.md +1 -1
- package/docs/terminal-setup.md +9 -9
- package/docs/tmux.md +10 -10
- package/docs/tui.md +2 -2
- package/docs/usage.md +9 -9
- package/package.json +6 -1
|
@@ -25,10 +25,19 @@
|
|
|
25
25
|
* src/tui/graph-view.ts overlay integration + key routing
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { keyHint, keyText, rawKeyHint } from "@bastani/atomic";
|
|
29
|
+
import {
|
|
30
|
+
SelectList,
|
|
31
|
+
truncateToWidth,
|
|
32
|
+
visibleWidth,
|
|
33
|
+
wrapTextWithAnsi,
|
|
34
|
+
type SelectItem,
|
|
35
|
+
type SelectListTheme,
|
|
36
|
+
} from "@earendil-works/pi-tui";
|
|
28
37
|
import type { PendingPrompt } from "../shared/store-types.js";
|
|
29
38
|
import type { GraphTheme } from "./graph-theme.js";
|
|
30
39
|
import { hexToAnsi, hexBg, paint, RESET, BOLD } from "./color-utils.js";
|
|
31
|
-
import { matchesKey
|
|
40
|
+
import { matchesKey } from "./text-helpers.js";
|
|
32
41
|
|
|
33
42
|
// ---------------------------------------------------------------------------
|
|
34
43
|
// State
|
|
@@ -49,6 +58,8 @@ export interface PromptCardState {
|
|
|
49
58
|
selectedIndex: number;
|
|
50
59
|
/** Boolean selection for `confirm` prompts (true = yes, false = no). */
|
|
51
60
|
confirmValue: boolean;
|
|
61
|
+
/** For multi-line editor prompts, Tab moves focus to a visible Submit action. */
|
|
62
|
+
editorSubmitFocused: boolean;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
export function createPromptCardState(prompt: PendingPrompt): PromptCardState {
|
|
@@ -59,6 +70,7 @@ export function createPromptCardState(prompt: PendingPrompt): PromptCardState {
|
|
|
59
70
|
caret: initial.length,
|
|
60
71
|
selectedIndex: 0,
|
|
61
72
|
confirmValue: false,
|
|
73
|
+
editorSubmitFocused: false,
|
|
62
74
|
};
|
|
63
75
|
}
|
|
64
76
|
|
|
@@ -87,7 +99,7 @@ export function handlePromptCardInput(
|
|
|
87
99
|
data: string,
|
|
88
100
|
state: PromptCardState,
|
|
89
101
|
): PromptCardAction {
|
|
90
|
-
if (data === "\
|
|
102
|
+
if (data === "\x03" || matchesKey(data, "escape")) {
|
|
91
103
|
return { kind: "cancel" };
|
|
92
104
|
}
|
|
93
105
|
|
|
@@ -131,18 +143,15 @@ function handleSelect(data: string, state: PromptCardState): PromptCardAction {
|
|
|
131
143
|
}
|
|
132
144
|
return { kind: "noop" };
|
|
133
145
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return { kind: "submit", response: choices[state.selectedIndex] ?? choices[0] };
|
|
144
|
-
}
|
|
145
|
-
return { kind: "noop" };
|
|
146
|
+
|
|
147
|
+
let action: PromptCardAction = { kind: "noop" };
|
|
148
|
+
const list = createPromptSelectList(state);
|
|
149
|
+
list.onSelect = (item) => {
|
|
150
|
+
const idx = Number(item.value);
|
|
151
|
+
action = { kind: "submit", response: choices[idx] ?? choices[0] };
|
|
152
|
+
};
|
|
153
|
+
list.handleInput(normalizeSelectKeyData(data));
|
|
154
|
+
return action;
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
function handleInput(data: string, state: PromptCardState): PromptCardAction {
|
|
@@ -153,11 +162,15 @@ function handleInput(data: string, state: PromptCardState): PromptCardAction {
|
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
function handleEditor(data: string, state: PromptCardState): PromptCardAction {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
|
|
166
|
+
state.editorSubmitFocused = !state.editorSubmitFocused;
|
|
167
|
+
return { kind: "noop" };
|
|
168
|
+
}
|
|
169
|
+
if (state.editorSubmitFocused) {
|
|
170
|
+
if (matchesKey(data, "enter")) {
|
|
171
|
+
return { kind: "submit", response: state.rawText };
|
|
172
|
+
}
|
|
173
|
+
return { kind: "noop" };
|
|
161
174
|
}
|
|
162
175
|
if (data === "\r" || data === "\n") {
|
|
163
176
|
state.rawText = state.rawText.slice(0, state.caret) + "\n" + state.rawText.slice(state.caret);
|
|
@@ -172,30 +185,75 @@ function applyTextEdit(
|
|
|
172
185
|
state: PromptCardState,
|
|
173
186
|
): PromptCardAction {
|
|
174
187
|
if (data === "\x1b[D") {
|
|
175
|
-
state.caret =
|
|
188
|
+
state.caret = previousGraphemeBoundary(state.rawText, state.caret);
|
|
176
189
|
return { kind: "noop" };
|
|
177
190
|
}
|
|
178
191
|
if (data === "\x1b[C") {
|
|
179
|
-
state.caret =
|
|
192
|
+
state.caret = nextGraphemeBoundary(state.rawText, state.caret);
|
|
180
193
|
return { kind: "noop" };
|
|
181
194
|
}
|
|
182
195
|
if (data === "\x7f" || data === "\b") {
|
|
183
196
|
if (state.caret > 0) {
|
|
184
|
-
state.rawText
|
|
185
|
-
|
|
186
|
-
state.caret
|
|
197
|
+
const prev = previousGraphemeBoundary(state.rawText, state.caret);
|
|
198
|
+
state.rawText = state.rawText.slice(0, prev) + state.rawText.slice(state.caret);
|
|
199
|
+
state.caret = prev;
|
|
187
200
|
}
|
|
188
201
|
return { kind: "noop" };
|
|
189
202
|
}
|
|
190
|
-
if (data
|
|
203
|
+
if (isPrintableText(data)) {
|
|
191
204
|
state.rawText =
|
|
192
205
|
state.rawText.slice(0, state.caret) + data + state.rawText.slice(state.caret);
|
|
193
|
-
state.caret +=
|
|
206
|
+
state.caret += data.length;
|
|
194
207
|
return { kind: "noop" };
|
|
195
208
|
}
|
|
196
209
|
return { kind: "noop" };
|
|
197
210
|
}
|
|
198
211
|
|
|
212
|
+
interface GraphemePart {
|
|
213
|
+
text: string;
|
|
214
|
+
start: number;
|
|
215
|
+
end: number;
|
|
216
|
+
width: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
220
|
+
|
|
221
|
+
function graphemeParts(value: string): GraphemePart[] {
|
|
222
|
+
const parts: GraphemePart[] = [];
|
|
223
|
+
for (const segment of segmenter.segment(value)) {
|
|
224
|
+
const text = segment.segment;
|
|
225
|
+
parts.push({
|
|
226
|
+
text,
|
|
227
|
+
start: segment.index,
|
|
228
|
+
end: segment.index + text.length,
|
|
229
|
+
width: visibleWidth(text),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return parts;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function previousGraphemeBoundary(value: string, caret: number): number {
|
|
236
|
+
const safeCaret = Math.max(0, Math.min(caret, value.length));
|
|
237
|
+
let prev = 0;
|
|
238
|
+
for (const part of graphemeParts(value)) {
|
|
239
|
+
if (part.start >= safeCaret) break;
|
|
240
|
+
prev = part.start;
|
|
241
|
+
}
|
|
242
|
+
return prev;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function nextGraphemeBoundary(value: string, caret: number): number {
|
|
246
|
+
const safeCaret = Math.max(0, Math.min(caret, value.length));
|
|
247
|
+
for (const part of graphemeParts(value)) {
|
|
248
|
+
if (part.end > safeCaret) return part.end;
|
|
249
|
+
}
|
|
250
|
+
return value.length;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isPrintableText(data: string): boolean {
|
|
254
|
+
return data.length > 0 && !data.startsWith("\x1b") && !/[\x00-\x1f\x7f]/.test(data);
|
|
255
|
+
}
|
|
256
|
+
|
|
199
257
|
/**
|
|
200
258
|
* Compute the safe default response when the user dismisses the prompt.
|
|
201
259
|
* Used by the overlay to keep the workflow body unblocked even on cancel.
|
|
@@ -212,6 +270,76 @@ export function defaultResponseFor(prompt: PendingPrompt): unknown {
|
|
|
212
270
|
}
|
|
213
271
|
}
|
|
214
272
|
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Select-list bridge
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
function createPromptSelectList(
|
|
278
|
+
state: PromptCardState,
|
|
279
|
+
theme?: GraphTheme,
|
|
280
|
+
maxVisible = 5,
|
|
281
|
+
): SelectList {
|
|
282
|
+
const choices = state.prompt.choices ?? [];
|
|
283
|
+
const items: SelectItem[] = choices.map((choice, idx) => ({
|
|
284
|
+
value: String(idx),
|
|
285
|
+
label: choice,
|
|
286
|
+
}));
|
|
287
|
+
const list = new SelectList(
|
|
288
|
+
items,
|
|
289
|
+
Math.max(1, Math.min(maxVisible, choices.length || 1)),
|
|
290
|
+
createSelectListTheme(theme),
|
|
291
|
+
{
|
|
292
|
+
minPrimaryColumnWidth: 1,
|
|
293
|
+
maxPrimaryColumnWidth: 80,
|
|
294
|
+
truncatePrimary: ({ text, maxWidth, isSelected }) => {
|
|
295
|
+
const clipped = truncateToWidth(text, maxWidth, "");
|
|
296
|
+
if (!theme) return clipped;
|
|
297
|
+
return paint(clipped, isSelected ? theme.text : theme.dim, { bold: isSelected });
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
const selectedIndex = normalizeSelectIndex(state.selectedIndex, choices.length);
|
|
302
|
+
list.setSelectedIndex(selectedIndex);
|
|
303
|
+
list.onSelectionChange = (item) => {
|
|
304
|
+
state.selectedIndex = normalizeSelectIndex(Number(item.value), choices.length);
|
|
305
|
+
};
|
|
306
|
+
return list;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function createSelectListTheme(theme?: GraphTheme): SelectListTheme {
|
|
310
|
+
if (!theme) {
|
|
311
|
+
return {
|
|
312
|
+
selectedPrefix: (text) => text,
|
|
313
|
+
selectedText: (text) => text,
|
|
314
|
+
description: (text) => text,
|
|
315
|
+
scrollInfo: (text) => text,
|
|
316
|
+
noMatch: (text) => text,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
selectedPrefix: (text) => paint(text, theme.accent, { bold: true }),
|
|
321
|
+
selectedText: (text) => paint(text, theme.text, { bold: true }),
|
|
322
|
+
description: (text) => paint(text, theme.textMuted),
|
|
323
|
+
scrollInfo: (text) => paint(text, theme.dim),
|
|
324
|
+
noMatch: (text) => paint(text, theme.dim),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function normalizeSelectIndex(index: number, length: number): number {
|
|
329
|
+
if (length <= 0) return 0;
|
|
330
|
+
const n = Number.isFinite(index) ? Math.trunc(index) : 0;
|
|
331
|
+
return ((n % length) + length) % length;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function normalizeSelectKeyData(data: string): string {
|
|
335
|
+
// The historical prompt card accepted left/right as select aliases; feed the
|
|
336
|
+
// corresponding vertical key into pi-tui's SelectList so it owns the actual
|
|
337
|
+
// wrap/clamp/selection update behavior.
|
|
338
|
+
if (matchesKey(data, "right") || data === "\x1b[C") return "\x1b[B";
|
|
339
|
+
if (matchesKey(data, "left") || data === "\x1b[D") return "\x1b[A";
|
|
340
|
+
return data;
|
|
341
|
+
}
|
|
342
|
+
|
|
215
343
|
// ---------------------------------------------------------------------------
|
|
216
344
|
// Rendering
|
|
217
345
|
// ---------------------------------------------------------------------------
|
|
@@ -293,25 +421,7 @@ function makePaddedRow(
|
|
|
293
421
|
|
|
294
422
|
function wrapText(text: string, width: number): string[] {
|
|
295
423
|
if (width <= 0) return [text];
|
|
296
|
-
|
|
297
|
-
for (const paragraph of text.split("\n")) {
|
|
298
|
-
if (paragraph.length === 0) {
|
|
299
|
-
out.push("");
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
let remaining = paragraph;
|
|
303
|
-
while (visibleWidth(remaining) > width) {
|
|
304
|
-
// Break on the last whitespace within `width` cells; fall back to a
|
|
305
|
-
// hard cut so glyphs longer than `width` don't run off the card.
|
|
306
|
-
const slice = remaining.slice(0, width);
|
|
307
|
-
const lastSpace = slice.lastIndexOf(" ");
|
|
308
|
-
const cut = lastSpace > 0 ? lastSpace : width;
|
|
309
|
-
out.push(remaining.slice(0, cut));
|
|
310
|
-
remaining = remaining.slice(cut).replace(/^\s+/, "");
|
|
311
|
-
}
|
|
312
|
-
out.push(remaining);
|
|
313
|
-
}
|
|
314
|
-
return out;
|
|
424
|
+
return wrapTextWithAnsi(text, width);
|
|
315
425
|
}
|
|
316
426
|
|
|
317
427
|
function renderResponseField(
|
|
@@ -324,7 +434,7 @@ function renderResponseField(
|
|
|
324
434
|
case "confirm":
|
|
325
435
|
return [renderConfirmRow(state, theme, usable)];
|
|
326
436
|
case "select":
|
|
327
|
-
return
|
|
437
|
+
return renderSelectRows(state, theme, usable);
|
|
328
438
|
case "input":
|
|
329
439
|
return [renderInputRow(state, theme, usable, cursorOn)];
|
|
330
440
|
case "editor":
|
|
@@ -350,23 +460,18 @@ function renderConfirmRow(
|
|
|
350
460
|
return padToUsable(row, usable);
|
|
351
461
|
}
|
|
352
462
|
|
|
353
|
-
function
|
|
463
|
+
function renderSelectRows(
|
|
354
464
|
state: PromptCardState,
|
|
355
465
|
theme: GraphTheme,
|
|
356
466
|
usable: number,
|
|
357
|
-
): string {
|
|
467
|
+
): string[] {
|
|
358
468
|
const choices = state.prompt.choices ?? [];
|
|
359
469
|
if (choices.length === 0) {
|
|
360
|
-
return padToUsable(paint("(no choices)", theme.dim), usable);
|
|
470
|
+
return [padToUsable(paint("(no choices)", theme.dim), usable)];
|
|
361
471
|
}
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const markerColor = sel ? theme.accent : theme.dim;
|
|
366
|
-
const textColor = sel ? theme.text : theme.dim;
|
|
367
|
-
return paint(marker, markerColor) + " " + paint(choice, textColor, { bold: sel });
|
|
368
|
-
});
|
|
369
|
-
return padToUsable(cells.join(" "), usable);
|
|
472
|
+
const maxVisible = Math.min(5, choices.length);
|
|
473
|
+
const list = createPromptSelectList(state, theme, maxVisible);
|
|
474
|
+
return list.render(usable).map((line) => padToUsable(line, usable));
|
|
370
475
|
}
|
|
371
476
|
|
|
372
477
|
function renderInputRow(
|
|
@@ -411,7 +516,7 @@ function renderEditorRows(
|
|
|
411
516
|
for (let i = 0; i < ROWS; i++) {
|
|
412
517
|
const lineIdx = safeStart + i;
|
|
413
518
|
const lineText = allLines[lineIdx] ?? "";
|
|
414
|
-
const isCaretLine = lineIdx === caretLine;
|
|
519
|
+
const isCaretLine = !state.editorSubmitFocused && lineIdx === caretLine;
|
|
415
520
|
const inner = usable - 2;
|
|
416
521
|
const clipped = clipToCaretWindow(lineText, isCaretLine ? caretCol : Math.min(caretCol, lineText.length), inner);
|
|
417
522
|
const withCursor = isCaretLine
|
|
@@ -420,19 +525,67 @@ function renderEditorRows(
|
|
|
420
525
|
const prefix = paint(isCaretLine ? "❯ " : " ", isCaretLine ? theme.accent : theme.dim);
|
|
421
526
|
rows.push(padToUsable(prefix + withCursor, usable));
|
|
422
527
|
}
|
|
528
|
+
rows.push(padToUsable(renderEditorSubmitAction(state.editorSubmitFocused, theme), usable));
|
|
423
529
|
return rows;
|
|
424
530
|
}
|
|
425
531
|
|
|
532
|
+
function renderEditorSubmitAction(focused: boolean, theme: GraphTheme): string {
|
|
533
|
+
const marker = focused ? "❯" : "○";
|
|
534
|
+
return (
|
|
535
|
+
paint(marker, focused ? theme.accent : theme.dim, { bold: focused }) +
|
|
536
|
+
" " +
|
|
537
|
+
paint("Submit response", focused ? theme.text : theme.textMuted, { bold: focused }) +
|
|
538
|
+
paint(" · ", theme.dim) +
|
|
539
|
+
graphKeyHint("tui.input.submit", "submit", theme)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
426
543
|
function clipToCaretWindow(
|
|
427
544
|
value: string,
|
|
428
545
|
caret: number,
|
|
429
546
|
windowWidth: number,
|
|
430
547
|
): { text: string; caret: number } {
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
548
|
+
if (windowWidth <= 0) return { text: "", caret: 0 };
|
|
549
|
+
if (visibleWidth(value) <= windowWidth) {
|
|
550
|
+
return { text: value, caret: Math.max(0, Math.min(caret, value.length)) };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const parts = graphemeParts(value);
|
|
554
|
+
const safeCaret = Math.max(0, Math.min(caret, value.length));
|
|
555
|
+
const caretPartIndex = parts.findIndex((part) => part.end > safeCaret);
|
|
556
|
+
const caretIndex = caretPartIndex === -1 ? parts.length : caretPartIndex;
|
|
557
|
+
|
|
558
|
+
// Keep the caret visible and bias toward a few cells of look-ahead, matching
|
|
559
|
+
// the old tail-biased input field while slicing on grapheme/cell boundaries.
|
|
560
|
+
let start = caretIndex;
|
|
561
|
+
let end = caretIndex;
|
|
562
|
+
let cells = 0;
|
|
563
|
+
const lookAheadCells = Math.min(4, windowWidth);
|
|
564
|
+
while (end < parts.length && (cells < lookAheadCells || start === end)) {
|
|
565
|
+
const width = Math.max(1, parts[end]!.width);
|
|
566
|
+
if (cells > 0 && cells + width > windowWidth) break;
|
|
567
|
+
cells += width;
|
|
568
|
+
end += 1;
|
|
569
|
+
}
|
|
570
|
+
while (start > 0) {
|
|
571
|
+
const width = Math.max(1, parts[start - 1]!.width);
|
|
572
|
+
if (cells > 0 && cells + width > windowWidth) break;
|
|
573
|
+
cells += width;
|
|
574
|
+
start -= 1;
|
|
575
|
+
}
|
|
576
|
+
while (end < parts.length) {
|
|
577
|
+
const width = Math.max(1, parts[end]!.width);
|
|
578
|
+
if (cells > 0 && cells + width > windowWidth) break;
|
|
579
|
+
cells += width;
|
|
580
|
+
end += 1;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const textStart = parts[start]?.start ?? 0;
|
|
584
|
+
const textEnd = parts[end - 1]?.end ?? textStart;
|
|
585
|
+
return {
|
|
586
|
+
text: value.slice(textStart, textEnd),
|
|
587
|
+
caret: Math.max(0, Math.min(safeCaret - textStart, textEnd - textStart)),
|
|
588
|
+
};
|
|
436
589
|
}
|
|
437
590
|
|
|
438
591
|
function drawCursor(
|
|
@@ -441,10 +594,15 @@ function drawCursor(
|
|
|
441
594
|
cursorOn: boolean,
|
|
442
595
|
theme: GraphTheme,
|
|
443
596
|
): string {
|
|
597
|
+
const parts = graphemeParts(text);
|
|
444
598
|
const safeCaret = Math.max(0, Math.min(caret, text.length));
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const
|
|
599
|
+
const caretPartIndex = parts.findIndex((part) => part.end > safeCaret);
|
|
600
|
+
const cursorPart = caretPartIndex === -1 ? undefined : parts[caretPartIndex];
|
|
601
|
+
const cursorStart = cursorPart?.start ?? text.length;
|
|
602
|
+
const cursorEnd = cursorPart?.end ?? text.length;
|
|
603
|
+
const before = text.slice(0, cursorStart);
|
|
604
|
+
const at = cursorPart?.text ?? " ";
|
|
605
|
+
const after = text.slice(cursorEnd);
|
|
448
606
|
const beforeFx = paint(before, theme.text);
|
|
449
607
|
const afterFx = paint(after, theme.text);
|
|
450
608
|
if (!cursorOn) return beforeFx + paint(at, theme.text) + afterFx;
|
|
@@ -459,43 +617,62 @@ function padToUsable(content: string, usable: number): string {
|
|
|
459
617
|
return content + " ".repeat(usable - w);
|
|
460
618
|
}
|
|
461
619
|
|
|
620
|
+
type CodingAgentKeybinding = Parameters<typeof keyHint>[0];
|
|
621
|
+
|
|
622
|
+
function graphKeyHint(
|
|
623
|
+
keybinding: CodingAgentKeybinding,
|
|
624
|
+
description: string,
|
|
625
|
+
theme: GraphTheme,
|
|
626
|
+
): string {
|
|
627
|
+
try {
|
|
628
|
+
return keyHint(keybinding, description);
|
|
629
|
+
} catch {
|
|
630
|
+
return localKeyHint(keyText(keybinding), description, theme);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function graphRawKeyHint(key: string, description: string, theme: GraphTheme): string {
|
|
635
|
+
try {
|
|
636
|
+
return rawKeyHint(key, description);
|
|
637
|
+
} catch {
|
|
638
|
+
return localKeyHint(key, description, theme);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function localKeyHint(key: string, description: string, theme: GraphTheme): string {
|
|
643
|
+
return paint(key, theme.text) + paint(` ${description}`, theme.textMuted);
|
|
644
|
+
}
|
|
645
|
+
|
|
462
646
|
function renderHints(kind: PendingPrompt["kind"], theme: GraphTheme): string {
|
|
463
|
-
const
|
|
464
|
-
const muted = hexToAnsi(theme.textMuted);
|
|
465
|
-
const dim = hexToAnsi(theme.dim);
|
|
466
|
-
const sep = `${dim} · ${RESET}`;
|
|
647
|
+
const sep = paint(" · ", theme.dim);
|
|
467
648
|
if (kind === "editor") {
|
|
468
649
|
return (
|
|
469
|
-
|
|
650
|
+
graphRawKeyHint("tab", "Submit Action", theme) +
|
|
470
651
|
sep +
|
|
471
|
-
|
|
652
|
+
graphKeyHint("tui.input.submit", "Newline/Submit", theme) +
|
|
472
653
|
sep +
|
|
473
|
-
|
|
654
|
+
graphKeyHint("tui.select.cancel", "Skip", theme)
|
|
474
655
|
);
|
|
475
656
|
}
|
|
476
657
|
if (kind === "confirm") {
|
|
477
658
|
return (
|
|
478
|
-
|
|
659
|
+
graphRawKeyHint("y", "Yes", theme) +
|
|
479
660
|
sep +
|
|
480
|
-
|
|
661
|
+
graphRawKeyHint("n", "No", theme) +
|
|
481
662
|
sep +
|
|
482
|
-
|
|
663
|
+
graphKeyHint("tui.select.confirm", "Submit", theme) +
|
|
483
664
|
sep +
|
|
484
|
-
|
|
665
|
+
graphKeyHint("tui.select.cancel", "Skip", theme)
|
|
485
666
|
);
|
|
486
667
|
}
|
|
487
668
|
if (kind === "select") {
|
|
488
669
|
return (
|
|
489
|
-
|
|
670
|
+
graphRawKeyHint("↑↓", "Choose", theme) +
|
|
490
671
|
sep +
|
|
491
|
-
|
|
672
|
+
graphKeyHint("tui.select.confirm", "Submit", theme) +
|
|
492
673
|
sep +
|
|
493
|
-
|
|
674
|
+
graphKeyHint("tui.select.cancel", "Skip", theme)
|
|
494
675
|
);
|
|
495
676
|
}
|
|
496
|
-
return (
|
|
497
|
-
`${accent}↵${RESET} ${muted}submit${RESET}` +
|
|
498
|
-
sep +
|
|
499
|
-
`${accent}esc${RESET} ${muted}skip${RESET}`
|
|
500
|
-
);
|
|
677
|
+
return graphKeyHint("tui.input.submit", "Submit", theme) + sep + graphKeyHint("tui.select.cancel", "Skip", theme);
|
|
501
678
|
}
|