parallel-codex-tui 0.2.9 → 0.3.0

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
@@ -5,6 +5,7 @@ import { Box, Text, useApp, useInput, useStdin } from "ink";
5
5
  import { Lexer } from "marked";
6
6
  import { copyTextToClipboard } from "../core/clipboard.js";
7
7
  import { readJson } from "../core/file-store.js";
8
+ import { CONFIGURABLE_ROLES, configuredRoleSelection } from "../core/role-configuration.js";
8
9
  import { WorkerStatusSchema } from "../domain/schemas.js";
9
10
  import { effectiveWorkerWatchdog, formatRoutePendingStatus, formatRoutePendingSummaryStatus, formatRouteStatus, formatRouteSummaryStatus, formatStatusLine, formatWorkerRuntimeStatus } from "./status-line.js";
10
11
  import { applyChatInputChunk, insertChatPaste } from "./chat-input.js";
@@ -19,7 +20,7 @@ import { TerminalOutput } from "./TerminalOutput.js";
19
20
  import { NativeTerminalScreen } from "./terminal-screen.js";
20
21
  import { WorkerOutputView } from "./WorkerOutputView.js";
21
22
  import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.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
+ import { isAttachShortcut, isCopyShortcut, isDiagnosticsShortcut, isExitShortcut, isLogsShortcut, isNewConversationShortcut, isRoleConfigurationShortcut, isRouterDiagnosticsShortcut, isStatusDetailsShortcut, isTaskResultShortcut, isTaskSessionsShortcut, isWorkerOverviewShortcut, isWorkerSearchShortcut, isWorkspaceShortcut, mouseScrollDelta, rawChatScrollArrowDelta, rawHistoryDelta, rawHorizontalArrowDelta, rawPageScrollDelta, rawPlainArrowDelta, scrollDelta, workerLogJumpKind } from "./keyboard.js";
23
24
  import { createRawInputDecoder, tokenizeRawInput } from "./raw-input-decoder.js";
24
25
  import { decodeHtmlEntities } from "./markdown-text.js";
25
26
  import { configureTuiTheme, TUI_THEME } from "./theme.js";
@@ -35,6 +36,8 @@ import { latestTaskResultMessageIndex, parseTaskResultSummary } from "./task-res
35
36
  import { buildNativeAttachLaunch, buildNativeForkLaunch, startNativeAttachProcess } from "../workers/native-attach.js";
36
37
  import { assignableWorkerProviderIds, workerProviderLabel, workerProviders } from "../workers/provider.js";
37
38
  import { StatusDetailView } from "./StatusDetailView.js";
39
+ import { RoleConfigurationView } from "./RoleConfigurationView.js";
40
+ import { cycleRoleProvider, moveRoleConfigurationSelection, nextRoleConfigurationScope, roleConfigurationScopeHasOverride, roleConfigurationSelectionForScope, selectedConfigurableRole, updateRoleModel } from "./role-configuration-state.js";
38
41
  import { runtimeConfigChange } from "./runtime-config-state.js";
39
42
  export function routeDecisionChatMessage(route) {
40
43
  return `route · ${route.mode} · ${route.source ?? "router"}\n${route.reason.trim()}`;
@@ -50,7 +53,7 @@ const EMPTY_WORKER_NAVIGATION_TARGETS = {
50
53
  errorOffsets: [],
51
54
  diffOffsets: []
52
55
  };
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 }) {
56
+ 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 }) {
54
57
  configureTuiTheme({
55
58
  theme: config.ui.theme,
56
59
  colors: config.ui.colors
@@ -77,6 +80,15 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
77
80
  const [canRetryTask, setCanRetryTask] = useState(initialCanRetryTask);
78
81
  const [attachError, setAttachError] = useState(null);
79
82
  const [configChange, setConfigChange] = useState(null);
83
+ const [roleConfigurationSnapshot, setRoleConfigurationSnapshot] = useState(null);
84
+ const [roleConfigurationDraft, setRoleConfigurationDraft] = useState(null);
85
+ const [roleConfigurationScope, setRoleConfigurationScope] = useState("next");
86
+ const [roleConfigurationSelectedIndex, setRoleConfigurationSelectedIndex] = useState(0);
87
+ const [roleConfigurationLoading, setRoleConfigurationLoading] = useState(false);
88
+ const [roleConfigurationSaving, setRoleConfigurationSaving] = useState(false);
89
+ const [roleConfigurationNotice, setRoleConfigurationNotice] = useState(null);
90
+ const [roleConfigurationError, setRoleConfigurationError] = useState(null);
91
+ const [roleModelEdit, setRoleModelEdit] = useState(null);
80
92
  const [clipboardNotice, setClipboardNotice] = useState(null);
81
93
  const [nativeInput, setNativeInput] = useState("");
82
94
  const [workerScrollOffset, setWorkerScrollOffset] = useState(0);
@@ -101,6 +113,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
101
113
  const [sessionCenterMode, setSessionCenterMode] = useState("tasks");
102
114
  const [mainConversations, setMainConversations] = useState([]);
103
115
  const [selectedMainConversationIndex, setSelectedMainConversationIndex] = useState(0);
116
+ const [mainConversationsIncludeArchived, setMainConversationsIncludeArchived] = useState(false);
117
+ const [mainConversationAction, setMainConversationAction] = useState(null);
104
118
  const [taskSessionsLoading, setTaskSessionsLoading] = useState(false);
105
119
  const [taskSessionsError, setTaskSessionsError] = useState(null);
106
120
  const [taskSessionsNotice, setTaskSessionsNotice] = useState(null);
@@ -156,6 +170,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
156
170
  const sessionCenterModeRef = useRef("tasks");
157
171
  const mainConversationsRef = useRef(mainConversations);
158
172
  const selectedMainConversationIndexRef = useRef(selectedMainConversationIndex);
173
+ const mainConversationsIncludeArchivedRef = useRef(mainConversationsIncludeArchived);
174
+ const mainConversationActionRef = useRef(mainConversationAction);
159
175
  const taskSessionsLoadingRef = useRef(taskSessionsLoading);
160
176
  const taskSessionsIncludeArchivedRef = useRef(taskSessionsIncludeArchived);
161
177
  const taskSessionActionRef = useRef(taskSessionAction);
@@ -165,6 +181,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
165
181
  const featureBoardSelectedIndexRef = useRef(0);
166
182
  const featureCancelPromptRef = useRef(null);
167
183
  const featureAssignmentPromptRef = useRef(null);
184
+ const roleConfigurationSnapshotRef = useRef(null);
185
+ const roleConfigurationDraftRef = useRef(null);
186
+ const roleConfigurationScopeRef = useRef("next");
187
+ const roleConfigurationSelectedIndexRef = useRef(0);
188
+ const roleConfigurationLoadingRef = useRef(false);
189
+ const roleConfigurationSavingRef = useRef(false);
190
+ const roleModelEditRef = useRef(null);
168
191
  const collaborationFeatureIndexRef = useRef(-1);
169
192
  const collaborationSelectedEventIdRef = useRef(null);
170
193
  const collaborationDetailOpenRef = useRef(false);
@@ -184,11 +207,19 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
184
207
  const confirmFeatureCancellationRef = useRef(confirmFeatureCancellation);
185
208
  const reassignSelectedFeatureRef = useRef(reassignSelectedFeature);
186
209
  const openCollaborationTimelineRef = useRef(openCollaborationTimeline);
210
+ const openRoleConfigurationRef = useRef(openRoleConfiguration);
211
+ const applyRoleConfigurationRef = useRef(applyRoleConfiguration);
212
+ const clearRoleConfigurationRef = useRef(clearRoleConfiguration);
187
213
  const refreshCollaborationTimelineRef = useRef(refreshCollaborationTimeline);
188
214
  const activateSelectedTaskSessionRef = useRef(activateSelectedTaskSession);
189
215
  const refreshTaskSessionsRef = useRef(refreshTaskSessions);
190
216
  const openMainConversationsRef = useRef(openMainConversations);
217
+ const refreshMainConversationsRef = useRef(refreshMainConversations);
191
218
  const restoreSelectedMainConversationRef = useRef(restoreSelectedMainConversation);
219
+ const renameSelectedMainConversationRef = useRef(renameSelectedMainConversation);
220
+ const archiveSelectedMainConversationRef = useRef(archiveSelectedMainConversation);
221
+ const deleteSelectedMainConversationRef = useRef(deleteSelectedMainConversation);
222
+ const exportSelectedMainConversationRef = useRef(exportSelectedMainConversation);
192
223
  const renameSelectedTaskSessionRef = useRef(renameSelectedTaskSession);
193
224
  const archiveSelectedTaskSessionRef = useRef(archiveSelectedTaskSession);
194
225
  const deleteSelectedTaskSessionRef = useRef(deleteSelectedTaskSession);
@@ -204,6 +235,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
204
235
  const taskSessionsReturnViewRef = useRef("chat");
205
236
  const collaborationReturnViewRef = useRef("workers");
206
237
  const statusReturnViewRef = useRef("chat");
238
+ const roleConfigurationReturnViewRef = useRef("chat");
239
+ const roleConfigurationLoadSequenceRef = useRef(0);
207
240
  const collaborationLoadSequenceRef = useRef(0);
208
241
  const routerLoadSequenceRef = useRef(0);
209
242
  const taskSessionsLoadSequenceRef = useRef(0);
@@ -247,6 +280,14 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
247
280
  const taskResultMessageIndex = useMemo(() => activeTaskId ? latestTaskResultMessageIndex(messages, activeTaskId) : -1, [activeTaskId, messages]);
248
281
  const visibleChatMessages = useMemo(() => routeAnnouncement ? [...messages, routeAnnouncement] : messages, [messages, routeAnnouncement]);
249
282
  const hasTaskResult = taskResultMessageIndex >= 0;
283
+ const activeRoleSelection = roleConfigurationSnapshot
284
+ ? activeTaskId
285
+ ? roleConfigurationSnapshot.activeTurn
286
+ ?? roleConfigurationSnapshot.task
287
+ ?? roleConfigurationSnapshot.future
288
+ : roleConfigurationSnapshot.future
289
+ : configuredRoleSelection(config);
290
+ const roleConfigurationHasOverride = roleConfigurationScopeHasOverride(roleConfigurationSnapshot, roleConfigurationScope);
250
291
  useEffect(() => {
251
292
  inputRef.current = input;
252
293
  }, [input]);
@@ -316,6 +357,54 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
316
357
  useEffect(() => {
317
358
  canRetryTaskRef.current = canRetryTask;
318
359
  }, [canRetryTask]);
360
+ useEffect(() => {
361
+ roleConfigurationSnapshotRef.current = roleConfigurationSnapshot;
362
+ }, [roleConfigurationSnapshot]);
363
+ useEffect(() => {
364
+ roleConfigurationDraftRef.current = roleConfigurationDraft;
365
+ }, [roleConfigurationDraft]);
366
+ useEffect(() => {
367
+ roleConfigurationScopeRef.current = roleConfigurationScope;
368
+ }, [roleConfigurationScope]);
369
+ useEffect(() => {
370
+ roleConfigurationSelectedIndexRef.current = roleConfigurationSelectedIndex;
371
+ }, [roleConfigurationSelectedIndex]);
372
+ useEffect(() => {
373
+ roleConfigurationLoadingRef.current = roleConfigurationLoading;
374
+ }, [roleConfigurationLoading]);
375
+ useEffect(() => {
376
+ roleConfigurationSavingRef.current = roleConfigurationSaving;
377
+ }, [roleConfigurationSaving]);
378
+ useEffect(() => {
379
+ roleModelEditRef.current = roleModelEdit;
380
+ }, [roleModelEdit]);
381
+ useEffect(() => {
382
+ let active = true;
383
+ const sequence = roleConfigurationLoadSequenceRef.current + 1;
384
+ roleConfigurationLoadSequenceRef.current = sequence;
385
+ const snapshotPromise = typeof orchestrator.roleConfigurationSnapshot === "function"
386
+ ? orchestrator.roleConfigurationSnapshot(activeTaskId)
387
+ : Promise.resolve(fallbackRoleConfigurationSnapshot(config));
388
+ void snapshotPromise.then((snapshot) => {
389
+ if (!active || roleConfigurationLoadSequenceRef.current !== sequence) {
390
+ return;
391
+ }
392
+ roleConfigurationSnapshotRef.current = snapshot;
393
+ setRoleConfigurationSnapshot(snapshot);
394
+ if (viewRef.current === "roles") {
395
+ const draft = roleConfigurationSelectionForScope(snapshot, roleConfigurationScopeRef.current);
396
+ roleConfigurationDraftRef.current = draft;
397
+ setRoleConfigurationDraft(draft);
398
+ }
399
+ }).catch((error) => {
400
+ if (active && roleConfigurationLoadSequenceRef.current === sequence) {
401
+ setRoleConfigurationError(error instanceof Error ? error.message : String(error));
402
+ }
403
+ });
404
+ return () => {
405
+ active = false;
406
+ };
407
+ }, [activeTaskId, orchestrator]);
319
408
  useEffect(() => {
320
409
  taskResultExpandedRef.current = taskResultExpanded;
321
410
  }, [taskResultExpanded]);
@@ -352,6 +441,12 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
352
441
  useEffect(() => {
353
442
  selectedMainConversationIndexRef.current = selectedMainConversationIndex;
354
443
  }, [selectedMainConversationIndex]);
444
+ useEffect(() => {
445
+ mainConversationsIncludeArchivedRef.current = mainConversationsIncludeArchived;
446
+ }, [mainConversationsIncludeArchived]);
447
+ useEffect(() => {
448
+ mainConversationActionRef.current = mainConversationAction;
449
+ }, [mainConversationAction]);
355
450
  useEffect(() => {
356
451
  taskSessionsLoadingRef.current = taskSessionsLoading;
357
452
  }, [taskSessionsLoading]);
@@ -418,13 +513,21 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
418
513
  openWorkerOverviewRef.current = openWorkerOverview;
419
514
  openTaskSessionsRef.current = openTaskSessions;
420
515
  openFeatureBoardRef.current = openFeatureBoard;
516
+ openRoleConfigurationRef.current = openRoleConfiguration;
517
+ applyRoleConfigurationRef.current = applyRoleConfiguration;
518
+ clearRoleConfigurationRef.current = clearRoleConfiguration;
421
519
  confirmFeatureCancellationRef.current = confirmFeatureCancellation;
422
520
  openCollaborationTimelineRef.current = openCollaborationTimeline;
423
521
  refreshCollaborationTimelineRef.current = refreshCollaborationTimeline;
424
522
  activateSelectedTaskSessionRef.current = activateSelectedTaskSession;
425
523
  refreshTaskSessionsRef.current = refreshTaskSessions;
426
524
  openMainConversationsRef.current = openMainConversations;
525
+ refreshMainConversationsRef.current = refreshMainConversations;
427
526
  restoreSelectedMainConversationRef.current = restoreSelectedMainConversation;
527
+ renameSelectedMainConversationRef.current = renameSelectedMainConversation;
528
+ archiveSelectedMainConversationRef.current = archiveSelectedMainConversation;
529
+ deleteSelectedMainConversationRef.current = deleteSelectedMainConversation;
530
+ exportSelectedMainConversationRef.current = exportSelectedMainConversation;
428
531
  renameSelectedTaskSessionRef.current = renameSelectedTaskSession;
429
532
  archiveSelectedTaskSessionRef.current = archiveSelectedTaskSession;
430
533
  deleteSelectedTaskSessionRef.current = deleteSelectedTaskSession;
@@ -808,6 +911,137 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
808
911
  if (currentView === "workspace") {
809
912
  return;
810
913
  }
914
+ if (currentView !== "native"
915
+ && tokenizeRawInput(chunk).some((inputChunk) => isRoleConfigurationShortcut(inputChunk, {}))
916
+ && !busyRef.current) {
917
+ if (currentView === "roles") {
918
+ roleModelEditRef.current = null;
919
+ setRoleModelEdit(null);
920
+ viewRef.current = roleConfigurationReturnViewRef.current;
921
+ setView(roleConfigurationReturnViewRef.current);
922
+ }
923
+ else {
924
+ void openRoleConfigurationRef.current();
925
+ }
926
+ return;
927
+ }
928
+ if (currentView === "roles") {
929
+ const roleChunks = tokenizeRawInput(chunk);
930
+ if (roleChunks.some((roleChunk) => isExitShortcut(roleChunk, {}))) {
931
+ activeRunControllerRef.current?.abort();
932
+ exitRef.current();
933
+ return;
934
+ }
935
+ if (roleChunks.some((roleChunk) => isStatusDetailsShortcut(roleChunk, {}))) {
936
+ statusReturnViewRef.current = "roles";
937
+ viewRef.current = "status";
938
+ setView("status");
939
+ return;
940
+ }
941
+ for (const roleChunk of roleChunks) {
942
+ const edit = roleModelEditRef.current;
943
+ if (edit) {
944
+ if (roleChunk === "\x1b") {
945
+ roleModelEditRef.current = null;
946
+ setRoleModelEdit(null);
947
+ continue;
948
+ }
949
+ const update = applyChatInputChunk(edit.value, roleChunk, edit.cursor);
950
+ if (update.submit !== null) {
951
+ const draft = roleConfigurationDraftRef.current;
952
+ if (draft) {
953
+ const nextDraft = updateRoleModel(draft, edit.role, update.submit);
954
+ roleConfigurationDraftRef.current = nextDraft;
955
+ setRoleConfigurationDraft(nextDraft);
956
+ }
957
+ roleModelEditRef.current = null;
958
+ setRoleModelEdit(null);
959
+ }
960
+ else {
961
+ const nextEdit = { ...edit, value: update.value, cursor: update.cursor };
962
+ roleModelEditRef.current = nextEdit;
963
+ setRoleModelEdit(nextEdit);
964
+ }
965
+ continue;
966
+ }
967
+ if (roleChunk === "\x1b") {
968
+ viewRef.current = roleConfigurationReturnViewRef.current;
969
+ setView(roleConfigurationReturnViewRef.current);
970
+ return;
971
+ }
972
+ if (roleConfigurationLoadingRef.current || roleConfigurationSavingRef.current) {
973
+ continue;
974
+ }
975
+ if (roleChunk === "\t") {
976
+ const nextScope = nextRoleConfigurationScope(roleConfigurationScopeRef.current, 1, Boolean(activeTaskIdRef.current));
977
+ roleConfigurationScopeRef.current = nextScope;
978
+ setRoleConfigurationScope(nextScope);
979
+ const snapshot = roleConfigurationSnapshotRef.current;
980
+ if (snapshot) {
981
+ const draft = roleConfigurationSelectionForScope(snapshot, nextScope);
982
+ roleConfigurationDraftRef.current = draft;
983
+ setRoleConfigurationDraft(draft);
984
+ }
985
+ setRoleConfigurationNotice(null);
986
+ setRoleConfigurationError(null);
987
+ continue;
988
+ }
989
+ const selectionDelta = -(rawHistoryDelta(roleChunk)
990
+ + rawPageScrollDelta(roleChunk, CONFIGURABLE_ROLES.length)
991
+ + mouseScrollDelta(roleChunk, 1));
992
+ if (selectionDelta !== 0) {
993
+ const nextIndex = moveRoleConfigurationSelection(roleConfigurationSelectedIndexRef.current, selectionDelta);
994
+ roleConfigurationSelectedIndexRef.current = nextIndex;
995
+ setRoleConfigurationSelectedIndex(nextIndex);
996
+ continue;
997
+ }
998
+ const providerDelta = rawHorizontalArrowDelta(roleChunk)
999
+ || (roleChunk === "]" ? 1 : roleChunk === "[" ? -1 : 0);
1000
+ if (providerDelta !== 0) {
1001
+ const snapshot = roleConfigurationSnapshotRef.current;
1002
+ const draft = roleConfigurationDraftRef.current;
1003
+ if (snapshot && draft) {
1004
+ const nextDraft = cycleRoleProvider(draft, selectedConfigurableRole(roleConfigurationSelectedIndexRef.current), snapshot.providers, providerDelta);
1005
+ roleConfigurationDraftRef.current = nextDraft;
1006
+ setRoleConfigurationDraft(nextDraft);
1007
+ setRoleConfigurationNotice(null);
1008
+ setRoleConfigurationError(null);
1009
+ }
1010
+ continue;
1011
+ }
1012
+ if (roleChunk === "m" || roleChunk === "M") {
1013
+ const role = selectedConfigurableRole(roleConfigurationSelectedIndexRef.current);
1014
+ const value = roleConfigurationDraftRef.current?.[role].model ?? "";
1015
+ const editState = {
1016
+ role,
1017
+ value,
1018
+ cursor: Array.from(value).length
1019
+ };
1020
+ roleModelEditRef.current = editState;
1021
+ setRoleModelEdit(editState);
1022
+ continue;
1023
+ }
1024
+ if (roleChunk === "\r" || roleChunk === "\n") {
1025
+ void applyRoleConfigurationRef.current();
1026
+ return;
1027
+ }
1028
+ if (roleChunk === "x" || roleChunk === "X") {
1029
+ void clearRoleConfigurationRef.current();
1030
+ return;
1031
+ }
1032
+ if (roleChunk === "r" || roleChunk === "R") {
1033
+ const snapshot = roleConfigurationSnapshotRef.current;
1034
+ if (snapshot) {
1035
+ const draft = roleConfigurationSelectionForScope(snapshot, roleConfigurationScopeRef.current);
1036
+ roleConfigurationDraftRef.current = draft;
1037
+ setRoleConfigurationDraft(draft);
1038
+ setRoleConfigurationNotice("Draft restored from saved configuration");
1039
+ setRoleConfigurationError(null);
1040
+ }
1041
+ }
1042
+ }
1043
+ return;
1044
+ }
811
1045
  if (currentView === "status") {
812
1046
  if (isExitShortcut(chunk, {})) {
813
1047
  activeRunControllerRef.current?.abort();
@@ -875,7 +1109,41 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
875
1109
  return;
876
1110
  }
877
1111
  if (sessionCenterModeRef.current === "conversations") {
1112
+ const conversationAction = mainConversationActionRef.current;
1113
+ if (conversationAction) {
1114
+ if (chunk === "\x1b") {
1115
+ updateMainConversationAction(null);
1116
+ setTaskSessionsError(null);
1117
+ return;
1118
+ }
1119
+ if (taskSessionsLoadingRef.current) {
1120
+ return;
1121
+ }
1122
+ if (conversationAction.type === "rename") {
1123
+ const update = applyChatInputChunk(conversationAction.value, chunk, conversationAction.cursor);
1124
+ if (update.submit !== null) {
1125
+ void renameSelectedMainConversationRef.current({
1126
+ ...conversationAction,
1127
+ value: update.submit,
1128
+ cursor: Array.from(update.submit).length
1129
+ });
1130
+ }
1131
+ else {
1132
+ updateMainConversationAction({
1133
+ ...conversationAction,
1134
+ value: update.value,
1135
+ cursor: update.cursor
1136
+ });
1137
+ }
1138
+ return;
1139
+ }
1140
+ if (chunk === "d" || chunk === "D") {
1141
+ void deleteSelectedMainConversationRef.current(conversationAction);
1142
+ }
1143
+ return;
1144
+ }
878
1145
  if (isTaskSessionsShortcut(chunk, {}) || chunk === "\x1b") {
1146
+ updateMainConversationAction(null);
879
1147
  setTaskSessionsError(null);
880
1148
  setTaskSessionsNotice(null);
881
1149
  viewRef.current = taskSessionsReturnViewRef.current;
@@ -890,6 +1158,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
890
1158
  return;
891
1159
  }
892
1160
  if (chunk === "t" || chunk === "T") {
1161
+ updateMainConversationAction(null);
893
1162
  sessionCenterModeRef.current = "tasks";
894
1163
  setSessionCenterMode("tasks");
895
1164
  setTaskSessionsError(null);
@@ -901,6 +1170,63 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
901
1170
  void openMainConversationsRef.current();
902
1171
  return;
903
1172
  }
1173
+ const selectedConversation = mainConversationsRef.current[selectedMainConversationIndexRef.current];
1174
+ if (chunk === "r" || chunk === "R") {
1175
+ if (selectedConversation) {
1176
+ updateMainConversationAction({
1177
+ type: "rename",
1178
+ conversationId: selectedConversation.id,
1179
+ title: selectedConversation.title,
1180
+ value: selectedConversation.title,
1181
+ cursor: Array.from(selectedConversation.title).length
1182
+ });
1183
+ setTaskSessionsError(null);
1184
+ setTaskSessionsNotice(null);
1185
+ }
1186
+ return;
1187
+ }
1188
+ if (chunk === "a" || chunk === "A") {
1189
+ void archiveSelectedMainConversationRef.current();
1190
+ return;
1191
+ }
1192
+ if (chunk === "d" || chunk === "D") {
1193
+ if (selectedConversation) {
1194
+ if (selectedConversation.current) {
1195
+ setTaskSessionsError("Restore another Main conversation before deleting the current one");
1196
+ }
1197
+ else {
1198
+ updateMainConversationAction({
1199
+ type: "delete",
1200
+ conversationId: selectedConversation.id,
1201
+ title: selectedConversation.title
1202
+ });
1203
+ setTaskSessionsError(null);
1204
+ setTaskSessionsNotice(null);
1205
+ }
1206
+ }
1207
+ return;
1208
+ }
1209
+ if (chunk === "e" || chunk === "E") {
1210
+ void exportSelectedMainConversationRef.current();
1211
+ return;
1212
+ }
1213
+ if (chunk === "h" || chunk === "H") {
1214
+ const includeArchived = !mainConversationsIncludeArchivedRef.current;
1215
+ mainConversationsIncludeArchivedRef.current = includeArchived;
1216
+ setMainConversationsIncludeArchived(includeArchived);
1217
+ setTaskSessionsError(null);
1218
+ setTaskSessionsNotice(null);
1219
+ void (async () => {
1220
+ await refreshMainConversationsRef.current(selectedConversation?.id);
1221
+ if (sessionCenterModeRef.current === "conversations"
1222
+ && mainConversationsIncludeArchivedRef.current === includeArchived) {
1223
+ setTaskSessionsNotice(includeArchived
1224
+ ? "Archived conversations shown"
1225
+ : "Archived conversations hidden");
1226
+ }
1227
+ })();
1228
+ return;
1229
+ }
904
1230
  if (chunk === "\r" || chunk === "\n") {
905
1231
  void restoreSelectedMainConversationRef.current();
906
1232
  return;
@@ -1729,6 +2055,15 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
1729
2055
  .join("\n")
1730
2056
  };
1731
2057
  }
2058
+ if (currentView === "roles") {
2059
+ const roles = roleConfigurationDraftRef.current;
2060
+ return {
2061
+ label: "role configuration",
2062
+ text: roles
2063
+ ? CONFIGURABLE_ROLES.map((role) => `${role} · ${roles[role].engine} · ${roles[role].model || "default"}`).join("\n")
2064
+ : ""
2065
+ };
2066
+ }
1732
2067
  if (currentView === "workers") {
1733
2068
  return {
1734
2069
  label: "workers",
@@ -2075,6 +2410,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2075
2410
  cwd,
2076
2411
  taskId: target.taskId,
2077
2412
  route: followUpRoute?.route,
2413
+ roleSelection: followUpRoute?.roleSelection,
2078
2414
  ...callbacks
2079
2415
  })
2080
2416
  : target.kind === "task-question"
@@ -2083,6 +2419,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2083
2419
  cwd,
2084
2420
  taskId: target.taskId,
2085
2421
  route: followUpRoute?.route,
2422
+ roleSelection: followUpRoute?.roleSelection,
2086
2423
  ...callbacks
2087
2424
  })
2088
2425
  : await orchestrator.handleRequest({
@@ -2097,6 +2434,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2097
2434
  setCanRetryTask(nextMemory.activeTaskId
2098
2435
  ? await orchestrator.canRetryTask(nextMemory.activeTaskId)
2099
2436
  : false);
2437
+ await refreshRoleConfigurationState(nextMemory.activeTaskId);
2100
2438
  setRouteAnnouncement(null);
2101
2439
  await appendVisibleMessage({ from: "system", text: result.summary }, nextMemory.activeTaskId ?? undefined);
2102
2440
  if (result.mode === "complex" && parseTaskResultSummary(result.summary)) {
@@ -2109,6 +2447,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2109
2447
  if (retryTaskId) {
2110
2448
  setCanRetryTask(await orchestrator.canRetryTask(retryTaskId));
2111
2449
  }
2450
+ await refreshRoleConfigurationState(retryTaskId);
2112
2451
  await appendVisibleMessage({
2113
2452
  from: "system",
2114
2453
  text: isAbortError(error) ? "cancelled · request stopped" : error instanceof Error ? error.message : String(error)
@@ -2156,6 +2495,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2156
2495
  setActiveTaskId(taskId);
2157
2496
  setActiveMode("complex");
2158
2497
  setCanRetryTask(false);
2498
+ await refreshRoleConfigurationState(taskId);
2159
2499
  setRouteAnnouncement(null);
2160
2500
  await appendVisibleMessage({ from: "system", text: result.summary }, taskId);
2161
2501
  if (parseTaskResultSummary(result.summary)) {
@@ -2165,6 +2505,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2165
2505
  catch (error) {
2166
2506
  setRouteAnnouncement(null);
2167
2507
  setCanRetryTask(await orchestrator.canRetryTask(taskId));
2508
+ await refreshRoleConfigurationState(taskId);
2168
2509
  await appendVisibleMessage({
2169
2510
  from: "system",
2170
2511
  text: isAbortError(error) ? "cancelled · retry stopped" : error instanceof Error ? error.message : String(error)
@@ -2193,6 +2534,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2193
2534
  return;
2194
2535
  }
2195
2536
  resetActiveTaskUiForMainConversation();
2537
+ await refreshRoleConfigurationState(null);
2196
2538
  await appendVisibleMessage({ from: "system", text: "new conversation · ready" });
2197
2539
  }
2198
2540
  function resetActiveTaskUiForMainConversation() {
@@ -2285,6 +2627,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2285
2627
  setTaskSessionDetailSelectedWorkerIndex(0);
2286
2628
  setTaskSessionDetailNotice(null);
2287
2629
  updateTaskSessionAction(null);
2630
+ updateMainConversationAction(null);
2288
2631
  sessionCenterModeRef.current = "tasks";
2289
2632
  setSessionCenterMode("tasks");
2290
2633
  await refreshTaskSessions(activeTaskIdRef.current);
@@ -2347,24 +2690,32 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2347
2690
  taskSessionDetailsRef.current = null;
2348
2691
  setTaskSessionDetails(null);
2349
2692
  updateTaskSessionAction(null);
2693
+ updateMainConversationAction(null);
2350
2694
  setTaskSessionsError(null);
2351
2695
  setTaskSessionsNotice(null);
2352
- taskSessionsLoadingRef.current = true;
2353
- setTaskSessionsLoading(true);
2696
+ await refreshMainConversations(previousId);
2697
+ }
2698
+ async function refreshMainConversations(preferredConversationId, manageLoading = true) {
2699
+ if (manageLoading) {
2700
+ taskSessionsLoadingRef.current = true;
2701
+ setTaskSessionsLoading(true);
2702
+ }
2354
2703
  const sequence = taskSessionsLoadSequenceRef.current + 1;
2355
2704
  taskSessionsLoadSequenceRef.current = sequence;
2356
2705
  try {
2357
2706
  if (!loadMainConversations) {
2358
2707
  throw new Error("Main conversations are unavailable");
2359
2708
  }
2360
- const conversations = await loadMainConversations();
2709
+ const conversations = await loadMainConversations({
2710
+ includeArchived: mainConversationsIncludeArchivedRef.current
2711
+ });
2361
2712
  if (taskSessionsLoadSequenceRef.current !== sequence) {
2362
2713
  return;
2363
2714
  }
2364
2715
  mainConversationsRef.current = conversations;
2365
2716
  setMainConversations(conversations);
2366
- const preservedIndex = previousId !== undefined
2367
- ? conversations.findIndex((conversation) => conversation.id === previousId)
2717
+ const preservedIndex = preferredConversationId !== undefined
2718
+ ? conversations.findIndex((conversation) => conversation.id === preferredConversationId)
2368
2719
  : -1;
2369
2720
  const currentIndex = conversations.findIndex((conversation) => conversation.current);
2370
2721
  const selectedIndex = preservedIndex >= 0
@@ -2381,7 +2732,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2381
2732
  }
2382
2733
  }
2383
2734
  finally {
2384
- if (taskSessionsLoadSequenceRef.current === sequence) {
2735
+ if (manageLoading && taskSessionsLoadSequenceRef.current === sequence) {
2385
2736
  taskSessionsLoadingRef.current = false;
2386
2737
  setTaskSessionsLoading(false);
2387
2738
  }
@@ -2405,8 +2756,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2405
2756
  }
2406
2757
  const result = await activateMainConversation(selected.id);
2407
2758
  if (!result.changed) {
2408
- await openMainConversations();
2409
2759
  setTaskSessionsNotice(`Already current · ${result.conversation.title}`);
2760
+ await refreshMainConversations(result.conversation.id, false);
2410
2761
  return;
2411
2762
  }
2412
2763
  await activateTaskSession?.(null);
@@ -2424,6 +2775,79 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2424
2775
  setTaskSessionsLoading(false);
2425
2776
  }
2426
2777
  }
2778
+ function updateMainConversationAction(next) {
2779
+ mainConversationActionRef.current = next;
2780
+ setMainConversationAction(next);
2781
+ }
2782
+ async function runMainConversationOperation(run, notice, preferredConversationId) {
2783
+ if (taskSessionsLoadingRef.current) {
2784
+ return;
2785
+ }
2786
+ taskSessionsLoadingRef.current = true;
2787
+ setTaskSessionsLoading(true);
2788
+ setTaskSessionsError(null);
2789
+ setTaskSessionsNotice(null);
2790
+ try {
2791
+ const result = await run();
2792
+ updateMainConversationAction(null);
2793
+ await refreshMainConversations(preferredConversationId, false);
2794
+ taskSessionsLoadingRef.current = false;
2795
+ setTaskSessionsLoading(false);
2796
+ setTaskSessionsNotice(typeof notice === "function" ? notice(result) : notice);
2797
+ }
2798
+ catch (error) {
2799
+ taskSessionsLoadingRef.current = false;
2800
+ setTaskSessionsLoading(false);
2801
+ setTaskSessionsError(error instanceof Error ? error.message : String(error));
2802
+ }
2803
+ finally {
2804
+ taskSessionsLoadingRef.current = false;
2805
+ setTaskSessionsLoading(false);
2806
+ }
2807
+ }
2808
+ async function renameSelectedMainConversation(action) {
2809
+ await runMainConversationOperation(async () => {
2810
+ if (!renameMainConversation) {
2811
+ throw new Error("Main conversation rename is unavailable");
2812
+ }
2813
+ const title = action.value.trim();
2814
+ await renameMainConversation(action.conversationId, title);
2815
+ return title;
2816
+ }, (title) => `Renamed · ${title}`, action.conversationId);
2817
+ }
2818
+ async function archiveSelectedMainConversation() {
2819
+ const selected = mainConversationsRef.current[selectedMainConversationIndexRef.current];
2820
+ if (!selected) {
2821
+ return;
2822
+ }
2823
+ const archived = !selected.archivedAt;
2824
+ await runMainConversationOperation(async () => {
2825
+ if (!setMainConversationArchived) {
2826
+ throw new Error("Main conversation archive is unavailable");
2827
+ }
2828
+ await setMainConversationArchived(selected.id, archived);
2829
+ }, archived ? `Archived · ${selected.title}` : `Unarchived · ${selected.title}`, selected.id);
2830
+ }
2831
+ async function deleteSelectedMainConversation(action) {
2832
+ await runMainConversationOperation(async () => {
2833
+ if (!deleteMainConversation) {
2834
+ throw new Error("Main conversation deletion is unavailable");
2835
+ }
2836
+ await deleteMainConversation(action.conversationId);
2837
+ }, `Deleted · ${action.title}`);
2838
+ }
2839
+ async function exportSelectedMainConversation() {
2840
+ const selected = mainConversationsRef.current[selectedMainConversationIndexRef.current];
2841
+ if (!selected) {
2842
+ return;
2843
+ }
2844
+ await runMainConversationOperation(async () => {
2845
+ if (!exportMainConversation) {
2846
+ throw new Error("Main conversation export is unavailable");
2847
+ }
2848
+ return exportMainConversation(selected.id);
2849
+ }, (path) => `Exported · ${path}`, selected.id);
2850
+ }
2427
2851
  function updateTaskSessionAction(next) {
2428
2852
  taskSessionActionRef.current = next;
2429
2853
  setTaskSessionAction(next);
@@ -2876,6 +3300,153 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2876
3300
  setAttachError(null);
2877
3301
  setView("workers");
2878
3302
  }
3303
+ async function refreshRoleConfigurationState(taskId) {
3304
+ try {
3305
+ const snapshot = typeof orchestrator.roleConfigurationSnapshot === "function"
3306
+ ? await orchestrator.roleConfigurationSnapshot(taskId)
3307
+ : fallbackRoleConfigurationSnapshot(config);
3308
+ roleConfigurationSnapshotRef.current = snapshot;
3309
+ setRoleConfigurationSnapshot(snapshot);
3310
+ if (viewRef.current === "roles") {
3311
+ const draft = roleConfigurationSelectionForScope(snapshot, roleConfigurationScopeRef.current);
3312
+ roleConfigurationDraftRef.current = draft;
3313
+ setRoleConfigurationDraft(draft);
3314
+ }
3315
+ }
3316
+ catch (error) {
3317
+ setRoleConfigurationError(error instanceof Error ? error.message : String(error));
3318
+ }
3319
+ }
3320
+ async function openRoleConfiguration() {
3321
+ const currentView = viewRef.current;
3322
+ if (busyRef.current || currentView === "native" || currentView === "workspace") {
3323
+ return;
3324
+ }
3325
+ const returnView = currentView === "roles" ? roleConfigurationReturnViewRef.current : currentView;
3326
+ roleConfigurationReturnViewRef.current = returnView;
3327
+ const scope = roleConfigurationScopeRef.current === "task" && !activeTaskIdRef.current
3328
+ ? "next"
3329
+ : roleConfigurationScopeRef.current;
3330
+ roleConfigurationScopeRef.current = scope;
3331
+ setRoleConfigurationScope(scope);
3332
+ roleModelEditRef.current = null;
3333
+ setRoleModelEdit(null);
3334
+ setRoleConfigurationNotice(null);
3335
+ setRoleConfigurationError(null);
3336
+ roleConfigurationLoadingRef.current = true;
3337
+ setRoleConfigurationLoading(true);
3338
+ viewRef.current = "roles";
3339
+ setView("roles");
3340
+ const sequence = roleConfigurationLoadSequenceRef.current + 1;
3341
+ roleConfigurationLoadSequenceRef.current = sequence;
3342
+ try {
3343
+ const snapshot = typeof orchestrator.roleConfigurationSnapshot === "function"
3344
+ ? await orchestrator.roleConfigurationSnapshot(activeTaskIdRef.current)
3345
+ : fallbackRoleConfigurationSnapshot(config);
3346
+ if (!mountedRef.current || roleConfigurationLoadSequenceRef.current !== sequence) {
3347
+ return;
3348
+ }
3349
+ roleConfigurationSnapshotRef.current = snapshot;
3350
+ setRoleConfigurationSnapshot(snapshot);
3351
+ const draft = roleConfigurationSelectionForScope(snapshot, scope);
3352
+ roleConfigurationDraftRef.current = draft;
3353
+ setRoleConfigurationDraft(draft);
3354
+ }
3355
+ catch (error) {
3356
+ if (mountedRef.current && roleConfigurationLoadSequenceRef.current === sequence) {
3357
+ setRoleConfigurationError(error instanceof Error ? error.message : String(error));
3358
+ }
3359
+ }
3360
+ finally {
3361
+ if (mountedRef.current && roleConfigurationLoadSequenceRef.current === sequence) {
3362
+ roleConfigurationLoadingRef.current = false;
3363
+ setRoleConfigurationLoading(false);
3364
+ }
3365
+ }
3366
+ }
3367
+ async function applyRoleConfiguration() {
3368
+ const draft = roleConfigurationDraftRef.current;
3369
+ if (!draft || roleConfigurationSavingRef.current || roleConfigurationLoadingRef.current) {
3370
+ return;
3371
+ }
3372
+ const scope = roleConfigurationScopeRef.current;
3373
+ const taskId = activeTaskIdRef.current;
3374
+ if (scope === "task" && !taskId) {
3375
+ setRoleConfigurationError("No active Task · choose next request or future requests");
3376
+ return;
3377
+ }
3378
+ roleConfigurationSavingRef.current = true;
3379
+ setRoleConfigurationSaving(true);
3380
+ setRoleConfigurationNotice(null);
3381
+ setRoleConfigurationError(null);
3382
+ try {
3383
+ if (typeof orchestrator.updateRoleConfiguration !== "function") {
3384
+ throw new Error("Role configuration is unavailable in this runtime");
3385
+ }
3386
+ const snapshot = await orchestrator.updateRoleConfiguration({
3387
+ scope,
3388
+ roles: draft,
3389
+ taskId
3390
+ });
3391
+ roleConfigurationSnapshotRef.current = snapshot;
3392
+ setRoleConfigurationSnapshot(snapshot);
3393
+ const saved = roleConfigurationSelectionForScope(snapshot, scope);
3394
+ roleConfigurationDraftRef.current = saved;
3395
+ setRoleConfigurationDraft(saved);
3396
+ setRoleConfigurationNotice(scope === "next"
3397
+ ? "Saved · the next request will consume this matrix once"
3398
+ : scope === "task"
3399
+ ? "Saved · current Task retries and follow-ups will use this matrix"
3400
+ : "Saved · future requests now use this default matrix");
3401
+ if (scope === "task" && taskId) {
3402
+ void refreshCollaborationTimelineRef.current(false);
3403
+ }
3404
+ }
3405
+ catch (error) {
3406
+ setRoleConfigurationError(error instanceof Error ? error.message : String(error));
3407
+ }
3408
+ finally {
3409
+ roleConfigurationSavingRef.current = false;
3410
+ setRoleConfigurationSaving(false);
3411
+ }
3412
+ }
3413
+ async function clearRoleConfiguration() {
3414
+ if (roleConfigurationSavingRef.current || roleConfigurationLoadingRef.current) {
3415
+ return;
3416
+ }
3417
+ const scope = roleConfigurationScopeRef.current;
3418
+ const taskId = activeTaskIdRef.current;
3419
+ if (scope === "task" && !taskId) {
3420
+ setRoleConfigurationError("No active Task · choose next request or future requests");
3421
+ return;
3422
+ }
3423
+ roleConfigurationSavingRef.current = true;
3424
+ setRoleConfigurationSaving(true);
3425
+ setRoleConfigurationNotice(null);
3426
+ setRoleConfigurationError(null);
3427
+ try {
3428
+ if (typeof orchestrator.clearRoleConfiguration !== "function") {
3429
+ throw new Error("Role configuration is unavailable in this runtime");
3430
+ }
3431
+ const snapshot = await orchestrator.clearRoleConfiguration(scope, taskId);
3432
+ roleConfigurationSnapshotRef.current = snapshot;
3433
+ setRoleConfigurationSnapshot(snapshot);
3434
+ const inherited = roleConfigurationSelectionForScope(snapshot, scope);
3435
+ roleConfigurationDraftRef.current = inherited;
3436
+ setRoleConfigurationDraft(inherited);
3437
+ setRoleConfigurationNotice(scope === "future" ? "Reset · future requests inherit config.toml" : "Reset · scope now inherits future defaults");
3438
+ if (scope === "task" && taskId) {
3439
+ void refreshCollaborationTimelineRef.current(false);
3440
+ }
3441
+ }
3442
+ catch (error) {
3443
+ setRoleConfigurationError(error instanceof Error ? error.message : String(error));
3444
+ }
3445
+ finally {
3446
+ roleConfigurationSavingRef.current = false;
3447
+ setRoleConfigurationSaving(false);
3448
+ }
3449
+ }
2879
3450
  function openWorkspacePicker() {
2880
3451
  const currentView = viewRef.current;
2881
3452
  if (busyRef.current || !switchWorkspace || currentView === "native" || currentView === "workspace") {
@@ -2907,15 +3478,27 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2907
3478
  if (view === "workspace") {
2908
3479
  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) }) }));
2909
3480
  }
2910
- 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"
2911
- ? { type: "rename", value: taskSessionAction.value, cursor: taskSessionAction.cursor }
2912
- : taskSessionAction?.type === "delete"
2913
- ? { type: "delete", title: taskSessionAction.title }
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"
3481
+ 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"
3482
+ ? mainConversationAction?.type === "rename"
3483
+ ? { type: "rename", value: mainConversationAction.value, cursor: mainConversationAction.cursor }
3484
+ : mainConversationAction?.type === "delete"
3485
+ ? { type: "delete", title: mainConversationAction.title }
3486
+ : null
3487
+ : taskSessionAction?.type === "rename"
3488
+ ? { type: "rename", value: taskSessionAction.value, cursor: taskSessionAction.cursor }
3489
+ : taskSessionAction?.type === "delete"
3490
+ ? { type: "delete", title: taskSessionAction.title }
3491
+ : null, taskSessionsIncludeArchived: sessionCenterMode === "conversations"
3492
+ ? mainConversationsIncludeArchived
3493
+ : 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, roleScope: roleConfigurationScope, roleEditingModel: roleModelEdit, roleCanApply: roleConfigurationScope !== "task" || Boolean(activeTaskId), roleSaving: roleConfigurationSaving, roleHasOverride: roleConfigurationHasOverride, value: workerSearch.open && view === "worker"
2915
3494
  ? workerSearch.query
2916
- : view === "native" || view === "router" || view === "workers" || view === "features" || view === "sessions" || view === "collaboration" || view === "status" ? "" : input, cursor: workerSearch.open && view === "worker"
3495
+ : view === "native" || view === "router" || view === "workers" || view === "features" || view === "sessions" || view === "collaboration" || view === "status" || view === "roles" ? "" : input, cursor: workerSearch.open && view === "worker"
2917
3496
  ? workerSearch.cursor
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"
3497
+ : view === "chat" ? inputCursor : undefined, terminalWidth: terminalWidth, onChange: view === "native" ? setNativeInput : setInput, onSubmit: view === "native" ? undefined : submit }), error: attachError, children: view === "roles" ? (_jsx(RoleConfigurationView, { snapshot: roleConfigurationSnapshot, draft: roleConfigurationDraft, scope: roleConfigurationScope, selectedRoleIndex: roleConfigurationSelectedIndex, loading: roleConfigurationLoading, saving: roleConfigurationSaving, notice: roleConfigurationNotice, error: roleConfigurationError, hasActiveTask: Boolean(activeTaskId), height: contentHeight, terminalWidth: terminalWidth })) : 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, roleSelection: activeRoleSelection, 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"
3498
+ ? { type: "rename", title: mainConversationAction.title }
3499
+ : mainConversationAction?.type === "delete"
3500
+ ? { type: "delete", title: mainConversationAction.title }
3501
+ : 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"
2919
3502
  ? { type: "rename", title: taskSessionAction.title }
2920
3503
  : taskSessionAction?.type === "delete"
2921
3504
  ? { type: "delete", title: taskSessionAction.title }
@@ -2948,6 +3531,22 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
2948
3531
  }
2949
3532
  } })) }));
2950
3533
  }
3534
+ function fallbackRoleConfigurationSnapshot(config) {
3535
+ const roles = configuredRoleSelection(config);
3536
+ return {
3537
+ baseline: roles,
3538
+ future: configuredRoleSelection(config),
3539
+ next: null,
3540
+ task: null,
3541
+ activeTurn: null,
3542
+ providers: workerProviders(config).map(({ id, config: provider }) => ({
3543
+ id,
3544
+ model: provider.model.name,
3545
+ modelProvider: provider.model.provider,
3546
+ assignable: provider.assignable
3547
+ }))
3548
+ };
3549
+ }
2951
3550
  function readTerminalSize() {
2952
3551
  return {
2953
3552
  columns: Math.max(1, Math.trunc(process.stdout.columns || 120)),