parallel-codex-tui 0.3.2 → 0.4.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 +12 -7
- package/dist/cli.js +19 -1
- package/dist/orchestrator/orchestrator.js +26 -4
- package/dist/supervisor/client.js +442 -0
- package/dist/supervisor/protocol.js +177 -0
- package/dist/supervisor/runner.js +238 -0
- package/dist/supervisor/store.js +213 -0
- package/dist/tui/App.js +107 -31
- package/dist/tui/AppShell.js +7 -3
- package/dist/tui/InputBar.js +9 -3
- package/dist/version.js +1 -1
- package/dist/workers/mock-adapter.js +18 -0
- package/package.json +1 -1
package/dist/tui/App.js
CHANGED
|
@@ -7,6 +7,7 @@ import { copyTextToClipboard } from "../core/clipboard.js";
|
|
|
7
7
|
import { readJson } from "../core/file-store.js";
|
|
8
8
|
import { CONFIGURABLE_ROLES, configuredRoleSelection } from "../core/role-configuration.js";
|
|
9
9
|
import { WorkerStatusSchema } from "../domain/schemas.js";
|
|
10
|
+
import { isSupervisorDetachedError } from "../supervisor/client.js";
|
|
10
11
|
import { effectiveWorkerWatchdog, formatRoutePendingStatus, formatRoutePendingSummaryStatus, formatRouteStatus, formatRouteSummaryStatus, formatStatusLine, formatWorkerRuntimeStatus } from "./status-line.js";
|
|
11
12
|
import { applyChatInputChunk, insertChatPaste } from "./chat-input.js";
|
|
12
13
|
import { chatRequestHistory, navigateChatDraftHistory } from "./chat-history.js";
|
|
@@ -62,6 +63,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
62
63
|
const [inputCursor, setInputCursor] = useState(0);
|
|
63
64
|
const [inputReady, setInputReady] = useState(false);
|
|
64
65
|
const [terminalSize, setTerminalSize] = useState(readTerminalSize);
|
|
66
|
+
const [, setBackgroundRunRevision] = useState(0);
|
|
65
67
|
const [messages, setMessages] = useState(() => [...initialMessages]);
|
|
66
68
|
const [taskResultExpanded, setTaskResultExpanded] = useState(() => (Boolean(initialTaskId) && latestTaskResultMessageIndex(initialMessages, initialTaskId) >= 0));
|
|
67
69
|
const [busy, setBusy] = useState(false);
|
|
@@ -148,6 +150,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
148
150
|
const copyTextByViewRef = useRef({ chat: "", worker: "" });
|
|
149
151
|
const messagesRef = useRef([...initialMessages]);
|
|
150
152
|
const activeRunControllerRef = useRef(null);
|
|
153
|
+
const backgroundRestoreStartedRef = useRef(false);
|
|
151
154
|
const activeTaskIdRef = useRef(initialTaskId);
|
|
152
155
|
const nativeInputRef = useRef(nativeInput);
|
|
153
156
|
const inputRef = useRef(input);
|
|
@@ -270,6 +273,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
270
273
|
&& !busy
|
|
271
274
|
&& canRetryTask
|
|
272
275
|
&& selectedBoardFeature?.state !== "approved";
|
|
276
|
+
const backgroundRunAttached = orchestrator.backgroundRunAttached?.() ?? false;
|
|
277
|
+
const backgroundRunControllable = orchestrator.backgroundRunControllable?.() ?? backgroundRunAttached;
|
|
273
278
|
const visibleStatus = statusLineWithWorkerRefs(status, workers);
|
|
274
279
|
const visibleRouteStatus = routePending
|
|
275
280
|
? formatRoutePendingStatus(routePending, routeElapsedMs)
|
|
@@ -290,6 +295,9 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
290
295
|
: roleConfigurationSnapshot.future
|
|
291
296
|
: configuredRoleSelection(config);
|
|
292
297
|
const roleConfigurationHasOverride = roleConfigurationScopeHasOverride(roleConfigurationSnapshot, roleConfigurationScope);
|
|
298
|
+
useEffect(() => orchestrator.subscribeBackgroundRunState?.(() => {
|
|
299
|
+
setBackgroundRunRevision((current) => current + 1);
|
|
300
|
+
}), [orchestrator]);
|
|
293
301
|
useEffect(() => {
|
|
294
302
|
inputRef.current = input;
|
|
295
303
|
}, [input]);
|
|
@@ -633,6 +641,70 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
633
641
|
active = false;
|
|
634
642
|
};
|
|
635
643
|
}, [activeTaskId, initialTaskId, initialWorkers, orchestrator]);
|
|
644
|
+
useEffect(() => {
|
|
645
|
+
if (!orchestrator.restorePendingRun || backgroundRestoreStartedRef.current) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
backgroundRestoreStartedRef.current = true;
|
|
649
|
+
const controller = new AbortController();
|
|
650
|
+
activeRunControllerRef.current = controller;
|
|
651
|
+
let active = true;
|
|
652
|
+
async function restoreBackgroundRun() {
|
|
653
|
+
busyRef.current = true;
|
|
654
|
+
setBusy(true);
|
|
655
|
+
try {
|
|
656
|
+
const result = await orchestrator.restorePendingRun?.({
|
|
657
|
+
cwd,
|
|
658
|
+
...createRunCallbacks(controller, { announceRoute: false })
|
|
659
|
+
});
|
|
660
|
+
if (!active || !result) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const taskId = result.taskId ?? activeTaskIdRef.current;
|
|
664
|
+
activeTaskIdRef.current = taskId;
|
|
665
|
+
setActiveTaskId(taskId);
|
|
666
|
+
setActiveMode(taskId ? "complex" : result.mode);
|
|
667
|
+
setCanRetryTask(taskId ? await orchestrator.canRetryTask(taskId) : false);
|
|
668
|
+
await refreshRoleConfigurationState(taskId);
|
|
669
|
+
if (!messagesRef.current.some((message) => message.from === "system" && message.text === result.summary && message.taskId === (taskId ?? undefined))) {
|
|
670
|
+
await appendVisibleMessage({ from: "system", text: result.summary }, taskId ?? undefined, { persist: false });
|
|
671
|
+
}
|
|
672
|
+
if (result.mode === "complex" && parseTaskResultSummary(result.summary)) {
|
|
673
|
+
setTaskResultExpanded(true);
|
|
674
|
+
}
|
|
675
|
+
await orchestrator.acknowledgeBackgroundRun?.();
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
if (!active || isSupervisorDetachedError(error)) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const taskId = activeTaskIdRef.current;
|
|
682
|
+
const text = isAbortError(error)
|
|
683
|
+
? "cancelled · request stopped"
|
|
684
|
+
: error instanceof Error ? error.message : String(error);
|
|
685
|
+
if (!messagesRef.current.some((message) => message.from === "system" && message.text === text && message.taskId === (taskId ?? undefined))) {
|
|
686
|
+
await appendVisibleMessage({ from: "system", text }, taskId ?? undefined, { persist: false });
|
|
687
|
+
}
|
|
688
|
+
await orchestrator.acknowledgeBackgroundRun?.();
|
|
689
|
+
setCanRetryTask(taskId ? await orchestrator.canRetryTask(taskId) : false);
|
|
690
|
+
}
|
|
691
|
+
finally {
|
|
692
|
+
if (activeRunControllerRef.current === controller) {
|
|
693
|
+
activeRunControllerRef.current = null;
|
|
694
|
+
}
|
|
695
|
+
if (active) {
|
|
696
|
+
setRoutePending(null);
|
|
697
|
+
setRouteAnnouncement(null);
|
|
698
|
+
busyRef.current = false;
|
|
699
|
+
setBusy(false);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
void restoreBackgroundRun();
|
|
704
|
+
return () => {
|
|
705
|
+
active = false;
|
|
706
|
+
};
|
|
707
|
+
}, [cwd, orchestrator]);
|
|
636
708
|
useEffect(() => {
|
|
637
709
|
if (workers.length === 0 || !status) {
|
|
638
710
|
return;
|
|
@@ -768,8 +840,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
768
840
|
process.stdout.write("\x1b[?2004h\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l");
|
|
769
841
|
const commitChatInputUpdate = (update, previousValue, previousCursor) => {
|
|
770
842
|
if (update.exit) {
|
|
771
|
-
|
|
772
|
-
exitRef.current();
|
|
843
|
+
requestAppExit();
|
|
773
844
|
return false;
|
|
774
845
|
}
|
|
775
846
|
if (busyRef.current) {
|
|
@@ -882,8 +953,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
882
953
|
if (currentView === "chat" && routeFallbackPromptRef.current) {
|
|
883
954
|
const fallbackChunks = tokenizeRawInput(chunk);
|
|
884
955
|
if (fallbackChunks.some((fallbackChunk) => isExitShortcut(fallbackChunk, {}))) {
|
|
885
|
-
|
|
886
|
-
exitRef.current();
|
|
956
|
+
requestAppExit();
|
|
887
957
|
return;
|
|
888
958
|
}
|
|
889
959
|
if (fallbackChunks.some((fallbackChunk) => isStatusDetailsShortcut(fallbackChunk, {}))) {
|
|
@@ -933,8 +1003,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
933
1003
|
if (currentView === "roles") {
|
|
934
1004
|
const roleChunks = tokenizeRawInput(chunk);
|
|
935
1005
|
if (roleChunks.some((roleChunk) => isExitShortcut(roleChunk, {}))) {
|
|
936
|
-
|
|
937
|
-
exitRef.current();
|
|
1006
|
+
requestAppExit();
|
|
938
1007
|
return;
|
|
939
1008
|
}
|
|
940
1009
|
if (roleChunks.some((roleChunk) => isStatusDetailsShortcut(roleChunk, {}))) {
|
|
@@ -1049,8 +1118,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1049
1118
|
}
|
|
1050
1119
|
if (currentView === "status") {
|
|
1051
1120
|
if (isExitShortcut(chunk, {})) {
|
|
1052
|
-
|
|
1053
|
-
exitRef.current();
|
|
1121
|
+
requestAppExit();
|
|
1054
1122
|
return;
|
|
1055
1123
|
}
|
|
1056
1124
|
if (isStatusDetailsShortcut(chunk, {}) || chunk === "\x1b") {
|
|
@@ -1068,8 +1136,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1068
1136
|
}
|
|
1069
1137
|
if (currentView === "sessions") {
|
|
1070
1138
|
if (isExitShortcut(chunk, {})) {
|
|
1071
|
-
|
|
1072
|
-
exitRef.current();
|
|
1139
|
+
requestAppExit();
|
|
1073
1140
|
return;
|
|
1074
1141
|
}
|
|
1075
1142
|
if (taskSessionDetailsRef.current) {
|
|
@@ -1422,8 +1489,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1422
1489
|
if (currentView === "features") {
|
|
1423
1490
|
const featureChunks = tokenizeRawInput(chunk);
|
|
1424
1491
|
if (featureChunks.some((featureChunk) => isExitShortcut(featureChunk, {}))) {
|
|
1425
|
-
|
|
1426
|
-
exitRef.current();
|
|
1492
|
+
requestAppExit();
|
|
1427
1493
|
return;
|
|
1428
1494
|
}
|
|
1429
1495
|
const pendingCancel = featureCancelPromptRef.current;
|
|
@@ -1575,8 +1641,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1575
1641
|
if (currentView === "collaboration") {
|
|
1576
1642
|
const timelineChunks = tokenizeRawInput(chunk);
|
|
1577
1643
|
if (timelineChunks.some((timelineChunk) => isExitShortcut(timelineChunk, {}))) {
|
|
1578
|
-
|
|
1579
|
-
exitRef.current();
|
|
1644
|
+
requestAppExit();
|
|
1580
1645
|
return;
|
|
1581
1646
|
}
|
|
1582
1647
|
for (const timelineChunk of timelineChunks) {
|
|
@@ -1666,8 +1731,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1666
1731
|
}
|
|
1667
1732
|
if (currentView === "workers") {
|
|
1668
1733
|
if (isExitShortcut(chunk, {})) {
|
|
1669
|
-
|
|
1670
|
-
exitRef.current();
|
|
1734
|
+
requestAppExit();
|
|
1671
1735
|
return;
|
|
1672
1736
|
}
|
|
1673
1737
|
if (isWorkerOverviewShortcut(chunk, {}) || chunk === "\x1b") {
|
|
@@ -1730,8 +1794,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1730
1794
|
if (currentView === "router") {
|
|
1731
1795
|
const routerChunks = tokenizeRawInput(chunk);
|
|
1732
1796
|
if (routerChunks.some((routerChunk) => isExitShortcut(routerChunk, {}))) {
|
|
1733
|
-
|
|
1734
|
-
exitRef.current();
|
|
1797
|
+
requestAppExit();
|
|
1735
1798
|
return;
|
|
1736
1799
|
}
|
|
1737
1800
|
for (const routerChunk of routerChunks) {
|
|
@@ -1768,8 +1831,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1768
1831
|
if (currentView === "worker") {
|
|
1769
1832
|
for (const workerChunk of tokenizeRawInput(chunk)) {
|
|
1770
1833
|
if (isExitShortcut(workerChunk, {})) {
|
|
1771
|
-
|
|
1772
|
-
exitRef.current();
|
|
1834
|
+
requestAppExit();
|
|
1773
1835
|
return;
|
|
1774
1836
|
}
|
|
1775
1837
|
if (workerSearchRef.current.open) {
|
|
@@ -2024,6 +2086,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2024
2086
|
nativeAttachRequestSequenceRef.current += 1;
|
|
2025
2087
|
nativeAttachRef.current?.process.kill();
|
|
2026
2088
|
nativeAttachRef.current = null;
|
|
2089
|
+
orchestrator.detachBackgroundRuns?.();
|
|
2027
2090
|
activeRunControllerRef.current?.abort();
|
|
2028
2091
|
collaborationLoadSequenceRef.current += 1;
|
|
2029
2092
|
routerLoadSequenceRef.current += 1;
|
|
@@ -2039,8 +2102,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2039
2102
|
return;
|
|
2040
2103
|
}
|
|
2041
2104
|
const shutdown = () => {
|
|
2042
|
-
|
|
2043
|
-
exitRef.current();
|
|
2105
|
+
requestAppExit();
|
|
2044
2106
|
};
|
|
2045
2107
|
if (shutdownSignal.aborted) {
|
|
2046
2108
|
shutdown();
|
|
@@ -2049,6 +2111,11 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2049
2111
|
shutdownSignal.addEventListener("abort", shutdown, { once: true });
|
|
2050
2112
|
return () => shutdownSignal.removeEventListener("abort", shutdown);
|
|
2051
2113
|
}, [shutdownSignal]);
|
|
2114
|
+
function requestAppExit() {
|
|
2115
|
+
orchestrator.detachBackgroundRuns?.();
|
|
2116
|
+
activeRunControllerRef.current?.abort();
|
|
2117
|
+
exitRef.current();
|
|
2118
|
+
}
|
|
2052
2119
|
async function copyVisibleView() {
|
|
2053
2120
|
const payload = visibleClipboardPayload(viewRef.current);
|
|
2054
2121
|
if (!payload?.text.trim()) {
|
|
@@ -2196,8 +2263,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2196
2263
|
useInput((inputKey, key) => {
|
|
2197
2264
|
if (view === "worker") {
|
|
2198
2265
|
if (isExitShortcut(inputKey, key)) {
|
|
2199
|
-
|
|
2200
|
-
exitRef.current();
|
|
2266
|
+
requestAppExit();
|
|
2201
2267
|
return;
|
|
2202
2268
|
}
|
|
2203
2269
|
if (isNewConversationShortcut(inputKey, key) && !busy) {
|
|
@@ -2419,14 +2485,14 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2419
2485
|
function settleRouteFallbackChoice(choice) {
|
|
2420
2486
|
routeFallbackResolverRef.current?.(choice);
|
|
2421
2487
|
}
|
|
2422
|
-
async function appendVisibleMessage(message, taskId) {
|
|
2488
|
+
async function appendVisibleMessage(message, taskId, options = {}) {
|
|
2423
2489
|
const visibleMessage = taskId ? { ...message, taskId } : message;
|
|
2424
2490
|
setMessages((current) => {
|
|
2425
2491
|
const next = [...current, visibleMessage];
|
|
2426
2492
|
messagesRef.current = next;
|
|
2427
2493
|
return next;
|
|
2428
2494
|
});
|
|
2429
|
-
if (!persistChatMessage) {
|
|
2495
|
+
if (!persistChatMessage || options.persist === false) {
|
|
2430
2496
|
return;
|
|
2431
2497
|
}
|
|
2432
2498
|
try {
|
|
@@ -2513,13 +2579,17 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2513
2579
|
: false);
|
|
2514
2580
|
await refreshRoleConfigurationState(nextMemory.activeTaskId);
|
|
2515
2581
|
setRouteAnnouncement(null);
|
|
2516
|
-
await appendVisibleMessage({ from: "system", text: result.summary }, nextMemory.activeTaskId ?? undefined);
|
|
2582
|
+
await appendVisibleMessage({ from: "system", text: result.summary }, nextMemory.activeTaskId ?? undefined, { persist: !orchestrator.persistsRunResults });
|
|
2583
|
+
await orchestrator.acknowledgeBackgroundRun?.();
|
|
2517
2584
|
if (result.mode === "complex" && parseTaskResultSummary(result.summary)) {
|
|
2518
2585
|
setTaskResultExpanded(true);
|
|
2519
2586
|
}
|
|
2520
2587
|
}
|
|
2521
2588
|
catch (error) {
|
|
2522
2589
|
setRouteAnnouncement(null);
|
|
2590
|
+
if (isSupervisorDetachedError(error)) {
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2523
2593
|
const retryTaskId = activeTaskIdRef.current;
|
|
2524
2594
|
if (retryTaskId) {
|
|
2525
2595
|
setCanRetryTask(await orchestrator.canRetryTask(retryTaskId));
|
|
@@ -2528,7 +2598,8 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2528
2598
|
await appendVisibleMessage({
|
|
2529
2599
|
from: "system",
|
|
2530
2600
|
text: isAbortError(error) ? "cancelled · request stopped" : error instanceof Error ? error.message : String(error)
|
|
2531
|
-
}, retryTaskId ?? undefined);
|
|
2601
|
+
}, retryTaskId ?? undefined, { persist: !orchestrator.persistsRunResults });
|
|
2602
|
+
await orchestrator.acknowledgeBackgroundRun?.();
|
|
2532
2603
|
}
|
|
2533
2604
|
finally {
|
|
2534
2605
|
if (activeRunControllerRef.current === controller) {
|
|
@@ -2574,19 +2645,24 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
2574
2645
|
setCanRetryTask(false);
|
|
2575
2646
|
await refreshRoleConfigurationState(taskId);
|
|
2576
2647
|
setRouteAnnouncement(null);
|
|
2577
|
-
await appendVisibleMessage({ from: "system", text: result.summary }, taskId);
|
|
2648
|
+
await appendVisibleMessage({ from: "system", text: result.summary }, taskId, { persist: !orchestrator.persistsRunResults });
|
|
2649
|
+
await orchestrator.acknowledgeBackgroundRun?.();
|
|
2578
2650
|
if (parseTaskResultSummary(result.summary)) {
|
|
2579
2651
|
setTaskResultExpanded(true);
|
|
2580
2652
|
}
|
|
2581
2653
|
}
|
|
2582
2654
|
catch (error) {
|
|
2583
2655
|
setRouteAnnouncement(null);
|
|
2656
|
+
if (isSupervisorDetachedError(error)) {
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2584
2659
|
setCanRetryTask(await orchestrator.canRetryTask(taskId));
|
|
2585
2660
|
await refreshRoleConfigurationState(taskId);
|
|
2586
2661
|
await appendVisibleMessage({
|
|
2587
2662
|
from: "system",
|
|
2588
2663
|
text: isAbortError(error) ? "cancelled · retry stopped" : error instanceof Error ? error.message : String(error)
|
|
2589
|
-
}, taskId);
|
|
2664
|
+
}, taskId, { persist: !orchestrator.persistsRunResults });
|
|
2665
|
+
await orchestrator.acknowledgeBackgroundRun?.();
|
|
2590
2666
|
}
|
|
2591
2667
|
finally {
|
|
2592
2668
|
if (activeRunControllerRef.current === controller) {
|
|
@@ -3574,7 +3650,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
3574
3650
|
if (view === "workspace") {
|
|
3575
3651
|
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) }) }));
|
|
3576
3652
|
}
|
|
3577
|
-
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), featureEditingModel: featureAssignmentPrompt?.modelEdit ?? null, taskSessionAction: sessionCenterMode === "conversations"
|
|
3653
|
+
return (_jsx(AppShell, { view: view, cwd: cwd, taskId: activeTaskId, busy: busy, detachable: backgroundRunAttached, controllable: backgroundRunControllable, 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, busyDetachable: backgroundRunAttached, busyControllable: backgroundRunControllable, 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), featureEditingModel: featureAssignmentPrompt?.modelEdit ?? null, taskSessionAction: sessionCenterMode === "conversations"
|
|
3578
3654
|
? mainConversationAction?.type === "rename"
|
|
3579
3655
|
? { type: "rename", value: mainConversationAction.value, cursor: mainConversationAction.cursor }
|
|
3580
3656
|
: mainConversationAction?.type === "delete"
|
package/dist/tui/AppShell.js
CHANGED
|
@@ -6,8 +6,8 @@ import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
|
|
|
6
6
|
import { TUI_THEME } from "./theme.js";
|
|
7
7
|
const APP_HEADER_ROOMY_SEPARATOR = " · ";
|
|
8
8
|
const APP_HEADER_COMPACT_SEPARATOR = " ";
|
|
9
|
-
export function AppShell({ view, cwd, taskId, statusText, contentHeight = 20, terminalWidth = process.stdout.columns || 120, showStatusBar = true, children, input, error = null }) {
|
|
10
|
-
const header = headerParts({ view, cwd, taskId, terminalWidth });
|
|
9
|
+
export function AppShell({ view, cwd, taskId, statusText, contentHeight = 20, terminalWidth = process.stdout.columns || 120, showStatusBar = true, busy = false, detachable = false, controllable = true, children, input, error = null }) {
|
|
10
|
+
const header = headerParts({ view, cwd, taskId, terminalWidth, busy, detachable, controllable });
|
|
11
11
|
const headerSegments = headerDisplaySegments(header);
|
|
12
12
|
const headerSeparatorText = headerSeparator(terminalWidth);
|
|
13
13
|
const headerLeadingWidth = terminalWidth > 1 ? 1 : 0;
|
|
@@ -186,7 +186,11 @@ function headerParts(input) {
|
|
|
186
186
|
view: nano ? "" : input.terminalWidth <= 24 && input.view === "native" ? "nat" : narrow ? shortViewLabel(input.view) : viewLabel(input.view),
|
|
187
187
|
task: showTask ? ultraNarrow ? ultraCompactTaskId(task) : narrow ? task : `#${task}` : "",
|
|
188
188
|
project: tiny || ultraNarrow ? "" : compactHeaderProject(input.cwd, veryNarrow ? 10 : narrow ? 16 : 40),
|
|
189
|
-
shortcut:
|
|
189
|
+
shortcut: input.busy && input.detachable
|
|
190
|
+
? input.controllable
|
|
191
|
+
? narrow ? "^C detach" : "Esc stop · ^C detach"
|
|
192
|
+
: narrow ? "^C detach" : "observe · ^C detach"
|
|
193
|
+
: narrow ? shortShortcutHint(input.view) : shortcutHint(input.view)
|
|
190
194
|
}, contentWidth, separator);
|
|
191
195
|
}
|
|
192
196
|
function fitHeaderParts(parts, contentWidth, separator) {
|
package/dist/tui/InputBar.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { compactEndByDisplayWidth, compactTailByDisplayWidth, displayWidth } from "./display-width.js";
|
|
4
4
|
import { TUI_THEME } from "./theme.js";
|
|
5
|
-
export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, featureEditingModel = null, taskSessionAction = null, taskSessionsIncludeArchived = false, taskSessionQuery = "", mainConversationSessions = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, clipboardNotice = null, roleScope = "next", roleEditingModel = null, roleCanApply = true, roleSaving = false, roleHasOverride = false, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
|
|
5
|
+
export function InputBar({ mode, ready = true, busy = false, busyDetachable = false, busyControllable = true, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, featureEditingModel = null, taskSessionAction = null, taskSessionsIncludeArchived = false, taskSessionQuery = "", mainConversationSessions = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, clipboardNotice = null, roleScope = "next", roleEditingModel = null, roleCanApply = true, roleSaving = false, roleHasOverride = false, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
|
|
6
6
|
const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
|
|
7
7
|
const fillRail = providedTerminalWidth !== undefined || typeof process.stdout.columns === "number";
|
|
8
8
|
if (clipboardNotice) {
|
|
@@ -113,7 +113,7 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
|
|
|
113
113
|
const hints = routeFallbackInputHints(terminalWidth);
|
|
114
114
|
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: hints.detail }) : null] }));
|
|
115
115
|
}
|
|
116
|
-
const busyText = chatBusyDisplayValue(terminalWidth);
|
|
116
|
+
const busyText = chatBusyDisplayValue(terminalWidth, busyDetachable, busyControllable);
|
|
117
117
|
const prompt = busy ? "run" : ">";
|
|
118
118
|
if (busy) {
|
|
119
119
|
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(busyText ? `${prompt} ${busyText}` : prompt), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, bold: true, children: prompt }), busyText ? (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, children: " " }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.warning, children: busyText })] })) : null] }));
|
|
@@ -370,13 +370,19 @@ function selectChatPlaceholder(terminalWidth, candidates) {
|
|
|
370
370
|
function chatPlaceholderValueWidth(terminalWidth) {
|
|
371
371
|
return Math.max(1, terminalWidth - (terminalWidth >= 10 ? 6 : 4));
|
|
372
372
|
}
|
|
373
|
-
export function chatBusyDisplayValue(terminalWidth) {
|
|
373
|
+
export function chatBusyDisplayValue(terminalWidth, detachable = false, controllable = true) {
|
|
374
374
|
if (terminalWidth < 14) {
|
|
375
375
|
return "";
|
|
376
376
|
}
|
|
377
377
|
if (terminalWidth < 22) {
|
|
378
378
|
return "busy";
|
|
379
379
|
}
|
|
380
|
+
if (detachable && !controllable) {
|
|
381
|
+
return terminalWidth >= 34 ? "observing · ^C detach" : "^C detach";
|
|
382
|
+
}
|
|
383
|
+
if (terminalWidth >= 48 && detachable) {
|
|
384
|
+
return "working · Esc stop · ^C detach";
|
|
385
|
+
}
|
|
380
386
|
if (terminalWidth >= 34) {
|
|
381
387
|
return "working · Esc stop";
|
|
382
388
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.
|
|
1
|
+
export const version = "0.4.0";
|
|
@@ -10,6 +10,7 @@ export class MockWorkerAdapter {
|
|
|
10
10
|
await spec.onNativeSession?.(nativeSessionId);
|
|
11
11
|
await setStatus(spec, "running", "mock-running", `${spec.role} mock worker running`, nativeSessionId);
|
|
12
12
|
await appendText(spec.outputLogPath, `[mock:${spec.role}] started\n`);
|
|
13
|
+
await waitForConfiguredMockDelay(spec);
|
|
13
14
|
if (spec.role === "judge") {
|
|
14
15
|
if (spec.prompt.includes("# Role:") && spec.prompt.includes("· Final acceptance")) {
|
|
15
16
|
const criterionIds = promptJsonArray(spec.prompt, "Required acceptance criterion ids");
|
|
@@ -67,6 +68,23 @@ export class MockWorkerAdapter {
|
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
async function waitForConfiguredMockDelay(spec) {
|
|
72
|
+
const configured = Number(spec.modelConfig?.env?.PCT_MOCK_DELAY_MS ?? 0);
|
|
73
|
+
const delayMs = Number.isFinite(configured) ? Math.min(10000, Math.max(0, Math.trunc(configured))) : 0;
|
|
74
|
+
if (delayMs === 0 || spec.signal?.aborted) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await new Promise((resolve) => {
|
|
78
|
+
const timer = setTimeout(finish, delayMs);
|
|
79
|
+
const signal = spec.signal;
|
|
80
|
+
function finish() {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
signal?.removeEventListener("abort", finish);
|
|
83
|
+
resolve();
|
|
84
|
+
}
|
|
85
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
70
88
|
async function cancelMockWorker(spec, nativeSessionId) {
|
|
71
89
|
await appendText(spec.outputLogPath, "[mock] cancelled\n");
|
|
72
90
|
await setStatus(spec, "cancelled", "mock-cancelled", `${spec.role} mock worker cancelled`, nativeSessionId);
|