parallel-codex-tui 0.2.7 → 0.2.9

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
@@ -19,7 +19,7 @@ import { TerminalOutput } from "./TerminalOutput.js";
19
19
  import { NativeTerminalScreen } from "./terminal-screen.js";
20
20
  import { WorkerOutputView } from "./WorkerOutputView.js";
21
21
  import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
22
- import { isAttachShortcut, isCopyShortcut, isDiagnosticsShortcut, isExitShortcut, isLogsShortcut, isNewTaskShortcut, isRouterDiagnosticsShortcut, isStatusDetailsShortcut, isTaskResultShortcut, isTaskSessionsShortcut, isWorkerOverviewShortcut, isWorkerSearchShortcut, isWorkspaceShortcut, mouseScrollDelta, rawChatScrollArrowDelta, rawHistoryDelta, rawPageScrollDelta, rawPlainArrowDelta, scrollDelta, workerLogJumpKind } from "./keyboard.js";
22
+ import { isAttachShortcut, isCopyShortcut, isDiagnosticsShortcut, isExitShortcut, isLogsShortcut, isNewConversationShortcut, isRouterDiagnosticsShortcut, isStatusDetailsShortcut, isTaskResultShortcut, isTaskSessionsShortcut, isWorkerOverviewShortcut, isWorkerSearchShortcut, isWorkspaceShortcut, mouseScrollDelta, rawChatScrollArrowDelta, rawHistoryDelta, rawPageScrollDelta, rawPlainArrowDelta, scrollDelta, workerLogJumpKind } from "./keyboard.js";
23
23
  import { createRawInputDecoder, tokenizeRawInput } from "./raw-input-decoder.js";
24
24
  import { decodeHtmlEntities } from "./markdown-text.js";
25
25
  import { configureTuiTheme, TUI_THEME } from "./theme.js";
@@ -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, 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, 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,9 @@ 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);
100
104
  const [taskSessionsLoading, setTaskSessionsLoading] = useState(false);
101
105
  const [taskSessionsError, setTaskSessionsError] = useState(null);
102
106
  const [taskSessionsNotice, setTaskSessionsNotice] = useState(null);
@@ -149,6 +153,9 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
149
153
  const routerMaxScrollOffsetRef = useRef(routerMaxScrollOffset);
150
154
  const taskSessionsRef = useRef(taskSessions);
151
155
  const selectedTaskSessionIndexRef = useRef(selectedTaskSessionIndex);
156
+ const sessionCenterModeRef = useRef("tasks");
157
+ const mainConversationsRef = useRef(mainConversations);
158
+ const selectedMainConversationIndexRef = useRef(selectedMainConversationIndex);
152
159
  const taskSessionsLoadingRef = useRef(taskSessionsLoading);
153
160
  const taskSessionsIncludeArchivedRef = useRef(taskSessionsIncludeArchived);
154
161
  const taskSessionActionRef = useRef(taskSessionAction);
@@ -168,7 +175,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
168
175
  const attachSelectedWorkerRef = useRef(attachSelectedWorker);
169
176
  const submitRef = useRef(submit);
170
177
  const retryRef = useRef(retryActiveTask);
171
- const newTaskRef = useRef(startNewTask);
178
+ const newConversationRef = useRef(startNewConversation);
172
179
  const openWorkspacePickerRef = useRef(openWorkspacePicker);
173
180
  const openRouterDiagnosticsRef = useRef(openRouterDiagnostics);
174
181
  const openWorkerOverviewRef = useRef(openWorkerOverview);
@@ -180,6 +187,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
180
187
  const refreshCollaborationTimelineRef = useRef(refreshCollaborationTimeline);
181
188
  const activateSelectedTaskSessionRef = useRef(activateSelectedTaskSession);
182
189
  const refreshTaskSessionsRef = useRef(refreshTaskSessions);
190
+ const openMainConversationsRef = useRef(openMainConversations);
191
+ const restoreSelectedMainConversationRef = useRef(restoreSelectedMainConversation);
183
192
  const renameSelectedTaskSessionRef = useRef(renameSelectedTaskSession);
184
193
  const archiveSelectedTaskSessionRef = useRef(archiveSelectedTaskSession);
185
194
  const deleteSelectedTaskSessionRef = useRef(deleteSelectedTaskSession);
@@ -334,6 +343,15 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
334
343
  useEffect(() => {
335
344
  selectedTaskSessionIndexRef.current = selectedTaskSessionIndex;
336
345
  }, [selectedTaskSessionIndex]);
346
+ useEffect(() => {
347
+ sessionCenterModeRef.current = sessionCenterMode;
348
+ }, [sessionCenterMode]);
349
+ useEffect(() => {
350
+ mainConversationsRef.current = mainConversations;
351
+ }, [mainConversations]);
352
+ useEffect(() => {
353
+ selectedMainConversationIndexRef.current = selectedMainConversationIndex;
354
+ }, [selectedMainConversationIndex]);
337
355
  useEffect(() => {
338
356
  taskSessionsLoadingRef.current = taskSessionsLoading;
339
357
  }, [taskSessionsLoading]);
@@ -394,7 +412,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
394
412
  attachSelectedWorkerRef.current = attachSelectedWorker;
395
413
  submitRef.current = submit;
396
414
  retryRef.current = retryActiveTask;
397
- newTaskRef.current = startNewTask;
415
+ newConversationRef.current = startNewConversation;
398
416
  openWorkspacePickerRef.current = openWorkspacePicker;
399
417
  openRouterDiagnosticsRef.current = openRouterDiagnostics;
400
418
  openWorkerOverviewRef.current = openWorkerOverview;
@@ -405,6 +423,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
405
423
  refreshCollaborationTimelineRef.current = refreshCollaborationTimeline;
406
424
  activateSelectedTaskSessionRef.current = activateSelectedTaskSession;
407
425
  refreshTaskSessionsRef.current = refreshTaskSessions;
426
+ openMainConversationsRef.current = openMainConversations;
427
+ restoreSelectedMainConversationRef.current = restoreSelectedMainConversation;
408
428
  renameSelectedTaskSessionRef.current = renameSelectedTaskSession;
409
429
  archiveSelectedTaskSessionRef.current = archiveSelectedTaskSession;
410
430
  deleteSelectedTaskSessionRef.current = deleteSelectedTaskSession;
@@ -685,6 +705,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
685
705
  setTaskSessionsError(null);
686
706
  setSelectedTaskSessionIndex(nextIndex);
687
707
  };
708
+ const moveSelectedMainConversation = (delta, wrap = false) => {
709
+ const nextIndex = moveMainConversationSelection(selectedMainConversationIndexRef.current, delta, mainConversationsRef.current.length, wrap);
710
+ selectedMainConversationIndexRef.current = nextIndex;
711
+ setTaskSessionsError(null);
712
+ setTaskSessionsNotice(null);
713
+ setSelectedMainConversationIndex(nextIndex);
714
+ };
688
715
  const moveSelectedTaskSessionDetailWorker = (delta, wrap = false) => {
689
716
  const nextIndex = moveTaskSessionDetailSelection(taskSessionDetailSelectedWorkerIndexRef.current, delta, taskSessionDetailsRef.current?.workers.length ?? 0, wrap);
690
717
  taskSessionDetailSelectedWorkerIndexRef.current = nextIndex;
@@ -847,6 +874,49 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
847
874
  }
848
875
  return;
849
876
  }
877
+ if (sessionCenterModeRef.current === "conversations") {
878
+ if (isTaskSessionsShortcut(chunk, {}) || chunk === "\x1b") {
879
+ setTaskSessionsError(null);
880
+ setTaskSessionsNotice(null);
881
+ viewRef.current = taskSessionsReturnViewRef.current;
882
+ setView(taskSessionsReturnViewRef.current);
883
+ return;
884
+ }
885
+ if (taskSessionsLoadingRef.current) {
886
+ return;
887
+ }
888
+ if (isNewConversationShortcut(chunk, {}) || chunk === "n" || chunk === "N") {
889
+ void newConversationRef.current();
890
+ return;
891
+ }
892
+ if (chunk === "t" || chunk === "T") {
893
+ sessionCenterModeRef.current = "tasks";
894
+ setSessionCenterMode("tasks");
895
+ setTaskSessionsError(null);
896
+ setTaskSessionsNotice(null);
897
+ void refreshTaskSessionsRef.current(activeTaskIdRef.current);
898
+ return;
899
+ }
900
+ if (chunk === "c" || chunk === "C") {
901
+ void openMainConversationsRef.current();
902
+ return;
903
+ }
904
+ if (chunk === "\r" || chunk === "\n") {
905
+ void restoreSelectedMainConversationRef.current();
906
+ return;
907
+ }
908
+ if (chunk === "\t") {
909
+ moveSelectedMainConversation(1, true);
910
+ return;
911
+ }
912
+ const conversationSelectionDelta = -(rawHistoryDelta(chunk)
913
+ + rawPageScrollDelta(chunk, Math.max(1, outputHeight - 2))
914
+ + mouseScrollDelta(chunk, 1));
915
+ if (conversationSelectionDelta !== 0) {
916
+ moveSelectedMainConversation(conversationSelectionDelta);
917
+ }
918
+ return;
919
+ }
850
920
  const sessionAction = taskSessionActionRef.current;
851
921
  if (sessionAction) {
852
922
  if (chunk === "\x1b") {
@@ -898,14 +968,12 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
898
968
  openWorkspacePickerRef.current();
899
969
  return;
900
970
  }
901
- if (isNewTaskShortcut(chunk, {}) && !busyRef.current) {
902
- if (activeTaskIdRef.current) {
903
- void newTaskRef.current();
904
- }
905
- else {
906
- viewRef.current = "chat";
907
- setView("chat");
908
- }
971
+ if (isNewConversationShortcut(chunk, {}) && !busyRef.current) {
972
+ void newConversationRef.current();
973
+ return;
974
+ }
975
+ if (chunk === "c" || chunk === "C") {
976
+ void openMainConversationsRef.current();
909
977
  return;
910
978
  }
911
979
  const selectedSession = taskSessionsRef.current[selectedTaskSessionIndexRef.current];
@@ -1217,8 +1285,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1217
1285
  openWorkspacePickerRef.current();
1218
1286
  return;
1219
1287
  }
1220
- if (isNewTaskShortcut(chunk, {}) && !busyRef.current) {
1221
- void newTaskRef.current();
1288
+ if (isNewConversationShortcut(chunk, {}) && !busyRef.current) {
1289
+ void newConversationRef.current();
1222
1290
  return;
1223
1291
  }
1224
1292
  if (chunk === "f" || chunk === "F") {
@@ -1336,8 +1404,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1336
1404
  jumpWorkerLog(jumpKind);
1337
1405
  continue;
1338
1406
  }
1339
- if (isNewTaskShortcut(workerChunk, {}) && !busyRef.current) {
1340
- void newTaskRef.current();
1407
+ if (isNewConversationShortcut(workerChunk, {}) && !busyRef.current) {
1408
+ void newConversationRef.current();
1341
1409
  return;
1342
1410
  }
1343
1411
  if (isWorkspaceShortcut(workerChunk, {}) && !busyRef.current) {
@@ -1413,8 +1481,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1413
1481
  }
1414
1482
  return;
1415
1483
  }
1416
- if (isNewTaskShortcut(chunk, {}) && !busyRef.current) {
1417
- void newTaskRef.current();
1484
+ if (isNewConversationShortcut(chunk, {}) && !busyRef.current) {
1485
+ void newConversationRef.current();
1418
1486
  return;
1419
1487
  }
1420
1488
  if (chunk === "\x1b") {
@@ -1688,7 +1756,15 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1688
1756
  label: "sessions",
1689
1757
  text: details
1690
1758
  ? details.workers.map((worker) => [worker.id, worker.role, worker.engine, worker.state].join(" · ")).join("\n")
1691
- : taskSessionsRef.current.map((task) => [task.id, task.title, task.status].join(" · ")).join("\n")
1759
+ : sessionCenterModeRef.current === "conversations"
1760
+ ? mainConversationsRef.current.map((conversation) => [
1761
+ conversation.current ? "current" : "saved",
1762
+ conversation.id ?? "legacy",
1763
+ conversation.title,
1764
+ `${conversation.messageCount} messages`,
1765
+ `${conversation.nativeSessionCount} native`
1766
+ ].join(" · ")).join("\n")
1767
+ : taskSessionsRef.current.map((task) => [task.id, task.title, task.status].join(" · ")).join("\n")
1692
1768
  };
1693
1769
  }
1694
1770
  if (currentView === "router") {
@@ -1712,8 +1788,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1712
1788
  exitRef.current();
1713
1789
  return;
1714
1790
  }
1715
- if (isNewTaskShortcut(inputKey, key) && !busy) {
1716
- void startNewTask();
1791
+ if (isNewConversationShortcut(inputKey, key) && !busy) {
1792
+ void startNewConversation();
1717
1793
  return;
1718
1794
  }
1719
1795
  const delta = scrollDelta(inputKey, key, outputHeight - 1);
@@ -2104,17 +2180,22 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2104
2180
  setBusy(false);
2105
2181
  }
2106
2182
  }
2107
- async function startNewTask() {
2108
- if (busyRef.current || !activeTaskIdRef.current) {
2183
+ async function startNewConversation() {
2184
+ if (busyRef.current) {
2109
2185
  return;
2110
2186
  }
2111
2187
  try {
2188
+ await startMainConversation?.();
2112
2189
  await activateTaskSession?.(null);
2113
2190
  }
2114
2191
  catch (error) {
2115
2192
  setAttachError(error instanceof Error ? error.message : String(error));
2116
2193
  return;
2117
2194
  }
2195
+ resetActiveTaskUiForMainConversation();
2196
+ await appendVisibleMessage({ from: "system", text: "new conversation · ready" });
2197
+ }
2198
+ function resetActiveTaskUiForMainConversation() {
2118
2199
  const nextMemory = newTaskMemoryState();
2119
2200
  activeTaskIdRef.current = nextMemory.activeTaskId;
2120
2201
  setActiveTaskId(nextMemory.activeTaskId);
@@ -2139,7 +2220,6 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2139
2220
  offset: 0,
2140
2221
  draft: { value: inputRef.current, cursor: inputCursorRef.current }
2141
2222
  };
2142
- await appendVisibleMessage({ from: "system", text: "new task · ready" });
2143
2223
  }
2144
2224
  async function openRouterDiagnostics() {
2145
2225
  const currentView = viewRef.current;
@@ -2205,6 +2285,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2205
2285
  setTaskSessionDetailSelectedWorkerIndex(0);
2206
2286
  setTaskSessionDetailNotice(null);
2207
2287
  updateTaskSessionAction(null);
2288
+ sessionCenterModeRef.current = "tasks";
2289
+ setSessionCenterMode("tasks");
2208
2290
  await refreshTaskSessions(activeTaskIdRef.current);
2209
2291
  }
2210
2292
  async function refreshTaskSessions(preferredTaskId = null) {
@@ -2245,6 +2327,103 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2245
2327
  }
2246
2328
  }
2247
2329
  }
2330
+ async function openMainConversations() {
2331
+ const currentView = viewRef.current;
2332
+ if (busyRef.current || currentView === "native" || currentView === "workspace") {
2333
+ return;
2334
+ }
2335
+ if (currentView !== "sessions") {
2336
+ taskSessionsReturnViewRef.current = currentView === "worker" || currentView === "workers" || currentView === "router"
2337
+ ? currentView
2338
+ : "chat";
2339
+ viewRef.current = "sessions";
2340
+ setView("sessions");
2341
+ }
2342
+ const previousId = sessionCenterModeRef.current === "conversations"
2343
+ ? mainConversationsRef.current[selectedMainConversationIndexRef.current]?.id
2344
+ : undefined;
2345
+ sessionCenterModeRef.current = "conversations";
2346
+ setSessionCenterMode("conversations");
2347
+ taskSessionDetailsRef.current = null;
2348
+ setTaskSessionDetails(null);
2349
+ updateTaskSessionAction(null);
2350
+ setTaskSessionsError(null);
2351
+ setTaskSessionsNotice(null);
2352
+ taskSessionsLoadingRef.current = true;
2353
+ setTaskSessionsLoading(true);
2354
+ const sequence = taskSessionsLoadSequenceRef.current + 1;
2355
+ taskSessionsLoadSequenceRef.current = sequence;
2356
+ try {
2357
+ if (!loadMainConversations) {
2358
+ throw new Error("Main conversations are unavailable");
2359
+ }
2360
+ const conversations = await loadMainConversations();
2361
+ if (taskSessionsLoadSequenceRef.current !== sequence) {
2362
+ return;
2363
+ }
2364
+ mainConversationsRef.current = conversations;
2365
+ setMainConversations(conversations);
2366
+ const preservedIndex = previousId !== undefined
2367
+ ? conversations.findIndex((conversation) => conversation.id === previousId)
2368
+ : -1;
2369
+ const currentIndex = conversations.findIndex((conversation) => conversation.current);
2370
+ const selectedIndex = preservedIndex >= 0
2371
+ ? preservedIndex
2372
+ : currentIndex >= 0
2373
+ ? currentIndex
2374
+ : 0;
2375
+ selectedMainConversationIndexRef.current = selectedIndex;
2376
+ setSelectedMainConversationIndex(selectedIndex);
2377
+ }
2378
+ catch (error) {
2379
+ if (taskSessionsLoadSequenceRef.current === sequence) {
2380
+ setTaskSessionsError(error instanceof Error ? error.message : String(error));
2381
+ }
2382
+ }
2383
+ finally {
2384
+ if (taskSessionsLoadSequenceRef.current === sequence) {
2385
+ taskSessionsLoadingRef.current = false;
2386
+ setTaskSessionsLoading(false);
2387
+ }
2388
+ }
2389
+ }
2390
+ async function restoreSelectedMainConversation() {
2391
+ if (busyRef.current || taskSessionsLoadingRef.current) {
2392
+ return;
2393
+ }
2394
+ const selected = mainConversationsRef.current[selectedMainConversationIndexRef.current];
2395
+ if (!selected) {
2396
+ return;
2397
+ }
2398
+ taskSessionsLoadingRef.current = true;
2399
+ setTaskSessionsLoading(true);
2400
+ setTaskSessionsError(null);
2401
+ setTaskSessionsNotice(null);
2402
+ try {
2403
+ if (!activateMainConversation) {
2404
+ throw new Error("Main conversation restore is unavailable");
2405
+ }
2406
+ const result = await activateMainConversation(selected.id);
2407
+ if (!result.changed) {
2408
+ await openMainConversations();
2409
+ setTaskSessionsNotice(`Already current · ${result.conversation.title}`);
2410
+ return;
2411
+ }
2412
+ await activateTaskSession?.(null);
2413
+ resetActiveTaskUiForMainConversation();
2414
+ await appendVisibleMessage({
2415
+ from: "system",
2416
+ text: `conversation restored · ${result.conversation.title} · ${result.restoredNativeSessions} native`
2417
+ });
2418
+ }
2419
+ catch (error) {
2420
+ setTaskSessionsError(error instanceof Error ? error.message : String(error));
2421
+ }
2422
+ finally {
2423
+ taskSessionsLoadingRef.current = false;
2424
+ setTaskSessionsLoading(false);
2425
+ }
2426
+ }
2248
2427
  function updateTaskSessionAction(next) {
2249
2428
  taskSessionActionRef.current = next;
2250
2429
  setTaskSessionAction(next);
@@ -2732,11 +2911,11 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2732
2911
  ? { type: "rename", value: taskSessionAction.value, cursor: taskSessionAction.cursor }
2733
2912
  : taskSessionAction?.type === "delete"
2734
2913
  ? { type: "delete", title: taskSessionAction.title }
2735
- : 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"
2914
+ : null, taskSessionsIncludeArchived: 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"
2736
2915
  ? workerSearch.query
2737
2916
  : view === "native" || view === "router" || view === "workers" || view === "features" || view === "sessions" || view === "collaboration" || view === "status" ? "" : input, cursor: workerSearch.open && view === "worker"
2738
2917
  ? workerSearch.cursor
2739
- : 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"
2918
+ : 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, notice: taskSessionsNotice, loading: taskSessionsLoading, error: taskSessionsError, height: contentHeight, terminalWidth: terminalWidth })) : (_jsx(TaskSessionsView, { tasks: taskSessions, activeTaskId: activeTaskId, selectedIndex: selectedTaskSessionIndex, includeArchived: taskSessionsIncludeArchived, notice: taskSessionsNotice, action: taskSessionAction?.type === "rename"
2740
2919
  ? { type: "rename", title: taskSessionAction.title }
2741
2920
  : taskSessionAction?.type === "delete"
2742
2921
  ? { 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);
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
  }
@@ -208,7 +212,7 @@ export function chatPlaceholderDisplayValue(terminalWidth, options = {}) {
208
212
  const maxScrollOffset = Math.max(0, options.maxScrollOffset ?? 0);
209
213
  const scrollOffset = Math.min(Math.max(0, options.scrollOffset ?? 0), maxScrollOffset);
210
214
  if (options.hasTaskResult) {
211
- return chatTaskResultPlaceholderDisplayValue(terminalWidth, Boolean(options.taskResultExpanded), Boolean(options.hasWorkers), Boolean(options.hasActiveTask), scrollOffset, maxScrollOffset);
215
+ return chatTaskResultPlaceholderDisplayValue(terminalWidth, Boolean(options.taskResultExpanded), Boolean(options.hasWorkers), scrollOffset, maxScrollOffset);
212
216
  }
213
217
  if (scrollOffset > 0) {
214
218
  return chatHistoryPlaceholderDisplayValue(terminalWidth, scrollOffset, maxScrollOffset);
@@ -224,20 +228,21 @@ export function chatPlaceholderDisplayValue(terminalWidth, options = {}) {
224
228
  ]);
225
229
  }
226
230
  if (options.hasWorkers) {
227
- return chatTaskPlaceholderDisplayValue(terminalWidth, maxScrollOffset > 0, options.hasActiveTask);
231
+ return chatTaskPlaceholderDisplayValue(terminalWidth, maxScrollOffset > 0);
228
232
  }
229
233
  if (maxScrollOffset > 0 && terminalWidth >= 22) {
230
234
  return selectChatPlaceholder(terminalWidth, ["message · scroll", "message", "msg"]);
231
235
  }
232
236
  return selectChatPlaceholder(terminalWidth, [
233
- "message · ^P project · ^T tasks · ^G routes",
234
- "message · ^P project · ^G routes",
235
- "message · ^P project",
237
+ "message · ^N new · ^P project · ^T tasks · ^G routes",
238
+ "message · ^N new · ^P project · ^G routes",
239
+ "message · ^N new · ^P project",
240
+ "message · ^N new",
236
241
  "message",
237
242
  "msg"
238
243
  ]);
239
244
  }
240
- function chatTaskResultPlaceholderDisplayValue(terminalWidth, expanded, hasWorkers, hasActiveTask, scrollOffset, maxScrollOffset) {
245
+ function chatTaskResultPlaceholderDisplayValue(terminalWidth, expanded, hasWorkers, scrollOffset, maxScrollOffset) {
241
246
  const toggle = expanded ? "^D compact" : "^D details";
242
247
  const position = expanded && maxScrollOffset > 0
243
248
  ? scrollOffset > 0 ? `result ${scrollOffset}/${maxScrollOffset}` : "result · scroll"
@@ -255,7 +260,7 @@ function chatTaskResultPlaceholderDisplayValue(terminalWidth, expanded, hasWorke
255
260
  "^D"
256
261
  ]
257
262
  : [
258
- `${position} · ${toggle}${hasActiveTask ? " · ^N new" : ""}`,
263
+ `${position} · ${toggle} · ^N new`,
259
264
  `${position} · ${toggle}`,
260
265
  toggle,
261
266
  "^D"
@@ -286,15 +291,17 @@ function chatHistoryPlaceholderDisplayValue(terminalWidth, offset, maxOffset) {
286
291
  "back"
287
292
  ]);
288
293
  }
289
- function chatTaskPlaceholderDisplayValue(terminalWidth, scrollable = false, activeTask = false) {
290
- if (activeTask && terminalWidth >= 72) {
294
+ function chatTaskPlaceholderDisplayValue(terminalWidth, scrollable = false) {
295
+ if (terminalWidth >= 72) {
291
296
  const activeCandidates = scrollable
292
297
  ? [
293
298
  "message · scroll · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
299
+ "scroll · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
294
300
  "message · scroll · ^N new · ^W logs · ^B workers · Tab · ^O attach · ^G routes"
295
301
  ]
296
302
  : [
297
303
  "message · ^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
304
+ "^N new · ^W logs · ^B workers · ^T tasks · Tab · ^O attach · ^G routes",
298
305
  "message · ^N new · ^W logs · ^B workers · Tab · ^O attach · ^G routes"
299
306
  ];
300
307
  const active = activeCandidates.find((candidate) => displayWidth(candidate) <= chatPlaceholderValueWidth(terminalWidth));
@@ -561,9 +568,13 @@ function collaborationDetailInputHints(width) {
561
568
  function taskSessionsInputHints(width, includeArchived) {
562
569
  const archivedAction = includeArchived ? "H hide archived" : "H archived";
563
570
  return selectInputHints(width, [
564
- { label: "sessions", detail: ` · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
565
- { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · Esc back" },
566
- { 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" },
567
578
  { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · Esc back" },
568
579
  { label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · Esc back" },
569
580
  { label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · Esc back" },
@@ -577,6 +588,18 @@ function taskSessionsInputHints(width, includeArchived) {
577
588
  { label: "s", detail: "" }
578
589
  ]);
579
590
  }
591
+ function mainConversationSessionsInputHints(width) {
592
+ return selectInputHints(width, [
593
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · N new · T tasks · Esc back" },
594
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · T tasks · Esc back" },
595
+ { label: "conversations", detail: " · Up/Dn select · Enter restore · Esc back" },
596
+ { label: "conversations", detail: " · Up/Dn select · Esc back" },
597
+ { label: "conversations", detail: " · Esc back" },
598
+ { label: "Esc back", detail: "" },
599
+ { label: "chats", detail: "" },
600
+ { label: "c", detail: "" }
601
+ ]);
602
+ }
580
603
  function taskSessionDetailInputHints(width, hasNative, canFork) {
581
604
  const nativeActions = hasNative
582
605
  ? `${canFork ? "C continue · B branch" : "C continue"} · `