projmux 0.15.2 → 0.16.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/README-ko.md +3 -4
- package/README.md +3 -3
- package/docs/agent-message-replies.md +82 -2
- package/docs/ai-agent-shortcuts.md +6 -5
- package/docs/architecture.md +149 -63
- package/docs/claude-coordination-endpoints.md +192 -21
- package/docs/cli-guide.md +322 -124
- package/docs/cli.md +605 -500
- package/docs/codex-installed-compatibility.md +6 -11
- package/docs/codex-native-required-migration.md +1 -59
- package/docs/configuration.md +164 -176
- package/docs/globalization.md +11 -1
- package/docs/heterogeneous-dialogue-canary.md +8 -3
- package/docs/hooks.md +83 -32
- package/docs/keybindings.md +108 -3
- package/docs/legacy-cli-retirement.md +3 -3
- package/docs/legacy-diagnostics-inventory.md +4 -4
- package/docs/native-picker.md +3 -5
- package/docs/notify-queue.md +1 -1
- package/docs/operational-diagnostics.md +53 -31
- package/docs/pr-guideline.md +66 -22
- package/docs/release.md +97 -0
- package/docs/replacement-contract.md +66 -58
- package/docs/resource-attribution.md +2 -2
- package/docs/session-restore.md +46 -80
- package/docs/settings-ia.md +43 -20
- package/docs/statusbar.md +25 -22
- package/docs/testing.md +15 -0
- package/docs/theme-palette.md +14 -0
- package/docs/tmux-surface-inventory.md +8 -10
- package/docs/troubleshooting.md +2 -4
- package/docs/upgrading.md +142 -7
- package/docs/usage-tracking.md +56 -53
- package/package.json +5 -5
- package/docs/agent-workflow.md +0 -2123
- package/docs/codex-generation-pool.md +0 -623
- package/docs/codex-stored-qualification.md +0 -45
package/README-ko.md
CHANGED
|
@@ -72,8 +72,8 @@ projmux create claude --project mobile-client -- "마이그레이션 계획 초
|
|
|
72
72
|
|
|
73
73
|
- [Resource Inspector](docs/resource-attribution.md) — 프로젝트·창·pane별 CPU와
|
|
74
74
|
RSS를 실시간으로 봅니다.
|
|
75
|
-
- [
|
|
76
|
-
|
|
75
|
+
- [Project Startup](docs/session-restore.md) — 닫힌 프로젝트를 Registry에 저장된
|
|
76
|
+
창 구성으로 이어 열거나 새로 만듭니다.
|
|
77
77
|
- 검색 루트, 키 설정, 업데이트는 설정에서 바꿉니다.
|
|
78
78
|
|
|
79
79
|
## 요구 사항
|
|
@@ -93,8 +93,7 @@ projmux create claude --project mobile-client -- "마이그레이션 계획 초
|
|
|
93
93
|
[상태 표시줄](docs/statusbar.md) ·
|
|
94
94
|
[훅](docs/hooks.md) ·
|
|
95
95
|
[사용량 추적](docs/usage-tracking.md) ·
|
|
96
|
-
[운영 진단](docs/operational-diagnostics.md)
|
|
97
|
-
[에이전트 작업 흐름](docs/agent-workflow.md)
|
|
96
|
+
[운영 진단](docs/operational-diagnostics.md)
|
|
98
97
|
|
|
99
98
|
## 개발
|
|
100
99
|
|
package/README.md
CHANGED
|
@@ -72,7 +72,8 @@ Templates and naming conventions are in
|
|
|
72
72
|
|
|
73
73
|
- Live per-project, per-window, and per-pane CPU/RSS in the
|
|
74
74
|
[Resource Inspector](docs/resource-attribution.md).
|
|
75
|
-
-
|
|
75
|
+
- Registry-based Continue project and Clear layout and open in
|
|
76
|
+
[Project Startup](docs/session-restore.md).
|
|
76
77
|
- Search roots, keybindings, and updates in Settings.
|
|
77
78
|
|
|
78
79
|
## Requirements
|
|
@@ -92,8 +93,7 @@ Templates and naming conventions are in
|
|
|
92
93
|
[Statusbar](docs/statusbar.md) ·
|
|
93
94
|
[Hooks](docs/hooks.md) ·
|
|
94
95
|
[Usage tracking](docs/usage-tracking.md) ·
|
|
95
|
-
[Operational Diagnostics](docs/operational-diagnostics.md)
|
|
96
|
-
[Agent Workflow](docs/agent-workflow.md)
|
|
96
|
+
[Operational Diagnostics](docs/operational-diagnostics.md)
|
|
97
97
|
|
|
98
98
|
## Development
|
|
99
99
|
|
|
@@ -33,12 +33,33 @@ execution guard, use the existing bounded command without that flag:
|
|
|
33
33
|
projmux agent message send uid:<original-source-agent> --reply-to <original-request-ref> -- '<corrected reply>'
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Operator input from the web client has no Agent route to reverse, so a reply
|
|
37
|
+
to it is refused with `explicit-reply-operator-origin` and stores nothing.
|
|
38
|
+
|
|
39
|
+
Within the same provider session, a reply also commits after the Claude lease
|
|
40
|
+
helper was replaced, for example by compact. The new helper did not push the
|
|
41
|
+
original, so it reads the original from the durable store and judges it by the
|
|
42
|
+
same checks. It reads the store only while its own route is the Registry's
|
|
43
|
+
current authority for the Agent; a replaced or unregistered helper reads and
|
|
44
|
+
writes nothing. `broker-reply-original-not-found` means the original is in
|
|
45
|
+
neither the helper nor the store. `invalid-explicit-reply-correlation` means the
|
|
46
|
+
reply's route or conversation does not match the original, or the helper is not
|
|
47
|
+
the current one.
|
|
48
|
+
|
|
36
49
|
A same-ref call returns the original immutable receipt and never pushes again.
|
|
37
50
|
Changing its payload is refused with the earlier ref and cause. A fresh ref
|
|
38
51
|
allows one new attempt only when every previous attempt is known-zero. Failed
|
|
39
52
|
records and the original request remain available; recovery never deletes or
|
|
40
|
-
resets them.
|
|
41
|
-
|
|
53
|
+
resets them.
|
|
54
|
+
|
|
55
|
+
Store capacity treats the two kinds of attempt differently. A first attempt,
|
|
56
|
+
one whose original has no stored attempt yet, is a new acceptance rather than a
|
|
57
|
+
recovery, so a full store reclaims room for it under the same rule a new
|
|
58
|
+
message uses, while pinning the original and every attempt already stored
|
|
59
|
+
against it. A recovery attempt, which follows an earlier known-zero attempt, is
|
|
60
|
+
still refused with a capacity error rather than discarding the history it is
|
|
61
|
+
recovering from. Either way a reclaimed record moves to the history log below
|
|
62
|
+
instead of being deleted, and a store with nothing reclaimable refuses both.
|
|
42
63
|
|
|
43
64
|
Delivered replies, partial writes, unknown outcomes, pending attempts, expired
|
|
44
65
|
deadlines, and stale routes must not be resent. Inspect their status and the
|
|
@@ -47,3 +68,62 @@ the stored state and specific zero-write reason must agree. These rules also
|
|
|
47
68
|
apply after store reload and to concurrent callers. Automatic resend is
|
|
48
69
|
disabled, and replies whose delivery target is Codex do not gain a new retry
|
|
49
70
|
policy here.
|
|
71
|
+
|
|
72
|
+
## Reclaimed records move to a history log
|
|
73
|
+
|
|
74
|
+
The store is a bounded hot inbox. When it accepts a new message, or a first
|
|
75
|
+
explicit reply attempt, it first reclaims records that went terminal more than
|
|
76
|
+
24 hours ago, and then, if it is still at its record limit, the oldest
|
|
77
|
+
unprotected terminal record. Before either step it expires any accepted or
|
|
78
|
+
held record whose deadline has passed, exactly as a status read would, so such
|
|
79
|
+
a record becomes terminal at that moment and follows the same reclaim rules;
|
|
80
|
+
its 24 hours count from that expiry, not from its deadline. Reclaiming is not
|
|
81
|
+
deleting: every reclaimed record is appended to
|
|
82
|
+
`<state>/agent-messages/history.jsonl`, one JSON object per line, under the same
|
|
83
|
+
file lock and with the same private directory and file permissions as the store.
|
|
84
|
+
|
|
85
|
+
The append is fsynced before the store renames its own new file into place, so a
|
|
86
|
+
crash in that window can leave a record in both the store and the log, but never
|
|
87
|
+
in neither.
|
|
88
|
+
|
|
89
|
+
Each line carries the envelope's own key names:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{"schemaVersion":1,"evictedAt":"…","reason":"retention","adapter":"claude-coordination",
|
|
93
|
+
"messageRef":"…","conversationRef":"…","replyTo":"…",
|
|
94
|
+
"state":"delivered","deliveryReason":"unspecified","handoffObserved":false,
|
|
95
|
+
"acceptedAt":"…","terminalAt":"…","payloadBytes":466,
|
|
96
|
+
"source":{"agentUID":"…","provider":"…"},
|
|
97
|
+
"target":{"agentUID":"…","provider":"…"}}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`reason` is `retention` for the 24-hour rule and `capacity` for the record
|
|
101
|
+
limit. `replyTo` is omitted when the record is not a reply. `outcomeUnknown`
|
|
102
|
+
appears, as `true`, only on a failed record whose outcome is unknown; it is
|
|
103
|
+
absent otherwise. `source` and `target` carry only `agentUID` and `provider`:
|
|
104
|
+
the Pane and activation generation fence a live delivery, and the incarnation
|
|
105
|
+
follows the provider conversation; all three mean nothing once the record has
|
|
106
|
+
left the store. The envelope's `deadline` is not written. A line for operator
|
|
107
|
+
input (see [Operator input](claude-coordination-endpoints.md#operator-input)) carries
|
|
108
|
+
`"origin":{"kind":"operator","client":"web"}` in place of `source`; an Agent
|
|
109
|
+
message's line has no `origin`. Every other key is always present.
|
|
110
|
+
|
|
111
|
+
Lines written by earlier builds may still be in the same file. They carry full
|
|
112
|
+
routes (`paneUID`, `activationGeneration`, `incarnation`), a `deadline`, and
|
|
113
|
+
`outcomeUnknown` even when it is `false`, and they read the same way: a reader
|
|
114
|
+
ignores keys it does not expect and reads an absent key as its zero value, so
|
|
115
|
+
both shapes give the same edges, states and times. Both are `schemaVersion` 1.
|
|
116
|
+
|
|
117
|
+
`schemaVersion` starts at 1 and follows the same rule as the
|
|
118
|
+
coordination frame's field of that name: an absent or zero value reads as 1, and
|
|
119
|
+
a reader that meets a higher version reads the fields it knows rather than
|
|
120
|
+
discarding the line or failing. It is independent of the durable envelope
|
|
121
|
+
version and of the store file version.
|
|
122
|
+
|
|
123
|
+
The log does not keep the message body. It records `payloadBytes`, the original
|
|
124
|
+
payload length, and has no `payload` key at all: the log is unbounded in time,
|
|
125
|
+
and its intended consumers need the message graph, not its text.
|
|
126
|
+
|
|
127
|
+
The active log rotates to `history.jsonl.1` once it would pass 8 MiB, replacing
|
|
128
|
+
any earlier `history.jsonl.1`. At most two generations exist, so the log's disk
|
|
129
|
+
use is bounded even though its history is not complete.
|
|
@@ -53,11 +53,12 @@ Everything after `--` reaches the configured provider executable. Placeholder
|
|
|
53
53
|
model, permission, and agent flags are private customization examples, not
|
|
54
54
|
project defaults. Omit the separator when there is no payload.
|
|
55
55
|
|
|
56
|
-
Settings > AI
|
|
57
|
-
Antigravity may launch. Canonical create routes respect that setting.
|
|
58
|
-
no
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
`Settings > Global > AI > Enabled providers` controls whether Claude, Codex,
|
|
57
|
+
and Antigravity may launch. Canonical create routes respect that setting.
|
|
58
|
+
There is no per-launch override for a disabled provider; enable it in Settings
|
|
59
|
+
or with `projmux config providers --enable <id>` (`--disable <id>` turns one
|
|
60
|
+
off), which goes through the same writer. For a plain shell split use
|
|
61
|
+
`projmux create pane`, not an Agent provider.
|
|
61
62
|
|
|
62
63
|
Interactive provider and resume selection belong to the app's picker surfaces.
|
|
63
64
|
CLI automation should choose an explicit provider or use `projmux agent resume
|
package/docs/architecture.md
CHANGED
|
@@ -131,7 +131,7 @@ procfs collector supplies PID+starttime identity, SID, CPU ticks, RSS, and host
|
|
|
131
131
|
capacity. Pure aggregation builds pane, unique-window, and project rows without
|
|
132
132
|
using labels, topics, titles, or cwd-derived names as ownership keys.
|
|
133
133
|
|
|
134
|
-
Resource snapshots are
|
|
134
|
+
Resource snapshots are in-memory only and are never saved or restored. See
|
|
135
135
|
[resource-attribution.md](resource-attribution.md) for metric, partial-state,
|
|
136
136
|
host-remainder, privacy, and measurement contracts.
|
|
137
137
|
|
|
@@ -144,7 +144,7 @@ CLI information architecture v2 resource routes.
|
|
|
144
144
|
Packages:
|
|
145
145
|
|
|
146
146
|
- `internal/core/metadata` is pure: the resource model, validation, name
|
|
147
|
-
allocation, schema migration,
|
|
147
|
+
allocation, schema migration, and the operation
|
|
148
148
|
transaction. It performs no I/O; the clock, uid source, and root-directory
|
|
149
149
|
probe are injected through `Mutator`.
|
|
150
150
|
- `internal/core/resourcegraph` is pure: the resolved resource graph that joins
|
|
@@ -171,7 +171,7 @@ Packages:
|
|
|
171
171
|
fills a `resourcegraph.Inventory` from one exact server.
|
|
172
172
|
- `internal/integrations/tmuxopts` is a dependency-free leaf holding the
|
|
173
173
|
canonical spelling of every projmux-owned tmux option name, so the generated
|
|
174
|
-
tmux config
|
|
174
|
+
tmux config and the resource mirror cannot drift.
|
|
175
175
|
|
|
176
176
|
Resources and ownership:
|
|
177
177
|
|
|
@@ -216,6 +216,50 @@ Resources and ownership:
|
|
|
216
216
|
live only in runtime inventory, outside the Project hierarchy. A
|
|
217
217
|
ControlSession is not a counter-example: it is a root resource that *names* a
|
|
218
218
|
session, and the session still carries no identity of its own.
|
|
219
|
+
- `Project.status.session` is the **last recorded** projection, not a live
|
|
220
|
+
read. It holds `name`, `live`, and `socketPath`: the exact absolute socket
|
|
221
|
+
path of the tmux server the session was last created or observed live on,
|
|
222
|
+
as the writing route verified it against the server's own `#{socket_path}`
|
|
223
|
+
(never derived from a socket name; empty when that route had no verified
|
|
224
|
+
path). Registration records the name with `live=false` and no path. Create,
|
|
225
|
+
materialize, Project startup, and legacy import write `live=true` with the
|
|
226
|
+
path of their route. A successful managed Project stop writes `live=false`
|
|
227
|
+
only after the exact Session is observed absent, keeping the name and
|
|
228
|
+
`socketPath`; deleting a Project's last valid primary Window lowers `live`
|
|
229
|
+
the same way. The full reconciler pass (`refreshSessionProjections`, run by
|
|
230
|
+
the create routes and by controller convergence) records `live=true` with
|
|
231
|
+
this pass's path for every Project whose session is on the server it
|
|
232
|
+
observed. It lowers by absence only a projection recorded on that server or
|
|
233
|
+
on no server, keeping its name and path; a projection recorded on another
|
|
234
|
+
server is left as it is, and a pass without a verified path lowers none that
|
|
235
|
+
record a path. The rule is `SessionAbsenceAttributableTo`.
|
|
236
|
+
`reconcile resources` scopes that pass to Projects with an observed
|
|
237
|
+
live Session, and outside that scope it lowers a `live=true` projection
|
|
238
|
+
whose `socketPath` is exactly the reconciled server's path and whose session
|
|
239
|
+
is absent there, keeping its name and `socketPath`; Projects with another or
|
|
240
|
+
no `socketPath` are untouched, because absence from one server is not
|
|
241
|
+
evidence about a session recorded on another. The one judgement is
|
|
242
|
+
`Mutator.LowerProjectSessionsEndedOnServer`.
|
|
243
|
+
The `window-unlinked` hook, which a raw `kill-session` fires, applies the
|
|
244
|
+
same judgement on its fast path: once its lifecycle stage has observed the
|
|
245
|
+
host, it lowers a live projection recorded on the hook's exact verified
|
|
246
|
+
server whose session is gone there, re-reading that server's sessions under
|
|
247
|
+
the Registry lock. It does not run the full pass, and when no live
|
|
248
|
+
projection records that server it costs the hook no extra tmux call.
|
|
249
|
+
- When the exact server `reconcile resources` targets is not running (its
|
|
250
|
+
runtime authority read fails with a missing-server signature, and only
|
|
251
|
+
then), the same judgement runs with an empty present set against the exact
|
|
252
|
+
target path: the `--socket-path` or inherited `$TMUX` path itself, or for
|
|
253
|
+
`--socket <name>` the path tmux would use for that label (the first existing
|
|
254
|
+
`tmux-<uid>` directory under `$TMUX_TMPDIR`, then `/tmp`, resolved through
|
|
255
|
+
symlinks). Every projection recorded `live=true` on exactly that path is
|
|
256
|
+
lowered in one Registry commit, keeping its name and `socketPath`, and the
|
|
257
|
+
receipt states the absent server and the lowered count; nothing else is
|
|
258
|
+
planned, written, or reobserved, and `--dry-run` previews the same items.
|
|
259
|
+
When nothing is left to lower, or the path cannot be resolved, the command
|
|
260
|
+
fails at the runtime authority stage exactly as before and writes nothing.
|
|
261
|
+
Any other authority failure (a refused or unreadable socket, a timeout, a
|
|
262
|
+
server that is not app-owned) never lowers anything.
|
|
219
263
|
- `Window` and `Pane` carry **no stored liveness field**, deliberately. Their
|
|
220
264
|
`status` block holds observed conditions only; live/offline is derived from a
|
|
221
265
|
live tmux observation at read time. See *Runtime observation and resource
|
|
@@ -321,7 +365,7 @@ support reports expose those counts but redact item reasons and identifiers.
|
|
|
321
365
|
Identity and naming:
|
|
322
366
|
|
|
323
367
|
- `metadata.uid` is opaque, immutable, and independent of tmux lifecycle. It
|
|
324
|
-
survives
|
|
368
|
+
survives runtime stop/Continue, runtime creation, and root rebind.
|
|
325
369
|
- `metadata.name` is the stable unique-within-scope query key. Project names
|
|
326
370
|
and ControlSession names are unique within their own root kind across the
|
|
327
371
|
registry. Window, Pane, and Agent names are unique by
|
|
@@ -335,6 +379,21 @@ Identity and naming:
|
|
|
335
379
|
or numeric suffix allocator. An explicit `--name` keeps its original spelling
|
|
336
380
|
after validation; explicit create and rename collisions fail with exit code 2
|
|
337
381
|
and zero Registry, tmux, or provider writes.
|
|
382
|
+
- A newly registered Project is the one exception. `create project` without
|
|
383
|
+
`--name`, and every implicit registration (first open of a directory,
|
|
384
|
+
`projmux shell`, Open fresh), name the Project after its root directory: the
|
|
385
|
+
root basename run through the same sanitizer as any name seed (`my repo`
|
|
386
|
+
becomes `my-repo`). If that basename sanitizes to nothing (the filesystem
|
|
387
|
+
root) or another Project already holds it, the Project falls back to the
|
|
388
|
+
exact-UID rule above -- never a numbered variant, never a `project`
|
|
389
|
+
placeholder. Legacy/orphan import and every Window, Pane, Agent, and
|
|
390
|
+
ControlSession keep exact-UID automatic names, and no stored name is ever
|
|
391
|
+
rewritten.
|
|
392
|
+
- Open fresh replaces the Project and carries its name over only when that
|
|
393
|
+
name is not shaped like a minted Project UID (`proj-` plus a full canonical
|
|
394
|
+
UID payload). An operator-chosen name such as `proj-front` survives; a
|
|
395
|
+
UID-shaped name -- the old exact-UID automatic name, or one copied from a
|
|
396
|
+
predecessor -- is dropped so the replacement is named after its root.
|
|
338
397
|
- `create agent` supplies an **explicit** name for the Pane its Agent owns:
|
|
339
398
|
`<agent-name>-pane`, derived from the Agent's own name. That used to be a
|
|
340
399
|
documented follow-up `rename pane` a launcher had to remember, so a caller
|
|
@@ -359,6 +418,27 @@ Identity and naming:
|
|
|
359
418
|
context may duplicate and is never a selector, reservation, ownerRef, or
|
|
360
419
|
durable identity input. `metadata.labels` remains key/value classification;
|
|
361
420
|
`metadata.annotations` remains non-identifying metadata such as an AI topic.
|
|
421
|
+
- Creator provenance: when an explicit `create agent` (every spelling,
|
|
422
|
+
`--create-window`, and each Agent of a fan-out) or `create window --provider`
|
|
423
|
+
runs inside an Agent's managed Pane, the new Agent and its managed Pane carry
|
|
424
|
+
`projmux.io/creator-agent` (the creator Agent's bare UID),
|
|
425
|
+
`projmux.io/creator-pane` (that Agent's managed Pane's bare UID), and
|
|
426
|
+
`projmux.io/creator-basis: pane-chain`, written in the same transaction that
|
|
427
|
+
commits the Agent. They are recorded only when the unmasked ambient
|
|
428
|
+
`%N` (`__PROJMUX_RUNTIME_ANCHOR_PANE`, then `TMUX_PANE`) is exactly one live
|
|
429
|
+
Registry Pane, that Pane round-trips with its owning Agent's
|
|
430
|
+
`status.paneRef`, one `display-message` confirms it on the create's own
|
|
431
|
+
app-owned socket and server pid, and the create process descends from its
|
|
432
|
+
`#{pane_pid}`. Any failed check writes none of the keys and changes nothing
|
|
433
|
+
else about the create. Stderr gets one `creator not recorded: <reason>` line
|
|
434
|
+
only when the ambient Pane is a live Agent Pane but a later check fails; an
|
|
435
|
+
ambient Pane that is malformed, unregistered, or a Window-owned shell is
|
|
436
|
+
silent. These keys are provenance, not
|
|
437
|
+
authentication, like a message `--source`. An absent key does not mean a
|
|
438
|
+
human created the Agent: UI intent creates (picker, pane menu, `ai split`,
|
|
439
|
+
launch choice) and creates a Codex Agent issues (its
|
|
440
|
+
commands run under the app-server, not below the Pane's process) leave them
|
|
441
|
+
empty, and nothing backfills older Agents.
|
|
362
442
|
|
|
363
443
|
Root lifecycle:
|
|
364
444
|
|
|
@@ -519,7 +599,8 @@ Exit reconciliation and lifecycle projection:
|
|
|
519
599
|
- Abnormal, killed, unknown, whole-host absence, missing/empty server inventory,
|
|
520
600
|
permission failure, foreign Window observation, stale generation, and an
|
|
521
601
|
Agent that now binds a resumed Pane all produce delete-plan zero. They keep the
|
|
522
|
-
retained lifecycle projection and canonical
|
|
602
|
+
retained lifecycle projection and the canonical exact-uid Registry-only
|
|
603
|
+
delete recovery of a paneless Offline or Failed Agent.
|
|
523
604
|
- The closed Agent transition table stays the authority. An Agent that may not
|
|
524
605
|
reach the implied phase keeps its phase, its `paneRef`, and its managed Pane;
|
|
525
606
|
only the evidence is recorded. A refused transition is not a reason to discard
|
|
@@ -736,9 +817,8 @@ Registry file and schema:
|
|
|
736
817
|
directory-existence and uid adapters.
|
|
737
818
|
- The canonical anchor is a schema-v2 write invariant. Until the separately
|
|
738
819
|
planned Project-start projection lands, the legacy `New` startup path's
|
|
739
|
-
prune-to-zero transaction fails validation and commits zero Registry bytes
|
|
740
|
-
|
|
741
|
-
preserves the latest snapshot byte-for-byte and performs no tmux mutation.
|
|
820
|
+
prune-to-zero transaction fails validation and commits zero Registry bytes
|
|
821
|
+
and performs no tmux mutation.
|
|
742
822
|
This fail-closed ordering is not Phase 3 authority to redesign Project start.
|
|
743
823
|
- Downgrade writes remain unsupported. Unversioned, malformed, and future
|
|
744
824
|
envelopes still fail closed before backup, staging, or replace.
|
|
@@ -967,21 +1047,11 @@ Resolved resource graph (`internal/core/resourcegraph`):
|
|
|
967
1047
|
process, and no tmux, so the same inputs always produce byte-identical output
|
|
968
1048
|
and a read can never materialize state.
|
|
969
1049
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
`uid`, `name`, `labels`, `owner_kind`, and `owner_uid` in the snapshot's own
|
|
976
|
-
snake_case spelling. No schema bump was needed, and a snapshot written
|
|
977
|
-
without resource metadata still serializes byte-identically to the older form.
|
|
978
|
-
- Snapshots written before resource metadata existed still project
|
|
979
|
-
deterministically into an explicitly selected, closed Registry Project:
|
|
980
|
-
existing Windows and Panes are reused positionally in Registry order and any
|
|
981
|
-
additional descendants receive new stable identities. Restore validates a
|
|
982
|
-
pure Project-scoped plan, atomically commits that desired subtree, and only
|
|
983
|
-
then invokes the ordinary Project materializer. It never directly replays
|
|
984
|
-
snapshot topology into tmux and never replaces the global Registry.
|
|
1050
|
+
Saved Project state:
|
|
1051
|
+
|
|
1052
|
+
- The Registry is the only saved Project state. projmux keeps no separate
|
|
1053
|
+
Project snapshot store, and a closed Project starts only from its Registry
|
|
1054
|
+
desired state through the ordinary Project materializer.
|
|
985
1055
|
|
|
986
1056
|
tmux transport mirror:
|
|
987
1057
|
|
|
@@ -1000,7 +1070,7 @@ tmux transport mirror:
|
|
|
1000
1070
|
every other mirror goes through, on the same plain `tmux` transport the session
|
|
1001
1071
|
was created on. The write is gated strictly on "this open registered the
|
|
1002
1072
|
Project": every already-registered Project converges through the Registry
|
|
1003
|
-
topology engine,
|
|
1073
|
+
topology engine,
|
|
1004
1074
|
and opening `$HOME` mints no managed identity at all, so neither writes a mirror
|
|
1005
1075
|
option through this first-open gate. That gate is also what makes repeating an
|
|
1006
1076
|
open write nothing. Repairing a session that is already live without its
|
|
@@ -1008,10 +1078,14 @@ tmux transport mirror:
|
|
|
1008
1078
|
recovery route.
|
|
1009
1079
|
- `rename pane` changes `Pane.metadata.name` and its `@projmux_pane_label`
|
|
1010
1080
|
mirror only. It never writes the raw tmux `pane_title`.
|
|
1011
|
-
- `rename window` is the explicit stable-identity path: it changes
|
|
1012
|
-
`Window.metadata.name`, its root-scoped same-kind name reservation,
|
|
1013
|
-
|
|
1014
|
-
`window_name
|
|
1081
|
+
- `rename window` is the explicit stable-identity path: it changes
|
|
1082
|
+
`Window.metadata.name`, its root-scoped same-kind name reservation, the exact
|
|
1083
|
+
live `@projmux_window_name` transport mirror and, when the invocation has a
|
|
1084
|
+
proven runtime route, the tmux tab `window_name` through the canonical
|
|
1085
|
+
`rename-window -t @N -- <name>` -- the same argv shape the rename key and the
|
|
1086
|
+
Window menu Rename item go through. With no route it stays Registry-only and
|
|
1087
|
+
says on stderr that the tab did not converge. It never changes
|
|
1088
|
+
`metadata.displayName`.
|
|
1015
1089
|
- `rename project` likewise writes only `Project.metadata.name` and the exact
|
|
1016
1090
|
live session's `@projmux_project_name`; it never renames the tmux session.
|
|
1017
1091
|
`rebind project` preserves the Project uid and session name while updating
|
|
@@ -1064,8 +1138,8 @@ Runtime observation and resource status:
|
|
|
1064
1138
|
precedence contract is unchanged, and it now applies to a Window or Pane whose
|
|
1065
1139
|
owning Project lost its root even while tmux is still running them.
|
|
1066
1140
|
- A **Project** is the one kind whose runtime object is a tmux *session*, which
|
|
1067
|
-
has no `@projmux` uid of its own, so Project status still reads
|
|
1068
|
-
`status.session`
|
|
1141
|
+
has no `@projmux` uid of its own, so Project status still reads the stored
|
|
1142
|
+
`status.session` projection (see *Resource metadata model* for its writers).
|
|
1069
1143
|
- An **Agent** owns no tmux object of its own — there is no `@projmux_agent_uid`
|
|
1070
1144
|
and there must not be one, because an Agent outlives the managed Pane it is
|
|
1071
1145
|
bound to. Its runtime object is **that managed Pane**, named by
|
|
@@ -1280,7 +1354,7 @@ Lifecycle trigger convergence:
|
|
|
1280
1354
|
transaction deletes exactly that Window, its Panes, its owned Agents, and
|
|
1281
1355
|
their reservations. A non-last Project Window reanchors to its existing
|
|
1282
1356
|
sibling. The last Project Window leaves the exact Project uid, root,
|
|
1283
|
-
reservation,
|
|
1357
|
+
reservation, and pins in the valid zero-Window state. A
|
|
1284
1358
|
ControlSession likewise loses only the Window and keeps its root uid.
|
|
1285
1359
|
Abnormal/killed/unknown exits, stale generations, unpaired or foreign handles,
|
|
1286
1360
|
unavailable/empty observations, and missing-server or permission failures
|
|
@@ -1294,11 +1368,9 @@ Lifecycle trigger convergence:
|
|
|
1294
1368
|
managed runtime and preserves every desired UID. Continue on retained-window
|
|
1295
1369
|
writes only runtime and materializes the same descendant UIDs; Continue on
|
|
1296
1370
|
zero-window atomically allocates one canonical Window/shell below the same
|
|
1297
|
-
Project UID before runtime materialization. The deleted+Continue cell
|
|
1298
|
-
|
|
1299
|
-
and
|
|
1300
|
-
the same cell is an unavailable zero-write refusal and never falls back to
|
|
1301
|
-
Fresh. Fresh atomically replaces either
|
|
1371
|
+
Project UID before runtime materialization. The deleted+Continue cell is
|
|
1372
|
+
always an unavailable zero-write `project-is-not-registered` refusal that
|
|
1373
|
+
points to Fresh and never falls back to it. Fresh atomically replaces either
|
|
1302
1374
|
registered state with a new Project/Window/shell UID chain and exactly one
|
|
1303
1375
|
same-root claimant. Within this runtime/startup lifecycle table, canonical
|
|
1304
1376
|
`delete project --yes` alone unregisters the Project graph; the separately
|
|
@@ -1366,6 +1438,25 @@ Projmux split UI:
|
|
|
1366
1438
|
The provider and shell branches render the exact argv an operator would type,
|
|
1367
1439
|
so a UI action and a typed command cannot disagree about what `--placement
|
|
1368
1440
|
down` means.
|
|
1441
|
+
- A split picker running in a popup does not call that route itself. tmux
|
|
1442
|
+
closes a popup only when the process inside it exits, so the picker hands its
|
|
1443
|
+
intent to a detached `run-shell -b` continuation on the same server
|
|
1444
|
+
(`internal agent-pane launch-selection`, carrying the origin Pane, client,
|
|
1445
|
+
and context directory as env) and exits. The continuation
|
|
1446
|
+
calls the same create funnel; a success writes nothing and anything else is
|
|
1447
|
+
one bounded line on the pressing client. Every selection travels: it is a
|
|
1448
|
+
plain value with no live handle left in the picker process.
|
|
1449
|
+
- A generated Window create asks before it commits. It opens the split picker in
|
|
1450
|
+
answer mode (`internal tmux popup-toggle --answer <file>`) on the Pane the key
|
|
1451
|
+
was pressed in; the picker writes the selection -- the same argv the
|
|
1452
|
+
continuation carries, read back by the same parser -- to a 0600 file its
|
|
1453
|
+
producer made, and creates nothing. The producer then commits the Window and
|
|
1454
|
+
the answer in one Registry transaction -- for an Agent, the same shape
|
|
1455
|
+
`create window --provider` commits: the Agent Pane splits off the new shell
|
|
1456
|
+
and the shell is retired -- and moves the pressing client onto it last. An
|
|
1457
|
+
Agent that cannot be opened rolls the whole Window back and leaves one line on
|
|
1458
|
+
the pressing client. A cancelled picker leaves the file empty and nothing is
|
|
1459
|
+
created.
|
|
1369
1460
|
- Only the materializer runs `split-window`. Before this convergence the saved
|
|
1370
1461
|
default and both pickers descended into a legacy split that called tmux
|
|
1371
1462
|
directly, so a pane opened from the UI was a runtime object the Registry had
|
|
@@ -1677,15 +1768,14 @@ Explicit Registry topology materialization:
|
|
|
1677
1768
|
deletion removes that desire. Exact uid/name/owner mirrors are retained. Stored
|
|
1678
1769
|
Pane CWD drives only that Pane's detached runtime cwd, while Project root
|
|
1679
1770
|
remains the session path anchor and `PROJMUX_CWD` hook value.
|
|
1680
|
-
`Pane.spec.command`,
|
|
1771
|
+
`Pane.spec.command`, notifications, and ephemeral sessions
|
|
1681
1772
|
are never execution inputs.
|
|
1682
1773
|
- An Agent whose managed Pane is not live is replayed into a new managed Pane on
|
|
1683
1774
|
its Window's proven anchor, through the same allocation, activation ledger,
|
|
1684
1775
|
ownership-checked adoption, and rollback the shell half uses. The **only**
|
|
1685
1776
|
replay identifier is Registry `status.sessionRef`: no provider conversation
|
|
1686
1777
|
store is read, `ClaudeSessionRef.TranscriptPath` in particular is never
|
|
1687
|
-
consulted
|
|
1688
|
-
this path. The launch argv comes from the two seams `create agent` already
|
|
1778
|
+
consulted. The launch argv comes from the two seams `create agent` already
|
|
1689
1779
|
owns -- `PlanAgentResume` for a ref that names a conversation, `PlanAgentLaunch`
|
|
1690
1780
|
with no payload otherwise -- so the topology engine holds no launch builder of
|
|
1691
1781
|
its own and the Settings enabled-agents gate still applies. An Agent that
|
|
@@ -1699,14 +1789,7 @@ Explicit Registry topology materialization:
|
|
|
1699
1789
|
the Window from it, and then replays the anchor Agent while preserving the
|
|
1700
1790
|
Agent Pane uid. The default shell is bootstrap, not a replacement anchor. A
|
|
1701
1791
|
successful repeat is a Registry-write-free and topology-write-free no-op.
|
|
1702
|
-
-
|
|
1703
|
-
restore. Metadata-bearing v1 snapshots preserve surviving final-v2
|
|
1704
|
-
anchor/default refs; metadata-free snapshots choose the first Window-local
|
|
1705
|
-
Pane as anchor and the first direct shell as optional default. Agent-only
|
|
1706
|
-
desired Windows remain Agent-anchored and acquire a shell only through the
|
|
1707
|
-
ordinary materializer. Source snapshot bytes and unrelated roots are never
|
|
1708
|
-
rewritten, and a second projection is byte-stable.
|
|
1709
|
-
- `Recreate Project` replaces the exact same-root Project graph, after an
|
|
1792
|
+
- `Clear layout and open` replaces the exact same-root Project graph, after an
|
|
1710
1793
|
explicit confirmation, in one Registry
|
|
1711
1794
|
commit. It always allocates a new Project UID plus one new canonical Window
|
|
1712
1795
|
and direct shell UID, whether the old Project retained Windows or had zero.
|
|
@@ -1780,7 +1863,10 @@ Plan-only runtime mutation boundary:
|
|
|
1780
1863
|
arbitrary Pane as invocation evidence. Generated popup/menu producers pass an exact Pane anchor which is
|
|
1781
1864
|
reobserved on that same socket rather than trusting a targetless current
|
|
1782
1865
|
Pane. Before a stage writes, the same `-S` runner refuses path, generation,
|
|
1783
|
-
class, or containment drift
|
|
1866
|
+
class, or containment drift, and names which of them happened: tmux answers a
|
|
1867
|
+
`display-message -t %N` for a Pane that is gone with exit 0 and blank
|
|
1868
|
+
`$N`/`@N`/`%N` columns, so an absent anchor Pane is reported as absent rather
|
|
1869
|
+
than as drift on a socket and a server generation that both still match. Only a create-session
|
|
1784
1870
|
stage may accept the typed no-server observation, because its explicit route
|
|
1785
1871
|
and absent-session ownership preflight are the facts required to create the
|
|
1786
1872
|
first server.
|
|
@@ -1906,7 +1992,13 @@ Resource-first create:
|
|
|
1906
1992
|
touched.
|
|
1907
1993
|
- **Everything is detached.** No create path issues `switch-client`,
|
|
1908
1994
|
`select-window`, `select-pane`, or `attach-session`. `focus pane` and
|
|
1909
|
-
`-o pane-id` are how a caller ends up in the new pane.
|
|
1995
|
+
`-o pane-id` are how a caller ends up in the new pane. One exception: the
|
|
1996
|
+
human intent route `internal tmux window-create` (`window.create` key, Window
|
|
1997
|
+
menu New At End) asks for the saved launch default before its create
|
|
1998
|
+
commits, fills the committed shell Pane with the answer -- an Agent through
|
|
1999
|
+
the same canonical create funnel a split uses plus a canonical delete of the
|
|
2000
|
+
shell -- and then moves exactly the pressing client to the new Window; public
|
|
2001
|
+
`create` stays detached and never reads that saved default.
|
|
1910
2002
|
- **Focus is navigation-only.** `focus project|window|pane` reads live tmux
|
|
1911
2003
|
inventory and may move an existing client, but has no Registry store and
|
|
1912
2004
|
issues no session/Window/Pane creation, identity-marker, rename, respawn, or
|
|
@@ -1948,8 +2040,8 @@ Selector and the implicit active target:
|
|
|
1948
2040
|
hit. The describe family remains the intentional Project-only difference;
|
|
1949
2041
|
Phase 14 extends only the generic rename family to ControlSession.
|
|
1950
2042
|
`get projects`, `describe|rename project`, `delete`, `rebind`, and `agent
|
|
1951
|
-
resume` are outside that reference scope, and notifications
|
|
1952
|
-
|
|
2043
|
+
resume` are outside that reference scope, and notifications belong to a
|
|
2044
|
+
separate store. Delete's exact live preflight nevertheless follows
|
|
1953
2045
|
either root kind through the selected descendant's owner chain.
|
|
1954
2046
|
- `--all-projects` is the explicit registry-wide escape for those three reads.
|
|
1955
2047
|
It is deliberately different from destructive `delete --all`, whose existing
|
|
@@ -2004,9 +2096,11 @@ Selector and the implicit active target:
|
|
|
2004
2096
|
|
|
2005
2097
|
Projmux keeps visible naming separate from source metadata:
|
|
2006
2098
|
|
|
2007
|
-
- **User pane label** is
|
|
2008
|
-
|
|
2009
|
-
|
|
2099
|
+
- **User pane label** is pane-scoped metadata stored in `@projmux_pane_label`,
|
|
2100
|
+
the live mirror of the Pane's Registry `metadata.name`. The Rename Pane action
|
|
2101
|
+
renames the Registry Pane through the same owner as `rename pane`, which
|
|
2102
|
+
writes this mirror; an empty response changes nothing, and the action never
|
|
2103
|
+
writes the AI topic or raw pane title.
|
|
2010
2104
|
- **Pane border label** is the primary visible pane name. In the app tmux
|
|
2011
2105
|
config and native previews it resolves to user pane label first, agent AI
|
|
2012
2106
|
topic second, known interactive shell command (`zsh`, `bash`, `fish`, `sh`,
|
|
@@ -2024,14 +2118,6 @@ Projmux keeps visible naming separate from source metadata:
|
|
|
2024
2118
|
user pane labels.
|
|
2025
2119
|
- **Git branch** belongs in the statusbar git segment. Branch-based terminal
|
|
2026
2120
|
title overwrites are not promoted to the primary Projmux pane or window name.
|
|
2027
|
-
- **Session snapshots** store source metadata separately: `window_name`, raw
|
|
2028
|
-
`pane_title`, user `label`, `@projmux_ai_topic`, manual topic ownership, and
|
|
2029
|
-
agent resume metadata. Old snapshots decode with an absent label and absent
|
|
2030
|
-
ownership; title/topic equality never infers either. Replay writes each
|
|
2031
|
-
semantic field to the exact pane id returned by tmux creation and restores
|
|
2032
|
-
raw title from `Pane.Title` after launch/startup replay. Snapshots do not
|
|
2033
|
-
store a resolved `display_label`; visible labels are recomputed by display
|
|
2034
|
-
policy.
|
|
2035
2121
|
|
|
2036
2122
|
## Notify queue
|
|
2037
2123
|
|
|
@@ -2174,7 +2260,7 @@ The maintained product table in `internal/app/runtime_mutation_surface.go` maps
|
|
|
2174
2260
|
generated catalog/menu producers, native provider/resume picker selections,
|
|
2175
2261
|
sidebar/session-picker stops, and app lifecycle entrypoints in both directions
|
|
2176
2262
|
to their handler and plan verb. It also records exact semantic exemptions for
|
|
2177
|
-
focus, labels, operator-requested layout, mouse forwarding,
|
|
2263
|
+
focus, labels, operator-requested layout, mouse forwarding,
|
|
2178
2264
|
ephemeral maintenance, app quit, and human runtime maintenance. Managed argv
|
|
2179
2265
|
verbs are selected only by the typed executor seam; generated Window
|
|
2180
2266
|
create/rename, Pane-menu create/delete, and automatic post-split layout writes
|