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/README.md +22 -7
- package/dist/bootstrap.js +8 -1
- package/dist/cli.js +18 -3
- package/dist/core/role-configuration.js +238 -0
- package/dist/core/session-manager.js +287 -4
- package/dist/domain/schemas.js +3 -1
- package/dist/orchestrator/orchestrator.js +135 -11
- package/dist/tui/App.js +615 -16
- package/dist/tui/AppShell.js +7 -1
- package/dist/tui/InputBar.js +35 -5
- package/dist/tui/MainConversationsView.js +28 -8
- package/dist/tui/RoleConfigurationView.js +96 -0
- package/dist/tui/StatusDetailView.js +9 -6
- package/dist/tui/keyboard.js +11 -0
- package/dist/tui/role-configuration-state.js +74 -0
- package/dist/version.js +1 -1
- package/dist/workers/native-attach.js +5 -1
- package/package.json +1 -1
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
|
-
|
|
2353
|
-
|
|
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 =
|
|
2367
|
-
? conversations.findIndex((conversation) => conversation.id ===
|
|
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:
|
|
2911
|
-
?
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
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,
|
|
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)),
|