projmux 0.8.3 → 0.9.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 +2 -2
- package/README.md +6 -5
- package/docs/agent-workflow.md +25 -15
- package/docs/ai-agent-shortcuts.md +25 -0
- package/docs/architecture.md +30 -8
- package/docs/cli.md +243 -74
- package/docs/configuration.md +71 -11
- package/docs/globalization.md +1 -1
- package/docs/hooks.md +68 -25
- package/docs/install.md +2 -2
- package/docs/keybindings.md +22 -15
- package/docs/native-picker-no-fzf-poc.md +5 -5
- package/docs/native-picker-parity.md +7 -5
- package/docs/notify-queue.md +4 -3
- package/docs/npm-distribution.md +2 -2
- package/docs/operational-diagnostics.md +80 -0
- package/docs/resource-attribution.md +132 -0
- package/docs/session-restore.md +31 -6
- package/docs/settings-ia.md +54 -14
- package/docs/statusbar.md +24 -6
- package/docs/testing.md +1 -1
- package/docs/tmux-surface-inventory.md +108 -774
- package/docs/usage-tracking.md +47 -28
- package/package.json +5 -5
package/docs/hooks.md
CHANGED
|
@@ -207,8 +207,9 @@ If a `send-noti` hook itself calls `projmux notify push`, projmux sees
|
|
|
207
207
|
|
|
208
208
|
`Settings > Notifications > Delivery sources` surfaces the active Codex hooks,
|
|
209
209
|
Claude, and tmux AI notify diagnostics: status, conflicts, config paths, and
|
|
210
|
-
copyable CLI install/remove/dry-run commands. It
|
|
211
|
-
|
|
210
|
+
copyable CLI install/remove/dry-run commands. It also shows whether
|
|
211
|
+
`PROJMUX_NOTIFY_HOOK` overrides the built-in desktop sender. It does not install
|
|
212
|
+
or remove external Codex, Claude, or tmux settings.
|
|
212
213
|
|
|
213
214
|
`PROJMUX_NOTIFY_HOOK` is separate from `[hooks.send-noti]`: it replaces the
|
|
214
215
|
desktop sender and receives positional arguments
|
|
@@ -617,37 +618,79 @@ catalog `install` field for installed hook events.
|
|
|
617
618
|
|
|
618
619
|
## Antigravity Hook Ingest
|
|
619
620
|
|
|
620
|
-
`projmux ai ingest antigravity-hook < payload.json`
|
|
621
|
-
Antigravity CLI `agy` hook
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
621
|
+
`projmux ai ingest antigravity-hook --event <event> < payload.json` accepts
|
|
622
|
+
Antigravity CLI `agy` hook payloads. Antigravity v1.1.12 stdin does
|
|
623
|
+
not include the event name, so the command's `--event` value is authoritative.
|
|
624
|
+
Payload event aliases remain a compatibility fallback when `--event` is
|
|
625
|
+
omitted. `projmux ai integrate antigravity [--dry-run|--remove]` owns exactly
|
|
626
|
+
the named `projmux` entry in `~/.gemini/config/hooks.json` and separately owns
|
|
627
|
+
only `statusLine` in `~/.gemini/antigravity-cli/settings.json`. Other named hooks,
|
|
628
|
+
their fields, and unknown JSON values remain untouched. The generated commands
|
|
629
|
+
use the stable absolute projmux executable because Antigravity runs handlers
|
|
630
|
+
with the config directory as cwd. The command refuses unmanaged name/command
|
|
631
|
+
conflicts, malformed JSON, symlink paths, and permission failures with an
|
|
632
|
+
actionable diagnostic. Doctor/Settings distinguish installed, missing,
|
|
633
|
+
conflicting, and stale managed entries; stale covers executable, event/schema,
|
|
634
|
+
or stdout-fallback drift and is refreshed by the install command.
|
|
635
|
+
The statusline object uses the official v1.1.12 command shape with
|
|
636
|
+
`enabled=true` and `stack_with_default=true`. Its direct explicit `Statusline`
|
|
637
|
+
ingest command emits empty stdout, preserving the built-in line. Existing
|
|
638
|
+
custom statusline commands are conflicts and are never wrapped or chained.
|
|
639
|
+
|
|
640
|
+
The default Antigravity catalog records the five official v1.1.12 events and
|
|
641
|
+
installs four non-permission events:
|
|
630
642
|
|
|
631
643
|
| Event/signal | Behavior |
|
|
632
644
|
| --- | --- |
|
|
633
|
-
| `
|
|
634
|
-
| `
|
|
635
|
-
| `
|
|
636
|
-
| `
|
|
645
|
+
| `PreToolUse` | known permission-changing event; never installed and no permission decision is synthesized |
|
|
646
|
+
| `PreInvocation` | marks the matched pane hook-active and moves it to thinking/busy; no notify queue entry is pushed |
|
|
647
|
+
| `PostInvocation` | marks the matched pane hook-active and writes a quiet bookkeeping diagnostic; no notify queue entry is pushed |
|
|
648
|
+
| `PostToolUse` | marks the matched pane hook-active, retains tool error metadata in quiet diagnostics, and pushes no notify queue entry |
|
|
649
|
+
| `Stop` | pushes an info completion unless an explicit error signal requires a critical error row |
|
|
650
|
+
| `Statusline` with `tool_confirmation_pending=true` | pushes/replaces a deduped critical approval-required row outside the hook catalog |
|
|
651
|
+
| `Statusline` with `agent_state=thinking|working|tool_use` | moves the matched pane to thinking/busy without notifying, unless a terminal completion/approval state must be preserved from a late refresh; a new `PreInvocation` resets the next generation to busy |
|
|
652
|
+
| `Statusline` with `agent_state=idle` or `tool_confirmation_pending=false` | quiet update; does not clear completion/approval attention and creates no notification |
|
|
637
653
|
| unknown events | mark the matched pane hook-active and write quiet ingest diagnostics only |
|
|
638
654
|
|
|
639
|
-
Antigravity notify rows use `agent=antigravity` metadata.
|
|
640
|
-
|
|
641
|
-
`
|
|
642
|
-
`
|
|
655
|
+
Antigravity notify rows use `agent=antigravity` metadata. The v1.1.12 parser
|
|
656
|
+
retains common camelCase `conversationId`, `workspacePaths`, `transcriptPath`,
|
|
657
|
+
`artifactDirectoryPath`, and `modelName`; invocation `invocationNum` and
|
|
658
|
+
`initialNumSteps`; post-tool `toolCall`, `stepIdx`, and `error`; and Stop
|
|
659
|
+
`executionNum`, `terminationReason`, `error`, and `fullyIdle`. Existing aliases
|
|
660
|
+
such as `conversation_id`, `cwd`, `workspace.path`, `agent_state`, and nested
|
|
661
|
+
`statusline.tool_confirmation_pending` remain accepted. The first non-empty
|
|
662
|
+
`workspacePaths` value is only a cwd fallback candidate; empty/absent arrays do
|
|
663
|
+
not become the process cwd, and inherited `$TMUX_PANE` still wins attribution.
|
|
664
|
+
`NO_TOOL_CALL`, `MODEL_STOP`, and known normal reasons are info completions.
|
|
665
|
+
Non-empty `error`, explicit `ERROR`, and `MAX_STEPS_EXCEEDED` families are
|
|
666
|
+
critical; unknown reasons retain diagnostic metadata but default to info.
|
|
667
|
+
Explicit non-permission hook ingest writes valid hook stdout: `{}` for
|
|
668
|
+
invocation/post-tool events and `{"decision":"stop"}` for Stop. The latter
|
|
669
|
+
allows the requested stop to complete; projmux does not emit `continue` or a
|
|
670
|
+
`PreToolUse` permission decision.
|
|
671
|
+
Each managed command includes its explicit event selector and a valid stdout
|
|
672
|
+
fallback: `{}` for invocation/post-tool events and `{"decision":"stop"}` for
|
|
673
|
+
Stop. The fallback is non-blocking and never returns `continue`,
|
|
674
|
+
`allow`, `deny`, or `ask`.
|
|
675
|
+
|
|
676
|
+
The named entry in `hooks.json` remains the install source of truth. Use
|
|
677
|
+
`agy -p '/hooks' --output-format json` only as a read-only runtime diagnosis of
|
|
678
|
+
loaded sources/events; its result is never used to generate or rewrite config.
|
|
643
679
|
Antigravity ingest uses `conversationId` as pane thread metadata for matching
|
|
644
680
|
and as session-state resume metadata. Session restore uses
|
|
645
681
|
`agy --conversation <uuid>` only when that id is present and UUID-shaped;
|
|
646
|
-
otherwise preview and doctor render `resume unavailable`.
|
|
647
|
-
`
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
682
|
+
otherwise preview and doctor render `resume unavailable`. Official snake_case
|
|
683
|
+
`cwd`, `conversation_id`, `transcript_path`, `agent_state`,
|
|
684
|
+
`tool_confirmation_pending`, and structured `context_window.used_percentage`
|
|
685
|
+
plus token fields are parsed directly. The structured percentage is persisted
|
|
686
|
+
with conversation identity to the usage state dir; the legacy string percentage
|
|
687
|
+
remains a fallback so the usage HUD can surface it as the conversation-local
|
|
688
|
+
`context` row. The official top-level `quota` map is persisted independently:
|
|
689
|
+
valid `remaining_fraction` values become used percent, exact upstream bucket
|
|
690
|
+
IDs render as separate `quota/<bucket>` account rows, and `reset_time` plus
|
|
691
|
+
optional `reset_in_seconds` retain their independent meanings. Invalid,
|
|
692
|
+
disabled, missing, or empty quota shapes degrade without inventing a cadence or
|
|
693
|
+
reinterpreting context. Raw payloads or transcript contents are not stored.
|
|
651
694
|
|
|
652
695
|
## Ingest Debug Log
|
|
653
696
|
|
package/docs/install.md
CHANGED
|
@@ -18,8 +18,8 @@ After installing, run:
|
|
|
18
18
|
projmux doctor
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
`doctor`
|
|
22
|
-
|
|
21
|
+
`doctor` performs read-only diagnostics for runtime tools such as `tmux`,
|
|
22
|
+
`git`, and `stty`.
|
|
23
23
|
|
|
24
24
|
Start the tmux app with:
|
|
25
25
|
|
package/docs/keybindings.md
CHANGED
|
@@ -17,7 +17,7 @@ The recommended path when a key does not fire:
|
|
|
17
17
|
`projmux shell` key adapter, then retry the physical key.
|
|
18
18
|
3. Run `projmux setup` outside tmux to see which bytes reach the process.
|
|
19
19
|
4. For supported terminals outside the native macOS app socket, preview
|
|
20
|
-
`projmux
|
|
20
|
+
`projmux setup terminal [terminal]`; add `--apply`
|
|
21
21
|
only after reviewing the merge.
|
|
22
22
|
5. For unsupported terminals, configure plain Meta bytes or add a custom key in
|
|
23
23
|
Settings > Keybindings.
|
|
@@ -56,6 +56,13 @@ These shortcuts are the guaranteed launch defaults. They need no tmux prefix.
|
|
|
56
56
|
to the selected live tmux window using that window's current active pane; it is
|
|
57
57
|
separate from `last-pane` and from the existing-session popup.
|
|
58
58
|
|
|
59
|
+
`Resources:Open` is listed in Settings > Keybindings without a default key.
|
|
60
|
+
Adding a safe direct chord opens the Linux/tmux Resource Inspector through the
|
|
61
|
+
canonical client-scoped popup-toggle path; pressing the same custom chord again
|
|
62
|
+
closes it without touching another client's popup. The action remains usable
|
|
63
|
+
when Settings > Labs > Live system resources is off—the Lab controls only
|
|
64
|
+
statusbar visibility.
|
|
65
|
+
|
|
59
66
|
The tmux prefix remains the upstream default `Ctrl-b`. Inside a running
|
|
60
67
|
session, `Ctrl-b ?` lists the live tmux bindings.
|
|
61
68
|
|
|
@@ -142,11 +149,11 @@ Advanced typed entry remains available from Action detail for literal
|
|
|
142
149
|
risky/reserved key copy, and raw diagnostics. Advanced delivery is still owned
|
|
143
150
|
by the selected Projmux action. The native macOS app-socket adapter reads the
|
|
144
151
|
same safe chords directly; supported Ghostty and Windows Terminal mappings for
|
|
145
|
-
other paths are previewed/applied through `projmux
|
|
152
|
+
other paths are previewed/applied through `projmux setup terminal`, not by storing raw
|
|
146
153
|
sequences in the primary keymap. Options covers unbinding the action and
|
|
147
154
|
reset/use-default flows. Diagnostic/probe/init workflows are not first-class
|
|
148
155
|
Settings tabs; use `projmux setup` and, where the native adapter does not apply,
|
|
149
|
-
`projmux
|
|
156
|
+
`projmux setup terminal` from the terminal when key delivery needs remediation.
|
|
150
157
|
|
|
151
158
|
Optional direct keys can be added for actions such as:
|
|
152
159
|
|
|
@@ -299,29 +306,29 @@ projmux setup --timeout 10s
|
|
|
299
306
|
projmux setup --non-interactive
|
|
300
307
|
```
|
|
301
308
|
|
|
302
|
-
##
|
|
309
|
+
## Terminal remediation: `projmux setup terminal`
|
|
303
310
|
|
|
304
|
-
`projmux
|
|
305
|
-
Default mode is
|
|
311
|
+
`projmux setup terminal` previews and optionally applies supported terminal
|
|
312
|
+
mappings. Default mode is a read-only preview; pass `--apply` to write changes with a timestamped
|
|
306
313
|
backup.
|
|
307
314
|
|
|
308
315
|
```sh
|
|
309
|
-
projmux
|
|
310
|
-
projmux
|
|
311
|
-
projmux
|
|
312
|
-
projmux
|
|
313
|
-
projmux
|
|
314
|
-
projmux
|
|
316
|
+
projmux setup terminal
|
|
317
|
+
projmux setup terminal ghostty
|
|
318
|
+
projmux setup terminal ghostty --apply
|
|
319
|
+
projmux setup terminal windows-terminal --apply
|
|
320
|
+
projmux setup terminal --config /path/to/file
|
|
321
|
+
projmux setup terminal --allow-symlink
|
|
315
322
|
```
|
|
316
323
|
|
|
317
324
|
The merge is idempotent: matching bindings are no-ops, missing bindings are
|
|
318
325
|
added, and keys already mapped to a different user action are skipped with a
|
|
319
|
-
warning. `projmux
|
|
326
|
+
warning. `projmux setup terminal` does not read `keymap.toml`; direct tmux keys still
|
|
320
327
|
belong in Settings > Keybindings or the keymap file.
|
|
321
328
|
|
|
322
329
|
### Ghostty
|
|
323
330
|
|
|
324
|
-
`projmux
|
|
331
|
+
`projmux setup terminal ghostty` emits plain Meta bytes for `Alt-1` through `Alt-5` in
|
|
325
332
|
the managed block:
|
|
326
333
|
|
|
327
334
|
```text
|
|
@@ -341,7 +348,7 @@ refused by default; pass `--allow-symlink` to write through the link.
|
|
|
341
348
|
|
|
342
349
|
### Windows Terminal
|
|
343
350
|
|
|
344
|
-
`projmux
|
|
351
|
+
`projmux setup terminal windows-terminal` merges `sendInput` actions identified by the
|
|
345
352
|
`User.projmux*` ID prefix. The generated inputs use plain Meta bytes, tmux
|
|
346
353
|
prefix sequences for split actions, and xterm modifier-arrow sequences for
|
|
347
354
|
previous/next window:
|
|
@@ -24,8 +24,8 @@ The fzf compatibility surface for the native engine is tracked in
|
|
|
24
24
|
contract. It is not a runtime backend. App code should
|
|
25
25
|
describe picker intent as rows, actions, preview commands, and initial focus,
|
|
26
26
|
then route through the native picker.
|
|
27
|
-
- Settings > Labs remains available for
|
|
28
|
-
backend
|
|
27
|
+
- Settings > Labs remains available for Live system resources and Project
|
|
28
|
+
Hooks, but picker backend/source information has been retired.
|
|
29
29
|
- Picker flows covered by the native path include AI picker/settings, shell
|
|
30
30
|
update prompt, settings hub sections, switch settings/add-pin, the main
|
|
31
31
|
project switcher list, recent sessions, and notify sidebar.
|
|
@@ -188,12 +188,12 @@ Manual UX checks for the Docker sandbox:
|
|
|
188
188
|
|
|
189
189
|
## Automated No-fzf Docker E2E Command
|
|
190
190
|
|
|
191
|
-
Run this from the repository root. It builds a Go 1.
|
|
191
|
+
Run this from the repository root. It builds a Go 1.25 Trixie no-fzf
|
|
192
192
|
dependency image from `test/docker/no-fzf-poc.Dockerfile`, including Go module
|
|
193
193
|
cache, then mounts the repository into an isolated `--network none` container,
|
|
194
194
|
builds `projmux`, asserts `fzf` is not on `PATH`, runs the focused native-picker
|
|
195
|
-
tests,
|
|
196
|
-
|
|
195
|
+
tests, opens Settings > Labs with legacy `fzf` env/file values, verifies native
|
|
196
|
+
operation without a Labs picker-source row or config rewrite, exercises `projmux switch --ui=sidebar`
|
|
197
197
|
search/selection under a container PTY,
|
|
198
198
|
exercises `projmux switch --ui=popup` and `projmux sessions --ui=popup` against
|
|
199
199
|
existing tmux sessions under a wide 150x30 PTY, sends `Right` and `Alt-Down`
|
|
@@ -66,8 +66,9 @@ native picker engine and is not a public dependency-policy change.
|
|
|
66
66
|
backend. It is not a runtime backend. This keeps app code closer to a
|
|
67
67
|
DI-style picker contract instead of embedding binding strings at each call
|
|
68
68
|
site.
|
|
69
|
-
- Settings > Labs remains available
|
|
70
|
-
|
|
69
|
+
- Settings > Labs remains available for Live system resources and Project
|
|
70
|
+
Hooks, but no longer exposes picker backend/source information. Deprecated
|
|
71
|
+
saved/env backend values remain read-compatible and normalize to native.
|
|
71
72
|
- The split lets projmux grow a first-party picker design independently from
|
|
72
73
|
the compatibility option/result mapper.
|
|
73
74
|
|
|
@@ -93,9 +94,10 @@ contract; native popups still rely on the existing borderless tmux popup path.
|
|
|
93
94
|
using Enter plus arrow-key navigation under a PTY. The Docker e2e also fails
|
|
94
95
|
if the Settings flows write tmux no-server noise to stderr while running
|
|
95
96
|
outside tmux.
|
|
96
|
-
- `settings > Labs`: unit-
|
|
97
|
-
|
|
98
|
-
`
|
|
97
|
+
- `settings > Labs`: unit and Docker no-fzf coverage assert only Live system
|
|
98
|
+
resources and Project Hooks are visible. The Docker smoke starts with legacy
|
|
99
|
+
`fzf` env/file values, verifies Settings still uses native without fzf, and
|
|
100
|
+
confirms the compatibility file is read without being rewritten.
|
|
99
101
|
- `switch --ui=sidebar`: Docker no-fzf e2e creates sample projects, types
|
|
100
102
|
`bravo`, selects `bravo-web`, and confirms the opened tmux shell path.
|
|
101
103
|
- `switch --ui=popup`: Docker no-fzf e2e creates existing tmux sessions using
|
package/docs/notify-queue.md
CHANGED
|
@@ -70,12 +70,13 @@ exit code 2.
|
|
|
70
70
|
routing/debug context such as `agent`, `thread_id`, `turn_id`, `cwd`,
|
|
71
71
|
`model`, and `client`; Claude hook rows also carry event-specific keys such as
|
|
72
72
|
`tool_name`, `tool_input.command`, `error_type`, `subagent_type`, and
|
|
73
|
-
`teammate_name`. Antigravity
|
|
73
|
+
`teammate_name`. Antigravity hook rows carry `agent=antigravity`,
|
|
74
74
|
`conversation_id`, `termination_reason`, `fully_idle`,
|
|
75
75
|
`tool_confirmation_pending`, `agent_state`, and `context_window` when present.
|
|
76
76
|
The same `conversation_id` can seed session-state restore via
|
|
77
|
-
`agy --conversation <uuid>` when it is UUID-shaped
|
|
78
|
-
remains
|
|
77
|
+
`agy --conversation <uuid>` when it is UUID-shaped. Antigravity
|
|
78
|
+
account quota remains outside notify attention semantics: `context_window` is a
|
|
79
|
+
separate conversation-local gauge and is never treated as quota data.
|
|
79
80
|
Tmux bell fallback rows carry `agent=bell`, `event=bell`, and tmux target
|
|
80
81
|
context such as pane title, command, session, window, pane, and socket.
|
|
81
82
|
`notify list --json` includes this metadata as the structured data channel
|
package/docs/npm-distribution.md
CHANGED
|
@@ -18,7 +18,7 @@ The shim sets `PROJMUX_INSTALLER=npm` before executing the real binary so
|
|
|
18
18
|
`projmux update status` and the Settings About screen can present
|
|
19
19
|
npm-specific guidance. npm is only an update/install source label here; the
|
|
20
20
|
keybinding flow remains `projmux shell` first, then `projmux setup` and
|
|
21
|
-
`projmux
|
|
21
|
+
`projmux setup terminal` only for terminals that swallow shortcuts.
|
|
22
22
|
|
|
23
23
|
## Local Packaging
|
|
24
24
|
|
|
@@ -116,5 +116,5 @@ completed update never leaves stale-path `run-shell` commands failing with
|
|
|
116
116
|
|
|
117
117
|
The npm installer must not install system dependencies, edit shell startup
|
|
118
118
|
files, or mutate tmux config. Those actions stay behind explicit
|
|
119
|
-
`projmux doctor`, `projmux
|
|
119
|
+
`projmux doctor`, `projmux setup terminal`, Settings About update actions, or future
|
|
120
120
|
opt-in install commands.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Operational Diagnostics and Privacy
|
|
2
|
+
|
|
3
|
+
Projmux records a small local-only operational journal so command failures and
|
|
4
|
+
state changes can be inspected after the originating process exits. It does
|
|
5
|
+
not upload the journal, create support archives, contact an issue tracker, or
|
|
6
|
+
provide a background telemetry service.
|
|
7
|
+
|
|
8
|
+
## Safe event contract
|
|
9
|
+
|
|
10
|
+
Each JSONL record has a closed schema: `at`, `level`, `component`, `event`,
|
|
11
|
+
`result`, `duration_ms`, `run_id`, `version`, `mux_backend`, and optional
|
|
12
|
+
allowlisted `command`, `subcommand`, `kind`, and sanitized `message`. There is
|
|
13
|
+
no generic metadata map.
|
|
14
|
+
|
|
15
|
+
Command and subcommand names come from static allowlists. Unknown argv values,
|
|
16
|
+
paths, flags, and arguments are dropped. Messages have control/format
|
|
17
|
+
characters removed, whitespace normalized, the current home path abbreviated
|
|
18
|
+
to `~`, and length capped at 512 Unicode code points. Top-level outcomes never
|
|
19
|
+
copy `error.Error()` into the journal: their message is one of three stable,
|
|
20
|
+
lossy phrases (`command failed`, `invalid command usage`, or a classified
|
|
21
|
+
non-success status). Error `kind` is stored separately from that phrase.
|
|
22
|
+
|
|
23
|
+
The journal must never contain raw argv, stdin, prompts, notification bodies,
|
|
24
|
+
pane captures/output/title/topic/content, transcripts, raw hook payloads,
|
|
25
|
+
configuration secrets, or arbitrary environment values. Phase 0 also does not
|
|
26
|
+
add session/window/pane or other routing identifiers.
|
|
27
|
+
|
|
28
|
+
## Storage and retention
|
|
29
|
+
|
|
30
|
+
The path is
|
|
31
|
+
`${XDG_STATE_HOME:-$HOME/.local/state}/projmux/logs/operations.jsonl`.
|
|
32
|
+
On POSIX systems the `projmux` state and `logs` directories are private
|
|
33
|
+
(`0700`) and the journal is private (`0600`); accesses make a best-effort
|
|
34
|
+
repair of older permissive modes.
|
|
35
|
+
|
|
36
|
+
Append and trim share an OS-owned advisory inter-process lock. The kernel
|
|
37
|
+
releases ownership when a process exits, so an orphaned lock path needs no
|
|
38
|
+
path deletion or stale-owner reclamation and cannot race a successor owner.
|
|
39
|
+
Lock acquisition has an explicit 200 ms total budget so this side channel
|
|
40
|
+
cannot materially delay the original command result. When the file exceeds
|
|
41
|
+
5 MiB, a platform-specific atomic replacement retains approximately the
|
|
42
|
+
newest 2 MiB, beginning at a complete valid record; Windows uses replace-
|
|
43
|
+
existing semantics rather than plain rename. A trailing partial record is
|
|
44
|
+
discarded before the next append, and the reader skips malformed or truncated
|
|
45
|
+
records.
|
|
46
|
+
|
|
47
|
+
Classification is intentionally conservative for mutation-capable interactive
|
|
48
|
+
commands: opening session/project/settings/popup flows is treated as changing
|
|
49
|
+
even when a user cancels. Explicit read variants (`status`, `list`, `get`,
|
|
50
|
+
`preview`, config printing, plain welcome, and the diagnostics viewer) remain
|
|
51
|
+
read-only. The successful automatic hook/poll paths `ai ingest`, `attention
|
|
52
|
+
arm`, `attention clear`, `attention window`, `tmux autosave-session-state`, and
|
|
53
|
+
`window record` are also read-only so high-frequency operation does not append
|
|
54
|
+
to the journal; an error from any of them still records exactly one safe error
|
|
55
|
+
outcome. Explicit user mutations such as `attention toggle` retain their
|
|
56
|
+
state-changing success record. Direct top-level help and explicit preview-only intents (`upgrade
|
|
57
|
+
--dry-run`, `update apply --dry-run`, `doctor --install-missing --dry-run`, AI
|
|
58
|
+
integration dry-runs, and the currently preview-only session restore) are also
|
|
59
|
+
read-only. Multi-mode commands such as AI status/topic, doctor install,
|
|
60
|
+
terminal apply, snapshot delete, update check, and welcome popup inspect only
|
|
61
|
+
allowlisted mode/flag names; boolean `=false` values retain mutation-capable
|
|
62
|
+
classification, and no flag values are ever recorded. Help-looking tokens
|
|
63
|
+
after the direct command position stay conservatively mutation-capable because
|
|
64
|
+
they may be values rather than help intent.
|
|
65
|
+
|
|
66
|
+
Failures to resolve the path, create/repair permissions, lock, append, or trim
|
|
67
|
+
are ignored by the top-level command boundary. They do not change the original
|
|
68
|
+
command's stdout, stderr, exit code, or success/failure meaning, and journal
|
|
69
|
+
failures are never recursively journaled.
|
|
70
|
+
|
|
71
|
+
## Inspecting records
|
|
72
|
+
|
|
73
|
+
Use `projmux diagnostics log`; see [cli.md](cli.md#diagnostics). All text,
|
|
74
|
+
JSONL, tail, and filter views consume the same tolerant reader. A successful
|
|
75
|
+
viewer read is excluded from success logging, so inspection does not create a
|
|
76
|
+
recursion loop.
|
|
77
|
+
|
|
78
|
+
The older bounded `ai-ingest.log` and subsystem-specific `PROJMUX_*_DEBUG`
|
|
79
|
+
surfaces retain their current paths, formats, and behavior. They are not
|
|
80
|
+
migrated by this foundation.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Linux resource attribution core
|
|
2
|
+
|
|
3
|
+
Phase 0 provides the read-only attribution contract consumed by the Resource
|
|
4
|
+
Inspector shipped in Phase 1. `projmux resources`, the client-scoped
|
|
5
|
+
`resource-inspector` popup, the statusbar range, and `Resources:Open` all keep
|
|
6
|
+
the snapshot in memory only for the interactive process lifetime; it remains
|
|
7
|
+
outside Session State.
|
|
8
|
+
|
|
9
|
+
## Identity and inventory
|
|
10
|
+
|
|
11
|
+
`tmux.Client.ListResourcePanes` reads a resource-specific inventory containing
|
|
12
|
+
socket path, session id/name, window id, pane id, pane PID/TTY, and the session
|
|
13
|
+
`@projmux_project_path` anchor. This is deliberately separate from the general
|
|
14
|
+
`tmux.Pane` inventory so the resource contract requires PID, TTY, and project
|
|
15
|
+
anchor data without weakening other pane consumers.
|
|
16
|
+
|
|
17
|
+
The ownership key is `(socket, pane_id)`. Process identity is `(PID,
|
|
18
|
+
/proc/<pid>/stat starttime)` and a process is attributed only when its POSIX
|
|
19
|
+
SID maps to exactly one unique pane PID. Pane labels, AI topics, raw titles,
|
|
20
|
+
current commands, and cwd-derived names are not ownership inputs.
|
|
21
|
+
|
|
22
|
+
Linked appearances of one pane are deduplicated. Multiple non-empty project
|
|
23
|
+
anchors become `Shared / ambiguous`; no anchor becomes `Unassigned`. Processes
|
|
24
|
+
that use `setsid` or otherwise leave the pane SID remain host-only and are
|
|
25
|
+
counted at the escaped boundary instead of guessed back onto a pane.
|
|
26
|
+
|
|
27
|
+
## Sampling and read model
|
|
28
|
+
|
|
29
|
+
The Linux collector enumerates `/proc` once per sample, then reads only
|
|
30
|
+
`/proc/stat`, `/proc/meminfo`, and numeric `/proc/<pid>/stat` files. It never
|
|
31
|
+
reads command lines, environment, prompts, pane content, transcripts, memory
|
|
32
|
+
maps, SQLite, protobuf, or remote telemetry.
|
|
33
|
+
|
|
34
|
+
- CPU needs two samples with the same positive logical CPU count. Primary CPU
|
|
35
|
+
is process tick delta divided by aggregate host tick delta (host capacity
|
|
36
|
+
share, normally comparable on a 0–100% scale); secondary CPU is that share
|
|
37
|
+
multiplied by logical CPUs (core-equivalent). First sample, invalid/reset
|
|
38
|
+
deltas, PID reuse, and logical CPU changes remain unknown/partial, never zero
|
|
39
|
+
or silently clamped.
|
|
40
|
+
- Memory is summed RSS bytes plus `RSS / MemTotal`. RSS is a per-process sum;
|
|
41
|
+
shared pages may therefore be counted more than once.
|
|
42
|
+
- Pane values are aggregated into unique window and project rows. Tests enforce
|
|
43
|
+
that window/project totals equal the same set of unique pane totals.
|
|
44
|
+
- `Attributed` is not host total. `Other / unattributed` is a separate host
|
|
45
|
+
remainder. When process-delta timing or RSS sharing makes attributed values
|
|
46
|
+
exceed the host comparison sample, the model exposes an overage and leaves
|
|
47
|
+
remainder unknown instead of clamping it to zero.
|
|
48
|
+
- Snapshot state is `warming`, `ready`, `partial`, or `unavailable`. Bounded
|
|
49
|
+
diagnostics contain scan duration and sampled/skipped/race/permission counts,
|
|
50
|
+
plus identity/delta quality counts; they contain no user payload.
|
|
51
|
+
|
|
52
|
+
## Measurements
|
|
53
|
+
|
|
54
|
+
Measured 2026-08-12 on Linux amd64, Intel Core Ultra 5 125U. Collector cases
|
|
55
|
+
used generated procfs directory fixtures and `-benchtime=20x`; aggregation used
|
|
56
|
+
`-benchtime=100x`. Commands:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
go test -run '^$' -bench BenchmarkCollectorScan -benchtime=20x -count=1 ./internal/integrations/procfsresources
|
|
60
|
+
go test -run '^$' -bench BenchmarkBuildSnapshot -benchtime=100x -count=1 ./internal/core/resources
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Panes | Processes | Procfs scan | Scan allocation | Aggregation | Aggregation allocation |
|
|
64
|
+
|---:|---:|---:|---:|---:|---:|
|
|
65
|
+
| 10 | 50 | 0.407 ms | 80.6 KB | 0.026 ms | 32.1 KB |
|
|
66
|
+
| 10 | 200 | 0.927 ms | 313.0 KB | 0.074 ms | 75.0 KB |
|
|
67
|
+
| 10 | 1000 | 6.332 ms | 1.56 MB | 0.230 ms | 481.6 KB |
|
|
68
|
+
| 50 | 50 | 0.407 ms | 80.6 KB | 0.084 ms | 90.4 KB |
|
|
69
|
+
| 50 | 200 | 0.927 ms | 313.0 KB | 0.111 ms | 133.2 KB |
|
|
70
|
+
| 50 | 1000 | 6.332 ms | 1.56 MB | 0.261 ms | 539.9 KB |
|
|
71
|
+
|
|
72
|
+
The scan is process-count-bound and does not multiply by pane count. This
|
|
73
|
+
supports the planned 2-second popup cadence without a daemon or persistent
|
|
74
|
+
history. A separate count-only live cost probe (`PROJMUX_RESOURCE_PSS_MEASURE=1
|
|
75
|
+
go test -run TestPSSReadCostMeasurement -v
|
|
76
|
+
./internal/integrations/procfsresources`) attempted 200 host processes: 53
|
|
77
|
+
`smaps_rollup` reads succeeded and 147 were skipped because of access
|
|
78
|
+
restrictions. Those reads took 92.865058 ms, versus 7.323994 ms for the matching
|
|
79
|
+
one-pass RSS/stat scan. This is not a universal cost multiplier: accessibility,
|
|
80
|
+
process shape, kernel state, and cache effects differ by host. It does show the
|
|
81
|
+
structural cost of a separate `smaps_rollup` read and kernel page accounting per
|
|
82
|
+
accessible process, whereas RSS comes from the stat file already needed for
|
|
83
|
+
CPU/identity. PSS is therefore **deferred**. It may be reconsidered only as an
|
|
84
|
+
on-demand pane-detail measurement with a concrete consumer; it is not part of
|
|
85
|
+
continuous refresh.
|
|
86
|
+
|
|
87
|
+
## Sanitized real tmux smoke
|
|
88
|
+
|
|
89
|
+
The opt-in read-only test below observes an existing socket and emits counts
|
|
90
|
+
only. It neither captures pane content nor reads process command lines.
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
PROJMUX_RESOURCE_TMUX_SOCKET=projmux go test \
|
|
94
|
+
-run TestResourceAttributionRealTmuxReadOnlySmoke -v \
|
|
95
|
+
./internal/integrations/tmux
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
2026-08-12 result: `panes=8`, `pane_pid_eq_sid=8`, `missing_pids=0`,
|
|
99
|
+
`attributed_processes=13`, `escaped_boundary=0`, `sampled=474`, `skipped=0`,
|
|
100
|
+
`race=0`, `permission=0`, `status=ready`. A separate tmux format-only count
|
|
101
|
+
showed four shell panes, three direct agent panes, and one launcher pane. The
|
|
102
|
+
zero escaped count is a valid observed boundary; the setsid fixture test pins
|
|
103
|
+
the non-attribution behavior deterministically. Race and permission states are
|
|
104
|
+
also deterministic fixtures: the collector test injects `fs.ErrNotExist` and
|
|
105
|
+
`fs.ErrPermission` for numeric proc entries and checks the separate counts.
|
|
106
|
+
|
|
107
|
+
A positive real-kernel setsid boundary is covered by an isolated transient
|
|
108
|
+
smoke. It creates and removes its own tmux socket/server and never touches the
|
|
109
|
+
existing production socket:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
PROJMUX_RESOURCE_TRANSIENT_SMOKE=1 go test \
|
|
113
|
+
-run TestResourceAttributionTransientSetsidSmoke -v \
|
|
114
|
+
./internal/integrations/tmux
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
2026-08-12 result: `panes=1`, `attributed_processes=1`,
|
|
118
|
+
`escaped_boundary=1`, `sampled=480`, `skipped=0`, `race=0`, `permission=0`.
|
|
119
|
+
The pane shell remained attributed while its real `setsid` child was counted
|
|
120
|
+
at the escaped/Other boundary, without reading the child command line.
|
|
121
|
+
|
|
122
|
+
## Phase 1 inspector
|
|
123
|
+
|
|
124
|
+
The popup retains warming/partial/unavailable and overage states, renders RSS
|
|
125
|
+
explicitly as a sum, keeps `Other / unattributed` non-drillable, and discards
|
|
126
|
+
samples when it closes. Its non-overlapping default cadence is two seconds;
|
|
127
|
+
Ctrl-R shares the same scan gate. Selection and query survive refresh by stable
|
|
128
|
+
row identity, while a vanished row clamps to the nearest valid neighbor.
|
|
129
|
+
Display labels use label → agent topic → known interactive shell → raw title,
|
|
130
|
+
but those values never become ownership keys. Unsupported platforms show an
|
|
131
|
+
unavailable reason, not zero metrics. PSS, non-Linux collectors, process-list
|
|
132
|
+
drill-down, history, and resource mutation remain outside this contract.
|
package/docs/session-restore.md
CHANGED
|
@@ -11,9 +11,10 @@ projmux session-state restore --dry-run [--session <name>]
|
|
|
11
11
|
projmux session-state delete [--session <name>]
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Snapshots preserve source metadata, not a
|
|
15
|
-
keep `window_name`; pane records keep `pane_title`, recipe
|
|
16
|
-
metadata (`@projmux_ai_topic`
|
|
14
|
+
Snapshots preserve source metadata, not a resolved display label. Window records
|
|
15
|
+
keep `window_name`; pane records keep the user label, raw `pane_title`, recipe
|
|
16
|
+
fields, AI topic and manual-ownership metadata (`@projmux_ai_topic` and
|
|
17
|
+
`@projmux_ai_topic_manual`), and resume metadata when available. There is no
|
|
17
18
|
`display_label` field in the snapshot schema. After restore, pane borders and
|
|
18
19
|
app window tabs are display-time tmux policy: the app config derives both from
|
|
19
20
|
the active pane's visible label expression, while raw shell or terminal titles
|
|
@@ -48,11 +49,21 @@ unknown sources are low or none. The old statusbar Session State shortcut has
|
|
|
48
49
|
been removed; use `Projects > Sessions > State` or the `projmux session-state`
|
|
49
50
|
CLI for inspection/actions.
|
|
50
51
|
|
|
52
|
+
Session snapshots capture each pane's user-owned `label` separately from its
|
|
53
|
+
raw `title` and agent recipe `topic`. Older snapshots decode with an empty
|
|
54
|
+
label and no manual topic ownership; no title/topic equality heuristic is
|
|
55
|
+
applied. Replay explicitly sets or clears the label, startup recipe fields, AI
|
|
56
|
+
agent/topic/ownership/resume fields, and finally the raw title on the pane id
|
|
57
|
+
returned by tmux creation. It does not derive a target or identity from pane
|
|
58
|
+
order, a visible title, or equality between saved fields.
|
|
59
|
+
|
|
51
60
|
Agent restore direct-starts supported resume commands when creating fresh tmux
|
|
52
61
|
panes, matching the `projmux ai split` wrapper shape: the wrapper prepends the
|
|
53
|
-
agent binary directory to `PATH`, changes to the saved cwd,
|
|
54
|
-
|
|
55
|
-
`
|
|
62
|
+
agent binary directory to `PATH`, changes to the saved cwd, then execs
|
|
63
|
+
`codex resume <id>`, `claude --resume <id>`, or
|
|
64
|
+
`agy --conversation <uuid>`. It does not copy the saved topic into OSC or raw
|
|
65
|
+
tmux title state; replay restores the final raw title only from `Pane.Title`.
|
|
66
|
+
Antigravity restore
|
|
56
67
|
uses only the stable statusline `conversation_id` or hook `conversationId`
|
|
57
68
|
metadata captured as the pane resume id; missing or non-UUID Antigravity ids
|
|
58
69
|
render as `resume unavailable` rather than falling back silently to a shell
|
|
@@ -63,6 +74,20 @@ environment, shell functions, aliases, or live process state. Startup recipes
|
|
|
63
74
|
continue to use their saved `send-keys` command replay, and shell recipes only
|
|
64
75
|
restore cwd/layout.
|
|
65
76
|
|
|
77
|
+
This live capture lane remains distinct from resume-picker disk discovery and
|
|
78
|
+
has high confidence (`hook`/`session-id`). When an Antigravity picker row starts
|
|
79
|
+
a pane, its source is captured too: a UUID verified by an exact regular
|
|
80
|
+
`conversations/<uuid>.db` through `last_conversations` or workspace-bearing
|
|
81
|
+
summarized metadata has medium confidence, while a legacy `history.jsonl` row
|
|
82
|
+
has low confidence. Preview and doctor report that source/confidence as stored;
|
|
83
|
+
they do not claim that the upstream cache exposes complete history. Disk
|
|
84
|
+
discovery does not replace an existing live hook source, and it never opens a
|
|
85
|
+
conversation database or reads prompt/transcript content.
|
|
86
|
+
Bounded Session State agent-pane previews place resume health before the full
|
|
87
|
+
resume id, topic, and title so status, confidence, and source remain visible;
|
|
88
|
+
the underlying snapshot and unbounded preview model retain those identity and
|
|
89
|
+
context fields unchanged. Non-agent pane preview ordering is unchanged.
|
|
90
|
+
|
|
66
91
|
Settings > Session State is global settings only: global auto-save, auto-save
|
|
67
92
|
interval, and storage/retention policy. It does not show the current
|
|
68
93
|
snapshot tree. Delete for current-session snapshots and destructive restore
|