projmux 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/agent-workflow.md +183 -20
- package/docs/ai-agent-shortcuts.md +22 -3
- package/docs/architecture.md +1291 -90
- package/docs/cli-guide.md +499 -57
- package/docs/cli.md +236 -64
- package/docs/configuration.md +197 -35
- package/docs/hooks.md +17 -3
- package/docs/keybindings.md +2 -2
- package/docs/legacy-cli-retirement.md +37 -8
- package/docs/operational-diagnostics.md +58 -2
- package/docs/session-restore.md +73 -157
- package/docs/settings-ia.md +48 -24
- package/docs/statusbar.md +10 -2
- package/docs/testing.md +80 -4
- package/docs/upgrading.md +243 -5
- package/docs/usage-tracking.md +56 -10
- package/package.json +5 -5
package/docs/cli-guide.md
CHANGED
|
@@ -111,13 +111,16 @@ The contract:
|
|
|
111
111
|
--name current` succeeds — so a sentinel token would silently shadow a real
|
|
112
112
|
resource.
|
|
113
113
|
- **"No selector at all" means exactly that.** Any positional `<ref>`, any
|
|
114
|
-
`--project`/`-p`, `--window`/`-w`, `--pane`, or any `--selector` label keeps
|
|
115
|
-
|
|
116
|
-
partially specified selector.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
`--project`/`-p`, `--window`/`-w`, `--pane`, or any `--selector` label keeps
|
|
115
|
+
picking the target itself. The active *target* is never blended into a
|
|
116
|
+
partially specified selector. The active *Project* is a separate rule and does
|
|
117
|
+
apply to a reference -- see [Reference scope](#reference-scope-the-active-project-namespace)
|
|
118
|
+
below.
|
|
119
|
+
- **Only the singular routes and `create`.** The plural reads (`get
|
|
120
|
+
projects|windows|panes|agents`) stay 0..N inventories over their whole scope,
|
|
121
|
+
and `delete` is unchanged. `create` has its own spelling of the same rule --
|
|
122
|
+
see [Create scope](#create-scope) -- because a create resolves a scope to put
|
|
123
|
+
something *into* rather than a target to act *on*.
|
|
121
124
|
- **Inside tmux is decided by `$TMUX_PANE` plus `$TMUX`**, not by whether a tmux
|
|
122
125
|
server answers. A bare `display-message` from outside a client still succeeds
|
|
123
126
|
and answers for the most-recently-used session; projmux never uses that.
|
|
@@ -136,6 +139,155 @@ and `@projmux_window_uid` on its window — and derives every ancestor from
|
|
|
136
139
|
registry `ownerRef`. The session-scoped `@projmux_project_uid` is deliberately
|
|
137
140
|
not consulted.
|
|
138
141
|
|
|
142
|
+
### Plural read scope: the active managed root
|
|
143
|
+
|
|
144
|
+
Inside tmux, selector-omitted `get windows|panes|agents` derives one exact
|
|
145
|
+
managed root from the active Window's Registry `ownerRef`:
|
|
146
|
+
|
|
147
|
+
- a Project-owned Window lists only that Project's descendants;
|
|
148
|
+
- a ControlSession-owned Window, including Home, lists only that
|
|
149
|
+
ControlSession's descendants;
|
|
150
|
+
- a missing Window mirror, a foreign Window uid, or a Window without one exact
|
|
151
|
+
existing Project or ControlSession owner is exit `2` with zero stdout. It
|
|
152
|
+
never falls through to the whole Registry.
|
|
153
|
+
|
|
154
|
+
The default narrows the Window universe and does not pick a target. Name and
|
|
155
|
+
label selectors keep their existing meaning inside that root. An applicable
|
|
156
|
+
explicit `uid:` occurrence is opaque Registry-global authority: like explicit
|
|
157
|
+
`--project`/`-p` and `--all-projects`/`-A`, it bypasses active-target observation
|
|
158
|
+
entirely, including from a foreign tmux Pane. The two whole-set spellings retain
|
|
159
|
+
their existing meaning and include ControlSession descendants. Outside tmux, an
|
|
160
|
+
omitted root keeps the historical whole-Registry inventory without probing a
|
|
161
|
+
default server.
|
|
162
|
+
|
|
163
|
+
### Reference scope: the active Project namespace
|
|
164
|
+
|
|
165
|
+
A `metadata.name` is unique inside its owner scope, never across the registry: a
|
|
166
|
+
Window name is unique inside its Project, a Pane name inside its Window or
|
|
167
|
+
Agent, an Agent name inside its Window. So inside a managed Project a reference
|
|
168
|
+
is resolved inside the Project that owns the active Window, which is the same
|
|
169
|
+
universe the plural reads use in that context:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
projmux describe window zsh # the Window named zsh in *this* Project
|
|
173
|
+
projmux describe pane log # the Pane named log in this Project
|
|
174
|
+
projmux describe agent codex # the Agent named codex in this Project
|
|
175
|
+
projmux rename window zsh --name review
|
|
176
|
+
projmux rename pane log --name build
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The Project-only read matrix is `describe window|pane|agent`. Generic
|
|
180
|
+
`rename window|pane|agent` uses the same narrowing rule but accepts either the
|
|
181
|
+
active Project or ControlSession as the exact owner root. `describe project`
|
|
182
|
+
and `rename project` are not in either matrix — a Project has no enclosing
|
|
183
|
+
root — and `delete`, `rebind`, and `agent resume` keep their selector meaning.
|
|
184
|
+
|
|
185
|
+
The contract:
|
|
186
|
+
|
|
187
|
+
- **It narrows the search, it does not pick the target.** The Project fixes the
|
|
188
|
+
universe and nothing else. The active Window and the active Pane are not used
|
|
189
|
+
to break a tie, so two same-named resources inside the one Project stay the
|
|
190
|
+
ordinary bounded `matched N ..., want exactly one` ambiguity. Resolving that
|
|
191
|
+
is what `--window`/`--pane` and a `uid:` reference are for.
|
|
192
|
+
- **Explicit `--project`/`-p` wins, with zero observations.** Naming a Project
|
|
193
|
+
never costs a tmux round trip and never depends on the pane you are sitting
|
|
194
|
+
in.
|
|
195
|
+
- **A `uid:` reference is scoped too.** A uid that belongs to another Project is
|
|
196
|
+
a no-match, not a cross-Project hit. Pass `--project` to address it.
|
|
197
|
+
- **Outside tmux nothing changes.** The whole registry is searched and the
|
|
198
|
+
previous `matched N ..., want exactly one` ambiguity is unchanged. No default
|
|
199
|
+
tmux server is probed.
|
|
200
|
+
- **Inside tmux a broken owner chain refuses.** A pane carrying no
|
|
201
|
+
`@projmux_window_uid`, a mirrored Window uid the registry does not hold, or a
|
|
202
|
+
Window with no exact existing Project or ControlSession owner is exit `2`
|
|
203
|
+
with zero bytes on stdout and zero mutations for generic rename. It is never
|
|
204
|
+
a silent fallback to the whole registry.
|
|
205
|
+
|
|
206
|
+
### Create scope
|
|
207
|
+
|
|
208
|
+
`create window|pane|agent|<provider>` is resource-backed on every spelling.
|
|
209
|
+
There is no mode flag and no second parser: the same argv means the same thing
|
|
210
|
+
whether or not `--project` is present, and `-w`, `--create-window`, `--pane`,
|
|
211
|
+
`--selector`, `--placement`, `--name`, `--label`, `--cwd`, `--add-dir`, `-o`,
|
|
212
|
+
and the `--` payload all reach the same parser either way.
|
|
213
|
+
|
|
214
|
+
The scope resolves in two branches:
|
|
215
|
+
|
|
216
|
+
- **Explicit `--project`/`-p` wins**, inside tmux and outside it. The active
|
|
217
|
+
tmux target is not consulted at all.
|
|
218
|
+
- **With no `--project`, the Project comes from the active managed runtime**:
|
|
219
|
+
the `@projmux_window_uid` mirrored on the pane you are in, and that Window's
|
|
220
|
+
registry `ownerRef`. This is the same seam the empty-selector reads use.
|
|
221
|
+
|
|
222
|
+
The Window and the anchor Pane follow the *whole* scope rather than the Project
|
|
223
|
+
flag alone:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
projmux create codex # active Project, active Window, split from the active Pane
|
|
227
|
+
projmux create codex -w hi --create-window # active Project, new Window "hi"
|
|
228
|
+
projmux create codex -p beta -w main # everything explicit
|
|
229
|
+
projmux create pane -p alpha # every Window of alpha; a deliberate fan-out
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
One explicit scope occurrence (`--project`, `--window`, `--pane`, or
|
|
233
|
+
`--selector`) makes the whole scope explicit, so naming a Window never picks up
|
|
234
|
+
an anchor from somewhere you did not address. With a scope but no `--pane`, the
|
|
235
|
+
split anchor is the target Window's role-agnostic `spec.anchorPaneRef`. A
|
|
236
|
+
shell-required offline operation may plan a lazy direct
|
|
237
|
+
`spec.defaultShellPaneRef` without replacing an Agent anchor. A missing or stale
|
|
238
|
+
anchor is exit `2` rather than a silent alternate-Pane repair.
|
|
239
|
+
|
|
240
|
+
Refusals are exit `2` with zero Registry writes and zero tmux mutations, and
|
|
241
|
+
they name `--project` as the fix:
|
|
242
|
+
|
|
243
|
+
- outside tmux with no `--project` — no default server is probed;
|
|
244
|
+
- inside Home, a control session, an unattributed pane, or a foreign pane —
|
|
245
|
+
none of those carry a managed identity, and projmux never invents a Project
|
|
246
|
+
from `$HOME`, a session name, or a cwd;
|
|
247
|
+
- a mirrored uid the Registry does not hold, or a Window whose owning Project is
|
|
248
|
+
gone — a `recoverable` runtime is reported, never adopted.
|
|
249
|
+
|
|
250
|
+
Every create is **detached**: no create moves the client. Use `focus pane` or
|
|
251
|
+
`-o pane-id` when you want to end up in the new pane. Creates run against the
|
|
252
|
+
inherited exact socket inside tmux; outside tmux an explicit `--project` is
|
|
253
|
+
required before anything live is touched.
|
|
254
|
+
|
|
255
|
+
#### Splits started from a popup
|
|
256
|
+
|
|
257
|
+
The split UI's own pickers (`M-7`, `M-4`/`C-r`, the pane context menu, and the
|
|
258
|
+
default split key when the saved mode is `selective` or `resume`) run inside a
|
|
259
|
+
`display-popup`. tmux exports `$TMUX` to a popup job and deliberately exports no
|
|
260
|
+
`$TMUX_PANE`, because a popup is not a pane — so the picker has no inherited
|
|
261
|
+
target of its own while still knowing, from the keypress that opened it, which
|
|
262
|
+
pane the operator was in. That pane travels on the create intent as an explicit
|
|
263
|
+
anchor and resolves Project, Window and split anchor through the same identity
|
|
264
|
+
mirror a pane-hosted invocation reads.
|
|
265
|
+
|
|
266
|
+
The anchor is something the split UI hands to `create`, never something `create`
|
|
267
|
+
reads from the environment. `$TMUX_SPLIT_TARGET_PANE` is not a scope override:
|
|
268
|
+
typing `projmux create pane --placement right` inside a popup is still an
|
|
269
|
+
invocation with no target and still refuses with the `--project` usage error
|
|
270
|
+
above, and no read, rename, or delete verb consults it.
|
|
271
|
+
|
|
272
|
+
#### Managed pane context menu
|
|
273
|
+
|
|
274
|
+
The generated `MouseDown3Pane` menu treats its Horizontal Split, Vertical
|
|
275
|
+
Split, and Kill entries as Projmux resource actions. Both splits pass the exact
|
|
276
|
+
clicked pane through the popup-origin anchor above and reach the same canonical
|
|
277
|
+
`create pane` materializer as the CLI, so the new pane receives a Registry uid.
|
|
278
|
+
Kill resolves that anchor's mirrored uid and reaches canonical `delete pane`,
|
|
279
|
+
including its printed delete result. The menu never falls back to a raw tmux
|
|
280
|
+
mutation when either route refuses. The reason is displayed on the exact client
|
|
281
|
+
that opened the menu instead of being lost as a `run-shell` exit code.
|
|
282
|
+
|
|
283
|
+
tmux Respawn has no equivalent in the current resource model: it preserves the
|
|
284
|
+
same pane handle, layout, Registry uid, and original command, while canonical
|
|
285
|
+
delete plus create removes that identity and creates another one (and may end
|
|
286
|
+
the Window or Project session when it deletes the last pane). The generated
|
|
287
|
+
menu therefore omits Respawn entirely and does not expose a refusal handler or
|
|
288
|
+
invent a replace operation. User-authored tmux bindings remain outside this
|
|
289
|
+
managed-menu contract.
|
|
290
|
+
|
|
139
291
|
### Rename and rebind live convergence
|
|
140
292
|
|
|
141
293
|
`rename project|window|pane` commits the selected Registry `metadata.name` and
|
|
@@ -166,6 +318,26 @@ duplicate UID claims remain fail-closed.
|
|
|
166
318
|
|
|
167
319
|
### Agent topic, interaction, activation, and workspace
|
|
168
320
|
|
|
321
|
+
`agent turn start`, `agent turn steer`, `agent turn interrupt`, and
|
|
322
|
+
`agent approval review` use the live Codex app-server connection only when the
|
|
323
|
+
selected Agent, its owned Pane, activation generation, thread, current turn,
|
|
324
|
+
and connection epoch still match exactly. `start` sends only the exact thread
|
|
325
|
+
id and one text input; `steer` supplies the current expected turn id; and
|
|
326
|
+
`interrupt` supplies that exact turn id. These commands never install sticky
|
|
327
|
+
model, effort, cwd, sandbox, permission, or collaboration overrides.
|
|
328
|
+
|
|
329
|
+
Approval review shows only the safe one-shot intersection supplied by the
|
|
330
|
+
exact pending request. Command, file, and network requests are limited to
|
|
331
|
+
`accept`, `decline`, and `cancel`; permission grants echo the received supported
|
|
332
|
+
profile with `scope=turn` and `strictAutoReview=null`. Session grants, policy or
|
|
333
|
+
network amendments, unstable root grants, legacy approval mutations, and
|
|
334
|
+
automatic approval are unavailable. The request envelope and raw JSON-RPC id
|
|
335
|
+
remain only in connection memory; reconnect, resolution, ambiguity, or any
|
|
336
|
+
identity mismatch produces no provider write. Approval queue rows advertise
|
|
337
|
+
`Review pending approval` only while that responder exists, otherwise they
|
|
338
|
+
advertise the exact-Agent `Open Codex` focus fallback; resolution removes the
|
|
339
|
+
row. Neither route stores prompt, command, path, permission, or request content.
|
|
340
|
+
|
|
169
341
|
`agent topic get|set|clear` and `agent status get|set` resolve exactly one
|
|
170
342
|
Agent, either from an explicit Agent reference or from the Agent-owned active
|
|
171
343
|
managed Pane. Topic is a non-identifying Registry annotation. Interaction is a
|
|
@@ -193,6 +365,40 @@ and shell Pane manual attention share the existing priority reducer, while
|
|
|
193
365
|
`dot`/`emoji`/`off`, glyphs, colors, and the Window aggregate are never stored
|
|
194
366
|
in resource metadata.
|
|
195
367
|
|
|
368
|
+
A clean managed process exit is topology authority only through the exact
|
|
369
|
+
generated `pane-exited` hook. The supervisor must have durably journaled a
|
|
370
|
+
same-generation `normal` receipt, the hook must name the exact `%N` Pane and
|
|
371
|
+
the owner Window must carry its exact last-positive `$N/@N` binding on the same
|
|
372
|
+
socket, and fresh preflight plus locked observations must still resolve the
|
|
373
|
+
same Registry owner chain. A non-last Pane is removed while its directly owning
|
|
374
|
+
Agent is retained Offline with its conversation identity. For a last Pane, that evidence is retained until a
|
|
375
|
+
matching `window-unlinked` hook removes the Window; a final Project Window also
|
|
376
|
+
removes its Window descendants while retaining the exact Project uid, root,
|
|
377
|
+
reservation, pins, snapshots, and external assets as a valid zero-Window
|
|
378
|
+
Project. Managed runtime Stop is different: it stops only the exact runtime and
|
|
379
|
+
keeps the complete desired Project/Window/Pane graph closed for a same-UID
|
|
380
|
+
Continue. Shell and Claude/Codex clean exit have the same result; `/exit`, pane
|
|
381
|
+
content, prompt, history, and transcript are never parsed. `abnormal`,
|
|
382
|
+
`killed`, `unknown`, stale/resumed bindings, empty or unavailable inventory,
|
|
383
|
+
permission failure, and foreign-host/window observations keep their diagnostic
|
|
384
|
+
rows and produce no automatic delete plan.
|
|
385
|
+
|
|
386
|
+
For `pane-exited`, tmux supplies `%N` as `#{hook_pane}`. Its current-context
|
|
387
|
+
session/window formats may already name a surviving client Window, so the owner
|
|
388
|
+
pair comes from the Window's last live Registry observation. The separate
|
|
389
|
+
`window-unlinked` hook supplies exact `#{hook_session}` and `#{hook_window}`;
|
|
390
|
+
only a matching causal pair authorizes last-Pane Window deletion.
|
|
391
|
+
|
|
392
|
+
An offline historical Window with no stored causal last-Pane receipt is not
|
|
393
|
+
absence-only migration authority and is never auto-deleted. Recover it with the
|
|
394
|
+
canonical exact route, `projmux delete window uid:<window-uid> --socket <name>
|
|
395
|
+
--yes` (or the corresponding `--socket-path`/inherited absolute `$TMUX` route).
|
|
396
|
+
Deleting a Project's last exact Window retains the Project with zero Windows;
|
|
397
|
+
within the runtime/startup lifecycle table, explicit `delete project --yes` is
|
|
398
|
+
the unregister operation. Runtime absence, managed Stop, ordinary Window close,
|
|
399
|
+
and Fresh never invoke the separately scoped filesystem-missing `prune project`
|
|
400
|
+
administrative policy.
|
|
401
|
+
|
|
196
402
|
Resource-backed Agent create accepts provider-neutral `--cwd <absolute>` and
|
|
197
403
|
repeatable `--add-dir <absolute>`. Explicit paths must exist, resolve without a
|
|
198
404
|
symlink escape, and remain inside a registered Project tree; only Codex and
|
|
@@ -210,8 +416,15 @@ owner Project root from `get`/`describe`, and a successful resume persists that
|
|
|
210
416
|
normalized effective workspace without changing Window Project ownership.
|
|
211
417
|
|
|
212
418
|
When `create agent -- <initial-prompt>` is used, normal resource creation and
|
|
213
|
-
provider activation are distinct. Projmux waits for
|
|
214
|
-
|
|
419
|
+
provider activation are distinct. Projmux first waits up to five seconds for an
|
|
420
|
+
exact provider `SessionStart`; that readiness evidence leaves activation
|
|
421
|
+
`pending` and opens an independent five-second initial-task acknowledgement
|
|
422
|
+
window. A `UserPromptSubmit` acknowledgement may also arrive directly before
|
|
423
|
+
the readiness observer sees `SessionStart`. The two stages are independently
|
|
424
|
+
bounded, so provider startup plus an acknowledgement later than two seconds may
|
|
425
|
+
take more than five but never more than ten seconds. Neither stage captures pane
|
|
426
|
+
content or stores the prompt. Acknowledgement returns success and the requested
|
|
427
|
+
exact `%N` output. If
|
|
215
428
|
activation cannot be confirmed, the command exits nonzero while naming the
|
|
216
429
|
exact Agent UID and Pane plus safe provider retry and `delete agent ... --yes`
|
|
217
430
|
cleanup options. The live resources remain explicit and retryable rather than
|
|
@@ -219,7 +432,13 @@ being reported as an ordinary success.
|
|
|
219
432
|
|
|
220
433
|
Activation metadata is bounded to provider-hook provenance and fixed
|
|
221
434
|
acknowledged/timed-out/failed diagnostics. Provider error strings and initial
|
|
222
|
-
prompt text are never stored.
|
|
435
|
+
prompt text are never stored. `pending` can become `unconfirmed` and a later
|
|
436
|
+
exact hook can refine either state to `acknowledged`; acknowledged never moves
|
|
437
|
+
backward. Late refinement must quote the same Agent, Pane uid, activation
|
|
438
|
+
generation, and recorded live `%N`, so a hook from a replaced materialization
|
|
439
|
+
cannot acknowledge its replacement. Codex and Claude initial payloads use the
|
|
440
|
+
same acknowledgement and result contract; provider-specific prompt content is
|
|
441
|
+
not evidence. Resource-backed Agent create and resume do not
|
|
223
442
|
start the legacy title/content watcher; that watcher remains only for legacy
|
|
224
443
|
non-resource panes and exits before reading title or capture content if resource
|
|
225
444
|
identity appears.
|
|
@@ -315,14 +534,23 @@ client only after it converges; a refusal, a failed preflight, or a rolled-back
|
|
|
315
534
|
partial leaves the client where it was and reports the exact stage. The
|
|
316
535
|
activation is pinned to the session the open targets, so a Project whose
|
|
317
536
|
Registry projects a different session name is refused instead of populating a
|
|
318
|
-
session the open never reaches.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
537
|
+
session the open never reaches. The closed-Project startup screen has exactly
|
|
538
|
+
two neutral actions. `Continue project` materializes current Registry desired
|
|
539
|
+
state with the same Project UID. A retained graph keeps descendant UIDs; a
|
|
540
|
+
zero-Window Project atomically receives a new canonical Window/shell UID chain.
|
|
541
|
+
A deleted Project may use only the exact usable snapshot compatibility path;
|
|
542
|
+
an unavailable Continue is an explicit zero-write refusal with no Fresh
|
|
543
|
+
fallback. `Open fresh` is one step with no danger styling, confirmation, or
|
|
544
|
+
delete counts: it atomically replaces the same-root graph with a new Project
|
|
545
|
+
UID and new canonical Window/shell UIDs, then hands off only after ordinary
|
|
546
|
+
materialization. A repeat replaces identity again. The root, git/worktrees,
|
|
547
|
+
trust decision, unrelated roots, and snapshot bytes remain unchanged.
|
|
548
|
+
|
|
549
|
+
Materialization launches or resumes declared Agents through the canonical
|
|
550
|
+
provider/trust path and creates their managed Agent-owned Panes. An individual
|
|
551
|
+
Agent refusal preserves the committed desired Registry and emits an item notice
|
|
552
|
+
for retry. It never directly executes `Pane.spec.command`; that field remains a
|
|
553
|
+
one-time name seed. A new Window binds only its own tmux-created primary Pane. On an existing Window,
|
|
326
554
|
every pre-existing uid-less Pane is refused rather than adopted; foreign,
|
|
327
555
|
duplicate, wrong-owner, or otherwise ambiguous UID state is refused before the
|
|
328
556
|
first create. Layout uses the existing deterministic right-axis equalizer and does
|
|
@@ -334,6 +562,193 @@ Registry-only: its complete Agent/Pane cascade is shown under `--dry-run`, no
|
|
|
334
562
|
tmux object is killed, and unrelated live objects and sockets are untouched. A
|
|
335
563
|
unique live mirror keeps the exact-kill path, while duplicate, foreign,
|
|
336
564
|
stale-owner, inventory-failure, and revalidation-race states are refused.
|
|
565
|
+
The selected Window's Registry `ownerRef` is authoritative in that preflight:
|
|
566
|
+
a Project uses its `status.session` plus the optional matching Project uid
|
|
567
|
+
mirror, while a ControlSession uses its exact `spec.session` and requires that
|
|
568
|
+
no Project uid mirror contaminate the control session. Window, Pane, and Agent
|
|
569
|
+
deletes preserve that `(root kind, root uid)` chain in the signed live plan and
|
|
570
|
+
report a final-window cascade with its actual root kind.
|
|
571
|
+
|
|
572
|
+
Pane and Agent Registry-only deletion is deliberately narrower. It accepts
|
|
573
|
+
only an explicit exact `uid:` selector: a Pane must carry durable
|
|
574
|
+
`MissingRuntime=True/RuntimeUnbound` evidence, and an Agent must be `Offline`
|
|
575
|
+
with no `paneRef` (with every retained descendant Pane also marked
|
|
576
|
+
`MissingRuntime`). The exact routed server must answer with a non-empty socket
|
|
577
|
+
identity and a non-empty Pane inventory that proves the target has zero mirrors.
|
|
578
|
+
A missing server, empty or failed inventory, unavailable or permission-denied
|
|
579
|
+
transport, implicit/name/scope/`--all` selection, and duplicate or foreign
|
|
580
|
+
mirrors are not absence authority and make zero writes. Dry-run and apply sign
|
|
581
|
+
the same socket, owner/root chain, lifecycle evidence, Pane activation
|
|
582
|
+
generation, and Agent binding; locked revalidation refuses zero-to-live,
|
|
583
|
+
live-to-zero, owner, generation, duplicate, or foreign changes. A successful
|
|
584
|
+
Registry-only result reports that no tmux Pane was killed, preserves the owning
|
|
585
|
+
Window/root/socket and all siblings, and repeating the exact apply returns the
|
|
586
|
+
ordinary no-match result.
|
|
587
|
+
|
|
588
|
+
`delete window|pane|agent` names the server its live half addresses the same
|
|
589
|
+
way `reconcile resources` does: `--socket <name>`, `--socket-path <absolute>`,
|
|
590
|
+
or the inherited absolute `$TMUX`. Outside tmux with neither flag it refuses
|
|
591
|
+
rather than reaching for the app's own socket, so a delete issued against an
|
|
592
|
+
isolated server can never inventory one host and kill objects on another.
|
|
593
|
+
|
|
594
|
+
Before it kills anything, a delete commits an intentional termination receipt
|
|
595
|
+
against every Pane whose process it is about to end, in its own Registry
|
|
596
|
+
transaction. If that write fails, nothing live is touched; if the delete then
|
|
597
|
+
refuses for any other reason, the receipt is withdrawn again. The receipt is
|
|
598
|
+
what tells a later reader that a process disappeared because someone asked for
|
|
599
|
+
it, rather than because it crashed.
|
|
600
|
+
|
|
601
|
+
## get runtime
|
|
602
|
+
|
|
603
|
+
```text
|
|
604
|
+
projmux get runtime sessions|windows|panes [--socket <name> | --socket-path <absolute>] [-o json|none]
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
`get runtime` is the read-only escape hatch onto one exact tmux server. The
|
|
608
|
+
resource reads (`get projects|windows|panes|agents`) enumerate the Registry;
|
|
609
|
+
this one enumerates the machine, including everything projmux does not own, and
|
|
610
|
+
it accepts no selector because most of what it reports has no name to resolve.
|
|
611
|
+
Its kinds are tmux object kinds, not resource kinds, and they have no singular
|
|
612
|
+
spelling for the same reason.
|
|
613
|
+
|
|
614
|
+
Every row carries the attribution the resolved resource graph decided from exact
|
|
615
|
+
evidence -- `managed`, `recoverable`, `control`, `ephemeral`, `unattributed`,
|
|
616
|
+
`foreign`, `conflict` -- the reason for it, the stable tmux id, the fully
|
|
617
|
+
qualified coordinate (`<session>`, `<session>:@N`, `<session>:@N.%N`), and, for a
|
|
618
|
+
managed object, the Registry resource it is bound to. A refused object is named
|
|
619
|
+
and explained and is never handed a resource identity.
|
|
620
|
+
|
|
621
|
+
Socket selection is the same fail-closed rule `reconcile resources` uses, with
|
|
622
|
+
one difference at the end:
|
|
623
|
+
|
|
624
|
+
- `--socket <name>` means exactly `tmux -L <name>`.
|
|
625
|
+
- `--socket-path <absolute>` means exactly `tmux -S <absolute>`.
|
|
626
|
+
- The two flags are mutually exclusive.
|
|
627
|
+
- With neither flag, an invocation inside tmux inherits only the absolute socket
|
|
628
|
+
path from `$TMUX` and uses `-S`.
|
|
629
|
+
- With neither flag outside tmux the read still succeeds. It returns the
|
|
630
|
+
unavailable projection: no items, every scope reported unobservable with a
|
|
631
|
+
reason, and zero tmux calls. There is no default-socket guess, and a sibling
|
|
632
|
+
socket is never read.
|
|
633
|
+
|
|
634
|
+
The default projection is a table preceded by a header line naming the host mode
|
|
635
|
+
and the exact transport, plus one line per scope that could not be observed. The
|
|
636
|
+
header is always printed, even when the table is empty: "no sessions" is only
|
|
637
|
+
trustworthy next to which server was asked and whether the answer could be taken
|
|
638
|
+
at all. `-o json` emits the same data as a stable `Runtime{Session,Window,Pane}List`
|
|
639
|
+
envelope; `-o none` prints nothing. The Registry projections (`uid`, `name`,
|
|
640
|
+
`ref`, `metadata`) are deliberately not offered, because most of what this route
|
|
641
|
+
returns has none of them.
|
|
642
|
+
|
|
643
|
+
The read writes nothing: the Registry is opened without being created, the
|
|
644
|
+
observation issues one option probe and three list queries whatever the size of
|
|
645
|
+
the server, and no write verb is ever sent.
|
|
646
|
+
|
|
647
|
+
## runtime diagnostics
|
|
648
|
+
|
|
649
|
+
```text
|
|
650
|
+
projmux runtime diagnostics [--socket <name> | --socket-path <absolute>] [--ui=popup|sidebar]
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
The interactive half of the same read. It lists every tmux object on the exact
|
|
654
|
+
server in containment order with an attribution tally, and it is deliberately
|
|
655
|
+
separate from `projmux runtime sessions`: that picker lists recent sessions so
|
|
656
|
+
you can open one, this one lists everything so you can understand it.
|
|
657
|
+
|
|
658
|
+
Selecting a row opens its action menu, which offers only routes that already
|
|
659
|
+
exist:
|
|
660
|
+
|
|
661
|
+
- **Focus** hands `projmux focus` the row's exact coordinate and the server's own
|
|
662
|
+
`#{socket_path}`. It moves a client and never materializes anything.
|
|
663
|
+
- **Attach** forwards to `projmux attach project uid:<uid>`, and is offered only
|
|
664
|
+
for a session bound to a Registry Project while you are outside tmux.
|
|
665
|
+
- **Open Resource Inspector** opens `projmux resources` unchanged.
|
|
666
|
+
|
|
667
|
+
An action that does not apply is listed with the reason instead of being hidden:
|
|
668
|
+
"no Registry Project claims this session; diagnostics never adopts one" is the
|
|
669
|
+
diagnostic. There is no adopt, import, rename, or kill action, and opening the
|
|
670
|
+
surface writes nothing.
|
|
671
|
+
|
|
672
|
+
## reconcile registry
|
|
673
|
+
|
|
674
|
+
```text
|
|
675
|
+
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]
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
`reconcile registry` is the recovery boundary for the Registry itself. It is a
|
|
679
|
+
sibling of `reconcile resources`, not a stronger version of it: `reconcile
|
|
680
|
+
resources` converges a Registry that loads, and this route runs when the
|
|
681
|
+
Registry is the thing that is wrong.
|
|
682
|
+
|
|
683
|
+
Planning writes nothing. With no `--source` — and with `--dry-run` at any time —
|
|
684
|
+
the command reads the current `registry.json`, the `registry.initialized`
|
|
685
|
+
marker, and the bounded copies under `recovery/`, then reports:
|
|
686
|
+
|
|
687
|
+
- the current state as `valid`, `first-use`, `missing`, `empty`, `malformed`,
|
|
688
|
+
`schema-too-new`, `invalid`, or `unreadable`, with a `sha256:` digest of the
|
|
689
|
+
exact bytes;
|
|
690
|
+
- every candidate newest first, each marked `eligible` or `rejected` with the
|
|
691
|
+
reason, its digest, size, mtime, schema version, and the
|
|
692
|
+
projects/windows/panes/agents/reservations it holds;
|
|
693
|
+
- the exact guarded command that would restore the candidate it suggests.
|
|
694
|
+
|
|
695
|
+
No lock is taken, no permission is repaired, and `<state>/projmux/metadata/` is
|
|
696
|
+
not created — a preview is safe against a first-use state directory and against
|
|
697
|
+
one nobody should be writing to yet.
|
|
698
|
+
|
|
699
|
+
Restoring requires `--source`. There is no "restore the newest" mode: which copy
|
|
700
|
+
is the truth is a judgment about which mutations were wanted. A source is an
|
|
701
|
+
exact copy name, a unique fragment of one, or an absolute path to a copy carried
|
|
702
|
+
from elsewhere; a fragment matching several copies is refused rather than ranked.
|
|
703
|
+
An explicit path gets exactly the same verification as a bounded copy.
|
|
704
|
+
|
|
705
|
+
Verification is fail closed. Malformed JSON, an empty file, an envelope newer
|
|
706
|
+
than this build, and a graph with a duplicate uid, a dangling `ownerRef`, or a
|
|
707
|
+
broken name reservation are all refused with the current Registry byte-identical.
|
|
708
|
+
The verified bytes are then published **verbatim**, so uids, owner relations, and
|
|
709
|
+
name reservations are preserved exactly rather than re-encoded, and a
|
|
710
|
+
known-older-but-valid envelope stays readable through the normal safe read and
|
|
711
|
+
migrates on the next semantic write.
|
|
712
|
+
|
|
713
|
+
The bytes being replaced are kept first, at
|
|
714
|
+
`recovery/replaced-<stamp>-<seq>.json`. Unlike the write-side copies this keeps
|
|
715
|
+
content that does not verify: a damaged Registry is the only remaining evidence
|
|
716
|
+
if the restore turns out to be the wrong call, and the preserved copy is offered
|
|
717
|
+
back as a candidate. Replaced copies are their own bounded family, so a restore
|
|
718
|
+
never consumes the automatic write history.
|
|
719
|
+
|
|
720
|
+
Race guards are the operator's tie to the plan they read:
|
|
721
|
+
|
|
722
|
+
- `--expect-source-checksum <sha256:hex>` refuses unless the source still hashes
|
|
723
|
+
to that digest.
|
|
724
|
+
- `--expect-current-checksum <sha256:hex>` refuses unless the current Registry
|
|
725
|
+
still does.
|
|
726
|
+
- Both are what the printed `next:` command already carries, so copy-pasting the
|
|
727
|
+
preview's suggestion is guarded by construction.
|
|
728
|
+
|
|
729
|
+
Underneath, the source is re-read and re-verified under the store lock, the
|
|
730
|
+
staged copy is re-validated, and both inputs are re-hashed immediately before the
|
|
731
|
+
single atomic rename. Anything that moved refuses with nothing published, no
|
|
732
|
+
preserved copy, and no staged file left behind, and says to re-run the preview. A
|
|
733
|
+
repeat restore is a byte no-op: no rename, no preserved copy, no marker write.
|
|
734
|
+
Restoring into a state directory with no marker publishes one, so a later loss on
|
|
735
|
+
that machine reads as state loss rather than as a fresh first use.
|
|
736
|
+
|
|
737
|
+
When recovery is needed and no verified copy exists, the report adds a mirror
|
|
738
|
+
diagnostic — and it is **only** a diagnostic. It reports the Projmux identity the
|
|
739
|
+
one exact tmux server still carries (Project/Window/Pane uids, mirrored names,
|
|
740
|
+
the Project root, and containment resolved from stable tmux ids) beside a fixed
|
|
741
|
+
statement of what no mirror can return: offline resources, every Agent (no tmux
|
|
742
|
+
option carries an Agent uid), an Agent-owned Pane's `ownerRef`, the name
|
|
743
|
+
reservation table, `spec.anchorPaneRef`, `spec.defaultShellPaneRef`, and labels/annotations/timestamps/
|
|
744
|
+
status. Panes carrying a provider option are counted as proof that Agents existed
|
|
745
|
+
whose uids are nowhere on the server. Nothing is imported and no Registry is
|
|
746
|
+
generated from fragments. Socket selection follows the same `--socket` /
|
|
747
|
+
`--socket-path` / inherited-`$TMUX` rule as `reconcile resources`, except that
|
|
748
|
+
having no exact target is reported as a reason rather than being a usage error:
|
|
749
|
+
a restore is a filesystem operation, so recovery must work on a machine with no
|
|
750
|
+
tmux server. The diagnostic is skipped entirely when a verified copy exists or
|
|
751
|
+
the Registry is healthy.
|
|
337
752
|
|
|
338
753
|
## Internal plumbing (`projmux internal ...`)
|
|
339
754
|
|
|
@@ -865,8 +1280,8 @@ supplied window.
|
|
|
865
1280
|
## Agent creation and hook ingress
|
|
866
1281
|
|
|
867
1282
|
```
|
|
868
|
-
projmux create agent --provider <claude|codex|antigravity> [--placement right|down] ...
|
|
869
|
-
projmux create pane [--placement right|down] ...
|
|
1283
|
+
projmux create agent --provider <claude|codex|antigravity> [--project <ref>] [--window <ref>]... [--create-window] [--placement right|down] ...
|
|
1284
|
+
projmux create pane [--project <ref>] [--window <ref>]... [--create-window] [--placement right|down] ...
|
|
870
1285
|
projmux config edit [--get|--set <mode>]
|
|
871
1286
|
projmux agent status set <thinking|waiting|idle> [pane]
|
|
872
1287
|
projmux agent topic ...
|
|
@@ -897,34 +1312,60 @@ yellow respectively. That palette is independent from notify queue
|
|
|
897
1312
|
can still render a non-red action-required status badge.
|
|
898
1313
|
|
|
899
1314
|
`create agent --provider ...` selects a provider without changing the saved
|
|
900
|
-
default. Concrete provider invocations create a new managed
|
|
901
|
-
|
|
902
|
-
not selected or reused
|
|
903
|
-
|
|
904
|
-
|
|
1315
|
+
default. Concrete provider invocations create a new Agent and a new managed
|
|
1316
|
+
Pane every time; existing managed AI panes in the same project/session are
|
|
1317
|
+
not selected or reused, and rebinding an existing conversation is `agent
|
|
1318
|
+
resume`, a different verb. The scope of the new resources follows
|
|
1319
|
+
[Create scope](#create-scope). The provider picker remains available through
|
|
1320
|
+
`internal agent-pane picker`. Arguments after `--` are extra arguments appended to
|
|
905
1321
|
the resolved `claude`, `codex`, or `agy` executable inside the managed wrapper;
|
|
906
|
-
projmux still sets the context directory, tmux title, AI pane metadata,
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
Automation callers
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1322
|
+
projmux still sets the context directory, tmux title, AI pane metadata, and
|
|
1323
|
+
split layout.
|
|
1324
|
+
|
|
1325
|
+
Automation callers get the new pane's handle from `-o pane-id` on the canonical
|
|
1326
|
+
create routes: `projmux create agent --provider <p> --placement right -o pane-id`
|
|
1327
|
+
and `projmux create pane --placement right -o pane-id` each print exactly the
|
|
1328
|
+
managed Pane's `%N` followed by one newline. See
|
|
1329
|
+
[AI Agent Shortcuts](ai-agent-shortcuts.md) for the shortcut spellings.
|
|
1330
|
+
|
|
1331
|
+
Every Projmux split surface produces the same canonical create intent. The
|
|
1332
|
+
default `ai-split-right/down` binding reads the saved split mode and turns it
|
|
1333
|
+
into one intent -- a provider Agent, a shell Pane, or one of the two pickers --
|
|
1334
|
+
and the `Alt-7` picker and the resume picker do the same with what the operator
|
|
1335
|
+
selected. Only the create route's materializer runs tmux's `split-window`, so a
|
|
1336
|
+
pane opened from the UI is a Registry resource on the same terms as one asked for
|
|
1337
|
+
by name, and a failed launch leaves zero Registry and zero tmux mutations. A raw
|
|
1338
|
+
unmanaged split exists only where you make one yourself.
|
|
1339
|
+
|
|
923
1340
|
The resume picker lists the newest deduplicated Claude, Codex, and Antigravity
|
|
924
1341
|
resume sessions for the current project, with `[+ New Session]` pinned first.
|
|
925
1342
|
If there are no resume sessions it goes straight to the existing selective
|
|
926
|
-
picker. Selecting a row
|
|
927
|
-
`
|
|
1343
|
+
picker. Selecting a row creates a managed Agent whose pane joins that
|
|
1344
|
+
conversation -- `claude --resume <id>`, `codex resume <id>`, or
|
|
1345
|
+
`agy --conversation <uuid>`. Rebinding an Agent the Registry already has is
|
|
1346
|
+
`projmux agent resume`, a different verb that never falls back to a fresh
|
|
1347
|
+
conversation.
|
|
1348
|
+
|
|
1349
|
+
Codex rows come from one source per picker invocation. A healthy app-server is
|
|
1350
|
+
primary: `thread/list` is paged with opaque cursors, non-archived and explicit
|
|
1351
|
+
`cli`/`vscode`/`appServer` source filters, provider recency ordering, and exact
|
|
1352
|
+
cwd/depth filtering. Rows preserve the exact thread id plus provider name,
|
|
1353
|
+
branch, and runtime status; an unnamed thread uses only its short id, never its
|
|
1354
|
+
prompt preview. The row suffix displays native or rollout source, confidence,
|
|
1355
|
+
status, and any closed fallback reason. Unsupported, unavailable, protocol, or
|
|
1356
|
+
malformed-pagination results discard the native partial result and run the
|
|
1357
|
+
existing rollout scan once. Selecting a native row resumes that same thread id
|
|
1358
|
+
through the native lane; selecting a fallback row keeps the current CLI lane.
|
|
1359
|
+
Claude and Antigravity discovery and launch semantics are unchanged.
|
|
1360
|
+
|
|
1361
|
+
`projmux agent review [<agent-ref>]` starts a native Codex review for
|
|
1362
|
+
uncommitted changes by default. Use exactly one of `--base <branch>`, `--commit
|
|
1363
|
+
<sha>`, or `--instructions <text>` to choose another review target. The action
|
|
1364
|
+
is available only when the selected Running Codex Agent still has an exact live
|
|
1365
|
+
Pane/thread binding and the current app-server supports `review/start`; every
|
|
1366
|
+
other case reports review as unavailable without changing the Agent. This route
|
|
1367
|
+
projects only the initial response into interaction status. It does not claim
|
|
1368
|
+
the later notification-driven completion lifecycle.
|
|
928
1369
|
|
|
929
1370
|
Live Antigravity hook/session-state resume metadata remains a separate,
|
|
930
1371
|
high-confidence lane; it is not enumerated from disk by the picker. Within the
|
|
@@ -944,10 +1385,8 @@ Settings > AI Settings > Enabled agents controls Claude/Codex/Antigravity launch
|
|
|
944
1385
|
visibility. Disabled agents are hidden from the selective picker and from the
|
|
945
1386
|
default-mode picker. A saved default that later becomes disabled fails clearly
|
|
946
1387
|
instead of falling back to another agent. Direct
|
|
947
|
-
|
|
948
|
-
|
|
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
|
|
1388
|
+
Canonical `create agent --provider <p>` launches and the provider shortcuts also
|
|
1389
|
+
fail when disabled. If all AI agents are disabled, the selective picker still offers the
|
|
951
1390
|
plain `shell` split and shows guidance to re-enable Claude/Codex/Antigravity.
|
|
952
1391
|
For user-level skill, slash-command, editor, or launcher registrations that
|
|
953
1392
|
call this contract, see [AI Agent Shortcuts](ai-agent-shortcuts.md).
|
|
@@ -1403,7 +1842,8 @@ returns a usage error.
|
|
|
1403
1842
|
|
|
1404
1843
|
The live tmux inventory is under `runtime`: `runtime sessions`, `runtime
|
|
1405
1844
|
attach`, `runtime stop`, `runtime tag`, and `runtime prune`. Project pins use
|
|
1406
|
-
`pin project list|add|remove|toggle|clear
|
|
1845
|
+
`pin project list|add|remove|toggle|clear|migrate`; `list` takes `--kind
|
|
1846
|
+
project|candidate` and `migrate` takes `--dry-run`. Resource retention uses `prune
|
|
1407
1847
|
project|snapshot`, while explicit snapshot deletion uses `delete snapshot`.
|
|
1408
1848
|
|
|
1409
1849
|
Popup-marker, preview, status, and tmux configuration plumbing is hidden under
|
|
@@ -1417,14 +1857,16 @@ human configuration work should prefer `config render` and `config apply`.
|
|
|
1417
1857
|
generated config. The generated app config uses absolute `$SHELL` as the
|
|
1418
1858
|
tmux default shell when set, otherwise `/bin/sh`. `shell` starts or attaches
|
|
1419
1859
|
the app session directly after resolving the target app session name and
|
|
1420
|
-
startup directory. Alt-1 sidebar project open defaults to `
|
|
1421
|
-
which materializes the Project's Registry Windows
|
|
1422
|
-
before the client moves
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1860
|
+
startup directory. Alt-1 sidebar project open defaults to `Continue project`,
|
|
1861
|
+
which materializes the Project's Registry Windows, shell Panes, and Agents
|
|
1862
|
+
before the client moves. When the startup picker is enabled it contains exactly
|
|
1863
|
+
`Continue project` and `Open fresh`; Esc returns to Projects. `Continue
|
|
1864
|
+
project` restores a deleted Project only from its usable exact snapshot and
|
|
1865
|
+
otherwise refuses with zero Registry writes. `Open fresh` is a neutral,
|
|
1866
|
+
confirmation-free one-step action that atomically replaces the Project with
|
|
1867
|
+
a new Project/Window/shell UID chain and one same-root claimant. Repeating it
|
|
1868
|
+
allocates another new identity. Neither action modifies snapshot
|
|
1869
|
+
bytes, the project directory, git/worktrees, unrelated roots, or trust state.
|
|
1428
1870
|
- `quit` — open an action picker with `Quit projmux` and `Cancel`. Selecting
|
|
1429
1871
|
`Quit projmux` terminates only a `tmux -L projmux` runtime whose global
|
|
1430
1872
|
`@projmux_app` option is set by the generated app config. Missing servers,
|