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/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 does not install or remove
211
- external Codex, Claude, or tmux settings.
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` is available for manual
621
- Antigravity CLI `agy` hook/statusline payloads. Projmux does not provide
622
- `projmux ai integrate antigravity`, does not install Antigravity hooks, and
623
- does not mutate Antigravity user config. The Delivery sources diagnostic
624
- therefore reports Antigravity as a read-only unsupported/manual row. If users
625
- wire it by hand, use an absolute `projmux` command path or a known cwd because
626
- relative command paths failed the Phase 0b smoke.
627
-
628
- The default known Antigravity catalog records only observed Phase 0b signals
629
- and marks them `install: false`:
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
- | `PostInvocation` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
634
- | `Stop` | pushes a completion row, or a critical error row when `error` is present or `terminationReason` is non-normal |
635
- | `Statusline` with `tool_confirmation_pending=true` | pushes a critical approval row; this is only active when a statusline/manual status payload is wired to ingest |
636
- | `Statusline` without `tool_confirmation_pending=true` | marks the matched pane hook-active and writes a quiet ingest diagnostic |
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. Accepted fields
640
- include `conversationId`/`conversation_id`, `cwd`, `workspace.path`,
641
- `transcriptPath`, `terminationReason`, `error`, `fullyIdle`, `agent_state`,
642
- `context_window`, and nested `statusline.tool_confirmation_pending`.
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`. The statusline
647
- `context_window` percentage is persisted to the usage state dir on ingest so
648
- the usage HUD can surface it as a `context-window-only` row — Antigravity has
649
- no 5-hour/weekly quota contract, so no quota bars are emitted. Raw payloads or
650
- transcript contents are not stored.
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` checks that runtime tools such as `tmux`, `git`, and `stty` are
22
- available.
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
 
@@ -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 init [terminal]`; add `--apply`
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 init`, not by storing raw
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 init` from the terminal when key delivery needs remediation.
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
- ## Auto-Config: `projmux init`
309
+ ## Terminal remediation: `projmux setup terminal`
303
310
 
304
- `projmux init` previews and optionally applies supported terminal mappings.
305
- Default mode is dry-run; pass `--apply` to write changes with a timestamped
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 init
310
- projmux init ghostty
311
- projmux init ghostty --apply
312
- projmux init windows-terminal --apply
313
- projmux init --config /path/to/file
314
- projmux init --allow-symlink
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 init` does not read `keymap.toml`; direct tmux keys still
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 init ghostty` emits plain Meta bytes for `Alt-1` through `Alt-5` in
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 init windows-terminal` merges `sendInput` actions identified by the
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 experimental settings, but picker
28
- backend selection has been retired.
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.24 Trixie no-fzf
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, stores the native backend through Settings > Labs, verifies the saved
196
- backend works without an env override, exercises `projmux switch --ui=sidebar`
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, but picker backend selection has been
70
- retired. Deprecated saved/env backend values normalize to native.
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-covered backend toggle writes
97
- `~/.config/projmux/picker-backend`, updates the tmux global
98
- `PROJMUX_PICKER_BACKEND`, and lets env override saved config.
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
@@ -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 manual hook rows carry `agent=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; Antigravity quota usage
78
- remains unsupported because `context_window` is not 5-hour/weekly quota data.
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
@@ -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 init` only for terminals that swallow shortcuts.
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 init`, Settings About update actions, or future
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.
@@ -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 final display label. Window records
15
- keep `window_name`; pane records keep `pane_title`, recipe fields, AI topic
16
- metadata (`@projmux_ai_topic`), and resume metadata when available. There is no
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, sets the terminal
54
- and tmux pane title from the saved agent topic, then execs `codex resume <id>`,
55
- `claude --resume <id>`, or `agy --conversation <uuid>`. Antigravity restore
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