@robota-sdk/agent-cli 3.0.0-beta.45 → 3.0.0-beta.47

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.
@@ -3,6 +3,7 @@ import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as
3
3
  import { join as join5, dirname as dirname2 } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { InteractiveSession as InteractiveSession2 } from "@robota-sdk/agent-sdk";
6
+ import { SessionStore } from "@robota-sdk/agent-sessions";
6
7
 
7
8
  // src/utils/cli-args.ts
8
9
  import { parseArgs } from "util";
@@ -31,12 +32,17 @@ function parseCliArgs() {
31
32
  allowPositionals: true,
32
33
  options: {
33
34
  p: { type: "boolean", short: "p", default: false },
34
- c: { type: "boolean", short: "c", default: false },
35
- r: { type: "string", short: "r" },
35
+ continue: { type: "boolean", short: "c", default: false },
36
+ resume: { type: "string", short: "r" },
36
37
  model: { type: "string" },
37
38
  language: { type: "string" },
38
39
  "permission-mode": { type: "string" },
39
40
  "max-turns": { type: "string" },
41
+ "fork-session": { type: "boolean", default: false },
42
+ name: { type: "string", short: "n" },
43
+ "output-format": { type: "string" },
44
+ "system-prompt": { type: "string" },
45
+ "append-system-prompt": { type: "string" },
40
46
  version: { type: "boolean", default: false },
41
47
  reset: { type: "boolean", default: false }
42
48
  }
@@ -44,12 +50,17 @@ function parseCliArgs() {
44
50
  return {
45
51
  positional: positionals,
46
52
  printMode: values["p"] ?? false,
47
- continueMode: values["c"] ?? false,
48
- resumeId: values["r"],
53
+ continueMode: values["continue"] ?? false,
54
+ resumeId: values["resume"],
49
55
  model: values["model"],
50
56
  language: values["language"],
51
57
  permissionMode: parsePermissionMode(values["permission-mode"]),
52
58
  maxTurns: parseMaxTurns(values["max-turns"]),
59
+ forkSession: values["fork-session"] ?? false,
60
+ sessionName: values["name"],
61
+ outputFormat: values["output-format"],
62
+ systemPrompt: values["system-prompt"],
63
+ appendSystemPrompt: values["append-system-prompt"],
53
64
  version: values["version"] ?? false,
54
65
  reset: values["reset"] ?? false
55
66
  };
@@ -129,12 +140,15 @@ function createProviderFromSettings(cwd, modelOverride) {
129
140
  }
130
141
  }
131
142
 
143
+ // src/cli.ts
144
+ import { createHeadlessTransport } from "@robota-sdk/agent-transport-headless";
145
+
132
146
  // src/ui/render.tsx
133
147
  import { render } from "ink";
134
148
 
135
149
  // src/ui/App.tsx
136
- import { useState as useState9, useRef as useRef7 } from "react";
137
- import { Box as Box11, Text as Text13, useApp, useInput as useInput7 } from "ink";
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";
138
152
  import { getModelName, createSystemMessage as createSystemMessage2, messageToHistoryEntry as messageToHistoryEntry2 } from "@robota-sdk/agent-core";
139
153
 
140
154
  // src/ui/hooks/useInteractiveSession.ts
@@ -258,7 +272,11 @@ function initializeSession(props, permissionHandler) {
258
272
  provider: props.provider,
259
273
  permissionMode: props.permissionMode,
260
274
  maxTurns: props.maxTurns,
261
- permissionHandler
275
+ permissionHandler,
276
+ sessionStore: props.sessionStore,
277
+ resumeSessionId: props.resumeSessionId,
278
+ forkSession: props.forkSession,
279
+ sessionName: props.sessionName
262
280
  });
263
281
  const registry = new CommandRegistry();
264
282
  registry.addSource(new BuiltinCommandSource());
@@ -313,6 +331,12 @@ function useInteractiveSession(props) {
313
331
  }
314
332
  const { interactiveSession, registry, manager } = stateRef.current;
315
333
  manager.onChange = () => forceRender((n) => n + 1);
334
+ if (manager.history.length === 0) {
335
+ const restored = interactiveSession.getFullHistory();
336
+ if (restored.length > 0) {
337
+ manager.syncHistory(restored);
338
+ }
339
+ }
316
340
  useEffect(() => {
317
341
  interactiveSession.on("text_delta", manager.onTextDelta);
318
342
  interactiveSession.on("tool_start", manager.onToolStart);
@@ -329,6 +353,10 @@ function useInteractiveSession(props) {
329
353
  usedTokens: ctx.usedTokens,
330
354
  maxTokens: ctx.maxTokens
331
355
  });
356
+ const restored = interactiveSession.getFullHistory();
357
+ if (restored.length > 0) {
358
+ manager.syncHistory(restored);
359
+ }
332
360
  clearInterval(initCheck);
333
361
  } catch {
334
362
  }
@@ -372,6 +400,14 @@ function useInteractiveSession(props) {
372
400
  effects._resetRequested = true;
373
401
  return;
374
402
  }
403
+ if (result.data?.triggerResumePicker) {
404
+ effects._triggerResumePicker = true;
405
+ return;
406
+ }
407
+ if (result.data?.name) {
408
+ effects._sessionName = result.data.name;
409
+ return;
410
+ }
375
411
  const ctx = interactiveSession.getContextState();
376
412
  manager.setContextState({
377
413
  percentage: ctx.usedPercentage,
@@ -764,7 +800,7 @@ function MessageList({ history }) {
764
800
  // src/ui/StatusBar.tsx
765
801
  import { Box as Box3, Text as Text3 } from "ink";
766
802
  import { formatTokenCount } from "@robota-sdk/agent-core";
767
- import { jsx as jsx2, jsxs as jsxs3 } from "react/jsx-runtime";
803
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs3 } from "react/jsx-runtime";
768
804
  var CONTEXT_YELLOW_THRESHOLD = 70;
769
805
  var CONTEXT_RED_THRESHOLD = 90;
770
806
  function getContextColor(percentage) {
@@ -780,7 +816,8 @@ function StatusBar({
780
816
  isThinking,
781
817
  contextPercentage,
782
818
  contextUsedTokens,
783
- contextMaxTokens
819
+ contextMaxTokens,
820
+ sessionName
784
821
  }) {
785
822
  const contextColor = getContextColor(contextPercentage);
786
823
  return /* @__PURE__ */ jsxs3(
@@ -796,6 +833,10 @@ function StatusBar({
796
833
  /* @__PURE__ */ jsx2(Text3, { color: "cyan", bold: true, children: "Mode:" }),
797
834
  " ",
798
835
  /* @__PURE__ */ jsx2(Text3, { children: permissionMode }),
836
+ sessionName && /* @__PURE__ */ jsxs3(Fragment2, { children: [
837
+ " | ",
838
+ /* @__PURE__ */ jsx2(Text3, { color: "magenta", children: sessionName })
839
+ ] }),
799
840
  " | ",
800
841
  /* @__PURE__ */ jsx2(Text3, { dimColor: true, children: modelName }),
801
842
  " | ",
@@ -879,9 +920,7 @@ function CjkTextInput({
879
920
  const pasteBufferRef = useRef2("");
880
921
  if (value !== valueRef.current) {
881
922
  valueRef.current = value;
882
- if (cursorRef.current > value.length) {
883
- cursorRef.current = value.length;
884
- }
923
+ cursorRef.current = value.length;
885
924
  }
886
925
  useInput(
887
926
  (input, key) => {
@@ -1137,7 +1176,8 @@ function InputArea({
1137
1176
  isDisabled,
1138
1177
  isAborting,
1139
1178
  pendingPrompt,
1140
- registry
1179
+ registry,
1180
+ sessionName
1141
1181
  }) {
1142
1182
  const [value, setValue] = useState4("");
1143
1183
  const pasteStore = useRef3(/* @__PURE__ */ new Map());
@@ -1161,23 +1201,23 @@ function InputArea({
1161
1201
  const label = `[Pasted text #${id} +${lineCount} lines]`;
1162
1202
  setValue((prev) => prev ? `${prev} ${label}` : label);
1163
1203
  }, []);
1164
- const handleSubmit = useCallback2(
1165
- (text) => {
1166
- const trimmed = text.trim();
1167
- if (trimmed.length === 0) return;
1168
- if (showPopup && filteredCommands[selectedIndex]) {
1169
- selectCommand(filteredCommands[selectedIndex]);
1204
+ const tabCompleteCommand = useCallback2(
1205
+ (cmd) => {
1206
+ const parsed = parseSlashInput(value);
1207
+ if (parsed.parentCommand) {
1208
+ setValue(`/${parsed.parentCommand} ${cmd.name} `);
1170
1209
  return;
1171
1210
  }
1172
- const expanded = expandPasteLabels(trimmed, pasteStore.current);
1173
- setValue("");
1174
- pasteStore.current.clear();
1175
- pasteIdRef.current = 0;
1176
- onSubmit(expanded);
1211
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
1212
+ setValue(`/${cmd.name} `);
1213
+ setSelectedIndex(0);
1214
+ return;
1215
+ }
1216
+ setValue(`/${cmd.name} `);
1177
1217
  },
1178
- [showPopup, filteredCommands, selectedIndex, onSubmit]
1218
+ [value, setSelectedIndex]
1179
1219
  );
1180
- const selectCommand = useCallback2(
1220
+ const enterSelectCommand = useCallback2(
1181
1221
  (cmd) => {
1182
1222
  const parsed = parseSlashInput(value);
1183
1223
  if (parsed.parentCommand) {
@@ -1196,6 +1236,22 @@ function InputArea({
1196
1236
  },
1197
1237
  [value, onSubmit, setSelectedIndex]
1198
1238
  );
1239
+ const handleSubmit = useCallback2(
1240
+ (text) => {
1241
+ const trimmed = text.trim();
1242
+ if (trimmed.length === 0) return;
1243
+ if (showPopup && filteredCommands[selectedIndex]) {
1244
+ enterSelectCommand(filteredCommands[selectedIndex]);
1245
+ return;
1246
+ }
1247
+ const expanded = expandPasteLabels(trimmed, pasteStore.current);
1248
+ setValue("");
1249
+ pasteStore.current.clear();
1250
+ pasteIdRef.current = 0;
1251
+ onSubmit(expanded);
1252
+ },
1253
+ [showPopup, filteredCommands, selectedIndex, onSubmit, enterSelectCommand]
1254
+ );
1199
1255
  useInput2(
1200
1256
  (_input, key) => {
1201
1257
  if (!showPopup) return;
@@ -1207,7 +1263,7 @@ function InputArea({
1207
1263
  setShowPopup(false);
1208
1264
  } else if (key.tab) {
1209
1265
  const cmd = filteredCommands[selectedIndex];
1210
- if (cmd) selectCommand(cmd);
1266
+ if (cmd) tabCompleteCommand(cmd);
1211
1267
  }
1212
1268
  },
1213
1269
  { isActive: showPopup && !isDisabled }
@@ -1220,6 +1276,17 @@ function InputArea({
1220
1276
  },
1221
1277
  { isActive: !!pendingPrompt }
1222
1278
  );
1279
+ const borderColor = isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green";
1280
+ const innerWidth = Math.max(1, terminalColumns - BORDER_HORIZONTAL);
1281
+ const topBorder = (() => {
1282
+ if (sessionName) {
1283
+ const label = ` "${sessionName}" `;
1284
+ const rightPad = 2;
1285
+ const leftLen = Math.max(0, innerWidth - label.length - rightPad);
1286
+ return { left: "\u250C" + "\u2500".repeat(leftLen), label, right: "\u2500".repeat(rightPad) + "\u2510" };
1287
+ }
1288
+ return { left: "\u250C" + "\u2500".repeat(innerWidth), label: "", right: "\u2510" };
1289
+ })();
1223
1290
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1224
1291
  showPopup && /* @__PURE__ */ jsx6(
1225
1292
  SlashAutocomplete,
@@ -1230,34 +1297,31 @@ function InputArea({
1230
1297
  isSubcommandMode
1231
1298
  }
1232
1299
  ),
1233
- /* @__PURE__ */ jsx6(
1234
- Box5,
1235
- {
1236
- borderStyle: "single",
1237
- borderColor: isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green",
1238
- paddingLeft: 1,
1239
- children: isAborting ? /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ jsxs5(Text7, { color: "cyan", children: [
1240
- " ",
1241
- "Queued: ",
1242
- pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
1243
- " ",
1244
- /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: "(Backspace to cancel)" })
1245
- ] }) : isDisabled ? /* @__PURE__ */ jsx6(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ jsxs5(Box5, { children: [
1246
- /* @__PURE__ */ jsx6(Text7, { color: "green", bold: true, children: "> " }),
1247
- /* @__PURE__ */ jsx6(
1248
- CjkTextInput,
1249
- {
1250
- value,
1251
- onChange: setValue,
1252
- onSubmit: handleSubmit,
1253
- onPaste: handlePaste,
1254
- placeholder: "Type a message or /help",
1255
- availableWidth
1256
- }
1257
- )
1258
- ] })
1259
- }
1260
- )
1300
+ /* @__PURE__ */ jsxs5(Text7, { color: borderColor, children: [
1301
+ topBorder.left,
1302
+ topBorder.label ? /* @__PURE__ */ jsx6(Text7, { backgroundColor: borderColor, color: "black", bold: true, children: topBorder.label }) : null,
1303
+ topBorder.right
1304
+ ] }),
1305
+ /* @__PURE__ */ jsx6(Box5, { borderStyle: "single", borderTop: false, borderColor, paddingLeft: 1, children: isAborting ? /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ jsxs5(Text7, { color: "cyan", children: [
1306
+ " ",
1307
+ "Queued: ",
1308
+ pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
1309
+ " ",
1310
+ /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: "(Backspace to cancel)" })
1311
+ ] }) : isDisabled ? /* @__PURE__ */ jsx6(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ jsxs5(Box5, { children: [
1312
+ /* @__PURE__ */ jsx6(Text7, { color: "green", bold: true, children: "> " }),
1313
+ /* @__PURE__ */ jsx6(
1314
+ CjkTextInput,
1315
+ {
1316
+ value,
1317
+ onChange: setValue,
1318
+ onSubmit: handleSubmit,
1319
+ onPaste: handlePaste,
1320
+ placeholder: "Type a message or /help",
1321
+ availableWidth
1322
+ }
1323
+ )
1324
+ ] }) })
1261
1325
  ] });
1262
1326
  }
1263
1327
 
@@ -1370,7 +1434,7 @@ function PermissionPrompt({ request }) {
1370
1434
 
1371
1435
  // src/ui/StreamingIndicator.tsx
1372
1436
  import { Box as Box8, Text as Text10 } from "ink";
1373
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1437
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1374
1438
  function getToolStyle(t) {
1375
1439
  if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
1376
1440
  if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
@@ -1381,7 +1445,7 @@ function StreamingIndicator({ text, activeTools }) {
1381
1445
  const hasTools = activeTools.length > 0;
1382
1446
  const hasText = text.length > 0;
1383
1447
  if (!hasTools && !hasText) {
1384
- return /* @__PURE__ */ jsx9(Fragment2, {});
1448
+ return /* @__PURE__ */ jsx9(Fragment3, {});
1385
1449
  }
1386
1450
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1387
1451
  hasTools && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
@@ -1862,10 +1926,90 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1862
1926
  );
1863
1927
  }
1864
1928
 
1865
- // src/ui/App.tsx
1929
+ // src/ui/ListPicker.tsx
1930
+ import { useState as useState9, useRef as useRef7 } from "react";
1931
+ import { Box as Box11, Text as Text13, useInput as useInput7 } from "ink";
1866
1932
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1933
+ var DEFAULT_MAX_VISIBLE = 3;
1934
+ function ListPicker({
1935
+ items,
1936
+ renderItem,
1937
+ onSelect,
1938
+ onCancel,
1939
+ maxVisible = DEFAULT_MAX_VISIBLE
1940
+ }) {
1941
+ const [selectedIndex, setSelectedIndex] = useState9(0);
1942
+ const [scrollOffset, setScrollOffset] = useState9(0);
1943
+ const selectedRef = useRef7(0);
1944
+ const resolvedRef = useRef7(false);
1945
+ useInput7((_input, key) => {
1946
+ if (resolvedRef.current) return;
1947
+ if (key.escape) {
1948
+ resolvedRef.current = true;
1949
+ onCancel();
1950
+ return;
1951
+ }
1952
+ if (items.length === 0) return;
1953
+ if (key.upArrow) {
1954
+ const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
1955
+ selectedRef.current = next;
1956
+ setSelectedIndex(next);
1957
+ if (next < scrollOffset) {
1958
+ setScrollOffset(next);
1959
+ }
1960
+ } else if (key.downArrow) {
1961
+ const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
1962
+ selectedRef.current = next;
1963
+ setSelectedIndex(next);
1964
+ if (next >= scrollOffset + maxVisible) {
1965
+ setScrollOffset(next - maxVisible + 1);
1966
+ }
1967
+ } else if (key.return) {
1968
+ const item = items[selectedRef.current];
1969
+ if (item !== void 0) {
1970
+ resolvedRef.current = true;
1971
+ onSelect(item);
1972
+ }
1973
+ }
1974
+ });
1975
+ if (items.length === 0) {
1976
+ return /* @__PURE__ */ jsx13(Box11, {});
1977
+ }
1978
+ const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
1979
+ const hasMore = scrollOffset + maxVisible < items.length;
1980
+ const hasLess = scrollOffset > 0;
1981
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1982
+ hasLess && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
1983
+ " \u2191 ",
1984
+ scrollOffset,
1985
+ " more above"
1986
+ ] }),
1987
+ visibleItems.map((item, index) => /* @__PURE__ */ jsx13(Box11, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
1988
+ hasMore && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
1989
+ " \u2193 ",
1990
+ items.length - scrollOffset - maxVisible,
1991
+ " more below"
1992
+ ] })
1993
+ ] });
1994
+ }
1995
+
1996
+ // src/ui/App.tsx
1997
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1867
1998
  var EXIT_DELAY_MS = 500;
1999
+ var SESSION_ID_DISPLAY_LENGTH = 8;
1868
2000
  function App(props) {
2001
+ const [activeSessionId, setActiveSessionId] = useState10(props.resumeSessionId);
2002
+ return /* @__PURE__ */ jsx14(
2003
+ AppInner,
2004
+ {
2005
+ ...props,
2006
+ resumeSessionId: activeSessionId,
2007
+ onSessionSwitch: (sessionId) => setActiveSessionId(sessionId)
2008
+ },
2009
+ activeSessionId ?? "__new__"
2010
+ );
2011
+ }
2012
+ function AppInner(props) {
1869
2013
  const { exit } = useApp();
1870
2014
  const cwd = props.cwd;
1871
2015
  const {
@@ -1887,12 +2031,28 @@ function App(props) {
1887
2031
  cwd,
1888
2032
  provider: props.provider,
1889
2033
  permissionMode: props.permissionMode,
1890
- maxTurns: props.maxTurns
2034
+ maxTurns: props.maxTurns,
2035
+ sessionStore: props.sessionStore,
2036
+ resumeSessionId: props.resumeSessionId,
2037
+ forkSession: props.forkSession,
2038
+ sessionName: props.sessionName
1891
2039
  });
1892
2040
  const pluginCallbacks = usePluginCallbacks(cwd);
1893
- const [pendingModelId, setPendingModelId] = useState9(null);
1894
- const pendingModelChangeRef = useRef7(null);
1895
- const [showPluginTUI, setShowPluginTUI] = useState9(false);
2041
+ const [pendingModelId, setPendingModelId] = useState10(null);
2042
+ const pendingModelChangeRef = useRef8(null);
2043
+ const [showPluginTUI, setShowPluginTUI] = useState10(false);
2044
+ const [showSessionPicker, setShowSessionPicker] = useState10(
2045
+ props.resumeSessionId === "__picker__"
2046
+ );
2047
+ const [sessionName, setSessionName] = useState10(props.sessionName);
2048
+ useEffect4(() => {
2049
+ const name = interactiveSession?.getName?.();
2050
+ if (name && !sessionName) setSessionName(name);
2051
+ }, [interactiveSession, sessionName]);
2052
+ useEffect4(() => {
2053
+ const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
2054
+ process.stdout.write(`\x1B]0;${title}\x07`);
2055
+ }, [sessionName]);
1896
2056
  const handleSubmit = async (input) => {
1897
2057
  await baseHandleSubmit(input);
1898
2058
  const sideEffects = interactiveSession;
@@ -1937,14 +2097,26 @@ function App(props) {
1937
2097
  setShowPluginTUI(true);
1938
2098
  return;
1939
2099
  }
2100
+ if (sideEffects._triggerResumePicker) {
2101
+ delete sideEffects._triggerResumePicker;
2102
+ setShowSessionPicker(true);
2103
+ return;
2104
+ }
2105
+ if (sideEffects._sessionName) {
2106
+ const name = sideEffects._sessionName;
2107
+ delete sideEffects._sessionName;
2108
+ interactiveSession.setName(name);
2109
+ setSessionName(name);
2110
+ return;
2111
+ }
1940
2112
  };
1941
- useInput7(
2113
+ useInput8(
1942
2114
  (_input, key) => {
1943
2115
  if (key.escape && isThinking) {
1944
2116
  handleAbort();
1945
2117
  }
1946
2118
  },
1947
- { isActive: !permissionRequest && !showPluginTUI }
2119
+ { isActive: !permissionRequest && !showPluginTUI && !showSessionPicker }
1948
2120
  );
1949
2121
  let permissionMode = props.permissionMode ?? "default";
1950
2122
  let sessionId = "";
@@ -1954,26 +2126,26 @@ function App(props) {
1954
2126
  sessionId = session.getSessionId();
1955
2127
  } catch {
1956
2128
  }
1957
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1958
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1959
- /* @__PURE__ */ jsx13(Text13, { color: "cyan", bold: true, children: `
2129
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
2130
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2131
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", bold: true, children: `
1960
2132
  ____ ___ ____ ___ _____ _
1961
2133
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1962
2134
  | |_) | | | | _ \\| | | || | / _ \\
1963
2135
  | _ <| |_| | |_) | |_| || |/ ___ \\
1964
2136
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1965
2137
  ` }),
1966
- /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
2138
+ /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
1967
2139
  " v",
1968
2140
  props.version ?? "0.0.0"
1969
2141
  ] })
1970
2142
  ] }),
1971
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1972
- /* @__PURE__ */ jsx13(MessageList, { history }),
1973
- (isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx13(StreamingIndicator, { text: streamingText, activeTools }) })
2143
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2144
+ /* @__PURE__ */ jsx14(MessageList, { history }),
2145
+ (isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx14(StreamingIndicator, { text: streamingText, activeTools }) })
1974
2146
  ] }),
1975
- permissionRequest && /* @__PURE__ */ jsx13(PermissionPrompt, { request: permissionRequest }),
1976
- pendingModelId && /* @__PURE__ */ jsx13(
2147
+ permissionRequest && /* @__PURE__ */ jsx14(PermissionPrompt, { request: permissionRequest }),
2148
+ pendingModelId && /* @__PURE__ */ jsx14(
1977
2149
  ConfirmPrompt,
1978
2150
  {
1979
2151
  message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
@@ -2007,7 +2179,7 @@ function App(props) {
2007
2179
  }
2008
2180
  }
2009
2181
  ),
2010
- showPluginTUI && /* @__PURE__ */ jsx13(
2182
+ showPluginTUI && /* @__PURE__ */ jsx14(
2011
2183
  PluginTUI,
2012
2184
  {
2013
2185
  callbacks: pluginCallbacks,
@@ -2015,7 +2187,52 @@ function App(props) {
2015
2187
  addMessage: (msg) => addEntry(messageToHistoryEntry2(createSystemMessage2(msg.content)))
2016
2188
  }
2017
2189
  ),
2018
- /* @__PURE__ */ jsx13(
2190
+ showSessionPicker && /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2191
+ /* @__PURE__ */ jsx14(Text14, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
2192
+ /* @__PURE__ */ jsx14(
2193
+ ListPicker,
2194
+ {
2195
+ items: (props.sessionStore?.list() ?? []).filter((s) => s.cwd === props.cwd),
2196
+ renderItem: (session, isSelected) => {
2197
+ const lastMsg = session.messages.slice().reverse().find((m) => {
2198
+ const msg = m;
2199
+ return msg.role === "assistant" && msg.content;
2200
+ });
2201
+ const rawPreview = lastMsg?.content?.replace(/[\n\r]+/g, " ").trim() ?? "";
2202
+ const preview = rawPreview ? rawPreview.slice(0, 60) + (rawPreview.length > 60 ? "..." : "") : "";
2203
+ return /* @__PURE__ */ jsxs12(Text14, { children: [
2204
+ isSelected ? "> " : " ",
2205
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
2206
+ " ",
2207
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
2208
+ month: "short",
2209
+ day: "numeric",
2210
+ hour: "2-digit",
2211
+ minute: "2-digit"
2212
+ }) }),
2213
+ " ",
2214
+ /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
2215
+ "msgs: ",
2216
+ session.messages.length
2217
+ ] }),
2218
+ preview ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
2219
+ "\n ",
2220
+ /* @__PURE__ */ jsx14(Text14, { color: "gray", children: preview })
2221
+ ] }) : null
2222
+ ] });
2223
+ },
2224
+ onSelect: (session) => {
2225
+ setShowSessionPicker(false);
2226
+ props.onSessionSwitch(session.id);
2227
+ },
2228
+ onCancel: () => {
2229
+ setShowSessionPicker(false);
2230
+ addEntry(messageToHistoryEntry2(createSystemMessage2("Session resume cancelled.")));
2231
+ }
2232
+ }
2233
+ )
2234
+ ] }),
2235
+ /* @__PURE__ */ jsx14(
2019
2236
  StatusBar,
2020
2237
  {
2021
2238
  permissionMode,
@@ -2025,26 +2242,28 @@ function App(props) {
2025
2242
  isThinking,
2026
2243
  contextPercentage: contextState.percentage,
2027
2244
  contextUsedTokens: contextState.usedTokens,
2028
- contextMaxTokens: contextState.maxTokens
2245
+ contextMaxTokens: contextState.maxTokens,
2246
+ sessionName
2029
2247
  }
2030
2248
  ),
2031
- /* @__PURE__ */ jsx13(
2249
+ /* @__PURE__ */ jsx14(
2032
2250
  InputArea,
2033
2251
  {
2034
2252
  onSubmit: handleSubmit,
2035
2253
  onCancelQueue: handleCancelQueue,
2036
- isDisabled: !!permissionRequest || showPluginTUI || isThinking && !!pendingPrompt,
2254
+ isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || isThinking && !!pendingPrompt,
2037
2255
  isAborting,
2038
2256
  pendingPrompt,
2039
- registry
2257
+ registry,
2258
+ sessionName
2040
2259
  }
2041
2260
  ),
2042
- /* @__PURE__ */ jsx13(Text13, { children: " " })
2261
+ /* @__PURE__ */ jsx14(Text14, { children: " " })
2043
2262
  ] });
2044
2263
  }
2045
2264
 
2046
2265
  // src/ui/render.tsx
2047
- import { jsx as jsx14 } from "react/jsx-runtime";
2266
+ import { jsx as jsx15 } from "react/jsx-runtime";
2048
2267
  function renderApp(options) {
2049
2268
  process.on("unhandledRejection", (reason) => {
2050
2269
  process.stderr.write(`
@@ -2058,7 +2277,7 @@ function renderApp(options) {
2058
2277
  if (process.stdin.isTTY && process.stdout.isTTY) {
2059
2278
  process.stdout.write("\x1B[?2004h");
2060
2279
  }
2061
- const instance = render(/* @__PURE__ */ jsx14(App, { ...options }), {
2280
+ const instance = render(/* @__PURE__ */ jsx15(App, { ...options }), {
2062
2281
  exitOnCtrlC: true
2063
2282
  });
2064
2283
  instance.waitUntilExit().then(() => {
@@ -2227,9 +2446,38 @@ async function startCli() {
2227
2446
  const providerSettings = readProviderSettings(cwd);
2228
2447
  const modelId = args.model ?? providerSettings.model;
2229
2448
  const provider = createProviderFromSettings(cwd, args.model);
2449
+ const sessionStore = new SessionStore();
2450
+ let resumeSessionId;
2451
+ if (args.continueMode) {
2452
+ const sessions = sessionStore.list().filter((s) => s.cwd === cwd);
2453
+ if (sessions.length > 0) {
2454
+ resumeSessionId = sessions[0].id;
2455
+ }
2456
+ } else if (args.resumeId !== void 0) {
2457
+ if (args.resumeId === "") {
2458
+ resumeSessionId = "__picker__";
2459
+ } else {
2460
+ const sessions = sessionStore.list();
2461
+ const match = sessions.find((s) => s.id === args.resumeId || s.name === args.resumeId);
2462
+ if (match) {
2463
+ resumeSessionId = match.id;
2464
+ } else {
2465
+ process.stderr.write(`Session not found: ${args.resumeId}
2466
+ `);
2467
+ process.exit(1);
2468
+ }
2469
+ }
2470
+ }
2230
2471
  if (args.printMode) {
2231
- const prompt = args.positional.join(" ").trim();
2232
- if (prompt.length === 0) {
2472
+ let prompt = args.positional.join(" ").trim();
2473
+ if (!prompt && !process.stdin.isTTY) {
2474
+ const chunks = [];
2475
+ for await (const chunk of process.stdin) {
2476
+ chunks.push(chunk);
2477
+ }
2478
+ prompt = Buffer.concat(chunks).toString("utf-8").trim();
2479
+ }
2480
+ if (!prompt) {
2233
2481
  process.stderr.write("Print mode (-p) requires a prompt argument.\n");
2234
2482
  process.exit(1);
2235
2483
  }
@@ -2237,21 +2485,17 @@ async function startCli() {
2237
2485
  cwd,
2238
2486
  provider,
2239
2487
  permissionMode: args.permissionMode ?? "bypassPermissions",
2240
- maxTurns: args.maxTurns
2488
+ maxTurns: args.maxTurns,
2489
+ sessionStore,
2490
+ sessionName: args.sessionName
2241
2491
  });
2242
- await new Promise((resolve, reject) => {
2243
- session.on("complete", (result) => {
2244
- process.stdout.write(result.response + "\n");
2245
- resolve();
2246
- });
2247
- session.on("interrupted", (result) => {
2248
- if (result.response) process.stdout.write(result.response + "\n");
2249
- resolve();
2250
- });
2251
- session.on("error", (err) => reject(err));
2252
- session.submit(prompt).catch(reject);
2492
+ const transport = createHeadlessTransport({
2493
+ outputFormat: args.outputFormat ?? "text",
2494
+ prompt
2253
2495
  });
2254
- return;
2496
+ session.attachTransport(transport);
2497
+ await transport.start();
2498
+ process.exit(transport.getExitCode());
2255
2499
  }
2256
2500
  renderApp({
2257
2501
  cwd,
@@ -2260,7 +2504,11 @@ async function startCli() {
2260
2504
  language: args.language,
2261
2505
  permissionMode: args.permissionMode,
2262
2506
  maxTurns: args.maxTurns,
2263
- version: readVersion()
2507
+ version: readVersion(),
2508
+ sessionStore,
2509
+ resumeSessionId,
2510
+ forkSession: args.forkSession,
2511
+ sessionName: args.sessionName
2264
2512
  });
2265
2513
  }
2266
2514