@rind-ai/cli 0.4.1 → 0.6.1
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/bin/rind.js +5 -5
- package/lib/assistant-renderer.js +179 -265
- package/lib/choice-menu-state.js +46 -46
- package/lib/cli-input-actions.js +548 -0
- package/lib/cli-output-controller.js +460 -0
- package/lib/cli-runtime-controller.js +350 -0
- package/lib/cli-state-store.js +32 -0
- package/lib/cli-state.js +41 -0
- package/lib/command-controller.js +159 -126
- package/lib/compact-context-state.js +22 -22
- package/lib/components/assistant-message.js +169 -0
- package/lib/components/composer-area.js +25 -0
- package/lib/components/dynamic-block.js +20 -0
- package/lib/components/monitor-stack.js +35 -0
- package/lib/components/text-block.js +47 -0
- package/lib/components/tool-block.js +122 -0
- package/lib/composer-terminal.js +224 -203
- package/lib/event-controller.js +243 -242
- package/lib/frontend-cli-implementation.js +656 -1111
- package/lib/input-controller.js +75 -94
- package/lib/input-errors.js +3 -3
- package/lib/interrupt-state.js +9 -9
- package/lib/line-editor.js +541 -541
- package/lib/local-slash-commands.js +217 -0
- package/lib/markdown-lines.js +103 -0
- package/lib/model-menu-state.js +50 -50
- package/lib/one-shot-progress.js +145 -0
- package/lib/one-shot.js +228 -0
- package/lib/question-menu-state.js +61 -0
- package/lib/rendering.js +1295 -1037
- package/lib/runtime-client.js +241 -193
- package/lib/runtime-env.js +21 -21
- package/lib/runtime-protocol.js +122 -15
- package/lib/slash-command-mode.js +16 -27
- package/lib/slash-menu-state.js +59 -59
- package/lib/{background-controller.js → task-monitor-controller.js} +411 -289
- package/lib/terminal-key.js +97 -97
- package/lib/text-width.js +335 -151
- package/lib/theme-menu-state.js +31 -0
- package/lib/theme.js +134 -0
- package/lib/tool-display.js +680 -0
- package/lib/tui/component.js +55 -0
- package/lib/tui/cursor.js +29 -0
- package/lib/tui/input-buffer.js +172 -0
- package/lib/tui/tui.js +591 -0
- package/lib/turn-controller.js +68 -78
- package/package.json +28 -28
- package/lib/assistant-stream-buffer.js +0 -25
- package/lib/terminal-ui.js +0 -581
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { createLineEditor } from "./line-editor.js";
|
|
2
|
+
import { createChoiceMenuState } from "./choice-menu-state.js";
|
|
3
|
+
import { createModelMenuState } from "./model-menu-state.js";
|
|
4
|
+
import { createThemeMenuState } from "./theme-menu-state.js";
|
|
5
|
+
import { createQuestionMenuState } from "./question-menu-state.js";
|
|
6
|
+
import { createSlashMenuState } from "./slash-menu-state.js";
|
|
7
|
+
import { REASONING_EFFORTS, runtimeMethods } from "./runtime-protocol.js";
|
|
8
|
+
import { parseTerminalKey } from "./terminal-key.js";
|
|
9
|
+
import {
|
|
10
|
+
answerPromptText,
|
|
11
|
+
answerPlaceholderText,
|
|
12
|
+
questionAnswerText,
|
|
13
|
+
questionText,
|
|
14
|
+
} from "./rendering.js";
|
|
15
|
+
|
|
16
|
+
export function createCliInputActions({
|
|
17
|
+
state,
|
|
18
|
+
request,
|
|
19
|
+
output,
|
|
20
|
+
getTurnController,
|
|
21
|
+
getTaskMonitor,
|
|
22
|
+
getLineInput,
|
|
23
|
+
pausePrompt,
|
|
24
|
+
resumePrompt,
|
|
25
|
+
handleSigint,
|
|
26
|
+
}) {
|
|
27
|
+
let cancelActiveInput = null;
|
|
28
|
+
const promptEditor = createLineEditor();
|
|
29
|
+
|
|
30
|
+
function restoreInputText(text) {
|
|
31
|
+
state.input.prefill = String(text || "");
|
|
32
|
+
if (state.input.session?.editor) {
|
|
33
|
+
state.input.session.editor.setInput(state.input.prefill);
|
|
34
|
+
state.input.prefill = "";
|
|
35
|
+
output.redraw();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addPendingInput(input, mode, result = {}) {
|
|
40
|
+
const inputId = String(result?.input_id || "").trim();
|
|
41
|
+
if (!inputId) {
|
|
42
|
+
throw new Error("Runtime accepted queued input without input_id.");
|
|
43
|
+
}
|
|
44
|
+
state.input.pending.push({ inputId, input, mode });
|
|
45
|
+
output.redraw();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function deliverQueuedInput(input, _mode, inputId) {
|
|
49
|
+
const index = state.input.pending.findIndex((entry) => entry.inputId === inputId);
|
|
50
|
+
if (index === -1) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
state.input.pending.splice(index, 1);
|
|
54
|
+
output.suspendPrompt(() => output.writeUserInput(input));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function retrievePendingInput(mode, session) {
|
|
58
|
+
if (state.input.retrievingModes.has(mode)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const entry = [...state.input.pending].reverse().find((item) => item.mode === mode);
|
|
62
|
+
if (!entry) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
state.input.retrievingModes.add(mode);
|
|
66
|
+
const method = mode === "steering"
|
|
67
|
+
? runtimeMethods.sessionUnsteer
|
|
68
|
+
: runtimeMethods.sessionDequeueFollowUp;
|
|
69
|
+
try {
|
|
70
|
+
const result = await request(method);
|
|
71
|
+
const inputId = String(result?.input_id || "").trim();
|
|
72
|
+
const input = String(result?.input || "");
|
|
73
|
+
if (result?.retrieved !== true || result?.mode !== mode || inputId !== entry.inputId || !input) {
|
|
74
|
+
throw new Error("Runtime returned an invalid queued input retrieval.");
|
|
75
|
+
}
|
|
76
|
+
const index = state.input.pending.findIndex((item) => item.inputId === inputId);
|
|
77
|
+
if (index === -1) {
|
|
78
|
+
throw new Error("Retrieved input is not present in the local queue.");
|
|
79
|
+
}
|
|
80
|
+
state.input.pending.splice(index, 1);
|
|
81
|
+
const current = session.editor.input();
|
|
82
|
+
session.editor.setInput([input, current].filter((value) => value.trim()).join("\n\n"));
|
|
83
|
+
output.redraw();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (state.runtime.status !== "closing") {
|
|
86
|
+
output.writeError(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
87
|
+
}
|
|
88
|
+
} finally {
|
|
89
|
+
state.input.retrievingModes.delete(mode);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function clearPendingInputs() {
|
|
94
|
+
if (!state.input.pending.length) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
state.input.pending.length = 0;
|
|
98
|
+
output.redraw();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function answerQuestion(event) {
|
|
102
|
+
pausePrompt();
|
|
103
|
+
output.closeAssistant();
|
|
104
|
+
if (output.beginQuestion) {
|
|
105
|
+
output.beginQuestion(event);
|
|
106
|
+
} else {
|
|
107
|
+
output.log(() => questionText(event));
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const options = Array.isArray(event.options) ? event.options : [];
|
|
111
|
+
const answer = output.terminalUi
|
|
112
|
+
? await askQuestionMenu(event)
|
|
113
|
+
: selectAnswer((await ask(answerPromptText(), answerPlaceholderText())).trim(), options);
|
|
114
|
+
if (state.turn.interruptRequested || state.runtime.status === "closing") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await request(runtimeMethods.userQuestionRespond, {
|
|
118
|
+
tool_call_id: event.tool_call_id,
|
|
119
|
+
answer,
|
|
120
|
+
});
|
|
121
|
+
if (output.finishQuestion) {
|
|
122
|
+
output.finishQuestion(event, answer);
|
|
123
|
+
} else {
|
|
124
|
+
output.log(() => questionAnswerText(event, answer));
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
resumePrompt();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function selectAnswer(raw, options) {
|
|
132
|
+
const index = Number(raw);
|
|
133
|
+
if (Number.isInteger(index) && index >= 1 && index <= options.length) {
|
|
134
|
+
const option = options[index - 1];
|
|
135
|
+
return typeof option === "string" ? option : String(option?.label || "");
|
|
136
|
+
}
|
|
137
|
+
return raw;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function ask(prompt, placeholder = "") {
|
|
141
|
+
if (!output.terminalUi) {
|
|
142
|
+
if (!getLineInput()) {
|
|
143
|
+
return Promise.reject(new Error("Input is not available"));
|
|
144
|
+
}
|
|
145
|
+
return askLine(promptValue(prompt));
|
|
146
|
+
}
|
|
147
|
+
return askTtyInput(prompt, placeholder);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function askLine(prompt) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const lineInput = getLineInput();
|
|
153
|
+
const cleanup = () => {
|
|
154
|
+
state.input.active = false;
|
|
155
|
+
lineInput.off("close", onClose);
|
|
156
|
+
if (cancelActiveInput === onCancel) {
|
|
157
|
+
cancelActiveInput = null;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const onClose = () => {
|
|
161
|
+
cleanup();
|
|
162
|
+
reject(new Error("Input closed"));
|
|
163
|
+
};
|
|
164
|
+
const onCancel = () => {
|
|
165
|
+
cleanup();
|
|
166
|
+
resolve("");
|
|
167
|
+
};
|
|
168
|
+
lineInput.once("close", onClose);
|
|
169
|
+
cancelActiveInput = onCancel;
|
|
170
|
+
state.input.active = true;
|
|
171
|
+
lineInput.question(prompt, (answer) => {
|
|
172
|
+
cleanup();
|
|
173
|
+
resolve(answer);
|
|
174
|
+
});
|
|
175
|
+
applyInputPrefill(lineInput);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function askTtyInput(prompt, placeholder) {
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
output.clearAssistantLineForInput();
|
|
182
|
+
const mode = placeholder && placeholder !== answerPlaceholderText() ? "prompt" : "line";
|
|
183
|
+
const initialInput = state.input.prefill;
|
|
184
|
+
const editor = mode === "prompt" ? promptEditor : createLineEditor(initialInput);
|
|
185
|
+
if (mode === "prompt") {
|
|
186
|
+
editor.setInput(initialInput);
|
|
187
|
+
}
|
|
188
|
+
editor.setViewportWidth(process.stdout.columns || 80);
|
|
189
|
+
const menuState = mode === "prompt" ? createSlashMenuState(state.session.commands) : null;
|
|
190
|
+
state.input.prefill = "";
|
|
191
|
+
state.input.session = { mode, prompt, placeholder, editor, menuState, resolve };
|
|
192
|
+
state.input.active = true;
|
|
193
|
+
cancelActiveInput = () => completeTtyInput(state.input.session, "", false);
|
|
194
|
+
output.redraw(true);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function handleTerminalInput(raw = "") {
|
|
199
|
+
const event = parseTerminalKey(raw);
|
|
200
|
+
if (!event) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (event.ctrl && event.name === "c") {
|
|
204
|
+
handleSigint();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (event.ctrl && !event.alt && !event.shift && event.name === "o") {
|
|
208
|
+
state.display.toolDetailsExpanded = !state.display.toolDetailsExpanded;
|
|
209
|
+
output.setToolsExpanded?.(state.display.toolDetailsExpanded);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (event.ctrl && !event.alt && !event.shift && event.name === "t") {
|
|
213
|
+
void cycleReasoningEffort();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const monitor = getTaskMonitor();
|
|
217
|
+
if (monitor?.isMonitoring()) {
|
|
218
|
+
monitor.handleInput(event);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (event.ctrl && event.name === "b") {
|
|
222
|
+
monitor?.enterMonitor();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const session = state.input.session;
|
|
226
|
+
if (!session) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (session.mode === "model") {
|
|
230
|
+
handleModelInput(session, event);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (session.mode === "theme") {
|
|
234
|
+
handleThemeInput(session, event);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (session.mode === "question") {
|
|
238
|
+
handleQuestionInput(session, event);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (session.mode === "sessions") {
|
|
242
|
+
handleSessionInput(session, event);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (session.mode === "team-blueprints") {
|
|
246
|
+
handleTeamBlueprintInput(session, event);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const key = event;
|
|
250
|
+
const matches = session.menuState ? syncSlashMenu(session) : [];
|
|
251
|
+
const menuKey = !key.ctrl && !key.alt && !key.shift && ["escape", "up", "down"].includes(key.name);
|
|
252
|
+
if (session.menuState && matches.length && menuKey && session.menuState.handleKey("", key)) {
|
|
253
|
+
output.redraw();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (session.mode === "prompt" && state.turn.active && !key.ctrl && !key.alt && !key.shift && key.name === "tab") {
|
|
257
|
+
queueTtyInput(session);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (session.mode === "prompt" && key.alt && !key.ctrl && !key.shift && key.name === "up" && state.input.pending.some((item) => item.mode === "follow_up")) {
|
|
261
|
+
void retrievePendingInput("follow_up", session);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (session.mode === "prompt" && key.alt && !key.ctrl && !key.shift && key.name === "down" && state.input.pending.some((item) => item.mode === "steering")) {
|
|
265
|
+
void retrievePendingInput("steering", session);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const result = session.editor.handleInput(key);
|
|
269
|
+
if (result === "submit") {
|
|
270
|
+
submitTtyInput(session);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (result) {
|
|
274
|
+
output.redraw();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function handleTerminalPaste(text) {
|
|
279
|
+
const monitor = getTaskMonitor();
|
|
280
|
+
if (monitor?.isMonitoring()) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const session = state.input.session;
|
|
284
|
+
if (!session || session.mode === "model" || session.mode === "theme" || session.mode === "sessions") {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (session.mode === "question" && !session.questionState.isEditing()) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
session.editor.handleInput({ kind: "paste", text });
|
|
291
|
+
output.redraw();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function handleModelInput(session, key) {
|
|
295
|
+
const modified = key.ctrl || key.alt || key.shift;
|
|
296
|
+
if (!modified && (key.name === "enter" || key.name === "return")) {
|
|
297
|
+
const model = session.modelState.selectedModel()?.name || "";
|
|
298
|
+
completeTtyInput(session, model, Boolean(model), "", model ? `/model set ${model}` : "");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (!modified && key.name === "escape") {
|
|
302
|
+
completeTtyInput(session, "", false);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!modified && session.modelState.handleKey(key)) output.redraw();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function askThemeMenu() {
|
|
309
|
+
return new Promise((resolve) => {
|
|
310
|
+
output.clearAssistantLineForInput();
|
|
311
|
+
const themeState = createThemeMenuState();
|
|
312
|
+
if (!themeState.items().length) {
|
|
313
|
+
resolve("");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const session = { mode: "theme", inputText: "/theme", themeState, resolve };
|
|
317
|
+
state.input.session = session;
|
|
318
|
+
state.input.active = true;
|
|
319
|
+
cancelActiveInput = () => completeTtyInput(session, "", false);
|
|
320
|
+
output.redraw(true);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function handleThemeInput(session, key) {
|
|
325
|
+
const modified = key.ctrl || key.alt || key.shift;
|
|
326
|
+
if (!modified && (key.name === "enter" || key.name === "return")) {
|
|
327
|
+
const theme = session.themeState.selectedTheme()?.name || "";
|
|
328
|
+
completeTtyInput(session, theme, Boolean(theme), "", theme ? `/theme ${theme}` : "");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!modified && key.name === "escape") {
|
|
332
|
+
completeTtyInput(session, "", false);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (!modified && session.themeState.handleKey(key)) output.redraw();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function submitTtyInput(session) {
|
|
339
|
+
if (session.menuState) syncSlashMenu(session);
|
|
340
|
+
const command = session.menuState?.selectedCommand();
|
|
341
|
+
const value = command ? `/${command.name}` : session.editor.input();
|
|
342
|
+
if (session.mode === "prompt") session.editor.addToHistory(value);
|
|
343
|
+
completeTtyInput(session, value, session.mode === "prompt" && !state.turn.active, session.mode === "line" ? "\n" : "");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function queueTtyInput(session) {
|
|
347
|
+
const value = session.editor.input();
|
|
348
|
+
if (!value.trim()) return;
|
|
349
|
+
session.editor.addToHistory(value);
|
|
350
|
+
completeTtyInput(session, "", false);
|
|
351
|
+
getTurnController().submitFollowUp(value);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function completeTtyInput(session, value, writeUser, lineText = "", displayValue = value) {
|
|
355
|
+
if (state.input.session !== session) return;
|
|
356
|
+
state.input.session = null;
|
|
357
|
+
state.input.active = false;
|
|
358
|
+
cancelActiveInput = null;
|
|
359
|
+
if (writeUser) {
|
|
360
|
+
output.writeUserInput(displayValue);
|
|
361
|
+
} else if (!output.terminalUi && lineText && String(value || "").trim()) {
|
|
362
|
+
process.stdout.write("\n");
|
|
363
|
+
}
|
|
364
|
+
session.resolve(value);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function askModelMenu(models, currentModel) {
|
|
368
|
+
return new Promise((resolve) => {
|
|
369
|
+
output.clearAssistantLineForInput();
|
|
370
|
+
const modelState = createModelMenuState(models, currentModel);
|
|
371
|
+
if (!modelState.items().length) {
|
|
372
|
+
resolve("");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const session = { mode: "model", inputText: "/model", modelState, resolve };
|
|
376
|
+
state.input.session = session;
|
|
377
|
+
state.input.active = true;
|
|
378
|
+
cancelActiveInput = () => completeTtyInput(session, "", false);
|
|
379
|
+
output.redraw(true);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function askEffortMenu(currentEffort) {
|
|
384
|
+
return new Promise((resolve) => {
|
|
385
|
+
output.clearAssistantLineForInput();
|
|
386
|
+
const modelState = createModelMenuState(REASONING_EFFORTS.slice(), currentEffort);
|
|
387
|
+
const session = { mode: "model", inputText: "/effort", modelState, resolve };
|
|
388
|
+
state.input.session = session;
|
|
389
|
+
state.input.active = true;
|
|
390
|
+
cancelActiveInput = () => completeTtyInput(session, "", false);
|
|
391
|
+
output.redraw(true);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function cycleReasoningEffort() {
|
|
396
|
+
const index = REASONING_EFFORTS.indexOf(String(state.session.info.reasoning_effort || "").toLowerCase());
|
|
397
|
+
const next = REASONING_EFFORTS[(index + 1 + REASONING_EFFORTS.length) % REASONING_EFFORTS.length];
|
|
398
|
+
try {
|
|
399
|
+
await request(runtimeMethods.modelEffortSet, { reasoning_effort: next });
|
|
400
|
+
state.session.info = { ...state.session.info, reasoning_effort: next };
|
|
401
|
+
output.writeError(`Reasoning effort: ${next}\n`);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
output.writeError(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function askQuestionMenu(event) {
|
|
408
|
+
return new Promise((resolve) => {
|
|
409
|
+
output.clearAssistantLineForInput();
|
|
410
|
+
const options = (Array.isArray(event.options) ? event.options : [])
|
|
411
|
+
.map((option) => ({ label: String(option?.label || "").trim(), description: String(option?.description || "").trim() }))
|
|
412
|
+
.filter((option) => option.label);
|
|
413
|
+
const questionState = createQuestionMenuState(options);
|
|
414
|
+
state.input.session = {
|
|
415
|
+
mode: "question",
|
|
416
|
+
question: String(event.question || "Input required"),
|
|
417
|
+
questionState,
|
|
418
|
+
editor: null,
|
|
419
|
+
resolve,
|
|
420
|
+
};
|
|
421
|
+
state.input.active = true;
|
|
422
|
+
cancelActiveInput = () => completeTtyInput(state.input.session, "", false);
|
|
423
|
+
output.redraw(true);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function askSessionMenu(options, sessions, currentIndex) {
|
|
428
|
+
return new Promise((resolve) => {
|
|
429
|
+
output.clearAssistantLineForInput();
|
|
430
|
+
const choiceState = createChoiceMenuState(options, options[currentIndex] || "");
|
|
431
|
+
const session = { mode: "sessions", inputText: "/sessions", choiceState, sessions, resolve };
|
|
432
|
+
state.input.session = session;
|
|
433
|
+
state.input.active = true;
|
|
434
|
+
cancelActiveInput = () => completeTtyInput(session, null, false);
|
|
435
|
+
output.redraw(true);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function askTeamBlueprint(blueprints) {
|
|
440
|
+
return new Promise((resolve) => {
|
|
441
|
+
output.clearAssistantLineForInput();
|
|
442
|
+
const items = (Array.isArray(blueprints) ? blueprints : []).map((item) => ({
|
|
443
|
+
id: String(item?.id || ""),
|
|
444
|
+
label: [item?.id, item?.name, item?.description].filter(Boolean).join(" · "),
|
|
445
|
+
})).filter((item) => item.id);
|
|
446
|
+
if (!items.length) {
|
|
447
|
+
resolve(null);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const choiceState = createChoiceMenuState(items.map((item) => item.label), items[0].label);
|
|
451
|
+
const session = { mode: "team-blueprints", inputText: "/team blueprint", choiceState, items, resolve };
|
|
452
|
+
state.input.session = session;
|
|
453
|
+
state.input.active = true;
|
|
454
|
+
cancelActiveInput = () => completeTtyInput(session, null, false);
|
|
455
|
+
output.redraw(true);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function handleQuestionInput(session, key) {
|
|
460
|
+
const modified = key.ctrl || key.alt || key.shift;
|
|
461
|
+
if (session.questionState.isEditing()) {
|
|
462
|
+
if (!modified && key.name === "escape") return completeTtyInput(session, "", false);
|
|
463
|
+
if (!modified && session.questionState.handleNavigation(key)) {
|
|
464
|
+
session.editor = null;
|
|
465
|
+
output.redraw();
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const result = session.editor.handleInput(key);
|
|
469
|
+
if (result === "submit") {
|
|
470
|
+
const answer = session.editor.input().trim();
|
|
471
|
+
if (answer) completeTtyInput(session, answer, false);
|
|
472
|
+
else output.redraw();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (result) output.redraw();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (!modified && (key.name === "enter" || key.name === "return" || key.name === "tab")) {
|
|
479
|
+
if (session.questionState.enterEditing()) {
|
|
480
|
+
session.editor = createLineEditor();
|
|
481
|
+
session.editor.setViewportWidth(process.stdout.columns || 80);
|
|
482
|
+
output.redraw();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (key.name === "tab") return;
|
|
486
|
+
completeTtyInput(session, session.questionState.selectedOption()?.label || "", false);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (!modified && key.name === "escape") return completeTtyInput(session, "", false);
|
|
490
|
+
if (!modified && session.questionState.handleNavigation(key)) {
|
|
491
|
+
session.editor = null;
|
|
492
|
+
output.redraw();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function handleSessionInput(session, key) {
|
|
497
|
+
const modified = key.ctrl || key.alt || key.shift;
|
|
498
|
+
if (!modified && (key.name === "enter" || key.name === "return")) {
|
|
499
|
+
completeTtyInput(session, session.sessions[session.choiceState.selectedIndex()] || null, false);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (!modified && key.name === "escape") return completeTtyInput(session, null, false);
|
|
503
|
+
if (!modified && session.choiceState.handleKey(key)) output.redraw();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function handleTeamBlueprintInput(session, key) {
|
|
507
|
+
const modified = key.ctrl || key.alt || key.shift;
|
|
508
|
+
if (!modified && (key.name === "enter" || key.name === "return")) {
|
|
509
|
+
completeTtyInput(session, session.items[session.choiceState.selectedIndex()] || null, false);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (!modified && key.name === "escape") return completeTtyInput(session, null, false);
|
|
513
|
+
if (!modified && session.choiceState.handleKey(key)) output.redraw();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function syncSlashMenu(session) {
|
|
517
|
+
session.menuState.setInput(session.editor.input());
|
|
518
|
+
return session.menuState.matches();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function applyInputPrefill(lineInput) {
|
|
522
|
+
if (!state.input.prefill) return;
|
|
523
|
+
const text = state.input.prefill;
|
|
524
|
+
state.input.prefill = "";
|
|
525
|
+
lineInput.write(text);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function promptValue(prompt) {
|
|
529
|
+
return typeof prompt === "function" ? prompt() : prompt;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
ask,
|
|
534
|
+
answerQuestion,
|
|
535
|
+
restoreInputText,
|
|
536
|
+
addPendingInput,
|
|
537
|
+
deliverQueuedInput,
|
|
538
|
+
clearPendingInputs,
|
|
539
|
+
handleTerminalInput,
|
|
540
|
+
handleTerminalPaste,
|
|
541
|
+
askModelMenu,
|
|
542
|
+
askEffortMenu,
|
|
543
|
+
askThemeMenu,
|
|
544
|
+
askSessionMenu,
|
|
545
|
+
askTeamBlueprint,
|
|
546
|
+
cancel: () => cancelActiveInput?.(),
|
|
547
|
+
};
|
|
548
|
+
}
|