@robota-sdk/agent-transport-tui 3.0.0-beta.63
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/LICENSE +21 -0
- package/dist/node/index.cjs +4041 -0
- package/dist/node/index.d.cts +62 -0
- package/dist/node/index.d.ts +62 -0
- package/dist/node/index.js +4008 -0
- package/package.json +62 -0
|
@@ -0,0 +1,4008 @@
|
|
|
1
|
+
// src/render.tsx
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
|
|
4
|
+
// src/App.tsx
|
|
5
|
+
import { useState as useState17, useEffect as useEffect6, useMemo as useMemo5 } from "react";
|
|
6
|
+
import { Box as Box21, Text as Text23, useApp as useApp2, useInput as useInput10 } from "ink";
|
|
7
|
+
import { listResumableSessionSummaries } from "@robota-sdk/agent-sdk";
|
|
8
|
+
import { createSystemMessage as createSystemMessage6, messageToHistoryEntry as messageToHistoryEntry6 } from "@robota-sdk/agent-core";
|
|
9
|
+
|
|
10
|
+
// src/hooks/useInteractiveSession.ts
|
|
11
|
+
import { useState, useRef, useCallback as useCallback2, useEffect } from "react";
|
|
12
|
+
import { InteractiveSession, CommandRegistry } from "@robota-sdk/agent-sdk";
|
|
13
|
+
import { createSystemMessage as createSystemMessage2, messageToHistoryEntry as messageToHistoryEntry2 } from "@robota-sdk/agent-core";
|
|
14
|
+
|
|
15
|
+
// src/tui-state-manager.ts
|
|
16
|
+
var MAX_RENDERED_MESSAGES = 100;
|
|
17
|
+
var STREAMING_DEBOUNCE_MS = 300;
|
|
18
|
+
function createDebouncedNotify(notify, ms) {
|
|
19
|
+
let timer = null;
|
|
20
|
+
return {
|
|
21
|
+
schedule() {
|
|
22
|
+
if (!timer) {
|
|
23
|
+
timer = setTimeout(() => {
|
|
24
|
+
timer = null;
|
|
25
|
+
notify();
|
|
26
|
+
}, ms);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
flush() {
|
|
30
|
+
if (timer) {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
timer = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
var TuiStateManager = class {
|
|
38
|
+
// ── Rendering state ───────────────────────────────────────────
|
|
39
|
+
history = [];
|
|
40
|
+
streamingText = "";
|
|
41
|
+
activeTools = [];
|
|
42
|
+
isThinking = false;
|
|
43
|
+
isAborting = false;
|
|
44
|
+
pendingPrompt = null;
|
|
45
|
+
contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
|
|
46
|
+
executionWorkspaceSnapshot = null;
|
|
47
|
+
selectedExecutionEntryId;
|
|
48
|
+
/** Called after any state change. React hook sets this to trigger re-render. */
|
|
49
|
+
onChange = null;
|
|
50
|
+
// ── Internal ──────────────────────────────────────────────────
|
|
51
|
+
streamBuf = "";
|
|
52
|
+
debouncedStreamNotify = createDebouncedNotify(() => this.notify(), STREAMING_DEBOUNCE_MS);
|
|
53
|
+
notify() {
|
|
54
|
+
this.onChange?.();
|
|
55
|
+
}
|
|
56
|
+
// ── Event handlers (InteractiveSession → state) ───────────────
|
|
57
|
+
onTextDelta = (delta) => {
|
|
58
|
+
this.streamBuf += delta;
|
|
59
|
+
this.streamingText = this.streamBuf;
|
|
60
|
+
this.debouncedStreamNotify.schedule();
|
|
61
|
+
};
|
|
62
|
+
onToolStart = (state) => {
|
|
63
|
+
this.activeTools = [...this.activeTools, state];
|
|
64
|
+
this.notify();
|
|
65
|
+
};
|
|
66
|
+
onToolEnd = (state) => {
|
|
67
|
+
const idx = this.activeTools.findLastIndex((t) => t.toolName === state.toolName && t.isRunning);
|
|
68
|
+
if (idx !== -1) {
|
|
69
|
+
const updated = [...this.activeTools];
|
|
70
|
+
updated[idx] = state;
|
|
71
|
+
this.activeTools = updated;
|
|
72
|
+
}
|
|
73
|
+
this.notify();
|
|
74
|
+
};
|
|
75
|
+
onThinking = (thinking) => {
|
|
76
|
+
this.isThinking = thinking;
|
|
77
|
+
if (thinking) {
|
|
78
|
+
this.debouncedStreamNotify.flush();
|
|
79
|
+
this.streamBuf = "";
|
|
80
|
+
this.streamingText = "";
|
|
81
|
+
this.activeTools = [];
|
|
82
|
+
} else {
|
|
83
|
+
this.isAborting = false;
|
|
84
|
+
}
|
|
85
|
+
this.notify();
|
|
86
|
+
};
|
|
87
|
+
onComplete = (result) => {
|
|
88
|
+
this.debouncedStreamNotify.flush();
|
|
89
|
+
this.streamBuf = "";
|
|
90
|
+
this.streamingText = "";
|
|
91
|
+
this.activeTools = [];
|
|
92
|
+
this.contextState = {
|
|
93
|
+
percentage: result.contextState.usedPercentage,
|
|
94
|
+
usedTokens: result.contextState.usedTokens,
|
|
95
|
+
maxTokens: result.contextState.maxTokens
|
|
96
|
+
};
|
|
97
|
+
this.notify();
|
|
98
|
+
};
|
|
99
|
+
onInterrupted = () => {
|
|
100
|
+
this.debouncedStreamNotify.flush();
|
|
101
|
+
this.streamBuf = "";
|
|
102
|
+
this.streamingText = "";
|
|
103
|
+
this.activeTools = [];
|
|
104
|
+
this.notify();
|
|
105
|
+
};
|
|
106
|
+
onError = () => {
|
|
107
|
+
this.debouncedStreamNotify.flush();
|
|
108
|
+
this.streamBuf = "";
|
|
109
|
+
this.streamingText = "";
|
|
110
|
+
this.activeTools = [];
|
|
111
|
+
this.notify();
|
|
112
|
+
};
|
|
113
|
+
onContextUpdate = (state) => {
|
|
114
|
+
this.setContextState({
|
|
115
|
+
percentage: state.usedPercentage,
|
|
116
|
+
usedTokens: state.usedTokens,
|
|
117
|
+
maxTokens: state.maxTokens
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
// ── State updates from external sources ───────────────────────
|
|
121
|
+
/** Sync history from InteractiveSession */
|
|
122
|
+
syncHistory(entries) {
|
|
123
|
+
if (entries.length === 0) return;
|
|
124
|
+
this.history = entries.length > MAX_RENDERED_MESSAGES ? entries.slice(-MAX_RENDERED_MESSAGES) : [...entries];
|
|
125
|
+
this.notify();
|
|
126
|
+
}
|
|
127
|
+
/** Add a single history entry */
|
|
128
|
+
addEntry(entry) {
|
|
129
|
+
const updated = [...this.history, entry];
|
|
130
|
+
this.history = updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
|
|
131
|
+
this.notify();
|
|
132
|
+
}
|
|
133
|
+
clearHistory() {
|
|
134
|
+
this.history = [];
|
|
135
|
+
this.debouncedStreamNotify.flush();
|
|
136
|
+
this.streamBuf = "";
|
|
137
|
+
this.streamingText = "";
|
|
138
|
+
this.activeTools = [];
|
|
139
|
+
this.notify();
|
|
140
|
+
}
|
|
141
|
+
/** Update pending prompt state */
|
|
142
|
+
setPendingPrompt(prompt) {
|
|
143
|
+
this.pendingPrompt = prompt;
|
|
144
|
+
this.notify();
|
|
145
|
+
}
|
|
146
|
+
/** Set aborting flag */
|
|
147
|
+
setAborting(aborting) {
|
|
148
|
+
this.isAborting = aborting;
|
|
149
|
+
this.notify();
|
|
150
|
+
}
|
|
151
|
+
/** Update context state */
|
|
152
|
+
setContextState(state) {
|
|
153
|
+
this.contextState = state;
|
|
154
|
+
this.notify();
|
|
155
|
+
}
|
|
156
|
+
syncExecutionWorkspaceSnapshot(snapshot) {
|
|
157
|
+
const currentSelection = this.selectedExecutionEntryId;
|
|
158
|
+
const hasCurrentSelection = currentSelection !== void 0 && snapshot.entries.some((entry) => entry.id === currentSelection);
|
|
159
|
+
const selectedExecutionEntryId = hasCurrentSelection ? currentSelection : snapshot.selectedEntryId ?? snapshot.entries[0]?.id;
|
|
160
|
+
this.executionWorkspaceSnapshot = {
|
|
161
|
+
...snapshot,
|
|
162
|
+
...selectedExecutionEntryId ? { selectedEntryId: selectedExecutionEntryId } : {}
|
|
163
|
+
};
|
|
164
|
+
this.selectedExecutionEntryId = selectedExecutionEntryId;
|
|
165
|
+
this.notify();
|
|
166
|
+
}
|
|
167
|
+
selectExecutionWorkspaceEntry(entryId) {
|
|
168
|
+
if (!this.executionWorkspaceSnapshot?.entries.some((entry) => entry.id === entryId)) return;
|
|
169
|
+
this.selectedExecutionEntryId = entryId;
|
|
170
|
+
this.executionWorkspaceSnapshot = {
|
|
171
|
+
...this.executionWorkspaceSnapshot,
|
|
172
|
+
selectedEntryId: entryId
|
|
173
|
+
};
|
|
174
|
+
this.notify();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/hooks/useSlashRouting.ts
|
|
179
|
+
import { useCallback } from "react";
|
|
180
|
+
import { createSystemMessage, messageToHistoryEntry } from "@robota-sdk/agent-core";
|
|
181
|
+
function useSlashRouting(interactiveSession, registry, manager, commandEffectQueue, reloadPluginCommandSource) {
|
|
182
|
+
return useCallback(
|
|
183
|
+
async (input) => {
|
|
184
|
+
if (!input.startsWith("/")) {
|
|
185
|
+
await interactiveSession.submit(input);
|
|
186
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const parts = input.slice(1).split(/\s+/);
|
|
190
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
191
|
+
const args = parts.slice(1).join(" ");
|
|
192
|
+
const result = await interactiveSession.executeCommand(cmd, args);
|
|
193
|
+
if (result) {
|
|
194
|
+
if (result.effects?.some((effect) => effect.type === "session-execution-started")) {
|
|
195
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
applySystemCommandResult(
|
|
199
|
+
result,
|
|
200
|
+
interactiveSession,
|
|
201
|
+
registry,
|
|
202
|
+
manager,
|
|
203
|
+
commandEffectQueue,
|
|
204
|
+
reloadPluginCommandSource
|
|
205
|
+
);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
manager.addEntry(
|
|
209
|
+
messageToHistoryEntry(
|
|
210
|
+
createSystemMessage(`Unknown command "/${cmd}". Type /help for help.`)
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
[interactiveSession, registry, manager, commandEffectQueue, reloadPluginCommandSource]
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
function applySystemCommandResult(result, interactiveSession, registry, manager, commandEffectQueue, reloadPluginCommandSource) {
|
|
218
|
+
const pendingEffects = applyImmediateCommandEffects(
|
|
219
|
+
result.effects,
|
|
220
|
+
registry,
|
|
221
|
+
manager,
|
|
222
|
+
reloadPluginCommandSource
|
|
223
|
+
);
|
|
224
|
+
manager.addEntry(messageToHistoryEntry(createSystemMessage(result.message)));
|
|
225
|
+
if (result.interaction !== void 0) {
|
|
226
|
+
commandEffectQueue.enqueueInteraction(result.interaction);
|
|
227
|
+
}
|
|
228
|
+
if (pendingEffects.length > 0) {
|
|
229
|
+
commandEffectQueue.enqueueEffects(pendingEffects);
|
|
230
|
+
}
|
|
231
|
+
if (interactiveSession.isInitialized) {
|
|
232
|
+
const ctx = interactiveSession.getContextState();
|
|
233
|
+
manager.setContextState({
|
|
234
|
+
percentage: ctx.usedPercentage,
|
|
235
|
+
usedTokens: ctx.usedTokens,
|
|
236
|
+
maxTokens: ctx.maxTokens
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function applyImmediateCommandEffects(effects, registry, manager, reloadPluginCommandSource) {
|
|
241
|
+
if (effects === void 0 || effects.length === 0) return [];
|
|
242
|
+
const pendingEffects = [];
|
|
243
|
+
for (const effect of effects) {
|
|
244
|
+
if (effect.type === "conversation-history-cleared") {
|
|
245
|
+
manager.clearHistory();
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (effect.type === "plugin-registry-reload-requested") {
|
|
249
|
+
reloadPluginCommandSource?.(registry);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
pendingEffects.push(effect);
|
|
253
|
+
}
|
|
254
|
+
return pendingEffects;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/hooks/command-effect-queue.ts
|
|
258
|
+
var CommandEffectQueue = class {
|
|
259
|
+
queue = [];
|
|
260
|
+
enqueueInteraction(interaction) {
|
|
261
|
+
this.queue.push({ type: "interaction", interaction });
|
|
262
|
+
}
|
|
263
|
+
enqueueEffects(effects) {
|
|
264
|
+
if (effects.length === 0) return;
|
|
265
|
+
this.queue.push({ type: "effects", effects: [...effects] });
|
|
266
|
+
}
|
|
267
|
+
drain() {
|
|
268
|
+
return this.queue.shift();
|
|
269
|
+
}
|
|
270
|
+
clear() {
|
|
271
|
+
this.queue.length = 0;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/hooks/useInteractiveSession.ts
|
|
276
|
+
var SESSION_INIT_POLL_MS = 200;
|
|
277
|
+
function applyCompactEventToManager(interactiveSession, manager) {
|
|
278
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
279
|
+
}
|
|
280
|
+
function applySkillActivationEventToManager(interactiveSession, manager) {
|
|
281
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
282
|
+
}
|
|
283
|
+
function syncExecutionWorkspaceFromSession(interactiveSession, manager) {
|
|
284
|
+
try {
|
|
285
|
+
manager.syncExecutionWorkspaceSnapshot(
|
|
286
|
+
interactiveSession.getExecutionWorkspaceSnapshot({
|
|
287
|
+
selectedEntryId: manager.selectedExecutionEntryId
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function initializeSession(props, permissionHandler) {
|
|
294
|
+
const interactiveSession = new InteractiveSession({
|
|
295
|
+
cwd: props.cwd,
|
|
296
|
+
provider: props.provider,
|
|
297
|
+
permissionMode: props.permissionMode,
|
|
298
|
+
maxTurns: props.maxTurns,
|
|
299
|
+
permissionHandler,
|
|
300
|
+
sessionStore: props.sessionStore,
|
|
301
|
+
resumeSessionId: props.resumeSessionId,
|
|
302
|
+
forkSession: props.forkSession,
|
|
303
|
+
sessionName: props.sessionName,
|
|
304
|
+
backgroundTaskRunners: props.backgroundTaskRunners,
|
|
305
|
+
subagentRunnerFactory: props.subagentRunnerFactory,
|
|
306
|
+
commandModules: props.commandModules,
|
|
307
|
+
commandHostAdapters: props.commandHostAdapters,
|
|
308
|
+
language: props.language
|
|
309
|
+
});
|
|
310
|
+
const registry = new CommandRegistry();
|
|
311
|
+
for (const module of props.commandModules ?? []) {
|
|
312
|
+
registry.addModule(module);
|
|
313
|
+
}
|
|
314
|
+
props.reloadPluginCommandSource?.(registry);
|
|
315
|
+
const manager = new TuiStateManager();
|
|
316
|
+
const commandEffectQueue = new CommandEffectQueue();
|
|
317
|
+
return { interactiveSession, registry, manager, commandEffectQueue };
|
|
318
|
+
}
|
|
319
|
+
function useInteractiveSession(props) {
|
|
320
|
+
const [, forceRender] = useState(0);
|
|
321
|
+
const [permissionRequest, setPermissionRequest] = useState(null);
|
|
322
|
+
const [isShuttingDown, setIsShuttingDown] = useState(false);
|
|
323
|
+
const permissionQueueRef = useRef([]);
|
|
324
|
+
const processingRef = useRef(false);
|
|
325
|
+
const processNextPermission = useCallback2(() => {
|
|
326
|
+
if (processingRef.current) return;
|
|
327
|
+
const next = permissionQueueRef.current[0];
|
|
328
|
+
if (!next) {
|
|
329
|
+
setPermissionRequest(null);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
processingRef.current = true;
|
|
333
|
+
setPermissionRequest({
|
|
334
|
+
toolName: next.toolName,
|
|
335
|
+
toolArgs: next.toolArgs,
|
|
336
|
+
resolve: (result) => {
|
|
337
|
+
permissionQueueRef.current.shift();
|
|
338
|
+
processingRef.current = false;
|
|
339
|
+
setPermissionRequest(null);
|
|
340
|
+
next.resolve(result);
|
|
341
|
+
setTimeout(() => processNextPermission(), 0);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}, []);
|
|
345
|
+
const permissionHandler = useCallback2(
|
|
346
|
+
(toolName, toolArgs) => new Promise((resolve) => {
|
|
347
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
348
|
+
processNextPermission();
|
|
349
|
+
}),
|
|
350
|
+
[processNextPermission]
|
|
351
|
+
);
|
|
352
|
+
const [initState] = useState(() => initializeSession(props, permissionHandler));
|
|
353
|
+
const { interactiveSession, registry, manager, commandEffectQueue } = initState;
|
|
354
|
+
manager.onChange = () => forceRender((n) => n + 1);
|
|
355
|
+
if (manager.history.length === 0) {
|
|
356
|
+
const restored = interactiveSession.getFullHistory();
|
|
357
|
+
if (restored.length > 0) {
|
|
358
|
+
manager.syncHistory(restored);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
if (!props.transportRegistry) return;
|
|
363
|
+
const reg = props.transportRegistry;
|
|
364
|
+
reg.startAll(interactiveSession).catch(() => void 0);
|
|
365
|
+
return () => {
|
|
366
|
+
reg.stopAll().catch(() => void 0);
|
|
367
|
+
};
|
|
368
|
+
}, [interactiveSession, props.transportRegistry]);
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
const onCompact = () => applyCompactEventToManager(interactiveSession, manager);
|
|
371
|
+
const onSkillActivation = () => applySkillActivationEventToManager(interactiveSession, manager);
|
|
372
|
+
const onExecutionWorkspaceEvent = (event) => manager.syncExecutionWorkspaceSnapshot(event.snapshot);
|
|
373
|
+
interactiveSession.on("text_delta", manager.onTextDelta);
|
|
374
|
+
interactiveSession.on("tool_start", manager.onToolStart);
|
|
375
|
+
interactiveSession.on("tool_end", manager.onToolEnd);
|
|
376
|
+
interactiveSession.on("thinking", manager.onThinking);
|
|
377
|
+
interactiveSession.on("complete", manager.onComplete);
|
|
378
|
+
interactiveSession.on("interrupted", manager.onInterrupted);
|
|
379
|
+
interactiveSession.on("error", manager.onError);
|
|
380
|
+
interactiveSession.on("context_update", manager.onContextUpdate);
|
|
381
|
+
interactiveSession.on("compact", onCompact);
|
|
382
|
+
interactiveSession.on("skill_activation", onSkillActivation);
|
|
383
|
+
interactiveSession.on("execution_workspace_event", onExecutionWorkspaceEvent);
|
|
384
|
+
const initCheck = setInterval(() => {
|
|
385
|
+
try {
|
|
386
|
+
const ctx = interactiveSession.getContextState();
|
|
387
|
+
manager.setContextState({
|
|
388
|
+
percentage: ctx.usedPercentage,
|
|
389
|
+
usedTokens: ctx.usedTokens,
|
|
390
|
+
maxTokens: ctx.maxTokens
|
|
391
|
+
});
|
|
392
|
+
const restored = interactiveSession.getFullHistory();
|
|
393
|
+
if (restored.length > 0) {
|
|
394
|
+
manager.syncHistory(restored);
|
|
395
|
+
}
|
|
396
|
+
syncExecutionWorkspaceFromSession(interactiveSession, manager);
|
|
397
|
+
clearInterval(initCheck);
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}, SESSION_INIT_POLL_MS);
|
|
401
|
+
return () => {
|
|
402
|
+
clearInterval(initCheck);
|
|
403
|
+
interactiveSession.off("text_delta", manager.onTextDelta);
|
|
404
|
+
interactiveSession.off("tool_start", manager.onToolStart);
|
|
405
|
+
interactiveSession.off("tool_end", manager.onToolEnd);
|
|
406
|
+
interactiveSession.off("thinking", manager.onThinking);
|
|
407
|
+
interactiveSession.off("complete", manager.onComplete);
|
|
408
|
+
interactiveSession.off("interrupted", manager.onInterrupted);
|
|
409
|
+
interactiveSession.off("error", manager.onError);
|
|
410
|
+
interactiveSession.off("context_update", manager.onContextUpdate);
|
|
411
|
+
interactiveSession.off("compact", onCompact);
|
|
412
|
+
interactiveSession.off("skill_activation", onSkillActivation);
|
|
413
|
+
interactiveSession.off("execution_workspace_event", onExecutionWorkspaceEvent);
|
|
414
|
+
};
|
|
415
|
+
}, [interactiveSession, manager]);
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
418
|
+
syncExecutionWorkspaceFromSession(interactiveSession, manager);
|
|
419
|
+
if (!manager.isThinking) {
|
|
420
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
421
|
+
}
|
|
422
|
+
}, [manager.isThinking, interactiveSession, manager]);
|
|
423
|
+
const handleSubmit = useSlashRouting(
|
|
424
|
+
interactiveSession,
|
|
425
|
+
registry,
|
|
426
|
+
manager,
|
|
427
|
+
commandEffectQueue,
|
|
428
|
+
props.reloadPluginCommandSource
|
|
429
|
+
);
|
|
430
|
+
const handleAbort = useCallback2(() => {
|
|
431
|
+
manager.setAborting(true);
|
|
432
|
+
interactiveSession.abort();
|
|
433
|
+
}, [interactiveSession, manager]);
|
|
434
|
+
const handleCancelQueue = useCallback2(() => {
|
|
435
|
+
interactiveSession.cancelQueue();
|
|
436
|
+
manager.setPendingPrompt(null);
|
|
437
|
+
}, [interactiveSession, manager]);
|
|
438
|
+
const handleShutdown = useCallback2(
|
|
439
|
+
async (reason = "prompt_input_exit") => {
|
|
440
|
+
if (isShuttingDown) return;
|
|
441
|
+
setIsShuttingDown(true);
|
|
442
|
+
manager.addEntry(messageToHistoryEntry2(createSystemMessage2("Shutting down...")));
|
|
443
|
+
await interactiveSession.shutdown({ reason, message: "CLI shutdown" });
|
|
444
|
+
},
|
|
445
|
+
[interactiveSession, manager, isShuttingDown]
|
|
446
|
+
);
|
|
447
|
+
const selectExecutionWorkspaceEntry = useCallback2(
|
|
448
|
+
(entryId) => manager.selectExecutionWorkspaceEntry(entryId),
|
|
449
|
+
[manager]
|
|
450
|
+
);
|
|
451
|
+
const readExecutionWorkspaceDetail = useCallback2(
|
|
452
|
+
(entryId) => interactiveSession.readExecutionWorkspaceDetail(entryId),
|
|
453
|
+
[interactiveSession]
|
|
454
|
+
);
|
|
455
|
+
return {
|
|
456
|
+
interactiveSession,
|
|
457
|
+
registry,
|
|
458
|
+
commandEffectQueue,
|
|
459
|
+
history: manager.history,
|
|
460
|
+
addEntry: (entry) => manager.addEntry(entry),
|
|
461
|
+
streamingText: manager.streamingText,
|
|
462
|
+
activeTools: manager.activeTools,
|
|
463
|
+
isThinking: manager.isThinking,
|
|
464
|
+
isAborting: manager.isAborting,
|
|
465
|
+
isShuttingDown,
|
|
466
|
+
pendingPrompt: manager.pendingPrompt,
|
|
467
|
+
executionWorkspaceSnapshot: manager.executionWorkspaceSnapshot,
|
|
468
|
+
selectedExecutionEntryId: manager.selectedExecutionEntryId,
|
|
469
|
+
permissionRequest,
|
|
470
|
+
contextState: manager.contextState,
|
|
471
|
+
handleSubmit,
|
|
472
|
+
handleAbort,
|
|
473
|
+
handleCancelQueue,
|
|
474
|
+
handleShutdown,
|
|
475
|
+
selectExecutionWorkspaceEntry,
|
|
476
|
+
readExecutionWorkspaceDetail
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/hooks/usePluginCallbacks.ts
|
|
481
|
+
import { useMemo } from "react";
|
|
482
|
+
function createNoOpPluginAdapter() {
|
|
483
|
+
return {
|
|
484
|
+
listInstalled: async () => [],
|
|
485
|
+
listAvailablePlugins: async () => [],
|
|
486
|
+
install: async () => void 0,
|
|
487
|
+
uninstall: async () => void 0,
|
|
488
|
+
enable: async () => void 0,
|
|
489
|
+
disable: async () => void 0,
|
|
490
|
+
marketplaceAdd: async () => "",
|
|
491
|
+
marketplaceRemove: async () => void 0,
|
|
492
|
+
marketplaceUpdate: async () => void 0,
|
|
493
|
+
marketplaceList: async () => [],
|
|
494
|
+
reloadPlugins: async () => ({ loadedPluginCount: 0 })
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function usePluginCallbacks(_cwd) {
|
|
498
|
+
return useMemo(() => createNoOpPluginAdapter(), []);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/hooks/useSideEffects.ts
|
|
502
|
+
import { useState as useState2, useRef as useRef2, useCallback as useCallback3 } from "react";
|
|
503
|
+
import { useApp } from "ink";
|
|
504
|
+
import { createSystemMessage as createSystemMessage5, messageToHistoryEntry as messageToHistoryEntry5 } from "@robota-sdk/agent-core";
|
|
505
|
+
|
|
506
|
+
// src/hooks/command-effect-handler.ts
|
|
507
|
+
import { isStatusLineCommandSettingsPatch } from "@robota-sdk/agent-sdk";
|
|
508
|
+
import { createSystemMessage as createSystemMessage3, messageToHistoryEntry as messageToHistoryEntry3 } from "@robota-sdk/agent-core";
|
|
509
|
+
function applyCommandEffects(effects, deps) {
|
|
510
|
+
for (const effect of effects) {
|
|
511
|
+
if (effect.type === "model-change-requested") {
|
|
512
|
+
deps.requestModelChange(effect.modelId);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
if (effect.type === "language-change-requested") {
|
|
516
|
+
applyLanguageEffect(effect.language, deps);
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
if (effect.type === "settings-reset-requested") {
|
|
520
|
+
applySettingsResetEffect(deps);
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
if (effect.type === "session-exit-requested") {
|
|
524
|
+
deps.requestShutdown(
|
|
525
|
+
effect.reason ?? "prompt_input_exit",
|
|
526
|
+
effect.message ?? "User requested exit"
|
|
527
|
+
);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
if (effect.type === "session-restart-requested") {
|
|
531
|
+
deps.requestShutdown(effect.reason, effect.message);
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
if (effect.type === "plugin-tui-requested") {
|
|
535
|
+
deps.openPluginTUI();
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
if (effect.type === "settings-tui-requested") {
|
|
539
|
+
deps.openTransportTUI();
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
if (effect.type === "session-picker-requested") {
|
|
543
|
+
deps.openSessionPicker();
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
if (effect.type === "session-renamed") {
|
|
547
|
+
deps.renameSession(effect.name);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
if (effect.type === "statusline-settings-patch") {
|
|
551
|
+
if (isStatusLineCommandSettingsPatch(effect.patch)) {
|
|
552
|
+
return deps.applyStatusLinePatch(effect.patch);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
function applyLanguageEffect(language, deps) {
|
|
559
|
+
const settingsPath = deps.cliAdapter.getUserSettingsPath();
|
|
560
|
+
const settings = deps.cliAdapter.readSettings(settingsPath);
|
|
561
|
+
settings.language = language;
|
|
562
|
+
deps.cliAdapter.writeSettings(settingsPath, settings);
|
|
563
|
+
deps.addEntry(
|
|
564
|
+
messageToHistoryEntry3(createSystemMessage3(`Language set to "${language}". Restarting...`))
|
|
565
|
+
);
|
|
566
|
+
deps.requestShutdown("other", "Language change restart");
|
|
567
|
+
}
|
|
568
|
+
function applySettingsResetEffect(deps) {
|
|
569
|
+
const settingsPath = deps.cliAdapter.getUserSettingsPath();
|
|
570
|
+
if (deps.cliAdapter.deleteSettings(settingsPath)) {
|
|
571
|
+
deps.addEntry(
|
|
572
|
+
messageToHistoryEntry3(createSystemMessage3(`Deleted ${settingsPath}. Exiting...`))
|
|
573
|
+
);
|
|
574
|
+
} else {
|
|
575
|
+
deps.addEntry(messageToHistoryEntry3(createSystemMessage3("No user settings found.")));
|
|
576
|
+
}
|
|
577
|
+
deps.requestShutdown("other", "Reset settings restart");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/tui-cli-adapter-context.tsx
|
|
581
|
+
import { createContext, useContext } from "react";
|
|
582
|
+
var TuiCliAdapterContext = createContext(null);
|
|
583
|
+
var TuiCliAdapterProvider = TuiCliAdapterContext.Provider;
|
|
584
|
+
function useTuiCliAdapter() {
|
|
585
|
+
const adapter = useContext(TuiCliAdapterContext);
|
|
586
|
+
if (!adapter) throw new Error("TuiCliAdapterContext not provided");
|
|
587
|
+
return adapter;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/hooks/model-change-side-effect.ts
|
|
591
|
+
import {
|
|
592
|
+
createSystemMessage as createSystemMessage4,
|
|
593
|
+
getModelName,
|
|
594
|
+
messageToHistoryEntry as messageToHistoryEntry4
|
|
595
|
+
} from "@robota-sdk/agent-core";
|
|
596
|
+
function formatModelChangeConfirmationMessage(modelId) {
|
|
597
|
+
return `Change model to ${getModelName(modelId)}? This will exit the current session so the next session uses it.`;
|
|
598
|
+
}
|
|
599
|
+
function formatModelChangeExitMessage(modelId) {
|
|
600
|
+
return `Model changed to ${getModelName(modelId)}. Exiting so the next session uses it.`;
|
|
601
|
+
}
|
|
602
|
+
function applyConfirmedModelChange(deps) {
|
|
603
|
+
const applyModelChange = deps.applyModelChange;
|
|
604
|
+
if (!applyModelChange) {
|
|
605
|
+
deps.addEntry(
|
|
606
|
+
messageToHistoryEntry4(createSystemMessage4("Model change unavailable: no adapter provided."))
|
|
607
|
+
);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const options = deps.providerOverride !== void 0 ? { providerOverride: deps.providerOverride } : void 0;
|
|
611
|
+
try {
|
|
612
|
+
applyModelChange(deps.cwd, deps.modelId, options);
|
|
613
|
+
deps.addEntry(
|
|
614
|
+
messageToHistoryEntry4(createSystemMessage4(formatModelChangeExitMessage(deps.modelId)))
|
|
615
|
+
);
|
|
616
|
+
deps.requestShutdown("other", "Model change applied");
|
|
617
|
+
} catch (error) {
|
|
618
|
+
deps.addEntry(
|
|
619
|
+
messageToHistoryEntry4(
|
|
620
|
+
createSystemMessage4(`Failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
621
|
+
)
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function addModelChangeCancelledMessage(addEntry) {
|
|
626
|
+
addEntry(messageToHistoryEntry4(createSystemMessage4("Model change cancelled.")));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/hooks/useSideEffects.ts
|
|
630
|
+
var EXIT_DELAY_MS = 500;
|
|
631
|
+
function useSideEffects({
|
|
632
|
+
cwd,
|
|
633
|
+
providerOverride,
|
|
634
|
+
interactiveSession,
|
|
635
|
+
commandEffectQueue,
|
|
636
|
+
addEntry,
|
|
637
|
+
baseHandleSubmit,
|
|
638
|
+
setSessionName,
|
|
639
|
+
setStatusLineSettings,
|
|
640
|
+
showSessionPickerOnStart
|
|
641
|
+
}) {
|
|
642
|
+
const { exit } = useApp();
|
|
643
|
+
const cliAdapter = useTuiCliAdapter();
|
|
644
|
+
const [pendingModelId, setPendingModelId] = useState2(null);
|
|
645
|
+
const pendingModelChangeRef = useRef2(null);
|
|
646
|
+
const [pendingInteractionPrompt, setPendingInteractionPrompt] = useState2(null);
|
|
647
|
+
const commandInteractionRef = useRef2(null);
|
|
648
|
+
const [showPluginTUI, setShowPluginTUI] = useState2(false);
|
|
649
|
+
const [showSessionPicker, setShowSessionPicker] = useState2(showSessionPickerOnStart ?? false);
|
|
650
|
+
const [showTransportTUI, setShowTransportTUI] = useState2(false);
|
|
651
|
+
const requestShutdown = useCallback3(
|
|
652
|
+
(reason, message) => {
|
|
653
|
+
addEntry(messageToHistoryEntry5(createSystemMessage5("Shutting down...")));
|
|
654
|
+
setTimeout(() => {
|
|
655
|
+
void interactiveSession.shutdown({ reason, message }).finally(() => exit());
|
|
656
|
+
}, EXIT_DELAY_MS);
|
|
657
|
+
},
|
|
658
|
+
[interactiveSession, addEntry, exit]
|
|
659
|
+
);
|
|
660
|
+
const applyEffects = useCallback3(
|
|
661
|
+
(effects) => applyCommandEffects(effects, {
|
|
662
|
+
addEntry,
|
|
663
|
+
requestShutdown,
|
|
664
|
+
requestModelChange: (modelId) => {
|
|
665
|
+
pendingModelChangeRef.current = modelId;
|
|
666
|
+
setPendingModelId(modelId);
|
|
667
|
+
},
|
|
668
|
+
openPluginTUI: () => setShowPluginTUI(true),
|
|
669
|
+
openSessionPicker: () => setShowSessionPicker(true),
|
|
670
|
+
openTransportTUI: () => setShowTransportTUI(true),
|
|
671
|
+
renameSession: (name) => {
|
|
672
|
+
interactiveSession.setName(name);
|
|
673
|
+
setSessionName(name);
|
|
674
|
+
},
|
|
675
|
+
applyStatusLinePatch: (patch) => {
|
|
676
|
+
setStatusLineSettings(
|
|
677
|
+
cliAdapter.applyStatusLineSettings(cliAdapter.getUserSettingsPath(), patch)
|
|
678
|
+
);
|
|
679
|
+
return true;
|
|
680
|
+
},
|
|
681
|
+
cliAdapter
|
|
682
|
+
}),
|
|
683
|
+
[
|
|
684
|
+
addEntry,
|
|
685
|
+
cliAdapter,
|
|
686
|
+
interactiveSession,
|
|
687
|
+
requestShutdown,
|
|
688
|
+
setSessionName,
|
|
689
|
+
setStatusLineSettings
|
|
690
|
+
]
|
|
691
|
+
);
|
|
692
|
+
const applyCommandResult = useCallback3(
|
|
693
|
+
(result) => {
|
|
694
|
+
if (result.message.length > 0) {
|
|
695
|
+
addEntry(messageToHistoryEntry5(createSystemMessage5(result.message)));
|
|
696
|
+
}
|
|
697
|
+
if (result.interaction !== void 0) {
|
|
698
|
+
commandInteractionRef.current = result.interaction;
|
|
699
|
+
setPendingInteractionPrompt(result.interaction.prompt);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
commandInteractionRef.current = null;
|
|
703
|
+
setPendingInteractionPrompt(null);
|
|
704
|
+
if (result.effects !== void 0 && result.effects.length > 0) {
|
|
705
|
+
applyEffects(result.effects);
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
[addEntry, applyEffects]
|
|
709
|
+
);
|
|
710
|
+
const applyQueuedCommandState = useCallback3(() => {
|
|
711
|
+
const queued = commandEffectQueue.drain();
|
|
712
|
+
if (queued === void 0) {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
if (queued.type === "interaction") {
|
|
716
|
+
const { interaction } = queued;
|
|
717
|
+
commandInteractionRef.current = interaction;
|
|
718
|
+
setPendingInteractionPrompt(interaction.prompt);
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return applyEffects(queued.effects);
|
|
722
|
+
}, [applyEffects, commandEffectQueue]);
|
|
723
|
+
const handleSubmit = useCallback3(
|
|
724
|
+
async (input) => {
|
|
725
|
+
await baseHandleSubmit(input);
|
|
726
|
+
applyQueuedCommandState();
|
|
727
|
+
},
|
|
728
|
+
[baseHandleSubmit, applyQueuedCommandState]
|
|
729
|
+
);
|
|
730
|
+
const handleModelConfirm = useCallback3(
|
|
731
|
+
(index) => {
|
|
732
|
+
const modelId = pendingModelChangeRef.current;
|
|
733
|
+
setPendingModelId(null);
|
|
734
|
+
pendingModelChangeRef.current = null;
|
|
735
|
+
if (index === 0 && modelId) {
|
|
736
|
+
applyConfirmedModelChange({
|
|
737
|
+
cwd,
|
|
738
|
+
modelId,
|
|
739
|
+
providerOverride,
|
|
740
|
+
addEntry,
|
|
741
|
+
requestShutdown,
|
|
742
|
+
applyModelChange: (c, m, opts) => {
|
|
743
|
+
cliAdapter.applyActiveModelChange(c, m, opts);
|
|
744
|
+
return { applied: true };
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
} else {
|
|
748
|
+
addModelChangeCancelledMessage(addEntry);
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
[cwd, providerOverride, addEntry, cliAdapter, requestShutdown]
|
|
752
|
+
);
|
|
753
|
+
const handleInteractionSubmit = useCallback3(
|
|
754
|
+
async (value) => {
|
|
755
|
+
const interaction = commandInteractionRef.current;
|
|
756
|
+
if (interaction === null) {
|
|
757
|
+
setPendingInteractionPrompt(null);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const result = await interaction.submit(value);
|
|
762
|
+
applyCommandResult(result);
|
|
763
|
+
} catch (err) {
|
|
764
|
+
commandInteractionRef.current = null;
|
|
765
|
+
setPendingInteractionPrompt(null);
|
|
766
|
+
addEntry(
|
|
767
|
+
messageToHistoryEntry5(
|
|
768
|
+
createSystemMessage5(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
769
|
+
)
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
[addEntry, applyCommandResult]
|
|
774
|
+
);
|
|
775
|
+
const handleInteractionCancel = useCallback3(() => {
|
|
776
|
+
const interaction = commandInteractionRef.current;
|
|
777
|
+
commandInteractionRef.current = null;
|
|
778
|
+
setPendingInteractionPrompt(null);
|
|
779
|
+
if (interaction?.cancel === void 0) {
|
|
780
|
+
addEntry(messageToHistoryEntry5(createSystemMessage5("Command interaction cancelled.")));
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
Promise.resolve(interaction.cancel()).then((result) => applyCommandResult(result)).catch((err) => {
|
|
784
|
+
addEntry(
|
|
785
|
+
messageToHistoryEntry5(
|
|
786
|
+
createSystemMessage5(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
787
|
+
)
|
|
788
|
+
);
|
|
789
|
+
});
|
|
790
|
+
}, [addEntry, applyCommandResult]);
|
|
791
|
+
return {
|
|
792
|
+
handleSubmit,
|
|
793
|
+
pendingModelId,
|
|
794
|
+
pendingInteractionPrompt,
|
|
795
|
+
showPluginTUI,
|
|
796
|
+
showSessionPicker,
|
|
797
|
+
showTransportTUI,
|
|
798
|
+
setPendingModelId,
|
|
799
|
+
setShowPluginTUI,
|
|
800
|
+
setShowSessionPicker,
|
|
801
|
+
setShowTransportTUI,
|
|
802
|
+
handleModelConfirm,
|
|
803
|
+
handleInteractionSubmit,
|
|
804
|
+
handleInteractionCancel
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// src/hooks/useStatusLineSettings.ts
|
|
809
|
+
import { useState as useState3 } from "react";
|
|
810
|
+
import { DEFAULT_STATUS_LINE_COMMAND_SETTINGS } from "@robota-sdk/agent-sdk";
|
|
811
|
+
function readStatusLineSettings(settings) {
|
|
812
|
+
const defaults = { ...DEFAULT_STATUS_LINE_COMMAND_SETTINGS };
|
|
813
|
+
const raw = settings.statusline;
|
|
814
|
+
if (!isRecord(raw)) {
|
|
815
|
+
return defaults;
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
enabled: typeof raw.enabled === "boolean" ? raw.enabled : defaults.enabled,
|
|
819
|
+
gitBranch: typeof raw.gitBranch === "boolean" ? raw.gitBranch : defaults.gitBranch
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function isRecord(value) {
|
|
823
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
824
|
+
}
|
|
825
|
+
function useStatusLineSettings() {
|
|
826
|
+
const cliAdapter = useTuiCliAdapter();
|
|
827
|
+
return useState3(
|
|
828
|
+
() => readStatusLineSettings(cliAdapter.readSettings(cliAdapter.getUserSettingsPath()))
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/MessageList.tsx
|
|
833
|
+
import React from "react";
|
|
834
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
835
|
+
import { isToolMessage, isAssistantMessage } from "@robota-sdk/agent-core";
|
|
836
|
+
|
|
837
|
+
// src/render-markdown.ts
|
|
838
|
+
import { marked } from "marked";
|
|
839
|
+
import TerminalRenderer from "marked-terminal";
|
|
840
|
+
var ANSI_LIGHT_RED = "\x1B[38;5;210m";
|
|
841
|
+
var ANSI_LIGHT_GREEN = "\x1B[38;5;120m";
|
|
842
|
+
var ANSI_CYAN = "\x1B[36m";
|
|
843
|
+
var ANSI_DIM = "\x1B[2m";
|
|
844
|
+
var ANSI_DARK_RED_BACKGROUND = "\x1B[48;5;52m";
|
|
845
|
+
var ANSI_DARK_GREEN_BACKGROUND = "\x1B[48;5;22m";
|
|
846
|
+
var ANSI_RESET = "\x1B[0m";
|
|
847
|
+
var CODE_BLOCK_INDENT = " ";
|
|
848
|
+
var ZERO_COLOR = "0";
|
|
849
|
+
var TerminalRendererConstructor = TerminalRenderer;
|
|
850
|
+
function shouldUseColor(option) {
|
|
851
|
+
if (option !== void 0) {
|
|
852
|
+
return option;
|
|
853
|
+
}
|
|
854
|
+
if (process.env.NO_COLOR || process.env.FORCE_COLOR === ZERO_COLOR) {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
if (process.env.FORCE_COLOR) {
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
return Boolean(process.stdout.isTTY);
|
|
861
|
+
}
|
|
862
|
+
function isDiffLanguage(language) {
|
|
863
|
+
return language?.trim().toLowerCase() === "diff";
|
|
864
|
+
}
|
|
865
|
+
function styleAddedOrRemovedDiffRow(line, rowWidth, color) {
|
|
866
|
+
const row = `${CODE_BLOCK_INDENT}${line}`.padEnd(rowWidth);
|
|
867
|
+
if (!color) {
|
|
868
|
+
return row.trimEnd();
|
|
869
|
+
}
|
|
870
|
+
if (line.startsWith("+")) {
|
|
871
|
+
return `${ANSI_DARK_GREEN_BACKGROUND}${ANSI_LIGHT_GREEN}${row}${ANSI_RESET}`;
|
|
872
|
+
}
|
|
873
|
+
if (line.startsWith("-")) {
|
|
874
|
+
return `${ANSI_DARK_RED_BACKGROUND}${ANSI_LIGHT_RED}${row}${ANSI_RESET}`;
|
|
875
|
+
}
|
|
876
|
+
return row.trimEnd();
|
|
877
|
+
}
|
|
878
|
+
function colorizeDiffLine(line, color, rowWidth) {
|
|
879
|
+
if (line.startsWith("+") || line.startsWith("-")) {
|
|
880
|
+
return styleAddedOrRemovedDiffRow(line, rowWidth, color);
|
|
881
|
+
}
|
|
882
|
+
const row = `${CODE_BLOCK_INDENT}${line}`;
|
|
883
|
+
if (!color) {
|
|
884
|
+
return row;
|
|
885
|
+
}
|
|
886
|
+
if (line.startsWith("@@")) {
|
|
887
|
+
return `${ANSI_CYAN}${row}${ANSI_RESET}`;
|
|
888
|
+
}
|
|
889
|
+
if (line.startsWith("diff ") || line.startsWith("index ")) {
|
|
890
|
+
return `${ANSI_DIM}${row}${ANSI_RESET}`;
|
|
891
|
+
}
|
|
892
|
+
return row;
|
|
893
|
+
}
|
|
894
|
+
function resolveDiffRowWidth(lines, requestedWidth) {
|
|
895
|
+
const minimumWidth = lines.reduce(
|
|
896
|
+
(maxWidth, line) => Math.max(maxWidth, CODE_BLOCK_INDENT.length + line.length),
|
|
897
|
+
0
|
|
898
|
+
);
|
|
899
|
+
if (requestedWidth === void 0) {
|
|
900
|
+
return minimumWidth;
|
|
901
|
+
}
|
|
902
|
+
return Math.max(minimumWidth, requestedWidth);
|
|
903
|
+
}
|
|
904
|
+
function renderDiffCodeBlock(code, color, codeBlockWidth) {
|
|
905
|
+
const lines = code.split("\n");
|
|
906
|
+
const rowWidth = resolveDiffRowWidth(lines, codeBlockWidth);
|
|
907
|
+
const body = lines.map((line) => colorizeDiffLine(line, color, rowWidth)).join("\n");
|
|
908
|
+
return `${body}
|
|
909
|
+
|
|
910
|
+
`;
|
|
911
|
+
}
|
|
912
|
+
function createTerminalRenderer(color, codeBlockWidth) {
|
|
913
|
+
const renderer = new TerminalRendererConstructor(void 0, { ignoreIllegals: true });
|
|
914
|
+
const renderCode = renderer.code.bind(renderer);
|
|
915
|
+
renderer.code = (code, language, escaped) => {
|
|
916
|
+
if (isDiffLanguage(language)) {
|
|
917
|
+
return renderDiffCodeBlock(code, color, codeBlockWidth);
|
|
918
|
+
}
|
|
919
|
+
return renderCode(code, language, escaped);
|
|
920
|
+
};
|
|
921
|
+
return renderer;
|
|
922
|
+
}
|
|
923
|
+
function renderMarkdown(md, options = {}) {
|
|
924
|
+
const result = marked.parse(md, {
|
|
925
|
+
renderer: createTerminalRenderer(shouldUseColor(options.color), options.codeBlockWidth)
|
|
926
|
+
});
|
|
927
|
+
return typeof result === "string" ? result.trimEnd() : md;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/ToolDiffBlock.tsx
|
|
931
|
+
import { Box, Text } from "ink";
|
|
932
|
+
|
|
933
|
+
// src/utils/tool-diff-summary.ts
|
|
934
|
+
var MAX_DIFF_LINES = 12;
|
|
935
|
+
var TRUNCATED_SHOW = 10;
|
|
936
|
+
function buildToolDiffSummary(input) {
|
|
937
|
+
const visibleLines = input.lines.length > MAX_DIFF_LINES ? selectVisibleDiffLines(input.lines) : input.lines;
|
|
938
|
+
const lineNumberWidth = Math.max(...visibleLines.map((line) => line.lineNumber), 0).toString().length;
|
|
939
|
+
const body = visibleLines.map((line) => formatDiffLine(line, lineNumberWidth));
|
|
940
|
+
const truncated = input.lines.length > MAX_DIFF_LINES;
|
|
941
|
+
return {
|
|
942
|
+
file: input.file,
|
|
943
|
+
markdown: ["```diff", ...body, "```"].join("\n"),
|
|
944
|
+
truncated,
|
|
945
|
+
remainingLineCount: truncated ? input.lines.length - visibleLines.length : 0
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function formatDiffLine(line, lineNumberWidth) {
|
|
949
|
+
if (line.type === "hunk") return line.text;
|
|
950
|
+
const lineNumber = line.lineNumber.toString().padStart(lineNumberWidth, " ");
|
|
951
|
+
if (line.type === "remove") return `- ${lineNumber} | ${line.text}`;
|
|
952
|
+
if (line.type === "add") return `+ ${lineNumber} | ${line.text}`;
|
|
953
|
+
return ` ${lineNumber} | ${line.text}`;
|
|
954
|
+
}
|
|
955
|
+
function selectVisibleDiffLines(lines) {
|
|
956
|
+
const groups = groupByHunk(lines);
|
|
957
|
+
const visible = [];
|
|
958
|
+
for (const group of groups) {
|
|
959
|
+
if (visible.length === 0 && group.length > TRUNCATED_SHOW) {
|
|
960
|
+
return group.slice(0, TRUNCATED_SHOW);
|
|
961
|
+
}
|
|
962
|
+
if (visible.length + group.length > TRUNCATED_SHOW) break;
|
|
963
|
+
visible.push(...group);
|
|
964
|
+
}
|
|
965
|
+
return visible.length > 0 ? visible : lines.slice(0, TRUNCATED_SHOW);
|
|
966
|
+
}
|
|
967
|
+
function groupByHunk(lines) {
|
|
968
|
+
const groups = [];
|
|
969
|
+
let current = [];
|
|
970
|
+
for (const line of lines) {
|
|
971
|
+
if (line.type === "hunk" && current.length > 0) {
|
|
972
|
+
groups.push(current);
|
|
973
|
+
current = [line];
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
current.push(line);
|
|
977
|
+
}
|
|
978
|
+
if (current.length > 0) {
|
|
979
|
+
groups.push(current);
|
|
980
|
+
}
|
|
981
|
+
return groups;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/ToolDiffBlock.tsx
|
|
985
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
986
|
+
function ToolDiffBlock({ file, lines }) {
|
|
987
|
+
const summary = buildToolDiffSummary({ file, lines });
|
|
988
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
989
|
+
summary.file && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
|
|
990
|
+
"\u2502 ",
|
|
991
|
+
summary.file
|
|
992
|
+
] }),
|
|
993
|
+
/* @__PURE__ */ jsx(Text, { children: renderMarkdown(summary.markdown) }),
|
|
994
|
+
summary.truncated && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
|
|
995
|
+
"\u2502 ... and ",
|
|
996
|
+
summary.remainingLineCount,
|
|
997
|
+
" more lines"
|
|
998
|
+
] })
|
|
999
|
+
] });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/UsageSummaryEntry.tsx
|
|
1003
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1004
|
+
import { formatTokenCount } from "@robota-sdk/agent-core";
|
|
1005
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1006
|
+
var TOKEN_COMPACT_THRESHOLD = 1e3;
|
|
1007
|
+
function UsageSummaryEntry({ entry }) {
|
|
1008
|
+
const usage = entry.data;
|
|
1009
|
+
if (!usage) return /* @__PURE__ */ jsx2(Fragment, {});
|
|
1010
|
+
const prompt = usage.promptTokens !== void 0 ? formatUsageTokenCount(usage.promptTokens) : "?";
|
|
1011
|
+
const completion = usage.completionTokens !== void 0 ? formatUsageTokenCount(usage.completionTokens) : "?";
|
|
1012
|
+
const total = formatUsageTokenCount(usage.totalTokens);
|
|
1013
|
+
const context = `${Math.round(usage.contextUsedPercentage)}% (${formatTokenCount(
|
|
1014
|
+
usage.contextUsedTokens
|
|
1015
|
+
)}/${formatTokenCount(usage.contextMaxTokens)})`;
|
|
1016
|
+
const costLabel = usage.costStatus === "unknown" ? "cost unknown" : `cost ${usage.costStatus}`;
|
|
1017
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1018
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
|
|
1019
|
+
"Usage:",
|
|
1020
|
+
" "
|
|
1021
|
+
] }),
|
|
1022
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1023
|
+
usage.kind,
|
|
1024
|
+
" ",
|
|
1025
|
+
total,
|
|
1026
|
+
" tokens (in ",
|
|
1027
|
+
prompt,
|
|
1028
|
+
" / out ",
|
|
1029
|
+
completion,
|
|
1030
|
+
") \xB7 Context ",
|
|
1031
|
+
context,
|
|
1032
|
+
" \xB7",
|
|
1033
|
+
" ",
|
|
1034
|
+
costLabel
|
|
1035
|
+
] })
|
|
1036
|
+
] }) });
|
|
1037
|
+
}
|
|
1038
|
+
function formatUsageTokenCount(tokens) {
|
|
1039
|
+
return tokens < TOKEN_COMPACT_THRESHOLD ? tokens.toLocaleString() : formatTokenCount(tokens);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/command-output-summary.ts
|
|
1043
|
+
var MAX_PREVIEW_LINES = 4;
|
|
1044
|
+
var SUCCESS_EXIT_CODE = 0;
|
|
1045
|
+
var COMMAND_TOOL_NAMES = /* @__PURE__ */ new Set(["Bash", "BackgroundProcess"]);
|
|
1046
|
+
function formatCommandOutputSummary(tool) {
|
|
1047
|
+
if (!COMMAND_TOOL_NAMES.has(tool.toolName) || !tool.toolResultData) return void 0;
|
|
1048
|
+
const parsed = parseToolResultData(tool.toolResultData);
|
|
1049
|
+
const exitCode = getNumberValue(parsed, "exitCode");
|
|
1050
|
+
const successValue = getBooleanValue(parsed, "success");
|
|
1051
|
+
const output = buildOutputText(tool.toolResultData, parsed);
|
|
1052
|
+
const lines = trimTrailingBlankLines(splitOutputLines(output));
|
|
1053
|
+
const previewLines = lines.slice(0, MAX_PREVIEW_LINES);
|
|
1054
|
+
const omittedLineCount = Math.max(0, lines.length - previewLines.length);
|
|
1055
|
+
const isFailed = tool.result === "error" || successValue === false || exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE;
|
|
1056
|
+
return {
|
|
1057
|
+
status: isFailed ? "error" : "success",
|
|
1058
|
+
statusLabel: formatStatusLabel(isFailed, exitCode),
|
|
1059
|
+
previewLines,
|
|
1060
|
+
omittedLineCount,
|
|
1061
|
+
transcriptHint: omittedLineCount > 0 ? `... +${omittedLineCount} lines (full output in session transcript)` : void 0
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
function parseToolResultData(value) {
|
|
1065
|
+
try {
|
|
1066
|
+
return JSON.parse(value);
|
|
1067
|
+
} catch {
|
|
1068
|
+
return value;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function buildOutputText(raw, parsed) {
|
|
1072
|
+
if (!isUniversalObject(parsed)) return raw;
|
|
1073
|
+
const output = getStringValue(parsed, "output");
|
|
1074
|
+
if (output !== void 0) return output;
|
|
1075
|
+
const stdout = getStringValue(parsed, "stdout");
|
|
1076
|
+
const stderr = getStringValue(parsed, "stderr");
|
|
1077
|
+
const error = getStringValue(parsed, "error");
|
|
1078
|
+
const lines = [];
|
|
1079
|
+
if (stdout) lines.push(stdout);
|
|
1080
|
+
if (stderr) lines.push(prefixLines(stderr, "[stderr] "));
|
|
1081
|
+
if (!stdout && !stderr && error) lines.push(error);
|
|
1082
|
+
return lines.join("\n");
|
|
1083
|
+
}
|
|
1084
|
+
function formatStatusLabel(isFailed, exitCode) {
|
|
1085
|
+
if (exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE) return `exit ${exitCode}`;
|
|
1086
|
+
return isFailed ? "error" : "ok";
|
|
1087
|
+
}
|
|
1088
|
+
function splitOutputLines(output) {
|
|
1089
|
+
if (!output) return [];
|
|
1090
|
+
return output.replace(/\r\n/g, "\n").split("\n");
|
|
1091
|
+
}
|
|
1092
|
+
function trimTrailingBlankLines(lines) {
|
|
1093
|
+
let end = lines.length;
|
|
1094
|
+
while (end > 0 && lines[end - 1].trim().length === 0) {
|
|
1095
|
+
end -= 1;
|
|
1096
|
+
}
|
|
1097
|
+
return lines.slice(0, end);
|
|
1098
|
+
}
|
|
1099
|
+
function prefixLines(value, prefix) {
|
|
1100
|
+
return splitOutputLines(value).map((line) => `${prefix}${line}`).join("\n");
|
|
1101
|
+
}
|
|
1102
|
+
function isUniversalObject(value) {
|
|
1103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
1104
|
+
}
|
|
1105
|
+
function getStringValue(source, key) {
|
|
1106
|
+
if (!isUniversalObject(source)) return void 0;
|
|
1107
|
+
const value = source[key];
|
|
1108
|
+
return typeof value === "string" ? value : void 0;
|
|
1109
|
+
}
|
|
1110
|
+
function getNumberValue(source, key) {
|
|
1111
|
+
if (!isUniversalObject(source)) return void 0;
|
|
1112
|
+
const value = source[key];
|
|
1113
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1114
|
+
}
|
|
1115
|
+
function getBooleanValue(source, key) {
|
|
1116
|
+
if (!isUniversalObject(source)) return void 0;
|
|
1117
|
+
const value = source[key];
|
|
1118
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/ToolCommandOutput.tsx
|
|
1122
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1123
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1124
|
+
function ToolCommandOutput({ tool }) {
|
|
1125
|
+
const summary = formatCommandOutputSummary(tool);
|
|
1126
|
+
if (!summary) return null;
|
|
1127
|
+
if (summary.statusLabel === "ok" && summary.previewLines.length === 0 && !summary.transcriptHint) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
const color = summary.status === "error" ? "red" : "white";
|
|
1131
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 4, children: [
|
|
1132
|
+
summary.statusLabel !== "ok" && /* @__PURE__ */ jsx3(Text3, { color, dimColor: true, children: summary.statusLabel }),
|
|
1133
|
+
summary.previewLines.map((line, index) => /* @__PURE__ */ jsx3(Text3, { color, dimColor: true, children: line }, `${line}-${index}`)),
|
|
1134
|
+
summary.transcriptHint && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: summary.transcriptHint })
|
|
1135
|
+
] });
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/MessageList.tsx
|
|
1139
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1140
|
+
function getToolSummaryStatus(tool) {
|
|
1141
|
+
if (formatCommandOutputSummary(tool)?.status === "error") return "\u2717";
|
|
1142
|
+
if (tool.isRunning) return "\u27F3";
|
|
1143
|
+
if (tool.result === "error") return "\u2717";
|
|
1144
|
+
if (tool.result === "denied") return "\u2298";
|
|
1145
|
+
return "\u2713";
|
|
1146
|
+
}
|
|
1147
|
+
function getToolSummaryColor(tool) {
|
|
1148
|
+
if (formatCommandOutputSummary(tool)?.status === "error" || tool.result === "error") return "red";
|
|
1149
|
+
if (tool.isRunning || tool.result === "denied") return "yellow";
|
|
1150
|
+
return "green";
|
|
1151
|
+
}
|
|
1152
|
+
function getToolSummaryLabel(tool) {
|
|
1153
|
+
return `${getToolSummaryStatus(tool)} ${tool.toolName}${tool.firstArg ? `(${tool.firstArg})` : ""}`;
|
|
1154
|
+
}
|
|
1155
|
+
function RoleLabel({ role }) {
|
|
1156
|
+
switch (role) {
|
|
1157
|
+
case "user":
|
|
1158
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: "green", bold: true, children: [
|
|
1159
|
+
"You:",
|
|
1160
|
+
" "
|
|
1161
|
+
] });
|
|
1162
|
+
case "assistant":
|
|
1163
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: "cyan", bold: true, children: [
|
|
1164
|
+
"Robota:",
|
|
1165
|
+
" "
|
|
1166
|
+
] });
|
|
1167
|
+
case "system":
|
|
1168
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: "yellow", bold: true, children: [
|
|
1169
|
+
"System:",
|
|
1170
|
+
" "
|
|
1171
|
+
] });
|
|
1172
|
+
case "tool":
|
|
1173
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: "white", bold: true, children: [
|
|
1174
|
+
"Tool:",
|
|
1175
|
+
" "
|
|
1176
|
+
] });
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function ToolMessage({ message }) {
|
|
1180
|
+
if (!isToolMessage(message)) {
|
|
1181
|
+
return /* @__PURE__ */ jsx4(Fragment2, {});
|
|
1182
|
+
}
|
|
1183
|
+
const toolName = message.name;
|
|
1184
|
+
const content = message.content;
|
|
1185
|
+
let summaries = null;
|
|
1186
|
+
try {
|
|
1187
|
+
const parsed = JSON.parse(content);
|
|
1188
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
|
|
1189
|
+
summaries = parsed;
|
|
1190
|
+
}
|
|
1191
|
+
} catch {
|
|
1192
|
+
}
|
|
1193
|
+
if (summaries) {
|
|
1194
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
1195
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
1196
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "white", bold: true, children: [
|
|
1197
|
+
"Tool:",
|
|
1198
|
+
" "
|
|
1199
|
+
] }),
|
|
1200
|
+
toolName && /* @__PURE__ */ jsxs4(Text4, { color: "white", dimColor: true, children: [
|
|
1201
|
+
"[",
|
|
1202
|
+
toolName,
|
|
1203
|
+
"]"
|
|
1204
|
+
] })
|
|
1205
|
+
] }),
|
|
1206
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1207
|
+
summaries.map((s, i) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
1208
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
|
|
1209
|
+
" ",
|
|
1210
|
+
"\u2713",
|
|
1211
|
+
" ",
|
|
1212
|
+
s.line
|
|
1213
|
+
] }),
|
|
1214
|
+
s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ jsx4(ToolDiffBlock, { file: s.diffFile, lines: s.diffLines })
|
|
1215
|
+
] }, i))
|
|
1216
|
+
] });
|
|
1217
|
+
}
|
|
1218
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1219
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
1220
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
1221
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "white", bold: true, children: [
|
|
1222
|
+
"Tool:",
|
|
1223
|
+
" "
|
|
1224
|
+
] }),
|
|
1225
|
+
toolName && /* @__PURE__ */ jsxs4(Text4, { color: "white", dimColor: true, children: [
|
|
1226
|
+
"[",
|
|
1227
|
+
toolName,
|
|
1228
|
+
"]"
|
|
1229
|
+
] })
|
|
1230
|
+
] }),
|
|
1231
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1232
|
+
lines.map((line, i) => /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
|
|
1233
|
+
" ",
|
|
1234
|
+
"\u2713",
|
|
1235
|
+
" ",
|
|
1236
|
+
line
|
|
1237
|
+
] }, i))
|
|
1238
|
+
] });
|
|
1239
|
+
}
|
|
1240
|
+
var MessageItem = React.memo(function MessageItem2({
|
|
1241
|
+
message
|
|
1242
|
+
}) {
|
|
1243
|
+
if (isToolMessage(message)) {
|
|
1244
|
+
return /* @__PURE__ */ jsx4(ToolMessage, { message });
|
|
1245
|
+
}
|
|
1246
|
+
const content = message.content ?? "";
|
|
1247
|
+
const isInterrupted = message.state === "interrupted";
|
|
1248
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
1249
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(RoleLabel, { role: message.role }) }),
|
|
1250
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1251
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: isAssistantMessage(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
|
|
1252
|
+
] });
|
|
1253
|
+
});
|
|
1254
|
+
function ToolSummaryEntry({ entry }) {
|
|
1255
|
+
const data = entry.data;
|
|
1256
|
+
const tools = data?.tools;
|
|
1257
|
+
const lines = data?.summary?.split("\n") ?? [];
|
|
1258
|
+
if (tools && tools.length > 0) {
|
|
1259
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
1260
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: "white", bold: true, children: [
|
|
1261
|
+
"Tool:",
|
|
1262
|
+
" "
|
|
1263
|
+
] }) }),
|
|
1264
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1265
|
+
tools.map((tool, i) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs4(Text4, { color: getToolSummaryColor(tool), children: [
|
|
1267
|
+
" ",
|
|
1268
|
+
getToolSummaryLabel(tool)
|
|
1269
|
+
] }),
|
|
1270
|
+
/* @__PURE__ */ jsx4(ToolCommandOutput, { tool }),
|
|
1271
|
+
tool.diffLines && tool.diffLines.length > 0 && /* @__PURE__ */ jsx4(ToolDiffBlock, { file: tool.diffFile, lines: tool.diffLines })
|
|
1272
|
+
] }, i))
|
|
1273
|
+
] });
|
|
1274
|
+
}
|
|
1275
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
1276
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: "white", bold: true, children: [
|
|
1277
|
+
"Tool:",
|
|
1278
|
+
" "
|
|
1279
|
+
] }) }),
|
|
1280
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1281
|
+
lines.map((line, i) => /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
|
|
1282
|
+
" ",
|
|
1283
|
+
line
|
|
1284
|
+
] }, i))
|
|
1285
|
+
] });
|
|
1286
|
+
}
|
|
1287
|
+
function EventEntry({ entry }) {
|
|
1288
|
+
const eventData = entry.data;
|
|
1289
|
+
const eventMessage = typeof eventData?.message === "string" ? eventData.message : typeof eventData?.content === "string" ? eventData.content : entry.type;
|
|
1290
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
1291
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: "yellow", bold: true, children: [
|
|
1292
|
+
"System:",
|
|
1293
|
+
" "
|
|
1294
|
+
] }) }),
|
|
1295
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1296
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: eventMessage }) })
|
|
1297
|
+
] });
|
|
1298
|
+
}
|
|
1299
|
+
function EntryItem({ entry }) {
|
|
1300
|
+
if (entry.category === "chat") {
|
|
1301
|
+
const message = entry.data;
|
|
1302
|
+
return /* @__PURE__ */ jsx4(MessageItem, { message });
|
|
1303
|
+
}
|
|
1304
|
+
if (entry.type === "tool-summary") {
|
|
1305
|
+
return /* @__PURE__ */ jsx4(ToolSummaryEntry, { entry });
|
|
1306
|
+
}
|
|
1307
|
+
if (entry.type === "usage-summary") {
|
|
1308
|
+
return /* @__PURE__ */ jsx4(UsageSummaryEntry, { entry });
|
|
1309
|
+
}
|
|
1310
|
+
if (entry.type === "tool-start" || entry.type === "tool-end") {
|
|
1311
|
+
return /* @__PURE__ */ jsx4(Fragment2, {});
|
|
1312
|
+
}
|
|
1313
|
+
return /* @__PURE__ */ jsx4(EventEntry, { entry });
|
|
1314
|
+
}
|
|
1315
|
+
function MessageList({ history }) {
|
|
1316
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: history.map((entry) => /* @__PURE__ */ jsx4(EntryItem, { entry }, entry.id)) });
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// src/SessionStatusBar.tsx
|
|
1320
|
+
import { useMemo as useMemo2 } from "react";
|
|
1321
|
+
import { getModelName as getModelName2 } from "@robota-sdk/agent-core";
|
|
1322
|
+
|
|
1323
|
+
// src/StatusBar.tsx
|
|
1324
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1325
|
+
import { formatTokenCount as formatTokenCount2 } from "@robota-sdk/agent-core";
|
|
1326
|
+
|
|
1327
|
+
// src/status-activity.ts
|
|
1328
|
+
var NO_ACTIVE_ITEMS = 0;
|
|
1329
|
+
function formatStatusActivity(input) {
|
|
1330
|
+
const base = getPrimaryActivity(input);
|
|
1331
|
+
const segments = input.hasPendingPrompt && base.kind !== "queued" ? ["queued"] : [];
|
|
1332
|
+
const text = [base.label, ...segments].join(" \xB7 ");
|
|
1333
|
+
return { ...base, segments, text };
|
|
1334
|
+
}
|
|
1335
|
+
function getPrimaryActivity(input) {
|
|
1336
|
+
if (input.activeToolCount > NO_ACTIVE_ITEMS) {
|
|
1337
|
+
return {
|
|
1338
|
+
kind: "tools",
|
|
1339
|
+
label: `Tools x${input.activeToolCount}`,
|
|
1340
|
+
color: "cyan"
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
if (input.isThinking) {
|
|
1344
|
+
return {
|
|
1345
|
+
kind: "thinking",
|
|
1346
|
+
label: "Thinking",
|
|
1347
|
+
color: "yellow"
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
if (input.activeBackgroundTaskCount > NO_ACTIVE_ITEMS) {
|
|
1351
|
+
return {
|
|
1352
|
+
kind: "background",
|
|
1353
|
+
label: `Background x${input.activeBackgroundTaskCount}`,
|
|
1354
|
+
color: "cyan"
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
if (input.hasPendingPrompt) {
|
|
1358
|
+
return {
|
|
1359
|
+
kind: "queued",
|
|
1360
|
+
label: "Queued",
|
|
1361
|
+
color: "yellow"
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
return {
|
|
1365
|
+
kind: "idle",
|
|
1366
|
+
label: "Idle",
|
|
1367
|
+
color: "gray"
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/StatusBar.tsx
|
|
1372
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1373
|
+
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
1374
|
+
var CONTEXT_RED_THRESHOLD = 90;
|
|
1375
|
+
function getContextColor(percentage) {
|
|
1376
|
+
if (percentage >= CONTEXT_RED_THRESHOLD) return "red";
|
|
1377
|
+
if (percentage >= CONTEXT_YELLOW_THRESHOLD) return "yellow";
|
|
1378
|
+
return "green";
|
|
1379
|
+
}
|
|
1380
|
+
function StatusActivityText({
|
|
1381
|
+
isThinking,
|
|
1382
|
+
activeToolCount,
|
|
1383
|
+
activeBackgroundTaskCount,
|
|
1384
|
+
hasPendingPrompt
|
|
1385
|
+
}) {
|
|
1386
|
+
const activity = formatStatusActivity({
|
|
1387
|
+
isThinking,
|
|
1388
|
+
activeToolCount,
|
|
1389
|
+
activeBackgroundTaskCount,
|
|
1390
|
+
hasPendingPrompt
|
|
1391
|
+
});
|
|
1392
|
+
return /* @__PURE__ */ jsx5(Text5, { color: activity.color, bold: activity.kind !== "idle", children: activity.text });
|
|
1393
|
+
}
|
|
1394
|
+
function ContextText({
|
|
1395
|
+
percentage,
|
|
1396
|
+
usedTokens,
|
|
1397
|
+
maxTokens
|
|
1398
|
+
}) {
|
|
1399
|
+
return /* @__PURE__ */ jsxs5(Text5, { color: getContextColor(percentage), children: [
|
|
1400
|
+
"Context: ",
|
|
1401
|
+
Math.round(percentage),
|
|
1402
|
+
"% (",
|
|
1403
|
+
formatTokenCount2(usedTokens),
|
|
1404
|
+
"/",
|
|
1405
|
+
formatTokenCount2(maxTokens),
|
|
1406
|
+
")"
|
|
1407
|
+
] });
|
|
1408
|
+
}
|
|
1409
|
+
function ModeText({ permissionMode }) {
|
|
1410
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1411
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "Mode:" }),
|
|
1412
|
+
" ",
|
|
1413
|
+
/* @__PURE__ */ jsx5(Text5, { children: permissionMode })
|
|
1414
|
+
] });
|
|
1415
|
+
}
|
|
1416
|
+
function shouldShowPermissionMode(permissionMode) {
|
|
1417
|
+
return permissionMode !== "default";
|
|
1418
|
+
}
|
|
1419
|
+
function ProviderText({
|
|
1420
|
+
modelName,
|
|
1421
|
+
providerProfileName,
|
|
1422
|
+
providerType
|
|
1423
|
+
}) {
|
|
1424
|
+
if (providerProfileName !== void 0 && providerType !== void 0) {
|
|
1425
|
+
return /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1426
|
+
providerProfileName,
|
|
1427
|
+
" (",
|
|
1428
|
+
providerType,
|
|
1429
|
+
") ",
|
|
1430
|
+
modelName
|
|
1431
|
+
] });
|
|
1432
|
+
}
|
|
1433
|
+
return /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: modelName });
|
|
1434
|
+
}
|
|
1435
|
+
function StatusLeft(props) {
|
|
1436
|
+
const shouldShowGitBranch = props.showGitBranch && props.gitBranch !== void 0 && props.gitBranch.length > 0;
|
|
1437
|
+
const showPermissionMode = shouldShowPermissionMode(props.permissionMode);
|
|
1438
|
+
return /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1439
|
+
/* @__PURE__ */ jsx5(
|
|
1440
|
+
StatusActivityText,
|
|
1441
|
+
{
|
|
1442
|
+
isThinking: props.isThinking,
|
|
1443
|
+
activeToolCount: props.activeToolCount,
|
|
1444
|
+
activeBackgroundTaskCount: props.activeBackgroundTaskCount,
|
|
1445
|
+
hasPendingPrompt: props.hasPendingPrompt
|
|
1446
|
+
}
|
|
1447
|
+
),
|
|
1448
|
+
showPermissionMode && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1449
|
+
" | ",
|
|
1450
|
+
/* @__PURE__ */ jsx5(ModeText, { permissionMode: props.permissionMode })
|
|
1451
|
+
] }),
|
|
1452
|
+
props.sessionName && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1453
|
+
" | ",
|
|
1454
|
+
/* @__PURE__ */ jsx5(Text5, { color: "magenta", children: props.sessionName })
|
|
1455
|
+
] }),
|
|
1456
|
+
shouldShowGitBranch && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1457
|
+
" | ",
|
|
1458
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1459
|
+
"git: ",
|
|
1460
|
+
props.gitBranch
|
|
1461
|
+
] })
|
|
1462
|
+
] }),
|
|
1463
|
+
" | ",
|
|
1464
|
+
/* @__PURE__ */ jsx5(
|
|
1465
|
+
ProviderText,
|
|
1466
|
+
{
|
|
1467
|
+
modelName: props.modelName,
|
|
1468
|
+
providerProfileName: props.providerProfileName,
|
|
1469
|
+
providerType: props.providerType
|
|
1470
|
+
}
|
|
1471
|
+
),
|
|
1472
|
+
" | ",
|
|
1473
|
+
/* @__PURE__ */ jsx5(
|
|
1474
|
+
ContextText,
|
|
1475
|
+
{
|
|
1476
|
+
percentage: props.contextPercentage,
|
|
1477
|
+
usedTokens: props.contextUsedTokens,
|
|
1478
|
+
maxTokens: props.contextMaxTokens
|
|
1479
|
+
}
|
|
1480
|
+
)
|
|
1481
|
+
] });
|
|
1482
|
+
}
|
|
1483
|
+
function StatusBar({
|
|
1484
|
+
permissionMode,
|
|
1485
|
+
modelName,
|
|
1486
|
+
providerProfileName,
|
|
1487
|
+
providerType,
|
|
1488
|
+
sessionId: _sessionId,
|
|
1489
|
+
isThinking,
|
|
1490
|
+
activeToolCount = 0,
|
|
1491
|
+
activeBackgroundTaskCount = 0,
|
|
1492
|
+
hasPendingPrompt = false,
|
|
1493
|
+
contextPercentage,
|
|
1494
|
+
contextUsedTokens,
|
|
1495
|
+
contextMaxTokens,
|
|
1496
|
+
sessionName,
|
|
1497
|
+
gitBranch,
|
|
1498
|
+
showGitBranch = true
|
|
1499
|
+
}) {
|
|
1500
|
+
return /* @__PURE__ */ jsx5(
|
|
1501
|
+
Box5,
|
|
1502
|
+
{
|
|
1503
|
+
borderStyle: "single",
|
|
1504
|
+
borderColor: "gray",
|
|
1505
|
+
paddingLeft: 1,
|
|
1506
|
+
paddingRight: 1,
|
|
1507
|
+
justifyContent: "space-between",
|
|
1508
|
+
children: /* @__PURE__ */ jsx5(
|
|
1509
|
+
StatusLeft,
|
|
1510
|
+
{
|
|
1511
|
+
permissionMode,
|
|
1512
|
+
modelName,
|
|
1513
|
+
providerProfileName,
|
|
1514
|
+
providerType,
|
|
1515
|
+
isThinking,
|
|
1516
|
+
activeToolCount,
|
|
1517
|
+
activeBackgroundTaskCount,
|
|
1518
|
+
hasPendingPrompt,
|
|
1519
|
+
contextPercentage,
|
|
1520
|
+
contextUsedTokens,
|
|
1521
|
+
contextMaxTokens,
|
|
1522
|
+
sessionName,
|
|
1523
|
+
gitBranch,
|
|
1524
|
+
showGitBranch
|
|
1525
|
+
}
|
|
1526
|
+
)
|
|
1527
|
+
}
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/SessionStatusBar.tsx
|
|
1532
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1533
|
+
function SessionStatusBar({
|
|
1534
|
+
cwd,
|
|
1535
|
+
permissionMode,
|
|
1536
|
+
modelId,
|
|
1537
|
+
providerProfileName,
|
|
1538
|
+
providerType,
|
|
1539
|
+
sessionId,
|
|
1540
|
+
isThinking,
|
|
1541
|
+
activeToolCount,
|
|
1542
|
+
activeBackgroundTaskCount,
|
|
1543
|
+
hasPendingPrompt,
|
|
1544
|
+
contextState,
|
|
1545
|
+
sessionName,
|
|
1546
|
+
settings
|
|
1547
|
+
}) {
|
|
1548
|
+
const cliAdapter = useTuiCliAdapter();
|
|
1549
|
+
const gitBranch = useMemo2(() => cliAdapter.getGitBranch(cwd), [cliAdapter, cwd]);
|
|
1550
|
+
if (!settings.enabled) return null;
|
|
1551
|
+
return /* @__PURE__ */ jsx6(
|
|
1552
|
+
StatusBar,
|
|
1553
|
+
{
|
|
1554
|
+
permissionMode,
|
|
1555
|
+
modelName: modelId ? getModelName2(modelId) : "",
|
|
1556
|
+
providerProfileName,
|
|
1557
|
+
providerType,
|
|
1558
|
+
sessionId,
|
|
1559
|
+
isThinking,
|
|
1560
|
+
activeToolCount,
|
|
1561
|
+
activeBackgroundTaskCount,
|
|
1562
|
+
hasPendingPrompt,
|
|
1563
|
+
contextPercentage: contextState.percentage,
|
|
1564
|
+
contextUsedTokens: contextState.usedTokens,
|
|
1565
|
+
contextMaxTokens: contextState.maxTokens,
|
|
1566
|
+
sessionName,
|
|
1567
|
+
gitBranch,
|
|
1568
|
+
showGitBranch: settings.gitBranch
|
|
1569
|
+
}
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/InputArea.tsx
|
|
1574
|
+
import { useState as useState8, useCallback as useCallback4, useRef as useRef4, useMemo as useMemo4 } from "react";
|
|
1575
|
+
import { Box as Box7, Text as Text9, useInput as useInput2, useWindowSize } from "ink";
|
|
1576
|
+
|
|
1577
|
+
// src/CjkTextInput.tsx
|
|
1578
|
+
import { useRef as useRef3, useState as useState4 } from "react";
|
|
1579
|
+
import { Text as Text6, useInput, usePaste } from "ink";
|
|
1580
|
+
import chalk from "chalk";
|
|
1581
|
+
|
|
1582
|
+
// src/flows/cjk-text-input-flow.ts
|
|
1583
|
+
import stringWidth from "string-width";
|
|
1584
|
+
var PASTE_START = "[200~";
|
|
1585
|
+
var PASTE_END = "[201~";
|
|
1586
|
+
var LAST_ASCII_CONTROL_CODE = 31;
|
|
1587
|
+
var DELETE_CONTROL_CODE = 127;
|
|
1588
|
+
function createCjkTextInputFlowState(value) {
|
|
1589
|
+
return { value, cursor: value.length, isPasting: false, pasteBuffer: "" };
|
|
1590
|
+
}
|
|
1591
|
+
function syncCjkTextInputFlowState(state, value, cursorHint) {
|
|
1592
|
+
if (value === state.value) {
|
|
1593
|
+
return state;
|
|
1594
|
+
}
|
|
1595
|
+
return {
|
|
1596
|
+
...state,
|
|
1597
|
+
value,
|
|
1598
|
+
cursor: cursorHint != null ? Math.min(cursorHint, value.length) : value.length
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function applyCjkTextInput(state, input, key, options) {
|
|
1602
|
+
const pasteResult = applyPasteBoundaryInput(state, input, options);
|
|
1603
|
+
if (pasteResult !== void 0) return pasteResult;
|
|
1604
|
+
const controlResult = applyControlInput(state, input, key, options);
|
|
1605
|
+
if (controlResult !== void 0) return controlResult;
|
|
1606
|
+
const cursorResult = applyCursorInput(state, key, options);
|
|
1607
|
+
if (cursorResult !== void 0) return cursorResult;
|
|
1608
|
+
return insertPrintableInput(state, input);
|
|
1609
|
+
}
|
|
1610
|
+
function applyCjkTextPaste(state, text, options) {
|
|
1611
|
+
const normalizedText = text.replace(/\r\n?/g, "\n");
|
|
1612
|
+
if (normalizedText.length === 0) {
|
|
1613
|
+
return { state, effect: { type: "none" } };
|
|
1614
|
+
}
|
|
1615
|
+
if (normalizedText.includes("\n") && options.canPaste) {
|
|
1616
|
+
return { state, effect: { type: "paste", text: normalizedText, cursor: state.cursor } };
|
|
1617
|
+
}
|
|
1618
|
+
return insertPrintableInput(state, normalizedText);
|
|
1619
|
+
}
|
|
1620
|
+
function applyPasteBoundaryInput(state, input, options) {
|
|
1621
|
+
if (input === PASTE_START || input.startsWith(PASTE_START)) {
|
|
1622
|
+
return startBracketedPaste(state, input);
|
|
1623
|
+
}
|
|
1624
|
+
if (state.isPasting) {
|
|
1625
|
+
return continueBracketedPaste(state, input, options);
|
|
1626
|
+
}
|
|
1627
|
+
return void 0;
|
|
1628
|
+
}
|
|
1629
|
+
function applyControlInput(state, input, key, options) {
|
|
1630
|
+
if (key.ctrl === true && input === "c" || key.tab === true) {
|
|
1631
|
+
return { state, effect: { type: "none" } };
|
|
1632
|
+
}
|
|
1633
|
+
if (key.return === true) {
|
|
1634
|
+
return { state, effect: { type: "submit", value: state.value } };
|
|
1635
|
+
}
|
|
1636
|
+
if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && options.canPaste) {
|
|
1637
|
+
return {
|
|
1638
|
+
state,
|
|
1639
|
+
effect: { type: "paste", text: input.replace(/\r\n?/g, "\n"), cursor: state.cursor }
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
return void 0;
|
|
1643
|
+
}
|
|
1644
|
+
function applyCursorInput(state, key, options) {
|
|
1645
|
+
if (key.upArrow === true || key.downArrow === true) {
|
|
1646
|
+
if (options.enableVerticalNavigation === false) {
|
|
1647
|
+
return { state, effect: { type: "none" } };
|
|
1648
|
+
}
|
|
1649
|
+
return moveCursorVertically(
|
|
1650
|
+
state,
|
|
1651
|
+
key.upArrow === true ? "up" : "down",
|
|
1652
|
+
options.availableWidth
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
if (key.leftArrow === true) {
|
|
1656
|
+
return moveCursorHorizontally(state, "left");
|
|
1657
|
+
}
|
|
1658
|
+
if (key.rightArrow === true) {
|
|
1659
|
+
return moveCursorHorizontally(state, "right");
|
|
1660
|
+
}
|
|
1661
|
+
if (key.backspace === true || key.delete === true) {
|
|
1662
|
+
return deleteBeforeCursor(state);
|
|
1663
|
+
}
|
|
1664
|
+
return void 0;
|
|
1665
|
+
}
|
|
1666
|
+
function filterPrintable(input) {
|
|
1667
|
+
if (!input || input.length === 0) return "";
|
|
1668
|
+
let output = "";
|
|
1669
|
+
for (const char of input) {
|
|
1670
|
+
const code = char.charCodeAt(0);
|
|
1671
|
+
if (code > LAST_ASCII_CONTROL_CODE && code !== DELETE_CONTROL_CODE) {
|
|
1672
|
+
output += char;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return output;
|
|
1676
|
+
}
|
|
1677
|
+
function insertAtCursor(value, cursor, input) {
|
|
1678
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
1679
|
+
return { value: next, cursor: cursor + input.length };
|
|
1680
|
+
}
|
|
1681
|
+
function displayOffset(chars, charIndex, width) {
|
|
1682
|
+
let offset = 0;
|
|
1683
|
+
for (let i = 0; i < charIndex && i < chars.length; i++) {
|
|
1684
|
+
const w = stringWidth(chars[i]);
|
|
1685
|
+
const col = offset % width;
|
|
1686
|
+
if (col + w > width) offset += width - col;
|
|
1687
|
+
offset += w;
|
|
1688
|
+
}
|
|
1689
|
+
return offset;
|
|
1690
|
+
}
|
|
1691
|
+
function charIndexAtDisplayOffset(chars, target, width) {
|
|
1692
|
+
let offset = 0;
|
|
1693
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1694
|
+
if (offset >= target) return i;
|
|
1695
|
+
const w = stringWidth(chars[i]);
|
|
1696
|
+
const col = offset % width;
|
|
1697
|
+
if (col + w > width) offset += width - col;
|
|
1698
|
+
offset += w;
|
|
1699
|
+
}
|
|
1700
|
+
return chars.length;
|
|
1701
|
+
}
|
|
1702
|
+
function startBracketedPaste(state, input) {
|
|
1703
|
+
return {
|
|
1704
|
+
state: { ...state, isPasting: true, pasteBuffer: input.slice(PASTE_START.length) },
|
|
1705
|
+
effect: { type: "none" }
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
function continueBracketedPaste(state, input, options) {
|
|
1709
|
+
if (input !== PASTE_END && !input.includes(PASTE_END)) {
|
|
1710
|
+
return {
|
|
1711
|
+
state: { ...state, pasteBuffer: state.pasteBuffer + input },
|
|
1712
|
+
effect: { type: "none" }
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
const beforeMarker = input.split(PASTE_END)[0] ?? "";
|
|
1716
|
+
const nextState = { ...state, isPasting: false, pasteBuffer: "" };
|
|
1717
|
+
return applyCjkTextPaste(nextState, state.pasteBuffer + beforeMarker, options);
|
|
1718
|
+
}
|
|
1719
|
+
function moveCursorVertically(state, direction, availableWidth) {
|
|
1720
|
+
if (!availableWidth || availableWidth <= 0) {
|
|
1721
|
+
return { state, effect: { type: "none" } };
|
|
1722
|
+
}
|
|
1723
|
+
const chars = [...state.value];
|
|
1724
|
+
const offset = displayOffset(chars, state.cursor, availableWidth);
|
|
1725
|
+
const target = direction === "up" ? offset - availableWidth : offset + availableWidth;
|
|
1726
|
+
if (target < 0) {
|
|
1727
|
+
return { state, effect: { type: "none" } };
|
|
1728
|
+
}
|
|
1729
|
+
const cursor = charIndexAtDisplayOffset(chars, target, availableWidth);
|
|
1730
|
+
if (cursor === state.cursor) {
|
|
1731
|
+
return { state, effect: { type: "none" } };
|
|
1732
|
+
}
|
|
1733
|
+
return { state: { ...state, cursor }, effect: { type: "render" } };
|
|
1734
|
+
}
|
|
1735
|
+
function moveCursorHorizontally(state, direction) {
|
|
1736
|
+
if (direction === "left" && state.cursor > 0) {
|
|
1737
|
+
return { state: { ...state, cursor: state.cursor - 1 }, effect: { type: "render" } };
|
|
1738
|
+
}
|
|
1739
|
+
if (direction === "right" && state.cursor < state.value.length) {
|
|
1740
|
+
return { state: { ...state, cursor: state.cursor + 1 }, effect: { type: "render" } };
|
|
1741
|
+
}
|
|
1742
|
+
return { state, effect: { type: "none" } };
|
|
1743
|
+
}
|
|
1744
|
+
function deleteBeforeCursor(state) {
|
|
1745
|
+
if (state.cursor === 0) {
|
|
1746
|
+
return { state, effect: { type: "none" } };
|
|
1747
|
+
}
|
|
1748
|
+
const value = state.value.slice(0, state.cursor - 1) + state.value.slice(state.cursor);
|
|
1749
|
+
return {
|
|
1750
|
+
state: { ...state, value, cursor: state.cursor - 1 },
|
|
1751
|
+
effect: { type: "change", value }
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
function insertPrintableInput(state, input) {
|
|
1755
|
+
const printable = filterPrintable(input);
|
|
1756
|
+
if (printable.length === 0) {
|
|
1757
|
+
return { state, effect: { type: "none" } };
|
|
1758
|
+
}
|
|
1759
|
+
const result = insertAtCursor(state.value, state.cursor, printable);
|
|
1760
|
+
return {
|
|
1761
|
+
state: { ...state, value: result.value, cursor: result.cursor },
|
|
1762
|
+
effect: { type: "change", value: result.value }
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// src/CjkTextInput.tsx
|
|
1767
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1768
|
+
function CjkTextInput({
|
|
1769
|
+
value,
|
|
1770
|
+
onChange,
|
|
1771
|
+
onSubmit,
|
|
1772
|
+
onPaste,
|
|
1773
|
+
placeholder = "",
|
|
1774
|
+
focus = true,
|
|
1775
|
+
showCursor = true,
|
|
1776
|
+
availableWidth,
|
|
1777
|
+
cursorHint = null,
|
|
1778
|
+
enableVerticalNavigation = true
|
|
1779
|
+
}) {
|
|
1780
|
+
const stateRef = useRef3(createCjkTextInputFlowState(value));
|
|
1781
|
+
const [, forceRender] = useState4(0);
|
|
1782
|
+
stateRef.current = syncCjkTextInputFlowState(stateRef.current, value, cursorHint);
|
|
1783
|
+
useCjkTextInputHandlers({
|
|
1784
|
+
stateRef,
|
|
1785
|
+
onChange,
|
|
1786
|
+
onSubmit,
|
|
1787
|
+
onPaste,
|
|
1788
|
+
availableWidth,
|
|
1789
|
+
focus,
|
|
1790
|
+
enableVerticalNavigation,
|
|
1791
|
+
forceRender
|
|
1792
|
+
});
|
|
1793
|
+
return /* @__PURE__ */ jsx7(Text6, { children: renderWithCursor(
|
|
1794
|
+
stateRef.current.value,
|
|
1795
|
+
stateRef.current.cursor,
|
|
1796
|
+
placeholder,
|
|
1797
|
+
showCursor && focus
|
|
1798
|
+
) });
|
|
1799
|
+
}
|
|
1800
|
+
function useCjkTextInputHandlers(options) {
|
|
1801
|
+
usePaste(
|
|
1802
|
+
(text) => {
|
|
1803
|
+
applyCjkFlowSafely(
|
|
1804
|
+
options,
|
|
1805
|
+
() => applyCjkTextPaste(options.stateRef.current, text, createFlowOptions(options))
|
|
1806
|
+
);
|
|
1807
|
+
},
|
|
1808
|
+
{ isActive: options.focus }
|
|
1809
|
+
);
|
|
1810
|
+
useInput(
|
|
1811
|
+
(input, key) => {
|
|
1812
|
+
applyCjkFlowSafely(
|
|
1813
|
+
options,
|
|
1814
|
+
() => applyCjkTextInput(options.stateRef.current, input, key, createFlowOptions(options))
|
|
1815
|
+
);
|
|
1816
|
+
},
|
|
1817
|
+
{ isActive: options.focus }
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
function createFlowOptions(options) {
|
|
1821
|
+
return {
|
|
1822
|
+
availableWidth: options.availableWidth,
|
|
1823
|
+
canPaste: options.onPaste !== void 0,
|
|
1824
|
+
enableVerticalNavigation: options.enableVerticalNavigation
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
function applyCjkFlowSafely(options, run) {
|
|
1828
|
+
try {
|
|
1829
|
+
const result = run();
|
|
1830
|
+
options.stateRef.current = result.state;
|
|
1831
|
+
applyCjkTextInputEffect(
|
|
1832
|
+
result.effect,
|
|
1833
|
+
options.onChange,
|
|
1834
|
+
options.onSubmit,
|
|
1835
|
+
options.onPaste,
|
|
1836
|
+
options.forceRender
|
|
1837
|
+
);
|
|
1838
|
+
} catch {
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
function applyCjkTextInputEffect(effect, onChange, onSubmit, onPaste, forceRender) {
|
|
1842
|
+
if (effect.type === "change") {
|
|
1843
|
+
onChange(effect.value);
|
|
1844
|
+
} else if (effect.type === "submit") {
|
|
1845
|
+
onSubmit?.(effect.value);
|
|
1846
|
+
} else if (effect.type === "paste") {
|
|
1847
|
+
onPaste?.(effect.text, effect.cursor);
|
|
1848
|
+
} else if (effect.type === "render") {
|
|
1849
|
+
forceRender((n) => n + 1);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
1853
|
+
if (!showCursor) {
|
|
1854
|
+
return value.length > 0 ? value : placeholder ? chalk.gray(placeholder) : "";
|
|
1855
|
+
}
|
|
1856
|
+
if (value.length === 0) {
|
|
1857
|
+
if (placeholder.length > 0) {
|
|
1858
|
+
return chalk.inverse(placeholder[0]) + chalk.gray(placeholder.slice(1));
|
|
1859
|
+
}
|
|
1860
|
+
return chalk.inverse(" ");
|
|
1861
|
+
}
|
|
1862
|
+
const chars = [...value];
|
|
1863
|
+
let rendered = "";
|
|
1864
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1865
|
+
const char = chars[i] ?? "";
|
|
1866
|
+
rendered += i === cursorOffset ? chalk.inverse(char) : char;
|
|
1867
|
+
}
|
|
1868
|
+
if (cursorOffset >= chars.length) {
|
|
1869
|
+
rendered += chalk.inverse(" ");
|
|
1870
|
+
}
|
|
1871
|
+
return rendered;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// src/WaveText.tsx
|
|
1875
|
+
import { useState as useState5, useEffect as useEffect2 } from "react";
|
|
1876
|
+
import { Text as Text7 } from "ink";
|
|
1877
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1878
|
+
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
1879
|
+
var INTERVAL_MS = 400;
|
|
1880
|
+
var CHARS_PER_GROUP = 4;
|
|
1881
|
+
function WaveText({ text }) {
|
|
1882
|
+
const [tick, setTick] = useState5(0);
|
|
1883
|
+
useEffect2(() => {
|
|
1884
|
+
const timer = setInterval(() => {
|
|
1885
|
+
setTick((prev) => prev + 1);
|
|
1886
|
+
}, INTERVAL_MS);
|
|
1887
|
+
return () => clearInterval(timer);
|
|
1888
|
+
}, []);
|
|
1889
|
+
const chars = [...text];
|
|
1890
|
+
return /* @__PURE__ */ jsx8(Text7, { children: chars.map((char, i) => {
|
|
1891
|
+
const group = Math.floor(i / CHARS_PER_GROUP);
|
|
1892
|
+
const colorIndex = (tick + group) % WAVE_COLORS.length;
|
|
1893
|
+
return /* @__PURE__ */ jsx8(Text7, { color: WAVE_COLORS[colorIndex], children: char }, i);
|
|
1894
|
+
}) });
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// src/SlashAutocomplete.tsx
|
|
1898
|
+
import { useState as useState6, useEffect as useEffect3 } from "react";
|
|
1899
|
+
import { Box as Box6, Text as Text8, useStdout } from "ink";
|
|
1900
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1901
|
+
var MAX_VISIBLE = 8;
|
|
1902
|
+
var OUTER_CHROME = 4;
|
|
1903
|
+
var MIN_ROW_WIDTH = 40;
|
|
1904
|
+
var NAME_COL_MAX = 20;
|
|
1905
|
+
function useRowWidth() {
|
|
1906
|
+
const { stdout } = useStdout();
|
|
1907
|
+
const measure = () => Math.max(MIN_ROW_WIDTH, (stdout.columns ?? 80) - OUTER_CHROME);
|
|
1908
|
+
const [width, setWidth] = useState6(measure);
|
|
1909
|
+
useEffect3(() => {
|
|
1910
|
+
const onResize = () => setWidth(measure());
|
|
1911
|
+
stdout.on("resize", onResize);
|
|
1912
|
+
return () => {
|
|
1913
|
+
stdout.off("resize", onResize);
|
|
1914
|
+
};
|
|
1915
|
+
}, [stdout]);
|
|
1916
|
+
return width;
|
|
1917
|
+
}
|
|
1918
|
+
function capName(name, colWidth) {
|
|
1919
|
+
return name.length > colWidth ? `${name.slice(0, colWidth - 1)}\u2026` : name.padEnd(colWidth);
|
|
1920
|
+
}
|
|
1921
|
+
function CommandRow(props) {
|
|
1922
|
+
const { cmd, isSelected, showSlash, rowWidth, nameColWidth } = props;
|
|
1923
|
+
const indicator = isSelected ? "\u25B8 " : " ";
|
|
1924
|
+
const nameColor = isSelected ? "cyan" : void 0;
|
|
1925
|
+
const dimmed = !isSelected;
|
|
1926
|
+
const namePart = capName(cmd.name, nameColWidth);
|
|
1927
|
+
const text = showSlash ? `${indicator}/${namePart} ${cmd.description ?? ""}` : `${indicator}${namePart} ${cmd.description ?? ""}`;
|
|
1928
|
+
return /* @__PURE__ */ jsx9(Box6, { width: rowWidth, children: /* @__PURE__ */ jsx9(Text8, { color: nameColor, dimColor: dimmed, wrap: "truncate-end", children: text }) });
|
|
1929
|
+
}
|
|
1930
|
+
function SlashAutocomplete({
|
|
1931
|
+
commands,
|
|
1932
|
+
selectedIndex,
|
|
1933
|
+
visible,
|
|
1934
|
+
isSubcommandMode
|
|
1935
|
+
}) {
|
|
1936
|
+
const rowWidth = useRowWidth();
|
|
1937
|
+
if (!visible || commands.length === 0) return null;
|
|
1938
|
+
const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
|
|
1939
|
+
const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
1940
|
+
const nameColWidth = Math.min(
|
|
1941
|
+
NAME_COL_MAX,
|
|
1942
|
+
Math.max(...visibleCommands.map((c) => c.name.length))
|
|
1943
|
+
);
|
|
1944
|
+
return /* @__PURE__ */ jsx9(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ jsx9(
|
|
1945
|
+
CommandRow,
|
|
1946
|
+
{
|
|
1947
|
+
cmd,
|
|
1948
|
+
isSelected: scrollOffset + i === selectedIndex,
|
|
1949
|
+
showSlash: !isSubcommandMode,
|
|
1950
|
+
rowWidth,
|
|
1951
|
+
nameColWidth
|
|
1952
|
+
},
|
|
1953
|
+
cmd.name
|
|
1954
|
+
)) });
|
|
1955
|
+
}
|
|
1956
|
+
function computeScrollOffset(selectedIndex, total) {
|
|
1957
|
+
if (total <= MAX_VISIBLE) return 0;
|
|
1958
|
+
if (selectedIndex < MAX_VISIBLE) return 0;
|
|
1959
|
+
const maxOffset = total - MAX_VISIBLE;
|
|
1960
|
+
return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/utils/paste-labels.ts
|
|
1964
|
+
var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
|
|
1965
|
+
function expandPasteLabels(text, store) {
|
|
1966
|
+
return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// src/hooks/useAutocomplete.ts
|
|
1970
|
+
import React6, { useState as useState7, useMemo as useMemo3 } from "react";
|
|
1971
|
+
function parseSlashInput(value) {
|
|
1972
|
+
if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
|
|
1973
|
+
const afterSlash = value.slice(1);
|
|
1974
|
+
const spaceIndex = afterSlash.indexOf(" ");
|
|
1975
|
+
if (spaceIndex === -1) return { isSlash: true, parentCommand: "", filter: afterSlash };
|
|
1976
|
+
const parent = afterSlash.slice(0, spaceIndex);
|
|
1977
|
+
const rest = afterSlash.slice(spaceIndex + 1);
|
|
1978
|
+
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
1979
|
+
}
|
|
1980
|
+
function useAutocomplete(value, registry) {
|
|
1981
|
+
const [selectedIndex, setSelectedIndex] = useState7(0);
|
|
1982
|
+
const [dismissed, setDismissed] = useState7(false);
|
|
1983
|
+
const prevValueRef = React6.useRef(value);
|
|
1984
|
+
if (prevValueRef.current !== value) {
|
|
1985
|
+
prevValueRef.current = value;
|
|
1986
|
+
if (dismissed) setDismissed(false);
|
|
1987
|
+
}
|
|
1988
|
+
const parsed = parseSlashInput(value);
|
|
1989
|
+
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
1990
|
+
const filteredCommands = useMemo3(() => {
|
|
1991
|
+
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
1992
|
+
if (isSubcommandMode) {
|
|
1993
|
+
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
1994
|
+
if (subs.length === 0) return [];
|
|
1995
|
+
if (!parsed.filter) return subs;
|
|
1996
|
+
const lower = parsed.filter.toLowerCase();
|
|
1997
|
+
return subs.filter((c) => c.name.toLowerCase().startsWith(lower));
|
|
1998
|
+
}
|
|
1999
|
+
return registry.getCommands(parsed.filter);
|
|
2000
|
+
}, [registry, parsed.isSlash, parsed.parentCommand, parsed.filter, dismissed, isSubcommandMode]);
|
|
2001
|
+
const showPopup = parsed.isSlash && filteredCommands.length > 0 && !dismissed;
|
|
2002
|
+
if (selectedIndex >= filteredCommands.length && filteredCommands.length > 0) {
|
|
2003
|
+
setSelectedIndex(filteredCommands.length - 1);
|
|
2004
|
+
}
|
|
2005
|
+
return {
|
|
2006
|
+
showPopup,
|
|
2007
|
+
filteredCommands,
|
|
2008
|
+
selectedIndex,
|
|
2009
|
+
setSelectedIndex,
|
|
2010
|
+
isSubcommandMode,
|
|
2011
|
+
setShowPopup: (val) => {
|
|
2012
|
+
if (typeof val === "function") {
|
|
2013
|
+
setDismissed((prev) => {
|
|
2014
|
+
const nextVal = val(!prev);
|
|
2015
|
+
return !nextVal;
|
|
2016
|
+
});
|
|
2017
|
+
} else {
|
|
2018
|
+
setDismissed(!val);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/flows/input-area-flow.ts
|
|
2025
|
+
function getAutocompletePopupAction(key) {
|
|
2026
|
+
if (key.upArrow === true) return "previous";
|
|
2027
|
+
if (key.downArrow === true) return "next";
|
|
2028
|
+
if (key.escape === true) return "close";
|
|
2029
|
+
if (key.tab === true) return "complete";
|
|
2030
|
+
return void 0;
|
|
2031
|
+
}
|
|
2032
|
+
function getPendingPromptInputAction(key) {
|
|
2033
|
+
if (key.backspace === true || key.delete === true) {
|
|
2034
|
+
return "cancelQueue";
|
|
2035
|
+
}
|
|
2036
|
+
return void 0;
|
|
2037
|
+
}
|
|
2038
|
+
function getPromptHistoryInputAction(key) {
|
|
2039
|
+
if (key.upArrow === true) return "previous";
|
|
2040
|
+
if (key.downArrow === true) return "next";
|
|
2041
|
+
return void 0;
|
|
2042
|
+
}
|
|
2043
|
+
function createPromptHistoryNavigationState() {
|
|
2044
|
+
return { selectedIndex: null, draft: "" };
|
|
2045
|
+
}
|
|
2046
|
+
function navigatePromptHistory(value, history, state, action) {
|
|
2047
|
+
if (history.length === 0) {
|
|
2048
|
+
return { value, cursorHint: value.length, state };
|
|
2049
|
+
}
|
|
2050
|
+
if (action === "previous") {
|
|
2051
|
+
const selectedIndex = state.selectedIndex === null ? history.length - 1 : Math.max(0, state.selectedIndex - 1);
|
|
2052
|
+
const nextValue = history[selectedIndex] ?? value;
|
|
2053
|
+
return {
|
|
2054
|
+
value: nextValue,
|
|
2055
|
+
cursorHint: nextValue.length,
|
|
2056
|
+
state: { selectedIndex, draft: state.selectedIndex === null ? value : state.draft }
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
if (state.selectedIndex === null) {
|
|
2060
|
+
return { value, cursorHint: value.length, state };
|
|
2061
|
+
}
|
|
2062
|
+
if (state.selectedIndex < history.length - 1) {
|
|
2063
|
+
const selectedIndex = state.selectedIndex + 1;
|
|
2064
|
+
const nextValue = history[selectedIndex] ?? value;
|
|
2065
|
+
return {
|
|
2066
|
+
value: nextValue,
|
|
2067
|
+
cursorHint: nextValue.length,
|
|
2068
|
+
state: { ...state, selectedIndex }
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
return {
|
|
2072
|
+
value: state.draft,
|
|
2073
|
+
cursorHint: state.draft.length,
|
|
2074
|
+
state: createPromptHistoryNavigationState()
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
function appendPromptHistory(history, value) {
|
|
2078
|
+
const prompt = value.trim();
|
|
2079
|
+
if (prompt.length === 0) return [...history];
|
|
2080
|
+
if (history[history.length - 1] === prompt) return [...history];
|
|
2081
|
+
return [...history, prompt];
|
|
2082
|
+
}
|
|
2083
|
+
function extractPromptHistory(entries) {
|
|
2084
|
+
let prompts = [];
|
|
2085
|
+
for (const entry of entries) {
|
|
2086
|
+
if (entry.category !== "chat" || entry.type !== "user") continue;
|
|
2087
|
+
const data = entry.data;
|
|
2088
|
+
if (typeof data?.content !== "string") continue;
|
|
2089
|
+
prompts = appendPromptHistory(prompts, data.content);
|
|
2090
|
+
}
|
|
2091
|
+
return prompts;
|
|
2092
|
+
}
|
|
2093
|
+
function moveAutocompleteSelection(selectedIndex, commandCount, direction) {
|
|
2094
|
+
if (commandCount === 0) return 0;
|
|
2095
|
+
if (direction === "previous") {
|
|
2096
|
+
return selectedIndex > 0 ? selectedIndex - 1 : commandCount - 1;
|
|
2097
|
+
}
|
|
2098
|
+
return selectedIndex < commandCount - 1 ? selectedIndex + 1 : 0;
|
|
2099
|
+
}
|
|
2100
|
+
function resolveTabCompletion(value, command) {
|
|
2101
|
+
const parsed = parseSlashInput(value);
|
|
2102
|
+
if (parsed.parentCommand) {
|
|
2103
|
+
return { type: "insert", value: `/${parsed.parentCommand} ${command.name} ` };
|
|
2104
|
+
}
|
|
2105
|
+
if (command.subcommands && command.subcommands.length > 0) {
|
|
2106
|
+
return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
|
|
2107
|
+
}
|
|
2108
|
+
return { type: "insert", value: `/${command.name} ` };
|
|
2109
|
+
}
|
|
2110
|
+
function resolveEnterCommandSelection(value, command) {
|
|
2111
|
+
const parsed = parseSlashInput(value);
|
|
2112
|
+
if (parsed.parentCommand) {
|
|
2113
|
+
return { type: "submit", value: `/${parsed.parentCommand} ${command.name}` };
|
|
2114
|
+
}
|
|
2115
|
+
if (command.subcommands && command.subcommands.length > 0) {
|
|
2116
|
+
return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
|
|
2117
|
+
}
|
|
2118
|
+
return { type: "submit", value: `/${command.name}` };
|
|
2119
|
+
}
|
|
2120
|
+
function createPasteLabelChange(value, cursorPosition, pasteId, text) {
|
|
2121
|
+
const lineCount = text.split("\n").length;
|
|
2122
|
+
const label = `[Pasted text #${pasteId} +${lineCount} lines]`;
|
|
2123
|
+
return {
|
|
2124
|
+
value: value.slice(0, cursorPosition) + label + value.slice(cursorPosition),
|
|
2125
|
+
cursorHint: cursorPosition + label.length,
|
|
2126
|
+
label,
|
|
2127
|
+
lineCount
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
function shouldSubmitInput(text) {
|
|
2131
|
+
return text.trim().length > 0;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// src/InputArea.tsx
|
|
2135
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2136
|
+
var PENDING_PROMPT_DISPLAY_MAX = 50;
|
|
2137
|
+
var PENDING_PROMPT_TAIL_KEEP = 47;
|
|
2138
|
+
var BORDER_HORIZONTAL = 2;
|
|
2139
|
+
var PADDING_LEFT = 1;
|
|
2140
|
+
var PROMPT_WIDTH = 2;
|
|
2141
|
+
var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
|
|
2142
|
+
var DEFAULT_TERMINAL_COLUMNS = 80;
|
|
2143
|
+
function InputArea({
|
|
2144
|
+
onSubmit,
|
|
2145
|
+
onCancelQueue,
|
|
2146
|
+
isDisabled,
|
|
2147
|
+
isAborting,
|
|
2148
|
+
pendingPrompt,
|
|
2149
|
+
registry,
|
|
2150
|
+
sessionName,
|
|
2151
|
+
history
|
|
2152
|
+
}) {
|
|
2153
|
+
const [value, setValue] = useState8("");
|
|
2154
|
+
const [cursorHint, setCursorHint] = useState8(null);
|
|
2155
|
+
const [historyState, setHistoryState] = useState8(createPromptHistoryNavigationState);
|
|
2156
|
+
const [localPromptHistory, setLocalPromptHistory] = useState8([]);
|
|
2157
|
+
const restoredPromptHistory = useMemo4(() => extractPromptHistory(history ?? []), [history]);
|
|
2158
|
+
const promptHistory = useMemo4(
|
|
2159
|
+
() => localPromptHistory.reduce(
|
|
2160
|
+
(prompts, prompt) => appendPromptHistory(prompts, prompt),
|
|
2161
|
+
restoredPromptHistory
|
|
2162
|
+
),
|
|
2163
|
+
[restoredPromptHistory, localPromptHistory]
|
|
2164
|
+
);
|
|
2165
|
+
const pasteStore = useRef4(/* @__PURE__ */ new Map());
|
|
2166
|
+
const { columns } = useWindowSize();
|
|
2167
|
+
const terminalColumns = columns > 0 ? columns : DEFAULT_TERMINAL_COLUMNS;
|
|
2168
|
+
const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
|
|
2169
|
+
const pasteIdRef = useRef4(0);
|
|
2170
|
+
const {
|
|
2171
|
+
showPopup,
|
|
2172
|
+
filteredCommands,
|
|
2173
|
+
selectedIndex,
|
|
2174
|
+
setSelectedIndex,
|
|
2175
|
+
isSubcommandMode,
|
|
2176
|
+
setShowPopup
|
|
2177
|
+
} = useAutocomplete(value, registry);
|
|
2178
|
+
const handlePaste = useCallback4((text, cursorPosition) => {
|
|
2179
|
+
pasteIdRef.current += 1;
|
|
2180
|
+
const id = pasteIdRef.current;
|
|
2181
|
+
pasteStore.current.set(id, text);
|
|
2182
|
+
setValue((prev) => {
|
|
2183
|
+
const change = createPasteLabelChange(prev, cursorPosition, id, text);
|
|
2184
|
+
setCursorHint(change.cursorHint);
|
|
2185
|
+
return change.value;
|
|
2186
|
+
});
|
|
2187
|
+
}, []);
|
|
2188
|
+
const resetHistoryNavigation = useCallback4(() => {
|
|
2189
|
+
setHistoryState(createPromptHistoryNavigationState());
|
|
2190
|
+
}, []);
|
|
2191
|
+
const recordPromptHistory = useCallback4((prompt) => {
|
|
2192
|
+
setLocalPromptHistory((prev) => appendPromptHistory(prev, prompt));
|
|
2193
|
+
}, []);
|
|
2194
|
+
const submitPrompt = useCallback4(
|
|
2195
|
+
(prompt) => {
|
|
2196
|
+
recordPromptHistory(prompt);
|
|
2197
|
+
resetHistoryNavigation();
|
|
2198
|
+
onSubmit(prompt);
|
|
2199
|
+
},
|
|
2200
|
+
[onSubmit, recordPromptHistory, resetHistoryNavigation]
|
|
2201
|
+
);
|
|
2202
|
+
const tabCompleteCommand = useCallback4(
|
|
2203
|
+
(cmd) => {
|
|
2204
|
+
const result = resolveTabCompletion(value, cmd);
|
|
2205
|
+
if (result.type === "insert") {
|
|
2206
|
+
setValue(result.value);
|
|
2207
|
+
if (result.selectedIndex !== void 0) {
|
|
2208
|
+
setSelectedIndex(result.selectedIndex);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
[value, setSelectedIndex]
|
|
2213
|
+
);
|
|
2214
|
+
const enterSelectCommand = useCallback4(
|
|
2215
|
+
(cmd) => {
|
|
2216
|
+
const result = resolveEnterCommandSelection(value, cmd);
|
|
2217
|
+
if (result.type === "insert") {
|
|
2218
|
+
setValue(result.value);
|
|
2219
|
+
if (result.selectedIndex !== void 0) {
|
|
2220
|
+
setSelectedIndex(result.selectedIndex);
|
|
2221
|
+
}
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
setValue("");
|
|
2225
|
+
submitPrompt(result.value);
|
|
2226
|
+
},
|
|
2227
|
+
[value, submitPrompt, setSelectedIndex]
|
|
2228
|
+
);
|
|
2229
|
+
const handleSubmit = useCallback4(
|
|
2230
|
+
(text) => {
|
|
2231
|
+
if (!shouldSubmitInput(text)) return;
|
|
2232
|
+
if (showPopup && filteredCommands[selectedIndex]) {
|
|
2233
|
+
enterSelectCommand(filteredCommands[selectedIndex]);
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
const expanded = expandPasteLabels(text.trim(), pasteStore.current);
|
|
2237
|
+
setValue("");
|
|
2238
|
+
pasteStore.current.clear();
|
|
2239
|
+
pasteIdRef.current = 0;
|
|
2240
|
+
submitPrompt(expanded);
|
|
2241
|
+
},
|
|
2242
|
+
[showPopup, filteredCommands, selectedIndex, enterSelectCommand, submitPrompt]
|
|
2243
|
+
);
|
|
2244
|
+
useInput2(
|
|
2245
|
+
(_input, key) => {
|
|
2246
|
+
if (!showPopup) return;
|
|
2247
|
+
const action = getAutocompletePopupAction(key);
|
|
2248
|
+
if (action === "previous" || action === "next") {
|
|
2249
|
+
setSelectedIndex(
|
|
2250
|
+
(prev) => moveAutocompleteSelection(prev, filteredCommands.length, action)
|
|
2251
|
+
);
|
|
2252
|
+
} else if (action === "close") {
|
|
2253
|
+
setShowPopup(false);
|
|
2254
|
+
} else if (action === "complete") {
|
|
2255
|
+
const cmd = filteredCommands[selectedIndex];
|
|
2256
|
+
if (cmd) tabCompleteCommand(cmd);
|
|
2257
|
+
}
|
|
2258
|
+
},
|
|
2259
|
+
{ isActive: showPopup && !isDisabled }
|
|
2260
|
+
);
|
|
2261
|
+
useInput2(
|
|
2262
|
+
(_input, key) => {
|
|
2263
|
+
const action = getPromptHistoryInputAction(key);
|
|
2264
|
+
if (!action) return;
|
|
2265
|
+
const result = navigatePromptHistory(value, promptHistory, historyState, action);
|
|
2266
|
+
setValue(result.value);
|
|
2267
|
+
setCursorHint(result.cursorHint);
|
|
2268
|
+
setHistoryState(result.state);
|
|
2269
|
+
},
|
|
2270
|
+
{ isActive: !showPopup && !isDisabled && !pendingPrompt }
|
|
2271
|
+
);
|
|
2272
|
+
useInput2(
|
|
2273
|
+
(_input, key) => {
|
|
2274
|
+
if (getPendingPromptInputAction(key) === "cancelQueue" && pendingPrompt) {
|
|
2275
|
+
onCancelQueue?.();
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
{ isActive: !!pendingPrompt }
|
|
2279
|
+
);
|
|
2280
|
+
const borderColor = isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green";
|
|
2281
|
+
const innerWidth = Math.max(1, terminalColumns - BORDER_HORIZONTAL);
|
|
2282
|
+
const topBorder = (() => {
|
|
2283
|
+
if (sessionName) {
|
|
2284
|
+
const label = ` "${sessionName}" `;
|
|
2285
|
+
const rightPad = 2;
|
|
2286
|
+
const leftLen = Math.max(0, innerWidth - label.length - rightPad);
|
|
2287
|
+
return { left: "\u250C" + "\u2500".repeat(leftLen), label, right: "\u2500".repeat(rightPad) + "\u2510" };
|
|
2288
|
+
}
|
|
2289
|
+
return { left: "\u250C" + "\u2500".repeat(innerWidth), label: "", right: "\u2510" };
|
|
2290
|
+
})();
|
|
2291
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
2292
|
+
showPopup && /* @__PURE__ */ jsx10(
|
|
2293
|
+
SlashAutocomplete,
|
|
2294
|
+
{
|
|
2295
|
+
commands: filteredCommands,
|
|
2296
|
+
selectedIndex,
|
|
2297
|
+
visible: showPopup,
|
|
2298
|
+
isSubcommandMode
|
|
2299
|
+
}
|
|
2300
|
+
),
|
|
2301
|
+
/* @__PURE__ */ jsxs6(Text9, { color: borderColor, children: [
|
|
2302
|
+
topBorder.left,
|
|
2303
|
+
topBorder.label ? /* @__PURE__ */ jsx10(Text9, { backgroundColor: borderColor, color: "black", bold: true, children: topBorder.label }) : null,
|
|
2304
|
+
topBorder.right
|
|
2305
|
+
] }),
|
|
2306
|
+
/* @__PURE__ */ jsx10(Box7, { borderStyle: "single", borderTop: false, borderColor, paddingLeft: 1, children: isAborting ? /* @__PURE__ */ jsx10(Text9, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ jsxs6(Text9, { color: "cyan", children: [
|
|
2307
|
+
" ",
|
|
2308
|
+
"Queued:",
|
|
2309
|
+
" ",
|
|
2310
|
+
pendingPrompt.length > PENDING_PROMPT_DISPLAY_MAX ? pendingPrompt.slice(0, PENDING_PROMPT_TAIL_KEEP) + "..." : pendingPrompt,
|
|
2311
|
+
" ",
|
|
2312
|
+
/* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "(Backspace to cancel)" })
|
|
2313
|
+
] }) : isDisabled ? /* @__PURE__ */ jsx10(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ jsxs6(Box7, { children: [
|
|
2314
|
+
/* @__PURE__ */ jsx10(Text9, { color: "green", bold: true, children: "> " }),
|
|
2315
|
+
/* @__PURE__ */ jsx10(
|
|
2316
|
+
CjkTextInput,
|
|
2317
|
+
{
|
|
2318
|
+
value,
|
|
2319
|
+
onChange: (v) => {
|
|
2320
|
+
setValue(v);
|
|
2321
|
+
resetHistoryNavigation();
|
|
2322
|
+
setCursorHint(null);
|
|
2323
|
+
},
|
|
2324
|
+
onSubmit: handleSubmit,
|
|
2325
|
+
onPaste: handlePaste,
|
|
2326
|
+
placeholder: "Type a message or /help",
|
|
2327
|
+
availableWidth,
|
|
2328
|
+
cursorHint,
|
|
2329
|
+
enableVerticalNavigation: false
|
|
2330
|
+
}
|
|
2331
|
+
)
|
|
2332
|
+
] }) })
|
|
2333
|
+
] });
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/ConfirmPrompt.tsx
|
|
2337
|
+
import { useState as useState9, useRef as useRef5, useCallback as useCallback5 } from "react";
|
|
2338
|
+
import { Box as Box8, Text as Text10, useInput as useInput3 } from "ink";
|
|
2339
|
+
|
|
2340
|
+
// src/flows/selection-flow.ts
|
|
2341
|
+
function createSelectionFlowState() {
|
|
2342
|
+
return { selectedIndex: 0, scrollOffset: 0, resolved: false };
|
|
2343
|
+
}
|
|
2344
|
+
function getVerticalSelectionInputAction(key) {
|
|
2345
|
+
if (key.escape === true) return "cancel";
|
|
2346
|
+
if (key.upArrow === true) return "previous";
|
|
2347
|
+
if (key.downArrow === true) return "next";
|
|
2348
|
+
if (key.return === true) return "select";
|
|
2349
|
+
return void 0;
|
|
2350
|
+
}
|
|
2351
|
+
function getDirectionalSelectionInputAction(key) {
|
|
2352
|
+
if (key.escape === true) return "cancel";
|
|
2353
|
+
if (key.leftArrow === true || key.upArrow === true) return "previous";
|
|
2354
|
+
if (key.rightArrow === true || key.downArrow === true) return "next";
|
|
2355
|
+
if (key.return === true) return "select";
|
|
2356
|
+
return void 0;
|
|
2357
|
+
}
|
|
2358
|
+
function applySelectionInput(state, action, options) {
|
|
2359
|
+
if (state.resolved) {
|
|
2360
|
+
return { state, effect: { type: "none" } };
|
|
2361
|
+
}
|
|
2362
|
+
if (action === "cancel") {
|
|
2363
|
+
return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
|
|
2364
|
+
}
|
|
2365
|
+
if (options.enabled === false || options.itemCount === 0) {
|
|
2366
|
+
return { state, effect: { type: "none" } };
|
|
2367
|
+
}
|
|
2368
|
+
if (action === "select") {
|
|
2369
|
+
const index = clampIndex(state.selectedIndex, options.itemCount);
|
|
2370
|
+
return {
|
|
2371
|
+
state: { ...state, selectedIndex: index, resolved: true },
|
|
2372
|
+
effect: { type: "select", index }
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
const selectedIndex = moveSelection(state.selectedIndex, action, options);
|
|
2376
|
+
const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
|
|
2377
|
+
return { state: { ...state, selectedIndex, scrollOffset }, effect: { type: "none" } };
|
|
2378
|
+
}
|
|
2379
|
+
function normalizeSelectionState(state, options) {
|
|
2380
|
+
if (options.itemCount === 0) {
|
|
2381
|
+
return { ...state, selectedIndex: 0, scrollOffset: 0 };
|
|
2382
|
+
}
|
|
2383
|
+
const selectedIndex = clampIndex(state.selectedIndex, options.itemCount);
|
|
2384
|
+
const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
|
|
2385
|
+
if (selectedIndex === state.selectedIndex && scrollOffset === state.scrollOffset) {
|
|
2386
|
+
return state;
|
|
2387
|
+
}
|
|
2388
|
+
return {
|
|
2389
|
+
...state,
|
|
2390
|
+
selectedIndex,
|
|
2391
|
+
scrollOffset
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
function moveSelection(selectedIndex, action, options) {
|
|
2395
|
+
if (action === "previous") {
|
|
2396
|
+
if (options.wrap === true && selectedIndex === 0) return options.itemCount - 1;
|
|
2397
|
+
return Math.max(0, selectedIndex - 1);
|
|
2398
|
+
}
|
|
2399
|
+
if (options.wrap === true && selectedIndex === options.itemCount - 1) return 0;
|
|
2400
|
+
return Math.min(options.itemCount - 1, selectedIndex + 1);
|
|
2401
|
+
}
|
|
2402
|
+
function resolveScrollOffset(selectedIndex, scrollOffset, options) {
|
|
2403
|
+
const maxVisible = options.maxVisible ?? options.itemCount;
|
|
2404
|
+
if (maxVisible <= 0) return 0;
|
|
2405
|
+
if (selectedIndex < scrollOffset) return selectedIndex;
|
|
2406
|
+
if (selectedIndex >= scrollOffset + maxVisible) return selectedIndex - maxVisible + 1;
|
|
2407
|
+
return Math.max(0, scrollOffset);
|
|
2408
|
+
}
|
|
2409
|
+
function clampIndex(index, itemCount) {
|
|
2410
|
+
return Math.min(Math.max(index, 0), itemCount - 1);
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/flows/confirm-prompt-flow.ts
|
|
2414
|
+
function getConfirmPromptInputAction(input, key, optionCount) {
|
|
2415
|
+
const action = getDirectionalSelectionInputAction({ ...key, escape: false });
|
|
2416
|
+
if (action !== void 0) {
|
|
2417
|
+
return action;
|
|
2418
|
+
}
|
|
2419
|
+
if (optionCount === 2 && input === "y") {
|
|
2420
|
+
return { type: "shortcut", index: 0 };
|
|
2421
|
+
}
|
|
2422
|
+
if (optionCount === 2 && input === "n") {
|
|
2423
|
+
return { type: "shortcut", index: 1 };
|
|
2424
|
+
}
|
|
2425
|
+
return void 0;
|
|
2426
|
+
}
|
|
2427
|
+
function applyConfirmPromptInput(state, action, optionCount) {
|
|
2428
|
+
if (state.resolved) {
|
|
2429
|
+
return { state, effect: { type: "none" } };
|
|
2430
|
+
}
|
|
2431
|
+
if (typeof action !== "string") {
|
|
2432
|
+
return {
|
|
2433
|
+
state: { ...state, selectedIndex: action.index, resolved: true },
|
|
2434
|
+
effect: { type: "select", index: action.index }
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
return applySelectionInput(state, action, { itemCount: optionCount });
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// src/ConfirmPrompt.tsx
|
|
2441
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2442
|
+
function ConfirmPrompt({
|
|
2443
|
+
message,
|
|
2444
|
+
options = ["Yes", "No"],
|
|
2445
|
+
onSelect
|
|
2446
|
+
}) {
|
|
2447
|
+
const [state, setState] = useState9(() => createSelectionFlowState());
|
|
2448
|
+
const stateRef = useRef5(state);
|
|
2449
|
+
const applyAction = useCallback5(
|
|
2450
|
+
(action) => {
|
|
2451
|
+
const result = applyConfirmPromptInput(stateRef.current, action, options.length);
|
|
2452
|
+
stateRef.current = result.state;
|
|
2453
|
+
setState(result.state);
|
|
2454
|
+
if (result.effect.type === "select") {
|
|
2455
|
+
onSelect(result.effect.index);
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
[onSelect, options.length]
|
|
2459
|
+
);
|
|
2460
|
+
useInput3((input, key) => {
|
|
2461
|
+
const action = getConfirmPromptInputAction(input, key, options.length);
|
|
2462
|
+
if (action !== void 0) {
|
|
2463
|
+
applyAction(action);
|
|
2464
|
+
}
|
|
2465
|
+
});
|
|
2466
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2467
|
+
/* @__PURE__ */ jsx11(Text10, { color: "yellow", children: message }),
|
|
2468
|
+
/* @__PURE__ */ jsx11(Box8, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsx11(Box8, { marginRight: 2, children: /* @__PURE__ */ jsxs7(
|
|
2469
|
+
Text10,
|
|
2470
|
+
{
|
|
2471
|
+
color: i === state.selectedIndex ? "cyan" : void 0,
|
|
2472
|
+
bold: i === state.selectedIndex,
|
|
2473
|
+
children: [
|
|
2474
|
+
i === state.selectedIndex ? "> " : " ",
|
|
2475
|
+
opt
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
) }, opt)) }),
|
|
2479
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
2480
|
+
] });
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
// src/InteractivePrompt.tsx
|
|
2484
|
+
import { Box as Box11, Text as Text13 } from "ink";
|
|
2485
|
+
|
|
2486
|
+
// src/ListPicker.tsx
|
|
2487
|
+
import { useState as useState10, useRef as useRef6, useCallback as useCallback6 } from "react";
|
|
2488
|
+
import { Box as Box9, Text as Text11, useInput as useInput4 } from "ink";
|
|
2489
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2490
|
+
var DEFAULT_MAX_VISIBLE = 3;
|
|
2491
|
+
function ListPicker({
|
|
2492
|
+
items,
|
|
2493
|
+
renderItem,
|
|
2494
|
+
onSelect,
|
|
2495
|
+
onCancel,
|
|
2496
|
+
maxVisible = DEFAULT_MAX_VISIBLE
|
|
2497
|
+
}) {
|
|
2498
|
+
const [state, setState] = useState10(() => createSelectionFlowState());
|
|
2499
|
+
const stateRef = useRef6(state);
|
|
2500
|
+
const applyAction = useCallback6(
|
|
2501
|
+
(action) => {
|
|
2502
|
+
const result = applySelectionInput(stateRef.current, action, {
|
|
2503
|
+
itemCount: items.length,
|
|
2504
|
+
maxVisible
|
|
2505
|
+
});
|
|
2506
|
+
stateRef.current = result.state;
|
|
2507
|
+
setState(result.state);
|
|
2508
|
+
if (result.effect.type === "cancel") {
|
|
2509
|
+
onCancel();
|
|
2510
|
+
} else if (result.effect.type === "select") {
|
|
2511
|
+
const item = items[result.effect.index];
|
|
2512
|
+
if (item !== void 0) {
|
|
2513
|
+
onSelect(item);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
},
|
|
2517
|
+
[items, maxVisible, onCancel, onSelect]
|
|
2518
|
+
);
|
|
2519
|
+
useInput4((_input, key) => {
|
|
2520
|
+
const action = getVerticalSelectionInputAction(key);
|
|
2521
|
+
if (action !== void 0) {
|
|
2522
|
+
applyAction(action);
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2525
|
+
if (items.length === 0) {
|
|
2526
|
+
return /* @__PURE__ */ jsx12(Box9, {});
|
|
2527
|
+
}
|
|
2528
|
+
const normalizedState = normalizeSelectionState(state, { itemCount: items.length, maxVisible });
|
|
2529
|
+
if (normalizedState !== state) {
|
|
2530
|
+
stateRef.current = normalizedState;
|
|
2531
|
+
}
|
|
2532
|
+
const { selectedIndex, scrollOffset } = normalizedState;
|
|
2533
|
+
const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
|
|
2534
|
+
const hasMore = scrollOffset + maxVisible < items.length;
|
|
2535
|
+
const hasLess = scrollOffset > 0;
|
|
2536
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
2537
|
+
hasLess && /* @__PURE__ */ jsxs8(Text11, { dimColor: true, children: [
|
|
2538
|
+
" \u2191 ",
|
|
2539
|
+
scrollOffset,
|
|
2540
|
+
" more above"
|
|
2541
|
+
] }),
|
|
2542
|
+
visibleItems.map((item, index) => /* @__PURE__ */ jsx12(Box9, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
|
|
2543
|
+
hasMore && /* @__PURE__ */ jsxs8(Text11, { dimColor: true, children: [
|
|
2544
|
+
" \u2193 ",
|
|
2545
|
+
items.length - scrollOffset - maxVisible,
|
|
2546
|
+
" more below"
|
|
2547
|
+
] })
|
|
2548
|
+
] });
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/TextPrompt.tsx
|
|
2552
|
+
import { useState as useState11, useRef as useRef7, useCallback as useCallback7 } from "react";
|
|
2553
|
+
import { Box as Box10, Text as Text12, useInput as useInput5 } from "ink";
|
|
2554
|
+
|
|
2555
|
+
// src/flows/text-prompt-flow.ts
|
|
2556
|
+
function createTextPromptFlowState() {
|
|
2557
|
+
return { value: "", resolved: false };
|
|
2558
|
+
}
|
|
2559
|
+
function getTextPromptInputAction(input, key) {
|
|
2560
|
+
if (key.escape === true) {
|
|
2561
|
+
return { type: "cancel" };
|
|
2562
|
+
}
|
|
2563
|
+
if (key.return === true) {
|
|
2564
|
+
return { type: "submit" };
|
|
2565
|
+
}
|
|
2566
|
+
if (key.backspace === true || key.delete === true) {
|
|
2567
|
+
return { type: "delete" };
|
|
2568
|
+
}
|
|
2569
|
+
if (input && key.ctrl !== true && key.meta !== true) {
|
|
2570
|
+
return { type: "insert", value: input };
|
|
2571
|
+
}
|
|
2572
|
+
return void 0;
|
|
2573
|
+
}
|
|
2574
|
+
function applyTextPromptInput(state, action, options) {
|
|
2575
|
+
if (state.resolved) {
|
|
2576
|
+
return { state, effect: { type: "none" } };
|
|
2577
|
+
}
|
|
2578
|
+
if (action.type === "cancel") {
|
|
2579
|
+
return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
|
|
2580
|
+
}
|
|
2581
|
+
if (action.type === "delete") {
|
|
2582
|
+
return {
|
|
2583
|
+
state: { ...state, value: state.value.slice(0, -1), error: void 0 },
|
|
2584
|
+
effect: { type: "none" }
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
if (action.type === "insert") {
|
|
2588
|
+
return {
|
|
2589
|
+
state: { ...state, value: state.value + action.value, error: void 0 },
|
|
2590
|
+
effect: { type: "none" }
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
return submitTextPromptValue(state, options);
|
|
2594
|
+
}
|
|
2595
|
+
function submitTextPromptValue(state, options) {
|
|
2596
|
+
const trimmed = state.value.trim();
|
|
2597
|
+
if (!trimmed && !options.allowEmpty) {
|
|
2598
|
+
const emptyError = options.validate?.(trimmed);
|
|
2599
|
+
return {
|
|
2600
|
+
state: emptyError ? { ...state, error: emptyError } : state,
|
|
2601
|
+
effect: { type: "none" }
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
const error = options.validate?.(trimmed);
|
|
2605
|
+
if (error !== void 0) {
|
|
2606
|
+
return { state: { ...state, error }, effect: { type: "none" } };
|
|
2607
|
+
}
|
|
2608
|
+
return { state: { ...state, resolved: true }, effect: { type: "submit", value: trimmed } };
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// src/TextPrompt.tsx
|
|
2612
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2613
|
+
function TextPrompt({
|
|
2614
|
+
title,
|
|
2615
|
+
description,
|
|
2616
|
+
placeholder,
|
|
2617
|
+
onSubmit,
|
|
2618
|
+
onCancel,
|
|
2619
|
+
validate,
|
|
2620
|
+
allowEmpty = false,
|
|
2621
|
+
masked = false
|
|
2622
|
+
}) {
|
|
2623
|
+
const [state, setState] = useState11(() => createTextPromptFlowState());
|
|
2624
|
+
const stateRef = useRef7(state);
|
|
2625
|
+
const applyAction = useCallback7(
|
|
2626
|
+
(action) => {
|
|
2627
|
+
const result = applyTextPromptInput(stateRef.current, action, { allowEmpty, validate });
|
|
2628
|
+
stateRef.current = result.state;
|
|
2629
|
+
setState(result.state);
|
|
2630
|
+
if (result.effect.type === "cancel") {
|
|
2631
|
+
onCancel();
|
|
2632
|
+
} else if (result.effect.type === "submit") {
|
|
2633
|
+
onSubmit(result.effect.value);
|
|
2634
|
+
}
|
|
2635
|
+
},
|
|
2636
|
+
[allowEmpty, validate, onCancel, onSubmit]
|
|
2637
|
+
);
|
|
2638
|
+
useInput5((input, key) => {
|
|
2639
|
+
const action = getTextPromptInputAction(input, key);
|
|
2640
|
+
if (action !== void 0) applyAction(action);
|
|
2641
|
+
});
|
|
2642
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2643
|
+
/* @__PURE__ */ jsx13(Text12, { color: "yellow", bold: true, children: title }),
|
|
2644
|
+
/* @__PURE__ */ jsx13(PromptDescription, { description }),
|
|
2645
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, children: [
|
|
2646
|
+
/* @__PURE__ */ jsx13(Text12, { color: "cyan", children: "> " }),
|
|
2647
|
+
state.value ? /* @__PURE__ */ jsx13(Text12, { children: masked ? "*".repeat(state.value.length) : state.value }) : placeholder ? /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: placeholder }) : null,
|
|
2648
|
+
/* @__PURE__ */ jsx13(Text12, { color: "cyan", children: "\u2588" })
|
|
2649
|
+
] }),
|
|
2650
|
+
state.error && /* @__PURE__ */ jsx13(Text12, { color: "red", children: state.error }),
|
|
2651
|
+
/* @__PURE__ */ jsx13(Text12, { dimColor: true, children: " Enter Submit Esc Cancel" })
|
|
2652
|
+
] });
|
|
2653
|
+
}
|
|
2654
|
+
function PromptDescription({ description }) {
|
|
2655
|
+
if (description === void 0 || description.length === 0) {
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
return /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: description });
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// src/InteractivePrompt.tsx
|
|
2662
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2663
|
+
function InteractivePrompt({
|
|
2664
|
+
prompt,
|
|
2665
|
+
onSubmit,
|
|
2666
|
+
onCancel
|
|
2667
|
+
}) {
|
|
2668
|
+
if (prompt.kind === "text") {
|
|
2669
|
+
return /* @__PURE__ */ jsx14(
|
|
2670
|
+
TextPrompt,
|
|
2671
|
+
{
|
|
2672
|
+
title: prompt.title,
|
|
2673
|
+
description: prompt.description,
|
|
2674
|
+
placeholder: prompt.placeholder,
|
|
2675
|
+
allowEmpty: prompt.allowEmpty,
|
|
2676
|
+
masked: prompt.masked,
|
|
2677
|
+
validate: prompt.validate,
|
|
2678
|
+
onSubmit,
|
|
2679
|
+
onCancel
|
|
2680
|
+
},
|
|
2681
|
+
`text:${prompt.title}`
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
2685
|
+
/* @__PURE__ */ jsx14(Text13, { bold: true, children: prompt.title }),
|
|
2686
|
+
prompt.description !== void 0 && prompt.description.length > 0 && /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: prompt.description }),
|
|
2687
|
+
/* @__PURE__ */ jsx14(
|
|
2688
|
+
ListPicker,
|
|
2689
|
+
{
|
|
2690
|
+
items: [...prompt.options],
|
|
2691
|
+
maxVisible: prompt.maxVisible,
|
|
2692
|
+
renderItem: (option, isSelected) => /* @__PURE__ */ jsxs10(Text13, { color: isSelected ? "cyan" : void 0, children: [
|
|
2693
|
+
isSelected ? "> " : " ",
|
|
2694
|
+
option.label
|
|
2695
|
+
] }),
|
|
2696
|
+
onSelect: (option) => onSubmit(option.value),
|
|
2697
|
+
onCancel
|
|
2698
|
+
}
|
|
2699
|
+
)
|
|
2700
|
+
] });
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// src/PermissionPrompt.tsx
|
|
2704
|
+
import React11 from "react";
|
|
2705
|
+
import { Box as Box12, Text as Text14, useInput as useInput6 } from "ink";
|
|
2706
|
+
|
|
2707
|
+
// src/flows/permission-prompt-flow.ts
|
|
2708
|
+
var PERMISSION_PROMPT_OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
2709
|
+
function getPermissionPromptInputAction(input, key) {
|
|
2710
|
+
const action = getDirectionalSelectionInputAction({ ...key, escape: false });
|
|
2711
|
+
if (action !== void 0) {
|
|
2712
|
+
return action;
|
|
2713
|
+
}
|
|
2714
|
+
if (input === "y" || input === "1") {
|
|
2715
|
+
return { type: "shortcut", index: 0 };
|
|
2716
|
+
}
|
|
2717
|
+
if (input === "a" || input === "2") {
|
|
2718
|
+
return { type: "shortcut", index: 1 };
|
|
2719
|
+
}
|
|
2720
|
+
if (input === "n" || input === "d" || input === "3") {
|
|
2721
|
+
return { type: "shortcut", index: 2 };
|
|
2722
|
+
}
|
|
2723
|
+
return void 0;
|
|
2724
|
+
}
|
|
2725
|
+
function applyPermissionPromptInput(state, action) {
|
|
2726
|
+
if (state.resolved) {
|
|
2727
|
+
return { state, effect: { type: "none" } };
|
|
2728
|
+
}
|
|
2729
|
+
if (typeof action !== "string") {
|
|
2730
|
+
return resolvePermissionIndex(state, action.index);
|
|
2731
|
+
}
|
|
2732
|
+
const result = applySelectionInput(state, action, {
|
|
2733
|
+
itemCount: PERMISSION_PROMPT_OPTIONS.length
|
|
2734
|
+
});
|
|
2735
|
+
if (result.effect.type !== "select") {
|
|
2736
|
+
return { state: result.state, effect: { type: "none" } };
|
|
2737
|
+
}
|
|
2738
|
+
return {
|
|
2739
|
+
state: result.state,
|
|
2740
|
+
effect: { type: "resolve", decision: getPermissionDecision(result.effect.index) }
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
function getPermissionDecision(index) {
|
|
2744
|
+
if (index === 0) return true;
|
|
2745
|
+
if (index === 1) return "allow-session";
|
|
2746
|
+
return false;
|
|
2747
|
+
}
|
|
2748
|
+
function resolvePermissionIndex(state, index) {
|
|
2749
|
+
return {
|
|
2750
|
+
state: { ...state, selectedIndex: index, resolved: true },
|
|
2751
|
+
effect: { type: "resolve", decision: getPermissionDecision(index) }
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// src/PermissionPrompt.tsx
|
|
2756
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2757
|
+
function formatArgs(args) {
|
|
2758
|
+
const entries = Object.entries(args);
|
|
2759
|
+
if (entries.length === 0) return "(no arguments)";
|
|
2760
|
+
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
2761
|
+
}
|
|
2762
|
+
function PermissionPrompt({ request }) {
|
|
2763
|
+
const [state, setState] = React11.useState(() => createSelectionFlowState());
|
|
2764
|
+
const stateRef = React11.useRef(state);
|
|
2765
|
+
const prevRequestRef = React11.useRef(request);
|
|
2766
|
+
if (prevRequestRef.current !== request) {
|
|
2767
|
+
prevRequestRef.current = request;
|
|
2768
|
+
const nextState = createSelectionFlowState();
|
|
2769
|
+
stateRef.current = nextState;
|
|
2770
|
+
setState(nextState);
|
|
2771
|
+
}
|
|
2772
|
+
const applyAction = React11.useCallback(
|
|
2773
|
+
(action) => {
|
|
2774
|
+
const result = applyPermissionPromptInput(stateRef.current, action);
|
|
2775
|
+
stateRef.current = result.state;
|
|
2776
|
+
setState(result.state);
|
|
2777
|
+
if (result.effect.type === "resolve") {
|
|
2778
|
+
request.resolve(result.effect.decision);
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
[request]
|
|
2782
|
+
);
|
|
2783
|
+
useInput6((input, key) => {
|
|
2784
|
+
const action = getPermissionPromptInputAction(input, key);
|
|
2785
|
+
if (action !== void 0) {
|
|
2786
|
+
applyAction(action);
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2790
|
+
/* @__PURE__ */ jsx15(Text14, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
2791
|
+
/* @__PURE__ */ jsxs11(Text14, { children: [
|
|
2792
|
+
"Tool:",
|
|
2793
|
+
" ",
|
|
2794
|
+
/* @__PURE__ */ jsx15(Text14, { color: "cyan", bold: true, children: request.toolName })
|
|
2795
|
+
] }),
|
|
2796
|
+
/* @__PURE__ */ jsxs11(Text14, { dimColor: true, children: [
|
|
2797
|
+
" ",
|
|
2798
|
+
formatArgs(request.toolArgs)
|
|
2799
|
+
] }),
|
|
2800
|
+
/* @__PURE__ */ jsx15(Box12, { marginTop: 1, children: PERMISSION_PROMPT_OPTIONS.map((opt, i) => /* @__PURE__ */ jsx15(Box12, { marginRight: 2, children: /* @__PURE__ */ jsxs11(
|
|
2801
|
+
Text14,
|
|
2802
|
+
{
|
|
2803
|
+
color: i === state.selectedIndex ? "cyan" : void 0,
|
|
2804
|
+
bold: i === state.selectedIndex,
|
|
2805
|
+
children: [
|
|
2806
|
+
i === state.selectedIndex ? "> " : " ",
|
|
2807
|
+
opt
|
|
2808
|
+
]
|
|
2809
|
+
}
|
|
2810
|
+
) }, opt)) }),
|
|
2811
|
+
/* @__PURE__ */ jsx15(Text14, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
2812
|
+
] });
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
// src/StreamingIndicator.tsx
|
|
2816
|
+
import { Box as Box13, Text as Text15 } from "ink";
|
|
2817
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2818
|
+
function getToolStyle(t) {
|
|
2819
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
2820
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
2821
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
2822
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
2823
|
+
}
|
|
2824
|
+
function renderThinkingFallback(isThinking) {
|
|
2825
|
+
if (!isThinking) return /* @__PURE__ */ jsx16(Fragment4, {});
|
|
2826
|
+
return /* @__PURE__ */ jsx16(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text15, { color: "yellow", children: "Thinking..." }) });
|
|
2827
|
+
}
|
|
2828
|
+
function renderTools(activeTools) {
|
|
2829
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
|
|
2830
|
+
/* @__PURE__ */ jsx16(Text15, { color: "white", bold: true, children: "Tools:" }),
|
|
2831
|
+
/* @__PURE__ */ jsx16(Text15, { children: " " }),
|
|
2832
|
+
activeTools.map((t, i) => {
|
|
2833
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
2834
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
|
|
2835
|
+
/* @__PURE__ */ jsxs12(Text15, { color, strikethrough, children: [
|
|
2836
|
+
" ",
|
|
2837
|
+
icon,
|
|
2838
|
+
" ",
|
|
2839
|
+
t.toolName,
|
|
2840
|
+
"(",
|
|
2841
|
+
t.firstArg,
|
|
2842
|
+
")"
|
|
2843
|
+
] }),
|
|
2844
|
+
t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ jsx16(ToolDiffBlock, { file: t.diffFile, lines: t.diffLines })
|
|
2845
|
+
] }, `${t.toolName}-${i}`);
|
|
2846
|
+
})
|
|
2847
|
+
] });
|
|
2848
|
+
}
|
|
2849
|
+
function StreamingIndicator({
|
|
2850
|
+
text,
|
|
2851
|
+
activeTools,
|
|
2852
|
+
isThinking = false
|
|
2853
|
+
}) {
|
|
2854
|
+
const hasTools = activeTools.length > 0;
|
|
2855
|
+
const hasText = text.length > 0;
|
|
2856
|
+
if (!hasTools && !hasText) {
|
|
2857
|
+
return renderThinkingFallback(isThinking);
|
|
2858
|
+
}
|
|
2859
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
|
|
2860
|
+
hasTools && renderTools(activeTools),
|
|
2861
|
+
hasText && /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
|
|
2862
|
+
/* @__PURE__ */ jsx16(Text15, { color: "cyan", bold: true, children: "Robota:" }),
|
|
2863
|
+
/* @__PURE__ */ jsx16(Text15, { children: " " }),
|
|
2864
|
+
/* @__PURE__ */ jsx16(Box13, { marginLeft: 2, children: /* @__PURE__ */ jsx16(Text15, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
2865
|
+
] })
|
|
2866
|
+
] });
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
// src/PluginTUI.tsx
|
|
2870
|
+
import { useState as useState14, useCallback as useCallback9 } from "react";
|
|
2871
|
+
|
|
2872
|
+
// src/MenuSelect.tsx
|
|
2873
|
+
import { useState as useState12, useCallback as useCallback8, useRef as useRef8 } from "react";
|
|
2874
|
+
import { Box as Box14, Text as Text16, useInput as useInput7 } from "ink";
|
|
2875
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2876
|
+
function MenuSelect({
|
|
2877
|
+
title,
|
|
2878
|
+
items,
|
|
2879
|
+
onSelect,
|
|
2880
|
+
onBack,
|
|
2881
|
+
loading,
|
|
2882
|
+
error
|
|
2883
|
+
}) {
|
|
2884
|
+
const [state, setState] = useState12(() => createSelectionFlowState());
|
|
2885
|
+
const stateRef = useRef8(state);
|
|
2886
|
+
const isEnabled = !loading && !error;
|
|
2887
|
+
const applyAction = useCallback8(
|
|
2888
|
+
(action) => {
|
|
2889
|
+
const result = applySelectionInput(stateRef.current, action, {
|
|
2890
|
+
itemCount: items.length,
|
|
2891
|
+
enabled: isEnabled
|
|
2892
|
+
});
|
|
2893
|
+
stateRef.current = result.state;
|
|
2894
|
+
setState(result.state);
|
|
2895
|
+
if (result.effect.type === "cancel") {
|
|
2896
|
+
onBack();
|
|
2897
|
+
} else if (result.effect.type === "select") {
|
|
2898
|
+
const item = items[result.effect.index];
|
|
2899
|
+
if (item !== void 0) {
|
|
2900
|
+
onSelect(item.value);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
},
|
|
2904
|
+
[isEnabled, items, onBack, onSelect]
|
|
2905
|
+
);
|
|
2906
|
+
useInput7((input, key) => {
|
|
2907
|
+
const action = getVerticalSelectionInputAction(key);
|
|
2908
|
+
if (action !== void 0) {
|
|
2909
|
+
applyAction(action);
|
|
2910
|
+
}
|
|
2911
|
+
});
|
|
2912
|
+
const normalizedState = normalizeSelectionState(state, { itemCount: items.length });
|
|
2913
|
+
if (normalizedState !== state) {
|
|
2914
|
+
stateRef.current = normalizedState;
|
|
2915
|
+
}
|
|
2916
|
+
const selected = normalizedState.selectedIndex;
|
|
2917
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2918
|
+
/* @__PURE__ */ jsx17(Text16, { color: "yellow", bold: true, children: title }),
|
|
2919
|
+
loading && /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text16, { dimColor: true, children: "Loading..." }) }),
|
|
2920
|
+
error && /* @__PURE__ */ jsxs13(Box14, { marginTop: 1, flexDirection: "column", children: [
|
|
2921
|
+
/* @__PURE__ */ jsx17(Text16, { color: "red", children: error }),
|
|
2922
|
+
/* @__PURE__ */ jsx17(Text16, { dimColor: true, children: "Press Esc to go back" })
|
|
2923
|
+
] }),
|
|
2924
|
+
!loading && !error && /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ jsxs13(Box14, { children: [
|
|
2925
|
+
/* @__PURE__ */ jsxs13(Text16, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
2926
|
+
i === selected ? "> " : " ",
|
|
2927
|
+
item.label
|
|
2928
|
+
] }),
|
|
2929
|
+
item.hint && /* @__PURE__ */ jsxs13(Text16, { dimColor: true, children: [
|
|
2930
|
+
" ",
|
|
2931
|
+
item.hint
|
|
2932
|
+
] })
|
|
2933
|
+
] }, item.value)) }),
|
|
2934
|
+
/* @__PURE__ */ jsx17(Text16, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
|
|
2935
|
+
] });
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
// src/plugin-tui-handlers.ts
|
|
2939
|
+
function handleMainSelect(value, nav) {
|
|
2940
|
+
if (value === "marketplace") {
|
|
2941
|
+
nav.push({ screen: "marketplace-list" });
|
|
2942
|
+
} else if (value === "installed") {
|
|
2943
|
+
nav.push({ screen: "installed-list" });
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
function handleMarketplaceListSelect(value, nav) {
|
|
2947
|
+
if (value === "__add__") {
|
|
2948
|
+
nav.push({ screen: "marketplace-add" });
|
|
2949
|
+
} else {
|
|
2950
|
+
nav.push({ screen: "marketplace-action", context: { marketplace: value } });
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
|
|
2954
|
+
if (value === "browse") {
|
|
2955
|
+
nav.push({ screen: "marketplace-browse", context: { marketplace } });
|
|
2956
|
+
} else if (value === "update") {
|
|
2957
|
+
callbacks.marketplaceUpdate(marketplace).then(() => {
|
|
2958
|
+
nav.notify(`Updated marketplace "${marketplace}".`);
|
|
2959
|
+
nav.pop();
|
|
2960
|
+
}).catch((err) => {
|
|
2961
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2962
|
+
});
|
|
2963
|
+
} else if (value === "remove") {
|
|
2964
|
+
nav.setConfirm({
|
|
2965
|
+
message: `Remove marketplace "${marketplace}" and all its plugins?`,
|
|
2966
|
+
onConfirm: () => {
|
|
2967
|
+
nav.setConfirm(void 0);
|
|
2968
|
+
callbacks.marketplaceRemove(marketplace).then(() => {
|
|
2969
|
+
nav.notify(`Removed marketplace "${marketplace}".`);
|
|
2970
|
+
nav.popN(2);
|
|
2971
|
+
}).catch((err) => {
|
|
2972
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2973
|
+
});
|
|
2974
|
+
},
|
|
2975
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
|
|
2980
|
+
const fullId = `${value}@${marketplace}`;
|
|
2981
|
+
const item = items.find((i) => i.value === value);
|
|
2982
|
+
if (item?.hint === "installed") {
|
|
2983
|
+
nav.push({ screen: "installed-action", context: { pluginId: fullId } });
|
|
2984
|
+
} else {
|
|
2985
|
+
nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
|
|
2989
|
+
const scope = value;
|
|
2990
|
+
callbacks.install(pluginId, scope).then(() => {
|
|
2991
|
+
nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
|
|
2992
|
+
nav.popN(2);
|
|
2993
|
+
}).catch((err) => {
|
|
2994
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
function handleInstalledListSelect(value, callbacks, nav) {
|
|
2998
|
+
nav.setConfirm({
|
|
2999
|
+
message: `Uninstall plugin "${value}"?`,
|
|
3000
|
+
onConfirm: () => {
|
|
3001
|
+
nav.setConfirm(void 0);
|
|
3002
|
+
callbacks.uninstall(value).then(() => {
|
|
3003
|
+
nav.notify(`Uninstalled plugin "${value}".`);
|
|
3004
|
+
nav.refresh();
|
|
3005
|
+
}).catch((err) => {
|
|
3006
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3007
|
+
});
|
|
3008
|
+
},
|
|
3009
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
|
|
3013
|
+
if (value === "uninstall") {
|
|
3014
|
+
nav.setConfirm({
|
|
3015
|
+
message: `Uninstall plugin "${pluginId}"?`,
|
|
3016
|
+
onConfirm: () => {
|
|
3017
|
+
nav.setConfirm(void 0);
|
|
3018
|
+
callbacks.uninstall(pluginId).then(() => {
|
|
3019
|
+
nav.notify(`Uninstalled plugin "${pluginId}".`);
|
|
3020
|
+
nav.popN(2);
|
|
3021
|
+
}).catch((err) => {
|
|
3022
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3023
|
+
});
|
|
3024
|
+
},
|
|
3025
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
// src/hooks/usePluginScreenData.ts
|
|
3031
|
+
import { useState as useState13, useEffect as useEffect4 } from "react";
|
|
3032
|
+
function usePluginScreenData(screen, marketplace, callbacks, refreshCounter, stackLength) {
|
|
3033
|
+
const [items, setItems] = useState13([]);
|
|
3034
|
+
const [loading, setLoading] = useState13(false);
|
|
3035
|
+
const [error, setError] = useState13();
|
|
3036
|
+
useEffect4(() => {
|
|
3037
|
+
setItems([]);
|
|
3038
|
+
setError(void 0);
|
|
3039
|
+
if (screen === "marketplace-list") {
|
|
3040
|
+
setLoading(true);
|
|
3041
|
+
callbacks.marketplaceList().then((sources) => {
|
|
3042
|
+
const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
|
|
3043
|
+
const sourceItems = sources.map((s) => ({
|
|
3044
|
+
label: s.name,
|
|
3045
|
+
value: s.name,
|
|
3046
|
+
hint: s.type
|
|
3047
|
+
}));
|
|
3048
|
+
setItems([...baseItems, ...sourceItems]);
|
|
3049
|
+
setLoading(false);
|
|
3050
|
+
}).catch((err) => {
|
|
3051
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3052
|
+
setLoading(false);
|
|
3053
|
+
});
|
|
3054
|
+
} else if (screen === "marketplace-browse") {
|
|
3055
|
+
const mp = marketplace ?? "";
|
|
3056
|
+
setLoading(true);
|
|
3057
|
+
callbacks.listAvailablePlugins(mp).then((plugins) => {
|
|
3058
|
+
setItems(
|
|
3059
|
+
plugins.map((p) => ({
|
|
3060
|
+
label: p.name,
|
|
3061
|
+
value: p.name,
|
|
3062
|
+
hint: p.installed ? "installed" : p.description
|
|
3063
|
+
}))
|
|
3064
|
+
);
|
|
3065
|
+
setLoading(false);
|
|
3066
|
+
}).catch((err) => {
|
|
3067
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3068
|
+
setLoading(false);
|
|
3069
|
+
});
|
|
3070
|
+
} else if (screen === "installed-list") {
|
|
3071
|
+
setLoading(true);
|
|
3072
|
+
callbacks.listInstalled().then((plugins) => {
|
|
3073
|
+
setItems(
|
|
3074
|
+
plugins.map((p) => ({
|
|
3075
|
+
label: p.name,
|
|
3076
|
+
value: p.name,
|
|
3077
|
+
hint: p.description
|
|
3078
|
+
}))
|
|
3079
|
+
);
|
|
3080
|
+
setLoading(false);
|
|
3081
|
+
}).catch((err) => {
|
|
3082
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3083
|
+
setLoading(false);
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
}, [stackLength, screen, marketplace, callbacks, refreshCounter]);
|
|
3087
|
+
return { items, loading, error };
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
// src/PluginTUI.tsx
|
|
3091
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
3092
|
+
function PluginTUI({ callbacks, onClose, addMessage }) {
|
|
3093
|
+
const [stack, setStack] = useState14([{ screen: "main" }]);
|
|
3094
|
+
const [confirm, setConfirm] = useState14();
|
|
3095
|
+
const [refreshCounter, setRefreshCounter] = useState14(0);
|
|
3096
|
+
const current = stack[stack.length - 1] ?? { screen: "main" };
|
|
3097
|
+
const push = useCallback9((state) => {
|
|
3098
|
+
setStack((prev) => [...prev, state]);
|
|
3099
|
+
}, []);
|
|
3100
|
+
const pop = useCallback9(() => {
|
|
3101
|
+
setStack((prev) => {
|
|
3102
|
+
if (prev.length <= 1) {
|
|
3103
|
+
onClose();
|
|
3104
|
+
return prev;
|
|
3105
|
+
}
|
|
3106
|
+
return prev.slice(0, -1);
|
|
3107
|
+
});
|
|
3108
|
+
}, [onClose]);
|
|
3109
|
+
const popN = useCallback9(
|
|
3110
|
+
(n) => {
|
|
3111
|
+
setStack((prev) => {
|
|
3112
|
+
const next = prev.slice(0, Math.max(1, prev.length - n));
|
|
3113
|
+
if (next.length === 0) {
|
|
3114
|
+
onClose();
|
|
3115
|
+
return prev;
|
|
3116
|
+
}
|
|
3117
|
+
return next;
|
|
3118
|
+
});
|
|
3119
|
+
},
|
|
3120
|
+
[onClose]
|
|
3121
|
+
);
|
|
3122
|
+
const notify = useCallback9(
|
|
3123
|
+
(content) => {
|
|
3124
|
+
addMessage?.({ role: "system", content });
|
|
3125
|
+
},
|
|
3126
|
+
[addMessage]
|
|
3127
|
+
);
|
|
3128
|
+
const refresh = useCallback9(() => {
|
|
3129
|
+
setRefreshCounter((c) => c + 1);
|
|
3130
|
+
}, []);
|
|
3131
|
+
const setConfirmNav = useCallback9(
|
|
3132
|
+
(state) => setConfirm(state),
|
|
3133
|
+
[setConfirm]
|
|
3134
|
+
);
|
|
3135
|
+
const pushNav = useCallback9(
|
|
3136
|
+
(state) => push({ screen: state.screen, context: state.context }),
|
|
3137
|
+
[push]
|
|
3138
|
+
);
|
|
3139
|
+
const nav = { push: pushNav, pop, popN, notify, setConfirm: setConfirmNav, refresh };
|
|
3140
|
+
const { items, loading, error } = usePluginScreenData(
|
|
3141
|
+
current.screen,
|
|
3142
|
+
current.context?.marketplace,
|
|
3143
|
+
callbacks,
|
|
3144
|
+
refreshCounter,
|
|
3145
|
+
stack.length
|
|
3146
|
+
);
|
|
3147
|
+
const handleSelect = useCallback9(
|
|
3148
|
+
(value) => {
|
|
3149
|
+
const screen2 = current.screen;
|
|
3150
|
+
const ctx = current.context;
|
|
3151
|
+
if (screen2 === "main") handleMainSelect(value, nav);
|
|
3152
|
+
else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
|
|
3153
|
+
else if (screen2 === "marketplace-action")
|
|
3154
|
+
handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
|
|
3155
|
+
else if (screen2 === "marketplace-browse")
|
|
3156
|
+
handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
|
|
3157
|
+
else if (screen2 === "marketplace-install-scope")
|
|
3158
|
+
handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
3159
|
+
else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
|
|
3160
|
+
else if (screen2 === "installed-action")
|
|
3161
|
+
handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
3162
|
+
},
|
|
3163
|
+
[current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
|
|
3164
|
+
);
|
|
3165
|
+
const handleTextSubmit = useCallback9(
|
|
3166
|
+
(value) => {
|
|
3167
|
+
if (current.screen === "marketplace-add") {
|
|
3168
|
+
callbacks.marketplaceAdd(value).then((name) => {
|
|
3169
|
+
notify(`Added marketplace "${name}" from ${value}.`);
|
|
3170
|
+
pop();
|
|
3171
|
+
}).catch((err) => {
|
|
3172
|
+
notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3173
|
+
pop();
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
},
|
|
3177
|
+
[current.screen, callbacks, notify, pop]
|
|
3178
|
+
);
|
|
3179
|
+
if (confirm) {
|
|
3180
|
+
return /* @__PURE__ */ jsx18(
|
|
3181
|
+
ConfirmPrompt,
|
|
3182
|
+
{
|
|
3183
|
+
message: confirm.message,
|
|
3184
|
+
onSelect: (index) => {
|
|
3185
|
+
if (index === 0) confirm.onConfirm();
|
|
3186
|
+
else confirm.onCancel();
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
);
|
|
3190
|
+
}
|
|
3191
|
+
const screen = current.screen;
|
|
3192
|
+
if (screen === "marketplace-add") {
|
|
3193
|
+
return /* @__PURE__ */ jsx18(
|
|
3194
|
+
TextPrompt,
|
|
3195
|
+
{
|
|
3196
|
+
title: "Add Marketplace Source",
|
|
3197
|
+
placeholder: "owner/repo or git URL",
|
|
3198
|
+
onSubmit: handleTextSubmit,
|
|
3199
|
+
onCancel: pop,
|
|
3200
|
+
validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
|
|
3201
|
+
}
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
if (screen === "marketplace-action") {
|
|
3205
|
+
return /* @__PURE__ */ jsx18(
|
|
3206
|
+
MenuSelect,
|
|
3207
|
+
{
|
|
3208
|
+
title: `Marketplace: ${current.context?.marketplace ?? ""}`,
|
|
3209
|
+
items: [
|
|
3210
|
+
{ label: "Browse plugins", value: "browse" },
|
|
3211
|
+
{ label: "Update", value: "update" },
|
|
3212
|
+
{ label: "Remove", value: "remove" }
|
|
3213
|
+
],
|
|
3214
|
+
onSelect: handleSelect,
|
|
3215
|
+
onBack: pop
|
|
3216
|
+
},
|
|
3217
|
+
stack.length
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
if (screen === "marketplace-install-scope") {
|
|
3221
|
+
return /* @__PURE__ */ jsx18(
|
|
3222
|
+
MenuSelect,
|
|
3223
|
+
{
|
|
3224
|
+
title: `Install scope for "${current.context?.pluginId ?? ""}"`,
|
|
3225
|
+
items: [
|
|
3226
|
+
{ label: "User scope", value: "user" },
|
|
3227
|
+
{ label: "Project scope", value: "project" }
|
|
3228
|
+
],
|
|
3229
|
+
onSelect: handleSelect,
|
|
3230
|
+
onBack: pop
|
|
3231
|
+
},
|
|
3232
|
+
stack.length
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
if (screen === "installed-action") {
|
|
3236
|
+
return /* @__PURE__ */ jsx18(
|
|
3237
|
+
MenuSelect,
|
|
3238
|
+
{
|
|
3239
|
+
title: `Plugin: ${current.context?.pluginId ?? ""}`,
|
|
3240
|
+
items: [{ label: "Uninstall", value: "uninstall" }],
|
|
3241
|
+
onSelect: handleSelect,
|
|
3242
|
+
onBack: pop
|
|
3243
|
+
},
|
|
3244
|
+
stack.length
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
const titleMap = {
|
|
3248
|
+
main: "Plugin Management",
|
|
3249
|
+
"marketplace-list": "Marketplace",
|
|
3250
|
+
"marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
|
|
3251
|
+
"installed-list": "Installed Plugins"
|
|
3252
|
+
};
|
|
3253
|
+
const staticItemsMap = {
|
|
3254
|
+
main: [
|
|
3255
|
+
{ label: "Marketplace", value: "marketplace" },
|
|
3256
|
+
{ label: "Installed Plugins", value: "installed" }
|
|
3257
|
+
]
|
|
3258
|
+
};
|
|
3259
|
+
return /* @__PURE__ */ jsx18(
|
|
3260
|
+
MenuSelect,
|
|
3261
|
+
{
|
|
3262
|
+
title: titleMap[screen] ?? "Plugin Management",
|
|
3263
|
+
items: staticItemsMap[screen] ?? items,
|
|
3264
|
+
onSelect: handleSelect,
|
|
3265
|
+
onBack: pop,
|
|
3266
|
+
loading,
|
|
3267
|
+
error
|
|
3268
|
+
},
|
|
3269
|
+
`${screen}-${stack.length}-${refreshCounter}`
|
|
3270
|
+
);
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
// src/TransportTUI.tsx
|
|
3274
|
+
import { useState as useState15, useCallback as useCallback10 } from "react";
|
|
3275
|
+
import { Box as Box15, Text as Text17, useInput as useInput8 } from "ink";
|
|
3276
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3277
|
+
var TRANSPORT_NAME_WIDTH = 18;
|
|
3278
|
+
function TransportEntryRow({ entry, selected }) {
|
|
3279
|
+
const enabled = entry.config.enabled;
|
|
3280
|
+
const dot = enabled ? "\u25CF" : "\u25CB";
|
|
3281
|
+
const badge = enabled ? "[enabled] " : "[disabled]";
|
|
3282
|
+
const portOpt = entry.config.options?.port;
|
|
3283
|
+
const portHint = typeof portOpt === "number" ? `port: ${portOpt}` : "";
|
|
3284
|
+
return /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsx19(Text17, { color: selected ? "cyan" : void 0, bold: selected, children: `${dot} ${entry.transport.name.padEnd(TRANSPORT_NAME_WIDTH)} ${badge} ${portHint}` }) });
|
|
3285
|
+
}
|
|
3286
|
+
function useTransportInput(entries, cursor, saving, registry, setCursor, setSaving, onClose, refresh) {
|
|
3287
|
+
useInput8(
|
|
3288
|
+
useCallback10(
|
|
3289
|
+
(_input, key) => {
|
|
3290
|
+
if (saving) return;
|
|
3291
|
+
if (key.upArrow) {
|
|
3292
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
if (key.downArrow) {
|
|
3296
|
+
setCursor((c) => Math.min(entries.length - 1, c + 1));
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
if (key.escape || key.return) {
|
|
3300
|
+
onClose();
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
if (_input === " ") {
|
|
3304
|
+
const entry = entries[cursor];
|
|
3305
|
+
if (!entry) return;
|
|
3306
|
+
setSaving(true);
|
|
3307
|
+
registry.setEnabled(entry.transport.name, !entry.config.enabled).then(() => {
|
|
3308
|
+
refresh();
|
|
3309
|
+
setSaving(false);
|
|
3310
|
+
}).catch(() => setSaving(false));
|
|
3311
|
+
}
|
|
3312
|
+
},
|
|
3313
|
+
[saving, entries, cursor, registry, onClose, refresh, setCursor, setSaving]
|
|
3314
|
+
)
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
function TransportTUI({ registry, onClose }) {
|
|
3318
|
+
const [entries, setEntries] = useState15(() => registry.getAll());
|
|
3319
|
+
const [cursor, setCursor] = useState15(0);
|
|
3320
|
+
const [saving, setSaving] = useState15(false);
|
|
3321
|
+
const refresh = useCallback10(() => {
|
|
3322
|
+
setEntries(registry.getAll());
|
|
3323
|
+
}, [registry]);
|
|
3324
|
+
useTransportInput(entries, cursor, saving, registry, setCursor, setSaving, onClose, refresh);
|
|
3325
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
3326
|
+
/* @__PURE__ */ jsx19(Text17, { bold: true, children: "Settings \u203A Transports" }),
|
|
3327
|
+
/* @__PURE__ */ jsx19(Box15, { marginTop: 1, flexDirection: "column", children: entries.map((entry, i) => /* @__PURE__ */ jsx19(TransportEntryRow, { entry, selected: i === cursor }, entry.transport.name)) }),
|
|
3328
|
+
/* @__PURE__ */ jsx19(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2191\u2193 select space toggle enter/esc close" }) }),
|
|
3329
|
+
saving && /* @__PURE__ */ jsx19(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text17, { color: "yellow", children: "Saving\u2026" }) })
|
|
3330
|
+
] });
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
// src/SessionPicker.tsx
|
|
3334
|
+
import { Box as Box16, Text as Text18 } from "ink";
|
|
3335
|
+
import { Fragment as Fragment5, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3336
|
+
var SESSION_ID_DISPLAY_LENGTH = 8;
|
|
3337
|
+
var SESSION_PREVIEW_DISPLAY_LENGTH = 60;
|
|
3338
|
+
function SessionPicker({
|
|
3339
|
+
sessions,
|
|
3340
|
+
onSelect,
|
|
3341
|
+
onCancel
|
|
3342
|
+
}) {
|
|
3343
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
3344
|
+
/* @__PURE__ */ jsx20(Text18, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
|
|
3345
|
+
/* @__PURE__ */ jsx20(
|
|
3346
|
+
ListPicker,
|
|
3347
|
+
{
|
|
3348
|
+
items: [...sessions],
|
|
3349
|
+
renderItem: (session, isSelected) => {
|
|
3350
|
+
const preview = session.preview ? session.preview.slice(0, SESSION_PREVIEW_DISPLAY_LENGTH) + (session.preview.length > SESSION_PREVIEW_DISPLAY_LENGTH ? "..." : "") : "";
|
|
3351
|
+
return /* @__PURE__ */ jsxs15(Text18, { children: [
|
|
3352
|
+
isSelected ? "> " : " ",
|
|
3353
|
+
/* @__PURE__ */ jsx20(Text18, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
|
|
3354
|
+
" ",
|
|
3355
|
+
/* @__PURE__ */ jsx20(Text18, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
|
|
3356
|
+
month: "short",
|
|
3357
|
+
day: "numeric",
|
|
3358
|
+
hour: "2-digit",
|
|
3359
|
+
minute: "2-digit"
|
|
3360
|
+
}) }),
|
|
3361
|
+
" ",
|
|
3362
|
+
/* @__PURE__ */ jsxs15(Text18, { dimColor: true, children: [
|
|
3363
|
+
"msgs: ",
|
|
3364
|
+
session.messageCount
|
|
3365
|
+
] }),
|
|
3366
|
+
preview ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
|
|
3367
|
+
"\n ",
|
|
3368
|
+
/* @__PURE__ */ jsx20(Text18, { color: "gray", children: preview })
|
|
3369
|
+
] }) : null
|
|
3370
|
+
] });
|
|
3371
|
+
},
|
|
3372
|
+
onSelect: (session) => onSelect(session.id),
|
|
3373
|
+
onCancel
|
|
3374
|
+
}
|
|
3375
|
+
)
|
|
3376
|
+
] });
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
// src/BackgroundTaskPanel.tsx
|
|
3380
|
+
import { Box as Box17, Text as Text19 } from "ink";
|
|
3381
|
+
|
|
3382
|
+
// src/execution-workspace-view-model.ts
|
|
3383
|
+
var ACTIVE_STATUSES = [
|
|
3384
|
+
"active",
|
|
3385
|
+
"queued",
|
|
3386
|
+
"running",
|
|
3387
|
+
"waiting_permission"
|
|
3388
|
+
];
|
|
3389
|
+
var DETAIL_RECORD_TEXT_LIMIT = 160;
|
|
3390
|
+
var PREVIEW_WHITESPACE = /\s+/g;
|
|
3391
|
+
var PREVIEW_SEPARATOR = " ";
|
|
3392
|
+
function getDefaultBackgroundWorkspaceEntries(snapshot) {
|
|
3393
|
+
return (snapshot?.entries ?? []).filter(
|
|
3394
|
+
(entry) => entry.kind === "background_task" && entry.visibility === "default"
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
function countActiveBackgroundWorkspaceEntries(snapshot) {
|
|
3398
|
+
return getDefaultBackgroundWorkspaceEntries(snapshot).filter(
|
|
3399
|
+
(entry) => ACTIVE_STATUSES.includes(entry.status)
|
|
3400
|
+
).length;
|
|
3401
|
+
}
|
|
3402
|
+
function formatExecutionWorkspaceEntryRow(entry, options = {}) {
|
|
3403
|
+
const isSelected = entry.id === options.selectedEntryId;
|
|
3404
|
+
const row = {
|
|
3405
|
+
id: entry.id,
|
|
3406
|
+
radio: isSelected ? "\u25CF" : "\u25CB",
|
|
3407
|
+
title: formatEntryTitle(entry),
|
|
3408
|
+
subtitle: formatEntrySubtitle(entry),
|
|
3409
|
+
statusLabel: formatStatusLabel2(entry.status),
|
|
3410
|
+
preview: trimPreview(entry.preview ?? entry.currentAction),
|
|
3411
|
+
color: getEntryColor(entry),
|
|
3412
|
+
isSelected
|
|
3413
|
+
};
|
|
3414
|
+
return { ...row, accessibleText: formatAccessibleText(row) };
|
|
3415
|
+
}
|
|
3416
|
+
function formatExecutionDetailRecord(record) {
|
|
3417
|
+
const text = record.text.trim().replace(PREVIEW_WHITESPACE, PREVIEW_SEPARATOR);
|
|
3418
|
+
if (!text) return record.kind;
|
|
3419
|
+
return text.length > DETAIL_RECORD_TEXT_LIMIT ? `${text.slice(0, DETAIL_RECORD_TEXT_LIMIT)}...` : text;
|
|
3420
|
+
}
|
|
3421
|
+
function formatEntryTitle(entry) {
|
|
3422
|
+
if (entry.kind === "main_thread") return entry.title;
|
|
3423
|
+
if (entry.kind === "background_group") return `${entry.title} group`;
|
|
3424
|
+
if (entry.taskKind === "agent") return `${entry.title} agent`;
|
|
3425
|
+
if (entry.taskKind === "process") return entry.title || "Process";
|
|
3426
|
+
return entry.title;
|
|
3427
|
+
}
|
|
3428
|
+
function formatEntrySubtitle(entry) {
|
|
3429
|
+
if (entry.kind === "main_thread") return entry.subtitle;
|
|
3430
|
+
const parts = [
|
|
3431
|
+
entry.taskKind,
|
|
3432
|
+
entry.subtitle,
|
|
3433
|
+
entry.attention === "none" ? void 0 : entry.attention
|
|
3434
|
+
];
|
|
3435
|
+
return parts.filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ") || void 0;
|
|
3436
|
+
}
|
|
3437
|
+
function formatStatusLabel2(status) {
|
|
3438
|
+
return status.replace(/_/g, " ");
|
|
3439
|
+
}
|
|
3440
|
+
function getEntryColor(entry) {
|
|
3441
|
+
if (entry.attention === "failed" || entry.status === "failed") return "red";
|
|
3442
|
+
if (entry.attention === "permission" || entry.status === "waiting_permission") return "yellow";
|
|
3443
|
+
if (entry.status === "completed") return "green";
|
|
3444
|
+
if (entry.status === "cancelled") return "yellow";
|
|
3445
|
+
if (ACTIVE_STATUSES.includes(entry.status)) return "cyan";
|
|
3446
|
+
return "white";
|
|
3447
|
+
}
|
|
3448
|
+
function trimPreview(value) {
|
|
3449
|
+
const preview = value?.trim().replace(PREVIEW_WHITESPACE, PREVIEW_SEPARATOR);
|
|
3450
|
+
return preview || void 0;
|
|
3451
|
+
}
|
|
3452
|
+
function formatAccessibleText(row) {
|
|
3453
|
+
const parts = [row.radio, row.title, row.statusLabel, row.subtitle, row.preview];
|
|
3454
|
+
return parts.filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ");
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
// src/background-task-row-format.ts
|
|
3458
|
+
function formatBackgroundTaskRow(entry, options = {}) {
|
|
3459
|
+
const row = formatExecutionWorkspaceEntryRow(entry);
|
|
3460
|
+
const marker = isActiveEntry(entry) ? "\u25A1" : "\u25A0";
|
|
3461
|
+
const segments = [row.statusLabel, row.subtitle].filter(
|
|
3462
|
+
(segment) => typeof segment === "string" && segment.length > 0
|
|
3463
|
+
);
|
|
3464
|
+
return {
|
|
3465
|
+
connector: options.isLast === false ? "\u251C" : "\u2514",
|
|
3466
|
+
marker,
|
|
3467
|
+
color: row.color,
|
|
3468
|
+
label: row.title,
|
|
3469
|
+
segments,
|
|
3470
|
+
preview: row.preview,
|
|
3471
|
+
accessibleText: [
|
|
3472
|
+
`${options.isLast === false ? "\u251C" : "\u2514"} ${marker} ${row.title}`,
|
|
3473
|
+
...segments,
|
|
3474
|
+
row.preview
|
|
3475
|
+
].filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ")
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
function isActiveEntry(entry) {
|
|
3479
|
+
return entry.status === "active" || entry.status === "queued" || entry.status === "running" || entry.status === "waiting_permission";
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
// src/BackgroundTaskPanel.tsx
|
|
3483
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3484
|
+
function BackgroundTaskPanel({ entries }) {
|
|
3485
|
+
if (entries.length === 0) return null;
|
|
3486
|
+
return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
|
|
3487
|
+
/* @__PURE__ */ jsx21(Text19, { color: "cyan", bold: true, children: "Background work" }),
|
|
3488
|
+
entries.map((entry, index) => {
|
|
3489
|
+
const row = formatBackgroundTaskRow(entry, { isLast: index === entries.length - 1 });
|
|
3490
|
+
return /* @__PURE__ */ jsxs16(Text19, { children: [
|
|
3491
|
+
`${row.connector} `,
|
|
3492
|
+
/* @__PURE__ */ jsx21(Text19, { color: row.color, children: row.marker }),
|
|
3493
|
+
` ${row.label}`,
|
|
3494
|
+
row.segments.map((segment, segmentIndex) => /* @__PURE__ */ jsx21(Text19, { dimColor: true, children: ` \xB7 ${segment}` }, `${segment}-${segmentIndex}`)),
|
|
3495
|
+
row.preview ? /* @__PURE__ */ jsx21(Text19, { dimColor: true, children: ` \xB7 ${row.preview}` }) : null
|
|
3496
|
+
] }, entry.id);
|
|
3497
|
+
})
|
|
3498
|
+
] });
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
// src/ExecutionWorkspaceSwitcher.tsx
|
|
3502
|
+
import { useEffect as useEffect5, useRef as useRef9, useState as useState16 } from "react";
|
|
3503
|
+
import { Box as Box18, Text as Text20, useInput as useInput9 } from "ink";
|
|
3504
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3505
|
+
var MAX_VISIBLE_WORKSPACE_ENTRIES = 8;
|
|
3506
|
+
function ExecutionWorkspaceSwitcher({
|
|
3507
|
+
snapshot,
|
|
3508
|
+
selectedEntryId,
|
|
3509
|
+
onSelect,
|
|
3510
|
+
onClose
|
|
3511
|
+
}) {
|
|
3512
|
+
const entries = [...snapshot?.entries ?? []];
|
|
3513
|
+
const { normalized, visibleEntries, applyAction } = useWorkspaceSwitcherSelection({
|
|
3514
|
+
entries,
|
|
3515
|
+
selectedEntryId,
|
|
3516
|
+
onSelect,
|
|
3517
|
+
onClose
|
|
3518
|
+
});
|
|
3519
|
+
useInput9((_input, key) => {
|
|
3520
|
+
const action = getVerticalSelectionInputAction(key);
|
|
3521
|
+
if (action !== void 0) applyAction(action);
|
|
3522
|
+
});
|
|
3523
|
+
return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
3524
|
+
/* @__PURE__ */ jsx22(Text20, { color: "cyan", bold: true, children: "Execution workspace" }),
|
|
3525
|
+
/* @__PURE__ */ jsx22(Box18, { flexDirection: "column", marginTop: 1, children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx22(Text20, { dimColor: true, children: "No workspace entries" }) : visibleEntries.map((entry, index) => /* @__PURE__ */ jsx22(
|
|
3526
|
+
ExecutionWorkspaceSwitcherRow,
|
|
3527
|
+
{
|
|
3528
|
+
entry,
|
|
3529
|
+
isFocused: normalized.scrollOffset + index === normalized.selectedIndex,
|
|
3530
|
+
selectedEntryId
|
|
3531
|
+
},
|
|
3532
|
+
entry.id
|
|
3533
|
+
)) }),
|
|
3534
|
+
/* @__PURE__ */ jsx22(Text20, { dimColor: true, children: "Ctrl+B Close \u2191\u2193 Navigate Enter Switch Esc Close" })
|
|
3535
|
+
] });
|
|
3536
|
+
}
|
|
3537
|
+
function useWorkspaceSwitcherSelection({
|
|
3538
|
+
entries,
|
|
3539
|
+
selectedEntryId,
|
|
3540
|
+
onSelect,
|
|
3541
|
+
onClose
|
|
3542
|
+
}) {
|
|
3543
|
+
const [state, setState] = useState16(() => createSelectionFlowState());
|
|
3544
|
+
const stateRef = useRef9(state);
|
|
3545
|
+
useEffect5(() => {
|
|
3546
|
+
const selectedIndex = Math.max(
|
|
3547
|
+
0,
|
|
3548
|
+
entries.findIndex((entry) => entry.id === selectedEntryId)
|
|
3549
|
+
);
|
|
3550
|
+
const nextState = createNormalizedSelection({ selectedIndex, itemCount: entries.length });
|
|
3551
|
+
stateRef.current = nextState;
|
|
3552
|
+
setState(nextState);
|
|
3553
|
+
}, [entries.length, selectedEntryId]);
|
|
3554
|
+
const normalized = createNormalizedSelection({
|
|
3555
|
+
selectedIndex: state.selectedIndex,
|
|
3556
|
+
scrollOffset: state.scrollOffset,
|
|
3557
|
+
itemCount: entries.length
|
|
3558
|
+
});
|
|
3559
|
+
if (normalized !== state) stateRef.current = normalized;
|
|
3560
|
+
return {
|
|
3561
|
+
normalized,
|
|
3562
|
+
visibleEntries: entries.slice(
|
|
3563
|
+
normalized.scrollOffset,
|
|
3564
|
+
normalized.scrollOffset + MAX_VISIBLE_WORKSPACE_ENTRIES
|
|
3565
|
+
),
|
|
3566
|
+
applyAction: createApplyAction({ entries, stateRef, setState, onSelect, onClose })
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
function createApplyAction({
|
|
3570
|
+
entries,
|
|
3571
|
+
stateRef,
|
|
3572
|
+
setState,
|
|
3573
|
+
onSelect,
|
|
3574
|
+
onClose
|
|
3575
|
+
}) {
|
|
3576
|
+
return (action) => {
|
|
3577
|
+
const result = applySelectionInput(stateRef.current, action, {
|
|
3578
|
+
itemCount: entries.length,
|
|
3579
|
+
maxVisible: MAX_VISIBLE_WORKSPACE_ENTRIES
|
|
3580
|
+
});
|
|
3581
|
+
const nextState = result.effect.type === "select" || result.effect.type === "cancel" ? { ...result.state, resolved: false } : result.state;
|
|
3582
|
+
stateRef.current = nextState;
|
|
3583
|
+
setState(nextState);
|
|
3584
|
+
if (result.effect.type === "cancel") {
|
|
3585
|
+
onClose();
|
|
3586
|
+
} else if (result.effect.type === "select") {
|
|
3587
|
+
const entry = entries[result.effect.index];
|
|
3588
|
+
if (entry) onSelect(entry.id);
|
|
3589
|
+
}
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
3592
|
+
function createNormalizedSelection(input) {
|
|
3593
|
+
return normalizeSelectionState(
|
|
3594
|
+
{
|
|
3595
|
+
selectedIndex: input.selectedIndex,
|
|
3596
|
+
scrollOffset: input.scrollOffset ?? 0,
|
|
3597
|
+
resolved: false
|
|
3598
|
+
},
|
|
3599
|
+
{ itemCount: input.itemCount, maxVisible: MAX_VISIBLE_WORKSPACE_ENTRIES }
|
|
3600
|
+
);
|
|
3601
|
+
}
|
|
3602
|
+
function ExecutionWorkspaceSwitcherRow({
|
|
3603
|
+
entry,
|
|
3604
|
+
isFocused,
|
|
3605
|
+
selectedEntryId
|
|
3606
|
+
}) {
|
|
3607
|
+
const row = formatExecutionWorkspaceEntryRow(entry, { selectedEntryId });
|
|
3608
|
+
return /* @__PURE__ */ jsxs17(Text20, { children: [
|
|
3609
|
+
/* @__PURE__ */ jsx22(Text20, { color: isFocused ? "cyan" : void 0, bold: isFocused, children: isFocused ? "> " : " " }),
|
|
3610
|
+
/* @__PURE__ */ jsx22(Text20, { color: row.color, children: row.radio }),
|
|
3611
|
+
/* @__PURE__ */ jsx22(Text20, { color: isFocused ? "cyan" : void 0, bold: isFocused, children: ` ${row.title}` }),
|
|
3612
|
+
/* @__PURE__ */ jsx22(Text20, { dimColor: true, children: ` \xB7 ${row.statusLabel}` }),
|
|
3613
|
+
row.subtitle ? /* @__PURE__ */ jsx22(Text20, { dimColor: true, children: ` \xB7 ${row.subtitle}` }) : null,
|
|
3614
|
+
row.preview ? /* @__PURE__ */ jsx22(Text20, { dimColor: true, children: ` \xB7 ${row.preview}` }) : null
|
|
3615
|
+
] });
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
// src/ExecutionWorkspaceDetailPane.tsx
|
|
3619
|
+
import { Box as Box19, Text as Text21 } from "ink";
|
|
3620
|
+
import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3621
|
+
var MAX_VISIBLE_DETAIL_RECORDS = 12;
|
|
3622
|
+
function ExecutionWorkspaceDetailPane({
|
|
3623
|
+
entry,
|
|
3624
|
+
page,
|
|
3625
|
+
loading,
|
|
3626
|
+
error
|
|
3627
|
+
}) {
|
|
3628
|
+
const row = formatExecutionWorkspaceEntryRow(entry, { selectedEntryId: entry.id });
|
|
3629
|
+
const records = page?.records.slice(-MAX_VISIBLE_DETAIL_RECORDS) ?? [];
|
|
3630
|
+
return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", marginBottom: 1, children: [
|
|
3631
|
+
/* @__PURE__ */ jsx23(Text21, { color: "cyan", bold: true, children: `Viewing ${row.title}` }),
|
|
3632
|
+
/* @__PURE__ */ jsxs18(Text21, { dimColor: true, children: [
|
|
3633
|
+
row.statusLabel,
|
|
3634
|
+
row.subtitle ? ` \xB7 ${row.subtitle}` : "",
|
|
3635
|
+
row.preview ? ` \xB7 ${row.preview}` : ""
|
|
3636
|
+
] }),
|
|
3637
|
+
loading ? /* @__PURE__ */ jsx23(Text21, { dimColor: true, children: "Loading workspace detail..." }) : null,
|
|
3638
|
+
error ? /* @__PURE__ */ jsx23(Text21, { color: "red", children: error }) : null,
|
|
3639
|
+
!loading && !error && records.length === 0 ? /* @__PURE__ */ jsx23(Text21, { dimColor: true, children: "No detail yet" }) : null,
|
|
3640
|
+
!loading && !error && records.map((record) => /* @__PURE__ */ jsx23(Text21, { color: getDetailRecordColor(record.kind), children: formatExecutionDetailRecord(record) }, record.id)),
|
|
3641
|
+
page?.nextCursor ? /* @__PURE__ */ jsx23(Text21, { dimColor: true, children: "... more detail available" }) : null
|
|
3642
|
+
] });
|
|
3643
|
+
}
|
|
3644
|
+
function getDetailRecordColor(kind) {
|
|
3645
|
+
if (kind === "error") return "red";
|
|
3646
|
+
if (kind === "result") return "green";
|
|
3647
|
+
if (kind === "process_output") return "white";
|
|
3648
|
+
if (kind === "group_summary") return "cyan";
|
|
3649
|
+
return void 0;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
// src/UpdateNotice.tsx
|
|
3653
|
+
import { Box as Box20, Text as Text22 } from "ink";
|
|
3654
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
3655
|
+
function UpdateNotice({ message }) {
|
|
3656
|
+
return /* @__PURE__ */ jsx24(Box20, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsx24(Text22, { color: "yellow", children: message }) });
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// src/App.tsx
|
|
3660
|
+
import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3661
|
+
function App(props) {
|
|
3662
|
+
const [activeSessionId, setActiveSessionId] = useState17(props.resumeSessionId);
|
|
3663
|
+
const [showInitialSessionPicker, setShowInitialSessionPicker] = useState17(
|
|
3664
|
+
props.showSessionPickerOnStart ?? false
|
|
3665
|
+
);
|
|
3666
|
+
return /* @__PURE__ */ jsx25(TuiCliAdapterProvider, { value: props.cliAdapter, children: /* @__PURE__ */ jsx25(
|
|
3667
|
+
AppInner,
|
|
3668
|
+
{
|
|
3669
|
+
...props,
|
|
3670
|
+
showSessionPickerOnStart: showInitialSessionPicker,
|
|
3671
|
+
resumeSessionId: activeSessionId,
|
|
3672
|
+
onSessionSwitch: (sessionId) => {
|
|
3673
|
+
setShowInitialSessionPicker(false);
|
|
3674
|
+
setActiveSessionId(sessionId);
|
|
3675
|
+
}
|
|
3676
|
+
},
|
|
3677
|
+
activeSessionId ?? "__new__"
|
|
3678
|
+
) });
|
|
3679
|
+
}
|
|
3680
|
+
function AppInner(props) {
|
|
3681
|
+
const cwd = props.cwd;
|
|
3682
|
+
const {
|
|
3683
|
+
interactiveSession,
|
|
3684
|
+
registry,
|
|
3685
|
+
commandEffectQueue,
|
|
3686
|
+
history,
|
|
3687
|
+
addEntry,
|
|
3688
|
+
streamingText,
|
|
3689
|
+
activeTools,
|
|
3690
|
+
isThinking,
|
|
3691
|
+
isAborting,
|
|
3692
|
+
isShuttingDown,
|
|
3693
|
+
pendingPrompt,
|
|
3694
|
+
executionWorkspaceSnapshot,
|
|
3695
|
+
selectedExecutionEntryId,
|
|
3696
|
+
selectExecutionWorkspaceEntry,
|
|
3697
|
+
readExecutionWorkspaceDetail,
|
|
3698
|
+
permissionRequest,
|
|
3699
|
+
contextState,
|
|
3700
|
+
handleSubmit: baseHandleSubmit,
|
|
3701
|
+
handleAbort,
|
|
3702
|
+
handleCancelQueue,
|
|
3703
|
+
handleShutdown
|
|
3704
|
+
} = useInteractiveSession({
|
|
3705
|
+
cwd,
|
|
3706
|
+
provider: props.provider,
|
|
3707
|
+
permissionMode: props.permissionMode,
|
|
3708
|
+
maxTurns: props.maxTurns,
|
|
3709
|
+
sessionStore: props.sessionStore,
|
|
3710
|
+
resumeSessionId: props.resumeSessionId,
|
|
3711
|
+
forkSession: props.forkSession,
|
|
3712
|
+
sessionName: props.sessionName,
|
|
3713
|
+
backgroundTaskRunners: props.backgroundTaskRunners,
|
|
3714
|
+
subagentRunnerFactory: props.subagentRunnerFactory,
|
|
3715
|
+
commandModules: props.commandModules,
|
|
3716
|
+
commandHostAdapters: props.commandHostAdapters,
|
|
3717
|
+
transportRegistry: props.transportRegistry,
|
|
3718
|
+
language: props.language,
|
|
3719
|
+
reloadPluginCommandSource: props.reloadPluginCommandSource
|
|
3720
|
+
});
|
|
3721
|
+
const fallbackPluginCallbacks = usePluginCallbacks(cwd);
|
|
3722
|
+
const pluginCallbacks = props.commandHostAdapters?.plugin ?? fallbackPluginCallbacks;
|
|
3723
|
+
const { exit } = useApp2();
|
|
3724
|
+
const [sessionName, setSessionName] = useState17(props.sessionName);
|
|
3725
|
+
const [updateNotice, setUpdateNotice] = useState17();
|
|
3726
|
+
const [showExecutionWorkspaceSwitcher, setShowExecutionWorkspaceSwitcher] = useState17(false);
|
|
3727
|
+
const [executionDetailPage, setExecutionDetailPage] = useState17(null);
|
|
3728
|
+
const [executionDetailError, setExecutionDetailError] = useState17();
|
|
3729
|
+
const [isExecutionDetailLoading, setIsExecutionDetailLoading] = useState17(false);
|
|
3730
|
+
const [statusLineSettings, setStatusLineSettings] = useStatusLineSettings();
|
|
3731
|
+
const backgroundWorkspaceEntries = useMemo5(
|
|
3732
|
+
() => getDefaultBackgroundWorkspaceEntries(executionWorkspaceSnapshot),
|
|
3733
|
+
[executionWorkspaceSnapshot]
|
|
3734
|
+
);
|
|
3735
|
+
const activeBackgroundTaskCount = countActiveBackgroundWorkspaceEntries(
|
|
3736
|
+
executionWorkspaceSnapshot
|
|
3737
|
+
);
|
|
3738
|
+
const selectedExecutionEntry = useMemo5(
|
|
3739
|
+
() => executionWorkspaceSnapshot?.entries.find((entry) => entry.id === selectedExecutionEntryId),
|
|
3740
|
+
[executionWorkspaceSnapshot, selectedExecutionEntryId]
|
|
3741
|
+
);
|
|
3742
|
+
const {
|
|
3743
|
+
handleSubmit,
|
|
3744
|
+
pendingModelId,
|
|
3745
|
+
pendingInteractionPrompt,
|
|
3746
|
+
showPluginTUI,
|
|
3747
|
+
showSessionPicker,
|
|
3748
|
+
showTransportTUI,
|
|
3749
|
+
setShowPluginTUI,
|
|
3750
|
+
setShowSessionPicker,
|
|
3751
|
+
setShowTransportTUI,
|
|
3752
|
+
handleModelConfirm,
|
|
3753
|
+
handleInteractionSubmit,
|
|
3754
|
+
handleInteractionCancel
|
|
3755
|
+
} = useSideEffects({
|
|
3756
|
+
cwd,
|
|
3757
|
+
providerOverride: props.providerOverride,
|
|
3758
|
+
interactiveSession,
|
|
3759
|
+
commandEffectQueue,
|
|
3760
|
+
addEntry,
|
|
3761
|
+
baseHandleSubmit,
|
|
3762
|
+
setSessionName,
|
|
3763
|
+
setStatusLineSettings,
|
|
3764
|
+
showSessionPickerOnStart: props.showSessionPickerOnStart
|
|
3765
|
+
});
|
|
3766
|
+
useEffect6(() => {
|
|
3767
|
+
const name = interactiveSession?.getName?.();
|
|
3768
|
+
if (name && !sessionName) setSessionName(name);
|
|
3769
|
+
}, [interactiveSession, sessionName]);
|
|
3770
|
+
useEffect6(() => {
|
|
3771
|
+
let isMounted = true;
|
|
3772
|
+
props.startupUpdateNotice?.then((notice) => {
|
|
3773
|
+
if (isMounted && notice !== void 0) {
|
|
3774
|
+
setUpdateNotice(notice);
|
|
3775
|
+
}
|
|
3776
|
+
}).catch(() => {
|
|
3777
|
+
});
|
|
3778
|
+
return () => {
|
|
3779
|
+
isMounted = false;
|
|
3780
|
+
};
|
|
3781
|
+
}, [props.startupUpdateNotice]);
|
|
3782
|
+
useEffect6(() => {
|
|
3783
|
+
const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
|
|
3784
|
+
process.stdout.write(`\x1B]0;${title}\x07`);
|
|
3785
|
+
}, [sessionName]);
|
|
3786
|
+
useInput10((_input, key) => {
|
|
3787
|
+
if (!key.escape || !isThinking) return;
|
|
3788
|
+
if (permissionRequest || showPluginTUI || showTransportTUI || showSessionPicker || showExecutionWorkspaceSwitcher) {
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
handleAbort();
|
|
3792
|
+
});
|
|
3793
|
+
useInput10((input, key) => {
|
|
3794
|
+
if (!key.ctrl || input !== "b") return;
|
|
3795
|
+
if (permissionRequest || showPluginTUI || showSessionPicker || isShuttingDown) return;
|
|
3796
|
+
setShowExecutionWorkspaceSwitcher((shown) => !shown);
|
|
3797
|
+
});
|
|
3798
|
+
useInput10((input, key) => {
|
|
3799
|
+
if (!key.ctrl || input !== "c" || isShuttingDown) return;
|
|
3800
|
+
void handleShutdown("prompt_input_exit").finally(() => exit());
|
|
3801
|
+
});
|
|
3802
|
+
useEffect6(() => {
|
|
3803
|
+
const onSigterm = () => {
|
|
3804
|
+
if (isShuttingDown) return;
|
|
3805
|
+
void handleShutdown("other").finally(() => exit());
|
|
3806
|
+
};
|
|
3807
|
+
process.once("SIGINT", onSigterm);
|
|
3808
|
+
process.once("SIGTERM", onSigterm);
|
|
3809
|
+
return () => {
|
|
3810
|
+
process.off("SIGINT", onSigterm);
|
|
3811
|
+
process.off("SIGTERM", onSigterm);
|
|
3812
|
+
};
|
|
3813
|
+
}, [handleShutdown, exit, isShuttingDown]);
|
|
3814
|
+
useEffect6(() => {
|
|
3815
|
+
if (!selectedExecutionEntry || selectedExecutionEntry.kind === "main_thread") {
|
|
3816
|
+
setExecutionDetailPage(null);
|
|
3817
|
+
setExecutionDetailError(void 0);
|
|
3818
|
+
setIsExecutionDetailLoading(false);
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
let isCurrent = true;
|
|
3822
|
+
setIsExecutionDetailLoading(true);
|
|
3823
|
+
setExecutionDetailError(void 0);
|
|
3824
|
+
readExecutionWorkspaceDetail(selectedExecutionEntry.id).then((page) => {
|
|
3825
|
+
if (!isCurrent) return;
|
|
3826
|
+
setExecutionDetailPage(page);
|
|
3827
|
+
setIsExecutionDetailLoading(false);
|
|
3828
|
+
}).catch((error) => {
|
|
3829
|
+
if (!isCurrent) return;
|
|
3830
|
+
setExecutionDetailError(error.message);
|
|
3831
|
+
setIsExecutionDetailLoading(false);
|
|
3832
|
+
});
|
|
3833
|
+
return () => {
|
|
3834
|
+
isCurrent = false;
|
|
3835
|
+
};
|
|
3836
|
+
}, [executionWorkspaceSnapshot, readExecutionWorkspaceDetail, selectedExecutionEntry]);
|
|
3837
|
+
let permissionMode = props.permissionMode ?? "default";
|
|
3838
|
+
let sessionId = "";
|
|
3839
|
+
try {
|
|
3840
|
+
const session = interactiveSession.getSession();
|
|
3841
|
+
permissionMode = session.getPermissionMode();
|
|
3842
|
+
sessionId = session.getSessionId();
|
|
3843
|
+
} catch {
|
|
3844
|
+
}
|
|
3845
|
+
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", children: [
|
|
3846
|
+
/* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
3847
|
+
/* @__PURE__ */ jsx25(Text23, { color: "cyan", bold: true, children: `
|
|
3848
|
+
____ ___ ____ ___ _____ _
|
|
3849
|
+
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
3850
|
+
| |_) | | | | _ \\| | | || | / _ \\
|
|
3851
|
+
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
3852
|
+
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
3853
|
+
` }),
|
|
3854
|
+
/* @__PURE__ */ jsxs19(Text23, { dimColor: true, children: [
|
|
3855
|
+
" v",
|
|
3856
|
+
props.version ?? "0.0.0"
|
|
3857
|
+
] })
|
|
3858
|
+
] }),
|
|
3859
|
+
updateNotice && /* @__PURE__ */ jsx25(UpdateNotice, { message: updateNotice }),
|
|
3860
|
+
/* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
3861
|
+
selectedExecutionEntry && selectedExecutionEntry.kind !== "main_thread" ? /* @__PURE__ */ jsx25(
|
|
3862
|
+
ExecutionWorkspaceDetailPane,
|
|
3863
|
+
{
|
|
3864
|
+
entry: selectedExecutionEntry,
|
|
3865
|
+
page: executionDetailPage,
|
|
3866
|
+
loading: isExecutionDetailLoading,
|
|
3867
|
+
error: executionDetailError
|
|
3868
|
+
}
|
|
3869
|
+
) : /* @__PURE__ */ jsx25(MessageList, { history }),
|
|
3870
|
+
isShuttingDown && /* @__PURE__ */ jsx25(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx25(Text23, { color: "yellow", children: "Shutting down..." }) }),
|
|
3871
|
+
(isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx25(Box21, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx25(
|
|
3872
|
+
StreamingIndicator,
|
|
3873
|
+
{
|
|
3874
|
+
text: streamingText,
|
|
3875
|
+
activeTools,
|
|
3876
|
+
isThinking
|
|
3877
|
+
}
|
|
3878
|
+
) }),
|
|
3879
|
+
/* @__PURE__ */ jsx25(BackgroundTaskPanel, { entries: backgroundWorkspaceEntries })
|
|
3880
|
+
] }),
|
|
3881
|
+
showExecutionWorkspaceSwitcher && /* @__PURE__ */ jsx25(
|
|
3882
|
+
ExecutionWorkspaceSwitcher,
|
|
3883
|
+
{
|
|
3884
|
+
snapshot: executionWorkspaceSnapshot,
|
|
3885
|
+
selectedEntryId: selectedExecutionEntryId,
|
|
3886
|
+
onSelect: selectExecutionWorkspaceEntry,
|
|
3887
|
+
onClose: () => setShowExecutionWorkspaceSwitcher(false)
|
|
3888
|
+
}
|
|
3889
|
+
),
|
|
3890
|
+
permissionRequest && /* @__PURE__ */ jsx25(PermissionPrompt, { request: permissionRequest }),
|
|
3891
|
+
pendingModelId && /* @__PURE__ */ jsx25(
|
|
3892
|
+
ConfirmPrompt,
|
|
3893
|
+
{
|
|
3894
|
+
message: formatModelChangeConfirmationMessage(pendingModelId),
|
|
3895
|
+
onSelect: handleModelConfirm
|
|
3896
|
+
}
|
|
3897
|
+
),
|
|
3898
|
+
pendingInteractionPrompt && /* @__PURE__ */ jsx25(
|
|
3899
|
+
InteractivePrompt,
|
|
3900
|
+
{
|
|
3901
|
+
prompt: pendingInteractionPrompt,
|
|
3902
|
+
onSubmit: handleInteractionSubmit,
|
|
3903
|
+
onCancel: handleInteractionCancel
|
|
3904
|
+
}
|
|
3905
|
+
),
|
|
3906
|
+
showPluginTUI && /* @__PURE__ */ jsx25(
|
|
3907
|
+
PluginTUI,
|
|
3908
|
+
{
|
|
3909
|
+
callbacks: pluginCallbacks,
|
|
3910
|
+
onClose: () => setShowPluginTUI(false),
|
|
3911
|
+
addMessage: (msg) => addEntry(messageToHistoryEntry6(createSystemMessage6(msg.content)))
|
|
3912
|
+
}
|
|
3913
|
+
),
|
|
3914
|
+
showTransportTUI && props.transportRegistry && /* @__PURE__ */ jsx25(
|
|
3915
|
+
TransportTUI,
|
|
3916
|
+
{
|
|
3917
|
+
registry: props.transportRegistry,
|
|
3918
|
+
onClose: () => setShowTransportTUI(false)
|
|
3919
|
+
}
|
|
3920
|
+
),
|
|
3921
|
+
showSessionPicker && /* @__PURE__ */ jsx25(
|
|
3922
|
+
SessionPicker,
|
|
3923
|
+
{
|
|
3924
|
+
sessions: listResumableSessionSummaries(props.sessionStore, props.cwd),
|
|
3925
|
+
onSelect: (id) => {
|
|
3926
|
+
setShowSessionPicker(false);
|
|
3927
|
+
props.onSessionSwitch(id);
|
|
3928
|
+
},
|
|
3929
|
+
onCancel: () => {
|
|
3930
|
+
setShowSessionPicker(false);
|
|
3931
|
+
addEntry(messageToHistoryEntry6(createSystemMessage6("Session resume cancelled.")));
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
),
|
|
3935
|
+
/* @__PURE__ */ jsx25(
|
|
3936
|
+
SessionStatusBar,
|
|
3937
|
+
{
|
|
3938
|
+
cwd,
|
|
3939
|
+
permissionMode,
|
|
3940
|
+
modelId: props.modelId,
|
|
3941
|
+
providerProfileName: props.providerProfileName,
|
|
3942
|
+
providerType: props.providerType,
|
|
3943
|
+
sessionId,
|
|
3944
|
+
isThinking,
|
|
3945
|
+
activeToolCount: activeTools.length,
|
|
3946
|
+
activeBackgroundTaskCount,
|
|
3947
|
+
hasPendingPrompt: pendingPrompt !== null,
|
|
3948
|
+
contextState,
|
|
3949
|
+
sessionName,
|
|
3950
|
+
settings: statusLineSettings
|
|
3951
|
+
}
|
|
3952
|
+
),
|
|
3953
|
+
/* @__PURE__ */ jsx25(
|
|
3954
|
+
InputArea,
|
|
3955
|
+
{
|
|
3956
|
+
onSubmit: handleSubmit,
|
|
3957
|
+
onCancelQueue: handleCancelQueue,
|
|
3958
|
+
isDisabled: !!permissionRequest || showPluginTUI || showTransportTUI || showSessionPicker || showExecutionWorkspaceSwitcher || isShuttingDown || pendingInteractionPrompt !== null || isThinking && !!pendingPrompt,
|
|
3959
|
+
isAborting,
|
|
3960
|
+
pendingPrompt,
|
|
3961
|
+
registry,
|
|
3962
|
+
sessionName,
|
|
3963
|
+
history
|
|
3964
|
+
}
|
|
3965
|
+
),
|
|
3966
|
+
/* @__PURE__ */ jsx25(Text23, { children: " " })
|
|
3967
|
+
] });
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
// src/render.tsx
|
|
3971
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
3972
|
+
async function renderApp(options) {
|
|
3973
|
+
process.on("unhandledRejection", (reason) => {
|
|
3974
|
+
process.stderr.write(`
|
|
3975
|
+
[UNHANDLED REJECTION] ${reason}
|
|
3976
|
+
`);
|
|
3977
|
+
if (reason instanceof Error) {
|
|
3978
|
+
process.stderr.write(`${reason.stack}
|
|
3979
|
+
`);
|
|
3980
|
+
}
|
|
3981
|
+
});
|
|
3982
|
+
const instance = render(/* @__PURE__ */ jsx26(App, { ...options }), { exitOnCtrlC: false });
|
|
3983
|
+
await instance.waitUntilExit();
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
// src/tui-transport.ts
|
|
3987
|
+
var TuiTransport = class {
|
|
3988
|
+
name = "tui";
|
|
3989
|
+
defaultEnabled = true;
|
|
3990
|
+
optionsSchema = {};
|
|
3991
|
+
options;
|
|
3992
|
+
constructor(options) {
|
|
3993
|
+
this.options = options;
|
|
3994
|
+
}
|
|
3995
|
+
attach(_session) {
|
|
3996
|
+
}
|
|
3997
|
+
async start() {
|
|
3998
|
+
await renderApp(this.options);
|
|
3999
|
+
}
|
|
4000
|
+
async stop() {
|
|
4001
|
+
}
|
|
4002
|
+
validateOptions(_options) {
|
|
4003
|
+
return true;
|
|
4004
|
+
}
|
|
4005
|
+
};
|
|
4006
|
+
export {
|
|
4007
|
+
TuiTransport
|
|
4008
|
+
};
|