@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.
@@ -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
+ };