projmux 0.12.2 → 0.13.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/docs/cli-guide.md CHANGED
@@ -63,6 +63,28 @@ rendered from the same command manifest the binary renders `projmux help`
63
63
  from and is verified against it on every `make test`, so it cannot drift.
64
64
  Nothing in this guide restates it.
65
65
 
66
+ Every executable route and root-level parser bridge in that same graph has one
67
+ **selectorless authority** class. The label describes what omission means; it
68
+ does not prevent a route from accepting an explicit selector:
69
+
70
+ - `natural-omitted` — omission resolves one predictable current resource or a
71
+ documented contextual read/scope, such as an active-root inventory or picker;
72
+ supplying any selector replaces that natural target or scope instead of
73
+ blending with it. Current Pane/Window mutations still require one exact
74
+ resource.
75
+ - `explicit-target` — the route or its generated caller must name the exact
76
+ target; ambient tmux context cannot supply or narrow it.
77
+ - `refusal` — the node has no safe selectorless action, so omission refuses
78
+ before output or mutation. Namespace nodes use this class when only a child
79
+ route is executable.
80
+ - `explicit-fan-out` — the route spelling is an intentional global or
81
+ whole-set operation. Resource mutations never enter this class merely
82
+ because a selector happened to match several rows.
83
+
84
+ The generated reference and `projmux <route> --help` print the class projected
85
+ from the graph. Completeness tests census every graph node plus bare/help/version
86
+ root bridges and reject missing, duplicate, conflicting, or unknown rows.
87
+
66
88
  ## Resource selectors and the active target
67
89
 
68
90
  The resource routes (`get`, `describe`, `create`, `rename`, `rebind`, `delete`,
@@ -111,13 +133,16 @@ The contract:
111
133
  --name current` succeeds — so a sentinel token would silently shadow a real
112
134
  resource.
113
135
  - **"No selector at all" means exactly that.** Any positional `<ref>`, any
114
- `--project`/`-p`, `--window`/`-w`, `--pane`, or any `--selector` label keeps the
115
- pre-existing behavior unchanged. The fallback is never blended into a
116
- partially specified selector.
117
- - **Only the singular routes.** The plural reads (`get projects|windows|panes|
118
- agents`) stay 0..N inventories over their whole scope. `delete` and `create`
119
- are unchanged: `create`'s omitted `--pane` selects a split anchor inside an
120
- already-chosen Window, which is not the same question.
136
+ `--project`/`-p`, `--window`/`-w`, `--pane`, or any `--selector` label keeps
137
+ picking the target itself. The active *target* is never blended into a
138
+ partially specified selector. The active *Project* is a separate rule and does
139
+ apply to a reference -- see [Reference scope](#reference-scope-the-active-project-namespace)
140
+ below.
141
+ - **Only the singular routes and `create`.** The plural reads (`get
142
+ projects|windows|panes|agents`) stay 0..N inventories over their whole scope,
143
+ and `delete` is unchanged. `create` has its own spelling of the same rule --
144
+ see [Create scope](#create-scope) -- because a create resolves a scope to put
145
+ something *into* rather than a target to act *on*.
121
146
  - **Inside tmux is decided by `$TMUX_PANE` plus `$TMUX`**, not by whether a tmux
122
147
  server answers. A bare `display-message` from outside a client still succeeds
123
148
  and answers for the most-recently-used session; projmux never uses that.
@@ -136,6 +161,170 @@ and `@projmux_window_uid` on its window — and derives every ancestor from
136
161
  registry `ownerRef`. The session-scoped `@projmux_project_uid` is deliberately
137
162
  not consulted.
138
163
 
164
+ ### Plural read scope: the active managed root
165
+
166
+ Inside tmux, selector-omitted `get windows|panes|agents` derives one exact
167
+ managed root from the active Window's Registry `ownerRef`:
168
+
169
+ - a Project-owned Window lists only that Project's descendants;
170
+ - a ControlSession-owned Window, including Home, lists only that
171
+ ControlSession's descendants;
172
+ - a missing Window mirror, a foreign Window uid, or a Window without one exact
173
+ existing Project or ControlSession owner is exit `2` with zero stdout. It
174
+ never falls through to the whole Registry.
175
+
176
+ The default narrows the Window universe and does not pick a target. Name and
177
+ label selectors keep their existing meaning inside that root. An applicable
178
+ explicit `uid:` occurrence is opaque Registry-global authority: like explicit
179
+ `--project`/`-p` and `--all-projects`/`-A`, it bypasses active-target observation
180
+ entirely, including from a foreign tmux Pane. The two whole-set spellings retain
181
+ their existing meaning and include ControlSession descendants. Outside tmux, an
182
+ omitted root keeps the historical whole-Registry inventory without probing a
183
+ default server.
184
+
185
+ ### Reference scope: the active Project namespace
186
+
187
+ A `metadata.name` is unique inside its owner scope, never across the registry: a
188
+ Window name is unique inside its Project, a Pane name inside its Window or
189
+ Agent, an Agent name inside its Window. So inside a managed Project a reference
190
+ is resolved inside the Project that owns the active Window, which is the same
191
+ universe the plural reads use in that context:
192
+
193
+ ```
194
+ projmux describe window zsh # the Window named zsh in *this* Project
195
+ projmux describe pane log # the Pane named log in this Project
196
+ projmux describe agent codex # the Agent named codex in this Project
197
+ projmux rename window zsh --name review
198
+ projmux rename pane log --name build
199
+ ```
200
+
201
+ The Project-only read matrix is `describe window|pane|agent`. Generic
202
+ `rename window|pane|agent` uses the same narrowing rule but accepts either the
203
+ active Project or ControlSession as the exact owner root. `describe project`
204
+ and `rename project` are not in either matrix — a Project has no enclosing
205
+ root — and `delete`, `rebind`, and `agent resume` keep their selector meaning.
206
+
207
+ The contract:
208
+
209
+ - **It narrows the search, it does not pick the target.** The Project fixes the
210
+ universe and nothing else. The active Window and the active Pane are not used
211
+ to break a tie, so two same-named resources inside the one Project stay the
212
+ ordinary bounded `matched N ..., want exactly one` ambiguity. Resolving that
213
+ is what `--window`/`--pane` and a `uid:` reference are for.
214
+ - **Explicit `--project`/`-p` wins, with zero observations.** Naming a Project
215
+ never costs a tmux round trip and never depends on the pane you are sitting
216
+ in.
217
+ - **A `uid:` reference is scoped too.** A uid that belongs to another Project is
218
+ a no-match, not a cross-Project hit. Pass `--project` to address it.
219
+ - **Outside tmux nothing changes.** The whole registry is searched and the
220
+ previous `matched N ..., want exactly one` ambiguity is unchanged. No default
221
+ tmux server is probed.
222
+ - **Inside tmux a broken owner chain refuses.** A pane carrying no
223
+ `@projmux_window_uid`, a mirrored Window uid the registry does not hold, or a
224
+ Window with no exact existing Project or ControlSession owner is exit `2`
225
+ with zero bytes on stdout and zero mutations for generic rename. It is never
226
+ a silent fallback to the whole registry.
227
+
228
+ ### Create scope
229
+
230
+ `create window|pane|agent|<provider>` is resource-backed on every spelling.
231
+ There is no mode flag and no second parser: the same argv means the same thing
232
+ whether or not `--project` is present, and `-w`, `--create-window`, `--pane`,
233
+ `--selector`, `--placement`, `--name`, `--label`, `--cwd`, `--add-dir`, `-o`,
234
+ and the `--` payload all reach the same parser either way.
235
+
236
+ The scope resolves in two branches:
237
+
238
+ - **Explicit `--project`/`-p` wins**, inside tmux and outside it. The active
239
+ tmux target is not consulted at all.
240
+ - **With no explicit scope occurrence, the Project comes from the active managed runtime**:
241
+ the `@projmux_window_uid` mirrored on the pane you are in, and that Window's
242
+ registry `ownerRef`. This is the same seam the empty-selector reads use.
243
+
244
+ The Window and the anchor Pane follow the *whole* scope rather than the Project
245
+ flag alone:
246
+
247
+ ```
248
+ projmux create codex # active Project, active Window, split from the active Pane
249
+ projmux create codex -p alpha -w hi --create-window # exact Project, new Window "hi"
250
+ projmux create codex -p beta -w main # everything explicit
251
+ projmux create pane -p alpha # every Window of alpha; a deliberate fan-out
252
+ ```
253
+
254
+ One explicit scope occurrence (`--project`, `--window`, `--pane`, or
255
+ `--selector`) makes the whole scope explicit, so naming a Window never picks up
256
+ an anchor from somewhere you did not address. With a scope but no `--pane`, the
257
+ split anchor is the target Window's role-agnostic `spec.anchorPaneRef`. A
258
+ shell-required offline operation may plan a lazy direct
259
+ `spec.defaultShellPaneRef` without replacing an Agent anchor. A missing or stale
260
+ anchor is exit `2` rather than a silent alternate-Pane repair.
261
+
262
+ An exact existing Window or Pane can reveal its owner Project through Registry
263
+ `ownerRef`. A Window named with `--create-window` does not exist yet and cannot;
264
+ without `--project` that spelling refuses and names `--project <ref>` as the
265
+ remedy, even when the invocation happens inside a managed Pane.
266
+
267
+ Refusals are exit `2` with zero Registry writes and zero tmux mutations, and
268
+ they name `--project` as the fix:
269
+
270
+ - outside tmux with no `--project` — no default server is probed;
271
+ - inside Home, a control session, an unattributed pane, or a foreign pane —
272
+ none of those carry a managed identity, and projmux never invents a Project
273
+ from `$HOME`, a session name, or a cwd;
274
+ - a mirrored uid the Registry does not hold, or a Window whose owning Project is
275
+ gone — a `recoverable` runtime is reported, never adopted.
276
+
277
+ Every create is **detached**: no create moves the client. Use `focus pane` or
278
+ `-o pane-id` when you want to end up in the new pane. A natural create validates
279
+ the inherited exact route and Pane containment. An explicit resource scope
280
+ binds the selected app resource route without letting unrelated inherited
281
+ `TMUX`/`TMUX_PANE` choose or change the resource target; exact Project plus
282
+ `--create-window` therefore uses the validated app logical `-L` route on its
283
+ first attempt, with no `env -u` workaround. Runtime safety remains independent
284
+ and may refuse before mutation. An inherited app-owned `TMUX` socket/PID stays
285
+ route evidence: projmux validates its exact `-S` path, ownership/logical
286
+ markers, logical `-L` alias, and PID while ignoring unrelated `TMUX_PANE`
287
+ containment. Outside tmux it validates the default app `-L` route. Marker,
288
+ physical-socket, server-PID, generation, and owner reobservation remain
289
+ mandatory. Commands that explicitly select an existing live `--socket-path`
290
+ keep that exact `-S` route unchanged.
291
+
292
+ #### Splits started from a popup
293
+
294
+ The split UI's own pickers (`M-7`, `M-4`/`C-r`, the pane context menu, and the
295
+ default split key when the saved mode is `selective` or `resume`) run inside a
296
+ `display-popup`. tmux exports `$TMUX` to a popup job and deliberately exports no
297
+ `$TMUX_PANE`, because a popup is not a pane — so the picker has no inherited
298
+ target of its own while still knowing, from the keypress that opened it, which
299
+ pane the operator was in. That pane travels on the create intent as an explicit
300
+ anchor and resolves Project, Window and split anchor through the same identity
301
+ mirror a pane-hosted invocation reads.
302
+
303
+ The anchor is something the split UI hands to `create`, never something `create`
304
+ reads from the environment. `$TMUX_SPLIT_TARGET_PANE` is not a scope override:
305
+ typing `projmux create pane --placement right` inside a popup is still an
306
+ invocation with no target and still refuses with the `--project` usage error
307
+ above, and no read, rename, or delete verb consults it.
308
+
309
+ #### Managed pane context menu
310
+
311
+ The generated `MouseDown3Pane` menu treats its Horizontal Split, Vertical
312
+ Split, and Kill entries as Projmux resource actions. Both splits pass the exact
313
+ clicked pane through the popup-origin anchor above and reach the same canonical
314
+ `create pane` materializer as the CLI, so the new pane receives a Registry uid.
315
+ Kill resolves that anchor's mirrored uid and reaches canonical `delete pane`,
316
+ including its printed delete result. The menu never falls back to a raw tmux
317
+ mutation when either route refuses. The reason is displayed on the exact client
318
+ that opened the menu instead of being lost as a `run-shell` exit code.
319
+
320
+ tmux Respawn has no equivalent in the current resource model: it preserves the
321
+ same pane handle, layout, Registry uid, and original command, while canonical
322
+ delete plus create removes that identity and creates another one (and may end
323
+ the Window or Project session when it deletes the last pane). The generated
324
+ menu therefore omits Respawn entirely and does not expose a refusal handler or
325
+ invent a replace operation. User-authored tmux bindings remain outside this
326
+ managed-menu contract.
327
+
139
328
  ### Rename and rebind live convergence
140
329
 
141
330
  `rename project|window|pane` commits the selected Registry `metadata.name` and
@@ -166,6 +355,26 @@ duplicate UID claims remain fail-closed.
166
355
 
167
356
  ### Agent topic, interaction, activation, and workspace
168
357
 
358
+ `agent turn start`, `agent turn steer`, `agent turn interrupt`, and
359
+ `agent approval review` use the live Codex app-server connection only when the
360
+ selected Agent, its owned Pane, activation generation, thread, current turn,
361
+ and connection epoch still match exactly. `start` sends only the exact thread
362
+ id and one text input; `steer` supplies the current expected turn id; and
363
+ `interrupt` supplies that exact turn id. These commands never install sticky
364
+ model, effort, cwd, sandbox, permission, or collaboration overrides.
365
+
366
+ Approval review shows only the safe one-shot intersection supplied by the
367
+ exact pending request. Command, file, and network requests are limited to
368
+ `accept`, `decline`, and `cancel`; permission grants echo the received supported
369
+ profile with `scope=turn` and `strictAutoReview=null`. Session grants, policy or
370
+ network amendments, unstable root grants, legacy approval mutations, and
371
+ automatic approval are unavailable. The request envelope and raw JSON-RPC id
372
+ remain only in connection memory; reconnect, resolution, ambiguity, or any
373
+ identity mismatch produces no provider write. Approval queue rows advertise
374
+ `Review pending approval` only while that responder exists, otherwise they
375
+ advertise the exact-Agent `Open Codex` focus fallback; resolution removes the
376
+ row. Neither route stores prompt, command, path, permission, or request content.
377
+
169
378
  `agent topic get|set|clear` and `agent status get|set` resolve exactly one
170
379
  Agent, either from an explicit Agent reference or from the Agent-owned active
171
380
  managed Pane. Topic is a non-identifying Registry annotation. Interaction is a
@@ -193,6 +402,40 @@ and shell Pane manual attention share the existing priority reducer, while
193
402
  `dot`/`emoji`/`off`, glyphs, colors, and the Window aggregate are never stored
194
403
  in resource metadata.
195
404
 
405
+ A clean managed process exit is topology authority only through the exact
406
+ generated `pane-exited` hook. The supervisor must have durably journaled a
407
+ same-generation `normal` receipt, the hook must name the exact `%N` Pane and
408
+ the owner Window must carry its exact last-positive `$N/@N` binding on the same
409
+ socket, and fresh preflight plus locked observations must still resolve the
410
+ same Registry owner chain. A non-last Pane is removed while its directly owning
411
+ Agent is retained Offline with its conversation identity. For a last Pane, that evidence is retained until a
412
+ matching `window-unlinked` hook removes the Window; a final Project Window also
413
+ removes its Window descendants while retaining the exact Project uid, root,
414
+ reservation, pins, snapshots, and external assets as a valid zero-Window
415
+ Project. Managed runtime Stop is different: it stops only the exact runtime and
416
+ keeps the complete desired Project/Window/Pane graph closed for a same-UID
417
+ Continue. Shell and Claude/Codex clean exit have the same result; `/exit`, pane
418
+ content, prompt, history, and transcript are never parsed. `abnormal`,
419
+ `killed`, `unknown`, stale/resumed bindings, empty or unavailable inventory,
420
+ permission failure, and foreign-host/window observations keep their diagnostic
421
+ rows and produce no automatic delete plan.
422
+
423
+ For `pane-exited`, tmux supplies `%N` as `#{hook_pane}`. Its current-context
424
+ session/window formats may already name a surviving client Window, so the owner
425
+ pair comes from the Window's last live Registry observation. The separate
426
+ `window-unlinked` hook supplies exact `#{hook_session}` and `#{hook_window}`;
427
+ only a matching causal pair authorizes last-Pane Window deletion.
428
+
429
+ An offline historical Window with no stored causal last-Pane receipt is not
430
+ absence-only migration authority and is never auto-deleted. Recover it with the
431
+ canonical exact route, `projmux delete window uid:<window-uid> --socket <name>
432
+ --yes` (or the corresponding `--socket-path`/inherited absolute `$TMUX` route).
433
+ Deleting a Project's last exact Window retains the Project with zero Windows;
434
+ within the runtime/startup lifecycle table, explicit `delete project --yes` is
435
+ the unregister operation. Runtime absence, managed Stop, ordinary Window close,
436
+ and Fresh never invoke the separately scoped filesystem-missing `prune project`
437
+ administrative policy.
438
+
196
439
  Resource-backed Agent create accepts provider-neutral `--cwd <absolute>` and
197
440
  repeatable `--add-dir <absolute>`. Explicit paths must exist, resolve without a
198
441
  symlink escape, and remain inside a registered Project tree; only Codex and
@@ -210,8 +453,15 @@ owner Project root from `get`/`describe`, and a successful resume persists that
210
453
  normalized effective workspace without changing Window Project ownership.
211
454
 
212
455
  When `create agent -- <initial-prompt>` is used, normal resource creation and
213
- provider activation are distinct. Projmux waits for bounded hook/lifecycle
214
- metadata only; it never captures pane content or stores the prompt. If
456
+ provider activation are distinct. Projmux first waits up to five seconds for an
457
+ exact provider `SessionStart`; that readiness evidence leaves activation
458
+ `pending` and opens an independent five-second initial-task acknowledgement
459
+ window. A `UserPromptSubmit` acknowledgement may also arrive directly before
460
+ the readiness observer sees `SessionStart`. The two stages are independently
461
+ bounded, so provider startup plus an acknowledgement later than two seconds may
462
+ take more than five but never more than ten seconds. Neither stage captures pane
463
+ content or stores the prompt. Acknowledgement returns success and the requested
464
+ exact `%N` output. If
215
465
  activation cannot be confirmed, the command exits nonzero while naming the
216
466
  exact Agent UID and Pane plus safe provider retry and `delete agent ... --yes`
217
467
  cleanup options. The live resources remain explicit and retryable rather than
@@ -219,7 +469,13 @@ being reported as an ordinary success.
219
469
 
220
470
  Activation metadata is bounded to provider-hook provenance and fixed
221
471
  acknowledged/timed-out/failed diagnostics. Provider error strings and initial
222
- prompt text are never stored. Resource-backed Agent create and resume do not
472
+ prompt text are never stored. `pending` can become `unconfirmed` and a later
473
+ exact hook can refine either state to `acknowledged`; acknowledged never moves
474
+ backward. Late refinement must quote the same Agent, Pane uid, activation
475
+ generation, and recorded live `%N`, so a hook from a replaced materialization
476
+ cannot acknowledge its replacement. Codex and Claude initial payloads use the
477
+ same acknowledgement and result contract; provider-specific prompt content is
478
+ not evidence. Resource-backed Agent create and resume do not
223
479
  start the legacy title/content watcher; that watcher remains only for legacy
224
480
  non-resource panes and exits before reading title or capture content if resource
225
481
  identity appears.
@@ -315,14 +571,23 @@ client only after it converges; a refusal, a failed preflight, or a rolled-back
315
571
  partial leaves the client where it was and reports the exact stage. The
316
572
  activation is pinned to the session the open targets, so a Project whose
317
573
  Registry projects a different session name is refused instead of populating a
318
- session the open never reaches. Choosing `Latest snapshot` or `Named snapshot`
319
- instead stays entirely on the Session State snapshot engine. Reading whether a
320
- Project declares topology is a zero-write snapshot read, so opening a directory
321
- that was never registered still creates no Registry state.
322
-
323
- Materialization never starts or resumes an Agent, creates an Agent-owned Pane,
324
- or executes `Pane.spec.command`; that field remains a one-time name seed. A
325
- new Window binds only its own tmux-created primary Pane. On an existing Window,
574
+ session the open never reaches. The closed-Project startup screen has exactly
575
+ two neutral actions. `Continue project` materializes current Registry desired
576
+ state with the same Project UID. A retained graph keeps descendant UIDs; a
577
+ zero-Window Project atomically receives a new canonical Window/shell UID chain.
578
+ A deleted Project may use only the exact usable snapshot compatibility path;
579
+ an unavailable Continue is an explicit zero-write refusal with no Fresh
580
+ fallback. `Open fresh` is one step with no danger styling, confirmation, or
581
+ delete counts: it atomically replaces the same-root graph with a new Project
582
+ UID and new canonical Window/shell UIDs, then hands off only after ordinary
583
+ materialization. A repeat replaces identity again. The root, git/worktrees,
584
+ trust decision, unrelated roots, and snapshot bytes remain unchanged.
585
+
586
+ Materialization launches or resumes declared Agents through the canonical
587
+ provider/trust path and creates their managed Agent-owned Panes. An individual
588
+ Agent refusal preserves the committed desired Registry and emits an item notice
589
+ for retry. It never directly executes `Pane.spec.command`; that field remains a
590
+ one-time name seed. A new Window binds only its own tmux-created primary Pane. On an existing Window,
326
591
  every pre-existing uid-less Pane is refused rather than adopted; foreign,
327
592
  duplicate, wrong-owner, or otherwise ambiguous UID state is refused before the
328
593
  first create. Layout uses the existing deterministic right-axis equalizer and does
@@ -334,6 +599,193 @@ Registry-only: its complete Agent/Pane cascade is shown under `--dry-run`, no
334
599
  tmux object is killed, and unrelated live objects and sockets are untouched. A
335
600
  unique live mirror keeps the exact-kill path, while duplicate, foreign,
336
601
  stale-owner, inventory-failure, and revalidation-race states are refused.
602
+ The selected Window's Registry `ownerRef` is authoritative in that preflight:
603
+ a Project uses its `status.session` plus the optional matching Project uid
604
+ mirror, while a ControlSession uses its exact `spec.session` and requires that
605
+ no Project uid mirror contaminate the control session. Window, Pane, and Agent
606
+ deletes preserve that `(root kind, root uid)` chain in the signed live plan and
607
+ report a final-window cascade with its actual root kind.
608
+
609
+ Pane and Agent Registry-only deletion is deliberately narrower. It accepts
610
+ only an explicit exact `uid:` selector: a Pane must carry durable
611
+ `MissingRuntime=True/RuntimeUnbound` evidence, and an Agent must be `Offline`
612
+ with no `paneRef` (with every retained descendant Pane also marked
613
+ `MissingRuntime`). The exact routed server must answer with a non-empty socket
614
+ identity and a non-empty Pane inventory that proves the target has zero mirrors.
615
+ A missing server, empty or failed inventory, unavailable or permission-denied
616
+ transport, implicit/name/scope/`--all` selection, and duplicate or foreign
617
+ mirrors are not absence authority and make zero writes. Dry-run and apply sign
618
+ the same socket, owner/root chain, lifecycle evidence, Pane activation
619
+ generation, and Agent binding; locked revalidation refuses zero-to-live,
620
+ live-to-zero, owner, generation, duplicate, or foreign changes. A successful
621
+ Registry-only result reports that no tmux Pane was killed, preserves the owning
622
+ Window/root/socket and all siblings, and repeating the exact apply returns the
623
+ ordinary no-match result.
624
+
625
+ `delete window|pane|agent` names the server its live half addresses the same
626
+ way `reconcile resources` does: `--socket <name>`, `--socket-path <absolute>`,
627
+ or the inherited absolute `$TMUX`. Outside tmux with neither flag it refuses
628
+ rather than reaching for the app's own socket, so a delete issued against an
629
+ isolated server can never inventory one host and kill objects on another.
630
+
631
+ Before it kills anything, a delete commits an intentional termination receipt
632
+ against every Pane whose process it is about to end, in its own Registry
633
+ transaction. If that write fails, nothing live is touched; if the delete then
634
+ refuses for any other reason, the receipt is withdrawn again. The receipt is
635
+ what tells a later reader that a process disappeared because someone asked for
636
+ it, rather than because it crashed.
637
+
638
+ ## get runtime
639
+
640
+ ```text
641
+ projmux get runtime sessions|windows|panes [--socket <name> | --socket-path <absolute>] [-o json|none]
642
+ ```
643
+
644
+ `get runtime` is the read-only escape hatch onto one exact tmux server. The
645
+ resource reads (`get projects|windows|panes|agents`) enumerate the Registry;
646
+ this one enumerates the machine, including everything projmux does not own, and
647
+ it accepts no selector because most of what it reports has no name to resolve.
648
+ Its kinds are tmux object kinds, not resource kinds, and they have no singular
649
+ spelling for the same reason.
650
+
651
+ Every row carries the attribution the resolved resource graph decided from exact
652
+ evidence -- `managed`, `recoverable`, `control`, `ephemeral`, `unattributed`,
653
+ `foreign`, `conflict` -- the reason for it, the stable tmux id, the fully
654
+ qualified coordinate (`<session>`, `<session>:@N`, `<session>:@N.%N`), and, for a
655
+ managed object, the Registry resource it is bound to. A refused object is named
656
+ and explained and is never handed a resource identity.
657
+
658
+ Socket selection is the same fail-closed rule `reconcile resources` uses, with
659
+ one difference at the end:
660
+
661
+ - `--socket <name>` means exactly `tmux -L <name>`.
662
+ - `--socket-path <absolute>` means exactly `tmux -S <absolute>`.
663
+ - The two flags are mutually exclusive.
664
+ - With neither flag, an invocation inside tmux inherits only the absolute socket
665
+ path from `$TMUX` and uses `-S`.
666
+ - With neither flag outside tmux the read still succeeds. It returns the
667
+ unavailable projection: no items, every scope reported unobservable with a
668
+ reason, and zero tmux calls. There is no default-socket guess, and a sibling
669
+ socket is never read.
670
+
671
+ The default projection is a table preceded by a header line naming the host mode
672
+ and the exact transport, plus one line per scope that could not be observed. The
673
+ header is always printed, even when the table is empty: "no sessions" is only
674
+ trustworthy next to which server was asked and whether the answer could be taken
675
+ at all. `-o json` emits the same data as a stable `Runtime{Session,Window,Pane}List`
676
+ envelope; `-o none` prints nothing. The Registry projections (`uid`, `name`,
677
+ `ref`, `metadata`) are deliberately not offered, because most of what this route
678
+ returns has none of them.
679
+
680
+ The read writes nothing: the Registry is opened without being created, the
681
+ observation issues one option probe and three list queries whatever the size of
682
+ the server, and no write verb is ever sent.
683
+
684
+ ## runtime diagnostics
685
+
686
+ ```text
687
+ projmux runtime diagnostics [--socket <name> | --socket-path <absolute>] [--ui=popup|sidebar]
688
+ ```
689
+
690
+ The interactive half of the same read. It lists every tmux object on the exact
691
+ server in containment order with an attribution tally, and it is deliberately
692
+ separate from `projmux runtime sessions`: that picker lists recent sessions so
693
+ you can open one, this one lists everything so you can understand it.
694
+
695
+ Selecting a row opens its action menu, which offers only routes that already
696
+ exist:
697
+
698
+ - **Focus** hands `projmux focus` the row's exact coordinate and the server's own
699
+ `#{socket_path}`. It moves a client and never materializes anything.
700
+ - **Attach** forwards to `projmux attach project uid:<uid>`, and is offered only
701
+ for a session bound to a Registry Project while you are outside tmux.
702
+ - **Open Resource Inspector** opens `projmux resources` unchanged.
703
+
704
+ An action that does not apply is listed with the reason instead of being hidden:
705
+ "no Registry Project claims this session; diagnostics never adopts one" is the
706
+ diagnostic. There is no adopt, import, rename, or kill action, and opening the
707
+ surface writes nothing.
708
+
709
+ ## reconcile registry
710
+
711
+ ```text
712
+ projmux reconcile registry [--dry-run] [--source <name|absolute-path>] [--expect-source-checksum <sha256:hex>] [--expect-current-checksum <sha256:hex>] [--socket <name> | --socket-path <absolute>] [-o json]
713
+ ```
714
+
715
+ `reconcile registry` is the recovery boundary for the Registry itself. It is a
716
+ sibling of `reconcile resources`, not a stronger version of it: `reconcile
717
+ resources` converges a Registry that loads, and this route runs when the
718
+ Registry is the thing that is wrong.
719
+
720
+ Planning writes nothing. With no `--source` — and with `--dry-run` at any time —
721
+ the command reads the current `registry.json`, the `registry.initialized`
722
+ marker, and the bounded copies under `recovery/`, then reports:
723
+
724
+ - the current state as `valid`, `first-use`, `missing`, `empty`, `malformed`,
725
+ `schema-too-new`, `invalid`, or `unreadable`, with a `sha256:` digest of the
726
+ exact bytes;
727
+ - every candidate newest first, each marked `eligible` or `rejected` with the
728
+ reason, its digest, size, mtime, schema version, and the
729
+ projects/windows/panes/agents/reservations it holds;
730
+ - the exact guarded command that would restore the candidate it suggests.
731
+
732
+ No lock is taken, no permission is repaired, and `<state>/projmux/metadata/` is
733
+ not created — a preview is safe against a first-use state directory and against
734
+ one nobody should be writing to yet.
735
+
736
+ Restoring requires `--source`. There is no "restore the newest" mode: which copy
737
+ is the truth is a judgment about which mutations were wanted. A source is an
738
+ exact copy name, a unique fragment of one, or an absolute path to a copy carried
739
+ from elsewhere; a fragment matching several copies is refused rather than ranked.
740
+ An explicit path gets exactly the same verification as a bounded copy.
741
+
742
+ Verification is fail closed. Malformed JSON, an empty file, an envelope newer
743
+ than this build, and a graph with a duplicate uid, a dangling `ownerRef`, or a
744
+ broken name reservation are all refused with the current Registry byte-identical.
745
+ The verified bytes are then published **verbatim**, so uids, owner relations, and
746
+ name reservations are preserved exactly rather than re-encoded, and a
747
+ known-older-but-valid envelope stays readable through the normal safe read and
748
+ migrates on the next semantic write.
749
+
750
+ The bytes being replaced are kept first, at
751
+ `recovery/replaced-<stamp>-<seq>.json`. Unlike the write-side copies this keeps
752
+ content that does not verify: a damaged Registry is the only remaining evidence
753
+ if the restore turns out to be the wrong call, and the preserved copy is offered
754
+ back as a candidate. Replaced copies are their own bounded family, so a restore
755
+ never consumes the automatic write history.
756
+
757
+ Race guards are the operator's tie to the plan they read:
758
+
759
+ - `--expect-source-checksum <sha256:hex>` refuses unless the source still hashes
760
+ to that digest.
761
+ - `--expect-current-checksum <sha256:hex>` refuses unless the current Registry
762
+ still does.
763
+ - Both are what the printed `next:` command already carries, so copy-pasting the
764
+ preview's suggestion is guarded by construction.
765
+
766
+ Underneath, the source is re-read and re-verified under the store lock, the
767
+ staged copy is re-validated, and both inputs are re-hashed immediately before the
768
+ single atomic rename. Anything that moved refuses with nothing published, no
769
+ preserved copy, and no staged file left behind, and says to re-run the preview. A
770
+ repeat restore is a byte no-op: no rename, no preserved copy, no marker write.
771
+ Restoring into a state directory with no marker publishes one, so a later loss on
772
+ that machine reads as state loss rather than as a fresh first use.
773
+
774
+ When recovery is needed and no verified copy exists, the report adds a mirror
775
+ diagnostic — and it is **only** a diagnostic. It reports the Projmux identity the
776
+ one exact tmux server still carries (Project/Window/Pane uids, mirrored names,
777
+ the Project root, and containment resolved from stable tmux ids) beside a fixed
778
+ statement of what no mirror can return: offline resources, every Agent (no tmux
779
+ option carries an Agent uid), an Agent-owned Pane's `ownerRef`, the name
780
+ reservation table, `spec.anchorPaneRef`, `spec.defaultShellPaneRef`, and labels/annotations/timestamps/
781
+ status. Panes carrying a provider option are counted as proof that Agents existed
782
+ whose uids are nowhere on the server. Nothing is imported and no Registry is
783
+ generated from fragments. Socket selection follows the same `--socket` /
784
+ `--socket-path` / inherited-`$TMUX` rule as `reconcile resources`, except that
785
+ having no exact target is reported as a reason rather than being a usage error:
786
+ a restore is a filesystem operation, so recovery must work on a machine with no
787
+ tmux server. The diagnostic is skipped entirely when a verified copy exists or
788
+ the Registry is healthy.
337
789
 
338
790
  ## Internal plumbing (`projmux internal ...`)
339
791
 
@@ -854,6 +1306,13 @@ projmux attention window [window]
854
1306
  Toggles the `✳` pane title prefix and the `@projmux_attention_state` pane
855
1307
  option. `toggle` flips between cleared and `reply`; `clear` always
856
1308
  clears; `arm` sets a pre-reply armed state used by the AI flow. The
1309
+ optional pane is the exact pane invoking the command: when it is omitted,
1310
+ Projmux requires inherited `$TMUX` plus an exact `$TMUX_PANE=%N` and verifies
1311
+ that same pane with a targeted tmux read before changing attention state. From
1312
+ outside tmux, or when that evidence is missing, malformed, or stale, pass an
1313
+ explicit pane target instead; the command fails without writing attention
1314
+ state. Explicit targets used by generated focus hooks keep their existing
1315
+ meaning.
857
1316
  producer side pushes the matching entry into the notify queue when the pane
858
1317
  has an associated AI agent option; clearing attention does not ack the queue
859
1318
  row (manual toggles on shell panes do not push). `list` reads `tmux list-panes -a` and shows live pane
@@ -865,8 +1324,8 @@ supplied window.
865
1324
  ## Agent creation and hook ingress
866
1325
 
867
1326
  ```
868
- projmux create agent --provider <claude|codex|antigravity> [--placement right|down] ...
869
- projmux create pane [--placement right|down] ...
1327
+ projmux create agent --provider <claude|codex|antigravity> [--project <ref>] [--window <ref>]... [--create-window] [--placement right|down] ...
1328
+ projmux create pane [--project <ref>] [--window <ref>]... [--create-window] [--placement right|down] ...
870
1329
  projmux config edit [--get|--set <mode>]
871
1330
  projmux agent status set <thinking|waiting|idle> [pane]
872
1331
  projmux agent topic ...
@@ -897,34 +1356,60 @@ yellow respectively. That palette is independent from notify queue
897
1356
  can still render a non-red action-required status badge.
898
1357
 
899
1358
  `create agent --provider ...` selects a provider without changing the saved
900
- default. Concrete provider invocations create a new managed
901
- agent pane every time; existing managed AI panes in the same project/session are
902
- not selected or reused.
903
- The provider picker and plain Pane creation remain available through the
904
- canonical create workflow. Arguments after `--` are extra arguments appended to
1359
+ default. Concrete provider invocations create a new Agent and a new managed
1360
+ Pane every time; existing managed AI panes in the same project/session are
1361
+ not selected or reused, and rebinding an existing conversation is `agent
1362
+ resume`, a different verb. The scope of the new resources follows
1363
+ [Create scope](#create-scope). The provider picker remains available through
1364
+ `internal agent-pane picker`. Arguments after `--` are extra arguments appended to
905
1365
  the resolved `claude`, `codex`, or `agy` executable inside the managed wrapper;
906
- projmux still sets the context directory, tmux title, AI pane metadata, title
907
- watcher, and split layout.
908
-
909
- Automation callers can add `--print-pane-id` to an explicit direct
910
- `--agent claude|codex|antigravity|shell` launch. On success, stdout contains
911
- exactly the new `%N` pane id followed by one newline. The value comes directly
912
- from tmux's existing
913
- `split-window -P -F '#{pane_id}'` result. If tmux returns no valid pane id, the
914
- command fails non-zero with tmux-specific guidance and writes no
915
- success value. Without `--print-pane-id`, successful split invocations keep the
916
- existing empty-stdout behavior.
917
-
918
- `--print-pane-id` is not available for the saved default mode or for
919
- `--agent selective|resume`, because those paths may open a picker and launch
920
- only after a later user selection. Those combinations fail before opening a
921
- picker or creating a pane. Arguments after `--` keep their existing argv-tail
922
- meaning when the flag is used with a concrete AI agent.
1366
+ projmux still sets the context directory, tmux title, AI pane metadata, and
1367
+ split layout.
1368
+
1369
+ Automation callers get the new pane's handle from `-o pane-id` on the canonical
1370
+ create routes: `projmux create agent --provider <p> --placement right -o pane-id`
1371
+ and `projmux create pane --placement right -o pane-id` each print exactly the
1372
+ managed Pane's `%N` followed by one newline. See
1373
+ [AI Agent Shortcuts](ai-agent-shortcuts.md) for the shortcut spellings.
1374
+
1375
+ Every Projmux split surface produces the same canonical create intent. The
1376
+ default `ai-split-right/down` binding reads the saved split mode and turns it
1377
+ into one intent -- a provider Agent, a shell Pane, or one of the two pickers --
1378
+ and the `Alt-7` picker and the resume picker do the same with what the operator
1379
+ selected. Only the create route's materializer runs tmux's `split-window`, so a
1380
+ pane opened from the UI is a Registry resource on the same terms as one asked for
1381
+ by name, and a failed launch leaves zero Registry and zero tmux mutations. A raw
1382
+ unmanaged split exists only where you make one yourself.
1383
+
923
1384
  The resume picker lists the newest deduplicated Claude, Codex, and Antigravity
924
1385
  resume sessions for the current project, with `[+ New Session]` pinned first.
925
1386
  If there are no resume sessions it goes straight to the existing selective
926
- picker. Selecting a row directly starts `claude --resume <id>`,
927
- `codex resume <id>`, or `agy --conversation <uuid>`.
1387
+ picker. Selecting a row creates a managed Agent whose pane joins that
1388
+ conversation -- `claude --resume <id>`, `codex resume <id>`, or
1389
+ `agy --conversation <uuid>`. Rebinding an Agent the Registry already has is
1390
+ `projmux agent resume`, a different verb that never falls back to a fresh
1391
+ conversation.
1392
+
1393
+ Codex rows come from one source per picker invocation. A healthy app-server is
1394
+ primary: `thread/list` is paged with opaque cursors, non-archived and explicit
1395
+ `cli`/`vscode`/`appServer` source filters, provider recency ordering, and exact
1396
+ cwd/depth filtering. Rows preserve the exact thread id plus provider name,
1397
+ branch, and runtime status; an unnamed thread uses only its short id, never its
1398
+ prompt preview. The row suffix displays native or rollout source, confidence,
1399
+ status, and any closed fallback reason. Unsupported, unavailable, protocol, or
1400
+ malformed-pagination results discard the native partial result and run the
1401
+ existing rollout scan once. Selecting a native row resumes that same thread id
1402
+ through the native lane; selecting a fallback row keeps the current CLI lane.
1403
+ Claude and Antigravity discovery and launch semantics are unchanged.
1404
+
1405
+ `projmux agent review [<agent-ref>]` starts a native Codex review for
1406
+ uncommitted changes by default. Use exactly one of `--base <branch>`, `--commit
1407
+ <sha>`, or `--instructions <text>` to choose another review target. The action
1408
+ is available only when the selected Running Codex Agent still has an exact live
1409
+ Pane/thread binding and the current app-server supports `review/start`; every
1410
+ other case reports review as unavailable without changing the Agent. This route
1411
+ projects only the initial response into interaction status. It does not claim
1412
+ the later notification-driven completion lifecycle.
928
1413
 
929
1414
  Live Antigravity hook/session-state resume metadata remains a separate,
930
1415
  high-confidence lane; it is not enumerated from disk by the picker. Within the
@@ -944,10 +1429,8 @@ Settings > AI Settings > Enabled agents controls Claude/Codex/Antigravity launch
944
1429
  visibility. Disabled agents are hidden from the selective picker and from the
945
1430
  default-mode picker. A saved default that later becomes disabled fails clearly
946
1431
  instead of falling back to another agent. Direct
947
- `--agent claude|codex|antigravity` launches also fail when disabled, including
948
- shortcuts that call the same command. For a deliberate one-shot direct CLI
949
- launch, pass `--force-agent`; picker and saved default paths do not use this
950
- override. If all AI agents are disabled, the selective picker still offers the
1432
+ Canonical `create agent --provider <p>` launches and the provider shortcuts also
1433
+ fail when disabled. If all AI agents are disabled, the selective picker still offers the
951
1434
  plain `shell` split and shows guidance to re-enable Claude/Codex/Antigravity.
952
1435
  For user-level skill, slash-command, editor, or launcher registrations that
953
1436
  call this contract, see [AI Agent Shortcuts](ai-agent-shortcuts.md).
@@ -1283,8 +1766,8 @@ keymap action is no longer accepted: replace a stale
1283
1766
  The `projmux agent topic set/clear` commands keep
1284
1767
  AI topic ownership separate from the user pane label and raw pane title.
1285
1768
  `apply` regenerates the app tmux config and reloads the live `-L projmux`
1286
- server without restarting it. `make install` and `projmux update apply` invoke it
1287
- after replacing the binary. Settings > Keybindings normally runs the same
1769
+ server without restarting it. `make install` and `projmux update apply` invoke
1770
+ it before binary publication and again afterward for verification. Settings > Keybindings normally runs the same
1288
1771
  save/config/reload flow automatically; use `projmux config apply` (or its
1289
1772
  hidden equivalent `projmux internal tmux apply`) as the CLI recovery or sync path after
1290
1773
  hand-editing `keymap.toml`, after saving Settings outside tmux, or after
@@ -1373,15 +1856,21 @@ binary in `$GOBIN`/`$GOPATH/bin`/`~/go/bin` as `go`, and a local `go build`
1373
1856
  still require an explicit `PROJMUX_INSTALLER=github-release`. Anything else is
1374
1857
  reported as `unknown` with guidance.
1375
1858
  `apply` is installer-aware and only runs after explicit user selection.
1376
- For npm installs, it runs `npm install -g projmux@latest` (which reliably
1859
+ For npm installs, the current binary first runs `config apply --bin` with the
1860
+ exact published target. Only after that succeeds does it run
1861
+ `npm install -g projmux@latest` (which reliably
1377
1862
  crosses minor/major versions where `npm update -g` does not, and re-resolves
1378
1863
  the per-platform optional dependency) and then runs the new binary's
1379
- `projmux config apply`. With `--no-apply`, that convergence step uses
1864
+ `projmux config apply` as post-publication verification. A failed preparation
1865
+ does not invoke the installer; later failures are non-zero and print the exact
1866
+ `projmux config apply --socket projmux` recovery. With `--no-apply`, the
1867
+ pre-publication live convergence is omitted and the post-update step uses
1380
1868
  `--no-reload`: it still migrates marker-owned files and writes generated
1381
- configuration without accessing live tmux. For Go installs, it uses the existing atomic
1869
+ configuration without accessing live tmux, then explicitly reports that live
1870
+ apply remains required. For Go installs, it uses the same ordering around the existing atomic
1382
1871
  replacement implementation. For `github-release` installs, it downloads the latest
1383
1872
  matching `projmux_<version>_<goos>_<goarch>.tar.gz` release asset, extracts the
1384
- binary, atomically replaces the current executable, then performs the same
1873
+ binary, pre-converges, atomically replaces the current executable, then performs the same
1385
1874
  apply/`--no-reload` convergence. `source` installs report an
1386
1875
  actionable error to update the checkout with `git pull --ff-only && make install`.
1387
1876
 
@@ -1403,7 +1892,8 @@ returns a usage error.
1403
1892
 
1404
1893
  The live tmux inventory is under `runtime`: `runtime sessions`, `runtime
1405
1894
  attach`, `runtime stop`, `runtime tag`, and `runtime prune`. Project pins use
1406
- `pin project list|add|remove|toggle|clear`. Resource retention uses `prune
1895
+ `pin project list|add|remove|toggle|clear|migrate`; `list` takes `--kind
1896
+ project|candidate` and `migrate` takes `--dry-run`. Resource retention uses `prune
1407
1897
  project|snapshot`, while explicit snapshot deletion uses `delete snapshot`.
1408
1898
 
1409
1899
  Popup-marker, preview, status, and tmux configuration plumbing is hidden under
@@ -1417,20 +1907,33 @@ human configuration work should prefer `config render` and `config apply`.
1417
1907
  generated config. The generated app config uses absolute `$SHELL` as the
1418
1908
  tmux default shell when set, otherwise `/bin/sh`. `shell` starts or attaches
1419
1909
  the app session directly after resolving the target app session name and
1420
- startup directory. Alt-1 sidebar project open defaults to `Project topology`,
1421
- which materializes the Project's Registry Windows and Window-owned shell Panes
1422
- before the client moves; the Session State `Sidebar startup picker` opt-in
1423
- shows `Latest snapshot`, `Named snapshot`, and `Project topology` before
1424
- starting a closed project session. `Latest snapshot` is auto-saved; named
1425
- snapshots are fixed until the user saves or replaces them. A directory with no
1426
- Registry Project, and a Project with no Registry Window, still start as a
1427
- single default session.
1428
- - `quit` — open an action picker with `Quit projmux` and `Cancel`. Selecting
1429
- `Quit projmux` terminates only a `tmux -L projmux` runtime whose global
1430
- `@projmux_app` option is set by the generated app config. Missing servers,
1431
- default tmux servers, embedded tmux servers, and other tmux runtimes without
1432
- that marker are no-ops. Non-interactive callers must pass `--yes` or
1433
- `--force`; the default command always goes through the action picker.
1910
+ startup directory. Alt-1 sidebar project open defaults to `Continue project`,
1911
+ which materializes the Project's Registry Windows, shell Panes, and Agents
1912
+ before the client moves. When the startup picker is enabled it contains exactly
1913
+ `Continue project` and `Open fresh`; Esc returns to Projects. `Continue
1914
+ project` restores a deleted Project only from its usable exact snapshot and
1915
+ otherwise refuses with zero Registry writes. `Open fresh` is a neutral,
1916
+ confirmation-free one-step action that atomically replaces the Project with
1917
+ a new Project/Window/shell UID chain and one same-root claimant. Repeating it
1918
+ allocates another new identity. Neither action modifies snapshot
1919
+ bytes, the project directory, git/worktrees, unrelated roots, or trust state.
1920
+ - `quit` — open an action picker with `Save Project snapshots and quit`, `Quit
1921
+ without saving`, and `Cancel`. The safe first action takes one complete,
1922
+ exact-socket Registry/resource-graph observation, freezes every live managed
1923
+ Project session in Project UID/session order, and captures each latest
1924
+ snapshot even when auto-save is off. Home/control, ephemeral, unattributed,
1925
+ recoverable, foreign, and offline sessions are excluded and reported as
1926
+ bounded class counts. Every target is attempted. A failed capture leaves the
1927
+ successful per-session atomic files in place, reports the exact failed
1928
+ session, and does not stop the app; retry captures every target again. Only an
1929
+ all-success ledger reaches the existing physical-socket, app-marker, and
1930
+ logical-route guarded shutdown. Named snapshots and Registry bytes are never
1931
+ written, and the batch is not a multi-file transaction or topology freeze.
1932
+ `Quit without saving` preserves the earlier guarded shutdown behavior:
1933
+ missing servers and runtimes without the app marker are no-ops. Existing
1934
+ non-interactive `--yes` and `--force` callers retain that same snapshot-free
1935
+ behavior and exact shutdown route; the default command always uses the
1936
+ action picker.
1434
1937
  - `attach project <ref>` — enter a Project runtime from outside tmux.
1435
1938
  Automatic live-runtime attachment is `runtime attach`.
1436
1939
  - `settings` — interactive configuration UI for the project picker, AI