parallel-codex-tui 0.1.6 → 0.1.8
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 +8 -6
- package/dist/cli.js +8 -1
- package/dist/tui/App.js +13 -6
- package/dist/tui/keyboard.js +8 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ Built with Codex-assisted development.
|
|
|
6
6
|
|
|
7
7
|
## Current Release
|
|
8
8
|
|
|
9
|
-
`v0.1.
|
|
9
|
+
`v0.1.8` is available from [npm](https://www.npmjs.com/package/parallel-codex-tui/v/0.1.8) and as a [GitHub Release](https://github.com/allendred/parallel-codex-tui/releases/tag/v0.1.8). It preserves the embedded native Agent scrollback when status details are opened and closed by skipping no-op xterm resizes, while still resizing both the screen and PTY after a real terminal size change.
|
|
10
|
+
|
|
11
|
+
The release retains the `v0.1.7` terminal behavior: it keeps terminal scrolling and copying available at the same time without requiring Shift. The outer TUI explicitly disables application mouse tracking, enters the alternate screen, and enables alternate scroll. Kitty and Zellij therefore keep ordinary left-drag selection while translating the wheel into cursor-scroll input for the active TUI view. `Ctrl+Y` remains an additional fallback that copies the visible chat, rendered Worker log, native Agent screen, or structured overview without changing terminal modes. Unicode text and Diff indentation are preserved.
|
|
10
12
|
|
|
11
13
|
The release also includes the stability, collaboration, session-management, Provider, TUI, and release work completed in `v0.1.5`:
|
|
12
14
|
|
|
@@ -17,7 +19,7 @@ The release also includes the stability, collaboration, session-management, Prov
|
|
|
17
19
|
- Worker overview, Feature board, collaboration timeline, status details, rendered Markdown/Diff/error logs, Unicode search, keyboard navigation, mouse scrolling, and configurable themes share one terminal UI system.
|
|
18
20
|
- SQLite migrations and recovery backups, owned-process cleanup, ten-turn Worker-history recovery, packaged CLI installation, and cross-platform CI are covered by automated tests.
|
|
19
21
|
|
|
20
|
-
Release acceptance
|
|
22
|
+
Release acceptance includes a real three-Feature Tetris task with parallel Actor/Critic waves and final integration review. For `v0.1.8`, the persisted Tetris workspace was rechecked with 30 passing project tests, a passing browser smoke test, and a clean build. The repository suite passed 1,241 tests across 119 files, including PTY coverage for alternate-scroll delivery with every application mouse mode disabled and for preserving the native output tail across status-detail round trips. A local Zellij 0.44 probe confirmed that one wheel event becomes three cursor-up events while a left-button drag sends no mouse bytes to the app. The npm tarball was independently installed and returned `parallel-codex-tui 0.1.8`; its installed `node-pty` path also launched a child process successfully.
|
|
21
23
|
|
|
22
24
|
Real Provider probes still depend on valid local CLI credentials. In particular, authenticate the Claude CLI before selecting a Claude-compatible Worker, then run `parallel-codex-tui --doctor --probe-agents` to prove fresh and resumed calls on that machine.
|
|
23
25
|
|
|
@@ -182,7 +184,7 @@ The doctor output includes `preview:` and `semantic:` ANSI swatch rows so you ca
|
|
|
182
184
|
- Simple requests stay in the main TUI flow and do not create Judge, Actor, or Critic workers.
|
|
183
185
|
- Consecutive simple requests reuse the main worker's native session across app restarts when the CLI exposes a session id.
|
|
184
186
|
- The workspace chat transcript is appended to `.parallel-codex/sessions/main/chat.jsonl`; startup restores the latest 200 valid messages and skips isolated corrupt rows. The shared tail reader reads JSONL backward in bounded chunks, so a long-running chat does not require loading its lifetime transcript and a partial or oversized final row cannot hide earlier valid messages.
|
|
185
|
-
- Chat drafts support Unicode-safe Left/Right, Home/End, Backspace, and Delete editing; Up/Down recalls persisted user requests and returns to the exact unsent draft. Long input stays on one row with the visible window centered around the logical cursor.
|
|
187
|
+
- Chat drafts support Unicode-safe Left/Right, Home/End, Backspace, and Delete editing; Ctrl+Up/Down recalls persisted user requests and returns to the exact unsent draft, while plain Up/Down remains available to alternate-scroll wheel input. Long input stays on one row with the visible window centered around the logical cursor.
|
|
186
188
|
- Bracketed multiline paste stays in one draft instead of submitting the first line; logical line breaks and tabs appear as `↵` and `⇥` in the single-row input until the complete request is submitted.
|
|
187
189
|
- Complex requests create a session under `.parallel-codex/sessions/`.
|
|
188
190
|
- Task lifecycle is a checked state machine. A task enters `routed` only after the turn and route files are durable, and enters `ready_for_pair` only after Judge artifacts validate; invalid phase skips are rejected before metadata or event projections change. Persisted follow-up turns that still carry an older `done` state are recovered as cancelled and retryable on startup instead of being hidden as completed.
|
|
@@ -429,7 +431,7 @@ Keep `[router.codex]` on `read-only`; routing only classifies requests and does
|
|
|
429
431
|
|
|
430
432
|
The process adapter sends each role prompt to stdin and records stdout/stderr in `output.log`. Worker success is separate from the operating-system exit code: a total, first-output, idle, or stdin watchdog failure remains failed even when the terminated CLI handles `SIGTERM` and exits with code `0`, so the next role never starts from a timed-out checkpoint. `firstOutputTimeoutMs` owns silent startup, `idleTimeoutMs` starts only after real stdout/stderr activity, and `timeoutMs` remains the hard ceiling; equal or longer secondary watchdogs do not race the total deadline.
|
|
431
433
|
|
|
432
|
-
In chat, scroll long conversation history with the mouse wheel or PageUp/PageDown; sending a new message returns to the latest reply.
|
|
434
|
+
In chat, scroll long conversation history with the mouse wheel, plain Up/Down, or PageUp/PageDown; sending a new message returns to the latest reply. The outer TUI never enables application mouse tracking, so ordinary left-drag selection and system copy work without Shift while alternate-scroll keeps the wheel active in chat and Worker logs. Use Ctrl+Up/Down for persisted request history. `Ctrl+Y` also copies the current visible chat, rendered Worker log, native Agent screen, or structured overview without changing terminal modes. macOS uses `pbcopy`; Linux uses `wl-copy`, `xclip`, or `xsel` when available and falls back to OSC 52. After a complex task completes, use the same controls to move through its structured result and press `Ctrl+D` to toggle details. Press `Ctrl+N` to preserve the current session and start a new task, `Ctrl+W` to open worker logs, and `Tab` to cycle the selected worker when workers exist. `Ctrl+S` opens the same status detail view from chat, logs, Worker overview, or an embedded native session; it keeps the footer compact while exposing the complete route, reason, selected Provider/model, phase, activity timestamp, and native session. Press `Ctrl+S` or `Esc` to return without interrupting the underlying Worker. While a request is running, press `Esc` to stop it. After a failed or cancelled task, press `Ctrl+R` to retry the same turn or `Ctrl+N` to start independently. In worker-log views, scroll with the mouse wheel, Up/Down, or PageUp/PageDown, press `Tab` to cycle workers, `Ctrl+N` to start a new task, and `Esc` to return to chat.
|
|
433
435
|
|
|
434
436
|
`Ctrl+B` opens a live Worker overview without replacing the `Ctrl+W` log shortcut. It summarizes every Judge, Actor, and Critic by turn, role, named Provider, persisted model, state, phase, latest summary, and native-session availability. The selected active worker gets a live activity line showing its first-output deadline while starting and its idle deadline after output begins. The line stays muted while healthy, changes to warning for the final 20% and danger once overdue. Up/Down, PageUp/PageDown, the mouse wheel, or `Tab` changes the selected worker; Enter or `Ctrl+W` opens its rendered log, `Ctrl+O` attaches to its native session, and `Esc` returns to the originating chat or log view. Router diagnostics and the project picker also return to the overview when opened from there.
|
|
435
437
|
|
|
@@ -468,12 +470,12 @@ The release job installs npm `^11.5.1`, runs on Node `24.15.x`, publishes the pr
|
|
|
468
470
|
To publish a release, update `package.json` and `src/version.ts` to the same version, then push a matching tag:
|
|
469
471
|
|
|
470
472
|
```bash
|
|
471
|
-
VERSION=0.1.
|
|
473
|
+
VERSION=0.1.8
|
|
472
474
|
git tag "v$VERSION"
|
|
473
475
|
git push origin "v$VERSION"
|
|
474
476
|
```
|
|
475
477
|
|
|
476
|
-
You can also run the Release workflow manually and enter the same tag value. The release tag must match `package.json`; for example, package version `0.1.
|
|
478
|
+
You can also run the Release workflow manually and enter the same tag value. The release tag must match `package.json`; for example, package version `0.1.8` requires tag `v0.1.8`. Published tags such as `v0.1.7` are immutable and must not be moved or reused.
|
|
477
479
|
|
|
478
480
|
## Publishing Hygiene
|
|
479
481
|
|
package/dist/cli.js
CHANGED
|
@@ -158,12 +158,14 @@ async function main() {
|
|
|
158
158
|
taskId
|
|
159
159
|
}) }, state.runtime.workspaceRoot));
|
|
160
160
|
const removeSigintHandler = installInteractiveSigintExitHandler(() => shutdownController.abort());
|
|
161
|
+
enterInteractiveTerminal();
|
|
161
162
|
try {
|
|
162
163
|
instance = render(appElement(current), { exitOnCtrlC: false });
|
|
163
164
|
await instance.waitUntilExit();
|
|
164
165
|
}
|
|
165
166
|
finally {
|
|
166
167
|
removeSigintHandler();
|
|
168
|
+
restoreInteractiveTerminal();
|
|
167
169
|
retryDeferredWorkspaceClosures(deferredWorkspaceClosures);
|
|
168
170
|
}
|
|
169
171
|
}
|
|
@@ -291,7 +293,12 @@ function restoreInteractiveTerminal() {
|
|
|
291
293
|
}
|
|
292
294
|
process.stdin.pause();
|
|
293
295
|
if (process.stdout.isTTY) {
|
|
294
|
-
process.stdout.write("\x1b[?1006l\x1b[?1000l\x1b[?2004l\x1b[?25h");
|
|
296
|
+
process.stdout.write("\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1007l\x1b[?1049l\x1b[?2004l\x1b[?25h");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function enterInteractiveTerminal() {
|
|
300
|
+
if (process.stdout.isTTY) {
|
|
301
|
+
process.stdout.write("\x1b[?1049h\x1b[?1007h\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l");
|
|
295
302
|
}
|
|
296
303
|
}
|
|
297
304
|
function formatStartupError(error) {
|
package/dist/tui/App.js
CHANGED
|
@@ -19,7 +19,7 @@ import { TerminalOutput } from "./TerminalOutput.js";
|
|
|
19
19
|
import { NativeTerminalScreen } from "./terminal-screen.js";
|
|
20
20
|
import { WorkerOutputView } from "./WorkerOutputView.js";
|
|
21
21
|
import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
|
|
22
|
-
import { isAttachShortcut, isCopyShortcut, isExitShortcut, isLogsShortcut, isNewTaskShortcut, isRouterDiagnosticsShortcut, isStatusDetailsShortcut, 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, rawPlainArrowDelta, scrollDelta, workerLogJumpKind } from "./keyboard.js";
|
|
23
23
|
import { createRawInputDecoder, tokenizeRawInput } from "./raw-input-decoder.js";
|
|
24
24
|
import { decodeHtmlEntities } from "./markdown-text.js";
|
|
25
25
|
import { configureTuiTheme, TUI_THEME } from "./theme.js";
|
|
@@ -403,6 +403,10 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
403
403
|
const resizeNativeAttach = () => {
|
|
404
404
|
const cols = nativeAttachTerminalColumns(process.stdout.columns || 120);
|
|
405
405
|
const rows = nativeAttachTerminalRows(process.stdout.rows || 30, Boolean(attachError), config.ui.showStatusBar);
|
|
406
|
+
const dimensions = screen.dimensions();
|
|
407
|
+
if (dimensions.cols === cols && dimensions.rows === rows) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
406
410
|
screen.resize(cols, rows);
|
|
407
411
|
nativeProcess.resize(cols, rows);
|
|
408
412
|
setNativeAttach((current) => current && current.screen === screen
|
|
@@ -591,7 +595,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
591
595
|
}, [config.ui.autoOpenFailedWorker, status?.taskId, workerActivityPolicies, workerRefreshKey]);
|
|
592
596
|
useEffect(() => {
|
|
593
597
|
setRawMode(true);
|
|
594
|
-
process.stdout.write("\x1b[?2004h\x1b[?
|
|
598
|
+
process.stdout.write("\x1b[?2004h\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l");
|
|
595
599
|
const commitChatInputUpdate = (update, previousValue, previousCursor) => {
|
|
596
600
|
if (update.exit) {
|
|
597
601
|
activeRunControllerRef.current?.abort();
|
|
@@ -1067,6 +1071,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1067
1071
|
}
|
|
1068
1072
|
if (collaborationDetailOpenRef.current) {
|
|
1069
1073
|
const detailDelta = mouseScrollDelta(timelineChunk, 3)
|
|
1074
|
+
+ rawPlainArrowDelta(timelineChunk)
|
|
1070
1075
|
+ rawPageScrollDelta(timelineChunk, Math.max(1, outputHeight - 3));
|
|
1071
1076
|
if (detailDelta !== 0) {
|
|
1072
1077
|
setCollaborationScrollOffset((current) => (nextScrollOffset(current, -detailDelta, collaborationMaxScrollOffsetRef.current)));
|
|
@@ -1233,6 +1238,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1233
1238
|
return;
|
|
1234
1239
|
}
|
|
1235
1240
|
const routeDelta = mouseScrollDelta(routerChunk, 3)
|
|
1241
|
+
+ rawPlainArrowDelta(routerChunk)
|
|
1236
1242
|
+ rawPageScrollDelta(routerChunk, Math.max(1, outputHeight - 1));
|
|
1237
1243
|
if (routeDelta !== 0) {
|
|
1238
1244
|
setRouterScrollOffset((current) => (nextScrollOffset(current, -routeDelta, routerMaxScrollOffsetRef.current)));
|
|
@@ -1308,7 +1314,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1308
1314
|
setView("chat");
|
|
1309
1315
|
return;
|
|
1310
1316
|
}
|
|
1311
|
-
const delta = mouseScrollDelta(workerChunk, 3);
|
|
1317
|
+
const delta = mouseScrollDelta(workerChunk, 3) + rawPlainArrowDelta(workerChunk);
|
|
1312
1318
|
if (delta !== 0) {
|
|
1313
1319
|
setWorkerScrollOffset((current) => nextScrollOffset(current, delta, null));
|
|
1314
1320
|
}
|
|
@@ -1374,8 +1380,9 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1374
1380
|
return;
|
|
1375
1381
|
}
|
|
1376
1382
|
const wheelDelta = mouseScrollDelta(chunk, 3);
|
|
1383
|
+
const arrowScrollDelta = rawPlainArrowDelta(chunk);
|
|
1377
1384
|
const pageDelta = rawPageScrollDelta(chunk, Math.max(1, outputHeight - 1));
|
|
1378
|
-
const historyDelta = wheelDelta + pageDelta;
|
|
1385
|
+
const historyDelta = wheelDelta + arrowScrollDelta + pageDelta;
|
|
1379
1386
|
if (historyDelta !== 0 && chatMaxScrollOffsetRef.current > 0) {
|
|
1380
1387
|
setChatScrollOffset((current) => {
|
|
1381
1388
|
const next = nextScrollOffset(current, taskResultExpandedRef.current ? -historyDelta : historyDelta, chatMaxScrollOffsetRef.current);
|
|
@@ -1424,7 +1431,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1424
1431
|
void retryRef.current();
|
|
1425
1432
|
return;
|
|
1426
1433
|
}
|
|
1427
|
-
const draftHistoryDelta = rawHistoryDelta(chunk);
|
|
1434
|
+
const draftHistoryDelta = rawHistoryDelta(chunk) - arrowScrollDelta;
|
|
1428
1435
|
if (draftHistoryDelta !== 0) {
|
|
1429
1436
|
if (busyRef.current) {
|
|
1430
1437
|
return;
|
|
@@ -1487,7 +1494,7 @@ export function App({ config, orchestrator, cwd, initialTaskId = null, initialRo
|
|
|
1487
1494
|
stdinEvents.removeListener("input", handleRawInput);
|
|
1488
1495
|
rawInputDecoderRef.current.end();
|
|
1489
1496
|
chatPasteDecoderRef.current.reset();
|
|
1490
|
-
process.stdout.write("\x1b[?1006l\x1b[?1000l\x1b[?2004l");
|
|
1497
|
+
process.stdout.write("\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?2004l");
|
|
1491
1498
|
setRawMode(false);
|
|
1492
1499
|
};
|
|
1493
1500
|
}, [outputHeight, setRawMode, stdinEvents]);
|
package/dist/tui/keyboard.js
CHANGED
|
@@ -68,6 +68,14 @@ export function rawHistoryDelta(input) {
|
|
|
68
68
|
}
|
|
69
69
|
return delta;
|
|
70
70
|
}
|
|
71
|
+
export function rawPlainArrowDelta(input) {
|
|
72
|
+
let delta = 0;
|
|
73
|
+
for (const match of input.matchAll(/\x1b(?:O([AB])|\[([AB]))/g)) {
|
|
74
|
+
const direction = match[1] ?? match[2];
|
|
75
|
+
delta += direction === "A" ? 1 : -1;
|
|
76
|
+
}
|
|
77
|
+
return delta;
|
|
78
|
+
}
|
|
71
79
|
export function mouseScrollDelta(input, linesPerWheel = 3) {
|
|
72
80
|
let delta = 0;
|
|
73
81
|
for (const match of input.matchAll(/\x1b\[<(\d+);\d+;\d+[mM]/g)) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.1.
|
|
1
|
+
export const version = "0.1.8";
|