parallel-codex-tui 0.1.3 → 0.1.5

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.
Files changed (78) hide show
  1. package/.parallel-codex/config.example.toml +136 -3
  2. package/README.md +299 -21
  3. package/dist/bootstrap.js +37 -17
  4. package/dist/cli-args.js +34 -3
  5. package/dist/cli-help.js +20 -0
  6. package/dist/cli-startup-preflight.js +18 -0
  7. package/dist/cli-startup-recovery.js +82 -0
  8. package/dist/cli-workspace-picker.js +330 -0
  9. package/dist/cli-workspace-transition.js +33 -0
  10. package/dist/cli-workspace.js +7 -71
  11. package/dist/cli.js +234 -24
  12. package/dist/core/collaboration-timeline.js +268 -0
  13. package/dist/core/config-errors.js +14 -0
  14. package/dist/core/config.js +297 -109
  15. package/dist/core/file-store.js +119 -6
  16. package/dist/core/lease-finalization.js +22 -0
  17. package/dist/core/paths.js +7 -0
  18. package/dist/core/process-identity.js +48 -0
  19. package/dist/core/process-mutation-turn.js +128 -0
  20. package/dist/core/process-ownership.js +276 -0
  21. package/dist/core/process-tree.js +90 -0
  22. package/dist/core/router-audit.js +155 -0
  23. package/dist/core/router-redaction.js +31 -0
  24. package/dist/core/router.js +462 -35
  25. package/dist/core/session-index.js +412 -88
  26. package/dist/core/session-manager.js +1110 -40
  27. package/dist/core/task-session-details.js +175 -0
  28. package/dist/core/task-state-machine.js +18 -0
  29. package/dist/core/workspace-commit-recovery.js +118 -0
  30. package/dist/core/workspace.js +19 -11
  31. package/dist/doctor.js +373 -34
  32. package/dist/domain/schemas.js +142 -7
  33. package/dist/orchestrator/collaboration-channel.js +289 -6
  34. package/dist/orchestrator/feature-plan.js +70 -0
  35. package/dist/orchestrator/final-acceptance.js +86 -0
  36. package/dist/orchestrator/judge-artifacts.js +236 -0
  37. package/dist/orchestrator/orchestrator.js +2086 -203
  38. package/dist/orchestrator/prompts.js +168 -5
  39. package/dist/orchestrator/supervisor-summary.js +56 -2
  40. package/dist/orchestrator/workspace-sandbox.js +927 -0
  41. package/dist/tui/App.js +3187 -161
  42. package/dist/tui/AppShell.js +196 -25
  43. package/dist/tui/CollaborationTimelineView.js +327 -0
  44. package/dist/tui/FeatureBoardView.js +232 -0
  45. package/dist/tui/InputBar.js +581 -57
  46. package/dist/tui/RouterDiagnosticsView.js +469 -0
  47. package/dist/tui/StatusBar.js +610 -57
  48. package/dist/tui/StatusDetailView.js +164 -0
  49. package/dist/tui/TaskSessionDetailView.js +222 -0
  50. package/dist/tui/TaskSessionsView.js +210 -0
  51. package/dist/tui/TerminalOutput.js +53 -9
  52. package/dist/tui/WorkerOutputView.js +1404 -161
  53. package/dist/tui/WorkerOverviewView.js +269 -0
  54. package/dist/tui/chat-history.js +25 -0
  55. package/dist/tui/chat-input.js +67 -19
  56. package/dist/tui/chat-paste.js +76 -0
  57. package/dist/tui/display-width.js +41 -3
  58. package/dist/tui/incremental-text-file.js +101 -0
  59. package/dist/tui/keyboard.js +49 -0
  60. package/dist/tui/markdown-text.js +14 -0
  61. package/dist/tui/raw-input-decoder.js +3 -0
  62. package/dist/tui/scrolling.js +2 -1
  63. package/dist/tui/status-line.js +360 -11
  64. package/dist/tui/task-memory.js +13 -1
  65. package/dist/tui/task-result.js +105 -0
  66. package/dist/tui/terminal-screen.js +13 -1
  67. package/dist/tui/theme-contrast.js +144 -0
  68. package/dist/tui/theme-preview.js +109 -0
  69. package/dist/tui/theme.js +158 -0
  70. package/dist/version.js +1 -1
  71. package/dist/workers/capabilities.js +213 -0
  72. package/dist/workers/live-probe.js +177 -0
  73. package/dist/workers/mock-adapter.js +73 -8
  74. package/dist/workers/native-attach.js +106 -16
  75. package/dist/workers/process-adapter.js +572 -77
  76. package/dist/workers/provider.js +26 -0
  77. package/dist/workers/registry.js +12 -20
  78. package/package.json +11 -2
package/README.md CHANGED
@@ -6,7 +6,8 @@ Built with Codex-assisted development.
6
6
 
7
7
  ## Requirements
8
8
 
9
- - Node.js 26+.
9
+ - Node.js 24.15+.
10
+ - macOS or Linux. Windows is not yet covered by CI; use WSL for the supported Linux path.
10
11
  - Codex CLI available as `codex` for Codex routing and Codex workers.
11
12
  - Claude CLI available as `claude` only when you configure Claude workers.
12
13
  - A project workspace you are comfortable letting configured workers edit.
@@ -23,17 +24,24 @@ Run it against the project you want the workers to operate on:
23
24
  cd /path/to/project
24
25
  parallel-codex-tui --init
25
26
  parallel-codex-tui --doctor
27
+ parallel-codex-tui --doctor --probe-agents
28
+ parallel-codex-tui --doctor --probe-router
26
29
  parallel-codex-tui --workspace /path/to/project
30
+ parallel-codex-tui --theme aurora --workspace /path/to/project
31
+ parallel-codex-tui --theme studio --workspace /path/to/project
27
32
  ```
28
33
 
29
34
  Startup resolves the worker project before routing:
30
35
 
31
36
  - `--workspace <path>` opens that project when it already exists.
32
- - If `--workspace <path>` does not exist in an interactive terminal, the CLI shows remembered projects from `.parallel-codex/workspaces.json`; press Enter to create the requested folder, pick a remembered project, or enter another path.
37
+ - If `--workspace <path>` does not exist in an interactive terminal, the themed project picker selects that create target by default; press Enter to create it, move to a remembered project, or choose `New project` and enter another path.
33
38
  - If `--workspace <path>` points to an existing file, the CLI reports that it is not a directory and will not use that file path as the default folder to create.
34
- - Without `--workspace`, an interactive terminal shows remembered projects from `.parallel-codex/workspaces.json`; choose a number or enter `n <path>` to create/open another folder.
39
+ - Without `--workspace`, an interactive terminal shows remembered projects from `.parallel-codex/workspaces.json`; use Up/Down and Enter, a displayed number, or `N` to enter a new folder path. The picker clears before the main chat opens.
35
40
  - In non-interactive startup, the CLI reuses the last remembered workspace, falls back to the current directory if none was saved, and creates an explicit `--workspace` path if needed.
36
41
  - The selected workspace is prepared before any router or worker process starts.
42
+ - Every interactive open and in-place workspace switch runs a quota-free startup preflight before the TUI accepts work. It checks workspace read/write/search access, every CLI required by the active route and role configuration, referenced model environment variables, each named Worker's Codex, Claude, or declared generic CLI capabilities, configured proxy endpoint reachability, and native workspace trust policy. Healthy checks stay quiet; warnings and failures appear in chat with the `parallel-codex-tui --doctor` action. Live model requests remain opt-in through `--probe-agents` and `--probe-router`.
43
+ - While the TUI is idle, press `Ctrl+P` to open the same project picker without exiting. `Esc` returns with the unsent draft intact; selecting or creating a folder locks duplicate picker input, shows the project being opened, rebuilds the runtime in place, and restores that workspace's latest task, route, workers, and chat. A failed open returns to the original view without replacing its runtime or draft.
44
+ - Workspace chat, task files, and `session-index.sqlite` stay isolated under each project. Router configuration and `router/routes.jsonl` remain shared under the app root, so classifications across projects use one global Router audit.
37
45
 
38
46
  From a source checkout, install dependencies and link the local binary:
39
47
 
@@ -49,17 +57,19 @@ For development without linking, run:
49
57
  npm run dev -- --workspace /path/to/project
50
58
  ```
51
59
 
52
- CLI options with values can also be passed as `--workspace=/path/to/project`, `--app-root=/path/to/app`, `--task=task-id`, `-w=/path/to/project`, and `-t=task-id`.
60
+ CLI options with values can also be passed as `--workspace=/path/to/project`, `--app-root=/path/to/app`, `--task=task-id`, `--theme=paper`, `-w=/path/to/project`, and `-t=task-id`.
53
61
 
54
62
  Check available flags or the installed version without starting the TUI:
55
63
 
56
64
  ```bash
57
65
  parallel-codex-tui --help
58
66
  parallel-codex-tui --doctor
67
+ parallel-codex-tui --doctor --probe-agents
68
+ parallel-codex-tui --doctor --probe-router
59
69
  parallel-codex-tui --version
60
70
  ```
61
71
 
62
- `--doctor` checks the configured commands and any `{env:NAME}` references in active worker model environment settings before workers start.
72
+ `--doctor` checks configured automated and interactive commands, `{env:NAME}` references, and the CLI help surfaces required for Codex exec/resume sandboxing and Claude print/resume permissions before workers start. Recognized standard CLI help that lacks a required option fails the check; an opaque wrapper keeps the compatible built-in profile's unverified warning, while an explicit `generic` capability contract is validated without guessing vendor-specific help. Doctor performs these checks for every named Worker Provider used by the active route, rejects an explicitly read-only Codex-compatible interactive sandbox when feature attach needs writable roots, and reminds you that native workspace trust remains an interactive decision. It reports proxy host/port reachability as a local-endpoint check, not as proof that the proxy upstream or model API is healthy. Add `--probe-agents` to make one minimal fresh request and, when configured, one same-session resume request through every active process Worker Provider. This explicit probe uses model quota, removes successful probe artifacts, preserves failed artifacts under `.parallel-codex/probes/`, and exits non-zero when a fresh or resumed request cannot be proven. Fresh sessions use the configured `freshSessionArgs`; Claude-compatible profiles default to a generated native `--session-id`, persisted only after the process emits output, so a silent failed launch cannot create a false resumable session. Add `--probe-router` to run one real classification through the configured Codex Router; the command exits non-zero when that live request falls back or fails. Doctor also reports the loaded TUI theme, core palette values, ANSI swatch previews, and color override values, including any temporary `--theme` override.
63
73
 
64
74
  ## Quick Start
65
75
 
@@ -91,15 +101,117 @@ Then start the TUI:
91
101
  parallel-codex-tui --workspace /path/to/project
92
102
  ```
93
103
 
104
+ ## Parallel Work
105
+
106
+ Limit how many feature-level Actor or Critic agents may run at once:
107
+
108
+ ```toml
109
+ [orchestration]
110
+ maxParallelFeatures = 3 # 1..8
111
+ maxRevisionRounds = 3 # 1..10
112
+ ```
113
+
114
+ Judge may plan up to eight dependency-aware features. The orchestrator keeps dependency order while running at most this many agents concurrently. When one worker fails, already-running peers are allowed to finish cleanly and queued features are not started.
115
+
116
+ Actor and Critic reuse their native sessions across revision rounds. Blocking findings and Actor replies remain in the Feature JSONL mailbox; the task stops with an explicit error when `maxRevisionRounds` is exhausted.
117
+
118
+ Queued Feature workers remain `queued` until they acquire a concurrency slot. Their persisted state changes to `actor_running` or `critic_running` only after the Worker is registered as cancellable, then to `actor_done` or `critic_done` before that active registration is removed. As a result, only `actor_running` and `critic_running` expose Feature cancellation; queued and role-complete Features cannot present a cancel action that has no live process behind it.
119
+
120
+ ## Theme
121
+
122
+ Set the TUI palette in `.parallel-codex/config.toml`:
123
+
124
+ ```toml
125
+ [ui]
126
+ theme = "codex" # codex, graphite, paper, aurora, studio
127
+ showStatusBar = true
128
+ autoOpenFailedWorker = true
129
+
130
+ [ui.colors]
131
+ accent = "ansi256(81)"
132
+ chrome = "ansi256(233)"
133
+ rail = "ansi256(236)"
134
+ surface = "ansi256(234)"
135
+ ```
136
+
137
+ Set `showStatusBar = false` to hide the bottom runtime status line and give that row back to the main content area.
138
+
139
+ Set `autoOpenFailedWorker = false` to keep the chat view open when a restored or running task has a failed worker.
140
+
141
+ `ui.colors` is optional and can override any theme key: `chrome`, `surface`, `rail`, `successSurface`, `dangerSurface`, `text`, `muted`, `accent`, `warning`, `success`, or `danger`. Color values are validated during config load and can use Chalk color names, `#rgb`/`#rrggbb`, `rgb(r,g,b)`, or `ansi256(0..255)`. Unknown UI and color keys are rejected so typos fail fast.
142
+
143
+ For quick previews without editing config, pass `--theme codex`, `--theme graphite`, `--theme paper`, `--theme aurora`, or `--theme studio` with `--doctor`:
144
+
145
+ ```bash
146
+ parallel-codex-tui --theme graphite --doctor
147
+ parallel-codex-tui --theme studio --doctor
148
+ ```
149
+
150
+ To compare every built-in palette without loading config or starting the TUI:
151
+
152
+ ```bash
153
+ parallel-codex-tui --themes
154
+ parallel-codex-tui --theme paper --themes
155
+ ```
156
+
157
+ The theme catalog includes complete palette groups for every built-in theme plus the same `preview:` and `semantic:` ANSI swatch rows. Built-in foreground and background pairs are kept at or above a `4.5:1` contrast ratio on every surface where the TUI renders them.
158
+
159
+ The doctor output includes `preview:` and `semantic:` ANSI swatch rows so you can see the effective terminal colors before starting a worker session. It also audits the 16 foreground/background combinations rendered by the TUI and reports custom pairs below `4.5:1` as warnings. Named colors and ANSI indexes `0..15` use the standard xterm palette for this estimate because terminals may redefine those colors.
160
+
94
161
  ## Behavior
95
162
 
96
163
  - Requests are routed by Codex by default, with a configured simple/complex fallback if the router process fails.
97
164
  - Router classification only receives the user request; workspace selection and session files are kept out of the router prompt.
98
165
  - Simple requests stay in the main TUI flow and do not create Judge, Actor, or Critic workers.
166
+ - Consecutive simple requests reuse the main worker's native session across app restarts when the CLI exposes a session id.
167
+ - 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.
168
+ - 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.
169
+ - 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.
99
170
  - Complex requests create a session under `.parallel-codex/sessions/`.
100
- - Complex requests run Judge -> Actor -> Critic.
171
+ - 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.
172
+ - Turn persistence writes every Turn into a hidden staging directory and atomically renames it to its numbered directory only after `user.md`, `route.json`, and `turn.json` are valid. During startup, complete pending Turns are published and request-only pending Turns are rebuilt from durable latest-route evidence; empty fragments move under `.abandoned` without consuming a Turn number. SQLite startup rebuild indexes only complete numbered Turn directories whose task, turn, request, and route identities agree. The startup notice distinguishes a restored follow-up Turn from missing completion evidence and says `request and route kept` before offering `Ctrl+R resume`.
173
+ - Task creation writes its complete first Turn into a hidden staging directory. Before any Task files appear, an exclusive process-identity claim reserves the Task id, so same-second collisions cannot overwrite another task. After its metadata, route, request, Turn, and creation event are valid, one rename atomically publishes the whole Task. The orchestrator hands the creation claim directly to the task run lease before reading task state or starting Judge. On startup, stale complete Task staging is published and made retryable, incomplete staging is archived under `.abandoned`, and staging owned by another live TUI remains untouched. The startup notice reports archived or externally active creations without duplicating ordinary interrupted-task recovery.
174
+ - Cancellation is rechecked after routing and after acquiring a task lease. An already-cancelled request cannot create a Task, initialize Main, append a Turn, or record a retry. Cancellation that arrives while a lease is being acquired releases that lease before returning, while a completed Router classification may remain in the shared audit without creating workspace-side execution state.
175
+ - Live workspace integration is the cancellation commit point. Cancellation immediately before commit leaves the live project untouched. Once a verified Wave commit succeeds, its integration evidence is durable; the final Wave's summary and Feature decision are completed, and task evidence finishes as `done` even if cancellation arrives after that commit. In a multi-Wave task, cancellation is observed again before the next Wave, so earlier committed checkpoints remain retry-safe without starting more work.
176
+ - Live commit persistence writes `integration.pending.json` as a durable two-phase commit intent before changing project files. On retry, each live path must still match either the Wave baseline or integration snapshot; a partial apply resumes without rerunning completed Workers, and a fully applied Wave with a lost final checkpoint is promoted using its original changed-path set. Any third-state content or extra live path blocks recovery and preserves the intent for inspection instead of rebuilding from an ambiguous workspace.
177
+ - New intents use `atomic-claim-v1`. Before a replacement or deletion, the existing target is atomically moved to a commit-scoped `.backup` and checked again against the Wave baseline. File and symlink publication refuses to replace a path that reappears, so an edit racing after preflight remains live while the pending intent, `.tmp`, and `.backup` preserve recovery evidence. Recovery finishes only owned artifacts that still match the integration and baseline snapshots. Legacy temp-only intents remain recoverable, but their ambiguous missing-target state is never assigned to the new protocol.
178
+ - Each pending `commit_id` scopes deterministic replacement temp files to one live commit. Recovery completes an owned replacement only when its content and mode exactly match the integration snapshot and its target is still baseline, missing, or already integrated. A foreign or mismatched temp file blocks recovery and remains on disk for inspection. After the final integrated checkpoint is durable, intent cleanup is best-effort and cannot downgrade the committed task. Startup removes a leftover intent only when the final integrated checkpoint matches it; mismatched or unreadable evidence remains on disk for retry and inspection.
179
+ - Complex requests run Judge -> Actor -> Critic. Judge also writes a bounded `features.json` dependency plan.
180
+ - Judge runs from its task-owned worker directory, reads the selected project without treating it as a write target, and snapshots `requirements.md`, `plan.md`, `acceptance.md`, role briefs, and `features.json` into each numbered turn.
181
+ - Before Actor starts, the orchestrator parses the Judge Markdown into stable requirement, plan, and acceptance entries and writes `judge-validation.json`. Missing lists, placeholder-only content, duplicate ids, and unknown requirement references fail before implementation; valid legacy list snapshots are normalized during retry without rerunning Judge.
182
+ - Independent features run as parallel Actor batches followed by parallel Critic batches. Dependent features start only after their prerequisite wave is approved and integrated.
183
+ - Parallel Actor, Critic, and revision batches honor `[orchestration].maxParallelFeatures` (default `3`).
184
+ - Each planned feature gets an isolated Actor implementation workspace, a disposable Critic review clone, worker directories, logs, status, native session ids, and a mailbox under `features/<turn>-<feature>`; the shared dialogue remains in `dialogue/actor-critic.jsonl`. Concurrent text and JSONL appends inside one TUI are serialized per resolved file path, preserving call order without blocking independent feature files; one failed append does not poison later writes.
185
+ - Feature revision mailboxes are a checked protocol rather than prompt-only convention. A `REVISION_REQUIRED` Critic must write one `{"id":"C-001","severity":"blocker","summary":"..."}` row per blocker to `critic-findings.jsonl`; the revision Actor must write matching `{"finding_id":"C-001","status":"fixed","notes":"..."}` rows to `actor-replies.jsonl`. Missing, malformed, unacknowledged, or contradictory findings stop the task before live integration. The Supervisor snapshots pending, inconsistent, and approved fixed/open state in `finding-resolution.json`; the collaboration timeline retains the original findings and replies as history and uses resolution evidence instead of counting every reply as a fix.
186
+ - Parallel workspace isolation works for both Git and non-Git projects. A wave records `baseline`, per-feature `features/` workspaces, refreshable `reviews/` copies, `staging`, and conflict evidence under `sessions/<task>/workspaces/turn-<turn>/wave-<wave>/` while excluding `.git` and the configured runtime data directory.
187
+ - Feature Critic implementation writes are discarded with the review clone. After an Actor revision, the same Critic native session rechecks a freshly cloned review path; only Actor workspace changes can enter staging.
188
+ - Approved feature changes are three-way merged into a task-owned integration workspace first; independent edits to the same text file are combined automatically. The live workspace remains unchanged during this stage.
189
+ - Every staged wave gets a combined Wave Critic run in a disposable verification copy. It checks the full Judge acceptance criteria, cross-feature behavior, tests, and builds. A missing `APPROVED`/`REVISION_REQUIRED` decision fails safely instead of being treated as approval.
190
+ - When combined verification requests changes, a session-backed Wave Actor fixes the integration workspace and the same Wave Critic native session reviews it again. Only an explicit final `APPROVED` allows the wave to reach the live workspace.
191
+ - Wave Actor/Critic prompts, logs, immutable `verification-review-01.md`/`02.md` rounds, native session ids, minimized additional-directory permissions, and `verification.json` audit evidence are stored with the task and remain available through worker logs and native attach.
192
+ - Overlapping edits fail the task without partially changing the live workspace. The chat error names the conflicting paths and points to marker files under the wave's `conflicts/` directory; `Ctrl+R` retries in the same task and native worker sessions.
193
+ - If the live workspace changes after a wave baseline is captured, staging or commit stops with the changed paths instead of silently absorbing an escaped worker edit or overwriting concurrent user work.
194
+ - Feature workspaces persist with the task so native attach can reopen the exact worker cwd. Delete an old task session when its audit trail and attachable workspaces are no longer needed.
195
+ - Complex follow-ups stay in the active task, append a numbered turn, restore the same Judge session to re-clarify the new direction, and save a turn-local Judge snapshot. A new multi-feature plan can start parallel workers immediately.
196
+ - Every numbered turn keeps its own immutable Judge, Actor, and Critic worker directories. Later turns use suffixed ids such as `judge-codex-0002`; the log viewer retains earlier workers, cycles them in turn order, and restores the same order after restart. The new worker record may reuse the prior native session id without overwriting the prior turn's prompt, status, log, or native-session metadata.
197
+ - `Ctrl+N` leaves the active complex task intact on disk while clearing its live context, worker selection, retry state, and status so the next complex request creates an independent task from turn `0001`.
198
+ - Single-feature follow-ups reuse the same Actor/Critic native sessions when available while moving them to the new turn's isolated workspace; prompts include up to five prior turn summaries as file-backed memory. A failed follow-up retry reuses a complete saved Judge plan, or restores Judge first when the earlier Judge run never produced one.
199
+ - Automated Judge, Actor, Critic, Wave Actor, and Wave Critic runs enforce process-level isolation: Codex is clamped to `workspace-write`, and Claude is clamped to `acceptEdits`, even when private command arguments contain a broader bypass mode. Native attach remains an explicit interactive path.
200
+ - Pressing `Esc` while a request is running stops the router or active worker and records an interrupted complex task as `cancelled`; exiting the outer TUI also terminates the active run.
201
+ - 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.
202
+ - 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.
203
+ - 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.
204
+ - Every complex run holds a task-owned `run-owner.json` lease, so another live TUI cannot concurrently retry or append a complex turn to the same task. Stale or corrupt lease replacement is serialized through file-backed claim intents, so exactly one recovery owner can proceed and a competing cleaner cannot delete its replacement lease. A retry revalidates the task state and reloads its latest turn, request, and route only after acquiring that lease, so an older caller cannot rerun a task another TUI already completed. Automated Worker processes run in owned process groups and persist `process.json` with their PID and OS process-start fingerprint while active; the role prompt is sent only after that ownership evidence is durable. If ownership recording fails, the process group is terminated before it receives the prompt and the Worker records `process-ownership-error`. Worker cleanup is shown as `running/process-stopping` until the full command tree is confirmed gone. Timeout and cancellation terminate the full group; normal parent exit also terminates any remaining descendants before terminal status and ownership removal. A detached leader PID reused after exit is never treated as its former process group, while a leaderless group that still owns the reserved ID is still terminated. If cleanup cannot be verified, the Worker records `process-cleanup-error` and keeps `process.json` so startup recovery remains fail-closed; only verified cleanup removes a valid record. Final log, status, native-session callback, or ownership-removal failures settle exactly once by rejecting the adapter call, retaining ownership evidence, and attempting a best-effort `process-finalization-error` status instead of leaving orchestration pending. Task failure convergence attempts every Feature status and the task status independently, so one broken Feature status cannot block the task from becoming retryable. State convergence errors are surfaced alongside the original Worker error. On retry, a missing or invalid Feature status is rebuilt independently. The recovery records `feature.status_recovered` and does not clear its spec, Actor worklog, replies, or Critic findings.
205
+ - Workspace startup reconciles nonterminal tasks only when their recorded TUI owner is gone. Recovery atomically claims the same task lease before touching status, logs, checkpoints, or processes, so concurrent TUI startups cannot recover one task twice or race a new retry. Recovery commits cancellation only after every recorded process group is confirmed stopped or safely identified as a reused PID. Matching orphan Worker process groups are terminated, active Worker and feature states become `cancelled`, native session ids and feature checkpoints stay intact, and the task becomes immediately retryable. A reused PID with a different start fingerprint is never signalled, and a task still owned by another live TUI is left untouched. An unverifiable or still-running process blocks startup, reports its `process.json` path, and leaves task, Worker, and feature states unchanged so `Ctrl+R` cannot overlap the old process. The restored chat shows `checkpoints kept · Ctrl+R resume`; recovery events remain in `events.jsonl`.
206
+ - Native session metadata is also recoverable. `native-session.json` is the active commit point and is projected into Worker status plus the `workers` and `native_sessions` SQLite rows. A matching `native-session.retired.json` tombstone always wins over a leftover active session file, while a different replacement session id remains active. Before restoring workers, startup reconciles the active file, Worker status, and both SQLite projections before the first TUI frame; tasks owned by another live TUI are left untouched.
207
+ - Session index rebuilding publishes either the previous complete snapshot or the new complete snapshot. Clearing and repopulating task, turn, Worker, and native-session rows runs in one SQLite transaction; a filesystem or database failure rolls the whole rebuild back, while concurrent readers keep seeing the previous committed snapshot.
208
+ - Terminal completion is evidence-guarded. The latest `supervisor-summary.md`, feature `decisions.md`, and approved feature states are published before task status can become `done`; duplicate task and feature state writes are idempotent, every real task transition records its `from` and `to` state, and a complete `done` task cannot regress unless a new follow-up turn has first been created. Each committed task state change carries a unique transition marker in `meta.json`; same-process retries repair missing projections immediately, and startup repairs a missing event or SQLite projection from that marker before another transition can replace it. Startup also audits legacy `done` tasks that have an integrated latest-turn checkpoint: missing summaries or unfinished feature states are recovered to `cancelled`, then `Ctrl+R` rebuilds final evidence without rerunning completed workers. Legacy log-only `done` sessions without integration proof remain untouched.
209
+ - Simple follow-up questions run through the persistent Main native session with the active task directory, original request, up to five recent turn summaries, valid worker statuses, and log tails as file-backed context. They hold the active task lease while committing the route, taking that context snapshot, and running Main, so the answer cannot race an in-progress complex turn. A failed or cancelled Main answer replaces the transient `running` state with its real terminal state and releases both task and Main leases, so the next question can proceed. They do not start another Judge, Actor, or Critic turn.
101
210
  - Worker prompts, logs, status, and outputs are written to disk.
102
- - The bottom status line shows the active task state.
211
+ - The bottom status line shows the active task state and feature progress such as `wave 1/2 · actor 2/3`, `wave 1/2 · integration 0/1`, and `wave 1/2 · verification 0/1`. While classification is running, it follows the real subprocess through `starting`, `waiting output`, `diagnostics`, `receiving`, `parsing`, and `stopping`, keeps live elapsed/limit progress, identifies a non-default Router runner, and identifies the path as `direct` or `via <proxy-host:port>`. Before output arrives it shows the active first-output deadline and total ceiling; after activity begins it shows total elapsed progress and the idle watchdog. As soon as a fresh initial or follow-up route settles, chat temporarily inserts a themed route rail with `route · <mode> · <source>` followed by the indented Router reason before Main or parallel execution finishes, then replaces it with the final answer or error without adding noise to persisted chat history. Task retry restores its saved route without announcing it as a new Router decision. The status line then replaces the wait state with the final route source, duration, or fallback cause.
212
+ - Completed complex tasks open as a structured result at the top of the chat viewport. Requirements, the complete bounded Actor worklog, authoritative integrated changed paths, Critic review, verification evidence, and findings render as themed full-width sections instead of losing everything after each section's first line. Changed files come from the workspace integration result rather than an Actor claim; verification keeps the Critic decision and reported test/build evidence. Multi-feature delivery uses the same result protocol and includes combined Wave verification. Long results start at the title and scroll toward findings; `Ctrl+D` toggles the focused result between full detail and its five-line compact summary, and beginning the next message collapses it automatically. Result lookup follows the persisted task id, so restoring one task cannot display another task's result. Empty state and ordinary short chat remain adjacent to the input.
213
+ - Restarting an existing task restores the latest persisted route evidence in the bottom status line, including fallback cause and duration. Follow-up Router classification has no task-side effects: it records the attempt in shared `routes.jsonl`, then a complex turn or simple question refreshes `sessions/<task>/latest-route.json` only after acquiring the task lease. A conflicting TUI leaves the previous committed route and turn files untouched. A corrupt latest-route record safely falls back to the latest worker turn and then the task's initial route record.
214
+ - Task session ids are single `task-...` path segments. CLI `--task` input, persisted metadata, startup scanning, and SQLite rebuilding reject path separators and ignore a task whose `meta.json` id differs from its directory, so restoring a session cannot escape or alias the workspace session root.
103
215
 
104
216
  ## Router
105
217
 
@@ -111,12 +223,52 @@ defaultMode = "auto"
111
223
 
112
224
  [router.codex]
113
225
  command = "codex"
114
- args = ["exec", "--skip-git-repo-check", "--sandbox", "workspace-write", "--color", "never", "-"]
115
- timeoutMs = 120000
116
- fallback = "complex"
226
+ args = ["exec", "--ephemeral", "--ignore-rules", "-c", "model_reasoning_effort=low", "--skip-git-repo-check", "--sandbox", "read-only", "--color", "never", "-"]
227
+ timeoutMs = 30000
228
+ firstOutputTimeoutMs = 15000
229
+ idleTimeoutMs = 15000
230
+ maxOutputBytes = 1048576
231
+ maxAttempts = 2
232
+ retryDelayMs = 500
233
+ followUpTimeoutMs = 20000
234
+ fallback = "simple"
117
235
  ```
118
236
 
119
- Set `defaultMode = "simple"` / `defaultMode = "complex"` to force one path. In `auto` mode, routing is semantic through Codex. If the router process fails or returns invalid JSON, `fallback = "simple"` or `fallback = "complex"` decides the path; there is no keyword-only router.
237
+ Set `defaultMode = "simple"` / `defaultMode = "complex"` to force one path. In `auto` mode, routing is semantic through an ephemeral, low-reasoning Codex run. Only `simple` and `complex` are accepted route modes; harmless casing and surrounding whitespace are normalized, while invalid JSON or an unknown mode uses the configured fallback and appears as `invalid output` in the status bar. `fallback = "simple"` or `fallback = "complex"` supplies the non-interactive fallback path; the safe default is `simple` and there is no keyword-only router. `firstOutputTimeoutMs` stops a silent process, `idleTimeoutMs` resets after every stdout or stderr chunk, and `timeoutMs` remains the hard ceiling even while output continues. `maxOutputBytes` bounds combined Router stdout and stderr in memory; exceeding it stops the process tree and reports invalid output instead of waiting for a timeout. The 1 MiB default can be configured from 1 KiB through 16 MiB. The 15-second first-output and idle defaults stay below the 30-second total ceiling, so their failure kinds remain visible. Two repeated silent starts settle in about 30.5 seconds instead of two full 30-second waits; an idle deadline begins after the latest output, while the hard ceiling still bounds every attempt. A watchdog only runs separately when its limit is lower than the active initial or follow-up total timeout, avoiding competing timers at the same deadline.
238
+
239
+ On POSIX systems the Router command runs in its own process group. Timeout, cancellation, and stdin failure send `SIGTERM`, wait briefly for graceful cleanup, then use `SIGKILL` when any group member remains. A retry, fallback choice, or completed cancellation is not exposed until that command tree is confirmed stopped, so a previous classifier cannot overlap the next Router or Worker. If termination cannot be verified after `SIGKILL`, the request fails closed instead of continuing around a live process.
240
+
241
+ `maxAttempts = 2` retries one transient classification failure after `retryDelayMs = 500`; set it to `1` to disable automatic retry. First-output and idle watchdogs plus explicit network/proxy failures are retryable. Total timeouts, authentication, rate limits, unavailable commands, and invalid route JSON go directly to the existing Main/Parallel/Retry/Cancel choice because another immediate attempt is unlikely to help. The status bar shows `retry 2/2` during cancellable backoff and records whether the final decision recovered through an automatic or manual retry. `duration_ms` remains the current Codex attempt, while `router_total_duration_ms` is the accumulated attempts and automatic backoff; Router diagnostics label them `attempt` and `journey`. Every failed attempt is retained with `router_fallback_resolution = "auto-retry"`; exhausting the automatic budget still preserves the manual `R` retry path.
242
+
243
+ When semantic routing falls back inside the TUI, execution pauses before Main or any task worker starts. `1` selects Main, `2` selects Parallel, `R` retries Codex routing, and `Esc` cancels the request. Active-task follow-ups use the same fallback choice, so a routing outage cannot silently turn a requested implementation change into a Main-only answer. Every failed and retried classification is retained in the shared audit with `router_attempt` and `router_fallback_resolution`; choosing Main or Parallel preserves the original failure evidence while recording the user's decision.
244
+
245
+ Valid `[router]` changes are reloaded before the next classification without restarting the TUI, so mode, Codex command arguments, timeouts, output limit, retry budget/backoff, fallback, and proxy environment updates take effect on the next request. Worker, pairing, role, orchestration, data-directory, and UI changes still require a restart because those runtime components are constructed at startup.
246
+
247
+ From chat or worker-log views, press `Ctrl+G` to open the global Router diagnostics view. It reads only the latest 100 valid rows from the shared audit without invoking a model and shows each route's source, duration, fallback cause, scope, and workspace, plus its attempt and automatic/user resolution, alongside the current watchdog and retry policy. The same bounded reverse reader skips malformed or oversized tail rows without reading the whole lifetime audit. `Tab` toggles between all workspaces and the current workspace; the scope summary always retains the loaded route total. The health row separates recovered automatic retries from terminal fallbacks. The latency panel includes p50/p95/max and each new semantic route saves the sanitized Router executable name, all three watchdog limits, retry attempt limit/backoff, the triggered `first-output`/`idle`/`total` timeout kind, proxy-configured flag, normalized proxy source/variable, sanitized proxy host:port, and normalized failure kind. Only the executable basename is retained, so a custom runner is identifiable without exposing its installation path. Successful Codex latency excludes fallback wait time, and the budget row marks each timeout budget as healthy, tight, or high against successful p95 without changing configuration. It requires at least three successful samples before judging a budget; smaller samples remain `learning`. New process traces preserve spawn time, first-output time, process duration, stdout/stderr byte counts, and failure stage, distinguishing a process that never started, failed while receiving input, stayed silent, emitted only diagnostics, exited, or returned an invalid response. Each new trace exposes dispatch, spawn, first output, process, parse, and total stages, with I/O byte evidence on a separate line. Every fallback adds a bounded diagnosis and a concrete next action based on that evidence. Proxy context is correlation evidence, not proof that the proxy caused a failure; a configured proxy remains context rather than a proven cause. The classifier receives the original user request, while `routes.jsonl` stores a sanitized diagnostic copy. URL userinfo, paths, queries, fragments, authorization values, secret assignments, and common provider tokens are removed before display or audit persistence; legacy records are sanitized again when read. Scroll with the mouse wheel or PageUp/PageDown; `Ctrl+G` refreshes it and `Esc` returns with the chat draft intact.
248
+
249
+ Every new Router fallback persists an authoritative `router_failure_kind` alongside its stage, timeout kind, and proxy context. The status rail and restored tasks prefer that structured evidence over reason-text matching; an old fallback that cannot be classified shows `unknown failure` instead of omitting the cause.
250
+
251
+ ### Proxy Environment
252
+
253
+ Some CLI runtimes do not inherit the macOS System Settings proxy. Configure the router explicitly when direct OpenAI connections are blocked:
254
+
255
+ ```toml
256
+ [router.codex.env]
257
+ HTTP_PROXY = "http://127.0.0.1:7890"
258
+ HTTPS_PROXY = "http://127.0.0.1:7890"
259
+ ALL_PROXY = "socks5h://127.0.0.1:7890"
260
+ NO_PROXY = "localhost,127.0.0.1"
261
+
262
+ [workers.codex.model.env]
263
+ HTTP_PROXY = "http://127.0.0.1:7890"
264
+ HTTPS_PROXY = "http://127.0.0.1:7890"
265
+ ALL_PROXY = "socks5h://127.0.0.1:7890"
266
+ NO_PROXY = "localhost,127.0.0.1"
267
+ ```
268
+
269
+ `router.codex.env` applies only to semantic classification. `workers.<id>.model.env` applies to fresh/resumed runs and embedded native attach for that named Worker Provider. Keep these values in local `config.toml`, which is ignored by Git.
270
+
271
+ Run `parallel-codex-tui --doctor` after changing proxy settings. Doctor checks referenced environment variables, reports when a macOS system proxy is not inherited by Codex subprocesses, and labels configured proxy host/port checks as local-endpoint reachability without printing credentials. Then run `parallel-codex-tui --doctor --probe-router` when you also need to verify the real Codex Router path through that proxy; this explicit probe can take up to `router.codex.timeoutMs`. A timed-out request is identified as `first output timeout`, `idle timeout after stdout/stderr`, or `total timeout`; `via <proxy-host:port>` remains context, not a claim that the proxy caused the failure. The audit never exposes the proxy URL or credentials.
120
272
 
121
273
  ## Mock Mode
122
274
 
@@ -146,43 +298,169 @@ args = ["--model", "{model}"]
146
298
  [workers.codex.model.env]
147
299
  OPENAI_API_KEY = "{env:OPENAI_API_KEY}"
148
300
 
301
+ [workers.codex.capabilities]
302
+ profile = "codex"
303
+ writableDirArgs = ["--add-dir", "{dir}"]
304
+ freshSessionArgs = []
305
+
149
306
  [workers.codex.interactive]
150
307
  command = "codex"
151
308
  args = ["resume", "{sessionId}"]
309
+ forkArgs = ["fork", "{sessionId}"]
152
310
 
153
311
  [workers.claude]
154
312
  command = "claude"
155
313
  args = ["--print", "--permission-mode", "acceptEdits", "--output-format", "text"]
156
314
 
315
+ [workers.claude.capabilities]
316
+ profile = "claude"
317
+ writableDirArgs = ["--add-dir", "{dir}"]
318
+ freshSessionArgs = ["--session-id", "{sessionId}"]
319
+
157
320
  [workers.claude.interactive]
158
321
  command = "claude"
159
322
  args = ["--resume", "{sessionId}"]
323
+ forkArgs = ["--resume", "{sessionId}", "--fork-session"]
324
+ ```
325
+
326
+ `model.args` and `model.env` apply to both automated worker runs and embedded native attach sessions. Native attach appends the rendered model arguments after `interactive.args`, so third-party `{model}` and `{provider}` selections remain active when you press `Ctrl+O`.
327
+
328
+ `model.provider` names the remote model service; `capabilities.profile` describes the local CLI protocol. Worker Provider IDs are lowercase names using letters, digits, or `_`; excluding `-` keeps generated Worker directory names unambiguous. Existing `codex`, `claude`, and `mock` IDs remain compatible, while additional profiles can inherit `codex`, `claude`, another named profile, or conservative `generic` defaults.
329
+
330
+ Each role can select an independent command, model, environment, and permission contract through `[pairing]`. This example uses an OpenAI-compatible coding CLI for Judge/Actor and an Anthropic-compatible coding CLI for Main/Critic:
331
+
332
+ ```toml
333
+ [workers.openai_compat]
334
+ extends = "codex"
335
+ command = "openai-compatible-coder"
336
+ args = ["exec", "--sandbox", "workspace-write", "-"]
337
+
338
+ [workers.openai_compat.model]
339
+ name = "third-party-code-model"
340
+ provider = "openai-compatible"
341
+ args = ["--model", "{model}", "--provider", "{provider}"]
342
+
343
+ [workers.openai_compat.model.env]
344
+ OPENAI_API_KEY = "{env:OPENAI_COMPAT_API_KEY}"
345
+ OPENAI_BASE_URL = "{env:OPENAI_COMPAT_BASE_URL}"
346
+
347
+ [workers.openai_compat.interactive]
348
+ command = "openai-compatible-coder"
349
+
350
+ [workers.anthropic_compat]
351
+ extends = "claude"
352
+ command = "anthropic-compatible-coder"
353
+
354
+ [workers.anthropic_compat.model]
355
+ name = "third-party-claude-model"
356
+ provider = "anthropic-compatible"
357
+
358
+ [workers.anthropic_compat.model.env]
359
+ ANTHROPIC_API_KEY = "{env:ANTHROPIC_COMPAT_API_KEY}"
360
+ ANTHROPIC_BASE_URL = "{env:ANTHROPIC_COMPAT_BASE_URL}"
361
+
362
+ [workers.anthropic_compat.interactive]
363
+ command = "anthropic-compatible-coder"
364
+
365
+ [pairing]
366
+ main = "anthropic_compat"
367
+ judge = "openai_compat"
368
+ actor = "openai_compat"
369
+ critic = "anthropic_compat"
370
+ ```
371
+
372
+ `extends` inherits the complete command protocol, including capabilities, native-session behavior, watchdogs, and interactive arguments. Override both `command` and `interactive.command` when a wrapper replaces both executables. `assignable = true` exposes a profile to Feature-level Provider cycling; custom profiles default to true, while the reserved deterministic `mock` profile defaults to false. Pairing an unknown profile, unsafe profile IDs, and inheritance cycles fail config validation before startup.
373
+
374
+ For a third-party command with its own syntax, inherit `generic` so parallel-codex-tui does not inject `--sandbox`, `--permission-mode`, or other built-in-only flags:
375
+
376
+ ```toml
377
+ [workers.vendor]
378
+ extends = "generic"
379
+ command = "vendor-coder"
380
+ args = ["run", "--stdin"]
381
+
382
+ [workers.vendor.capabilities]
383
+ profile = "generic"
384
+ writableDirArgs = ["--allow-root", "{dir}"]
385
+ freshSessionArgs = ["--new-session", "{sessionId}"]
386
+
387
+ [workers.vendor.nativeSession]
388
+ enabled = true
389
+ resumeArgs = ["run", "--resume", "{sessionId}", "--stdin"]
390
+
391
+ [workers.vendor.interactive]
392
+ command = "vendor-coder"
393
+ args = ["resume", "{sessionId}"]
394
+ forkArgs = ["branch", "{sessionId}"]
160
395
  ```
161
396
 
162
- Keep `[router.codex]` on `workspace-write`; routing only classifies requests and does not need host-level access. If a trusted local project needs Docker, OrbStack, or other host services, opt into broader worker permissions in your private `.parallel-codex/config.toml` rather than committing them.
397
+ Generic profiles start with native resume disabled, no vendor flags, and the isolated Worker cwd. Set either capability argument list to `[]` when the wrapper needs no extra argument. A non-empty `writableDirArgs` must include `{dir}`, while non-empty `freshSessionArgs` and `interactive.forkArgs` must include `{sessionId}`. Set `forkArgs = []` when a generic CLI cannot fork a native conversation. Doctor validates the declared capability contract without guessing the wrapper's `--help` format; `--doctor --probe-agents` remains the end-to-end fresh/resume check. Every new Worker status records the actual profile ID plus its rendered model/provider snapshot, so later config changes do not relabel historical runs.
398
+
399
+ Customize each role independently; the main role is applied to simple chat, while Judge, Actor, and Critic receive their configured instructions during complex work:
400
+
401
+ ```toml
402
+ [roles.main]
403
+ title = "Guide"
404
+ instructions = ["Answer directly and keep prior context."]
405
+
406
+ [roles.actor]
407
+ title = "Builder"
408
+ instructions = ["Implement small verified changes.", "Record decisions in worklog.md."]
409
+ ```
410
+
411
+ Keep `[router.codex]` on `read-only`; routing only classifies requests and does not need project writes. Automated Judge/Actor/Critic runs also clamp unsafe Codex or Claude permission flags to their isolated workspace policy, so a private `danger-full-access` or bypass flag cannot silently defeat live-workspace isolation. Use native attach for deliberate interactive host-level work such as Docker or OrbStack access.
412
+
413
+ 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.
414
+
415
+ In chat, scroll long conversation history with the mouse wheel or PageUp/PageDown; sending a new message returns to the latest reply. 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 or PageUp/PageDown, press `Tab` to cycle workers, `Ctrl+N` to start a new task, and `Esc` to return to chat.
416
+
417
+ `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.
418
+
419
+ From Worker overview, press `F` to open the file-backed Feature board. It summarizes every planned feature by turn, state, blocked dependencies and open Critic findings while preserving descriptions and Actor replies from the same collaboration files. Use Up/Down, PageUp/PageDown, the mouse wheel, or `Tab` to select a feature; Enter opens the selected feature's collaboration timeline; `R` refreshes immediately; and `Esc` returns to Worker overview. When a timeline was opened from this board, `Esc` returns to the Feature board instead of skipping back to Workers. While the selected feature is running, press `P` twice to pause only its active Actor or Critic process, or press `X` twice to cancel it; the first press opens a confirmation and `Esc` keeps it running. A paused feature preserves completed peers and same-wave checkpoints; select it and press `Ctrl+R` to resume the same task, turn, and native Worker session. After cancellation, already-running peers finish, queued workers stop, and integration remains blocked, so a partial wave never reaches the live workspace. Once the task settles as cancelled, `Ctrl+R` retries the cancelled task with persisted worker sessions; completed peers are loaded from their same-wave checkpoints instead of running again. Checkpoint load, reuse, and recovery events appear in the collaboration timeline.
163
420
 
164
- The process adapter sends each role prompt to stdin and records stdout/stderr in `output.log`.
421
+ For a failed, cancelled, or paused task, select an unfinished Feature and press `M` to edit its Provider assignment. `A` cycles that Feature's Actor through every `assignable` Worker Provider, `C` cycles its Critic, and `M` or `Esc` closes the assignment prompt. The choice is persisted in `features/<turn>-<feature>/assignment.json`, appears on the Feature board and collaboration artifacts, and takes effect on the next `Ctrl+R` retry. A changed role starts a new Provider-specific Worker while every previous Worker log and native-session record remains available. Assignment writes hold the same task lease as execution, so another live TUI cannot change a Feature while it is resuming.
165
422
 
166
- While viewing a worker log, press `Ctrl+O` to attach to the 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. Input is forwarded to the configured interactive command and output is shown in the native attach panel. Press `Ctrl+]` to detach and return to worker logs; `Ctrl+C` is forwarded to the native agent while attached. In chat and worker-log views, press `Ctrl+C` to exit the outer TUI.
423
+ From Worker overview, press `C` to open the file-backed Actor/Critic collaboration timeline. It merges `dialogue/actor-critic.jsonl`, feature status, Critic findings, Actor replies, finding resolution, and Wave events into one chronological view. The timeline follows new file evidence automatically and shows verified `fixed`/`open` counts when resolution evidence exists. Up/Down selects a collaboration event; `Enter` opens its complete event detail, including artifact paths from dialogue, status, Critic findings, Actor replies, and finding resolution; `U` filters to unresolved feature evidence; `Tab` cycles all features and each individual feature; `R` refreshes immediately; the mouse wheel or PageUp/PageDown scrolls history; and `Esc` returns to Worker overview.
167
424
 
168
- 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.
425
+ `Ctrl+T` opens the workspace's persisted Task sessions without exiting the outer TUI. The list shows status, creation time, turn and worker counts, plus the number of distinct native sessions after deduplicating reused `engine + session_id` bindings; `>` marks the selected row and `*` marks the active task. Use Up/Down, PageUp/PageDown, the mouse wheel, or `Tab` to select, Enter to restore, `I` to inspect the complete session hierarchy, `R` to rename with Unicode-safe cursor editing, `A` to archive or unarchive, `D` twice to confirm deletion, `E` to export, `H` to show or hide archived sessions, `Ctrl+N` to start with an empty task context, and `Esc` to return without losing the chat draft. Archived sessions are hidden by default, cannot be restored until unarchived, and are excluded from automatic startup selection. Archive, delete, and export require a terminal task with no live task lease; the active task cannot be archived or deleted. Exports are complete file-backed snapshots under `.parallel-codex/exports/<task>-<timestamp>/` with a versioned manifest and without transient lease files. Restoring a task reloads its route, workers, retry state, and recorded native session ids. The selected active task is stored in `session-index.sqlite` and survives process and workspace switches; `Ctrl+N` persists an intentionally empty active-task context instead of reopening an older task on restart. SQLite schema changes run as ordered, transactional migrations. Existing catalogs receive a pre-migration snapshot, each successful startup/reindex refreshes a healthy backup, and an integrity failure restores that backup or rebuilds the catalog from authoritative task files while preserving the corrupt copy for inspection. `meta.json` and the Worker files remain the rebuild authority.
426
+
427
+ The Task detail view reads authoritative task files and presents `Project -> Task -> Turn -> Worker -> Native session`, including each request, role, engine, configured model, Worker state, native session id, working directory, and last activity time. Historical Workers from earlier turns remain selectable after same-task follow-ups. Use Up/Down or `Tab` to select a Worker, Enter to open its retained log, `C` or `Ctrl+O` to continue the original native session, `B` to ask the native CLI to fork it, `R` to refresh, and `Esc` to return to the Task list. Continue and fork restore the selected Worker's recorded cwd and writable roots while the outer TUI stays open. Codex and Claude have built-in fork commands; a generic CLI exposes `B` only when `interactive.forkArgs` is configured. The child fork is owned and persisted by the native CLI; the indexed parent session remains unchanged.
428
+
429
+ 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.
430
+
431
+ 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, press `Ctrl+C` to exit the outer TUI. An operating-system SIGINT first asks the App to abort its Router or Worker and clean up any PTY; a second SIGINT restores terminal modes and forces exit.
432
+
433
+ 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.
169
434
 
170
435
  ## Release
171
436
 
172
- GitHub Actions runs CI on pushes and pull requests to `main`.
437
+ GitHub Actions runs CI on Ubuntu with Node.js 24.15 and 26, plus macOS with Node.js 26, for pushes and pull requests to `main`. Every job also packs the release artifact, installs that tarball into a clean global prefix, and executes the installed `--version` and `--help` entrypoints.
438
+
439
+ Releases publish to npm through npm Trusted Publishing with GitHub OIDC. Do not configure `NPM_TOKEN` for the release workflow. In npm, configure Trusted Publishing for organization/user `allendred`, repository `parallel-codex-tui`, workflow filename `release.yml`, and allowed action `npm publish`. The package already exists on npm, so future releases can use Trusted Publishing directly. You can also configure the trust relationship from an authenticated npm CLI session:
440
+
441
+ ```bash
442
+ npm install -g npm@^11.15.0
443
+ npm trust github parallel-codex-tui --repo allendred/parallel-codex-tui --file release.yml --allow-publish --dry-run
444
+ npm trust github parallel-codex-tui --repo allendred/parallel-codex-tui --file release.yml --allow-publish --yes
445
+ ```
446
+
447
+ Creating or listing trusted publishers may require npm two-factor authentication in the browser.
448
+
449
+ The release job installs npm `^11.5.1`, runs on Node `24.15.x`, publishes the prepared tarball through OIDC, waits for the package to become visible on npm, installs it globally in a temporary prefix, and checks `parallel-codex-tui --version` before creating the GitHub Release. If npm returns `ENEEDAUTH` or `E401`, fix the npm Trusted Publishing package settings rather than adding a token fallback.
173
450
 
174
- Configure npm Trusted Publishing for this GitHub Actions workflow with organization/user `allendred`, repository `parallel-codex-tui`, and workflow filename `release.yml`. The release job installs npm `^11.5.1`, which is required for trusted publishing. Alternatively, add an `NPM_TOKEN` repository secret with npm publish permission; it must be an npm automation token so CI can publish without an interactive one-time password. If npm returns `EOTP`, replace the secret with an automation token or remove the secret and use Trusted Publishing. To publish a release, update `package.json` and `src/version.ts` to the same version, then push a matching tag:
451
+ To publish a release, update `package.json` and `src/version.ts` to the same version, then push a matching tag:
175
452
 
176
453
  ```bash
177
- git tag v0.1.3
178
- git push origin v0.1.3
454
+ git tag v0.1.5
455
+ git push origin v0.1.5
179
456
  ```
180
457
 
181
- 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.3` requires tag `v0.1.3`.
458
+ 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.5` requires tag `v0.1.5`.
182
459
 
183
460
  ## Publishing Hygiene
184
461
 
185
462
  - `.parallel-codex/config.toml` is local-only and ignored.
186
463
  - `.parallel-codex/last-workspace` and `.parallel-codex/workspaces.json` are local workspace-selection state and are ignored.
187
- - `.parallel-codex/sessions/` contains task prompts, logs, native session ids, and worker output; never commit it.
464
+ - `.parallel-codex/router/` contains local request classification audit records and is ignored.
465
+ - `.parallel-codex/sessions/` contains the workspace chat transcript, task prompts, logs, native session ids, isolated feature workspaces, and conflict evidence; never commit it.
188
466
  - `docs/superpowers/` contains internal planning notes and is ignored for public releases.
package/dist/bootstrap.js CHANGED
@@ -5,6 +5,7 @@ import { routerRuntimeDir } from "./core/paths.js";
5
5
  import { SessionIndex } from "./core/session-index.js";
6
6
  import { SessionManager } from "./core/session-manager.js";
7
7
  import { prepareWorkspace } from "./core/workspace.js";
8
+ import { reconcileWorkspaceCommitIntents } from "./core/workspace-commit-recovery.js";
8
9
  import { Orchestrator } from "./orchestrator/orchestrator.js";
9
10
  import { createWorkerRegistry } from "./workers/registry.js";
10
11
  export async function createRuntime(appRoot, workspaceRoot = appRoot) {
@@ -17,21 +18,40 @@ export async function createRuntime(appRoot, workspaceRoot = appRoot) {
17
18
  await ensureDir(routerCwd);
18
19
  const preparedWorkspace = await prepareWorkspace(appRoot, workspaceRoot);
19
20
  const index = await SessionIndex.open(preparedWorkspace, config.dataDir);
20
- await index.rebuildFromFiles();
21
- const sessions = new SessionManager({
22
- projectRoot: preparedWorkspace,
23
- dataDir: config.dataDir,
24
- index
25
- });
26
- const workers = createWorkerRegistry(config);
27
- const orchestrator = new Orchestrator(config, sessions, workers, undefined, routerCwd);
28
- return {
29
- config,
30
- workspaceRoot: preparedWorkspace,
31
- routerCwd,
32
- index,
33
- sessions,
34
- workers,
35
- orchestrator
36
- };
21
+ try {
22
+ await index.rebuildFromFiles();
23
+ const sessions = new SessionManager({
24
+ projectRoot: preparedWorkspace,
25
+ dataDir: config.dataDir,
26
+ index
27
+ });
28
+ const pendingTaskCreations = await sessions.reconcilePendingTaskCreations();
29
+ const workspaceCommitRecovery = await reconcileWorkspaceCommitIntents(preparedWorkspace, config.dataDir);
30
+ await sessions.reconcileInterruptedMainSession();
31
+ await sessions.reconcileNativeSessionState();
32
+ const recoveredTasks = await sessions.reconcileInterruptedTasks();
33
+ const workers = createWorkerRegistry(config);
34
+ const orchestrator = new Orchestrator(config, sessions, workers, undefined, routerCwd, async () => (await loadConfig(appRoot)).router);
35
+ return {
36
+ config,
37
+ workspaceRoot: preparedWorkspace,
38
+ routerCwd,
39
+ index,
40
+ sessions,
41
+ workers,
42
+ orchestrator,
43
+ pendingTaskCreations,
44
+ workspaceCommitRecovery,
45
+ recoveredTasks
46
+ };
47
+ }
48
+ catch (error) {
49
+ try {
50
+ index.close();
51
+ }
52
+ catch {
53
+ // Preserve the startup failure that prevented the runtime from being created.
54
+ }
55
+ throw error;
56
+ }
37
57
  }