projmux 0.15.3 → 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/README-ko.md +3 -4
- package/README.md +3 -3
- package/docs/agent-message-replies.md +149 -6
- package/docs/ai-agent-shortcuts.md +6 -5
- package/docs/architecture.md +467 -66
- package/docs/claude-coordination-endpoints.md +192 -20
- package/docs/cli-guide.md +872 -148
- package/docs/cli.md +1459 -485
- package/docs/codex-installed-compatibility.md +6 -11
- package/docs/codex-native-required-migration.md +1 -59
- package/docs/configuration.md +694 -222
- package/docs/globalization.md +18 -5
- package/docs/hooks.md +529 -62
- package/docs/keybindings.md +74 -4
- package/docs/legacy-cli-retirement.md +3 -3
- package/docs/legacy-diagnostics-inventory.md +4 -4
- package/docs/native-picker.md +3 -5
- package/docs/notify-queue.md +1 -1
- package/docs/npm-distribution.md +4 -0
- package/docs/operational-diagnostics.md +319 -34
- package/docs/pr-guideline.md +66 -22
- package/docs/release.md +101 -0
- package/docs/replacement-contract.md +72 -58
- package/docs/repo-layout.md +3 -0
- package/docs/resource-attribution.md +6 -2
- package/docs/session-restore.md +50 -80
- package/docs/settings-ia.md +14 -22
- package/docs/statusbar.md +38 -37
- package/docs/testing.md +83 -18
- package/docs/theme-palette.md +6 -6
- package/docs/tmux-surface-inventory.md +21 -10
- package/docs/troubleshooting.md +2 -4
- package/docs/upgrading.md +161 -24
- package/docs/usage-tracking.md +40 -49
- package/package.json +5 -5
- package/docs/agent-workflow.md +0 -2596
- package/docs/codex-generation-pool.md +0 -623
- package/docs/codex-stored-qualification.md +0 -45
package/docs/architecture.md
CHANGED
|
@@ -131,7 +131,7 @@ procfs collector supplies PID+starttime identity, SID, CPU ticks, RSS, and host
|
|
|
131
131
|
capacity. Pure aggregation builds pane, unique-window, and project rows without
|
|
132
132
|
using labels, topics, titles, or cwd-derived names as ownership keys.
|
|
133
133
|
|
|
134
|
-
Resource snapshots are
|
|
134
|
+
Resource snapshots are in-memory only and are never saved or restored. See
|
|
135
135
|
[resource-attribution.md](resource-attribution.md) for metric, partial-state,
|
|
136
136
|
host-remainder, privacy, and measurement contracts.
|
|
137
137
|
|
|
@@ -144,7 +144,7 @@ CLI information architecture v2 resource routes.
|
|
|
144
144
|
Packages:
|
|
145
145
|
|
|
146
146
|
- `internal/core/metadata` is pure: the resource model, validation, name
|
|
147
|
-
allocation, schema migration,
|
|
147
|
+
allocation, schema migration, and the operation
|
|
148
148
|
transaction. It performs no I/O; the clock, uid source, and root-directory
|
|
149
149
|
probe are injected through `Mutator`.
|
|
150
150
|
- `internal/core/resourcegraph` is pure: the resolved resource graph that joins
|
|
@@ -171,7 +171,7 @@ Packages:
|
|
|
171
171
|
fills a `resourcegraph.Inventory` from one exact server.
|
|
172
172
|
- `internal/integrations/tmuxopts` is a dependency-free leaf holding the
|
|
173
173
|
canonical spelling of every projmux-owned tmux option name, so the generated
|
|
174
|
-
tmux config
|
|
174
|
+
tmux config and the resource mirror cannot drift.
|
|
175
175
|
|
|
176
176
|
Resources and ownership:
|
|
177
177
|
|
|
@@ -216,6 +216,50 @@ Resources and ownership:
|
|
|
216
216
|
live only in runtime inventory, outside the Project hierarchy. A
|
|
217
217
|
ControlSession is not a counter-example: it is a root resource that *names* a
|
|
218
218
|
session, and the session still carries no identity of its own.
|
|
219
|
+
- `Project.status.session` is the **last recorded** projection, not a live
|
|
220
|
+
read. It holds `name`, `live`, and `socketPath`: the exact absolute socket
|
|
221
|
+
path of the tmux server the session was last created or observed live on,
|
|
222
|
+
as the writing route verified it against the server's own `#{socket_path}`
|
|
223
|
+
(never derived from a socket name; empty when that route had no verified
|
|
224
|
+
path). Registration records the name with `live=false` and no path. Create,
|
|
225
|
+
materialize, Project startup, and legacy import write `live=true` with the
|
|
226
|
+
path of their route. A successful managed Project stop writes `live=false`
|
|
227
|
+
only after the exact Session is observed absent, keeping the name and
|
|
228
|
+
`socketPath`; deleting a Project's last valid primary Window lowers `live`
|
|
229
|
+
the same way. The full reconciler pass (`refreshSessionProjections`, run by
|
|
230
|
+
the create routes and by controller convergence) records `live=true` with
|
|
231
|
+
this pass's path for every Project whose session is on the server it
|
|
232
|
+
observed. It lowers by absence only a projection recorded on that server or
|
|
233
|
+
on no server, keeping its name and path; a projection recorded on another
|
|
234
|
+
server is left as it is, and a pass without a verified path lowers none that
|
|
235
|
+
record a path. The rule is `SessionAbsenceAttributableTo`.
|
|
236
|
+
`reconcile resources` scopes that pass to Projects with an observed
|
|
237
|
+
live Session, and outside that scope it lowers a `live=true` projection
|
|
238
|
+
whose `socketPath` is exactly the reconciled server's path and whose session
|
|
239
|
+
is absent there, keeping its name and `socketPath`; Projects with another or
|
|
240
|
+
no `socketPath` are untouched, because absence from one server is not
|
|
241
|
+
evidence about a session recorded on another. The one judgement is
|
|
242
|
+
`Mutator.LowerProjectSessionsEndedOnServer`.
|
|
243
|
+
The `window-unlinked` hook, which a raw `kill-session` fires, applies the
|
|
244
|
+
same judgement on its fast path: once its lifecycle stage has observed the
|
|
245
|
+
host, it lowers a live projection recorded on the hook's exact verified
|
|
246
|
+
server whose session is gone there, re-reading that server's sessions under
|
|
247
|
+
the Registry lock. It does not run the full pass, and when no live
|
|
248
|
+
projection records that server it costs the hook no extra tmux call.
|
|
249
|
+
- When the exact server `reconcile resources` targets is not running (its
|
|
250
|
+
runtime authority read fails with a missing-server signature, and only
|
|
251
|
+
then), the same judgement runs with an empty present set against the exact
|
|
252
|
+
target path: the `--socket-path` or inherited `$TMUX` path itself, or for
|
|
253
|
+
`--socket <name>` the path tmux would use for that label (the first existing
|
|
254
|
+
`tmux-<uid>` directory under `$TMUX_TMPDIR`, then `/tmp`, resolved through
|
|
255
|
+
symlinks). Every projection recorded `live=true` on exactly that path is
|
|
256
|
+
lowered in one Registry commit, keeping its name and `socketPath`, and the
|
|
257
|
+
receipt states the absent server and the lowered count; nothing else is
|
|
258
|
+
planned, written, or reobserved, and `--dry-run` previews the same items.
|
|
259
|
+
When nothing is left to lower, or the path cannot be resolved, the command
|
|
260
|
+
fails at the runtime authority stage exactly as before and writes nothing.
|
|
261
|
+
Any other authority failure (a refused or unreadable socket, a timeout, a
|
|
262
|
+
server that is not app-owned) never lowers anything.
|
|
219
263
|
- `Window` and `Pane` carry **no stored liveness field**, deliberately. Their
|
|
220
264
|
`status` block holds observed conditions only; live/offline is derived from a
|
|
221
265
|
live tmux observation at read time. See *Runtime observation and resource
|
|
@@ -321,7 +365,7 @@ support reports expose those counts but redact item reasons and identifiers.
|
|
|
321
365
|
Identity and naming:
|
|
322
366
|
|
|
323
367
|
- `metadata.uid` is opaque, immutable, and independent of tmux lifecycle. It
|
|
324
|
-
survives
|
|
368
|
+
survives runtime stop/Continue, runtime creation, and root rebind.
|
|
325
369
|
- `metadata.name` is the stable unique-within-scope query key. Project names
|
|
326
370
|
and ControlSession names are unique within their own root kind across the
|
|
327
371
|
registry. Window, Pane, and Agent names are unique by
|
|
@@ -335,6 +379,21 @@ Identity and naming:
|
|
|
335
379
|
or numeric suffix allocator. An explicit `--name` keeps its original spelling
|
|
336
380
|
after validation; explicit create and rename collisions fail with exit code 2
|
|
337
381
|
and zero Registry, tmux, or provider writes.
|
|
382
|
+
- A newly registered Project is the one exception. `create project` without
|
|
383
|
+
`--name`, and every implicit registration (first open of a directory,
|
|
384
|
+
`projmux shell`, Open fresh), name the Project after its root directory: the
|
|
385
|
+
root basename run through the same sanitizer as any name seed (`my repo`
|
|
386
|
+
becomes `my-repo`). If that basename sanitizes to nothing (the filesystem
|
|
387
|
+
root) or another Project already holds it, the Project falls back to the
|
|
388
|
+
exact-UID rule above -- never a numbered variant, never a `project`
|
|
389
|
+
placeholder. Legacy/orphan import and every Window, Pane, Agent, and
|
|
390
|
+
ControlSession keep exact-UID automatic names, and no stored name is ever
|
|
391
|
+
rewritten.
|
|
392
|
+
- Open fresh replaces the Project and carries its name over only when that
|
|
393
|
+
name is not shaped like a minted Project UID (`proj-` plus a full canonical
|
|
394
|
+
UID payload). An operator-chosen name such as `proj-front` survives; a
|
|
395
|
+
UID-shaped name -- the old exact-UID automatic name, or one copied from a
|
|
396
|
+
predecessor -- is dropped so the replacement is named after its root.
|
|
338
397
|
- `create agent` supplies an **explicit** name for the Pane its Agent owns:
|
|
339
398
|
`<agent-name>-pane`, derived from the Agent's own name. That used to be a
|
|
340
399
|
documented follow-up `rename pane` a launcher had to remember, so a caller
|
|
@@ -359,6 +418,51 @@ Identity and naming:
|
|
|
359
418
|
context may duplicate and is never a selector, reservation, ownerRef, or
|
|
360
419
|
durable identity input. `metadata.labels` remains key/value classification;
|
|
361
420
|
`metadata.annotations` remains non-identifying metadata such as an AI topic.
|
|
421
|
+
- Creator provenance: every Agent create records on the new Agent and its
|
|
422
|
+
managed Pane, in the same transaction that commits the Agent, exactly one of
|
|
423
|
+
these, decided once per create (a fan-out records the same one on every
|
|
424
|
+
Agent). `projmux.io/creator-basis` names the kind of evidence, and only the
|
|
425
|
+
keys that evidence proves are written:
|
|
426
|
+
1. `pane-chain`, with `projmux.io/creator-agent` (the creator Agent's bare
|
|
427
|
+
UID) and `projmux.io/creator-pane` (that Agent's managed Pane's bare
|
|
428
|
+
UID): an explicit `create agent` (every spelling, `--create-window`, and
|
|
429
|
+
each Agent of a fan-out) or `create window --provider` ran inside an
|
|
430
|
+
Agent's managed Pane. It is recorded only when the unmasked ambient `%N`
|
|
431
|
+
(`__PROJMUX_RUNTIME_ANCHOR_PANE`, then `TMUX_PANE`) is exactly one live
|
|
432
|
+
Registry Pane, that Pane round-trips with its owning Agent's
|
|
433
|
+
`status.paneRef`, one `display-message` confirms it on the create's own
|
|
434
|
+
app-owned socket and server pid, and the create process descends from its
|
|
435
|
+
`#{pane_pid}`. It always wins.
|
|
436
|
+
2. `explicit`, with `projmux.io/creator-agent` only: the caller declared
|
|
437
|
+
`--creator uid:<agent>` and no pane chain was recorded. The declaration
|
|
438
|
+
must name an Agent in the Registry, or the create is refused before
|
|
439
|
+
anything changes. A declaration that disagrees with a recorded pane chain
|
|
440
|
+
is dropped, and stderr gets one `creator declaration not recorded:
|
|
441
|
+
pane-chain-disagrees (--creator uid:<agent>)` line.
|
|
442
|
+
3. `operator`, with `projmux.io/creator-client` only: the create ran in
|
|
443
|
+
process for a named operator client. The UI intent creates (picker, pane
|
|
444
|
+
menu, `ai split`, launch choice, the new-Window key) record the client
|
|
445
|
+
`ui`; a client layered on projmux that runs creates in process uses the
|
|
446
|
+
`recordOperatorCreator` seam. Such a create observes no pane chain,
|
|
447
|
+
because its process environment says nothing about who asked. A
|
|
448
|
+
`--creator` on it is dropped with the token `operator-client`. There is no
|
|
449
|
+
argv spelling of this basis; `TestNoArgvPathBuildsAnOperatorCreator`
|
|
450
|
+
holds its producers. Client names follow the one rule in
|
|
451
|
+
`internal/core/operatorclient`: 1-32 bytes of lowercase ASCII letters,
|
|
452
|
+
digits, and `-`, starting with a letter.
|
|
453
|
+
4. Nothing, when there is no evidence.
|
|
454
|
+
|
|
455
|
+
A failed pane-chain check changes nothing else about the create. When
|
|
456
|
+
nothing is recorded, stderr gets one `creator not recorded: <reason>` line
|
|
457
|
+
only if the ambient Pane is a live Agent Pane but a later check fails; an
|
|
458
|
+
ambient Pane that is malformed, unregistered, or a Window-owned shell is
|
|
459
|
+
silent. The creator Agent is never the created Agent. These keys are
|
|
460
|
+
provenance, not authentication, like a message `--source`: no permission,
|
|
461
|
+
route, or selector reads them, and `explicit` can be forged, which is what
|
|
462
|
+
its basis says. An absent key does not mean a human created the Agent: a
|
|
463
|
+
Codex Agent's own commands run under the app-server, not below the Pane's
|
|
464
|
+
process, so they record nothing unless they declare `--creator`. Nothing
|
|
465
|
+
backfills older Agents.
|
|
362
466
|
|
|
363
467
|
Root lifecycle:
|
|
364
468
|
|
|
@@ -452,6 +556,62 @@ Termination evidence transport:
|
|
|
452
556
|
receipt is the post-delete diagnostic: source, classification, observed time,
|
|
453
557
|
Pane/Agent uid, generation, and wait status only. No command, pane content,
|
|
454
558
|
prompt, transcript, or provider payload is recorded.
|
|
559
|
+
- Deletion records: every explicit deletion that commits its Registry
|
|
560
|
+
transaction appends exactly one JSON line to
|
|
561
|
+
`<state>/deletion-records.jsonl`, next to `termination-receipts.jsonl` and the
|
|
562
|
+
Registry it changed (`${XDG_STATE_HOME:-$HOME/.local/state}/projmux` by default). The writers are
|
|
563
|
+
`delete window|pane|agent`, `unregister project` and its deprecated alias
|
|
564
|
+
`delete project`, and `prune agent|project --yes`. The line is
|
|
565
|
+
`{"schemaVersion":1,"at","operation","operationID","via","actor":{"agentUID","paneUID","basis"},"targets":[{"kind","uid","name"}],"affected":[{"kind","uid","name","action"}]}`,
|
|
566
|
+
where `at` is RFC 3339 UTC; `operation` is one of `delete-window`,
|
|
567
|
+
`delete-pane`, `delete-agent`, `unregister-project`, `delete-project`,
|
|
568
|
+
`prune-agent`, `prune-project` (the record's own spelling; the receipt line a
|
|
569
|
+
delete prints spells it `delete.pane` and so on, a separate surface that is
|
|
570
|
+
not unified with it); `operationID` is the delete's intentional termination
|
|
571
|
+
receipt id, or one minted the same way for unregister and prune;
|
|
572
|
+
and `via` is `cli`, `ui` (the generated Pane and Window delete keys), or
|
|
573
|
+
`prune`. `targets` are what the operator named; `affected` is every
|
|
574
|
+
Project, ControlSession, Window, Agent, and Pane present before the commit
|
|
575
|
+
and absent after it (targets included), ordered by kind and then uid, each
|
|
576
|
+
with action `deleted`. No explicit deletion route removes a ControlSession
|
|
577
|
+
today (only a rolled-back transaction does, and that writes no record), so
|
|
578
|
+
that kind is listed for completeness of the two root kinds.
|
|
579
|
+
- The actor is the same pane-chain judgment creator provenance uses, run
|
|
580
|
+
against the pre-commit Registry inside the delete's own transaction, so a
|
|
581
|
+
delete that removes its own Agent still names it. `basis` is `pane-chain`
|
|
582
|
+
when it succeeds, creation's skip token otherwise (`anchor-invalid`,
|
|
583
|
+
`anchor-pane-unregistered`, `anchor-pane-ambiguous`, `caller-pane-not-agent`,
|
|
584
|
+
`pane-ref-mismatch`, `anchor-server-unproven`, `anchor-query-failed`,
|
|
585
|
+
`anchor-server-mismatch`, `anchor-pane-mismatch`,
|
|
586
|
+
`process-chain-unobservable`, `not-pane-descendant`), and empty when there
|
|
587
|
+
was no ambient Pane. The server step is the delete's own: one
|
|
588
|
+
`display-message -t %N` through the route the delete addresses must answer
|
|
589
|
+
with the `$TMUX` socket and server pid, the same `%N`, and the Registry
|
|
590
|
+
Pane's mirrored uid. `prune` has no socket flags and judges on the inherited
|
|
591
|
+
`$TMUX`; `unregister project` judges on its `--socket`/`--socket-path` or
|
|
592
|
+
`$TMUX`. A `ui` deletion is never judged and records an empty actor.
|
|
593
|
+
- A deletion a detached daemon runs (a Codex app-server command, say) is never
|
|
594
|
+
recorded as an Agent's. With no ambient Pane in its environment the actor is
|
|
595
|
+
empty with an empty basis; with an Agent Pane's inherited `TMUX`/`TMUX_PANE`
|
|
596
|
+
but a process that does not descend from that Pane, `basis` is
|
|
597
|
+
`not-pane-descendant` and both uids are empty.
|
|
598
|
+
- **An empty actor does not mean a human ran the deletion.** The record is
|
|
599
|
+
provenance, not authentication.
|
|
600
|
+
- The file is appended with one framed `O_APPEND` write and `fsync`, like the
|
|
601
|
+
termination journal, and is never truncated, rotated, or read back by
|
|
602
|
+
projmux. It is written after the commit and before the result is printed or
|
|
603
|
+
a self-targeted kill is queued. A record that cannot be written never fails
|
|
604
|
+
or changes the delete: stdout and the exit code are unchanged and stderr gets
|
|
605
|
+
exactly one `deletion not recorded: <token>` line, where token is
|
|
606
|
+
`state-dir-unavailable`, `append-failed`, or `operation-id-unavailable`.
|
|
607
|
+
`--dry-run`, a refusal, a declined confirmation, and a failed commit write
|
|
608
|
+
nothing.
|
|
609
|
+
- Non-guarantees: automatic lifecycle teardown (exit reconciliation), internal
|
|
610
|
+
row cleanup (resume handoff, topology replay, initial shell retirement,
|
|
611
|
+
rollback, Project fresh start), and the supervisor's process-exit path write
|
|
612
|
+
no deletion record. A delete issued by a Codex Agent's command runs under the
|
|
613
|
+
app-server rather than below the Pane's process and records
|
|
614
|
+
`not-pane-descendant`. There is no retention policy and no reader CLI.
|
|
455
615
|
- The supervisor resolves its state paths from the pane's own inherited
|
|
456
616
|
environment, which is the tmux **server's** environment rather than the
|
|
457
617
|
environment of the CLI call that created the pane. That is the correct
|
|
@@ -519,7 +679,8 @@ Exit reconciliation and lifecycle projection:
|
|
|
519
679
|
- Abnormal, killed, unknown, whole-host absence, missing/empty server inventory,
|
|
520
680
|
permission failure, foreign Window observation, stale generation, and an
|
|
521
681
|
Agent that now binds a resumed Pane all produce delete-plan zero. They keep the
|
|
522
|
-
retained lifecycle projection and canonical
|
|
682
|
+
retained lifecycle projection and the canonical exact-uid Registry-only
|
|
683
|
+
delete recovery of a paneless Offline or Failed Agent.
|
|
523
684
|
- The closed Agent transition table stays the authority. An Agent that may not
|
|
524
685
|
reach the implied phase keeps its phase, its `paneRef`, and its managed Pane;
|
|
525
686
|
only the evidence is recorded. A refused transition is not a reason to discard
|
|
@@ -582,9 +743,21 @@ Agent provider session ref:
|
|
|
582
743
|
- Codex's turn id is deliberately **not** stored. A turn addresses one turn
|
|
583
744
|
inside the conversation and changes on every hook event, so it is not a
|
|
584
745
|
pointer to the conversation.
|
|
585
|
-
- Transcript **paths** are recorded as the hook reported them
|
|
586
|
-
|
|
587
|
-
|
|
746
|
+
- Transcript **paths** are recorded as the hook reported them, and no
|
|
747
|
+
transcript content is read to fill the ref. The Registry, hook ingest, and
|
|
748
|
+
Agent resume paths (`agent resume`, `agent relaunch`, and the topology replay
|
|
749
|
+
below) never read provider config files, and they read transcript
|
|
750
|
+
**contents** only in these documented places:
|
|
751
|
+
- The three bounded tail readers that
|
|
752
|
+
[hooks.md](hooks.md#claude-code-hook-ingest) lists and scopes: `Stop` hook
|
|
753
|
+
ingest, the held coordination message release, and the Agent's pane
|
|
754
|
+
supervisor. Each reads at most the last 256 KiB of one Claude transcript,
|
|
755
|
+
and none writes what it read to the Registry. `Stop` keeps the last
|
|
756
|
+
assistant text as its notification row's text; hooks.md says where that
|
|
757
|
+
text goes.
|
|
758
|
+
- The explicit, user-run `agent sessions backfill` (see "Agent session
|
|
759
|
+
history (Claude)" below), which reads Claude transcript contents read-only
|
|
760
|
+
and only to attribute past sessions from delivered coordination frames.
|
|
588
761
|
- The field is additive inside `schemaVersion: 1`. It is an optional pointer
|
|
589
762
|
with `omitempty`, so a registry written before it existed decodes with a nil
|
|
590
763
|
ref, validates, and re-encodes byte-identically. Bumping the envelope would
|
|
@@ -606,6 +779,214 @@ Agent provider session ref:
|
|
|
606
779
|
hook whose provider contradicts the Agent's `spec.provider` is refused with
|
|
607
780
|
zero mutations.
|
|
608
781
|
|
|
782
|
+
Agent session history (Claude and Codex):
|
|
783
|
+
|
|
784
|
+
- `status.sessionRef` holds one conversation and is overwritten when an Agent
|
|
785
|
+
moves to another one. The conversations a Claude or Codex Agent left are kept in
|
|
786
|
+
an append-only file outside the Registry,
|
|
787
|
+
`<state>/agent-session-history.jsonl` (next to `deletion-records.jsonl`;
|
|
788
|
+
`${XDG_STATE_HOME:-$HOME/.local/state}/projmux` by default). The Registry
|
|
789
|
+
schema and its version are unchanged.
|
|
790
|
+
- One line is appended each time a committed Registry write binds or replaces
|
|
791
|
+
a Claude session id or Codex thread id. Re-observing the same conversation,
|
|
792
|
+
including a same-thread Codex resume or endpoint handover, appends nothing.
|
|
793
|
+
The writers are a closed set of four functions: `persistAgentSessionRef`
|
|
794
|
+
(hook ingest) and `persistManagedAgentInteractionWithActivationPolicy`
|
|
795
|
+
(managed-Agent interaction commit) in `internal/app/agent_session_ref.go`,
|
|
796
|
+
`openIntentAgent` (resume-picker create and intent native Codex create) in
|
|
797
|
+
`internal/app/create_intent.go`, and `createAgent` (native Codex fresh
|
|
798
|
+
create) in `internal/app/create_agent.go`.
|
|
799
|
+
`TestClaudeSessionRefWritersRecordHistory` pins the callers of
|
|
800
|
+
`RecordAgentSessionRef`, and
|
|
801
|
+
`TestSessionHistoryObservedRowWritersCarryAffiliation` pins the set of four,
|
|
802
|
+
that each builds its row with `sessionhistory.ObservedRecordFor` (directly or
|
|
803
|
+
through `claudeSessionHistoryRecord`) from its transaction's `working`
|
|
804
|
+
Registry, that each reaches a post-commit append helper, and that no other
|
|
805
|
+
code builds an `observed` row with `RecordFor`. The append runs after the
|
|
806
|
+
commit and never fails its caller: a hook logs one `session-history` line to
|
|
807
|
+
`ai-ingest.log`, a create prints one
|
|
808
|
+
`agent session history not recorded: append-failed` line on stderr.
|
|
809
|
+
- Each line is `{"agentUID","provider","sessionId","transcriptPath","observedAt","source"}`:
|
|
810
|
+
`provider` is `claude` or `codex`, `observedAt` is the ref's RFC 3339 UTC observation
|
|
811
|
+
time, and `transcriptPath` is the path the Claude hook reported (empty for
|
|
812
|
+
Codex; never read by the
|
|
813
|
+
writers or by `agent sessions list`). `source` is `observed` for every line
|
|
814
|
+
a writer appends; the read side adds `current` for the Registry's ref; the
|
|
815
|
+
only producer of `estimated` is `agent sessions backfill` (below), whose
|
|
816
|
+
lines add one key, `lastRecordAt`. Lines are one framed `O_APPEND` write under an
|
|
817
|
+
exclusive `flock`, then `fsync`; the file is `0600` in a `0700` directory.
|
|
818
|
+
Readers skip and count an unparsable line.
|
|
819
|
+
- An `observed` line also carries the Agent's affiliation in three trailing
|
|
820
|
+
keys, `projectUID`, `windowUID`, and `agentName`, so a session stays
|
|
821
|
+
attributable after its Agent is deleted from the Registry. The writer
|
|
822
|
+
resolves them inside the same transaction, from that transaction's working
|
|
823
|
+
Registry (`sessionhistory.ResolveAffiliation`), link by link: `agentName`
|
|
824
|
+
when the Agent exists, `windowUID` when its ownerRef is an existing Window,
|
|
825
|
+
`projectUID` when that Window's ownerRef is an existing Project. A key whose
|
|
826
|
+
link does not resolve is omitted; nothing is guessed, and a chain that does
|
|
827
|
+
not resolve never withholds the line or fails the writer. `current` and
|
|
828
|
+
`estimated` rows carry none of the three. When `agent sessions list` merges
|
|
829
|
+
a conversation seen more than once, each of the three is the non-empty value
|
|
830
|
+
of the latest row that carries one.
|
|
831
|
+
- A line `agent sessions attribute` (below) writes adds a fourth key after
|
|
832
|
+
them, `affiliationBasis: "registry"`: its affiliation is the one the current
|
|
833
|
+
Registry gave the Agent when the user ran it, not its writer's transaction.
|
|
834
|
+
No other writer sets the key; an `observed` line without it is recorded at
|
|
835
|
+
write time. Merging carries `affiliationBasis` with `projectUID`: it is the
|
|
836
|
+
basis of the row that supplied the kept `projectUID`.
|
|
837
|
+
- `projmux agent sessions list <agent-ref> [-o json]` and the Go read function
|
|
838
|
+
`sessionhistory.List(stateDir, agent)` return the same rows: history joined
|
|
839
|
+
with the Registry's current ref, one row per `(agentUID, provider, sessionId)` in
|
|
840
|
+
`observedAt` order. A conversation seen more than once keeps its latest
|
|
841
|
+
observation time and transcript path, and is `current` when the Registry
|
|
842
|
+
names it; otherwise it is `observed` if any of its rows is, and `estimated`
|
|
843
|
+
only when every row is, so a backfilled estimate never hides an observation.
|
|
844
|
+
The JSON envelope adds `agentName` and `corruptLines`.
|
|
845
|
+
- `projmux agent sessions project <project-ref> [-o json]` and the Go read
|
|
846
|
+
function `sessionhistory.ListProject(stateDir, registry, projectUID)` list
|
|
847
|
+
the sessions attributed to one exact Project (resolved like any Project
|
|
848
|
+
ref). It is read-only. A Project no longer in the Registry is queried by its
|
|
849
|
+
exact `uid:<uid>` ref, where `<uid>` has exactly the shape projmux mints for
|
|
850
|
+
a Project: the listing then comes from the history rows alone,
|
|
851
|
+
`projectName` is `""`, and the table names the Project `project/uid:<uid>`.
|
|
852
|
+
A name ref, or a `uid:` ref of another kind or shape, resolves against the
|
|
853
|
+
Registry only and fails as before when it matches nothing.
|
|
854
|
+
- Input: every `claude` or `codex` line of the history file, plus one
|
|
855
|
+
`current` row for each Registry Agent with a supported provider.
|
|
856
|
+
- Attribution, per row: a row with a `projectUID` is attributed with its
|
|
857
|
+
own `projectUID`, `windowUID`, and `agentName`, never re-resolved, with
|
|
858
|
+
basis `registry` when its `affiliationBasis` is `registry` (it holds after
|
|
859
|
+
the Agent is deleted, with `inRegistry: false`) and `recorded` otherwise;
|
|
860
|
+
otherwise, a row whose Agent is in the current Registry with a complete
|
|
861
|
+
Agent -> Window -> Project chain is attributed with basis `registry`,
|
|
862
|
+
using the current values; any other row is unattributed. Rows are never
|
|
863
|
+
attributed from deletion records, Registry backups, a transcript's folder
|
|
864
|
+
or cwd, or time proximity.
|
|
865
|
+
- A session is `(provider, sessionId)`. A session none of whose rows is
|
|
866
|
+
attributed counts in `unattributed`; one whose attributed rows name two or
|
|
867
|
+
more Projects counts in `ambiguous` and is listed under none; one naming
|
|
868
|
+
exactly the requested Project is listed. Both counts, and `corruptLines`,
|
|
869
|
+
are over the whole history, not per Project.
|
|
870
|
+
- Each listed session has `provider`, `sessionId`, `transcriptPath`,
|
|
871
|
+
`observedAt`, `lastRecordAt` (when any row has one), and `source`, merged
|
|
872
|
+
over its attributed rows with the `agent sessions list` rules, and
|
|
873
|
+
`agents`: one entry per distinct Agent of those rows, in first-appearance
|
|
874
|
+
order (history file order, then the current rows), with `agentUID`,
|
|
875
|
+
`agentName`, `windowUID`, `inRegistry` (the Agent exists in the current
|
|
876
|
+
Registry), and `basis` (`recorded` wins when an Agent has both). Sessions
|
|
877
|
+
are ordered by `observedAt`.
|
|
878
|
+
- JSON: `{"projectUID","projectName","sessions","unattributed","ambiguous","corruptLines"}`;
|
|
879
|
+
`sessions` is `[]` when empty. The table prints
|
|
880
|
+
`project/<name> has no recorded sessions` when empty, marks an Agent no
|
|
881
|
+
longer in the Registry `(deleted)`, and prints one stderr line for each
|
|
882
|
+
non-zero `corruptLines`, `unattributed`, and `ambiguous` count.
|
|
883
|
+
- `projmux agent sessions backfill [--dry-run] [-o json]` recovers a subset of
|
|
884
|
+
the conversations from before this history existed. It is the one explicit,
|
|
885
|
+
user-run reader of transcript contents. Apart from the bounded tail readers
|
|
886
|
+
that [hooks.md](hooks.md#claude-code-hook-ingest) lists (`Stop` hook ingest,
|
|
887
|
+
the held coordination message release, and the pane supervisor), the
|
|
888
|
+
Registry, hook ingest, Agent resume, and `agent sessions list` never read a
|
|
889
|
+
transcript, and none of those tail readers adds a row to this history.
|
|
890
|
+
- Input: the top-level Claude transcripts
|
|
891
|
+
`${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects/<project>/<session>.jsonl`
|
|
892
|
+
(a blank `CLAUDE_CONFIG_DIR` is unset). Only regular files exactly one
|
|
893
|
+
directory deep are scanned: symlinks, directories, and deeper files such
|
|
894
|
+
as `<session>/subagents/*.jsonl` are not. Files are opened read-only and
|
|
895
|
+
never written; a missing projects directory scans nothing.
|
|
896
|
+
- Attribution: only `type: "user"` records that are not `isSidechain: true`
|
|
897
|
+
count, and within them only a string `message.content` or its
|
|
898
|
+
`type: "text"` blocks; tool results, assistant records, attachments, and
|
|
899
|
+
queue operations can quote a frame and are ignored. At each occurrence of
|
|
900
|
+
`{"kind":"projmux-coordination"` in that text, one JSON value is decoded;
|
|
901
|
+
a frame with that `kind` and a non-empty `target.agentUID` names its
|
|
902
|
+
target, and an occurrence that does not decode is skipped and counted.
|
|
903
|
+
A transcript naming exactly one distinct target Agent is attributed to
|
|
904
|
+
it; none is `noFrame`, two or more is `ambiguous`, and neither is ever
|
|
905
|
+
resolved from the folder name, the working directory, or time proximity.
|
|
906
|
+
- Meaning of `estimated`: the session received a delivered projmux message
|
|
907
|
+
addressed to that Agent. It is a subset of past sessions -- a session that
|
|
908
|
+
never received a message is not recovered -- and an attribution, not an
|
|
909
|
+
observation. `sessionId` is the file name, `transcriptPath` the absolute
|
|
910
|
+
file path, `observedAt` the transcript's first record `timestamp`, and
|
|
911
|
+
`lastRecordAt` its last one (both over every parseable record, RFC 3339
|
|
912
|
+
UTC). A candidate with no parseable timestamp is `unreadable`.
|
|
913
|
+
- Precedence and idempotency: a session already `observed` in the history
|
|
914
|
+
or `current` in the Registry for any Agent is `skippedObserved`; one
|
|
915
|
+
already `estimated` is `alreadyEstimated`; only the rest are appended, as
|
|
916
|
+
framed lines like every other writer's. The run holds the history file's
|
|
917
|
+
exclusive `flock` across the read, the check, and the append (not the
|
|
918
|
+
transcript scan), so concurrent runs cannot add a session twice and a
|
|
919
|
+
second run appends nothing.
|
|
920
|
+
- Report: `scanned`, `attributed`, `alreadyEstimated`, `skippedObserved`,
|
|
921
|
+
`ambiguous`, `noFrame`, `unreadable` (each transcript lands in exactly one
|
|
922
|
+
of these six), `malformedLines` (transcript lines that are not JSON
|
|
923
|
+
objects, skipped), `malformedFrames`, plus `dryRun`, `projectsDir`,
|
|
924
|
+
`historyPath`, and `rows` (the attributed rows; `[]` when none).
|
|
925
|
+
`--dry-run` reports exactly what a real run would append and writes
|
|
926
|
+
nothing, not even the state directory.
|
|
927
|
+
- Removing the estimated rows: with no projmux command running that could
|
|
928
|
+
write the history, keep every other line and swap the file in atomically
|
|
929
|
+
(the blank lines of the framed format are simply dropped):
|
|
930
|
+
|
|
931
|
+
```sh
|
|
932
|
+
h="${XDG_STATE_HOME:-$HOME/.local/state}/projmux/agent-session-history.jsonl"
|
|
933
|
+
jq -c 'select(.source != "estimated")' "$h" > "$h.tmp" && chmod 600 "$h.tmp" && mv "$h.tmp" "$h"
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
A line `jq` cannot parse stops it with an error before the `mv`; readers
|
|
937
|
+
already skip such lines, so remove or repair it first.
|
|
938
|
+
- `projmux agent sessions attribute [--dry-run] [-o json]` persists what the
|
|
939
|
+
current Registry proves about the conversations whose history carries no
|
|
940
|
+
Project, so `agent sessions project` keeps listing them after their Agent
|
|
941
|
+
is deleted. It reads only the history file and the current Registry: never
|
|
942
|
+
deletion records, Registry backups, or transcripts. It persists only what
|
|
943
|
+
the current Registry proves, and never restores an Agent that is no longer
|
|
944
|
+
in it.
|
|
945
|
+
- Input: every `(agentUID, provider, sessionId)` of the file's `claude` and
|
|
946
|
+
`codex` lines, in file order, then each Registry Agent's current ref
|
|
947
|
+
(supported providers only) whose key the file does not name.
|
|
948
|
+
- Precedence: a key with a line that has a `projectUID` is
|
|
949
|
+
`alreadyAttributed`; a key whose Agent is not in the Registry, or whose
|
|
950
|
+
Agent -> Window -> Project chain does not resolve, is `unresolved`; both
|
|
951
|
+
are left untouched. Every other key is `attributed`: one line is appended
|
|
952
|
+
for it, with `observedAt`, `transcriptPath`, and `lastRecordAt` merged
|
|
953
|
+
over its lines and its current ref with the `agent sessions list` rules;
|
|
954
|
+
`source` the highest-ranked source of its lines, or `observed` for a key
|
|
955
|
+
only the Registry names (`current` is never written); `projectUID`,
|
|
956
|
+
`windowUID`, and `agentName` from the current Registry; and
|
|
957
|
+
`affiliationBasis: "registry"`.
|
|
958
|
+
- Report: `scanned` (`attributed` + `alreadyAttributed` + `unresolved`),
|
|
959
|
+
`attributed`, `alreadyAttributed`, `unresolved`, `corruptLines`, plus
|
|
960
|
+
`dryRun`, `historyPath`, and `rows` (the attributed lines; `[]` when
|
|
961
|
+
none).
|
|
962
|
+
- Idempotency and locking: like backfill, a run holds the history file's
|
|
963
|
+
exclusive `flock` across the read, the classification, and one write of
|
|
964
|
+
every framed line, and releases it before the `fsync`. A second run
|
|
965
|
+
appends nothing, because every key it attributed now has a `projectUID`.
|
|
966
|
+
Existing lines are never rewritten. A run with nothing to append creates
|
|
967
|
+
nothing, and `--dry-run` reads without the lock, writes nothing, and
|
|
968
|
+
reports exactly what a real run would append.
|
|
969
|
+
- Undoing it, the same way as removing estimated rows:
|
|
970
|
+
|
|
971
|
+
```sh
|
|
972
|
+
h="${XDG_STATE_HOME:-$HOME/.local/state}/projmux/agent-session-history.jsonl"
|
|
973
|
+
jq -c 'select(.affiliationBasis != "registry")' "$h" > "$h.tmp" && chmod 600 "$h.tmp" && mv "$h.tmp" "$h"
|
|
974
|
+
```
|
|
975
|
+
- Non-guarantees: no session end time is recorded for observed rows;
|
|
976
|
+
conversations from before this history existed appear only as the `current`
|
|
977
|
+
row unless `agent sessions backfill` attributes them; Claude and Codex
|
|
978
|
+
conversation changes are recorded, Antigravity's are not; a line edited or
|
|
979
|
+
deleted by hand is not recovered; the history is kept after the Agent is deleted; the Registry
|
|
980
|
+
commit and the append are not atomic, so a crash between them loses that one
|
|
981
|
+
line. There is no retention policy. Lines written before the affiliation
|
|
982
|
+
keys existed carry none, so once their Agent is deleted they count only in
|
|
983
|
+
`unattributed` unless `agent sessions attribute` persisted their
|
|
984
|
+
affiliation while the Agent was still in the Registry; `agentName` is a
|
|
985
|
+
write-time snapshot and does not follow a later rename; a deleted Project
|
|
986
|
+
can no longer be named by name, only by its exact `uid:` ref, and then only
|
|
987
|
+
the history rows that carry its `projectUID` are listed; `agent sessions
|
|
988
|
+
project` computes no token totals.
|
|
989
|
+
|
|
609
990
|
Agent launch argv (workspace / task boundary):
|
|
610
991
|
|
|
611
992
|
- One Agent launch hands the provider CLI two independent things in a single
|
|
@@ -736,9 +1117,8 @@ Registry file and schema:
|
|
|
736
1117
|
directory-existence and uid adapters.
|
|
737
1118
|
- The canonical anchor is a schema-v2 write invariant. Until the separately
|
|
738
1119
|
planned Project-start projection lands, the legacy `New` startup path's
|
|
739
|
-
prune-to-zero transaction fails validation and commits zero Registry bytes
|
|
740
|
-
|
|
741
|
-
preserves the latest snapshot byte-for-byte and performs no tmux mutation.
|
|
1120
|
+
prune-to-zero transaction fails validation and commits zero Registry bytes
|
|
1121
|
+
and performs no tmux mutation.
|
|
742
1122
|
This fail-closed ordering is not Phase 3 authority to redesign Project start.
|
|
743
1123
|
- Downgrade writes remain unsupported. Unversioned, malformed, and future
|
|
744
1124
|
envelopes still fail closed before backup, staging, or replace.
|
|
@@ -960,6 +1340,10 @@ Resolved resource graph (`internal/core/resourcegraph`):
|
|
|
960
1340
|
first and the inherited `$TMUX` socket path second. There is no implicit
|
|
961
1341
|
default-server probe: with no transport the graph is a Registry-only snapshot
|
|
962
1342
|
whose runtime answers are all `unknown`, and a sibling socket is never read.
|
|
1343
|
+
The Registry views `get` and `describe` add one rung after `$TMUX`: they
|
|
1344
|
+
have no socket flag, so outside tmux they observe the app socket
|
|
1345
|
+
`-L projmux` (see *Runtime observation and resource status*). That is still
|
|
1346
|
+
one exact server, never the default one.
|
|
963
1347
|
- **Bounded and pure.** One observation costs one option probe plus three list
|
|
964
1348
|
queries whatever the size of the server, is memoized for the invocation rather
|
|
965
1349
|
than cached with a TTL — closing a pane must make the *next* command report it
|
|
@@ -967,21 +1351,11 @@ Resolved resource graph (`internal/core/resourcegraph`):
|
|
|
967
1351
|
process, and no tmux, so the same inputs always produce byte-identical output
|
|
968
1352
|
and a read can never materialize state.
|
|
969
1353
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
`uid`, `name`, `labels`, `owner_kind`, and `owner_uid` in the snapshot's own
|
|
976
|
-
snake_case spelling. No schema bump was needed, and a snapshot written
|
|
977
|
-
without resource metadata still serializes byte-identically to the older form.
|
|
978
|
-
- Snapshots written before resource metadata existed still project
|
|
979
|
-
deterministically into an explicitly selected, closed Registry Project:
|
|
980
|
-
existing Windows and Panes are reused positionally in Registry order and any
|
|
981
|
-
additional descendants receive new stable identities. Restore validates a
|
|
982
|
-
pure Project-scoped plan, atomically commits that desired subtree, and only
|
|
983
|
-
then invokes the ordinary Project materializer. It never directly replays
|
|
984
|
-
snapshot topology into tmux and never replaces the global Registry.
|
|
1354
|
+
Saved Project state:
|
|
1355
|
+
|
|
1356
|
+
- The Registry is the only saved Project state. projmux keeps no separate
|
|
1357
|
+
Project snapshot store, and a closed Project starts only from its Registry
|
|
1358
|
+
desired state through the ordinary Project materializer.
|
|
985
1359
|
|
|
986
1360
|
tmux transport mirror:
|
|
987
1361
|
|
|
@@ -1000,7 +1374,7 @@ tmux transport mirror:
|
|
|
1000
1374
|
every other mirror goes through, on the same plain `tmux` transport the session
|
|
1001
1375
|
was created on. The write is gated strictly on "this open registered the
|
|
1002
1376
|
Project": every already-registered Project converges through the Registry
|
|
1003
|
-
topology engine,
|
|
1377
|
+
topology engine,
|
|
1004
1378
|
and opening `$HOME` mints no managed identity at all, so neither writes a mirror
|
|
1005
1379
|
option through this first-open gate. That gate is also what makes repeating an
|
|
1006
1380
|
open write nothing. Repairing a session that is already live without its
|
|
@@ -1062,14 +1436,25 @@ Runtime observation and resource status:
|
|
|
1062
1436
|
live tmux object still mirrors its `@projmux_window_uid` /
|
|
1063
1437
|
`@projmux_pane_uid`. A registry object bound to nothing live is an **orphan**,
|
|
1064
1438
|
and an orphan is not live.
|
|
1065
|
-
- `selector.ObservedStatus(missingRoot, bound)` is the **single**
|
|
1066
|
-
rule in the codebase, and every kind goes through it.
|
|
1067
|
-
everything, then `bound`
|
|
1068
|
-
|
|
1069
|
-
|
|
1439
|
+
- `selector.ObservedStatus(missingRoot, bound, unobserved)` is the **single**
|
|
1440
|
+
derivation rule in the codebase, and every kind goes through it.
|
|
1441
|
+
`missing-root` outranks everything, then `bound` is `live`, then a resource
|
|
1442
|
+
whose observation could not be taken is `unknown`, and only a readable
|
|
1443
|
+
observation with nothing bound is `offline` -- the same precedence the
|
|
1444
|
+
resource graph applies. The MissingRoot precedence contract is unchanged, and
|
|
1445
|
+
it now applies to a Window or Pane whose owning Project lost its root even
|
|
1446
|
+
while tmux is still running them.
|
|
1447
|
+
- `get` and `describe` build their observation from the resolved resource
|
|
1448
|
+
graph, so a row's STATUS and the ACTIONS `registryview` offers for it come
|
|
1449
|
+
from one judgement. An `unknown` row offers only `delete`: it was not seen
|
|
1450
|
+
live, so `open` has nothing to move to, and it was not seen absent, so
|
|
1451
|
+
`start`/`resume` could target something already running.
|
|
1070
1452
|
- A **Project** is the one kind whose runtime object is a tmux *session*, which
|
|
1071
|
-
has no `@projmux` uid of its own
|
|
1072
|
-
|
|
1453
|
+
has no `@projmux` uid of its own. On `get` and `describe` its status is the
|
|
1454
|
+
graph's observation of that session; an identity-only resolver that took no
|
|
1455
|
+
observation (`selector.New`) still reads the stored `status.session`
|
|
1456
|
+
projection (see *Resource metadata model* for its writers) and never reports
|
|
1457
|
+
`unknown`.
|
|
1073
1458
|
- An **Agent** owns no tmux object of its own — there is no `@projmux_agent_uid`
|
|
1074
1459
|
and there must not be one, because an Agent outlives the managed Pane it is
|
|
1075
1460
|
bound to. Its runtime object is **that managed Pane**, named by
|
|
@@ -1096,6 +1481,15 @@ Runtime observation and resource status:
|
|
|
1096
1481
|
exactly that. It is also not a per-route reconcile, because the read verbs
|
|
1097
1482
|
load the registry read-only and must never materialize
|
|
1098
1483
|
`<state>/projmux/metadata/`.
|
|
1484
|
+
- **The server a view observes** is the one its caller is attached to: the
|
|
1485
|
+
inherited absolute `$TMUX` socket, even when that is not the app server.
|
|
1486
|
+
With no `$TMUX` -- another terminal, an IDE, a script -- `get` and `describe`
|
|
1487
|
+
observe the app socket `-L projmux`, the server `create` and
|
|
1488
|
+
`start project` run resources on, so a view outside tmux renders the same
|
|
1489
|
+
rows as the same view inside the app server. An app socket with no server
|
|
1490
|
+
behind it is observed as an absent server: everything reads `offline`, which
|
|
1491
|
+
is what is running there. `get runtime` and `reconcile resources` keep their
|
|
1492
|
+
own rule (flag or `$TMUX`, otherwise no transport).
|
|
1099
1493
|
- A failed inventory query yields an **empty** observation, never a fallback to
|
|
1100
1494
|
a stored value. Empty can only downgrade a resource to offline; it can never
|
|
1101
1495
|
invent a live one, and "nothing is live" is the truthful answer for a machine
|
|
@@ -1284,7 +1678,7 @@ Lifecycle trigger convergence:
|
|
|
1284
1678
|
transaction deletes exactly that Window, its Panes, its owned Agents, and
|
|
1285
1679
|
their reservations. A non-last Project Window reanchors to its existing
|
|
1286
1680
|
sibling. The last Project Window leaves the exact Project uid, root,
|
|
1287
|
-
reservation,
|
|
1681
|
+
reservation, and pins in the valid zero-Window state. A
|
|
1288
1682
|
ControlSession likewise loses only the Window and keeps its root uid.
|
|
1289
1683
|
Abnormal/killed/unknown exits, stale generations, unpaired or foreign handles,
|
|
1290
1684
|
unavailable/empty observations, and missing-server or permission failures
|
|
@@ -1298,11 +1692,9 @@ Lifecycle trigger convergence:
|
|
|
1298
1692
|
managed runtime and preserves every desired UID. Continue on retained-window
|
|
1299
1693
|
writes only runtime and materializes the same descendant UIDs; Continue on
|
|
1300
1694
|
zero-window atomically allocates one canonical Window/shell below the same
|
|
1301
|
-
Project UID before runtime materialization. The deleted+Continue cell
|
|
1302
|
-
|
|
1303
|
-
and
|
|
1304
|
-
the same cell is an unavailable zero-write refusal and never falls back to
|
|
1305
|
-
Fresh. Fresh atomically replaces either
|
|
1695
|
+
Project UID before runtime materialization. The deleted+Continue cell is
|
|
1696
|
+
always an unavailable zero-write `project-is-not-registered` refusal that
|
|
1697
|
+
points to Fresh and never falls back to it. Fresh atomically replaces either
|
|
1306
1698
|
registered state with a new Project/Window/shell UID chain and exactly one
|
|
1307
1699
|
same-root claimant. Within this runtime/startup lifecycle table, canonical
|
|
1308
1700
|
`delete project --yes` alone unregisters the Project graph; the separately
|
|
@@ -1370,6 +1762,25 @@ Projmux split UI:
|
|
|
1370
1762
|
The provider and shell branches render the exact argv an operator would type,
|
|
1371
1763
|
so a UI action and a typed command cannot disagree about what `--placement
|
|
1372
1764
|
down` means.
|
|
1765
|
+
- A split picker running in a popup does not call that route itself. tmux
|
|
1766
|
+
closes a popup only when the process inside it exits, so the picker hands its
|
|
1767
|
+
intent to a detached `run-shell -b` continuation on the same server
|
|
1768
|
+
(`internal agent-pane launch-selection`, carrying the origin Pane, client,
|
|
1769
|
+
and context directory as env) and exits. The continuation
|
|
1770
|
+
calls the same create funnel; a success writes nothing and anything else is
|
|
1771
|
+
one bounded line on the pressing client. Every selection travels: it is a
|
|
1772
|
+
plain value with no live handle left in the picker process.
|
|
1773
|
+
- A generated Window create asks before it commits. It opens the split picker in
|
|
1774
|
+
answer mode (`internal tmux popup-toggle --answer <file>`) on the Pane the key
|
|
1775
|
+
was pressed in; the picker writes the selection -- the same argv the
|
|
1776
|
+
continuation carries, read back by the same parser -- to a 0600 file its
|
|
1777
|
+
producer made, and creates nothing. The producer then commits the Window and
|
|
1778
|
+
the answer in one Registry transaction -- for an Agent, the same shape
|
|
1779
|
+
`create window --provider` commits: the Agent Pane splits off the new shell
|
|
1780
|
+
and the shell is retired -- and moves the pressing client onto it last. An
|
|
1781
|
+
Agent that cannot be opened rolls the whole Window back and leaves one line on
|
|
1782
|
+
the pressing client. A cancelled picker leaves the file empty and nothing is
|
|
1783
|
+
created.
|
|
1373
1784
|
- Only the materializer runs `split-window`. Before this convergence the saved
|
|
1374
1785
|
default and both pickers descended into a legacy split that called tmux
|
|
1375
1786
|
directly, so a pane opened from the UI was a runtime object the Registry had
|
|
@@ -1564,7 +1975,7 @@ a discovery input *and* the only record that a directory mattered. They are five
|
|
|
1564
1975
|
separate authorities now, and the boundaries are the point.
|
|
1565
1976
|
|
|
1566
1977
|
- **Workdirs and project roots are scan roots.** `PROJMUX_MANAGED_ROOTS`,
|
|
1567
|
-
`PROJMUX_PROJDIR` and
|
|
1978
|
+
`PROJMUX_PROJDIR` and `$HOME/.config/projmux/workdirs` name directories to look
|
|
1568
1979
|
inside. Looking inside a directory registers nothing. On Windows they are
|
|
1569
1980
|
OS-native paths and stay OS-native paths; nothing normalizes them into identity.
|
|
1570
1981
|
- **A discovered child is an unregistered candidate.** It is a filesystem fact
|
|
@@ -1681,15 +2092,14 @@ Explicit Registry topology materialization:
|
|
|
1681
2092
|
deletion removes that desire. Exact uid/name/owner mirrors are retained. Stored
|
|
1682
2093
|
Pane CWD drives only that Pane's detached runtime cwd, while Project root
|
|
1683
2094
|
remains the session path anchor and `PROJMUX_CWD` hook value.
|
|
1684
|
-
`Pane.spec.command`,
|
|
2095
|
+
`Pane.spec.command`, notifications, and ephemeral sessions
|
|
1685
2096
|
are never execution inputs.
|
|
1686
2097
|
- An Agent whose managed Pane is not live is replayed into a new managed Pane on
|
|
1687
2098
|
its Window's proven anchor, through the same allocation, activation ledger,
|
|
1688
2099
|
ownership-checked adoption, and rollback the shell half uses. The **only**
|
|
1689
2100
|
replay identifier is Registry `status.sessionRef`: no provider conversation
|
|
1690
2101
|
store is read, `ClaudeSessionRef.TranscriptPath` in particular is never
|
|
1691
|
-
consulted
|
|
1692
|
-
this path. The launch argv comes from the two seams `create agent` already
|
|
2102
|
+
consulted. The launch argv comes from the two seams `create agent` already
|
|
1693
2103
|
owns -- `PlanAgentResume` for a ref that names a conversation, `PlanAgentLaunch`
|
|
1694
2104
|
with no payload otherwise -- so the topology engine holds no launch builder of
|
|
1695
2105
|
its own and the Settings enabled-agents gate still applies. An Agent that
|
|
@@ -1703,14 +2113,7 @@ Explicit Registry topology materialization:
|
|
|
1703
2113
|
the Window from it, and then replays the anchor Agent while preserving the
|
|
1704
2114
|
Agent Pane uid. The default shell is bootstrap, not a replacement anchor. A
|
|
1705
2115
|
successful repeat is a Registry-write-free and topology-write-free no-op.
|
|
1706
|
-
-
|
|
1707
|
-
restore. Metadata-bearing v1 snapshots preserve surviving final-v2
|
|
1708
|
-
anchor/default refs; metadata-free snapshots choose the first Window-local
|
|
1709
|
-
Pane as anchor and the first direct shell as optional default. Agent-only
|
|
1710
|
-
desired Windows remain Agent-anchored and acquire a shell only through the
|
|
1711
|
-
ordinary materializer. Source snapshot bytes and unrelated roots are never
|
|
1712
|
-
rewritten, and a second projection is byte-stable.
|
|
1713
|
-
- `Recreate Project` replaces the exact same-root Project graph, after an
|
|
2116
|
+
- `Clear layout and open` replaces the exact same-root Project graph, after an
|
|
1714
2117
|
explicit confirmation, in one Registry
|
|
1715
2118
|
commit. It always allocates a new Project UID plus one new canonical Window
|
|
1716
2119
|
and direct shell UID, whether the old Project retained Windows or had zero.
|
|
@@ -1784,7 +2187,10 @@ Plan-only runtime mutation boundary:
|
|
|
1784
2187
|
arbitrary Pane as invocation evidence. Generated popup/menu producers pass an exact Pane anchor which is
|
|
1785
2188
|
reobserved on that same socket rather than trusting a targetless current
|
|
1786
2189
|
Pane. Before a stage writes, the same `-S` runner refuses path, generation,
|
|
1787
|
-
class, or containment drift
|
|
2190
|
+
class, or containment drift, and names which of them happened: tmux answers a
|
|
2191
|
+
`display-message -t %N` for a Pane that is gone with exit 0 and blank
|
|
2192
|
+
`$N`/`@N`/`%N` columns, so an absent anchor Pane is reported as absent rather
|
|
2193
|
+
than as drift on a socket and a server generation that both still match. Only a create-session
|
|
1788
2194
|
stage may accept the typed no-server observation, because its explicit route
|
|
1789
2195
|
and absent-session ownership preflight are the facts required to create the
|
|
1790
2196
|
first server.
|
|
@@ -1912,8 +2318,11 @@ Resource-first create:
|
|
|
1912
2318
|
`select-window`, `select-pane`, or `attach-session`. `focus pane` and
|
|
1913
2319
|
`-o pane-id` are how a caller ends up in the new pane. One exception: the
|
|
1914
2320
|
human intent route `internal tmux window-create` (`window.create` key, Window
|
|
1915
|
-
menu New At End)
|
|
1916
|
-
|
|
2321
|
+
menu New At End) asks for the saved launch default before its create
|
|
2322
|
+
commits, fills the committed shell Pane with the answer -- an Agent through
|
|
2323
|
+
the same canonical create funnel a split uses plus a canonical delete of the
|
|
2324
|
+
shell -- and then moves exactly the pressing client to the new Window; public
|
|
2325
|
+
`create` stays detached and never reads that saved default.
|
|
1917
2326
|
- **Focus is navigation-only.** `focus project|window|pane` reads live tmux
|
|
1918
2327
|
inventory and may move an existing client, but has no Registry store and
|
|
1919
2328
|
issues no session/Window/Pane creation, identity-marker, rename, respawn, or
|
|
@@ -1955,8 +2364,8 @@ Selector and the implicit active target:
|
|
|
1955
2364
|
hit. The describe family remains the intentional Project-only difference;
|
|
1956
2365
|
Phase 14 extends only the generic rename family to ControlSession.
|
|
1957
2366
|
`get projects`, `describe|rename project`, `delete`, `rebind`, and `agent
|
|
1958
|
-
resume` are outside that reference scope, and notifications
|
|
1959
|
-
|
|
2367
|
+
resume` are outside that reference scope, and notifications belong to a
|
|
2368
|
+
separate store. Delete's exact live preflight nevertheless follows
|
|
1960
2369
|
either root kind through the selected descendant's owner chain.
|
|
1961
2370
|
- `--all-projects` is the explicit registry-wide escape for those three reads.
|
|
1962
2371
|
It is deliberately different from destructive `delete --all`, whose existing
|
|
@@ -2033,14 +2442,6 @@ Projmux keeps visible naming separate from source metadata:
|
|
|
2033
2442
|
user pane labels.
|
|
2034
2443
|
- **Git branch** belongs in the statusbar git segment. Branch-based terminal
|
|
2035
2444
|
title overwrites are not promoted to the primary Projmux pane or window name.
|
|
2036
|
-
- **Session snapshots** store source metadata separately: `window_name`, raw
|
|
2037
|
-
`pane_title`, user `label`, `@projmux_ai_topic`, manual topic ownership, and
|
|
2038
|
-
agent resume metadata. Old snapshots decode with an absent label and absent
|
|
2039
|
-
ownership; title/topic equality never infers either. Replay writes each
|
|
2040
|
-
semantic field to the exact pane id returned by tmux creation and restores
|
|
2041
|
-
raw title from `Pane.Title` after launch/startup replay. Snapshots do not
|
|
2042
|
-
store a resolved `display_label`; visible labels are recomputed by display
|
|
2043
|
-
policy.
|
|
2044
2445
|
|
|
2045
2446
|
## Notify queue
|
|
2046
2447
|
|
|
@@ -2183,7 +2584,7 @@ The maintained product table in `internal/app/runtime_mutation_surface.go` maps
|
|
|
2183
2584
|
generated catalog/menu producers, native provider/resume picker selections,
|
|
2184
2585
|
sidebar/session-picker stops, and app lifecycle entrypoints in both directions
|
|
2185
2586
|
to their handler and plan verb. It also records exact semantic exemptions for
|
|
2186
|
-
focus, labels, operator-requested layout, mouse forwarding,
|
|
2587
|
+
focus, labels, operator-requested layout, mouse forwarding,
|
|
2187
2588
|
ephemeral maintenance, app quit, and human runtime maintenance. Managed argv
|
|
2188
2589
|
verbs are selected only by the typed executor seam; generated Window
|
|
2189
2590
|
create/rename, Pane-menu create/delete, and automatic post-split layout writes
|