parallel-codex-tui 0.1.4 → 0.1.6
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/.parallel-codex/config.example.toml +46 -0
- package/README.md +96 -19
- package/dist/cli-startup-preflight.js +18 -0
- package/dist/cli-startup-recovery.js +13 -1
- package/dist/cli.js +19 -7
- package/dist/core/clipboard.js +97 -0
- package/dist/core/collaboration-timeline.js +9 -2
- package/dist/core/config.js +161 -103
- package/dist/core/router.js +1 -1
- package/dist/core/session-index.js +234 -61
- package/dist/core/session-manager.js +25 -1
- package/dist/core/task-session-details.js +175 -0
- package/dist/core/task-state-machine.js +10 -9
- package/dist/doctor.js +58 -39
- package/dist/domain/schemas.js +15 -1
- package/dist/orchestrator/collaboration-channel.js +35 -3
- package/dist/orchestrator/final-acceptance.js +86 -0
- package/dist/orchestrator/orchestrator.js +405 -69
- package/dist/orchestrator/prompts.js +42 -3
- package/dist/orchestrator/workspace-sandbox.js +16 -0
- package/dist/tui/App.js +514 -56
- package/dist/tui/AppShell.js +9 -3
- package/dist/tui/FeatureBoardView.js +7 -2
- package/dist/tui/InputBar.js +87 -15
- package/dist/tui/StatusBar.js +1 -1
- package/dist/tui/StatusDetailView.js +164 -0
- package/dist/tui/TaskSessionDetailView.js +222 -0
- package/dist/tui/TaskSessionsView.js +5 -2
- package/dist/tui/WorkerOutputView.js +6 -1
- package/dist/tui/WorkerOverviewView.js +23 -4
- package/dist/tui/keyboard.js +6 -0
- package/dist/tui/status-line.js +42 -0
- package/dist/version.js +1 -1
- package/dist/workers/capabilities.js +4 -3
- package/dist/workers/live-probe.js +4 -3
- package/dist/workers/mock-adapter.js +37 -5
- package/dist/workers/native-attach.js +32 -17
- package/dist/workers/process-adapter.js +2 -0
- package/dist/workers/provider.js +26 -0
- package/dist/workers/registry.js +12 -22
- package/package.json +7 -1
package/dist/tui/App.js
CHANGED
|
@@ -3,9 +3,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
3
3
|
import { basename } from "node:path";
|
|
4
4
|
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
|
5
5
|
import { Lexer } from "marked";
|
|
6
|
+
import { copyTextToClipboard } from "../core/clipboard.js";
|
|
6
7
|
import { readJson } from "../core/file-store.js";
|
|
7
8
|
import { WorkerStatusSchema } from "../domain/schemas.js";
|
|
8
|
-
import { effectiveWorkerWatchdog, formatRoutePendingStatus,
|
|
9
|
+
import { effectiveWorkerWatchdog, formatRoutePendingStatus, formatRoutePendingSummaryStatus, formatRouteStatus, formatRouteSummaryStatus, formatStatusLine, formatWorkerRuntimeStatus } from "./status-line.js";
|
|
9
10
|
import { applyChatInputChunk, insertChatPaste } from "./chat-input.js";
|
|
10
11
|
import { chatRequestHistory, navigateChatDraftHistory } from "./chat-history.js";
|
|
11
12
|
import { createChatPasteDecoder } from "./chat-paste.js";
|
|
@@ -18,7 +19,7 @@ import { TerminalOutput } from "./TerminalOutput.js";
|
|
|
18
19
|
import { NativeTerminalScreen } from "./terminal-screen.js";
|
|
19
20
|
import { WorkerOutputView } from "./WorkerOutputView.js";
|
|
20
21
|
import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
|
|
21
|
-
import { isAttachShortcut, isExitShortcut, isLogsShortcut, isNewTaskShortcut, isRouterDiagnosticsShortcut, isTaskResultShortcut, isTaskSessionsShortcut, isWorkerOverviewShortcut, isWorkerSearchShortcut, isWorkspaceShortcut, mouseScrollDelta, rawHistoryDelta, rawPageScrollDelta, scrollDelta, workerLogJumpKind } from "./keyboard.js";
|
|
22
|
+
import { isAttachShortcut, isCopyShortcut, isExitShortcut, isLogsShortcut, isNewTaskShortcut, isRouterDiagnosticsShortcut, isStatusDetailsShortcut, isTaskResultShortcut, isTaskSessionsShortcut, isWorkerOverviewShortcut, isWorkerSearchShortcut, isWorkspaceShortcut, mouseScrollDelta, rawHistoryDelta, rawPageScrollDelta, scrollDelta, workerLogJumpKind } from "./keyboard.js";
|
|
22
23
|
import { createRawInputDecoder, tokenizeRawInput } from "./raw-input-decoder.js";
|
|
23
24
|
import { decodeHtmlEntities } from "./markdown-text.js";
|
|
24
25
|
import { configureTuiTheme, TUI_THEME } from "./theme.js";
|
|
@@ -28,8 +29,11 @@ import { moveWorkerSelection, WorkerOverviewView } from "./WorkerOverviewView.js
|
|
|
28
29
|
import { FeatureBoardView, moveFeatureBoardSelection } from "./FeatureBoardView.js";
|
|
29
30
|
import { CollaborationTimelineView, collaborationSelectionScrollOffset, collaborationTimelineEvents, moveCollaborationEventSelection, nextCollaborationFeatureIndex } from "./CollaborationTimelineView.js";
|
|
30
31
|
import { moveTaskSessionSelection, TaskSessionsView } from "./TaskSessionsView.js";
|
|
32
|
+
import { moveTaskSessionDetailSelection, TaskSessionDetailView } from "./TaskSessionDetailView.js";
|
|
31
33
|
import { latestTaskResultMessageIndex, parseTaskResultSummary } from "./task-result.js";
|
|
32
|
-
import { buildNativeAttachLaunch, startNativeAttachProcess } from "../workers/native-attach.js";
|
|
34
|
+
import { buildNativeAttachLaunch, buildNativeForkLaunch, startNativeAttachProcess } from "../workers/native-attach.js";
|
|
35
|
+
import { assignableWorkerProviderIds, workerProviderLabel, workerProviders } from "../workers/provider.js";
|
|
36
|
+
import { StatusDetailView } from "./StatusDetailView.js";
|
|
33
37
|
export function routeDecisionChatMessage(route) {
|
|
34
38
|
return `route · ${route.mode} · ${route.source ?? "router"}\n${route.reason.trim()}`;
|
|
35
39
|
}
|
|
@@ -44,7 +48,7 @@ const EMPTY_WORKER_NAVIGATION_TARGETS = {
|
|
|
44
48
|
errorOffsets: [],
|
|
45
49
|
diffOffsets: []
|
|
46
50
|
};
|
|
47
|
-
export function App({ config, orchestrator, cwd, initialTaskId = null, initialRoute = null, initialWorkers, initialCanRetryTask = false, initialMessages = [], persistChatMessage, workspaceChoices = [], switchWorkspace, loadRouterDiagnostics, loadTaskSessions, renameTaskSession, setTaskSessionArchived, deleteTaskSession, exportTaskSession, loadCollaborationTimeline, activateTaskSession, prepareNativeAttach, startNativeAttach, shutdownSignal }) {
|
|
51
|
+
export function App({ config, orchestrator, cwd, initialTaskId = null, initialRoute = null, initialWorkers, initialCanRetryTask = false, initialMessages = [], persistChatMessage, workspaceChoices = [], switchWorkspace, loadRouterDiagnostics, loadTaskSessions, loadTaskSessionDetails, renameTaskSession, setTaskSessionArchived, deleteTaskSession, exportTaskSession, loadCollaborationTimeline, activateTaskSession, prepareNativeAttach, prepareNativeFork, startNativeAttach, copyToClipboard = copyTextToClipboard, shutdownSignal }) {
|
|
48
52
|
configureTuiTheme({
|
|
49
53
|
theme: config.ui.theme,
|
|
50
54
|
colors: config.ui.colors
|
|
@@ -70,6 +74,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
70
74
|
const [activeMode, setActiveMode] = useState(initialTaskId ? "complex" : null);
|
|
71
75
|
const [canRetryTask, setCanRetryTask] = useState(initialCanRetryTask);
|
|
72
76
|
const [attachError, setAttachError] = useState(null);
|
|
77
|
+
const [clipboardNotice, setClipboardNotice] = useState(null);
|
|
73
78
|
const [nativeInput, setNativeInput] = useState("");
|
|
74
79
|
const [workerScrollOffset, setWorkerScrollOffset] = useState(0);
|
|
75
80
|
const [workerSearch, setWorkerSearch] = useState({
|
|
@@ -95,9 +100,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
95
100
|
const [taskSessionsNotice, setTaskSessionsNotice] = useState(null);
|
|
96
101
|
const [taskSessionsIncludeArchived, setTaskSessionsIncludeArchived] = useState(false);
|
|
97
102
|
const [taskSessionAction, setTaskSessionAction] = useState(null);
|
|
103
|
+
const [taskSessionDetails, setTaskSessionDetails] = useState(null);
|
|
104
|
+
const [taskSessionDetailSelectedWorkerIndex, setTaskSessionDetailSelectedWorkerIndex] = useState(0);
|
|
105
|
+
const [taskSessionDetailNotice, setTaskSessionDetailNotice] = useState(null);
|
|
98
106
|
const [collaborationTimeline, setCollaborationTimeline] = useState(null);
|
|
99
107
|
const [featureBoardSelectedIndex, setFeatureBoardSelectedIndex] = useState(0);
|
|
100
108
|
const [featureCancelPrompt, setFeatureCancelPrompt] = useState(null);
|
|
109
|
+
const [featureAssignmentPrompt, setFeatureAssignmentPrompt] = useState(null);
|
|
101
110
|
const [featureBoardNotice, setFeatureBoardNotice] = useState(null);
|
|
102
111
|
const [collaborationLoading, setCollaborationLoading] = useState(false);
|
|
103
112
|
const [collaborationError, setCollaborationError] = useState(null);
|
|
@@ -113,6 +122,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
113
122
|
const mountedRef = useRef(true);
|
|
114
123
|
const nativeAttachRef = useRef(nativeAttach);
|
|
115
124
|
const nativeAttachRequestSequenceRef = useRef(0);
|
|
125
|
+
const clipboardNoticeTimerRef = useRef(null);
|
|
126
|
+
const copyTextByViewRef = useRef({ chat: "", worker: "" });
|
|
116
127
|
const messagesRef = useRef([...initialMessages]);
|
|
117
128
|
const activeRunControllerRef = useRef(null);
|
|
118
129
|
const activeTaskIdRef = useRef(initialTaskId);
|
|
@@ -121,6 +132,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
121
132
|
const inputCursorRef = useRef(inputCursor);
|
|
122
133
|
const viewRef = useRef(view);
|
|
123
134
|
const busyRef = useRef(busy);
|
|
135
|
+
const canRetryTaskRef = useRef(initialCanRetryTask);
|
|
124
136
|
const routeFallbackPromptRef = useRef(null);
|
|
125
137
|
const routeFallbackResolverRef = useRef(null);
|
|
126
138
|
const workersRef = useRef(workers);
|
|
@@ -137,9 +149,12 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
137
149
|
const taskSessionsLoadingRef = useRef(taskSessionsLoading);
|
|
138
150
|
const taskSessionsIncludeArchivedRef = useRef(taskSessionsIncludeArchived);
|
|
139
151
|
const taskSessionActionRef = useRef(taskSessionAction);
|
|
152
|
+
const taskSessionDetailsRef = useRef(null);
|
|
153
|
+
const taskSessionDetailSelectedWorkerIndexRef = useRef(0);
|
|
140
154
|
const collaborationTimelineRef = useRef(null);
|
|
141
155
|
const featureBoardSelectedIndexRef = useRef(0);
|
|
142
156
|
const featureCancelPromptRef = useRef(null);
|
|
157
|
+
const featureAssignmentPromptRef = useRef(null);
|
|
143
158
|
const collaborationFeatureIndexRef = useRef(-1);
|
|
144
159
|
const collaborationSelectedEventIdRef = useRef(null);
|
|
145
160
|
const collaborationDetailOpenRef = useRef(false);
|
|
@@ -157,6 +172,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
157
172
|
const openTaskSessionsRef = useRef(openTaskSessions);
|
|
158
173
|
const openFeatureBoardRef = useRef(openFeatureBoard);
|
|
159
174
|
const confirmFeatureCancellationRef = useRef(confirmFeatureCancellation);
|
|
175
|
+
const reassignSelectedFeatureRef = useRef(reassignSelectedFeature);
|
|
160
176
|
const openCollaborationTimelineRef = useRef(openCollaborationTimeline);
|
|
161
177
|
const refreshCollaborationTimelineRef = useRef(refreshCollaborationTimeline);
|
|
162
178
|
const activateSelectedTaskSessionRef = useRef(activateSelectedTaskSession);
|
|
@@ -165,11 +181,16 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
165
181
|
const archiveSelectedTaskSessionRef = useRef(archiveSelectedTaskSession);
|
|
166
182
|
const deleteSelectedTaskSessionRef = useRef(deleteSelectedTaskSession);
|
|
167
183
|
const exportSelectedTaskSessionRef = useRef(exportSelectedTaskSession);
|
|
184
|
+
const openSelectedTaskSessionDetailsRef = useRef(openSelectedTaskSessionDetails);
|
|
185
|
+
const refreshTaskSessionDetailsRef = useRef(refreshTaskSessionDetails);
|
|
186
|
+
const openTaskSessionDetailWorkerRef = useRef(openTaskSessionDetailWorker);
|
|
187
|
+
const copyVisibleViewRef = useRef(copyVisibleView);
|
|
168
188
|
const workspaceReturnViewRef = useRef("chat");
|
|
169
189
|
const routerReturnViewRef = useRef("chat");
|
|
170
190
|
const workerOverviewReturnViewRef = useRef("chat");
|
|
171
191
|
const taskSessionsReturnViewRef = useRef("chat");
|
|
172
192
|
const collaborationReturnViewRef = useRef("workers");
|
|
193
|
+
const statusReturnViewRef = useRef("chat");
|
|
173
194
|
const collaborationLoadSequenceRef = useRef(0);
|
|
174
195
|
const routerLoadSequenceRef = useRef(0);
|
|
175
196
|
const taskSessionsLoadSequenceRef = useRef(0);
|
|
@@ -183,34 +204,31 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
183
204
|
const contentHeight = appContentHeight(terminalSize.rows, Boolean(attachError), config.ui.showStatusBar);
|
|
184
205
|
const outputHeight = Math.max(1, contentHeight);
|
|
185
206
|
const terminalWidth = terminalSize.columns;
|
|
186
|
-
const workerActivityPolicies = useMemo(() => ({
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
claude: {
|
|
193
|
-
timeoutMs: config.workers.claude.timeoutMs,
|
|
194
|
-
idleTimeoutMs: config.workers.claude.idleTimeoutMs,
|
|
195
|
-
firstOutputTimeoutMs: config.workers.claude.firstOutputTimeoutMs
|
|
196
|
-
},
|
|
197
|
-
mock: {
|
|
198
|
-
timeoutMs: config.workers.mock.timeoutMs,
|
|
199
|
-
idleTimeoutMs: config.workers.mock.idleTimeoutMs,
|
|
200
|
-
firstOutputTimeoutMs: config.workers.mock.firstOutputTimeoutMs
|
|
201
|
-
}
|
|
202
|
-
}), [config.workers.claude, config.workers.codex, config.workers.mock]);
|
|
207
|
+
const workerActivityPolicies = useMemo(() => Object.fromEntries(workerProviders(config).map(({ id, config: provider }) => [id, {
|
|
208
|
+
timeoutMs: provider.timeoutMs,
|
|
209
|
+
idleTimeoutMs: provider.idleTimeoutMs,
|
|
210
|
+
firstOutputTimeoutMs: provider.firstOutputTimeoutMs
|
|
211
|
+
}])), [config.workers]);
|
|
212
|
+
const assignableProviderIds = useMemo(() => assignableWorkerProviderIds(config), [config.workers]);
|
|
203
213
|
const selectedBoardFeature = collaborationTimeline?.features[featureBoardSelectedIndex];
|
|
214
|
+
const selectedTaskSessionDetailWorker = taskSessionDetails?.workers[taskSessionDetailSelectedWorkerIndex];
|
|
215
|
+
const taskSessionDetailHasNative = Boolean(selectedTaskSessionDetailWorker?.nativeSession && !taskSessionDetails?.task.archived_at);
|
|
216
|
+
const taskSessionDetailCanFork = Boolean(taskSessionDetailHasNative
|
|
217
|
+
&& selectedTaskSessionDetailWorker
|
|
218
|
+
&& (config.workers[selectedTaskSessionDetailWorker.engine]?.interactive.forkArgs.length ?? 0) > 0);
|
|
204
219
|
const featureCanCancel = busy && (selectedBoardFeature?.state === "actor_running"
|
|
205
220
|
|| selectedBoardFeature?.state === "critic_running");
|
|
221
|
+
const featureCanReassign = assignableProviderIds.length > 1
|
|
222
|
+
&& !busy
|
|
223
|
+
&& canRetryTask
|
|
224
|
+
&& selectedBoardFeature?.state !== "approved";
|
|
206
225
|
const visibleStatus = statusLineWithWorkerRefs(status, workers);
|
|
207
|
-
const selectedWorkerStatus = formatSelectedWorkerStatus(visibleStatus, selectedWorkerIndex);
|
|
208
|
-
const visibleWorkerStatus = view === "chat" || view === "router" || view === "sessions" || view === "features" || view === "collaboration"
|
|
209
|
-
? ""
|
|
210
|
-
: selectedWorkerStatus;
|
|
211
226
|
const visibleRouteStatus = routePending
|
|
212
227
|
? formatRoutePendingStatus(routePending, routeElapsedMs)
|
|
213
228
|
: formatRouteStatus(lastRoute);
|
|
229
|
+
const visibleRouteSummary = routePending
|
|
230
|
+
? formatRoutePendingSummaryStatus(routePending, routeElapsedMs)
|
|
231
|
+
: formatRouteSummaryStatus(lastRoute);
|
|
214
232
|
const visibleTaskStatus = routePending && !activeTaskId ? "" : formatStatusLine(visibleStatus);
|
|
215
233
|
const workerRefreshKey = workers.map((worker) => `${worker.id}\u0000${worker.statusPath}`).join("\u0001");
|
|
216
234
|
const taskResultMessageIndex = useMemo(() => activeTaskId ? latestTaskResultMessageIndex(messages, activeTaskId) : -1, [activeTaskId, messages]);
|
|
@@ -245,6 +263,9 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
245
263
|
useEffect(() => {
|
|
246
264
|
busyRef.current = busy;
|
|
247
265
|
}, [busy]);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
canRetryTaskRef.current = canRetryTask;
|
|
268
|
+
}, [canRetryTask]);
|
|
248
269
|
useEffect(() => {
|
|
249
270
|
taskResultExpandedRef.current = taskResultExpanded;
|
|
250
271
|
}, [taskResultExpanded]);
|
|
@@ -281,6 +302,12 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
281
302
|
useEffect(() => {
|
|
282
303
|
taskSessionActionRef.current = taskSessionAction;
|
|
283
304
|
}, [taskSessionAction]);
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
taskSessionDetailsRef.current = taskSessionDetails;
|
|
307
|
+
}, [taskSessionDetails]);
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
taskSessionDetailSelectedWorkerIndexRef.current = taskSessionDetailSelectedWorkerIndex;
|
|
310
|
+
}, [taskSessionDetailSelectedWorkerIndex]);
|
|
284
311
|
useEffect(() => {
|
|
285
312
|
featureBoardSelectedIndexRef.current = featureBoardSelectedIndex;
|
|
286
313
|
}, [featureBoardSelectedIndex]);
|
|
@@ -341,6 +368,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
341
368
|
archiveSelectedTaskSessionRef.current = archiveSelectedTaskSession;
|
|
342
369
|
deleteSelectedTaskSessionRef.current = deleteSelectedTaskSession;
|
|
343
370
|
exportSelectedTaskSessionRef.current = exportSelectedTaskSession;
|
|
371
|
+
openSelectedTaskSessionDetailsRef.current = openSelectedTaskSessionDetails;
|
|
372
|
+
refreshTaskSessionDetailsRef.current = refreshTaskSessionDetails;
|
|
373
|
+
openTaskSessionDetailWorkerRef.current = openTaskSessionDetailWorker;
|
|
374
|
+
copyVisibleViewRef.current = copyVisibleView;
|
|
344
375
|
});
|
|
345
376
|
useEffect(() => {
|
|
346
377
|
if ((view !== "collaboration" && view !== "features") || !activeTaskId || !loadCollaborationTimeline) {
|
|
@@ -560,7 +591,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
560
591
|
}, [config.ui.autoOpenFailedWorker, status?.taskId, workerActivityPolicies, workerRefreshKey]);
|
|
561
592
|
useEffect(() => {
|
|
562
593
|
setRawMode(true);
|
|
563
|
-
process.stdout.write("\x1b[?2004h\x1b[?1000h\x1b[?
|
|
594
|
+
process.stdout.write("\x1b[?2004h\x1b[?1000h\x1b[?1006h");
|
|
564
595
|
const commitChatInputUpdate = (update, previousValue, previousCursor) => {
|
|
565
596
|
if (update.exit) {
|
|
566
597
|
activeRunControllerRef.current?.abort();
|
|
@@ -608,11 +639,18 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
608
639
|
setTaskSessionsError(null);
|
|
609
640
|
setSelectedTaskSessionIndex(nextIndex);
|
|
610
641
|
};
|
|
642
|
+
const moveSelectedTaskSessionDetailWorker = (delta, wrap = false) => {
|
|
643
|
+
const nextIndex = moveTaskSessionDetailSelection(taskSessionDetailSelectedWorkerIndexRef.current, delta, taskSessionDetailsRef.current?.workers.length ?? 0, wrap);
|
|
644
|
+
taskSessionDetailSelectedWorkerIndexRef.current = nextIndex;
|
|
645
|
+
setTaskSessionDetailNotice(null);
|
|
646
|
+
setTaskSessionDetailSelectedWorkerIndex(nextIndex);
|
|
647
|
+
};
|
|
611
648
|
const moveSelectedFeature = (delta, wrap = false) => {
|
|
612
649
|
const nextIndex = moveFeatureBoardSelection(featureBoardSelectedIndexRef.current, delta, collaborationTimelineRef.current?.features.length ?? 0, wrap);
|
|
613
650
|
featureBoardSelectedIndexRef.current = nextIndex;
|
|
614
651
|
setCollaborationError(null);
|
|
615
652
|
updateFeatureCancelPrompt(null);
|
|
653
|
+
updateFeatureAssignmentPrompt(null);
|
|
616
654
|
setFeatureBoardNotice(null);
|
|
617
655
|
setFeatureBoardSelectedIndex(nextIndex);
|
|
618
656
|
};
|
|
@@ -650,6 +688,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
650
688
|
return;
|
|
651
689
|
}
|
|
652
690
|
const currentView = viewRef.current;
|
|
691
|
+
if (currentView !== "workspace" && tokenizeRawInput(chunk).some((inputChunk) => isCopyShortcut(inputChunk, {}))) {
|
|
692
|
+
void copyVisibleViewRef.current();
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
653
695
|
if (currentView === "chat" && routeFallbackPromptRef.current) {
|
|
654
696
|
const fallbackChunks = tokenizeRawInput(chunk);
|
|
655
697
|
if (fallbackChunks.some((fallbackChunk) => isExitShortcut(fallbackChunk, {}))) {
|
|
@@ -657,6 +699,13 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
657
699
|
exitRef.current();
|
|
658
700
|
return;
|
|
659
701
|
}
|
|
702
|
+
if (fallbackChunks.some((fallbackChunk) => isStatusDetailsShortcut(fallbackChunk, {}))) {
|
|
703
|
+
statusReturnViewRef.current = "chat";
|
|
704
|
+
viewRef.current = "status";
|
|
705
|
+
setAttachError(null);
|
|
706
|
+
setView("status");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
660
709
|
for (const fallbackChunk of fallbackChunks) {
|
|
661
710
|
if (fallbackChunk === "\x1b") {
|
|
662
711
|
settleRouteFallbackChoice("cancel");
|
|
@@ -680,12 +729,72 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
680
729
|
if (currentView === "workspace") {
|
|
681
730
|
return;
|
|
682
731
|
}
|
|
732
|
+
if (currentView === "status") {
|
|
733
|
+
if (isExitShortcut(chunk, {})) {
|
|
734
|
+
activeRunControllerRef.current?.abort();
|
|
735
|
+
exitRef.current();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (isStatusDetailsShortcut(chunk, {}) || chunk === "\x1b") {
|
|
739
|
+
viewRef.current = statusReturnViewRef.current;
|
|
740
|
+
setView(statusReturnViewRef.current);
|
|
741
|
+
}
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (isStatusDetailsShortcut(chunk, {})) {
|
|
745
|
+
statusReturnViewRef.current = currentView;
|
|
746
|
+
viewRef.current = "status";
|
|
747
|
+
setAttachError(null);
|
|
748
|
+
setView("status");
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
683
751
|
if (currentView === "sessions") {
|
|
684
752
|
if (isExitShortcut(chunk, {})) {
|
|
685
753
|
activeRunControllerRef.current?.abort();
|
|
686
754
|
exitRef.current();
|
|
687
755
|
return;
|
|
688
756
|
}
|
|
757
|
+
if (taskSessionDetailsRef.current) {
|
|
758
|
+
if (isTaskSessionsShortcut(chunk, {}) || chunk === "\x1b") {
|
|
759
|
+
taskSessionDetailsRef.current = null;
|
|
760
|
+
taskSessionDetailSelectedWorkerIndexRef.current = 0;
|
|
761
|
+
setTaskSessionDetails(null);
|
|
762
|
+
setTaskSessionDetailSelectedWorkerIndex(0);
|
|
763
|
+
setTaskSessionDetailNotice(null);
|
|
764
|
+
setTaskSessionsError(null);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (taskSessionsLoadingRef.current) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (chunk === "r" || chunk === "R") {
|
|
771
|
+
void refreshTaskSessionDetailsRef.current();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (chunk === "c" || chunk === "C" || chunk === "\u000f") {
|
|
775
|
+
void openTaskSessionDetailWorkerRef.current("resume");
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (chunk === "b" || chunk === "B") {
|
|
779
|
+
void openTaskSessionDetailWorkerRef.current("fork");
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (chunk === "\r" || chunk === "\n") {
|
|
783
|
+
void openTaskSessionDetailWorkerRef.current("logs");
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (chunk === "\t") {
|
|
787
|
+
moveSelectedTaskSessionDetailWorker(1, true);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const detailSelectionDelta = -(rawHistoryDelta(chunk)
|
|
791
|
+
+ rawPageScrollDelta(chunk, Math.max(1, outputHeight - 2))
|
|
792
|
+
+ mouseScrollDelta(chunk, 1));
|
|
793
|
+
if (detailSelectionDelta !== 0) {
|
|
794
|
+
moveSelectedTaskSessionDetailWorker(detailSelectionDelta);
|
|
795
|
+
}
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
689
798
|
const sessionAction = taskSessionActionRef.current;
|
|
690
799
|
if (sessionAction) {
|
|
691
800
|
if (chunk === "\x1b") {
|
|
@@ -787,6 +896,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
787
896
|
void exportSelectedTaskSessionRef.current();
|
|
788
897
|
return;
|
|
789
898
|
}
|
|
899
|
+
if (chunk === "i" || chunk === "I") {
|
|
900
|
+
void openSelectedTaskSessionDetailsRef.current();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
790
903
|
if (chunk === "h" || chunk === "H") {
|
|
791
904
|
const includeArchived = !taskSessionsIncludeArchivedRef.current;
|
|
792
905
|
taskSessionsIncludeArchivedRef.current = includeArchived;
|
|
@@ -826,13 +939,33 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
826
939
|
setFeatureBoardNotice(null);
|
|
827
940
|
return;
|
|
828
941
|
}
|
|
829
|
-
if (featureChunk === "x" || featureChunk === "X")
|
|
942
|
+
if ((pendingCancel.action === "cancel" && (featureChunk === "x" || featureChunk === "X"))
|
|
943
|
+
|| (pendingCancel.action === "pause" && (featureChunk === "p" || featureChunk === "P"))) {
|
|
830
944
|
void confirmFeatureCancellationRef.current(pendingCancel);
|
|
831
945
|
return;
|
|
832
946
|
}
|
|
833
947
|
}
|
|
834
948
|
return;
|
|
835
949
|
}
|
|
950
|
+
const pendingAssignment = featureAssignmentPromptRef.current;
|
|
951
|
+
if (pendingAssignment) {
|
|
952
|
+
for (const featureChunk of featureChunks) {
|
|
953
|
+
if (featureChunk === "\x1b" || featureChunk === "m" || featureChunk === "M") {
|
|
954
|
+
updateFeatureAssignmentPrompt(null);
|
|
955
|
+
setFeatureBoardNotice(null);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (featureChunk === "a" || featureChunk === "A") {
|
|
959
|
+
void reassignSelectedFeatureRef.current(pendingAssignment, "actor");
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (featureChunk === "c" || featureChunk === "C") {
|
|
963
|
+
void reassignSelectedFeatureRef.current(pendingAssignment, "critic");
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
836
969
|
for (const featureChunk of featureChunks) {
|
|
837
970
|
if (featureChunk === "\x1b" || isWorkerOverviewShortcut(featureChunk, {})) {
|
|
838
971
|
setCollaborationError(null);
|
|
@@ -855,11 +988,43 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
855
988
|
setFeatureBoardNotice("Selected feature has no active Actor/Critic to cancel.");
|
|
856
989
|
return;
|
|
857
990
|
}
|
|
858
|
-
const prompt = { taskId, featureId: feature.id, title: feature.title };
|
|
991
|
+
const prompt = { action: "cancel", taskId, featureId: feature.id, title: feature.title };
|
|
859
992
|
updateFeatureCancelPrompt(prompt);
|
|
860
993
|
setFeatureBoardNotice(`Cancel ${feature.title}? Active peers will finish; integration stays blocked.`);
|
|
861
994
|
return;
|
|
862
995
|
}
|
|
996
|
+
if (featureChunk === "p" || featureChunk === "P") {
|
|
997
|
+
const taskId = activeTaskIdRef.current;
|
|
998
|
+
const feature = collaborationTimelineRef.current?.features[featureBoardSelectedIndexRef.current];
|
|
999
|
+
const pausable = busyRef.current
|
|
1000
|
+
&& (feature?.state === "actor_running" || feature?.state === "critic_running");
|
|
1001
|
+
if (!taskId || !feature || !pausable) {
|
|
1002
|
+
setFeatureBoardNotice("Selected feature has no active Actor/Critic to pause.");
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const prompt = { action: "pause", taskId, featureId: feature.id, title: feature.title };
|
|
1006
|
+
updateFeatureCancelPrompt(prompt);
|
|
1007
|
+
setFeatureBoardNotice(`Pause ${feature.title}? Completed peer checkpoints will be kept.`);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (featureChunk === "m" || featureChunk === "M") {
|
|
1011
|
+
const taskId = activeTaskIdRef.current;
|
|
1012
|
+
const feature = collaborationTimelineRef.current?.features[featureBoardSelectedIndexRef.current];
|
|
1013
|
+
const reassignable = !busyRef.current
|
|
1014
|
+
&& canRetryTaskRef.current
|
|
1015
|
+
&& feature?.state !== "approved";
|
|
1016
|
+
if (!taskId || !feature || !reassignable) {
|
|
1017
|
+
setFeatureBoardNotice("Pause or stop the task before reassigning this Feature.");
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
updateFeatureAssignmentPrompt({
|
|
1021
|
+
taskId,
|
|
1022
|
+
featureId: feature.id,
|
|
1023
|
+
title: feature.title
|
|
1024
|
+
});
|
|
1025
|
+
setFeatureBoardNotice(`Reassign ${feature.title} · A Actor (${feature.actorEngine ?? config.pairing.actor}) · C Critic (${feature.criticEngine ?? config.pairing.critic})`);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
863
1028
|
if (featureChunk === "r" || featureChunk === "R") {
|
|
864
1029
|
setFeatureBoardNotice(null);
|
|
865
1030
|
void refreshCollaborationTimelineRef.current(false);
|
|
@@ -1322,7 +1487,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1322
1487
|
stdinEvents.removeListener("input", handleRawInput);
|
|
1323
1488
|
rawInputDecoderRef.current.end();
|
|
1324
1489
|
chatPasteDecoderRef.current.reset();
|
|
1325
|
-
process.stdout.write("\x1b[?1006l\x1b[?
|
|
1490
|
+
process.stdout.write("\x1b[?1006l\x1b[?1000l\x1b[?2004l");
|
|
1326
1491
|
setRawMode(false);
|
|
1327
1492
|
};
|
|
1328
1493
|
}, [outputHeight, setRawMode, stdinEvents]);
|
|
@@ -1337,6 +1502,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1337
1502
|
collaborationLoadSequenceRef.current += 1;
|
|
1338
1503
|
routerLoadSequenceRef.current += 1;
|
|
1339
1504
|
taskSessionsLoadSequenceRef.current += 1;
|
|
1505
|
+
if (clipboardNoticeTimerRef.current) {
|
|
1506
|
+
clearTimeout(clipboardNoticeTimerRef.current);
|
|
1507
|
+
clipboardNoticeTimerRef.current = null;
|
|
1508
|
+
}
|
|
1340
1509
|
};
|
|
1341
1510
|
}, []);
|
|
1342
1511
|
useEffect(() => {
|
|
@@ -1354,6 +1523,93 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1354
1523
|
shutdownSignal.addEventListener("abort", shutdown, { once: true });
|
|
1355
1524
|
return () => shutdownSignal.removeEventListener("abort", shutdown);
|
|
1356
1525
|
}, [shutdownSignal]);
|
|
1526
|
+
async function copyVisibleView() {
|
|
1527
|
+
const payload = visibleClipboardPayload(viewRef.current);
|
|
1528
|
+
if (!payload?.text.trim()) {
|
|
1529
|
+
setAttachError("No visible text to copy in this view");
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
if (clipboardNoticeTimerRef.current) {
|
|
1533
|
+
clearTimeout(clipboardNoticeTimerRef.current);
|
|
1534
|
+
clipboardNoticeTimerRef.current = null;
|
|
1535
|
+
}
|
|
1536
|
+
setAttachError(null);
|
|
1537
|
+
setClipboardNotice({ state: "copying", text: `copying visible ${payload.label}` });
|
|
1538
|
+
try {
|
|
1539
|
+
await copyToClipboard(payload.text);
|
|
1540
|
+
setClipboardNotice({ state: "copied", text: `copied visible ${payload.label} · wheel still active` });
|
|
1541
|
+
clipboardNoticeTimerRef.current = setTimeout(() => {
|
|
1542
|
+
clipboardNoticeTimerRef.current = null;
|
|
1543
|
+
setClipboardNotice(null);
|
|
1544
|
+
}, 1800);
|
|
1545
|
+
}
|
|
1546
|
+
catch (error) {
|
|
1547
|
+
setClipboardNotice(null);
|
|
1548
|
+
setAttachError(`Copy failed · ${error instanceof Error ? error.message : String(error)}`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function visibleClipboardPayload(currentView) {
|
|
1552
|
+
if (currentView === "chat") {
|
|
1553
|
+
return { label: "chat", text: copyTextByViewRef.current.chat };
|
|
1554
|
+
}
|
|
1555
|
+
if (currentView === "worker") {
|
|
1556
|
+
return { label: "logs", text: copyTextByViewRef.current.worker };
|
|
1557
|
+
}
|
|
1558
|
+
if (currentView === "native") {
|
|
1559
|
+
return { label: "native output", text: nativeAttachRef.current?.screen.snapshot() ?? "" };
|
|
1560
|
+
}
|
|
1561
|
+
if (currentView === "status") {
|
|
1562
|
+
return {
|
|
1563
|
+
label: "status",
|
|
1564
|
+
text: [cwd, activeTaskIdRef.current, visibleTaskStatus, visibleRouteSummary]
|
|
1565
|
+
.filter(Boolean)
|
|
1566
|
+
.join("\n")
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
if (currentView === "workers") {
|
|
1570
|
+
return {
|
|
1571
|
+
label: "workers",
|
|
1572
|
+
text: workersRef.current.map((worker) => [
|
|
1573
|
+
worker.label,
|
|
1574
|
+
worker.runtimeStatus?.state,
|
|
1575
|
+
worker.runtimeStatus?.phase,
|
|
1576
|
+
worker.runtimeStatus?.summary
|
|
1577
|
+
].filter(Boolean).join(" · ")).join("\n")
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
if (currentView === "features") {
|
|
1581
|
+
return {
|
|
1582
|
+
label: "features",
|
|
1583
|
+
text: (collaborationTimelineRef.current?.features ?? []).map((feature) => [
|
|
1584
|
+
feature.id,
|
|
1585
|
+
feature.title,
|
|
1586
|
+
feature.state
|
|
1587
|
+
].filter(Boolean).join(" · ")).join("\n")
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
if (currentView === "sessions") {
|
|
1591
|
+
const details = taskSessionDetailsRef.current;
|
|
1592
|
+
return {
|
|
1593
|
+
label: "sessions",
|
|
1594
|
+
text: details
|
|
1595
|
+
? details.workers.map((worker) => [worker.id, worker.role, worker.engine, worker.state].join(" · ")).join("\n")
|
|
1596
|
+
: taskSessionsRef.current.map((task) => [task.id, task.title, task.status].join(" · ")).join("\n")
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
if (currentView === "router") {
|
|
1600
|
+
return { label: "routes", text: routerRecords.map((record) => JSON.stringify(record)).join("\n") };
|
|
1601
|
+
}
|
|
1602
|
+
if (currentView === "collaboration") {
|
|
1603
|
+
const timeline = collaborationTimelineRef.current;
|
|
1604
|
+
return {
|
|
1605
|
+
label: "timeline",
|
|
1606
|
+
text: timeline
|
|
1607
|
+
? collaborationTimelineEvents(timeline, collaborationFeatureIndexRef.current, collaborationUnresolvedOnlyRef.current).map((event) => JSON.stringify(event)).join("\n")
|
|
1608
|
+
: ""
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1357
1613
|
useInput((inputKey, key) => {
|
|
1358
1614
|
if (view === "worker") {
|
|
1359
1615
|
if (isExitShortcut(inputKey, key)) {
|
|
@@ -1396,18 +1652,19 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1396
1652
|
void attachSelectedWorker(worker);
|
|
1397
1653
|
}
|
|
1398
1654
|
}, { isActive: view === "worker" && !workerSearch.open });
|
|
1399
|
-
async function attachSelectedWorker(worker) {
|
|
1655
|
+
async function attachSelectedWorker(worker, mode = "resume") {
|
|
1400
1656
|
const requestSequence = nativeAttachRequestSequenceRef.current + 1;
|
|
1401
1657
|
nativeAttachRequestSequenceRef.current = requestSequence;
|
|
1402
1658
|
const attachIsCurrent = () => (mountedRef.current && nativeAttachRequestSequenceRef.current === requestSequence);
|
|
1403
1659
|
setAttachError(null);
|
|
1404
1660
|
try {
|
|
1405
|
-
const launch = await (
|
|
1406
|
-
?
|
|
1407
|
-
|
|
1408
|
-
config,
|
|
1409
|
-
|
|
1410
|
-
|
|
1661
|
+
const launch = await (mode === "fork"
|
|
1662
|
+
? prepareNativeFork
|
|
1663
|
+
? prepareNativeFork(worker)
|
|
1664
|
+
: buildNativeForkLaunch({ config, worker })
|
|
1665
|
+
: prepareNativeAttach
|
|
1666
|
+
? prepareNativeAttach(worker)
|
|
1667
|
+
: buildNativeAttachLaunch({ config, worker }));
|
|
1411
1668
|
if (!attachIsCurrent()) {
|
|
1412
1669
|
return;
|
|
1413
1670
|
}
|
|
@@ -1445,7 +1702,12 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1445
1702
|
if (!attachIsCurrent()) {
|
|
1446
1703
|
return;
|
|
1447
1704
|
}
|
|
1448
|
-
|
|
1705
|
+
const failureHint = nativeAttachFailureHint(screen.snapshot(), code, worker.engine);
|
|
1706
|
+
const closeOutput = [
|
|
1707
|
+
nativeAttachExitLine(code, screen.dimensions().cols),
|
|
1708
|
+
...(failureHint ? [failureHint] : [])
|
|
1709
|
+
].join("\r\n");
|
|
1710
|
+
void screen.write(`\r\n${closeOutput}\r\n`).then(() => {
|
|
1449
1711
|
if (!attachIsCurrent()) {
|
|
1450
1712
|
return;
|
|
1451
1713
|
}
|
|
@@ -1705,11 +1967,20 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1705
1967
|
setRouteAnnouncement(null);
|
|
1706
1968
|
setLastRoute(null);
|
|
1707
1969
|
try {
|
|
1708
|
-
const
|
|
1970
|
+
const selectedPausedFeature = viewRef.current === "features"
|
|
1971
|
+
? collaborationTimelineRef.current?.features[featureBoardSelectedIndexRef.current]
|
|
1972
|
+
: null;
|
|
1973
|
+
const retryInput = {
|
|
1709
1974
|
taskId,
|
|
1710
1975
|
cwd,
|
|
1711
1976
|
...createRunCallbacks(controller, { announceRoute: false })
|
|
1712
|
-
}
|
|
1977
|
+
};
|
|
1978
|
+
const result = selectedPausedFeature?.state === "paused"
|
|
1979
|
+
? await orchestrator.resumeFeature({
|
|
1980
|
+
...retryInput,
|
|
1981
|
+
featureId: selectedPausedFeature.id
|
|
1982
|
+
})
|
|
1983
|
+
: await orchestrator.retryTask(retryInput);
|
|
1713
1984
|
activeTaskIdRef.current = taskId;
|
|
1714
1985
|
setActiveTaskId(taskId);
|
|
1715
1986
|
setActiveMode("complex");
|
|
@@ -1833,6 +2104,11 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1833
2104
|
setAttachError(null);
|
|
1834
2105
|
setTaskSessionsError(null);
|
|
1835
2106
|
setTaskSessionsNotice(null);
|
|
2107
|
+
taskSessionDetailsRef.current = null;
|
|
2108
|
+
taskSessionDetailSelectedWorkerIndexRef.current = 0;
|
|
2109
|
+
setTaskSessionDetails(null);
|
|
2110
|
+
setTaskSessionDetailSelectedWorkerIndex(0);
|
|
2111
|
+
setTaskSessionDetailNotice(null);
|
|
1836
2112
|
updateTaskSessionAction(null);
|
|
1837
2113
|
await refreshTaskSessions(activeTaskIdRef.current);
|
|
1838
2114
|
}
|
|
@@ -1943,7 +2219,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1943
2219
|
return exportTaskSession(selected.id);
|
|
1944
2220
|
}, (path) => `Exported · ${path}`, selected.id);
|
|
1945
2221
|
}
|
|
1946
|
-
async function
|
|
2222
|
+
async function openSelectedTaskSessionDetails() {
|
|
1947
2223
|
if (busyRef.current || taskSessionsLoadingRef.current) {
|
|
1948
2224
|
return;
|
|
1949
2225
|
}
|
|
@@ -1951,9 +2227,103 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1951
2227
|
if (!selected) {
|
|
1952
2228
|
return;
|
|
1953
2229
|
}
|
|
2230
|
+
taskSessionsLoadingRef.current = true;
|
|
2231
|
+
setTaskSessionsLoading(true);
|
|
2232
|
+
setTaskSessionsError(null);
|
|
2233
|
+
setTaskSessionDetailNotice(null);
|
|
2234
|
+
const sequence = taskSessionsLoadSequenceRef.current + 1;
|
|
2235
|
+
taskSessionsLoadSequenceRef.current = sequence;
|
|
2236
|
+
try {
|
|
2237
|
+
if (!loadTaskSessionDetails) {
|
|
2238
|
+
throw new Error("Task session details are unavailable");
|
|
2239
|
+
}
|
|
2240
|
+
const details = await loadTaskSessionDetails(selected);
|
|
2241
|
+
if (taskSessionsLoadSequenceRef.current !== sequence) {
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
taskSessionDetailsRef.current = details;
|
|
2245
|
+
taskSessionDetailSelectedWorkerIndexRef.current = 0;
|
|
2246
|
+
setTaskSessionDetails(details);
|
|
2247
|
+
setTaskSessionDetailSelectedWorkerIndex(0);
|
|
2248
|
+
}
|
|
2249
|
+
catch (error) {
|
|
2250
|
+
if (taskSessionsLoadSequenceRef.current === sequence) {
|
|
2251
|
+
setTaskSessionsError(error instanceof Error ? error.message : String(error));
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
finally {
|
|
2255
|
+
if (taskSessionsLoadSequenceRef.current === sequence) {
|
|
2256
|
+
taskSessionsLoadingRef.current = false;
|
|
2257
|
+
setTaskSessionsLoading(false);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
async function refreshTaskSessionDetails() {
|
|
2262
|
+
const current = taskSessionDetailsRef.current;
|
|
2263
|
+
if (!current || taskSessionsLoadingRef.current || !loadTaskSessionDetails) {
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
const selectedWorkerId = current.workers[taskSessionDetailSelectedWorkerIndexRef.current]?.id;
|
|
2267
|
+
taskSessionsLoadingRef.current = true;
|
|
2268
|
+
setTaskSessionsLoading(true);
|
|
2269
|
+
setTaskSessionsError(null);
|
|
2270
|
+
const sequence = taskSessionsLoadSequenceRef.current + 1;
|
|
2271
|
+
taskSessionsLoadSequenceRef.current = sequence;
|
|
2272
|
+
try {
|
|
2273
|
+
const details = await loadTaskSessionDetails(current.task);
|
|
2274
|
+
if (taskSessionsLoadSequenceRef.current !== sequence) {
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
const selectedIndex = selectedWorkerId
|
|
2278
|
+
? Math.max(0, details.workers.findIndex((worker) => worker.id === selectedWorkerId))
|
|
2279
|
+
: 0;
|
|
2280
|
+
taskSessionDetailsRef.current = details;
|
|
2281
|
+
taskSessionDetailSelectedWorkerIndexRef.current = selectedIndex;
|
|
2282
|
+
setTaskSessionDetails(details);
|
|
2283
|
+
setTaskSessionDetailSelectedWorkerIndex(selectedIndex);
|
|
2284
|
+
setTaskSessionDetailNotice("Session hierarchy refreshed");
|
|
2285
|
+
}
|
|
2286
|
+
catch (error) {
|
|
2287
|
+
if (taskSessionsLoadSequenceRef.current === sequence) {
|
|
2288
|
+
setTaskSessionsError(error instanceof Error ? error.message : String(error));
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
finally {
|
|
2292
|
+
if (taskSessionsLoadSequenceRef.current === sequence) {
|
|
2293
|
+
taskSessionsLoadingRef.current = false;
|
|
2294
|
+
setTaskSessionsLoading(false);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
async function openTaskSessionDetailWorker(mode) {
|
|
2299
|
+
const details = taskSessionDetailsRef.current;
|
|
2300
|
+
const detailWorker = details?.workers[taskSessionDetailSelectedWorkerIndexRef.current];
|
|
2301
|
+
if (!details || !detailWorker || taskSessionsLoadingRef.current) {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
if (mode !== "logs" && !detailWorker.nativeSession) {
|
|
2305
|
+
setTaskSessionDetailNotice(`No native session recorded for ${sessionDetailWorkerLabel(detailWorker)}`);
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
const worker = await activateTaskSessionRecord(details.task, detailWorker.id, "worker");
|
|
2309
|
+
if (worker && mode !== "logs") {
|
|
2310
|
+
await attachSelectedWorkerRef.current(worker, mode === "fork" ? "fork" : "resume");
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
async function activateSelectedTaskSession() {
|
|
2314
|
+
const selected = taskSessionsRef.current[selectedTaskSessionIndexRef.current];
|
|
2315
|
+
if (!selected) {
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
await activateTaskSessionRecord(selected, null, "chat");
|
|
2319
|
+
}
|
|
2320
|
+
async function activateTaskSessionRecord(selected, preferredWorkerId, targetView) {
|
|
2321
|
+
if (busyRef.current || taskSessionsLoadingRef.current) {
|
|
2322
|
+
return null;
|
|
2323
|
+
}
|
|
1954
2324
|
if (selected.archived_at) {
|
|
1955
2325
|
setTaskSessionsError(`Unarchive ${selected.title} before restoring it`);
|
|
1956
|
-
return;
|
|
2326
|
+
return null;
|
|
1957
2327
|
}
|
|
1958
2328
|
taskSessionsLoadingRef.current = true;
|
|
1959
2329
|
setTaskSessionsLoading(true);
|
|
@@ -1966,7 +2336,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1966
2336
|
}
|
|
1967
2337
|
const restored = await activateTaskSession(selected.id);
|
|
1968
2338
|
if (taskSessionsLoadSequenceRef.current !== sequence) {
|
|
1969
|
-
return;
|
|
2339
|
+
return null;
|
|
1970
2340
|
}
|
|
1971
2341
|
if (!restored) {
|
|
1972
2342
|
throw new Error(`Task session not found: ${selected.id}`);
|
|
@@ -1981,19 +2351,28 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1981
2351
|
setTaskResultExpanded(latestTaskResultMessageIndex(messagesRef.current, restored.taskId) >= 0);
|
|
1982
2352
|
workersRef.current = restored.workers;
|
|
1983
2353
|
setWorkers(restored.workers);
|
|
1984
|
-
|
|
1985
|
-
|
|
2354
|
+
const preferredWorkerIndex = preferredWorkerId
|
|
2355
|
+
? restored.workers.findIndex((worker) => worker.id === preferredWorkerId)
|
|
2356
|
+
: -1;
|
|
2357
|
+
if (preferredWorkerId && preferredWorkerIndex < 0) {
|
|
2358
|
+
throw new Error(`Worker is no longer available: ${preferredWorkerId}`);
|
|
2359
|
+
}
|
|
2360
|
+
const nextWorkerIndex = preferredWorkerIndex >= 0 ? preferredWorkerIndex : 0;
|
|
2361
|
+
selectedWorkerIndexRef.current = nextWorkerIndex;
|
|
2362
|
+
setSelectedWorkerIndex(nextWorkerIndex);
|
|
1986
2363
|
setWorkerScrollOffset(0);
|
|
1987
2364
|
autoSelectedFailedWorkerRef.current = false;
|
|
1988
2365
|
userSelectedWorkerRef.current = false;
|
|
1989
2366
|
setAttachError(null);
|
|
1990
|
-
viewRef.current =
|
|
1991
|
-
setView(
|
|
2367
|
+
viewRef.current = targetView;
|
|
2368
|
+
setView(targetView);
|
|
2369
|
+
return restored.workers[nextWorkerIndex] ?? null;
|
|
1992
2370
|
}
|
|
1993
2371
|
catch (error) {
|
|
1994
2372
|
if (taskSessionsLoadSequenceRef.current === sequence) {
|
|
1995
2373
|
setTaskSessionsError(error instanceof Error ? error.message : String(error));
|
|
1996
2374
|
}
|
|
2375
|
+
return null;
|
|
1997
2376
|
}
|
|
1998
2377
|
finally {
|
|
1999
2378
|
if (taskSessionsLoadSequenceRef.current === sequence) {
|
|
@@ -2006,17 +2385,51 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2006
2385
|
featureCancelPromptRef.current = next;
|
|
2007
2386
|
setFeatureCancelPrompt(next);
|
|
2008
2387
|
}
|
|
2388
|
+
function updateFeatureAssignmentPrompt(next) {
|
|
2389
|
+
featureAssignmentPromptRef.current = next;
|
|
2390
|
+
setFeatureAssignmentPrompt(next);
|
|
2391
|
+
}
|
|
2392
|
+
async function reassignSelectedFeature(prompt, role) {
|
|
2393
|
+
const feature = collaborationTimelineRef.current?.features.find((item) => item.id === prompt.featureId);
|
|
2394
|
+
if (!feature) {
|
|
2395
|
+
setFeatureBoardNotice(`Feature no longer exists: ${prompt.featureId}`);
|
|
2396
|
+
updateFeatureAssignmentPrompt(null);
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
const current = role === "actor"
|
|
2400
|
+
? feature.actorEngine ?? config.pairing.actor
|
|
2401
|
+
: feature.criticEngine ?? config.pairing.critic;
|
|
2402
|
+
const engine = nextAssignableFeatureEngine(current, assignableProviderIds);
|
|
2403
|
+
try {
|
|
2404
|
+
await orchestrator.reassignFeature({
|
|
2405
|
+
taskId: prompt.taskId,
|
|
2406
|
+
featureId: prompt.featureId,
|
|
2407
|
+
role,
|
|
2408
|
+
engine
|
|
2409
|
+
});
|
|
2410
|
+
setFeatureBoardNotice(`${role === "actor" ? "Actor" : "Critic"} reassigned to ${featureEngineDisplay(config, engine)} · Ctrl+R resumes the task.`);
|
|
2411
|
+
await refreshCollaborationTimeline(false);
|
|
2412
|
+
}
|
|
2413
|
+
catch (error) {
|
|
2414
|
+
setFeatureBoardNotice(`Reassign failed for ${prompt.title}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2009
2417
|
async function confirmFeatureCancellation(prompt) {
|
|
2010
2418
|
updateFeatureCancelPrompt(null);
|
|
2011
2419
|
setAttachError(null);
|
|
2012
2420
|
try {
|
|
2013
|
-
const result =
|
|
2421
|
+
const result = prompt.action === "pause"
|
|
2422
|
+
? await orchestrator.pauseFeature(prompt.taskId, prompt.featureId)
|
|
2423
|
+
: await orchestrator.cancelFeature(prompt.taskId, prompt.featureId);
|
|
2014
2424
|
setFeatureBoardNotice(result.requested
|
|
2015
|
-
?
|
|
2425
|
+
? prompt.action === "pause"
|
|
2426
|
+
? `Pause requested for ${prompt.title} · ${result.role ?? "worker"} stopping; checkpoints stay resumable.`
|
|
2427
|
+
: `Cancellation requested for ${prompt.title} · ${result.role ?? "worker"} stopping; active peers will finish.`
|
|
2016
2428
|
: `No active Actor/Critic for ${prompt.title}; refresh and try again.`);
|
|
2017
2429
|
}
|
|
2018
2430
|
catch (error) {
|
|
2019
|
-
|
|
2431
|
+
const action = prompt.action === "pause" ? "Pause" : "Cancel";
|
|
2432
|
+
setFeatureBoardNotice(`${action} failed for ${prompt.title}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2020
2433
|
}
|
|
2021
2434
|
}
|
|
2022
2435
|
async function openFeatureBoard() {
|
|
@@ -2032,6 +2445,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2032
2445
|
setAttachError(null);
|
|
2033
2446
|
setCollaborationError(null);
|
|
2034
2447
|
updateFeatureCancelPrompt(null);
|
|
2448
|
+
updateFeatureAssignmentPrompt(null);
|
|
2035
2449
|
setFeatureBoardNotice(null);
|
|
2036
2450
|
collaborationTimelineRef.current = null;
|
|
2037
2451
|
setCollaborationTimeline(null);
|
|
@@ -2219,19 +2633,19 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2219
2633
|
if (view === "workspace") {
|
|
2220
2634
|
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) }) }));
|
|
2221
2635
|
}
|
|
2222
|
-
return (_jsx(AppShell, { view: view, cwd: cwd, taskId: activeTaskId, statusText: [visibleTaskStatus,
|
|
2636
|
+
return (_jsx(AppShell, { view: view, cwd: cwd, taskId: activeTaskId, statusText: [visibleTaskStatus, visibleRouteSummary].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"
|
|
2223
2637
|
? { type: "rename", value: taskSessionAction.value, cursor: taskSessionAction.cursor }
|
|
2224
2638
|
: taskSessionAction?.type === "delete"
|
|
2225
2639
|
? { type: "delete", title: taskSessionAction.title }
|
|
2226
|
-
: null, taskSessionsIncludeArchived: taskSessionsIncludeArchived, 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, value: workerSearch.open && view === "worker"
|
|
2640
|
+
: null, taskSessionsIncludeArchived: taskSessionsIncludeArchived, taskSessionDetail: Boolean(taskSessionDetails), taskSessionDetailHasNative: taskSessionDetailHasNative, taskSessionDetailCanFork: taskSessionDetailCanFork, canRetry: canRetryTask, hasWorkers: workers.length > 0, hasActiveTask: Boolean(activeTaskId), hasTaskResult: hasTaskResult, taskResultExpanded: taskResultExpanded, chatScrollOffset: chatScrollOffset, chatMaxScrollOffset: chatMaxScrollOffset, nativeClosed: view === "native" && nativeAttach?.closedCode !== null, searchMatchIndex: workerSearch.matchIndex, searchMatchCount: workerNavigationTargets.searchOffsets.length, clipboardNotice: clipboardNotice, value: workerSearch.open && view === "worker"
|
|
2227
2641
|
? workerSearch.query
|
|
2228
|
-
: view === "native" || view === "router" || view === "workers" || view === "features" || view === "sessions" || view === "collaboration" ? "" : input, cursor: workerSearch.open && view === "worker"
|
|
2642
|
+
: view === "native" || view === "router" || view === "workers" || view === "features" || view === "sessions" || view === "collaboration" || view === "status" ? "" : input, cursor: workerSearch.open && view === "worker"
|
|
2229
2643
|
? workerSearch.cursor
|
|
2230
|
-
: view === "chat" ? inputCursor : undefined, terminalWidth: terminalWidth, onChange: view === "native" ? setNativeInput : setInput, onSubmit: view === "native" ? undefined : submit }), error: attachError, children: view === "native" ? (_jsx(NativeAttachView, { attach: nativeAttach, viewportHeight: contentHeight })) : view === "sessions" ? (_jsx(TaskSessionsView, { tasks: taskSessions, activeTaskId: activeTaskId, selectedIndex: selectedTaskSessionIndex, includeArchived: taskSessionsIncludeArchived, notice: taskSessionsNotice, action: taskSessionAction?.type === "rename"
|
|
2644
|
+
: 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, workers: workers, selectedWorkerIndex: selectedWorkerIndex, height: contentHeight, terminalWidth: terminalWidth })) : view === "native" ? (_jsx(NativeAttachView, { attach: nativeAttach, viewportHeight: contentHeight })) : view === "sessions" ? (taskSessionDetails ? (_jsx(TaskSessionDetailView, { details: taskSessionDetails, selectedWorkerIndex: taskSessionDetailSelectedWorkerIndex, loading: taskSessionsLoading, error: taskSessionsError, notice: taskSessionDetailNotice, height: contentHeight, terminalWidth: terminalWidth })) : (_jsx(TaskSessionsView, { tasks: taskSessions, activeTaskId: activeTaskId, selectedIndex: selectedTaskSessionIndex, includeArchived: taskSessionsIncludeArchived, notice: taskSessionsNotice, action: taskSessionAction?.type === "rename"
|
|
2231
2645
|
? { type: "rename", title: taskSessionAction.title }
|
|
2232
2646
|
: taskSessionAction?.type === "delete"
|
|
2233
2647
|
? { type: "delete", title: taskSessionAction.title }
|
|
2234
|
-
: null, loading: taskSessionsLoading, error: taskSessionsError, height: contentHeight, terminalWidth: terminalWidth })) : view === "features" ? (_jsx(FeatureBoardView, { timeline: collaborationTimeline, selectedIndex: featureBoardSelectedIndex, loading: collaborationLoading, error: collaborationError, notice: featureBoardNotice, height: contentHeight, terminalWidth: terminalWidth })) : view === "collaboration" ? (_jsx(CollaborationTimelineView, { timeline: collaborationTimeline, featureIndex: collaborationFeatureIndex, selectedEventId: collaborationSelectedEventId, detailOpen: collaborationDetailOpen, unresolvedOnly: collaborationUnresolvedOnly, loading: collaborationLoading, error: collaborationError, scrollOffset: collaborationScrollOffset, height: contentHeight, terminalWidth: terminalWidth, onViewportChange: ({ offset, maxOffset }) => {
|
|
2648
|
+
: null, loading: taskSessionsLoading, error: taskSessionsError, height: contentHeight, terminalWidth: terminalWidth }))) : view === "features" ? (_jsx(FeatureBoardView, { timeline: collaborationTimeline, selectedIndex: featureBoardSelectedIndex, loading: collaborationLoading, error: collaborationError, notice: featureBoardNotice, height: contentHeight, terminalWidth: terminalWidth })) : view === "collaboration" ? (_jsx(CollaborationTimelineView, { timeline: collaborationTimeline, featureIndex: collaborationFeatureIndex, selectedEventId: collaborationSelectedEventId, detailOpen: collaborationDetailOpen, unresolvedOnly: collaborationUnresolvedOnly, loading: collaborationLoading, error: collaborationError, scrollOffset: collaborationScrollOffset, height: contentHeight, terminalWidth: terminalWidth, onViewportChange: ({ offset, maxOffset }) => {
|
|
2235
2649
|
collaborationMaxScrollOffsetRef.current = maxOffset;
|
|
2236
2650
|
setCollaborationMaxScrollOffset(maxOffset);
|
|
2237
2651
|
if (offset !== collaborationScrollOffset) {
|
|
@@ -2250,7 +2664,11 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2250
2664
|
chatScrollOffsetRef.current = offset;
|
|
2251
2665
|
setChatScrollOffset(offset);
|
|
2252
2666
|
}
|
|
2253
|
-
}
|
|
2667
|
+
}, onCopyTextChange: (text) => {
|
|
2668
|
+
copyTextByViewRef.current.chat = text;
|
|
2669
|
+
} })) : (_jsx(WorkerOutputView, { title: workerTitle(workers, selectedWorkerIndex), role: workers[selectedWorkerIndex]?.role, featureId: workers[selectedWorkerIndex]?.featureId, logPath: workers[selectedWorkerIndex]?.logPath ?? null, scrollOffset: workerScrollOffset, searchQuery: workerSearch.open ? workerSearch.query : "", searchMatchIndex: workerSearch.matchIndex, height: Math.max(1, outputHeight - 1), terminalWidth: terminalWidth, onNavigationChange: updateWorkerNavigationTargets, onCopyTextChange: (text) => {
|
|
2670
|
+
copyTextByViewRef.current.worker = text;
|
|
2671
|
+
}, onViewportChange: ({ offset }) => {
|
|
2254
2672
|
if (offset !== workerScrollOffset) {
|
|
2255
2673
|
setWorkerScrollOffset(offset);
|
|
2256
2674
|
}
|
|
@@ -2262,7 +2680,7 @@ function readTerminalSize() {
|
|
|
2262
2680
|
rows: Math.max(1, Math.trunc(process.stdout.rows || 30))
|
|
2263
2681
|
};
|
|
2264
2682
|
}
|
|
2265
|
-
export function ChatView({ messages, cwd, activeTaskId, terminalWidth = process.stdout.columns || 120, viewportHeight, scrollOffset = 0, expandedTaskResult = false, onViewportChange }) {
|
|
2683
|
+
export function ChatView({ messages, cwd, activeTaskId, terminalWidth = process.stdout.columns || 120, viewportHeight, scrollOffset = 0, expandedTaskResult = false, onViewportChange, onCopyTextChange }) {
|
|
2266
2684
|
const height = viewportHeight ? Math.max(1, viewportHeight) : undefined;
|
|
2267
2685
|
const viewport = useMemo(() => chatMessageViewport(messages, terminalWidth, height ?? 12, scrollOffset, {
|
|
2268
2686
|
expandedTaskResult,
|
|
@@ -2274,6 +2692,10 @@ export function ChatView({ messages, cwd, activeTaskId, terminalWidth = process.
|
|
|
2274
2692
|
maxOffset: viewport.maxOffset
|
|
2275
2693
|
});
|
|
2276
2694
|
}, [onViewportChange, viewport.clampedOffset, viewport.maxOffset]);
|
|
2695
|
+
const visibleCopyText = viewport.lines.map((line) => line.text).join("\n");
|
|
2696
|
+
useEffect(() => {
|
|
2697
|
+
onCopyTextChange?.(visibleCopyText);
|
|
2698
|
+
}, [onCopyTextChange, visibleCopyText]);
|
|
2277
2699
|
if (messages.length === 0) {
|
|
2278
2700
|
const spacerLines = chatViewportSpacerLineCount(1, height);
|
|
2279
2701
|
return (_jsxs(Box, { flexDirection: "column", height: height, children: [_jsx(ChatViewportSpacerLines, { count: spacerLines, terminalWidth: terminalWidth }), _jsx(ChatEmptyState, { cwd: cwd, activeTaskId: activeTaskId, terminalWidth: terminalWidth })] }));
|
|
@@ -3046,6 +3468,21 @@ function compactChatTaskId(taskId) {
|
|
|
3046
3468
|
}
|
|
3047
3469
|
return taskId.startsWith("task-") ? taskId.slice("task-".length) : taskId;
|
|
3048
3470
|
}
|
|
3471
|
+
export function nextAssignableFeatureEngine(current, providers = ["codex", "claude"]) {
|
|
3472
|
+
const available = [...new Set(providers)];
|
|
3473
|
+
if (available.length === 0) {
|
|
3474
|
+
return current;
|
|
3475
|
+
}
|
|
3476
|
+
const currentIndex = available.indexOf(current);
|
|
3477
|
+
return available[(currentIndex + 1 + available.length) % available.length] ?? current;
|
|
3478
|
+
}
|
|
3479
|
+
function sessionDetailWorkerLabel(worker) {
|
|
3480
|
+
const role = `${worker.role.slice(0, 1).toUpperCase()}${worker.role.slice(1)}`;
|
|
3481
|
+
return `${role} (${worker.engine})${worker.featureTitle ? ` · ${worker.featureTitle}` : ""}`;
|
|
3482
|
+
}
|
|
3483
|
+
function featureEngineDisplay(config, engine) {
|
|
3484
|
+
return workerProviderLabel(config, engine);
|
|
3485
|
+
}
|
|
3049
3486
|
function compactChatText(text, maxLength) {
|
|
3050
3487
|
if (maxLength <= 5) {
|
|
3051
3488
|
return takeChatTextStartByDisplayWidth(text, maxLength);
|
|
@@ -3364,6 +3801,27 @@ export function nativeAttachExitLine(code, nativeTerminalCols) {
|
|
|
3364
3801
|
];
|
|
3365
3802
|
return firstNativeTitleThatFits(candidates, contentWidth);
|
|
3366
3803
|
}
|
|
3804
|
+
export function nativeAttachFailureHint(output, code, providerId = "codex") {
|
|
3805
|
+
if (code === 0) {
|
|
3806
|
+
return null;
|
|
3807
|
+
}
|
|
3808
|
+
if (/effective permissions do not allow additional writable roots|error adding directories|read-only sandbox/i.test(output)) {
|
|
3809
|
+
return `fix · set workers.${providerId}.interactive.args sandbox to workspace-write or danger-full-access, then reattach`;
|
|
3810
|
+
}
|
|
3811
|
+
if (/not inside a trusted directory|trusted directory/i.test(output)) {
|
|
3812
|
+
return "fix · open Codex once in this workspace and approve directory trust, then reattach";
|
|
3813
|
+
}
|
|
3814
|
+
if (/unexpected argument ['\"]?--skip-git-repo-check|--skip-git-repo-check.*not found/i.test(output)) {
|
|
3815
|
+
return `fix · remove --skip-git-repo-check from workers.${providerId}.interactive.args, then reattach`;
|
|
3816
|
+
}
|
|
3817
|
+
if (/stdin is not a terminal/i.test(output)) {
|
|
3818
|
+
return "fix · native attach requires its embedded PTY; reopen it with Ctrl+O instead of piping codex resume";
|
|
3819
|
+
}
|
|
3820
|
+
if (/permission denied|\bEACCES\b|\bEPERM\b/i.test(output)) {
|
|
3821
|
+
return "fix · grant the terminal read/write access to the workspace and .parallel-codex directories, then reattach";
|
|
3822
|
+
}
|
|
3823
|
+
return null;
|
|
3824
|
+
}
|
|
3367
3825
|
function withNativeTitleSuffix(candidates, suffix) {
|
|
3368
3826
|
const cleanSuffix = suffix?.trim();
|
|
3369
3827
|
if (!cleanSuffix) {
|