projmux 0.16.0 → 0.16.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/agent-message-replies.md +77 -14
- package/docs/architecture.md +355 -31
- package/docs/claude-coordination-endpoints.md +35 -13
- package/docs/cli-guide.md +688 -122
- package/docs/cli.md +1114 -222
- package/docs/configuration.md +597 -48
- package/docs/globalization.md +7 -4
- package/docs/hooks.md +466 -50
- package/docs/keybindings.md +9 -5
- package/docs/npm-distribution.md +4 -0
- package/docs/operational-diagnostics.md +270 -7
- package/docs/release.md +4 -0
- package/docs/replacement-contract.md +6 -0
- package/docs/repo-layout.md +3 -0
- package/docs/resource-attribution.md +4 -0
- package/docs/session-restore.md +11 -7
- package/docs/statusbar.md +23 -19
- package/docs/testing.md +68 -18
- package/docs/theme-palette.md +6 -6
- package/docs/tmux-surface-inventory.md +13 -0
- package/docs/upgrading.md +25 -23
- package/docs/usage-tracking.md +16 -1
- package/package.json +5 -5
package/docs/keybindings.md
CHANGED
|
@@ -233,8 +233,12 @@ per-launch model or effort row.
|
|
|
233
233
|
|
|
234
234
|
`window.create` (v0 id `new-window`) and the Window menu's New At End decide the
|
|
235
235
|
new Window's first Pane before the Window exists, following the saved launch
|
|
236
|
-
default
|
|
237
|
-
|
|
236
|
+
default. That default is the TUI file
|
|
237
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/projmux/tmux-ai-split-mode` (Settings > AI
|
|
238
|
+
Settings, the same setting the saved-default split key reads) when it holds a
|
|
239
|
+
valid mode, then the central `ai-new-window-mode`, then `selective`; see
|
|
240
|
+
[New AI Window Default](configuration.md#new-ai-window-default). The create
|
|
241
|
+
then runs in this order:
|
|
238
242
|
|
|
239
243
|
1. Ask. A picker mode opens its picker on the Pane the key was pressed in; a
|
|
240
244
|
provider mode and `shell` are already the answer and open nothing.
|
|
@@ -249,7 +253,7 @@ The client therefore never sees a shell Pane that is about to be replaced.
|
|
|
249
253
|
| --- | --- |
|
|
250
254
|
| `shell` | the shell Pane the create made; nothing else runs |
|
|
251
255
|
| `claude`, `codex`, `antigravity` | exactly that Agent Pane, with no picker |
|
|
252
|
-
| `selective` (also the
|
|
256
|
+
| `selective` (also the default when neither `tmux-ai-split-mode` nor `ai-new-window-mode` holds a valid mode) | whatever the `Alt-7` picker chose: that Agent Pane, or the shell Pane for the shell row; its `resume` row opens the resume session list in the same popup |
|
|
253
257
|
| `resume` | whatever the resume picker chose, on the same terms |
|
|
254
258
|
|
|
255
259
|
Cancelling the picker creates nothing: no Window, and the client stays where it
|
|
@@ -605,14 +609,14 @@ still required before ordinary mutation.
|
|
|
605
609
|
To roll v2 back to the immediately previous v1 file, restore its pre-v2 backup:
|
|
606
610
|
|
|
607
611
|
```sh
|
|
608
|
-
cp
|
|
612
|
+
cp "${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml.pre-v2-<digest>.bak" "${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml"
|
|
609
613
|
```
|
|
610
614
|
|
|
611
615
|
For a keymap that entered migration as v0, restore the established pre-v1
|
|
612
616
|
backup **before** installing a projmux that predates versioned keymaps:
|
|
613
617
|
|
|
614
618
|
```sh
|
|
615
|
-
cp
|
|
619
|
+
cp "${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml.pre-v1-<digest>.bak" "${XDG_CONFIG_HOME:-$HOME/.config}/projmux/keymap.toml"
|
|
616
620
|
```
|
|
617
621
|
|
|
618
622
|
A projmux that predates this schema reads `schema_version` as an unsupported
|
package/docs/npm-distribution.md
CHANGED
|
@@ -14,6 +14,10 @@ this package layout:
|
|
|
14
14
|
| `@projmux/darwin-x64` | `darwin/amd64` `bin/projmux` |
|
|
15
15
|
| `@projmux/darwin-arm64` | `darwin/arm64` `bin/projmux` |
|
|
16
16
|
|
|
17
|
+
Each platform package also carries `THIRD_PARTY_NOTICES`, the license notices
|
|
18
|
+
of the Go runtime and the Go modules its binary links. The root package has no
|
|
19
|
+
binary and no notices.
|
|
20
|
+
|
|
17
21
|
The shim sets `PROJMUX_INSTALLER=npm` before executing the real binary so
|
|
18
22
|
`projmux update status` and the Settings About screen can present
|
|
19
23
|
npm-specific guidance. npm is only an update/install source label here; the
|
|
@@ -16,8 +16,15 @@ no generic metadata map. Runtime lifecycle records add only closed
|
|
|
16
16
|
attach, switch, kill, and config apply; codes are stable failure/health
|
|
17
17
|
classifications and never carry routing identity or subprocess details.
|
|
18
18
|
|
|
19
|
-
Command and subcommand names come from static allowlists.
|
|
20
|
-
|
|
19
|
+
Command and subcommand names come from static allowlists. The allowlist covers
|
|
20
|
+
every route of the CLI route graph, `internal <namespace>` included, and its
|
|
21
|
+
direct child subcommand; a sweep test over the graph enforces this. Routes
|
|
22
|
+
covered only for this purpose gain error attribution: whether a success is
|
|
23
|
+
recorded still follows the state-changing rules below. An alias records the
|
|
24
|
+
canonical child name (`get project` records `get projects`). Unknown argv stays
|
|
25
|
+
unnamed, and argv values, paths, flags, and arguments are dropped. A log written
|
|
26
|
+
by a newer binary may carry names an older reader does not know; that reader
|
|
27
|
+
skips those lines. Messages have control/format
|
|
21
28
|
characters removed, whitespace normalized, the current home path abbreviated
|
|
22
29
|
to `~`, and length capped at 512 Unicode code points. Top-level outcomes never
|
|
23
30
|
copy `error.Error()` into the journal: their message is one of three stable,
|
|
@@ -36,6 +43,59 @@ operation instead of recording nested outcomes. Lifecycle ownership replaces
|
|
|
36
43
|
the generic top-level `command.outcome`; it never duplicates it. Start/outcome
|
|
37
44
|
append failures are ignored and do not change the command result.
|
|
38
45
|
|
|
46
|
+
The `lifecycle.outcome` of `operation=tmux.apply` (`config apply` and the
|
|
47
|
+
hidden `internal tmux apply`) also carries a step and Registry lock
|
|
48
|
+
breakdown. It is measurement only: the apply's steps, their order, its
|
|
49
|
+
stdout, stderr, exit status, rollback, and every lock boundary are exactly
|
|
50
|
+
as without it. The record is appended after the apply route returned, so
|
|
51
|
+
after every Registry lock it took was released, and a journal failure
|
|
52
|
+
changes nothing about the apply. Only closed names and integers are added;
|
|
53
|
+
every other event family, including `lifecycle.outcome` of any other
|
|
54
|
+
operation and `lifecycle.start`, rejects each of these fields.
|
|
55
|
+
|
|
56
|
+
- One `step_<name>_ms` field per apply step the apply entered, in order:
|
|
57
|
+
`step_keymap_migration_ms` (keymap migration),
|
|
58
|
+
`step_hook_file_migration_ms` (managed agent hook file migration),
|
|
59
|
+
`step_retired_file_reclaim_ms` (retired snapshot, Codex generation, and
|
|
60
|
+
sidebar startup file reclaim and the status bar default seed),
|
|
61
|
+
`step_route_bind_ms` (binding to the exact live app server),
|
|
62
|
+
`step_bell_hook_migration_ms` (managed tmux bell hook migration),
|
|
63
|
+
`step_config_write_ms` (writing the generated config),
|
|
64
|
+
`step_key_sequence_retire_ms` (retiring recorded key sequence state),
|
|
65
|
+
`step_source_file_ms` (the route guard and `source-file`),
|
|
66
|
+
`step_route_marker_ms` (the logical socket marker),
|
|
67
|
+
`step_exhausted_replay_ms` (replaying retry-exhausted clean exits), and
|
|
68
|
+
`step_converge_ms` (the controller convergence). A step the apply did not
|
|
69
|
+
enter -- `--no-reload`, no live server, or an earlier failure -- is absent.
|
|
70
|
+
Steps are measured on the same clock as `duration_ms`, start no earlier
|
|
71
|
+
than it, and do not overlap; each opens where the previous one closes, so
|
|
72
|
+
the few statements between two steps count toward the earlier one. Each is
|
|
73
|
+
rounded down to whole milliseconds, and `sum(step ms) <= duration_ms`
|
|
74
|
+
always holds on disk: a step set that would exceed it is dropped whole.
|
|
75
|
+
- `lock_acquisition_count`, `lock_wait_total_ms`, and `lock_held_total_ms`
|
|
76
|
+
total every Registry lock acquisition the apply made while it ran, whatever
|
|
77
|
+
site made it, with the wait and hold the Registry Store measured for
|
|
78
|
+
`registry.lock.acquisition`. They are present whenever the breakdown is,
|
|
79
|
+
and zero when the apply took no lock.
|
|
80
|
+
- `longest_lock_kind`, `longest_lock_step`, `longest_lock_wait_ms`, and
|
|
81
|
+
`longest_lock_held_ms` describe the one released acquisition that held the
|
|
82
|
+
lock longest. The kind is the Registry transaction it belonged to:
|
|
83
|
+
`preexisting-dead-agent`, `control-targets`, `mirror-recovery`,
|
|
84
|
+
`binding-converge`, `lifecycle-reconcile`, `session-lower`, or `other` for
|
|
85
|
+
an acquisition no site named. The step is the apply step it ran in, or
|
|
86
|
+
`other` outside every step. All four are absent when no acquisition held
|
|
87
|
+
the lock, and the longest wait and hold never exceed the totals.
|
|
88
|
+
- `longest_lock_observe_ms`, `longest_lock_plan_ms`, `longest_lock_commit_ms`,
|
|
89
|
+
and `longest_lock_store_write_ms` split that hold: live tmux reads and
|
|
90
|
+
their classification, plan and reconcile computation, Registry and tmux
|
|
91
|
+
writes inside the transaction, and from the transaction's callback
|
|
92
|
+
returning to the release being observed (normalize, validate, the durable
|
|
93
|
+
write, the unlock). A phase the transaction did not mark is absent, and the
|
|
94
|
+
grant and the Store's locked read before the first mark are left
|
|
95
|
+
unattributed. `sum(phase ms) <= longest_lock_held_ms` always holds on disk:
|
|
96
|
+
a breakdown that would exceed the hold is dropped whole and the rest of the
|
|
97
|
+
longest lock is kept.
|
|
98
|
+
|
|
39
99
|
Project lifecycle operator diagnostics also keep plans mutually exclusive:
|
|
40
100
|
`stop`, `close-window`, `delete-project`, and `fresh` are distinct operation
|
|
41
101
|
classes. Startup and unregister failures print the closed action, failing
|
|
@@ -66,10 +126,11 @@ and the UI new Window, including one whose answer is an Agent), `pane`
|
|
|
66
126
|
(`create pane`, the split UI's shell Pane, and the pane-menu split), `agent`
|
|
67
127
|
(`create agent` and the split UI's Agent Pane, including a resume-picker
|
|
68
128
|
pick, which creates a new Agent), or `resume` (`agent resume`, including the
|
|
69
|
-
resume `agent
|
|
129
|
+
resume `agent relaunch` runs). A successful transaction is `info`/`success`;
|
|
70
130
|
a failed one, including one that was rolled back, is `error`/`error` with
|
|
71
131
|
`kind=runtime`, so the support report's existing error-only projection
|
|
72
|
-
carries it. The record adds
|
|
132
|
+
carries it. The record adds two timings and, inside the lock hold, their
|
|
133
|
+
breakdown:
|
|
73
134
|
|
|
74
135
|
- `duration_ms` runs from entering the create transaction to its return. It
|
|
75
136
|
includes the runtime route bind, the wait for the Registry lock, the time
|
|
@@ -85,14 +146,212 @@ carries it. The record adds only two timings:
|
|
|
85
146
|
excludes the wait for the lock and the Registry read before the mutation
|
|
86
147
|
starts. It is absent when the transaction failed before it entered the
|
|
87
148
|
mutation, and `0 <= lock_held_ms <= duration_ms` always holds.
|
|
149
|
+
- Six phase fields split `lock_held_ms` into the stages of the mutation, in
|
|
150
|
+
order, each starting where the previous one ended:
|
|
151
|
+
`phase_guard_ms` (the ownership guards' preflight against tmux),
|
|
152
|
+
`phase_first_reconcile_ms` (the reconcile pass before the create's own
|
|
153
|
+
writes), `phase_operation_ms` (the create itself: tmux splits and mirrors,
|
|
154
|
+
the Registry edit, and for an Agent the supervised child spawn),
|
|
155
|
+
`phase_second_reconcile_ms` (the reconcile pass after those writes),
|
|
156
|
+
`phase_reprove_ms` (the re-proof of any route identity the transaction
|
|
157
|
+
reused), and `phase_store_write_ms` (from the mutation callback returning
|
|
158
|
+
to the Registry update returning: normalize, validate, the durable write,
|
|
159
|
+
the unlock, and anything the store does after the unlock before it
|
|
160
|
+
returns). A phase the transaction never reached, because an earlier stage
|
|
161
|
+
failed, is absent; the store-write phase is present whenever the mutation
|
|
162
|
+
was entered. The phases appear only with `lock_held_ms`, and
|
|
163
|
+
`sum(phase ms) <= lock_held_ms` always holds on disk: a breakdown that
|
|
164
|
+
would exceed the hold (a clock that went backwards) is dropped whole.
|
|
165
|
+
- `spawn_to_release_ms` is written for `agent` and `resume` only. It runs
|
|
166
|
+
from the first supervised child spawn -- the split that starts the managed
|
|
167
|
+
Pane's supervisor returning that Pane -- to the Registry update returning:
|
|
168
|
+
how much of the child's own Registry lock budget the creator consumed
|
|
169
|
+
before the child could take the lock. It is absent when no supervised
|
|
170
|
+
child was spawned, and `0 <= spawn_to_release_ms <= lock_held_ms` always
|
|
171
|
+
holds.
|
|
88
172
|
|
|
89
173
|
The record is appended after the transaction returns, so never while the
|
|
90
174
|
Registry lock is held, and a journal failure never changes the create's
|
|
91
175
|
result, exit status, stdout, or stderr. It does not replace the invocation's
|
|
92
176
|
`command.outcome`, and it does not count guard reads or time individual
|
|
93
177
|
guards. The generated Window rename also runs through the same transaction
|
|
94
|
-
and is not recorded, because it is not a create. Every
|
|
95
|
-
|
|
178
|
+
and is not recorded, because it is not a create. Every event family other
|
|
179
|
+
than `create.outcome` rejects the `create` component, the six phase fields,
|
|
180
|
+
and `spawn_to_release_ms`; every family other than `create.outcome` and
|
|
181
|
+
`registry.lock.acquisition` rejects `lock_held_ms`.
|
|
182
|
+
|
|
183
|
+
Registry lock acquisitions use `component=registry` and
|
|
184
|
+
`event=registry.lock.acquisition`. Every acquisition of the Registry mutation
|
|
185
|
+
lock -- a Registry read, update, convergent update, schema migration, or
|
|
186
|
+
admission barrier -- is measured, and one record is written for an
|
|
187
|
+
acquisition that waited at least one second, held the lock at least one
|
|
188
|
+
second, or timed out; every other acquisition writes nothing. The record is
|
|
189
|
+
appended after the lock is released (or after the acquisition gave up), under
|
|
190
|
+
the invocation's `run_id`, and it is best effort: a journal failure, or any
|
|
191
|
+
failure in the recorder, never changes the Registry operation's result or
|
|
192
|
+
the bytes it wrote. When a create's own acquisition is recorded, that append
|
|
193
|
+
runs after the unlock but before the Registry update returns, so its cost
|
|
194
|
+
falls inside that create's `lock_held_ms` and `phase_store_write_ms`. The
|
|
195
|
+
record carries only:
|
|
196
|
+
|
|
197
|
+
- `command` and `subcommand`: the invocation's catalog command, classified
|
|
198
|
+
like `command.outcome` and absent for an unclassified invocation. No argv
|
|
199
|
+
value, prompt, flag, path, pid, or process name is recorded.
|
|
200
|
+
- `operation`: the Registry entry point that took the lock, one of `update`,
|
|
201
|
+
`update-convergent`, `load`, `migrate`, or `admission-barrier`.
|
|
202
|
+
- `wait_ms`: from just before the acquisition to the grant, or to giving up.
|
|
203
|
+
- `lock_held_ms`: from the grant to just after the release; present only when
|
|
204
|
+
the lock was held.
|
|
205
|
+
- `duration_ms`: always exactly `wait_ms + lock_held_ms`.
|
|
206
|
+
- `result`, `level`, `kind`, and `code`: a held and released lock whose work
|
|
207
|
+
succeeded is `success` with no kind or code, at level `info`, or `warn` when
|
|
208
|
+
it held the lock for five seconds or more. A held lock whose work failed
|
|
209
|
+
(a refused callback, a failed validation or write) is `error`/`error`,
|
|
210
|
+
`kind=runtime`, `code=registry.mutation.failed`. A deadline timeout is
|
|
211
|
+
`error`/`error`, `kind=runtime`, `code=registry.lock.timeout`, and any other
|
|
212
|
+
acquisition failure is `code=registry.lock.acquire-failed`; neither carries
|
|
213
|
+
`lock_held_ms`.
|
|
214
|
+
|
|
215
|
+
`warn` is used by this event alone, and every other event family rejects
|
|
216
|
+
`wait_ms`, the `registry` component, and level `warn`. `diagnostics log
|
|
217
|
+
--level warn` selects these records; the support report's error-only
|
|
218
|
+
projection carries the `error` ones.
|
|
219
|
+
|
|
220
|
+
A Registry lock timeout error names the process the lock marker records as
|
|
221
|
+
holder, when that process is observed running, by the leading command words
|
|
222
|
+
of its `/proc/<pid>/cmdline`: the program's base name followed by the words
|
|
223
|
+
after it while they look like catalog command words (a lowercase letter, then
|
|
224
|
+
lowercase letters, digits, and hyphens), stopping at the first flag, `--`,
|
|
225
|
+
uid, path, number, or prompt, and at four words in all -- for example
|
|
226
|
+
`holder: pid 1234 (projmux create agent), running`. When the command line
|
|
227
|
+
cannot be read or yields no word, the kernel process name is used, and when
|
|
228
|
+
that cannot be read either, the command is reported as unavailable.
|
|
229
|
+
|
|
230
|
+
An accepted `agent message send` whose Claude source Agent's registered
|
|
231
|
+
Claude process is not an ancestor of the sender writes one `component=agent`,
|
|
232
|
+
`event=agent.message.foreign-source` `info`/`success` record. It adds only the
|
|
233
|
+
opaque source `agent_uid` (`agent-…`) and its `pane_uid` (`pane-…`); the
|
|
234
|
+
provider session id, process ids, and environment shown in the stderr warning
|
|
235
|
+
are never recorded, and every family other than the two `agent` events below
|
|
236
|
+
rejects `agent_uid` and the `agent` component. A journal failure never changes
|
|
237
|
+
the send.
|
|
238
|
+
|
|
239
|
+
The Claude messaging endpoint registration writes `component=agent`,
|
|
240
|
+
`event=agent.claude.registration` records. The chain is the SessionStart hook
|
|
241
|
+
`internal claude-endpoint-register`, which builds a bootstrap and starts the
|
|
242
|
+
detached helper `internal claude-endpoint-helper`; the helper claims and
|
|
243
|
+
records the registration in one Registry transaction and becomes Ready. Every
|
|
244
|
+
refusal along it, the helper's Ready, and the end of a Ready helper's serving
|
|
245
|
+
loop each write one record. A record carries only:
|
|
246
|
+
|
|
247
|
+
- `source`: the process that wrote it, `hook` or `helper`.
|
|
248
|
+
- `code`: `claude.registration.<reason>`, one reason of the closed table
|
|
249
|
+
below. A reason the table does not list, or does not allow for that source,
|
|
250
|
+
drops the record.
|
|
251
|
+
- `result`, `level`, and `kind`: a refusal before Ready is
|
|
252
|
+
`error`/`error`, `kind=runtime`. `ready` and the `ended-*` reasons are
|
|
253
|
+
`info`/`success` with no kind: the registration ran, and the `ended-*`
|
|
254
|
+
reason says how its lifetime ended.
|
|
255
|
+
- `duration_ms`: from that process's route entry to the append.
|
|
256
|
+
- `agent_uid` (`agent-…`) and `pane_uid` (`pane-…`): only once the Agent and
|
|
257
|
+
Pane matched the Registry, and omitted when not strictly shaped. The hook
|
|
258
|
+
sets them from the provider process check onward, once its bootstrap matched
|
|
259
|
+
the Pane and its Agent against the Registry, and for every helper start
|
|
260
|
+
refusal. The helper sets them only after its producer check passed: the
|
|
261
|
+
bootstrap then provably came from the live hook that made that Registry
|
|
262
|
+
match, so its UIDs are the Registry-matched ones.
|
|
263
|
+
|
|
264
|
+
The provider session id, the registration nonce (`registrationGeneration`),
|
|
265
|
+
the messaging token and socket path, lease and coordination socket paths,
|
|
266
|
+
process ids and start identities, argv, error text, and the working directory
|
|
267
|
+
are never recorded.
|
|
268
|
+
|
|
269
|
+
| code (`claude.registration.…`) | source | where |
|
|
270
|
+
| --- | --- | --- |
|
|
271
|
+
| `hook-arguments-present` | hook | the hook route got arguments |
|
|
272
|
+
| `registry-path-invalid` | hook | the activation Registry path is set but not the exact shape |
|
|
273
|
+
| `registry-unreadable` | hook, helper | the hook's Registry read failed, or the helper's read after its claim |
|
|
274
|
+
| `hook-input-unreadable` | hook | stdin failed to read or exceeded 64 KiB |
|
|
275
|
+
| `payload-not-session-start` | hook | the payload is not a parsable `SessionStart` |
|
|
276
|
+
| `pane-binding-mismatch` | hook | the Pane, activation generation, or Claude process binding does not match |
|
|
277
|
+
| `agent-mismatch` | hook | the Pane's Agent is not the running Claude Agent that owns it |
|
|
278
|
+
| `provider-process-mismatch` | hook | the hook's parent is not the bound Claude process |
|
|
279
|
+
| `messaging-credential-invalid` | hook | the messaging socket or token is missing or malformed |
|
|
280
|
+
| `session-id-embeds-credential` | hook | the session id contains the token or socket |
|
|
281
|
+
| `messaging-socket-unavailable` | hook, helper | the messaging socket failed inspection |
|
|
282
|
+
| `nonce-unavailable` | hook | no registration nonce could be generated |
|
|
283
|
+
| `authority-invalid` | hook, helper | the registration authority is not valid |
|
|
284
|
+
| `hook-identity-unavailable` | hook | the hook's own process identity is unavailable |
|
|
285
|
+
| `reply-tool-policy-unavailable` | hook | the reply tool policy could not be captured |
|
|
286
|
+
| `helper-executable-unavailable` | hook | the running binary could not be located |
|
|
287
|
+
| `helper-bootstrap-unavailable` | hook | the bootstrap could not be encoded |
|
|
288
|
+
| `helper-ack-pipe-unavailable` | hook | the acknowledgement pipe could not be created |
|
|
289
|
+
| `helper-start-failed` | hook | the helper process did not start |
|
|
290
|
+
| `helper-admission-unconfirmed` | hook | no acknowledgement arrived before the 3s deadline or EOF |
|
|
291
|
+
| `helper-arguments-invalid` | helper | the helper got arguments or inherited a messaging credential variable |
|
|
292
|
+
| `helper-ack-missing` | helper | fd 3 is absent |
|
|
293
|
+
| `helper-ack-not-pipe` | helper | fd 3 is not a pipe |
|
|
294
|
+
| `helper-input-unreadable` | helper | stdin failed to read, exceeded 64 KiB, or is not a bootstrap |
|
|
295
|
+
| `producer-mismatch` | helper | the parent is not the hook that built the bootstrap |
|
|
296
|
+
| `bootstrap-invalid` | helper | the bootstrap Registry path or token is invalid |
|
|
297
|
+
| `helper-identity-unavailable` | helper | the helper's own process identity is unavailable |
|
|
298
|
+
| `lease-unavailable` | helper | the private lease directory, socket, or its mode could not be set up |
|
|
299
|
+
| `coordination-unavailable` | helper | the coordination listener could not be opened |
|
|
300
|
+
| `lease-owner-unavailable` | helper | the lease owner receipt could not be written |
|
|
301
|
+
| `provider-process-gone` | helper | in the transaction, the Claude process is gone |
|
|
302
|
+
| `claim-refused-activation` | helper | in the transaction, the activation is no longer this helper's claim target |
|
|
303
|
+
| `claim-refused-competing` | helper | in the transaction, the same registration generation carries another session or lease |
|
|
304
|
+
| `claim-refused-newer` | helper | in the transaction, a newer SessionStart claimed another generation |
|
|
305
|
+
| `lock-timeout` | helper | the transaction gave up on the Registry lock deadline |
|
|
306
|
+
| `lock-acquire-failed` | helper | the transaction never ran for any other Store reason (the one fallback, below) |
|
|
307
|
+
| `registry-degraded` | helper | the Store refused the transaction on a degraded Registry |
|
|
308
|
+
| `registry-write-failed` | helper | the transaction's validation or durable write failed |
|
|
309
|
+
| `route-mismatch` | helper | the recorded route does not resolve to this registration |
|
|
310
|
+
| `dialogue-broker-unavailable` | helper | the dialogue broker could not start |
|
|
311
|
+
| `reply-tool-gate-unavailable` | helper | the reply tool gate could not be built |
|
|
312
|
+
| `stale-before-ack` | helper | the registration stopped being current before the acknowledgement |
|
|
313
|
+
| `ready` | helper | the acknowledgement byte was written |
|
|
314
|
+
| `ended-context-done` | helper | the serving loop's context ended |
|
|
315
|
+
| `ended-not-current` | helper | the serving loop found the registration no longer current |
|
|
316
|
+
| `ended-accept-failed` | helper | the lease listener failed |
|
|
317
|
+
|
|
318
|
+
A Claude session projmux did not launch writes nothing. The user-wide
|
|
319
|
+
SessionStart hook still runs for it, but with no activation Registry path set
|
|
320
|
+
at all (`PMX_INTERNAL_CLAUDE_REGISTRY_PATH` empty or unset) it is outside any
|
|
321
|
+
managed activation: the hook stops with the closed reason `unmanaged-session`,
|
|
322
|
+
which the recorder drops and every reader rejects, so it is never journaled
|
|
323
|
+
at any level. A set but malformed path is still `registry-path-invalid`. A
|
|
324
|
+
nested unmanaged Claude that inherited a managed Pane's activation environment
|
|
325
|
+
is refused as `provider-process-mismatch`, because its parent is not the bound
|
|
326
|
+
Claude process; that record names the managed Pane's `agent_uid` and
|
|
327
|
+
`pane_uid`, whose activation environment it inherited.
|
|
328
|
+
|
|
329
|
+
`lock-acquire-failed` is the one fallback of the transaction's
|
|
330
|
+
classification: the claim callback never ran, and the Store error is neither
|
|
331
|
+
a lock timeout nor a degraded Registry. That is a failed lock acquisition, or
|
|
332
|
+
a locked Registry read or recovery inspection failure the Store did not
|
|
333
|
+
classify as degraded. A failure after the callback ran is always
|
|
334
|
+
`registry-write-failed`.
|
|
335
|
+
|
|
336
|
+
The hook writes at most one record, and only once its attempt is over: at a
|
|
337
|
+
refusal before it started the helper, or after its acknowledgement wait
|
|
338
|
+
returned, which is `helper-admission-unconfirmed` or a start refusal. It never
|
|
339
|
+
appends between starting the helper and the helper's producer check, and a
|
|
340
|
+
confirmed admission writes nothing from the hook because the helper writes
|
|
341
|
+
`ready`. The helper writes `ready` exactly once, right after the
|
|
342
|
+
acknowledgement byte, and one more record when it returns: the refusal that
|
|
343
|
+
stopped it before Ready, or the `ended-*` reason after it. That last record is
|
|
344
|
+
appended only after the helper closed fd 3, so the hook's EOF never waits on
|
|
345
|
+
the journal. Appends are best effort: a failing or slow journal never changes
|
|
346
|
+
the hook's empty output, its nil result, or the helper's argv, environment,
|
|
347
|
+
and stdin. Every other event family rejects the `hook` and `helper` sources
|
|
348
|
+
and every `claude.registration.*` code.
|
|
349
|
+
|
|
350
|
+
Both routes are classified as the internal-only commands
|
|
351
|
+
`claude-endpoint-register` and `claude-endpoint-helper`, so the helper's slow
|
|
352
|
+
`registry.lock.acquisition` records carry `command=claude-endpoint-helper`.
|
|
353
|
+
Neither is state-changing and both always return nil, so neither writes a
|
|
354
|
+
`command.outcome`.
|
|
96
355
|
|
|
97
356
|
projmux no longer emits `session-state.outcome` records. Project snapshots
|
|
98
357
|
were removed, and the retained `internal tmux autosave-session-state` route is
|
|
@@ -313,7 +572,11 @@ Classification is intentionally conservative for mutation-capable interactive
|
|
|
313
572
|
commands: opening session/project/settings/popup flows is treated as changing
|
|
314
573
|
even when a user cancels. Explicit read variants (`internal status`, `list`,
|
|
315
574
|
`get`, config rendering, plain welcome, and the diagnostics viewer) remain
|
|
316
|
-
read-only.
|
|
575
|
+
read-only. Read-only classification governs `command.outcome` only: a
|
|
576
|
+
read-only command whose locked Registry read waited for or held the lock for a
|
|
577
|
+
second or more still writes its `registry.lock.acquisition` measurement, except
|
|
578
|
+
Doctor, the support report, and the retired no-write argv, which never append.
|
|
579
|
+
The successful automatic hook/poll paths `internal agent-hook ingest`, `attention
|
|
317
580
|
arm`, `attention clear`, `attention window`, `internal tmux autosave-session-state`, and
|
|
318
581
|
`window record` are also read-only so high-frequency operation does not append
|
|
319
582
|
to the journal; an error from any of them still records exactly one safe error
|
package/docs/release.md
CHANGED
|
@@ -34,6 +34,10 @@ then builds the linux/darwin × amd64/arm64 matrix and uploads tarballs to the
|
|
|
34
34
|
drafted release (`gh release upload --clobber`). `Build Release` depends on the
|
|
35
35
|
aggregate rather than on individual shards.
|
|
36
36
|
|
|
37
|
+
Each tarball holds the binary, `README.md`, `README-ko.md`, `LICENSE`, and
|
|
38
|
+
`THIRD_PARTY_NOTICES`. The last is generated by `make notices` and committed;
|
|
39
|
+
`make test` fails when it no longer matches what the binary links.
|
|
40
|
+
|
|
37
41
|
Do not add hardcoded notes back to that workflow. release-please owns the
|
|
38
42
|
notes.
|
|
39
43
|
|
|
@@ -400,6 +400,12 @@ platform with no executable link to read declines to drain rather than draining
|
|
|
400
400
|
on a guess: `defaultProjmuxImageReplaced` answers false on darwin, and the
|
|
401
401
|
absence is stated by this table's `unsupported-platform` row.
|
|
402
402
|
|
|
403
|
+
A native observer whose first bind receives a drain refusal leaves the Pane on
|
|
404
|
+
hook fallback and keeps retrying that exact Agent and thread. It writes no
|
|
405
|
+
composite authority until a current broker binding opens. When the old broker
|
|
406
|
+
finishes its accepted work and exits, the observer can bind to the replacement
|
|
407
|
+
and publish authority; messages remain refused while the binding is absent.
|
|
408
|
+
|
|
403
409
|
### The install pass
|
|
404
410
|
|
|
405
411
|
`projmux internal install-replace` runs as a step of `make install`, immediately
|
package/docs/repo-layout.md
CHANGED
|
@@ -21,6 +21,7 @@ projmux/
|
|
|
21
21
|
state/
|
|
22
22
|
tools/
|
|
23
23
|
gendocs/
|
|
24
|
+
gennotices/
|
|
24
25
|
ui/
|
|
25
26
|
picker/
|
|
26
27
|
pickercompat/
|
|
@@ -40,6 +41,8 @@ projmux/
|
|
|
40
41
|
- `internal/core` contains product behavior that should be testable without tmux.
|
|
41
42
|
- `internal/tools/gendocs` is a build-time `main` package, not part of the shipped
|
|
42
43
|
binary. `make docs` runs it to regenerate `docs/cli.md` from the command manifest.
|
|
44
|
+
- `internal/tools/gennotices` is a build-time `main` package too. `make notices` runs it
|
|
45
|
+
to regenerate `THIRD_PARTY_NOTICES` from the modules `./cmd/projmux` links.
|
|
43
46
|
- `internal/integrations/tmux` should be the only place that knows tmux command strings and output formats.
|
|
44
47
|
- `internal/ui/picker` and `internal/ui/projmuxpicker` own native picker behavior.
|
|
45
48
|
- `internal/ui/pickercompat` is an internal compatibility option/result shape for older app call sites. It is not a runtime backend; product code should route through the native picker.
|
|
@@ -145,6 +145,10 @@ PROJMUX_RESOURCE_PROJECT_FALLBACK_SMOKE=1 go test \
|
|
|
145
145
|
./internal/integrations/tmux
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
`PROJMUX_REAL_TMUX_STRICT=1` also turns on both isolated smokes, and the CI
|
|
149
|
+
Unit Tests job sets it, so they run there and a missing tmux fails them
|
|
150
|
+
instead of skipping. The read-only smoke above stays opt-in.
|
|
151
|
+
|
|
148
152
|
## Phase 1 inspector
|
|
149
153
|
|
|
150
154
|
The popup retains warming/partial/unavailable and overage states, renders RSS
|
package/docs/session-restore.md
CHANGED
|
@@ -17,9 +17,12 @@ registered Project is not asked: it opens fresh, which registers it.
|
|
|
17
17
|
the exact old Project UID and its Window/Pane/Agent counts. Declining returns
|
|
18
18
|
to the startup rows and writes nothing. It does not archive or retain the old
|
|
19
19
|
generation. Its new Window's first Pane follows the saved launch default
|
|
20
|
-
(`tmux-ai-split-mode
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
(`tmux-ai-split-mode`, then the central `ai-new-window-mode`, then
|
|
21
|
+
`selective`; see
|
|
22
|
+
[New AI Window Default](configuration.md#new-ai-window-default)), exactly as
|
|
23
|
+
a Window created from the UI does: the choice is made before anything is
|
|
24
|
+
cleared. The first open of an unregistered root, which resolves to the same
|
|
25
|
+
fresh start, behaves the same.
|
|
23
26
|
|
|
24
27
|
Esc/cancel returns to Projects; it is not an action row. Picker failure falls
|
|
25
28
|
back to the non-destructive `Continue project` action.
|
|
@@ -77,10 +80,11 @@ each successful result has exactly one Project claiming the root.
|
|
|
77
80
|
The saved launch default is used only when the open carries the exact client
|
|
78
81
|
that pressed the row. The order is:
|
|
79
82
|
|
|
80
|
-
1. Ask. A picker mode (`selective`, the
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
1. Ask. A picker mode (`selective`, the default when neither
|
|
84
|
+
`tmux-ai-split-mode` nor `ai-new-window-mode` holds a valid mode, or
|
|
85
|
+
`resume`) opens its picker on the Pane the row was pressed in, before the
|
|
86
|
+
old layout is cleared or the new Session exists; a provider mode and
|
|
87
|
+
`shell` are already the answer and open nothing.
|
|
84
88
|
2. Clear the layout and create the new Session with its one shell Pane.
|
|
85
89
|
3. Fill it: an Agent answer is created in that Window first, and the shell is
|
|
86
90
|
then removed through the canonical Pane delete.
|
package/docs/statusbar.md
CHANGED
|
@@ -51,10 +51,11 @@ row 1 [#S] #{pane_current_path} <git> CPU 12% MEM 41% %H:%M
|
|
|
51
51
|
mouse-target context resolves the clicked window directly. All
|
|
52
52
|
other ranges fall through to `run-shell projmux internal statusbar click
|
|
53
53
|
...`, which dispatches by range id. The in-config short-circuit
|
|
54
|
-
is required because
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
is required because tmux has no format variable for the clicked
|
|
55
|
+
window: it is only reachable through tmux's internal mouse target
|
|
56
|
+
(`-t =`), which does not survive `run-shell`, so a handler can't
|
|
57
|
+
recover the target after the fact — the Go dispatcher's
|
|
58
|
+
`window|<idx>` index path is defense-in-depth only.
|
|
58
59
|
Each window tab reserves a one-cell live pane attention prefix from
|
|
59
60
|
`projmux attention window #{window_id}` before the index. AI panes use the
|
|
60
61
|
semantic `@projmux_ai_badge_kind` first: approval/input-required panes use
|
|
@@ -72,11 +73,12 @@ row 1 [#S] #{pane_current_path} <git> CPU 12% MEM 41% %H:%M
|
|
|
72
73
|
response-complete live badge, including stale `@projmux_ai_state=waiting`
|
|
73
74
|
fallback state; action-required and in-progress live badges remain visible.
|
|
74
75
|
Window-list badges and app pane-border badges use the same semantic priority,
|
|
75
|
-
with display style controlled by Settings > Appearance > AI badge style and
|
|
76
|
-
|
|
77
|
-
`⏳` for approval/input-required,
|
|
78
|
-
in-progress. `off` (also accepted as
|
|
79
|
-
the same spacing without drawing a
|
|
76
|
+
with display style controlled by Settings > Appearance > AI badge style and
|
|
77
|
+
persisted in `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-badge-style`.
|
|
78
|
+
The default is `dot`; `emoji` renders `⏳` for approval/input-required,
|
|
79
|
+
`✅` for response-complete, and `🔄` for in-progress. `off` (also accepted as
|
|
80
|
+
`minimal` when read from disk) preserves the same spacing without drawing a
|
|
81
|
+
marker.
|
|
80
82
|
The session, pwd, and git segments on this row are wrapped
|
|
81
83
|
in `#[range=user|<id>]` ranges and dispatched through the projmux
|
|
82
84
|
handler. The standalone config also wraps the right-side `projmux`
|
|
@@ -244,11 +246,12 @@ A single tmux bind handles both lines:
|
|
|
244
246
|
```tmux
|
|
245
247
|
bind-key -n MouseDown1Status if-shell -F "#{==:#{mouse_status_range},window}" \
|
|
246
248
|
{ select-window -t = } \
|
|
247
|
-
{ run-shell "'<projmux>' internal statusbar click \"#{mouse_status_range}\" --client \"#{client_tty}\"
|
|
249
|
+
{ run-shell "'<projmux>' internal statusbar click \"#{mouse_status_range}\" --client \"#{client_tty}\"" }
|
|
248
250
|
```
|
|
249
251
|
|
|
250
252
|
`MouseDown1Status` fires from any line of a multi-line status bar with
|
|
251
|
-
`#{mouse_status_range}` resolving to the range under the cursor.
|
|
253
|
+
`#{mouse_status_range}` resolving to the range under the cursor. The click
|
|
254
|
+
command passes only `#{mouse_status_range}` and `#{client_tty}`.
|
|
252
255
|
|
|
253
256
|
## Usage element drop order
|
|
254
257
|
|
|
@@ -403,10 +406,11 @@ Internal notify commands use `NotifySidebar:*` IDs in `keymap.toml`; runtime
|
|
|
403
406
|
footers render key guides from the merged keymap and prefer the default alias
|
|
404
407
|
when it is still configured.
|
|
405
408
|
|
|
406
|
-
Empty `#{mouse_status_range}` (a click on whitespace)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
409
|
+
Empty `#{mouse_status_range}` (a click on whitespace) is a no-op. Unknown
|
|
410
|
+
user range ids are non-specialized placeholder surfaces and no-op until a
|
|
411
|
+
handler is wired into the dispatcher. `--mouse-window <v>` is accepted for
|
|
412
|
+
compatibility with bindings generated by older releases (live on a running
|
|
413
|
+
tmux server until `projmux config apply`) and ignored.
|
|
410
414
|
|
|
411
415
|
## Keyboard chord
|
|
412
416
|
|
|
@@ -474,14 +478,14 @@ git-provider, or bell icon), or `emoji`. Git branch decoration follows
|
|
|
474
478
|
fox-style mark, and other remotes use a generic git branch mark. Saving
|
|
475
479
|
visibility or Resources regenerates the app/standalone config and source-loads
|
|
476
480
|
the generated app config when Settings is running inside tmux. The legacy
|
|
477
|
-
|
|
481
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/projmux/statusbar-decoration` and
|
|
478
482
|
`@projmux_statusbar_decoration` remain fallback defaults for older configs.
|
|
479
483
|
Settings > Theme controls the bottom status bar background through
|
|
480
484
|
`status_background`; `surface` controls popup and native frame backgrounds.
|
|
481
485
|
|
|
482
|
-
Resources uses
|
|
483
|
-
state; there is no separate Resources visibility file
|
|
484
|
-
sampling state is an internal,
|
|
486
|
+
Resources uses `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/live-resources` as
|
|
487
|
+
its single saved enabled state; there is no separate Resources visibility file
|
|
488
|
+
or duplicate toggle. CPU sampling state is an internal,
|
|
485
489
|
atomically replaced file under `${XDG_STATE_HOME:-~/.local/state}/projmux/`
|
|
486
490
|
and is not a user-edited setting.
|
|
487
491
|
|