parallel-codex-tui 0.3.2 → 0.4.1
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 +42 -7
- package/dist/cli-args.js +45 -2
- package/dist/cli-help.js +3 -0
- package/dist/cli.js +44 -1
- package/dist/orchestrator/orchestrator.js +26 -4
- package/dist/supervisor/client.js +442 -0
- package/dist/supervisor/operations.js +116 -0
- package/dist/supervisor/protocol.js +177 -0
- package/dist/supervisor/runner.js +238 -0
- package/dist/supervisor/store.js +216 -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/README.md
CHANGED
|
@@ -6,7 +6,13 @@ Built with Codex-assisted development.
|
|
|
6
6
|
|
|
7
7
|
## Current Release
|
|
8
8
|
|
|
9
|
-
`v0.
|
|
9
|
+
`v0.4.1` is available from [npm](https://www.npmjs.com/package/parallel-codex-tui/v/0.4.1) and as a [GitHub Release](https://github.com/allendred/parallel-codex-tui/releases/tag/v0.4.1). It adds a non-interactive Supervisor control plane: `--runs` lists persisted background runs, `--runs --json` provides versioned machine-readable status, and `--cancel-run [id]` cancels a selected or latest active run without opening the TUI. The status payload excludes requests, prompts, logs, command arguments, and environment values.
|
|
10
|
+
|
|
11
|
+
Main and parallel executions run in a detached, per-request Supervisor after the Router resolves in the foreground. `Ctrl+C` closes the outer TUI without stopping an attached run, reopening the same workspace restores its live events or terminal result, and `Esc` remains the explicit cancellation command. One TUI owns the control lease while additional TUIs observe the same run; an observer takes control automatically after the prior controller detaches or exits.
|
|
12
|
+
|
|
13
|
+
Supervisor requests, state, events, commands, controller ownership, acknowledgements, and diagnostic output are retained under `.parallel-codex/supervisor/runs/<run-id>/`. The Supervisor writes the final chat result itself, so completion survives a closed terminal without duplicate history when the TUI is reopened. Unexpected Supervisor exit is converted into a durable failed state instead of leaving an indefinitely running UI.
|
|
14
|
+
|
|
15
|
+
In the Task session center, `Ctrl+F` searches durable evidence across Tasks and Turns, Feature names, roles, Providers, models, Worker states, summaries, and native session ids. Filters such as `turn:"中文输入" role:actor provider:codex state:done` can be combined with ordinary Unicode text; live async results reject stale responses, Enter keeps the filter, Esc restores the previously applied query, and `X` clears it.
|
|
10
16
|
|
|
11
17
|
Every Task export now includes human-readable `report.md` and structured `report.json` beside the complete raw `session/` snapshot. Reports preserve each Turn request and route, Judge requirements/plans/acceptance criteria, supervisor summaries, Feature assignments and latest review evidence, Wave verification results, Worker Provider/model/state, active native session metadata, completion contracts, and final acceptance evidence. Integrated changed paths are compared with the current workspace as `match`, `drift`, `missing`, `unexpected`, or `unavailable`; the newest integrated wave for each path is authoritative, deleted paths are represented correctly, and unsafe or symlink-traversing paths are never read.
|
|
12
18
|
|
|
@@ -33,7 +39,7 @@ Highlights:
|
|
|
33
39
|
- Named Worker Providers support Codex-compatible, Claude-compatible, OpenAI-compatible, Anthropic-compatible, and custom generic commands with independent role, model, environment, permission, resume, and interactive settings.
|
|
34
40
|
- Worker overview, Feature board, collaboration timeline, Task and Main conversation centers, status details, rendered Markdown/Diff/error logs, Unicode search, keyboard navigation, mouse scrolling, and configurable themes share one terminal UI system.
|
|
35
41
|
|
|
36
|
-
Release acceptance includes a real three-Feature Tetris task with parallel Actor/Critic waves and final integration review. A clean `v0.2.5` task also ran Codex Judge and Actor, a buffered Claude Critic that independently executed `node --test` and `npm test`, atomic integration, and a resumed Codex Judge that independently passed all seven acceptance criteria. Real Codex and Claude probes both proved fresh and same-session resume calls; Codex fresh and resume runs executed workspace writes with root-level `-a never`, and Claude automated sessions executed safe Bash tools with `auto` permissions. The semantic Router completed a live classification, and one TUI completed Main calls in two workspaces before restoring the first workspace without leaking chat state. PTY coverage runs in Apple Terminal, tmux, and Zellij profiles at narrow and wide sizes, including status/log equivalence, preserving the native output tail across status-detail round trips, and proving both inline and on-demand file memory after native-session rollover. The deterministic repository suite contains 1,
|
|
42
|
+
Release acceptance includes a real three-Feature Tetris task with parallel Actor/Critic waves and final integration review. A clean `v0.2.5` task also ran Codex Judge and Actor, a buffered Claude Critic that independently executed `node --test` and `npm test`, atomic integration, and a resumed Codex Judge that independently passed all seven acceptance criteria. Real Codex and Claude probes both proved fresh and same-session resume calls; Codex fresh and resume runs executed workspace writes with root-level `-a never`, and Claude automated sessions executed safe Bash tools with `auto` permissions. The semantic Router completed a live classification, and one TUI completed Main calls in two workspaces before restoring the first workspace without leaking chat state. PTY coverage runs in Apple Terminal, tmux, and Zellij profiles at narrow and wide sizes, including status/log equivalence, preserving the native output tail across status-detail round trips, and proving both inline and on-demand file memory after native-session rollover. Supervisor PTY coverage proves detached Main and complex execution, restart recovery, controller/observer takeover, explicit cancellation, process-owner crash recovery, and out-of-process status/cancellation commands. The deterministic repository suite contains 1,363 tests across 143 files: 1,362 pass by default, while one quota-consuming real-Agent test is skipped and passes through `npm run test:real-agents`.
|
|
37
43
|
|
|
38
44
|
Real Provider probes 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.
|
|
39
45
|
|
|
@@ -61,6 +67,9 @@ parallel-codex-tui --doctor --probe-agents
|
|
|
61
67
|
parallel-codex-tui --doctor --probe-router
|
|
62
68
|
parallel-codex-tui --diagnostics
|
|
63
69
|
parallel-codex-tui --diagnostics ./support-bundle
|
|
70
|
+
parallel-codex-tui --workspace /path/to/project --runs
|
|
71
|
+
parallel-codex-tui --workspace /path/to/project --runs --json
|
|
72
|
+
parallel-codex-tui --workspace /path/to/project --cancel-run
|
|
64
73
|
parallel-codex-tui --workspace /path/to/project
|
|
65
74
|
parallel-codex-tui --theme aurora --workspace /path/to/project
|
|
66
75
|
parallel-codex-tui --theme studio --workspace /path/to/project
|
|
@@ -103,6 +112,10 @@ parallel-codex-tui --doctor --probe-agents
|
|
|
103
112
|
parallel-codex-tui --doctor --probe-router
|
|
104
113
|
parallel-codex-tui --diagnostics
|
|
105
114
|
parallel-codex-tui --diagnostics ./support-bundle
|
|
115
|
+
parallel-codex-tui --workspace /path/to/project --runs
|
|
116
|
+
parallel-codex-tui --workspace /path/to/project --runs --json
|
|
117
|
+
parallel-codex-tui --workspace /path/to/project --cancel-run
|
|
118
|
+
parallel-codex-tui --workspace /path/to/project --cancel-run run-20260721T000000Z-deadbeef
|
|
106
119
|
parallel-codex-tui --version
|
|
107
120
|
```
|
|
108
121
|
|
|
@@ -121,6 +134,27 @@ Without an explicit destination, the bundle is created under `.parallel-codex/di
|
|
|
121
134
|
|
|
122
135
|
The bundle contains `manifest.json`, `report.md`, `report.json`, `doctor.txt`, `tasks.json`, `workers.json`, `router-audit.jsonl`, and bounded Worker log tails. It exports at most the latest 20 tasks, 200 Workers, 100 Router rows, and 200 lines or 64 KiB per Worker log. Workspace, app-root, and home paths are aliased; URL credentials and paths, authorization values, secret assignments, common token formats, and environment-variable values are redacted. Prompts, role instructions, command arguments, source files, and lifetime logs are excluded. Review the bundle before sharing it, because application output can still contain project-specific text that no automatic redactor can fully understand.
|
|
123
136
|
|
|
137
|
+
## Background Runs
|
|
138
|
+
|
|
139
|
+
Inspect a Workspace without opening the TUI:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
parallel-codex-tui --workspace /path/to/project --runs
|
|
143
|
+
parallel-codex-tui --workspace /path/to/project --runs --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Runs are ordered newest first. Text and JSON output include the run and Task ids, operation kind, lifecycle status, control state, timestamps, Supervisor PID, process liveness, controller PID/liveness, and whether the terminal result has been acknowledged by a TUI. Control states distinguish `starting`, `controlled`, `detached`, `settled`, and `stale`. The JSON document is versioned as `1` and intentionally excludes the original request, prompts, Worker logs, command arguments, and environment values.
|
|
147
|
+
|
|
148
|
+
Cancel the latest active run, or address one explicitly:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
parallel-codex-tui --workspace /path/to/project --cancel-run
|
|
152
|
+
parallel-codex-tui --workspace /path/to/project --cancel-run run-20260721T000000Z-deadbeef
|
|
153
|
+
parallel-codex-tui --workspace /path/to/project --cancel-run run-20260721T000000Z-deadbeef --json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Cancellation appends a checked command to the run's existing Supervisor command stream. It does not signal Worker PIDs directly, so the normal orchestrator cancellation, process-tree cleanup, terminal state, chat persistence, and retry evidence remain authoritative. An explicit administrative cancellation can be sent while a TUI is observing or controlling the run. Missing, stale, and already-terminal targets fail without creating a new Workspace or changing historical run evidence.
|
|
157
|
+
|
|
124
158
|
## Quick Start
|
|
125
159
|
|
|
126
160
|
Create a local config in the app root:
|
|
@@ -250,7 +284,7 @@ The doctor output includes `preview:` and `semantic:` ANSI swatch rows so you ca
|
|
|
250
284
|
- `Ctrl+N` leaves the active complex Task and all Worker logs intact on disk, clears only the live selection and retry state, starts a new Main `conversation_id`, and retires the old Main native session. It works from ordinary chat too, so the next request cannot inherit an unrelated native or file-backed conversation; the next complex request creates an independent Task from turn `0001`.
|
|
251
285
|
- Single-feature follow-ups reuse the same Actor/Critic native sessions when available while moving them to the new turn's isolated workspace. File-backed prompt memory keeps the root Turn and the latest 19 previous Turn summaries within a 12,000-character ceiling; longer histories retain an inline count and path to every immutable Turn summary on disk. A failed follow-up retry reuses a complete saved Judge plan, or restores Judge first when the earlier Judge run never produced one.
|
|
252
286
|
- Automated Judge, Actor, Critic, Wave Actor, and Wave Critic runs enforce process-level isolation: Codex is clamped to root-level `-a never` plus `workspace-write`, and Claude is clamped to non-interactive `auto`, even when private command arguments request manual approval or contain a broader bypass mode. Native attach remains an explicit interactive path.
|
|
253
|
-
-
|
|
287
|
+
- Routing remains in the foreground so fallback choices stay interactive. After a route resolves, Main or parallel execution moves into a detached per-run Supervisor. Pressing `Esc` as the controller cancels the active run and records an interrupted complex task as `cancelled`; pressing `Ctrl+C` detaches and exits the outer TUI while execution continues. Reopening the workspace follows persisted events and restores the final result. A second TUI observes the run and automatically acquires the control lease after the previous controller detaches or exits.
|
|
254
288
|
- Failed and cancelled tasks expose `Ctrl+R` retry. Retry keeps the same task and turn, reuses recorded native worker sessions, preserves prior output behind a retry separator, does not route the request again, and reuses the persisted feature dependency plan. A complete Judge snapshot and fully integrated waves are skipped; an unchanged in-progress wave reuses successful Actor and Critic checkpoints and runs only unfinished workers. If the live workspace no longer matches the saved baseline, the stale wave checkpoint is rejected and rebuilt from the current project before workers continue.
|
|
255
289
|
- The shared Main chat holds its own `sessions/main/run-owner.json` lease across prompt initialization, native-session reuse, and Worker completion. A second TUI is rejected before it can clear or overwrite the active Main prompt, log, status, or native session, and startup native-session reconciliation skips a Main session owned by another live TUI. After a hard exit, startup atomically claims the stale Main lease, terminates every verifiable orphan process group, marks active Main Workers `cancelled`, preserves native session ids, and records recovery before exposing the runtime. An unverifiable or still-running Main process blocks startup without changing its status or checkpoints.
|
|
256
290
|
- Task and Main runs use the same lease finalizer, so a successful run cannot report success until its lease is released. If both the run and lease release fail, the top-level error preserves both causes in an `AggregateError` instead of replacing the original Worker failure.
|
|
@@ -477,7 +511,7 @@ Keep `[router.codex]` on `read-only`; routing only classifies requests and does
|
|
|
477
511
|
|
|
478
512
|
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. Claude `--print` with `text` or `json` emits only its final result, so those launches display `working · buffered` and use `timeoutMs` without arming a separate first-output watchdog. Claude `stream-json` remains streaming and retains the configured first-output deadline.
|
|
479
513
|
|
|
480
|
-
In chat, scroll long conversation history with the mouse wheel or PageUp/PageDown; sending a new message returns to the latest reply. A single Up/Down recalls persisted request history, and Ctrl+Up/Down remains an alternative. 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. `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 all history and start a fresh conversation, `Ctrl+E` to open runtime role/model control, `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, active Main/Judge/Actor/Critic Provider/model map, selected historical Provider/model, phase, activity timestamp, native session, and config effective/restart state. Press `Ctrl+S` or `Esc` to return without interrupting the underlying Worker. While a request is running, press `Esc` to
|
|
514
|
+
In chat, scroll long conversation history with the mouse wheel or PageUp/PageDown; sending a new message returns to the latest reply. A single Up/Down recalls persisted request history, and Ctrl+Up/Down remains an alternative. 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. `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 all history and start a fresh conversation, `Ctrl+E` to open runtime role/model control, `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, active Main/Judge/Actor/Critic Provider/model map, selected historical Provider/model, phase, activity timestamp, native session, and config effective/restart state. Press `Ctrl+S` or `Esc` to return without interrupting the underlying Worker. While a request is running, press `Esc` to cancel it or `Ctrl+C` to close the TUI and leave the Supervisor running. 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 fresh conversation, and `Esc` to return to chat.
|
|
481
515
|
|
|
482
516
|
`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.
|
|
483
517
|
|
|
@@ -497,7 +531,7 @@ The Task detail view reads authoritative task files and presents `Project -> Tas
|
|
|
497
531
|
|
|
498
532
|
In a Worker log, `Ctrl+F` searches the final rendered Worker log rather than raw file offsets. Type Unicode text normally; Enter moves to the next match and Up/Down moves backward or forward. `Esc` closes search. The current match is marked with `>` without shifting Diff or source line-number columns. With search closed, press `E` to cycle rendered error lines or `D` to cycle Diff files and hunks.
|
|
499
533
|
|
|
500
|
-
In chat or worker-log views, press `Ctrl+O` to attach to the selected worker's native session inside `parallel-codex-tui`. Native attach runs through a PTY, so full-screen CLIs such as `codex resume {sessionId}` receive a real terminal instead of pipe stdin. Native attach follows outer terminal resize, updating both the rendered screen and child PTY so Codex or Claude reflows at the current dimensions. Input is forwarded to the configured interactive command and output is shown in the native attach panel. When a Codex feature session needs its recorded worker, turn, or feature directories and the interactive command does not already choose a sandbox, native attach adds `--sandbox workspace-write` before its `--add-dir` arguments; an explicitly configured native sandbox remains unchanged. A non-zero native exit recognizes read-only sandbox/add-dir conflicts, untrusted directories, misplaced `--skip-git-repo-check`, missing PTY input, and host permission failures, then appends the exact configuration or terminal action before the exit line scrolls away. Overlapping attach preparations keep only the latest request. Press `Ctrl+]` to return to worker logs; this and closing the outer App terminate the active PTY. An attach preparation that finishes after App shutdown is discarded instead of starting a detached agent. `Ctrl+C` is forwarded to the native agent while attached. In chat and worker-log views,
|
|
534
|
+
In chat or worker-log views, press `Ctrl+O` to attach to the selected worker's native session inside `parallel-codex-tui`. Native attach runs through a PTY, so full-screen CLIs such as `codex resume {sessionId}` receive a real terminal instead of pipe stdin. Native attach follows outer terminal resize, updating both the rendered screen and child PTY so Codex or Claude reflows at the current dimensions. Input is forwarded to the configured interactive command and output is shown in the native attach panel. When a Codex feature session needs its recorded worker, turn, or feature directories and the interactive command does not already choose a sandbox, native attach adds `--sandbox workspace-write` before its `--add-dir` arguments; an explicitly configured native sandbox remains unchanged. A non-zero native exit recognizes read-only sandbox/add-dir conflicts, untrusted directories, misplaced `--skip-git-repo-check`, missing PTY input, and host permission failures, then appends the exact configuration or terminal action before the exit line scrolls away. Overlapping attach preparations keep only the latest request. Press `Ctrl+]` to return to worker logs; this and closing the outer App terminate the active PTY. An attach preparation that finishes after App shutdown is discarded instead of starting a detached agent. `Ctrl+C` is forwarded to the native agent while attached. In chat and worker-log views, `Ctrl+C` detaches an active Supervisor run and exits the outer TUI; during foreground routing it cancels classification before exiting. A second operating-system SIGINT restores terminal modes and forces exit.
|
|
501
535
|
|
|
502
536
|
If a native resume fails because the underlying CLI reports that its context window is full, configure `fallback = "new"` under `[workers.<engine>.nativeSession]`. The old native session is archived as `native-session.retired.json`, removed from active use, and the worker is retried once with the normal fresh-session command. A valid retirement tombstone is also a cross-turn inheritance barrier: a retry or later turn cannot resurrect the same session from an older worker copy, while a newer replacement session remains reusable. A `process-cleanup-error` or `process-ownership-error` always blocks native-session fallback, even when the CLI output also mentions a full context window, so a fresh Worker cannot overlap an untracked or still-running resume process.
|
|
503
537
|
|
|
@@ -545,12 +579,12 @@ The release job installs npm `^11.5.1`, runs on Node `24.15.x`, publishes the pr
|
|
|
545
579
|
To publish a release, update `package.json` and `src/version.ts` to the same version, then push a matching tag:
|
|
546
580
|
|
|
547
581
|
```bash
|
|
548
|
-
VERSION=0.
|
|
582
|
+
VERSION=0.4.1
|
|
549
583
|
git tag "v$VERSION"
|
|
550
584
|
git push origin "v$VERSION"
|
|
551
585
|
```
|
|
552
586
|
|
|
553
|
-
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.
|
|
587
|
+
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.4.1` requires tag `v0.4.1`. Published tags such as `v0.2.10` are immutable and must not be moved or reused.
|
|
554
588
|
|
|
555
589
|
## Publishing Hygiene
|
|
556
590
|
|
|
@@ -558,6 +592,7 @@ You can also run the Release workflow manually and enter the same tag value. The
|
|
|
558
592
|
- `.parallel-codex/role-configuration.json` and `.parallel-codex/role-configuration.next.json` are local runtime role/model selections and are ignored.
|
|
559
593
|
- `.parallel-codex/last-workspace` and `.parallel-codex/workspaces.json` are local workspace-selection state and are ignored.
|
|
560
594
|
- `.parallel-codex/router/` contains local request classification audit records and is ignored.
|
|
595
|
+
- `.parallel-codex/supervisor/` contains detached-run requests, lifecycle state, control commands, event streams, controller leases, acknowledgements, and Supervisor diagnostics; it is local evidence and is ignored.
|
|
561
596
|
- `.parallel-codex/sessions/` contains the workspace chat transcript, task prompts, logs, native session ids, isolated feature workspaces, and conflict evidence; never commit it.
|
|
562
597
|
- `.parallel-codex/diagnostics/` contains redacted support bundles and is ignored; review a bundle before sharing it.
|
|
563
598
|
- `.parallel-codex/probes/` contains failed live Agent probe evidence and is ignored.
|
package/dist/cli-args.js
CHANGED
|
@@ -2,8 +2,29 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { normalizeTuiThemeName, TUI_THEME_NAMES } from "./tui/theme.js";
|
|
4
4
|
import { TaskIdSchema } from "./domain/schemas.js";
|
|
5
|
-
const allowedValueOptions = new Set([
|
|
6
|
-
|
|
5
|
+
const allowedValueOptions = new Set([
|
|
6
|
+
"--app-root",
|
|
7
|
+
"--workspace",
|
|
8
|
+
"-w",
|
|
9
|
+
"--task",
|
|
10
|
+
"-t",
|
|
11
|
+
"--theme",
|
|
12
|
+
"--diagnostics",
|
|
13
|
+
"--cancel-run"
|
|
14
|
+
]);
|
|
15
|
+
const allowedBooleanOptions = new Set([
|
|
16
|
+
"--doctor",
|
|
17
|
+
"--help",
|
|
18
|
+
"-h",
|
|
19
|
+
"--init",
|
|
20
|
+
"--json",
|
|
21
|
+
"--probe-agents",
|
|
22
|
+
"--probe-router",
|
|
23
|
+
"--runs",
|
|
24
|
+
"--themes",
|
|
25
|
+
"--version",
|
|
26
|
+
"-v"
|
|
27
|
+
]);
|
|
7
28
|
export function parseCliArgs(args, cwd) {
|
|
8
29
|
const optionArgs = argsBeforeTerminator(args);
|
|
9
30
|
const appRootFlagIndex = lastFlagIndex(optionArgs, (arg) => arg === "--app-root" || arg.startsWith("--app-root="));
|
|
@@ -11,12 +32,17 @@ export function parseCliArgs(args, cwd) {
|
|
|
11
32
|
const taskFlagIndex = lastFlagIndex(optionArgs, (arg) => arg === "--task" || arg.startsWith("--task=") || arg === "-t" || arg.startsWith("-t="));
|
|
12
33
|
const themeFlagIndex = lastFlagIndex(optionArgs, (arg) => arg === "--theme" || arg.startsWith("--theme="));
|
|
13
34
|
const diagnosticsFlagIndex = lastFlagIndex(optionArgs, (arg) => arg === "--diagnostics" || arg.startsWith("--diagnostics="));
|
|
35
|
+
const cancelRunFlagIndex = lastFlagIndex(optionArgs, (arg) => arg === "--cancel-run" || arg.startsWith("--cancel-run="));
|
|
36
|
+
const cancelRun = cancelRunFlagIndex >= 0;
|
|
37
|
+
const cancelRunId = flagValue(optionArgs, cancelRunFlagIndex);
|
|
14
38
|
const diagnostics = diagnosticsFlagIndex >= 0;
|
|
15
39
|
const doctor = optionArgs.includes("--doctor");
|
|
16
40
|
const help = optionArgs.includes("--help") || optionArgs.includes("-h");
|
|
17
41
|
const init = optionArgs.includes("--init");
|
|
42
|
+
const json = optionArgs.includes("--json");
|
|
18
43
|
const probeAgents = optionArgs.includes("--probe-agents");
|
|
19
44
|
const probeRouter = optionArgs.includes("--probe-router");
|
|
45
|
+
const runs = optionArgs.includes("--runs");
|
|
20
46
|
const themes = optionArgs.includes("--themes");
|
|
21
47
|
const version = optionArgs.includes("--version") || optionArgs.includes("-v");
|
|
22
48
|
const appRootValue = flagValue(optionArgs, appRootFlagIndex);
|
|
@@ -29,14 +55,18 @@ export function parseCliArgs(args, cwd) {
|
|
|
29
55
|
const diagnosticsPath = diagnosticsValue ? resolvePathArg(cwd, diagnosticsValue) : null;
|
|
30
56
|
return {
|
|
31
57
|
appRoot,
|
|
58
|
+
cancelRun,
|
|
59
|
+
cancelRunId,
|
|
32
60
|
diagnostics,
|
|
33
61
|
diagnosticsPath,
|
|
34
62
|
doctor,
|
|
35
63
|
explicitWorkspace,
|
|
36
64
|
help,
|
|
37
65
|
init,
|
|
66
|
+
json,
|
|
38
67
|
probeAgents,
|
|
39
68
|
probeRouter,
|
|
69
|
+
runs,
|
|
40
70
|
workspaceRoot,
|
|
41
71
|
taskId,
|
|
42
72
|
theme,
|
|
@@ -86,6 +116,19 @@ export function validateCliArgs(args) {
|
|
|
86
116
|
if (optionArgs.includes("--probe-agents") && !optionArgs.includes("--doctor")) {
|
|
87
117
|
errors.push("--probe-agents requires --doctor");
|
|
88
118
|
}
|
|
119
|
+
const runs = optionArgs.includes("--runs");
|
|
120
|
+
const cancelRunFlagIndex = lastFlagIndex(optionArgs, (arg) => arg === "--cancel-run" || arg.startsWith("--cancel-run="));
|
|
121
|
+
const cancelRun = cancelRunFlagIndex >= 0;
|
|
122
|
+
const cancelRunId = flagValue(optionArgs, cancelRunFlagIndex);
|
|
123
|
+
if (runs && cancelRun) {
|
|
124
|
+
errors.push("--runs and --cancel-run cannot be used together");
|
|
125
|
+
}
|
|
126
|
+
if (optionArgs.includes("--json") && !runs && !cancelRun) {
|
|
127
|
+
errors.push("--json requires --runs or --cancel-run");
|
|
128
|
+
}
|
|
129
|
+
if (cancelRunId && !/^run-[A-Za-z0-9._-]+$/.test(cancelRunId)) {
|
|
130
|
+
errors.push("Invalid --cancel-run: expected run- followed by letters, numbers, dot, underscore, or hyphen");
|
|
131
|
+
}
|
|
89
132
|
return errors;
|
|
90
133
|
}
|
|
91
134
|
function argsBeforeTerminator(args) {
|
package/dist/cli-help.js
CHANGED
|
@@ -11,6 +11,9 @@ Options:
|
|
|
11
11
|
--init Write .parallel-codex/config.toml if missing
|
|
12
12
|
--doctor Check config, agent commands, and theme palette preview
|
|
13
13
|
--diagnostics [dir] Export a sanitized support bundle; defaults inside the workspace
|
|
14
|
+
--runs List persisted Supervisor runs without opening the TUI
|
|
15
|
+
--cancel-run [id] Cancel a run by id, or the latest active run when omitted
|
|
16
|
+
--json Emit machine-readable output with --runs or --cancel-run
|
|
14
17
|
--probe-agents With --doctor, run fresh + resume probes (uses model quota)
|
|
15
18
|
--probe-router With --doctor, run one live Codex Router request
|
|
16
19
|
-v, --version Print the current version
|
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,9 @@ import { formatTuiThemeCatalog } from "./tui/theme-preview.js";
|
|
|
27
27
|
import { configureTuiTheme } from "./tui/theme.js";
|
|
28
28
|
import { routerDiagnosticsPolicy } from "./tui/RouterDiagnosticsView.js";
|
|
29
29
|
import { version } from "./version.js";
|
|
30
|
+
import { SupervisorOrchestrator } from "./supervisor/client.js";
|
|
31
|
+
import { formatSupervisorCancellation, formatSupervisorRuns, inspectSupervisorRuns, requestSupervisorRunCancellation } from "./supervisor/operations.js";
|
|
32
|
+
import { runSupervisorJob } from "./supervisor/runner.js";
|
|
30
33
|
main().catch((error) => {
|
|
31
34
|
if (error instanceof WorkspaceSelectionCancelledError) {
|
|
32
35
|
return;
|
|
@@ -35,6 +38,12 @@ main().catch((error) => {
|
|
|
35
38
|
process.exit(1);
|
|
36
39
|
});
|
|
37
40
|
async function main() {
|
|
41
|
+
const supervisorRunDir = process.env.PCT_SUPERVISOR_RUN_DIR;
|
|
42
|
+
if (supervisorRunDir) {
|
|
43
|
+
delete process.env.PCT_SUPERVISOR_RUN_DIR;
|
|
44
|
+
await runSupervisorJob(supervisorRunDir);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
38
47
|
const rawArgs = process.argv.slice(2);
|
|
39
48
|
const cliArgErrors = validateCliArgs(rawArgs);
|
|
40
49
|
if (cliArgErrors.length > 0) {
|
|
@@ -91,6 +100,30 @@ async function main() {
|
|
|
91
100
|
runtime.index.close();
|
|
92
101
|
}
|
|
93
102
|
}
|
|
103
|
+
else if (cliArgs.runs || cliArgs.cancelRun) {
|
|
104
|
+
const workspaceRoot = await selectWorkspaceForCli({
|
|
105
|
+
appRoot: cliArgs.appRoot,
|
|
106
|
+
cwd: process.cwd(),
|
|
107
|
+
explicitWorkspace: cliArgs.explicitWorkspace,
|
|
108
|
+
interactive: false
|
|
109
|
+
});
|
|
110
|
+
const config = await loadConfig(cliArgs.appRoot);
|
|
111
|
+
try {
|
|
112
|
+
if (cliArgs.runs) {
|
|
113
|
+
const report = await inspectSupervisorRuns(workspaceRoot, config.dataDir);
|
|
114
|
+
console.log(cliArgs.json ? JSON.stringify(report, null, 2) : formatSupervisorRuns(report));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const result = await requestSupervisorRunCancellation(workspaceRoot, config.dataDir, cliArgs.cancelRunId);
|
|
118
|
+
console.log(cliArgs.json ? JSON.stringify(result, null, 2) : formatSupervisorCancellation(result));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
process.stderr.write(`Supervisor error: ${message}\n`);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
94
127
|
else if (cliArgs.init) {
|
|
95
128
|
if (await pathExists(localConfigPath)) {
|
|
96
129
|
console.log(`Config already exists: ${localConfigPath}`);
|
|
@@ -119,7 +152,7 @@ async function main() {
|
|
|
119
152
|
let instance = null;
|
|
120
153
|
const shutdownController = new AbortController();
|
|
121
154
|
const deferredWorkspaceClosures = new Set();
|
|
122
|
-
const appElement = (state) => (_jsx(App, { config: withUiThemeOverride(state.runtime.config, cliArgs.theme), orchestrator: state.
|
|
155
|
+
const appElement = (state) => (_jsx(App, { config: withUiThemeOverride(state.runtime.config, cliArgs.theme), orchestrator: state.supervisor, cwd: state.runtime.workspaceRoot, initialTaskId: state.initialTaskId, initialRoute: state.initialRoute, initialWorkers: state.initialWorkers, initialCanRetryTask: state.initialCanRetryTask, initialMessages: state.initialMessages, workspaceChoices: state.workspaceChoices, shutdownSignal: shutdownController.signal, loadRouterDiagnostics: async () => {
|
|
123
156
|
const [records, latestConfig] = await Promise.all([
|
|
124
157
|
readRouterAudit(join(state.runtime.routerCwd, "routes.jsonl"), 100),
|
|
125
158
|
loadConfig(cliArgs.appRoot)
|
|
@@ -212,6 +245,7 @@ async function main() {
|
|
|
212
245
|
finally {
|
|
213
246
|
removeSigintHandler();
|
|
214
247
|
restoreInteractiveTerminal();
|
|
248
|
+
closeInteractiveWorkspace(current);
|
|
215
249
|
retryDeferredWorkspaceClosures(deferredWorkspaceClosures);
|
|
216
250
|
}
|
|
217
251
|
}
|
|
@@ -219,6 +253,13 @@ async function main() {
|
|
|
219
253
|
async function loadInteractiveWorkspace(appRoot, workspaceRoot, requestedTaskId) {
|
|
220
254
|
const runtime = await createRuntime(appRoot, workspaceRoot);
|
|
221
255
|
try {
|
|
256
|
+
const supervisor = await SupervisorOrchestrator.open({
|
|
257
|
+
delegate: runtime.orchestrator,
|
|
258
|
+
sessions: runtime.sessions,
|
|
259
|
+
appRoot,
|
|
260
|
+
workspaceRoot: runtime.workspaceRoot,
|
|
261
|
+
dataDir: runtime.config.dataDir
|
|
262
|
+
});
|
|
222
263
|
const preflightPromise = runRuntimePreflight(runtime.config, runtime.workspaceRoot, process.env).catch((error) => ({
|
|
223
264
|
ok: false,
|
|
224
265
|
lines: [`preflight: failed (${error instanceof Error ? error.message : String(error)})`]
|
|
@@ -271,6 +312,7 @@ async function loadInteractiveWorkspace(appRoot, workspaceRoot, requestedTaskId)
|
|
|
271
312
|
const recoveryMessages = startupRecoveryMessages(runtime.recoveredTasks, initialTaskId, runtime.pendingTaskCreations, runtime.index.recovery);
|
|
272
313
|
return {
|
|
273
314
|
runtime,
|
|
315
|
+
supervisor,
|
|
274
316
|
initialTaskId,
|
|
275
317
|
initialRoute,
|
|
276
318
|
initialWorkers,
|
|
@@ -293,6 +335,7 @@ async function loadInteractiveWorkspace(appRoot, workspaceRoot, requestedTaskId)
|
|
|
293
335
|
}
|
|
294
336
|
}
|
|
295
337
|
function closeInteractiveWorkspace(state) {
|
|
338
|
+
state.supervisor.detachBackgroundRuns();
|
|
296
339
|
state.runtime.index.close();
|
|
297
340
|
}
|
|
298
341
|
function retryDeferredWorkspaceClosures(states) {
|
|
@@ -75,10 +75,17 @@ export class Orchestrator {
|
|
|
75
75
|
}
|
|
76
76
|
async handleRequest(input) {
|
|
77
77
|
throwIfCancelled(input.signal);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
let roleSelection;
|
|
79
|
+
let route;
|
|
80
|
+
if (input.route) {
|
|
81
|
+
roleSelection = input.roleSelection ?? await this.roleConfiguration.selectionForRequest();
|
|
82
|
+
route = routeWithRoleSelection(input.route, roleSelection);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const routed = await this.routeInitialRequest(input);
|
|
86
|
+
roleSelection = routed.roleSelection;
|
|
87
|
+
route = routed.route;
|
|
88
|
+
}
|
|
82
89
|
throwIfCancelled(input.signal);
|
|
83
90
|
const workers = [];
|
|
84
91
|
const executionInput = { ...input, roleSelection };
|
|
@@ -115,6 +122,21 @@ export class Orchestrator {
|
|
|
115
122
|
await this.roleConfiguration.writeTurnSelection(turn.dir, roleSelectionWithEngines(route, roleSelection));
|
|
116
123
|
return this.withTaskRunLease(task, () => this.runInitialTask(executionInput, task, route, turn, workers));
|
|
117
124
|
}
|
|
125
|
+
async routeInitialRequest(input) {
|
|
126
|
+
throwIfCancelled(input.signal);
|
|
127
|
+
const routed = await this.routeRequest(input.request, input.cwd, input.signal, "initial", input.onRouteStart, input.onRouteFallback, input.onRouteProgress);
|
|
128
|
+
const roleSelection = input.roleSelection ?? await this.roleConfiguration.selectionForRequest();
|
|
129
|
+
const route = routeWithRoleSelection(routed, roleSelection);
|
|
130
|
+
input.onRoute?.(route);
|
|
131
|
+
throwIfCancelled(input.signal);
|
|
132
|
+
return {
|
|
133
|
+
mode: route.mode,
|
|
134
|
+
taskId: null,
|
|
135
|
+
reason: route.reason,
|
|
136
|
+
route,
|
|
137
|
+
roleSelection
|
|
138
|
+
};
|
|
139
|
+
}
|
|
118
140
|
async handleTaskTurn(input) {
|
|
119
141
|
throwIfCancelled(input.signal);
|
|
120
142
|
const task = this.sessions.taskFromId(input.taskId);
|