parallel-codex-tui 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui/App.js CHANGED
@@ -29,6 +29,7 @@ import { moveWorkerSelection, WorkerOverviewView } from "./WorkerOverviewView.js
29
29
  import { FeatureBoardView, moveFeatureBoardSelection } from "./FeatureBoardView.js";
30
30
  import { CollaborationTimelineView, collaborationSelectionScrollOffset, collaborationTimelineEvents, moveCollaborationEventSelection, nextCollaborationFeatureIndex } from "./CollaborationTimelineView.js";
31
31
  import { moveTaskSessionSelection, TaskSessionsView } from "./TaskSessionsView.js";
32
+ import { MainConversationsView, moveMainConversationSelection } from "./MainConversationsView.js";
32
33
  import { moveTaskSessionDetailSelection, TaskSessionDetailView } from "./TaskSessionDetailView.js";
33
34
  import { latestTaskResultMessageIndex, parseTaskResultSummary } from "./task-result.js";
34
35
  import { buildNativeAttachLaunch, buildNativeForkLaunch, startNativeAttachProcess } from "../workers/native-attach.js";
@@ -49,7 +50,7 @@ const EMPTY_WORKER_NAVIGATION_TARGETS = {
49
50
  errorOffsets: [],
50
51
  diffOffsets: []
51
52
  };
52
- export function App({ config, orchestrator, cwd, initialTaskId = null, initialRoute = null, initialWorkers, initialCanRetryTask = false, initialMessages = [], persistChatMessage, reloadConfig, workspaceChoices = [], switchWorkspace, loadRouterDiagnostics, loadTaskSessions, loadTaskSessionDetails, renameTaskSession, setTaskSessionArchived, deleteTaskSession, exportTaskSession, exportDiagnostics, loadCollaborationTimeline, activateTaskSession, startMainConversation, prepareNativeAttach, prepareNativeFork, startNativeAttach, copyToClipboard = copyTextToClipboard, shutdownSignal }) {
53
+ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRoute = null, initialWorkers, initialCanRetryTask = false, initialMessages = [], persistChatMessage, reloadConfig, workspaceChoices = [], switchWorkspace, loadRouterDiagnostics, loadTaskSessions, loadMainConversations, activateMainConversation, renameMainConversation, setMainConversationArchived, deleteMainConversation, exportMainConversation, loadTaskSessionDetails, renameTaskSession, setTaskSessionArchived, deleteTaskSession, exportTaskSession, exportDiagnostics, loadCollaborationTimeline, activateTaskSession, startMainConversation, prepareNativeAttach, prepareNativeFork, startNativeAttach, copyToClipboard = copyTextToClipboard, shutdownSignal }) {
53
54
  configureTuiTheme({
54
55
  theme: config.ui.theme,
55
56
  colors: config.ui.colors
@@ -97,6 +98,11 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
97
98
  const [routerMaxScrollOffset, setRouterMaxScrollOffset] = useState(0);
98
99
  const [taskSessions, setTaskSessions] = useState([]);
99
100
  const [selectedTaskSessionIndex, setSelectedTaskSessionIndex] = useState(0);
101
+ const [sessionCenterMode, setSessionCenterMode] = useState("tasks");
102
+ const [mainConversations, setMainConversations] = useState([]);
103
+ const [selectedMainConversationIndex, setSelectedMainConversationIndex] = useState(0);
104
+ const [mainConversationsIncludeArchived, setMainConversationsIncludeArchived] = useState(false);
105
+ const [mainConversationAction, setMainConversationAction] = useState(null);
100
106
  const [taskSessionsLoading, setTaskSessionsLoading] = useState(false);
101
107
  const [taskSessionsError, setTaskSessionsError] = useState(null);
102
108
  const [taskSessionsNotice, setTaskSessionsNotice] = useState(null);
@@ -149,6 +155,11 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
149
155
  const routerMaxScrollOffsetRef = useRef(routerMaxScrollOffset);
150
156
  const taskSessionsRef = useRef(taskSessions);
151
157
  const selectedTaskSessionIndexRef = useRef(selectedTaskSessionIndex);
158
+ const sessionCenterModeRef = useRef("tasks");
159
+ const mainConversationsRef = useRef(mainConversations);
160
+ const selectedMainConversationIndexRef = useRef(selectedMainConversationIndex);
161
+ const mainConversationsIncludeArchivedRef = useRef(mainConversationsIncludeArchived);
162
+ const mainConversationActionRef = useRef(mainConversationAction);
152
163
  const taskSessionsLoadingRef = useRef(taskSessionsLoading);
153
164
  const taskSessionsIncludeArchivedRef = useRef(taskSessionsIncludeArchived);
154
165
  const taskSessionActionRef = useRef(taskSessionAction);
@@ -180,6 +191,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
180
191
  const refreshCollaborationTimelineRef = useRef(refreshCollaborationTimeline);
181
192
  const activateSelectedTaskSessionRef = useRef(activateSelectedTaskSession);
182
193
  const refreshTaskSessionsRef = useRef(refreshTaskSessions);
194
+ const openMainConversationsRef = useRef(openMainConversations);
195
+ const refreshMainConversationsRef = useRef(refreshMainConversations);
196
+ const restoreSelectedMainConversationRef = useRef(restoreSelectedMainConversation);
197
+ const renameSelectedMainConversationRef = useRef(renameSelectedMainConversation);
198
+ const archiveSelectedMainConversationRef = useRef(archiveSelectedMainConversation);
199
+ const deleteSelectedMainConversationRef = useRef(deleteSelectedMainConversation);
200
+ const exportSelectedMainConversationRef = useRef(exportSelectedMainConversation);
183
201
  const renameSelectedTaskSessionRef = useRef(renameSelectedTaskSession);
184
202
  const archiveSelectedTaskSessionRef = useRef(archiveSelectedTaskSession);
185
203
  const deleteSelectedTaskSessionRef = useRef(deleteSelectedTaskSession);
@@ -334,6 +352,21 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
334
352
  useEffect(() => {
335
353
  selectedTaskSessionIndexRef.current = selectedTaskSessionIndex;
336
354
  }, [selectedTaskSessionIndex]);
355
+ useEffect(() => {
356
+ sessionCenterModeRef.current = sessionCenterMode;
357
+ }, [sessionCenterMode]);
358
+ useEffect(() => {
359
+ mainConversationsRef.current = mainConversations;
360
+ }, [mainConversations]);
361
+ useEffect(() => {
362
+ selectedMainConversationIndexRef.current = selectedMainConversationIndex;
363
+ }, [selectedMainConversationIndex]);
364
+ useEffect(() => {
365
+ mainConversationsIncludeArchivedRef.current = mainConversationsIncludeArchived;
366
+ }, [mainConversationsIncludeArchived]);
367
+ useEffect(() => {
368
+ mainConversationActionRef.current = mainConversationAction;
369
+ }, [mainConversationAction]);
337
370
  useEffect(() => {
338
371
  taskSessionsLoadingRef.current = taskSessionsLoading;
339
372
  }, [taskSessionsLoading]);
@@ -405,6 +438,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
405
438
  refreshCollaborationTimelineRef.current = refreshCollaborationTimeline;
406
439
  activateSelectedTaskSessionRef.current = activateSelectedTaskSession;
407
440
  refreshTaskSessionsRef.current = refreshTaskSessions;
441
+ openMainConversationsRef.current = openMainConversations;
442
+ refreshMainConversationsRef.current = refreshMainConversations;
443
+ restoreSelectedMainConversationRef.current = restoreSelectedMainConversation;
444
+ renameSelectedMainConversationRef.current = renameSelectedMainConversation;
445
+ archiveSelectedMainConversationRef.current = archiveSelectedMainConversation;
446
+ deleteSelectedMainConversationRef.current = deleteSelectedMainConversation;
447
+ exportSelectedMainConversationRef.current = exportSelectedMainConversation;
408
448
  renameSelectedTaskSessionRef.current = renameSelectedTaskSession;
409
449
  archiveSelectedTaskSessionRef.current = archiveSelectedTaskSession;
410
450
  deleteSelectedTaskSessionRef.current = deleteSelectedTaskSession;
@@ -685,6 +725,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
685
725
  setTaskSessionsError(null);
686
726
  setSelectedTaskSessionIndex(nextIndex);
687
727
  };
728
+ const moveSelectedMainConversation = (delta, wrap = false) => {
729
+ const nextIndex = moveMainConversationSelection(selectedMainConversationIndexRef.current, delta, mainConversationsRef.current.length, wrap);
730
+ selectedMainConversationIndexRef.current = nextIndex;
731
+ setTaskSessionsError(null);
732
+ setTaskSessionsNotice(null);
733
+ setSelectedMainConversationIndex(nextIndex);
734
+ };
688
735
  const moveSelectedTaskSessionDetailWorker = (delta, wrap = false) => {
689
736
  const nextIndex = moveTaskSessionDetailSelection(taskSessionDetailSelectedWorkerIndexRef.current, delta, taskSessionDetailsRef.current?.workers.length ?? 0, wrap);
690
737
  taskSessionDetailSelectedWorkerIndexRef.current = nextIndex;
@@ -847,6 +894,141 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
847
894
  }
848
895
  return;
849
896
  }
897
+ if (sessionCenterModeRef.current === "conversations") {
898
+ const conversationAction = mainConversationActionRef.current;
899
+ if (conversationAction) {
900
+ if (chunk === "\x1b") {
901
+ updateMainConversationAction(null);
902
+ setTaskSessionsError(null);
903
+ return;
904
+ }
905
+ if (taskSessionsLoadingRef.current) {
906
+ return;
907
+ }
908
+ if (conversationAction.type === "rename") {
909
+ const update = applyChatInputChunk(conversationAction.value, chunk, conversationAction.cursor);
910
+ if (update.submit !== null) {
911
+ void renameSelectedMainConversationRef.current({
912
+ ...conversationAction,
913
+ value: update.submit,
914
+ cursor: Array.from(update.submit).length
915
+ });
916
+ }
917
+ else {
918
+ updateMainConversationAction({
919
+ ...conversationAction,
920
+ value: update.value,
921
+ cursor: update.cursor
922
+ });
923
+ }
924
+ return;
925
+ }
926
+ if (chunk === "d" || chunk === "D") {
927
+ void deleteSelectedMainConversationRef.current(conversationAction);
928
+ }
929
+ return;
930
+ }
931
+ if (isTaskSessionsShortcut(chunk, {}) || chunk === "\x1b") {
932
+ updateMainConversationAction(null);
933
+ setTaskSessionsError(null);
934
+ setTaskSessionsNotice(null);
935
+ viewRef.current = taskSessionsReturnViewRef.current;
936
+ setView(taskSessionsReturnViewRef.current);
937
+ return;
938
+ }
939
+ if (taskSessionsLoadingRef.current) {
940
+ return;
941
+ }
942
+ if (isNewConversationShortcut(chunk, {}) || chunk === "n" || chunk === "N") {
943
+ void newConversationRef.current();
944
+ return;
945
+ }
946
+ if (chunk === "t" || chunk === "T") {
947
+ updateMainConversationAction(null);
948
+ sessionCenterModeRef.current = "tasks";
949
+ setSessionCenterMode("tasks");
950
+ setTaskSessionsError(null);
951
+ setTaskSessionsNotice(null);
952
+ void refreshTaskSessionsRef.current(activeTaskIdRef.current);
953
+ return;
954
+ }
955
+ if (chunk === "c" || chunk === "C") {
956
+ void openMainConversationsRef.current();
957
+ return;
958
+ }
959
+ const selectedConversation = mainConversationsRef.current[selectedMainConversationIndexRef.current];
960
+ if (chunk === "r" || chunk === "R") {
961
+ if (selectedConversation) {
962
+ updateMainConversationAction({
963
+ type: "rename",
964
+ conversationId: selectedConversation.id,
965
+ title: selectedConversation.title,
966
+ value: selectedConversation.title,
967
+ cursor: Array.from(selectedConversation.title).length
968
+ });
969
+ setTaskSessionsError(null);
970
+ setTaskSessionsNotice(null);
971
+ }
972
+ return;
973
+ }
974
+ if (chunk === "a" || chunk === "A") {
975
+ void archiveSelectedMainConversationRef.current();
976
+ return;
977
+ }
978
+ if (chunk === "d" || chunk === "D") {
979
+ if (selectedConversation) {
980
+ if (selectedConversation.current) {
981
+ setTaskSessionsError("Restore another Main conversation before deleting the current one");
982
+ }
983
+ else {
984
+ updateMainConversationAction({
985
+ type: "delete",
986
+ conversationId: selectedConversation.id,
987
+ title: selectedConversation.title
988
+ });
989
+ setTaskSessionsError(null);
990
+ setTaskSessionsNotice(null);
991
+ }
992
+ }
993
+ return;
994
+ }
995
+ if (chunk === "e" || chunk === "E") {
996
+ void exportSelectedMainConversationRef.current();
997
+ return;
998
+ }
999
+ if (chunk === "h" || chunk === "H") {
1000
+ const includeArchived = !mainConversationsIncludeArchivedRef.current;
1001
+ mainConversationsIncludeArchivedRef.current = includeArchived;
1002
+ setMainConversationsIncludeArchived(includeArchived);
1003
+ setTaskSessionsError(null);
1004
+ setTaskSessionsNotice(null);
1005
+ void (async () => {
1006
+ await refreshMainConversationsRef.current(selectedConversation?.id);
1007
+ if (sessionCenterModeRef.current === "conversations"
1008
+ && mainConversationsIncludeArchivedRef.current === includeArchived) {
1009
+ setTaskSessionsNotice(includeArchived
1010
+ ? "Archived conversations shown"
1011
+ : "Archived conversations hidden");
1012
+ }
1013
+ })();
1014
+ return;
1015
+ }
1016
+ if (chunk === "\r" || chunk === "\n") {
1017
+ void restoreSelectedMainConversationRef.current();
1018
+ return;
1019
+ }
1020
+ if (chunk === "\t") {
1021
+ moveSelectedMainConversation(1, true);
1022
+ return;
1023
+ }
1024
+ const conversationSelectionDelta = -(rawHistoryDelta(chunk)
1025
+ + rawPageScrollDelta(chunk, Math.max(1, outputHeight - 2))
1026
+ + mouseScrollDelta(chunk, 1));
1027
+ if (conversationSelectionDelta !== 0) {
1028
+ moveSelectedMainConversation(conversationSelectionDelta);
1029
+ }
1030
+ return;
1031
+ }
850
1032
  const sessionAction = taskSessionActionRef.current;
851
1033
  if (sessionAction) {
852
1034
  if (chunk === "\x1b") {
@@ -902,6 +1084,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
902
1084
  void newConversationRef.current();
903
1085
  return;
904
1086
  }
1087
+ if (chunk === "c" || chunk === "C") {
1088
+ void openMainConversationsRef.current();
1089
+ return;
1090
+ }
905
1091
  const selectedSession = taskSessionsRef.current[selectedTaskSessionIndexRef.current];
906
1092
  if (chunk === "r" || chunk === "R") {
907
1093
  if (selectedSession) {
@@ -1682,7 +1868,15 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1682
1868
  label: "sessions",
1683
1869
  text: details
1684
1870
  ? details.workers.map((worker) => [worker.id, worker.role, worker.engine, worker.state].join(" · ")).join("\n")
1685
- : taskSessionsRef.current.map((task) => [task.id, task.title, task.status].join(" · ")).join("\n")
1871
+ : sessionCenterModeRef.current === "conversations"
1872
+ ? mainConversationsRef.current.map((conversation) => [
1873
+ conversation.current ? "current" : "saved",
1874
+ conversation.id ?? "legacy",
1875
+ conversation.title,
1876
+ `${conversation.messageCount} messages`,
1877
+ `${conversation.nativeSessionCount} native`
1878
+ ].join(" · ")).join("\n")
1879
+ : taskSessionsRef.current.map((task) => [task.id, task.title, task.status].join(" · ")).join("\n")
1686
1880
  };
1687
1881
  }
1688
1882
  if (currentView === "router") {
@@ -2110,6 +2304,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2110
2304
  setAttachError(error instanceof Error ? error.message : String(error));
2111
2305
  return;
2112
2306
  }
2307
+ resetActiveTaskUiForMainConversation();
2308
+ await appendVisibleMessage({ from: "system", text: "new conversation · ready" });
2309
+ }
2310
+ function resetActiveTaskUiForMainConversation() {
2113
2311
  const nextMemory = newTaskMemoryState();
2114
2312
  activeTaskIdRef.current = nextMemory.activeTaskId;
2115
2313
  setActiveTaskId(nextMemory.activeTaskId);
@@ -2134,7 +2332,6 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2134
2332
  offset: 0,
2135
2333
  draft: { value: inputRef.current, cursor: inputCursorRef.current }
2136
2334
  };
2137
- await appendVisibleMessage({ from: "system", text: "new conversation · ready" });
2138
2335
  }
2139
2336
  async function openRouterDiagnostics() {
2140
2337
  const currentView = viewRef.current;
@@ -2200,6 +2397,9 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2200
2397
  setTaskSessionDetailSelectedWorkerIndex(0);
2201
2398
  setTaskSessionDetailNotice(null);
2202
2399
  updateTaskSessionAction(null);
2400
+ updateMainConversationAction(null);
2401
+ sessionCenterModeRef.current = "tasks";
2402
+ setSessionCenterMode("tasks");
2203
2403
  await refreshTaskSessions(activeTaskIdRef.current);
2204
2404
  }
2205
2405
  async function refreshTaskSessions(preferredTaskId = null) {
@@ -2240,6 +2440,184 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2240
2440
  }
2241
2441
  }
2242
2442
  }
2443
+ async function openMainConversations() {
2444
+ const currentView = viewRef.current;
2445
+ if (busyRef.current || currentView === "native" || currentView === "workspace") {
2446
+ return;
2447
+ }
2448
+ if (currentView !== "sessions") {
2449
+ taskSessionsReturnViewRef.current = currentView === "worker" || currentView === "workers" || currentView === "router"
2450
+ ? currentView
2451
+ : "chat";
2452
+ viewRef.current = "sessions";
2453
+ setView("sessions");
2454
+ }
2455
+ const previousId = sessionCenterModeRef.current === "conversations"
2456
+ ? mainConversationsRef.current[selectedMainConversationIndexRef.current]?.id
2457
+ : undefined;
2458
+ sessionCenterModeRef.current = "conversations";
2459
+ setSessionCenterMode("conversations");
2460
+ taskSessionDetailsRef.current = null;
2461
+ setTaskSessionDetails(null);
2462
+ updateTaskSessionAction(null);
2463
+ updateMainConversationAction(null);
2464
+ setTaskSessionsError(null);
2465
+ setTaskSessionsNotice(null);
2466
+ await refreshMainConversations(previousId);
2467
+ }
2468
+ async function refreshMainConversations(preferredConversationId, manageLoading = true) {
2469
+ if (manageLoading) {
2470
+ taskSessionsLoadingRef.current = true;
2471
+ setTaskSessionsLoading(true);
2472
+ }
2473
+ const sequence = taskSessionsLoadSequenceRef.current + 1;
2474
+ taskSessionsLoadSequenceRef.current = sequence;
2475
+ try {
2476
+ if (!loadMainConversations) {
2477
+ throw new Error("Main conversations are unavailable");
2478
+ }
2479
+ const conversations = await loadMainConversations({
2480
+ includeArchived: mainConversationsIncludeArchivedRef.current
2481
+ });
2482
+ if (taskSessionsLoadSequenceRef.current !== sequence) {
2483
+ return;
2484
+ }
2485
+ mainConversationsRef.current = conversations;
2486
+ setMainConversations(conversations);
2487
+ const preservedIndex = preferredConversationId !== undefined
2488
+ ? conversations.findIndex((conversation) => conversation.id === preferredConversationId)
2489
+ : -1;
2490
+ const currentIndex = conversations.findIndex((conversation) => conversation.current);
2491
+ const selectedIndex = preservedIndex >= 0
2492
+ ? preservedIndex
2493
+ : currentIndex >= 0
2494
+ ? currentIndex
2495
+ : 0;
2496
+ selectedMainConversationIndexRef.current = selectedIndex;
2497
+ setSelectedMainConversationIndex(selectedIndex);
2498
+ }
2499
+ catch (error) {
2500
+ if (taskSessionsLoadSequenceRef.current === sequence) {
2501
+ setTaskSessionsError(error instanceof Error ? error.message : String(error));
2502
+ }
2503
+ }
2504
+ finally {
2505
+ if (manageLoading && taskSessionsLoadSequenceRef.current === sequence) {
2506
+ taskSessionsLoadingRef.current = false;
2507
+ setTaskSessionsLoading(false);
2508
+ }
2509
+ }
2510
+ }
2511
+ async function restoreSelectedMainConversation() {
2512
+ if (busyRef.current || taskSessionsLoadingRef.current) {
2513
+ return;
2514
+ }
2515
+ const selected = mainConversationsRef.current[selectedMainConversationIndexRef.current];
2516
+ if (!selected) {
2517
+ return;
2518
+ }
2519
+ taskSessionsLoadingRef.current = true;
2520
+ setTaskSessionsLoading(true);
2521
+ setTaskSessionsError(null);
2522
+ setTaskSessionsNotice(null);
2523
+ try {
2524
+ if (!activateMainConversation) {
2525
+ throw new Error("Main conversation restore is unavailable");
2526
+ }
2527
+ const result = await activateMainConversation(selected.id);
2528
+ if (!result.changed) {
2529
+ setTaskSessionsNotice(`Already current · ${result.conversation.title}`);
2530
+ await refreshMainConversations(result.conversation.id, false);
2531
+ return;
2532
+ }
2533
+ await activateTaskSession?.(null);
2534
+ resetActiveTaskUiForMainConversation();
2535
+ await appendVisibleMessage({
2536
+ from: "system",
2537
+ text: `conversation restored · ${result.conversation.title} · ${result.restoredNativeSessions} native`
2538
+ });
2539
+ }
2540
+ catch (error) {
2541
+ setTaskSessionsError(error instanceof Error ? error.message : String(error));
2542
+ }
2543
+ finally {
2544
+ taskSessionsLoadingRef.current = false;
2545
+ setTaskSessionsLoading(false);
2546
+ }
2547
+ }
2548
+ function updateMainConversationAction(next) {
2549
+ mainConversationActionRef.current = next;
2550
+ setMainConversationAction(next);
2551
+ }
2552
+ async function runMainConversationOperation(run, notice, preferredConversationId) {
2553
+ if (taskSessionsLoadingRef.current) {
2554
+ return;
2555
+ }
2556
+ taskSessionsLoadingRef.current = true;
2557
+ setTaskSessionsLoading(true);
2558
+ setTaskSessionsError(null);
2559
+ setTaskSessionsNotice(null);
2560
+ try {
2561
+ const result = await run();
2562
+ updateMainConversationAction(null);
2563
+ await refreshMainConversations(preferredConversationId, false);
2564
+ taskSessionsLoadingRef.current = false;
2565
+ setTaskSessionsLoading(false);
2566
+ setTaskSessionsNotice(typeof notice === "function" ? notice(result) : notice);
2567
+ }
2568
+ catch (error) {
2569
+ taskSessionsLoadingRef.current = false;
2570
+ setTaskSessionsLoading(false);
2571
+ setTaskSessionsError(error instanceof Error ? error.message : String(error));
2572
+ }
2573
+ finally {
2574
+ taskSessionsLoadingRef.current = false;
2575
+ setTaskSessionsLoading(false);
2576
+ }
2577
+ }
2578
+ async function renameSelectedMainConversation(action) {
2579
+ await runMainConversationOperation(async () => {
2580
+ if (!renameMainConversation) {
2581
+ throw new Error("Main conversation rename is unavailable");
2582
+ }
2583
+ const title = action.value.trim();
2584
+ await renameMainConversation(action.conversationId, title);
2585
+ return title;
2586
+ }, (title) => `Renamed · ${title}`, action.conversationId);
2587
+ }
2588
+ async function archiveSelectedMainConversation() {
2589
+ const selected = mainConversationsRef.current[selectedMainConversationIndexRef.current];
2590
+ if (!selected) {
2591
+ return;
2592
+ }
2593
+ const archived = !selected.archivedAt;
2594
+ await runMainConversationOperation(async () => {
2595
+ if (!setMainConversationArchived) {
2596
+ throw new Error("Main conversation archive is unavailable");
2597
+ }
2598
+ await setMainConversationArchived(selected.id, archived);
2599
+ }, archived ? `Archived · ${selected.title}` : `Unarchived · ${selected.title}`, selected.id);
2600
+ }
2601
+ async function deleteSelectedMainConversation(action) {
2602
+ await runMainConversationOperation(async () => {
2603
+ if (!deleteMainConversation) {
2604
+ throw new Error("Main conversation deletion is unavailable");
2605
+ }
2606
+ await deleteMainConversation(action.conversationId);
2607
+ }, `Deleted · ${action.title}`);
2608
+ }
2609
+ async function exportSelectedMainConversation() {
2610
+ const selected = mainConversationsRef.current[selectedMainConversationIndexRef.current];
2611
+ if (!selected) {
2612
+ return;
2613
+ }
2614
+ await runMainConversationOperation(async () => {
2615
+ if (!exportMainConversation) {
2616
+ throw new Error("Main conversation export is unavailable");
2617
+ }
2618
+ return exportMainConversation(selected.id);
2619
+ }, (path) => `Exported · ${path}`, selected.id);
2620
+ }
2243
2621
  function updateTaskSessionAction(next) {
2244
2622
  taskSessionActionRef.current = next;
2245
2623
  setTaskSessionAction(next);
@@ -2723,15 +3101,27 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2723
3101
  if (view === "workspace") {
2724
3102
  return (_jsx(Box, { flexDirection: "column", height: terminalSize.rows, children: _jsx(WorkspacePicker, { cwd: cwd, choices: workspaceChoices, terminalHeight: terminalSize.rows, terminalWidth: terminalWidth, onCancel: closeWorkspacePicker, onSelect: (workspace) => void selectWorkspace(workspace) }) }));
2725
3103
  }
2726
- return (_jsx(AppShell, { view: view, cwd: cwd, taskId: activeTaskId, statusText: [visibleTaskStatus, visibleRouteSummary, configChange?.compact].filter(Boolean).join(" | "), contentHeight: contentHeight, showStatusBar: config.ui.showStatusBar, input: _jsx(InputBar, { mode: view === "worker" && workerSearch.open ? "worker-search" : view, ready: inputReady, busy: busy, routeFallback: Boolean(routeFallbackPrompt), collaborationDetail: collaborationDetailOpen, collaborationUnresolved: collaborationUnresolvedOnly, collaborationBack: collaborationReturnViewRef.current, featureCanCancel: featureCanCancel, featureCanPause: featureCanCancel, featureCanReassign: featureCanReassign, featureCancelConfirm: featureCancelPrompt?.action === "cancel", featurePauseConfirm: featureCancelPrompt?.action === "pause", featureAssignment: Boolean(featureAssignmentPrompt), taskSessionAction: taskSessionAction?.type === "rename"
2727
- ? { type: "rename", value: taskSessionAction.value, cursor: taskSessionAction.cursor }
2728
- : taskSessionAction?.type === "delete"
2729
- ? { type: "delete", title: taskSessionAction.title }
2730
- : null, taskSessionsIncludeArchived: taskSessionsIncludeArchived, taskSessionDetail: Boolean(taskSessionDetails), taskSessionDetailHasNative: taskSessionDetailHasNative, taskSessionDetailCanFork: taskSessionDetailCanFork, canRetry: canRetryTask, hasWorkers: workers.length > 0, hasActiveTask: Boolean(activeTaskId), hasTaskResult: hasTaskResult, taskResultExpanded: taskResultExpanded, chatScrollOffset: chatScrollOffset, chatMaxScrollOffset: chatMaxScrollOffset, nativeClosed: view === "native" && nativeAttach?.closedCode !== null, searchMatchIndex: workerSearch.matchIndex, searchMatchCount: workerNavigationTargets.searchOffsets.length, clipboardNotice: clipboardNotice, value: workerSearch.open && view === "worker"
3104
+ return (_jsx(AppShell, { view: view, cwd: cwd, taskId: activeTaskId, statusText: [visibleTaskStatus, visibleRouteSummary, configChange?.compact].filter(Boolean).join(" | "), contentHeight: contentHeight, showStatusBar: config.ui.showStatusBar, input: _jsx(InputBar, { mode: view === "worker" && workerSearch.open ? "worker-search" : view, ready: inputReady, busy: busy, routeFallback: Boolean(routeFallbackPrompt), collaborationDetail: collaborationDetailOpen, collaborationUnresolved: collaborationUnresolvedOnly, collaborationBack: collaborationReturnViewRef.current, featureCanCancel: featureCanCancel, featureCanPause: featureCanCancel, featureCanReassign: featureCanReassign, featureCancelConfirm: featureCancelPrompt?.action === "cancel", featurePauseConfirm: featureCancelPrompt?.action === "pause", featureAssignment: Boolean(featureAssignmentPrompt), taskSessionAction: sessionCenterMode === "conversations"
3105
+ ? mainConversationAction?.type === "rename"
3106
+ ? { type: "rename", value: mainConversationAction.value, cursor: mainConversationAction.cursor }
3107
+ : mainConversationAction?.type === "delete"
3108
+ ? { type: "delete", title: mainConversationAction.title }
3109
+ : null
3110
+ : taskSessionAction?.type === "rename"
3111
+ ? { type: "rename", value: taskSessionAction.value, cursor: taskSessionAction.cursor }
3112
+ : taskSessionAction?.type === "delete"
3113
+ ? { type: "delete", title: taskSessionAction.title }
3114
+ : null, taskSessionsIncludeArchived: sessionCenterMode === "conversations"
3115
+ ? mainConversationsIncludeArchived
3116
+ : taskSessionsIncludeArchived, mainConversationSessions: sessionCenterMode === "conversations" && !taskSessionDetails, taskSessionDetail: Boolean(taskSessionDetails), taskSessionDetailHasNative: taskSessionDetailHasNative, taskSessionDetailCanFork: taskSessionDetailCanFork, canRetry: canRetryTask, hasWorkers: workers.length > 0, hasActiveTask: Boolean(activeTaskId), hasTaskResult: hasTaskResult, taskResultExpanded: taskResultExpanded, chatScrollOffset: chatScrollOffset, chatMaxScrollOffset: chatMaxScrollOffset, nativeClosed: view === "native" && nativeAttach?.closedCode !== null, searchMatchIndex: workerSearch.matchIndex, searchMatchCount: workerNavigationTargets.searchOffsets.length, clipboardNotice: clipboardNotice, value: workerSearch.open && view === "worker"
2731
3117
  ? workerSearch.query
2732
3118
  : view === "native" || view === "router" || view === "workers" || view === "features" || view === "sessions" || view === "collaboration" || view === "status" ? "" : input, cursor: workerSearch.open && view === "worker"
2733
3119
  ? workerSearch.cursor
2734
- : view === "chat" ? inputCursor : undefined, terminalWidth: terminalWidth, onChange: view === "native" ? setNativeInput : setInput, onSubmit: view === "native" ? undefined : submit }), error: attachError, children: view === "status" ? (_jsx(StatusDetailView, { cwd: cwd, taskId: activeTaskId, mode: activeMode, busy: busy, canRetry: canRetryTask, taskStatus: visibleTaskStatus, routeStatus: visibleRouteStatus, routeReason: routePending ? undefined : lastRoute?.reason, pairing: config.pairing, configStatus: configChange?.detail, configRestartRequired: configChange?.kind === "restart", workers: workers, selectedWorkerIndex: selectedWorkerIndex, height: contentHeight, terminalWidth: terminalWidth })) : view === "native" ? (_jsx(NativeAttachView, { attach: nativeAttach, viewportHeight: contentHeight })) : view === "sessions" ? (taskSessionDetails ? (_jsx(TaskSessionDetailView, { details: taskSessionDetails, selectedWorkerIndex: taskSessionDetailSelectedWorkerIndex, loading: taskSessionsLoading, error: taskSessionsError, notice: taskSessionDetailNotice, height: contentHeight, terminalWidth: terminalWidth })) : (_jsx(TaskSessionsView, { tasks: taskSessions, activeTaskId: activeTaskId, selectedIndex: selectedTaskSessionIndex, includeArchived: taskSessionsIncludeArchived, notice: taskSessionsNotice, action: taskSessionAction?.type === "rename"
3120
+ : view === "chat" ? inputCursor : undefined, terminalWidth: terminalWidth, onChange: view === "native" ? setNativeInput : setInput, onSubmit: view === "native" ? undefined : submit }), error: attachError, children: view === "status" ? (_jsx(StatusDetailView, { cwd: cwd, taskId: activeTaskId, mode: activeMode, busy: busy, canRetry: canRetryTask, taskStatus: visibleTaskStatus, routeStatus: visibleRouteStatus, routeReason: routePending ? undefined : lastRoute?.reason, pairing: config.pairing, configStatus: configChange?.detail, configRestartRequired: configChange?.kind === "restart", workers: workers, selectedWorkerIndex: selectedWorkerIndex, height: contentHeight, terminalWidth: terminalWidth })) : view === "native" ? (_jsx(NativeAttachView, { attach: nativeAttach, viewportHeight: contentHeight })) : view === "sessions" ? (taskSessionDetails ? (_jsx(TaskSessionDetailView, { details: taskSessionDetails, selectedWorkerIndex: taskSessionDetailSelectedWorkerIndex, loading: taskSessionsLoading, error: taskSessionsError, notice: taskSessionDetailNotice, height: contentHeight, terminalWidth: terminalWidth })) : sessionCenterMode === "conversations" ? (_jsx(MainConversationsView, { conversations: mainConversations, selectedIndex: selectedMainConversationIndex, includeArchived: mainConversationsIncludeArchived, notice: taskSessionsNotice, action: mainConversationAction?.type === "rename"
3121
+ ? { type: "rename", title: mainConversationAction.title }
3122
+ : mainConversationAction?.type === "delete"
3123
+ ? { type: "delete", title: mainConversationAction.title }
3124
+ : null, loading: taskSessionsLoading, error: taskSessionsError, height: contentHeight, terminalWidth: terminalWidth })) : (_jsx(TaskSessionsView, { tasks: taskSessions, activeTaskId: activeTaskId, selectedIndex: selectedTaskSessionIndex, includeArchived: taskSessionsIncludeArchived, notice: taskSessionsNotice, action: taskSessionAction?.type === "rename"
2735
3125
  ? { type: "rename", title: taskSessionAction.title }
2736
3126
  : taskSessionAction?.type === "delete"
2737
3127
  ? { type: "delete", title: taskSessionAction.title }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Box, Text } from "ink";
3
3
  import { compactEndByDisplayWidth, compactTailByDisplayWidth, displayWidth } from "./display-width.js";
4
4
  import { TUI_THEME } from "./theme.js";
5
- export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, taskSessionAction = null, taskSessionsIncludeArchived = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, clipboardNotice = null, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
5
+ export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, taskSessionAction = null, taskSessionsIncludeArchived = false, mainConversationSessions = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, clipboardNotice = null, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
6
6
  const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
7
7
  const fillRail = providedTerminalWidth !== undefined || typeof process.stdout.columns === "number";
8
8
  if (clipboardNotice) {
@@ -39,6 +39,10 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
39
39
  const hints = taskSessionDetailInputHints(terminalWidth, taskSessionDetailHasNative, taskSessionDetailCanFork);
40
40
  return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
41
41
  }
42
+ if (mainConversationSessions) {
43
+ const hints = mainConversationSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
44
+ return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
45
+ }
42
46
  const hints = taskSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
43
47
  return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
44
48
  }
@@ -564,9 +568,13 @@ function collaborationDetailInputHints(width) {
564
568
  function taskSessionsInputHints(width, includeArchived) {
565
569
  const archivedAction = includeArchived ? "H hide archived" : "H archived";
566
570
  return selectInputHints(width, [
567
- { label: "sessions", detail: ` · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
568
- { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · Esc back" },
569
- { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · Esc back" },
571
+ { label: "sessions", detail: ` · Up/Dn select · Enter restore · C conversations · I inspect · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
572
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · C conversations · I inspect · R rename · A archive · D delete · E export · Esc back" },
573
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · C chats · I inspect · R rename · A archive · D delete · Esc back" },
574
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · C chats · I inspect · R rename · A archive · Esc back" },
575
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · C chats · I inspect · R rename · Esc back" },
576
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · C chats · R rename · Esc back" },
577
+ { label: "sessions", detail: " · Up/Dn select · Enter restore · C chats · Esc back" },
570
578
  { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · Esc back" },
571
579
  { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · Esc back" },
572
580
  { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · Esc back" },
@@ -580,6 +588,24 @@ function taskSessionsInputHints(width, includeArchived) {
580
588
  { label: "s", detail: "" }
581
589
  ]);
582
590
  }
591
+ export function mainConversationSessionsInputHints(width, includeArchived) {
592
+ const archivedAction = includeArchived ? "H hide archived" : "H archived";
593
+ return selectInputHints(width, [
594
+ { label: "conversations", detail: ` · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · ${archivedAction} · N new · T tasks · Esc back` },
595
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · N new · T tasks · Esc back" },
596
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · N new · T tasks · Esc back" },
597
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · A archive · N new · T tasks · Esc back" },
598
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · R rename · N new · T tasks · Esc back" },
599
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · N new · T tasks · Esc back" },
600
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · T tasks · Esc back" },
601
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · Esc back" },
602
+ { label: "conversations", detail: " · Up/Dn select · Esc back" },
603
+ { label: "conversations", detail: " · Esc back" },
604
+ { label: "Esc back", detail: "" },
605
+ { label: "chats", detail: "" },
606
+ { label: "c", detail: "" }
607
+ ]);
608
+ }
583
609
  function taskSessionDetailInputHints(width, hasNative, canFork) {
584
610
  const nativeActions = hasNative
585
611
  ? `${canFork ? "C continue · B branch" : "C continue"} · `