@robota-sdk/agent-cli 3.0.0-beta.51 → 3.0.0-beta.53

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.
@@ -75,7 +75,14 @@ function getUserSettingsPath() {
75
75
  }
76
76
  function readSettings(path) {
77
77
  if (!existsSync(path)) return {};
78
- return JSON.parse(readFileSync(path, "utf8"));
78
+ const raw = readFileSync(path, "utf8");
79
+ try {
80
+ return JSON.parse(raw);
81
+ } catch {
82
+ process.stderr.write(`Warning: corrupt settings file at ${path}, resetting to defaults
83
+ `);
84
+ return {};
85
+ }
79
86
  }
80
87
  function writeSettings(path, settings) {
81
88
  mkdirSync(dirname(path), { recursive: true });
@@ -147,12 +154,12 @@ import { createHeadlessTransport } from "@robota-sdk/agent-transport-headless";
147
154
  import { render } from "ink";
148
155
 
149
156
  // src/ui/App.tsx
150
- import { useState as useState10, useRef as useRef8, useEffect as useEffect4 } from "react";
151
- import { Box as Box12, Text as Text14, useApp, useInput as useInput8 } from "ink";
152
- import { getModelName, createSystemMessage as createSystemMessage2, messageToHistoryEntry as messageToHistoryEntry2 } from "@robota-sdk/agent-core";
157
+ import { useState as useState13, useEffect as useEffect4 } from "react";
158
+ import { Box as Box13, Text as Text15, useInput as useInput8 } from "ink";
159
+ import { getModelName as getModelName2, createSystemMessage as createSystemMessage3, messageToHistoryEntry as messageToHistoryEntry3 } from "@robota-sdk/agent-core";
153
160
 
154
161
  // src/ui/hooks/useInteractiveSession.ts
155
- import { useState, useRef, useCallback, useEffect } from "react";
162
+ import { useState, useRef, useCallback as useCallback2, useEffect } from "react";
156
163
  import { homedir as homedir2 } from "os";
157
164
  import { join as join3 } from "path";
158
165
  import {
@@ -161,11 +168,8 @@ import {
161
168
  BuiltinCommandSource,
162
169
  SkillCommandSource,
163
170
  PluginCommandSource,
164
- BundlePluginLoader,
165
- buildSkillPrompt
171
+ BundlePluginLoader
166
172
  } from "@robota-sdk/agent-sdk";
167
- import { createSystemMessage, messageToHistoryEntry } from "@robota-sdk/agent-core";
168
- import { randomUUID } from "crypto";
169
173
 
170
174
  // src/ui/tui-state-manager.ts
171
175
  var MAX_RENDERED_MESSAGES = 100;
@@ -293,6 +297,94 @@ var TuiStateManager = class {
293
297
  }
294
298
  };
295
299
 
300
+ // src/ui/hooks/useSlashRouting.ts
301
+ import { useCallback } from "react";
302
+ import { randomUUID } from "crypto";
303
+ import { buildSkillPrompt } from "@robota-sdk/agent-sdk";
304
+ import { createSystemMessage, messageToHistoryEntry } from "@robota-sdk/agent-core";
305
+ function useSlashRouting(interactiveSession, registry, manager) {
306
+ return useCallback(
307
+ async (input) => {
308
+ if (!input.startsWith("/")) {
309
+ await interactiveSession.submit(input);
310
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
311
+ return;
312
+ }
313
+ const parts = input.slice(1).split(/\s+/);
314
+ const cmd = parts[0]?.toLowerCase() ?? "";
315
+ const args = parts.slice(1).join(" ");
316
+ const result = await interactiveSession.executeCommand(cmd, args);
317
+ if (result) {
318
+ manager.addEntry(messageToHistoryEntry(createSystemMessage(result.message)));
319
+ const effects = interactiveSession;
320
+ if (result.data?.modelId) {
321
+ effects._pendingModelId = result.data.modelId;
322
+ return;
323
+ }
324
+ if (result.data?.language) {
325
+ effects._pendingLanguage = result.data.language;
326
+ return;
327
+ }
328
+ if (result.data?.resetRequested) {
329
+ effects._resetRequested = true;
330
+ return;
331
+ }
332
+ if (result.data?.triggerResumePicker) {
333
+ effects._triggerResumePicker = true;
334
+ return;
335
+ }
336
+ if (result.data?.name) {
337
+ effects._sessionName = result.data.name;
338
+ return;
339
+ }
340
+ const ctx = interactiveSession.getContextState();
341
+ manager.setContextState({
342
+ percentage: ctx.usedPercentage,
343
+ usedTokens: ctx.usedTokens,
344
+ maxTokens: ctx.maxTokens
345
+ });
346
+ return;
347
+ }
348
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
349
+ if (skillCmd) {
350
+ manager.addEntry({
351
+ id: randomUUID(),
352
+ timestamp: /* @__PURE__ */ new Date(),
353
+ category: "event",
354
+ type: "skill-invocation",
355
+ data: {
356
+ skillName: cmd,
357
+ source: skillCmd.source,
358
+ message: `Invoking ${skillCmd.source}: ${cmd}`
359
+ }
360
+ });
361
+ const prompt = await buildSkillPrompt(input, registry);
362
+ if (prompt) {
363
+ const qualifiedName = registry.resolveQualifiedName(cmd);
364
+ const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
365
+ await interactiveSession.submit(prompt, input, hookInput);
366
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
367
+ return;
368
+ }
369
+ }
370
+ if (cmd === "exit") {
371
+ interactiveSession._exitRequested = true;
372
+ return;
373
+ }
374
+ if (cmd === "plugin") {
375
+ interactiveSession._triggerPluginTUI = true;
376
+ return;
377
+ }
378
+ manager.addEntry(
379
+ messageToHistoryEntry(
380
+ createSystemMessage(`Unknown command "/${cmd}". Type /help for help.`)
381
+ )
382
+ );
383
+ },
384
+ [interactiveSession, registry, manager]
385
+ );
386
+ }
387
+
296
388
  // src/ui/hooks/useInteractiveSession.ts
297
389
  function initializeSession(props, permissionHandler) {
298
390
  const interactiveSession = new InteractiveSession({
@@ -326,7 +418,7 @@ function useInteractiveSession(props) {
326
418
  const [permissionRequest, setPermissionRequest] = useState(null);
327
419
  const permissionQueueRef = useRef([]);
328
420
  const processingRef = useRef(false);
329
- const processNextPermission = useCallback(() => {
421
+ const processNextPermission = useCallback2(() => {
330
422
  if (processingRef.current) return;
331
423
  const next = permissionQueueRef.current[0];
332
424
  if (!next) {
@@ -346,7 +438,7 @@ function useInteractiveSession(props) {
346
438
  }
347
439
  });
348
440
  }, []);
349
- const permissionHandler = useCallback(
441
+ const permissionHandler = useCallback2(
350
442
  (toolName, toolArgs) => new Promise((resolve) => {
351
443
  permissionQueueRef.current.push({ toolName, toolArgs, resolve });
352
444
  processNextPermission();
@@ -406,91 +498,12 @@ function useInteractiveSession(props) {
406
498
  manager.setPendingPrompt(interactiveSession.getPendingPrompt());
407
499
  }
408
500
  }, [manager.isThinking, interactiveSession, manager]);
409
- const handleSubmit = useCallback(
410
- async (input) => {
411
- if (input.startsWith("/")) {
412
- const parts = input.slice(1).split(/\s+/);
413
- const cmd = parts[0]?.toLowerCase() ?? "";
414
- const args = parts.slice(1).join(" ");
415
- const result = await interactiveSession.executeCommand(cmd, args);
416
- if (result) {
417
- manager.addEntry(messageToHistoryEntry(createSystemMessage(result.message)));
418
- const effects = interactiveSession;
419
- if (result.data?.modelId) {
420
- effects._pendingModelId = result.data.modelId;
421
- return;
422
- }
423
- if (result.data?.language) {
424
- effects._pendingLanguage = result.data.language;
425
- return;
426
- }
427
- if (result.data?.resetRequested) {
428
- effects._resetRequested = true;
429
- return;
430
- }
431
- if (result.data?.triggerResumePicker) {
432
- effects._triggerResumePicker = true;
433
- return;
434
- }
435
- if (result.data?.name) {
436
- effects._sessionName = result.data.name;
437
- return;
438
- }
439
- const ctx = interactiveSession.getContextState();
440
- manager.setContextState({
441
- percentage: ctx.usedPercentage,
442
- usedTokens: ctx.usedTokens,
443
- maxTokens: ctx.maxTokens
444
- });
445
- return;
446
- }
447
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
448
- if (skillCmd) {
449
- manager.addEntry({
450
- id: randomUUID(),
451
- timestamp: /* @__PURE__ */ new Date(),
452
- category: "event",
453
- type: "skill-invocation",
454
- data: {
455
- skillName: cmd,
456
- source: skillCmd.source,
457
- message: `Invoking ${skillCmd.source}: ${cmd}`
458
- }
459
- });
460
- const prompt = await buildSkillPrompt(input, registry);
461
- if (prompt) {
462
- const qualifiedName = registry.resolveQualifiedName(cmd);
463
- const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
464
- await interactiveSession.submit(prompt, input, hookInput);
465
- manager.setPendingPrompt(interactiveSession.getPendingPrompt());
466
- return;
467
- }
468
- }
469
- if (cmd === "exit") {
470
- interactiveSession._exitRequested = true;
471
- return;
472
- }
473
- if (cmd === "plugin") {
474
- interactiveSession._triggerPluginTUI = true;
475
- return;
476
- }
477
- manager.addEntry(
478
- messageToHistoryEntry(
479
- createSystemMessage(`Unknown command "/${cmd}". Type /help for help.`)
480
- )
481
- );
482
- return;
483
- }
484
- await interactiveSession.submit(input);
485
- manager.setPendingPrompt(interactiveSession.getPendingPrompt());
486
- },
487
- [interactiveSession, registry, manager]
488
- );
489
- const handleAbort = useCallback(() => {
501
+ const handleSubmit = useSlashRouting(interactiveSession, registry, manager);
502
+ const handleAbort = useCallback2(() => {
490
503
  manager.setAborting(true);
491
504
  interactiveSession.abort();
492
505
  }, [interactiveSession, manager]);
493
- const handleCancelQueue = useCallback(() => {
506
+ const handleCancelQueue = useCallback2(() => {
494
507
  interactiveSession.cancelQueue();
495
508
  manager.setPendingPrompt(null);
496
509
  }, [interactiveSession, manager]);
@@ -621,8 +634,126 @@ function usePluginCallbacks(cwd) {
621
634
  }, [cwd]);
622
635
  }
623
636
 
637
+ // src/ui/hooks/useSideEffects.ts
638
+ import { useState as useState2, useRef as useRef2, useCallback as useCallback3 } from "react";
639
+ import { useApp } from "ink";
640
+ import { createSystemMessage as createSystemMessage2, messageToHistoryEntry as messageToHistoryEntry2, getModelName } from "@robota-sdk/agent-core";
641
+ var EXIT_DELAY_MS = 500;
642
+ function useSideEffects({
643
+ interactiveSession,
644
+ addEntry,
645
+ baseHandleSubmit,
646
+ setSessionName
647
+ }) {
648
+ const { exit } = useApp();
649
+ const [pendingModelId, setPendingModelId] = useState2(null);
650
+ const pendingModelChangeRef = useRef2(null);
651
+ const [showPluginTUI, setShowPluginTUI] = useState2(false);
652
+ const [showSessionPicker, setShowSessionPicker] = useState2(false);
653
+ const handleSubmit = useCallback3(
654
+ async (input) => {
655
+ await baseHandleSubmit(input);
656
+ const sideEffects = interactiveSession;
657
+ if (sideEffects._pendingModelId) {
658
+ const modelId = sideEffects._pendingModelId;
659
+ delete sideEffects._pendingModelId;
660
+ pendingModelChangeRef.current = modelId;
661
+ setPendingModelId(modelId);
662
+ return;
663
+ }
664
+ if (sideEffects._pendingLanguage) {
665
+ const lang = sideEffects._pendingLanguage;
666
+ delete sideEffects._pendingLanguage;
667
+ const settingsPath = getUserSettingsPath();
668
+ const settings = readSettings(settingsPath);
669
+ settings.language = lang;
670
+ writeSettings(settingsPath, settings);
671
+ addEntry(
672
+ messageToHistoryEntry2(createSystemMessage2(`Language set to "${lang}". Restarting...`))
673
+ );
674
+ setTimeout(() => exit(), EXIT_DELAY_MS);
675
+ return;
676
+ }
677
+ if (sideEffects._resetRequested) {
678
+ delete sideEffects._resetRequested;
679
+ const settingsPath = getUserSettingsPath();
680
+ if (deleteSettings(settingsPath)) {
681
+ addEntry(
682
+ messageToHistoryEntry2(createSystemMessage2(`Deleted ${settingsPath}. Exiting...`))
683
+ );
684
+ } else {
685
+ addEntry(messageToHistoryEntry2(createSystemMessage2("No user settings found.")));
686
+ }
687
+ setTimeout(() => exit(), EXIT_DELAY_MS);
688
+ return;
689
+ }
690
+ if (sideEffects._exitRequested) {
691
+ delete sideEffects._exitRequested;
692
+ setTimeout(() => exit(), EXIT_DELAY_MS);
693
+ return;
694
+ }
695
+ if (sideEffects._triggerPluginTUI) {
696
+ delete sideEffects._triggerPluginTUI;
697
+ setShowPluginTUI(true);
698
+ return;
699
+ }
700
+ if (sideEffects._triggerResumePicker) {
701
+ delete sideEffects._triggerResumePicker;
702
+ setShowSessionPicker(true);
703
+ return;
704
+ }
705
+ if (sideEffects._sessionName) {
706
+ const name = sideEffects._sessionName;
707
+ delete sideEffects._sessionName;
708
+ interactiveSession.setName(name);
709
+ setSessionName(name);
710
+ return;
711
+ }
712
+ },
713
+ [interactiveSession, baseHandleSubmit, addEntry, exit, setSessionName]
714
+ );
715
+ const handleModelConfirm = useCallback3(
716
+ (index) => {
717
+ const modelId = pendingModelChangeRef.current;
718
+ setPendingModelId(null);
719
+ pendingModelChangeRef.current = null;
720
+ if (index === 0 && modelId) {
721
+ try {
722
+ const settingsPath = getUserSettingsPath();
723
+ updateModelInSettings(settingsPath, modelId);
724
+ addEntry(
725
+ messageToHistoryEntry2(
726
+ createSystemMessage2(`Model changed to ${getModelName(modelId)}. Restarting...`)
727
+ )
728
+ );
729
+ setTimeout(() => exit(), EXIT_DELAY_MS);
730
+ } catch (err) {
731
+ addEntry(
732
+ messageToHistoryEntry2(
733
+ createSystemMessage2(`Failed: ${err instanceof Error ? err.message : String(err)}`)
734
+ )
735
+ );
736
+ }
737
+ } else {
738
+ addEntry(messageToHistoryEntry2(createSystemMessage2("Model change cancelled.")));
739
+ }
740
+ },
741
+ [addEntry, exit]
742
+ );
743
+ return {
744
+ handleSubmit,
745
+ pendingModelId,
746
+ showPluginTUI,
747
+ showSessionPicker,
748
+ setPendingModelId,
749
+ setShowPluginTUI,
750
+ setShowSessionPicker,
751
+ handleModelConfirm
752
+ };
753
+ }
754
+
624
755
  // src/ui/MessageList.tsx
625
- import React2 from "react";
756
+ import React from "react";
626
757
  import { Box as Box2, Text as Text2 } from "ink";
627
758
  import { isToolMessage, isAssistantMessage } from "@robota-sdk/agent-core";
628
759
 
@@ -770,7 +901,7 @@ function ToolMessage({ message }) {
770
901
  ] }, i))
771
902
  ] });
772
903
  }
773
- var MessageItem = React2.memo(function MessageItem2({
904
+ var MessageItem = React.memo(function MessageItem2({
774
905
  message
775
906
  }) {
776
907
  if (isToolMessage(message)) {
@@ -894,11 +1025,11 @@ function StatusBar({
894
1025
  }
895
1026
 
896
1027
  // src/ui/InputArea.tsx
897
- import React5, { useState as useState4, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef3 } from "react";
1028
+ import { useState as useState6, useCallback as useCallback4, useRef as useRef4 } from "react";
898
1029
  import { Box as Box5, Text as Text7, useInput as useInput2, useStdout } from "ink";
899
1030
 
900
1031
  // src/ui/CjkTextInput.tsx
901
- import { useRef as useRef2, useState as useState2 } from "react";
1032
+ import { useRef as useRef3, useState as useState3 } from "react";
902
1033
  import { Text as Text4, useInput } from "ink";
903
1034
  import chalk from "chalk";
904
1035
  import stringWidth from "string-width";
@@ -942,16 +1073,17 @@ function CjkTextInput({
942
1073
  placeholder = "",
943
1074
  focus = true,
944
1075
  showCursor = true,
945
- availableWidth
1076
+ availableWidth,
1077
+ cursorHint = null
946
1078
  }) {
947
- const valueRef = useRef2(value);
948
- const cursorRef = useRef2(value.length);
949
- const [, forceRender] = useState2(0);
950
- const isPastingRef = useRef2(false);
951
- const pasteBufferRef = useRef2("");
1079
+ const valueRef = useRef3(value);
1080
+ const cursorRef = useRef3(value.length);
1081
+ const [, forceRender] = useState3(0);
1082
+ const isPastingRef = useRef3(false);
1083
+ const pasteBufferRef = useRef3("");
952
1084
  if (value !== valueRef.current) {
953
1085
  valueRef.current = value;
954
- cursorRef.current = value.length;
1086
+ cursorRef.current = cursorHint != null ? Math.min(cursorHint, value.length) : value.length;
955
1087
  }
956
1088
  useInput(
957
1089
  (input, key) => {
@@ -973,7 +1105,7 @@ function CjkTextInput({
973
1105
  isPastingRef.current = false;
974
1106
  if (text.length > 0) {
975
1107
  if (text.includes("\n") && onPaste) {
976
- onPaste(text);
1108
+ onPaste(text, cursorRef.current);
977
1109
  } else {
978
1110
  const printable2 = filterPrintable(text);
979
1111
  if (printable2.length > 0) {
@@ -1012,7 +1144,7 @@ function CjkTextInput({
1012
1144
  return;
1013
1145
  }
1014
1146
  if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
1015
- onPaste(input.replace(/\r\n?/g, "\n"));
1147
+ onPaste(input.replace(/\r\n?/g, "\n"), cursorRef.current);
1016
1148
  return;
1017
1149
  }
1018
1150
  if (key.leftArrow) {
@@ -1075,14 +1207,14 @@ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
1075
1207
  }
1076
1208
 
1077
1209
  // src/ui/WaveText.tsx
1078
- import { useState as useState3, useEffect as useEffect2 } from "react";
1210
+ import { useState as useState4, useEffect as useEffect2 } from "react";
1079
1211
  import { Text as Text5 } from "ink";
1080
1212
  import { jsx as jsx4 } from "react/jsx-runtime";
1081
1213
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
1082
1214
  var INTERVAL_MS = 400;
1083
1215
  var CHARS_PER_GROUP = 4;
1084
1216
  function WaveText({ text }) {
1085
- const [tick, setTick] = useState3(0);
1217
+ const [tick, setTick] = useState4(0);
1086
1218
  useEffect2(() => {
1087
1219
  const timer = setInterval(() => {
1088
1220
  setTick((prev) => prev + 1);
@@ -1143,8 +1275,8 @@ function expandPasteLabels(text, store) {
1143
1275
  return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
1144
1276
  }
1145
1277
 
1146
- // src/ui/InputArea.tsx
1147
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1278
+ // src/ui/hooks/useAutocomplete.ts
1279
+ import React4, { useState as useState5, useMemo as useMemo2 } from "react";
1148
1280
  function parseSlashInput(value) {
1149
1281
  if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
1150
1282
  const afterSlash = value.slice(1);
@@ -1155,9 +1287,9 @@ function parseSlashInput(value) {
1155
1287
  return { isSlash: true, parentCommand: parent, filter: rest };
1156
1288
  }
1157
1289
  function useAutocomplete(value, registry) {
1158
- const [selectedIndex, setSelectedIndex] = useState4(0);
1159
- const [dismissed, setDismissed] = useState4(false);
1160
- const prevValueRef = React5.useRef(value);
1290
+ const [selectedIndex, setSelectedIndex] = useState5(0);
1291
+ const [dismissed, setDismissed] = useState5(false);
1292
+ const prevValueRef = React4.useRef(value);
1161
1293
  if (prevValueRef.current !== value) {
1162
1294
  prevValueRef.current = value;
1163
1295
  if (dismissed) setDismissed(false);
@@ -1197,6 +1329,9 @@ function useAutocomplete(value, registry) {
1197
1329
  }
1198
1330
  };
1199
1331
  }
1332
+
1333
+ // src/ui/InputArea.tsx
1334
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1200
1335
  var BORDER_HORIZONTAL = 2;
1201
1336
  var PADDING_LEFT = 1;
1202
1337
  var PROMPT_WIDTH = 2;
@@ -1210,12 +1345,13 @@ function InputArea({
1210
1345
  registry,
1211
1346
  sessionName
1212
1347
  }) {
1213
- const [value, setValue] = useState4("");
1214
- const pasteStore = useRef3(/* @__PURE__ */ new Map());
1348
+ const [value, setValue] = useState6("");
1349
+ const [cursorHint, setCursorHint] = useState6(null);
1350
+ const pasteStore = useRef4(/* @__PURE__ */ new Map());
1215
1351
  const { stdout } = useStdout();
1216
1352
  const terminalColumns = stdout?.columns ?? 80;
1217
1353
  const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
1218
- const pasteIdRef = useRef3(0);
1354
+ const pasteIdRef = useRef4(0);
1219
1355
  const {
1220
1356
  showPopup,
1221
1357
  filteredCommands,
@@ -1224,15 +1360,17 @@ function InputArea({
1224
1360
  isSubcommandMode,
1225
1361
  setShowPopup
1226
1362
  } = useAutocomplete(value, registry);
1227
- const handlePaste = useCallback2((text) => {
1363
+ const handlePaste = useCallback4((text, cursorPosition) => {
1228
1364
  pasteIdRef.current += 1;
1229
1365
  const id = pasteIdRef.current;
1230
1366
  pasteStore.current.set(id, text);
1231
1367
  const lineCount = text.split("\n").length;
1232
1368
  const label = `[Pasted text #${id} +${lineCount} lines]`;
1233
- setValue((prev) => prev ? `${prev} ${label}` : label);
1369
+ const newCursorPos = cursorPosition + label.length;
1370
+ setCursorHint(newCursorPos);
1371
+ setValue((prev) => prev.slice(0, cursorPosition) + label + prev.slice(cursorPosition));
1234
1372
  }, []);
1235
- const tabCompleteCommand = useCallback2(
1373
+ const tabCompleteCommand = useCallback4(
1236
1374
  (cmd) => {
1237
1375
  const parsed = parseSlashInput(value);
1238
1376
  if (parsed.parentCommand) {
@@ -1248,7 +1386,7 @@ function InputArea({
1248
1386
  },
1249
1387
  [value, setSelectedIndex]
1250
1388
  );
1251
- const enterSelectCommand = useCallback2(
1389
+ const enterSelectCommand = useCallback4(
1252
1390
  (cmd) => {
1253
1391
  const parsed = parseSlashInput(value);
1254
1392
  if (parsed.parentCommand) {
@@ -1267,7 +1405,7 @@ function InputArea({
1267
1405
  },
1268
1406
  [value, onSubmit, setSelectedIndex]
1269
1407
  );
1270
- const handleSubmit = useCallback2(
1408
+ const handleSubmit = useCallback4(
1271
1409
  (text) => {
1272
1410
  const trimmed = text.trim();
1273
1411
  if (trimmed.length === 0) return;
@@ -1345,11 +1483,15 @@ function InputArea({
1345
1483
  CjkTextInput,
1346
1484
  {
1347
1485
  value,
1348
- onChange: setValue,
1486
+ onChange: (v) => {
1487
+ setValue(v);
1488
+ setCursorHint(null);
1489
+ },
1349
1490
  onSubmit: handleSubmit,
1350
1491
  onPaste: handlePaste,
1351
1492
  placeholder: "Type a message or /help",
1352
- availableWidth
1493
+ availableWidth,
1494
+ cursorHint
1353
1495
  }
1354
1496
  )
1355
1497
  ] }) })
@@ -1357,7 +1499,7 @@ function InputArea({
1357
1499
  }
1358
1500
 
1359
1501
  // src/ui/ConfirmPrompt.tsx
1360
- import { useState as useState5, useCallback as useCallback3, useRef as useRef4 } from "react";
1502
+ import { useState as useState7, useCallback as useCallback5, useRef as useRef5 } from "react";
1361
1503
  import { Box as Box6, Text as Text8, useInput as useInput3 } from "ink";
1362
1504
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1363
1505
  function ConfirmPrompt({
@@ -1365,9 +1507,9 @@ function ConfirmPrompt({
1365
1507
  options = ["Yes", "No"],
1366
1508
  onSelect
1367
1509
  }) {
1368
- const [selected, setSelected] = useState5(0);
1369
- const resolvedRef = useRef4(false);
1370
- const doSelect = useCallback3(
1510
+ const [selected, setSelected] = useState7(0);
1511
+ const resolvedRef = useRef5(false);
1512
+ const doSelect = useCallback5(
1371
1513
  (index) => {
1372
1514
  if (resolvedRef.current) return;
1373
1515
  resolvedRef.current = true;
@@ -1507,10 +1649,10 @@ function StreamingIndicator({ text, activeTools }) {
1507
1649
  }
1508
1650
 
1509
1651
  // src/ui/PluginTUI.tsx
1510
- import { useState as useState8, useEffect as useEffect3, useCallback as useCallback6 } from "react";
1652
+ import { useState as useState11, useCallback as useCallback8 } from "react";
1511
1653
 
1512
1654
  // src/ui/MenuSelect.tsx
1513
- import { useState as useState6, useCallback as useCallback4, useRef as useRef5 } from "react";
1655
+ import { useState as useState8, useCallback as useCallback6, useRef as useRef6 } from "react";
1514
1656
  import { Box as Box9, Text as Text11, useInput as useInput5 } from "ink";
1515
1657
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1516
1658
  function MenuSelect({
@@ -1521,10 +1663,10 @@ function MenuSelect({
1521
1663
  loading,
1522
1664
  error
1523
1665
  }) {
1524
- const [selected, setSelected] = useState6(0);
1525
- const selectedRef = useRef5(0);
1526
- const resolvedRef = useRef5(false);
1527
- const doSelect = useCallback4(
1666
+ const [selected, setSelected] = useState8(0);
1667
+ const selectedRef = useRef6(0);
1668
+ const resolvedRef = useRef6(false);
1669
+ const doSelect = useCallback6(
1528
1670
  (index) => {
1529
1671
  if (resolvedRef.current || items.length === 0) return;
1530
1672
  resolvedRef.current = true;
@@ -1574,7 +1716,7 @@ function MenuSelect({
1574
1716
  }
1575
1717
 
1576
1718
  // src/ui/TextPrompt.tsx
1577
- import { useState as useState7, useRef as useRef6, useCallback as useCallback5 } from "react";
1719
+ import { useState as useState9, useRef as useRef7, useCallback as useCallback7 } from "react";
1578
1720
  import { Box as Box10, Text as Text12, useInput as useInput6 } from "ink";
1579
1721
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1580
1722
  function TextPrompt({
@@ -1584,11 +1726,11 @@ function TextPrompt({
1584
1726
  onCancel,
1585
1727
  validate
1586
1728
  }) {
1587
- const [value, setValue] = useState7("");
1588
- const [error, setError] = useState7();
1589
- const resolvedRef = useRef6(false);
1590
- const valueRef = useRef6("");
1591
- const handleSubmit = useCallback5(() => {
1729
+ const [value, setValue] = useState9("");
1730
+ const [error, setError] = useState9();
1731
+ const resolvedRef = useRef7(false);
1732
+ const valueRef = useRef7("");
1733
+ const handleSubmit = useCallback7(() => {
1592
1734
  if (resolvedRef.current) return;
1593
1735
  const trimmed = valueRef.current.trim();
1594
1736
  if (!trimmed) return;
@@ -1729,61 +1871,16 @@ function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
1729
1871
  }
1730
1872
  }
1731
1873
 
1732
- // src/ui/PluginTUI.tsx
1733
- import { jsx as jsx12 } from "react/jsx-runtime";
1734
- function PluginTUI({ callbacks, onClose, addMessage }) {
1735
- const [stack, setStack] = useState8([{ screen: "main" }]);
1736
- const [items, setItems] = useState8([]);
1737
- const [loading, setLoading] = useState8(false);
1738
- const [error, setError] = useState8();
1739
- const [confirm, setConfirm] = useState8();
1740
- const [refreshCounter, setRefreshCounter] = useState8(0);
1741
- const current = stack[stack.length - 1] ?? { screen: "main" };
1742
- const push = useCallback6((state) => {
1743
- setStack((prev) => [...prev, state]);
1744
- setItems([]);
1745
- setError(void 0);
1746
- }, []);
1747
- const pop = useCallback6(() => {
1748
- setStack((prev) => {
1749
- if (prev.length <= 1) {
1750
- onClose();
1751
- return prev;
1752
- }
1753
- return prev.slice(0, -1);
1754
- });
1874
+ // src/ui/hooks/usePluginScreenData.ts
1875
+ import { useState as useState10, useEffect as useEffect3 } from "react";
1876
+ function usePluginScreenData(screen, marketplace, callbacks, refreshCounter, stackLength) {
1877
+ const [items, setItems] = useState10([]);
1878
+ const [loading, setLoading] = useState10(false);
1879
+ const [error, setError] = useState10();
1880
+ useEffect3(() => {
1755
1881
  setItems([]);
1756
1882
  setError(void 0);
1757
- }, [onClose]);
1758
- const popN = useCallback6(
1759
- (n) => {
1760
- setStack((prev) => {
1761
- const next = prev.slice(0, Math.max(1, prev.length - n));
1762
- if (next.length === 0) {
1763
- onClose();
1764
- return prev;
1765
- }
1766
- return next;
1767
- });
1768
- setItems([]);
1769
- setError(void 0);
1770
- },
1771
- [onClose]
1772
- );
1773
- const notify = useCallback6(
1774
- (content) => {
1775
- addMessage?.({ role: "system", content });
1776
- },
1777
- [addMessage]
1778
- );
1779
- const refresh = useCallback6(() => {
1780
- setItems([]);
1781
- setRefreshCounter((c) => c + 1);
1782
- }, []);
1783
- const nav = { push, pop, popN, notify, setConfirm, refresh };
1784
- useEffect3(() => {
1785
- const screen2 = current.screen;
1786
- if (screen2 === "marketplace-list") {
1883
+ if (screen === "marketplace-list") {
1787
1884
  setLoading(true);
1788
1885
  callbacks.marketplaceList().then((sources) => {
1789
1886
  const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
@@ -1798,10 +1895,10 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1798
1895
  setError(err instanceof Error ? err.message : String(err));
1799
1896
  setLoading(false);
1800
1897
  });
1801
- } else if (screen2 === "marketplace-browse") {
1802
- const marketplace = current.context?.marketplace ?? "";
1898
+ } else if (screen === "marketplace-browse") {
1899
+ const mp = marketplace ?? "";
1803
1900
  setLoading(true);
1804
- callbacks.listAvailablePlugins(marketplace).then((plugins) => {
1901
+ callbacks.listAvailablePlugins(mp).then((plugins) => {
1805
1902
  setItems(
1806
1903
  plugins.map((p) => ({
1807
1904
  label: p.name,
@@ -1814,7 +1911,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1814
1911
  setError(err instanceof Error ? err.message : String(err));
1815
1912
  setLoading(false);
1816
1913
  });
1817
- } else if (screen2 === "installed-list") {
1914
+ } else if (screen === "installed-list") {
1818
1915
  setLoading(true);
1819
1916
  callbacks.listInstalled().then((plugins) => {
1820
1917
  setItems(
@@ -1830,8 +1927,60 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1830
1927
  setLoading(false);
1831
1928
  });
1832
1929
  }
1833
- }, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
1834
- const handleSelect = useCallback6(
1930
+ }, [stackLength, screen, marketplace, callbacks, refreshCounter]);
1931
+ return { items, loading, error };
1932
+ }
1933
+
1934
+ // src/ui/PluginTUI.tsx
1935
+ import { jsx as jsx12 } from "react/jsx-runtime";
1936
+ function PluginTUI({ callbacks, onClose, addMessage }) {
1937
+ const [stack, setStack] = useState11([{ screen: "main" }]);
1938
+ const [confirm, setConfirm] = useState11();
1939
+ const [refreshCounter, setRefreshCounter] = useState11(0);
1940
+ const current = stack[stack.length - 1] ?? { screen: "main" };
1941
+ const push = useCallback8((state) => {
1942
+ setStack((prev) => [...prev, state]);
1943
+ }, []);
1944
+ const pop = useCallback8(() => {
1945
+ setStack((prev) => {
1946
+ if (prev.length <= 1) {
1947
+ onClose();
1948
+ return prev;
1949
+ }
1950
+ return prev.slice(0, -1);
1951
+ });
1952
+ }, [onClose]);
1953
+ const popN = useCallback8(
1954
+ (n) => {
1955
+ setStack((prev) => {
1956
+ const next = prev.slice(0, Math.max(1, prev.length - n));
1957
+ if (next.length === 0) {
1958
+ onClose();
1959
+ return prev;
1960
+ }
1961
+ return next;
1962
+ });
1963
+ },
1964
+ [onClose]
1965
+ );
1966
+ const notify = useCallback8(
1967
+ (content) => {
1968
+ addMessage?.({ role: "system", content });
1969
+ },
1970
+ [addMessage]
1971
+ );
1972
+ const refresh = useCallback8(() => {
1973
+ setRefreshCounter((c) => c + 1);
1974
+ }, []);
1975
+ const nav = { push, pop, popN, notify, setConfirm, refresh };
1976
+ const { items, loading, error } = usePluginScreenData(
1977
+ current.screen,
1978
+ current.context?.marketplace,
1979
+ callbacks,
1980
+ refreshCounter,
1981
+ stack.length
1982
+ );
1983
+ const handleSelect = useCallback8(
1835
1984
  (value) => {
1836
1985
  const screen2 = current.screen;
1837
1986
  const ctx = current.context;
@@ -1849,7 +1998,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1849
1998
  },
1850
1999
  [current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
1851
2000
  );
1852
- const handleTextSubmit = useCallback6(
2001
+ const handleTextSubmit = useCallback8(
1853
2002
  (value) => {
1854
2003
  if (current.screen === "marketplace-add") {
1855
2004
  callbacks.marketplaceAdd(value).then((name) => {
@@ -1957,8 +2106,11 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1957
2106
  );
1958
2107
  }
1959
2108
 
2109
+ // src/ui/SessionPicker.tsx
2110
+ import { Box as Box12, Text as Text14 } from "ink";
2111
+
1960
2112
  // src/ui/ListPicker.tsx
1961
- import { useState as useState9, useRef as useRef7 } from "react";
2113
+ import { useState as useState12, useRef as useRef8 } from "react";
1962
2114
  import { Box as Box11, Text as Text13, useInput as useInput7 } from "ink";
1963
2115
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1964
2116
  var DEFAULT_MAX_VISIBLE = 3;
@@ -1969,10 +2121,10 @@ function ListPicker({
1969
2121
  onCancel,
1970
2122
  maxVisible = DEFAULT_MAX_VISIBLE
1971
2123
  }) {
1972
- const [selectedIndex, setSelectedIndex] = useState9(0);
1973
- const [scrollOffset, setScrollOffset] = useState9(0);
1974
- const selectedRef = useRef7(0);
1975
- const resolvedRef = useRef7(false);
2124
+ const [selectedIndex, setSelectedIndex] = useState12(0);
2125
+ const [scrollOffset, setScrollOffset] = useState12(0);
2126
+ const selectedRef = useRef8(0);
2127
+ const resolvedRef = useRef8(false);
1976
2128
  useInput7((_input, key) => {
1977
2129
  if (resolvedRef.current) return;
1978
2130
  if (key.escape) {
@@ -2024,13 +2176,62 @@ function ListPicker({
2024
2176
  ] });
2025
2177
  }
2026
2178
 
2027
- // src/ui/App.tsx
2179
+ // src/ui/SessionPicker.tsx
2028
2180
  import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
2029
- var EXIT_DELAY_MS = 500;
2030
2181
  var SESSION_ID_DISPLAY_LENGTH = 8;
2182
+ function SessionPicker({
2183
+ sessionStore,
2184
+ cwd,
2185
+ onSelect,
2186
+ onCancel
2187
+ }) {
2188
+ const sessions = (sessionStore?.list() ?? []).filter((s) => s.cwd === cwd);
2189
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2190
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
2191
+ /* @__PURE__ */ jsx14(
2192
+ ListPicker,
2193
+ {
2194
+ items: sessions,
2195
+ renderItem: (session, isSelected) => {
2196
+ const lastMsg = session.messages.slice().reverse().find((m) => {
2197
+ const msg = m;
2198
+ return msg.role === "assistant" && msg.content;
2199
+ });
2200
+ const rawPreview = lastMsg?.content?.replace(/[\n\r]+/g, " ").trim() ?? "";
2201
+ const preview = rawPreview ? rawPreview.slice(0, 60) + (rawPreview.length > 60 ? "..." : "") : "";
2202
+ return /* @__PURE__ */ jsxs12(Text14, { children: [
2203
+ isSelected ? "> " : " ",
2204
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
2205
+ " ",
2206
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
2207
+ month: "short",
2208
+ day: "numeric",
2209
+ hour: "2-digit",
2210
+ minute: "2-digit"
2211
+ }) }),
2212
+ " ",
2213
+ /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
2214
+ "msgs: ",
2215
+ session.messages.length
2216
+ ] }),
2217
+ preview ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
2218
+ "\n ",
2219
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: preview })
2220
+ ] }) : null
2221
+ ] });
2222
+ },
2223
+ onSelect: (session) => onSelect(session.id),
2224
+ onCancel
2225
+ }
2226
+ )
2227
+ ] });
2228
+ }
2229
+
2230
+ // src/ui/App.tsx
2231
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2031
2232
  function App(props) {
2032
- const [activeSessionId, setActiveSessionId] = useState10(props.resumeSessionId);
2033
- return /* @__PURE__ */ jsx14(
2233
+ const [activeSessionId, setActiveSessionId] = useState13(props.resumeSessionId);
2234
+ return /* @__PURE__ */ jsx15(
2034
2235
  AppInner,
2035
2236
  {
2036
2237
  ...props,
@@ -2041,7 +2242,6 @@ function App(props) {
2041
2242
  );
2042
2243
  }
2043
2244
  function AppInner(props) {
2044
- const { exit } = useApp();
2045
2245
  const cwd = props.cwd;
2046
2246
  const {
2047
2247
  interactiveSession,
@@ -2069,13 +2269,21 @@ function AppInner(props) {
2069
2269
  sessionName: props.sessionName
2070
2270
  });
2071
2271
  const pluginCallbacks = usePluginCallbacks(cwd);
2072
- const [pendingModelId, setPendingModelId] = useState10(null);
2073
- const pendingModelChangeRef = useRef8(null);
2074
- const [showPluginTUI, setShowPluginTUI] = useState10(false);
2075
- const [showSessionPicker, setShowSessionPicker] = useState10(
2076
- props.resumeSessionId === "__picker__"
2077
- );
2078
- const [sessionName, setSessionName] = useState10(props.sessionName);
2272
+ const [sessionName, setSessionName] = useState13(props.sessionName);
2273
+ const {
2274
+ handleSubmit,
2275
+ pendingModelId,
2276
+ showPluginTUI,
2277
+ showSessionPicker,
2278
+ setShowPluginTUI,
2279
+ setShowSessionPicker,
2280
+ handleModelConfirm
2281
+ } = useSideEffects({
2282
+ interactiveSession,
2283
+ addEntry,
2284
+ baseHandleSubmit,
2285
+ setSessionName
2286
+ });
2079
2287
  useEffect4(() => {
2080
2288
  const name = interactiveSession?.getName?.();
2081
2289
  if (name && !sessionName) setSessionName(name);
@@ -2084,68 +2292,9 @@ function AppInner(props) {
2084
2292
  const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
2085
2293
  process.stdout.write(`\x1B]0;${title}\x07`);
2086
2294
  }, [sessionName]);
2087
- const handleSubmit = async (input) => {
2088
- await baseHandleSubmit(input);
2089
- const sideEffects = interactiveSession;
2090
- if (sideEffects._pendingModelId) {
2091
- const modelId = sideEffects._pendingModelId;
2092
- delete sideEffects._pendingModelId;
2093
- pendingModelChangeRef.current = modelId;
2094
- setPendingModelId(modelId);
2095
- return;
2096
- }
2097
- if (sideEffects._pendingLanguage) {
2098
- const lang = sideEffects._pendingLanguage;
2099
- delete sideEffects._pendingLanguage;
2100
- const settingsPath = getUserSettingsPath();
2101
- const settings = readSettings(settingsPath);
2102
- settings.language = lang;
2103
- writeSettings(settingsPath, settings);
2104
- addEntry(
2105
- messageToHistoryEntry2(createSystemMessage2(`Language set to "${lang}". Restarting...`))
2106
- );
2107
- setTimeout(() => exit(), EXIT_DELAY_MS);
2108
- return;
2109
- }
2110
- if (sideEffects._resetRequested) {
2111
- delete sideEffects._resetRequested;
2112
- const settingsPath = getUserSettingsPath();
2113
- if (deleteSettings(settingsPath)) {
2114
- addEntry(messageToHistoryEntry2(createSystemMessage2(`Deleted ${settingsPath}. Exiting...`)));
2115
- } else {
2116
- addEntry(messageToHistoryEntry2(createSystemMessage2("No user settings found.")));
2117
- }
2118
- setTimeout(() => exit(), EXIT_DELAY_MS);
2119
- return;
2120
- }
2121
- if (sideEffects._exitRequested) {
2122
- delete sideEffects._exitRequested;
2123
- setTimeout(() => exit(), EXIT_DELAY_MS);
2124
- return;
2125
- }
2126
- if (sideEffects._triggerPluginTUI) {
2127
- delete sideEffects._triggerPluginTUI;
2128
- setShowPluginTUI(true);
2129
- return;
2130
- }
2131
- if (sideEffects._triggerResumePicker) {
2132
- delete sideEffects._triggerResumePicker;
2133
- setShowSessionPicker(true);
2134
- return;
2135
- }
2136
- if (sideEffects._sessionName) {
2137
- const name = sideEffects._sessionName;
2138
- delete sideEffects._sessionName;
2139
- interactiveSession.setName(name);
2140
- setSessionName(name);
2141
- return;
2142
- }
2143
- };
2144
2295
  useInput8(
2145
2296
  (_input, key) => {
2146
- if (key.escape && isThinking) {
2147
- handleAbort();
2148
- }
2297
+ if (key.escape && isThinking) handleAbort();
2149
2298
  },
2150
2299
  { isActive: !permissionRequest && !showPluginTUI && !showSessionPicker }
2151
2300
  );
@@ -2157,117 +2306,60 @@ function AppInner(props) {
2157
2306
  sessionId = session.getSessionId();
2158
2307
  } catch {
2159
2308
  }
2160
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
2161
- /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2162
- /* @__PURE__ */ jsx14(Text14, { color: "cyan", bold: true, children: `
2309
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
2310
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2311
+ /* @__PURE__ */ jsx15(Text15, { color: "cyan", bold: true, children: `
2163
2312
  ____ ___ ____ ___ _____ _
2164
2313
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
2165
2314
  | |_) | | | | _ \\| | | || | / _ \\
2166
2315
  | _ <| |_| | |_) | |_| || |/ ___ \\
2167
2316
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
2168
2317
  ` }),
2169
- /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
2318
+ /* @__PURE__ */ jsxs13(Text15, { dimColor: true, children: [
2170
2319
  " v",
2171
2320
  props.version ?? "0.0.0"
2172
2321
  ] })
2173
2322
  ] }),
2174
- /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2175
- /* @__PURE__ */ jsx14(MessageList, { history }),
2176
- (isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx14(StreamingIndicator, { text: streamingText, activeTools }) })
2323
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2324
+ /* @__PURE__ */ jsx15(MessageList, { history }),
2325
+ (isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx15(StreamingIndicator, { text: streamingText, activeTools }) })
2177
2326
  ] }),
2178
- permissionRequest && /* @__PURE__ */ jsx14(PermissionPrompt, { request: permissionRequest }),
2179
- pendingModelId && /* @__PURE__ */ jsx14(
2327
+ permissionRequest && /* @__PURE__ */ jsx15(PermissionPrompt, { request: permissionRequest }),
2328
+ pendingModelId && /* @__PURE__ */ jsx15(
2180
2329
  ConfirmPrompt,
2181
2330
  {
2182
- message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
2183
- onSelect: (index) => {
2184
- setPendingModelId(null);
2185
- pendingModelChangeRef.current = null;
2186
- if (index === 0) {
2187
- try {
2188
- const settingsPath = getUserSettingsPath();
2189
- updateModelInSettings(settingsPath, pendingModelId);
2190
- addEntry(
2191
- messageToHistoryEntry2(
2192
- createSystemMessage2(
2193
- `Model changed to ${getModelName(pendingModelId)}. Restarting...`
2194
- )
2195
- )
2196
- );
2197
- setTimeout(() => exit(), EXIT_DELAY_MS);
2198
- } catch (err) {
2199
- addEntry(
2200
- messageToHistoryEntry2(
2201
- createSystemMessage2(
2202
- `Failed: ${err instanceof Error ? err.message : String(err)}`
2203
- )
2204
- )
2205
- );
2206
- }
2207
- } else {
2208
- addEntry(messageToHistoryEntry2(createSystemMessage2("Model change cancelled.")));
2209
- }
2210
- }
2331
+ message: `Change model to ${getModelName2(pendingModelId)}? This will restart the session.`,
2332
+ onSelect: handleModelConfirm
2211
2333
  }
2212
2334
  ),
2213
- showPluginTUI && /* @__PURE__ */ jsx14(
2335
+ showPluginTUI && /* @__PURE__ */ jsx15(
2214
2336
  PluginTUI,
2215
2337
  {
2216
2338
  callbacks: pluginCallbacks,
2217
2339
  onClose: () => setShowPluginTUI(false),
2218
- addMessage: (msg) => addEntry(messageToHistoryEntry2(createSystemMessage2(msg.content)))
2340
+ addMessage: (msg) => addEntry(messageToHistoryEntry3(createSystemMessage3(msg.content)))
2219
2341
  }
2220
2342
  ),
2221
- showSessionPicker && /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2222
- /* @__PURE__ */ jsx14(Text14, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
2223
- /* @__PURE__ */ jsx14(
2224
- ListPicker,
2225
- {
2226
- items: (props.sessionStore?.list() ?? []).filter((s) => s.cwd === props.cwd),
2227
- renderItem: (session, isSelected) => {
2228
- const lastMsg = session.messages.slice().reverse().find((m) => {
2229
- const msg = m;
2230
- return msg.role === "assistant" && msg.content;
2231
- });
2232
- const rawPreview = lastMsg?.content?.replace(/[\n\r]+/g, " ").trim() ?? "";
2233
- const preview = rawPreview ? rawPreview.slice(0, 60) + (rawPreview.length > 60 ? "..." : "") : "";
2234
- return /* @__PURE__ */ jsxs12(Text14, { children: [
2235
- isSelected ? "> " : " ",
2236
- /* @__PURE__ */ jsx14(Text14, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
2237
- " ",
2238
- /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
2239
- month: "short",
2240
- day: "numeric",
2241
- hour: "2-digit",
2242
- minute: "2-digit"
2243
- }) }),
2244
- " ",
2245
- /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
2246
- "msgs: ",
2247
- session.messages.length
2248
- ] }),
2249
- preview ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
2250
- "\n ",
2251
- /* @__PURE__ */ jsx14(Text14, { color: "gray", children: preview })
2252
- ] }) : null
2253
- ] });
2254
- },
2255
- onSelect: (session) => {
2256
- setShowSessionPicker(false);
2257
- props.onSessionSwitch(session.id);
2258
- },
2259
- onCancel: () => {
2260
- setShowSessionPicker(false);
2261
- addEntry(messageToHistoryEntry2(createSystemMessage2("Session resume cancelled.")));
2262
- }
2343
+ showSessionPicker && /* @__PURE__ */ jsx15(
2344
+ SessionPicker,
2345
+ {
2346
+ sessionStore: props.sessionStore,
2347
+ cwd: props.cwd,
2348
+ onSelect: (id) => {
2349
+ setShowSessionPicker(false);
2350
+ props.onSessionSwitch(id);
2351
+ },
2352
+ onCancel: () => {
2353
+ setShowSessionPicker(false);
2354
+ addEntry(messageToHistoryEntry3(createSystemMessage3("Session resume cancelled.")));
2263
2355
  }
2264
- )
2265
- ] }),
2266
- /* @__PURE__ */ jsx14(
2356
+ }
2357
+ ),
2358
+ /* @__PURE__ */ jsx15(
2267
2359
  StatusBar,
2268
2360
  {
2269
2361
  permissionMode,
2270
- modelName: props.modelId ? getModelName(props.modelId) : "",
2362
+ modelName: props.modelId ? getModelName2(props.modelId) : "",
2271
2363
  sessionId,
2272
2364
  messageCount: history.length,
2273
2365
  isThinking,
@@ -2277,7 +2369,7 @@ function AppInner(props) {
2277
2369
  sessionName
2278
2370
  }
2279
2371
  ),
2280
- /* @__PURE__ */ jsx14(
2372
+ /* @__PURE__ */ jsx15(
2281
2373
  InputArea,
2282
2374
  {
2283
2375
  onSubmit: handleSubmit,
@@ -2289,12 +2381,12 @@ function AppInner(props) {
2289
2381
  sessionName
2290
2382
  }
2291
2383
  ),
2292
- /* @__PURE__ */ jsx14(Text14, { children: " " })
2384
+ /* @__PURE__ */ jsx15(Text15, { children: " " })
2293
2385
  ] });
2294
2386
  }
2295
2387
 
2296
2388
  // src/ui/render.tsx
2297
- import { jsx as jsx15 } from "react/jsx-runtime";
2389
+ import { jsx as jsx16 } from "react/jsx-runtime";
2298
2390
  function renderApp(options) {
2299
2391
  process.on("unhandledRejection", (reason) => {
2300
2392
  process.stderr.write(`
@@ -2308,7 +2400,7 @@ function renderApp(options) {
2308
2400
  if (process.stdin.isTTY && process.stdout.isTTY) {
2309
2401
  process.stdout.write("\x1B[?2004h");
2310
2402
  }
2311
- const instance = render(/* @__PURE__ */ jsx15(App, { ...options }), {
2403
+ const instance = render(/* @__PURE__ */ jsx16(App, { ...options }), {
2312
2404
  exitOnCtrlC: true
2313
2405
  });
2314
2406
  instance.waitUntilExit().then(() => {