@martintrojer/mu 0.3.1 → 0.3.2

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.
@@ -245,7 +245,7 @@ returning. Three steps, in order:
245
245
  that has no matching `agents` row but whose pane title looks like
246
246
  an agent name, add it to the orphans list. **Do not auto-adopt** —
247
247
  `mu agent list` shows orphans under a separate "(orphans)" section and
248
- the user runs `mu adopt %15 [--name X]` to formally claim them.
248
+ the user runs `mu agent adopt %15 [--name X]` to formally claim them.
249
249
 
250
250
  Full algorithm lives in `src/reconcile.ts` (the canonical
251
251
  implementation).
@@ -282,7 +282,7 @@ Each module is concrete and consumed today.
282
282
  | `src/detect.ts` | Pi-only status detector (`busy` / `needs_input` / `idle` / `done`) |
283
283
  | `src/reconcile.ts` | Ghost prune + status detect + orphan surface; "reality wins" |
284
284
  | `src/agents.ts` | Hub: CRUD + send / read / list / close / free + liveness + reaper. Re-exports `src/agents/*` (spawn, adopt, errors); pane-title composition (`composeAgentTitle`) lives here. |
285
- | `src/agents/*.ts` | Cohesive cluster of agent-lifecycle internals: `spawn.ts` (spawnAgent + resolveCliCommand / awaitSpawnLiveness / pane create-or-reuse / prestage / rollback), `adopt.ts` (register an existing tmux pane as a managed agent), `errors.ts` (typed agent error classes — `AgentNotFoundError`, `AgentDiedOnSpawnError`, …). |
285
+ | `src/agents/*.ts` | Cohesive cluster of agent-lifecycle internals: `spawn.ts` (spawnAgent + resolveCliCommand / awaitSpawnLiveness / pane create-or-reuse / prestage / rollback), `adopt.ts` (register an existing tmux pane as a managed agent), `kick.ts` (signal the foreground pgid of an agent pane's TTY — escape hatch for wedged tool subprocesses), `errors.ts` (typed agent error classes — `AgentNotFoundError`, `AgentDiedOnSpawnError`, …). |
286
286
  | `src/tasks.ts` | Hub: every read/write verb on the DAG (edit / edges / queries) + cycle check + auto-event emission. Re-exports `src/tasks/*` (status, claim, lifecycle, wait, errors). |
287
287
  | `src/tasks/*.ts` | Cohesive cluster of task-graph internals: `status.ts` (TaskStatus enum + helpers — single source of truth), `claim.ts` (claim/release + `resolveActorIdentity`, atomic CAS), `lifecycle.ts` (setTaskStatus / closeTask / openTask / rejectTask / deferTask + cascade), `wait.ts` (waitForTasks: block until tasks reach a target status), `errors.ts` (typed task error classes — `TaskAlreadyOwnedError`, `CycleError`, …). |
288
288
  | `src/tracks.ts` | Parallel-tracks union-find with diamond merge |
@@ -291,7 +291,7 @@ Each module is concrete and consumed today.
291
291
  | `src/importing.ts` | Inverse of `src/exporting.ts`: parses a v0.3 bucket directory and rebuilds every source-ws as live tasks + edges + notes. Markdown-only (never reads .db); per-source-ws transactional; refuses silent merges into existing workstreams |
292
292
  | `src/archives.ts` | Cross-workstream **archives** — feature complete (SDK + 6 CLI verbs: `mu archive create / list / show / add / remove / delete`, plus `search` and `export` via the unified bucket renderer): `createArchive` / `listArchives` / `getArchive` / `deleteArchive` / `addToArchive` (idempotent at `(archive, source_workstream)`) / `removeFromArchive` / `listArchivedTasks`. Backed by the v6 `archives` + `archived_tasks` + `archived_edges` + `archived_notes` + `archived_events` tables; archives outlive workstreams (TEXT `source_workstream` columns, no FK). |
293
293
  | `src/logs.ts` | `agent_logs` SDK: appendLog / listLogs / latestSeq / emitEvent |
294
- | `src/vcs.ts` | `VcsBackend` interface + jj / sl / git / none impls; detection precedence; `commitsBehind(workspacePath, ref)` for staleness signal (no auto-fetch; pure observation) |
294
+ | `src/vcs.ts` | `VcsBackend` interface + jj / sl / git / none impls; detection precedence; `commitsBehind(workspacePath, ref)` for staleness signal (no auto-fetch; pure observation); `isClean(workspacePath)` cheap working-copy probe used by `closeAgent`'s clean-workspace auto-free path |
295
295
  | `src/workspace.ts` | Per-agent VCS workspaces (registry layer on top of vcs.ts); CRUD + cascade; orphan-dir detection (`listWorkspaceOrphans`); staleness decoration (`decorateWithStaleness` populates `commitsBehindMain` per row) |
296
296
  | `src/snapshots.ts` | Whole-DB snapshots (`VACUUM INTO`); auto-captured before destructive verbs; SDK for `mu undo`. The `snapshots` table is schema v4 (carried forward unchanged through v5/v6/v7). |
297
297
  | `src/output.ts` | NextStep type + `printNextSteps` + `errorNextSteps` plumbing for self-documenting output |
@@ -328,7 +328,7 @@ each are deliberately small.
328
328
 
329
329
  | Seam | Add a new impl by... |
330
330
  | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
331
- | `VcsBackend` | Implementing `detect / createWorkspace / freeWorkspace / commitsBehind` (~80–150 LOC; jj/sl/git/none are working examples) |
331
+ | `VcsBackend` | Implementing `detect / createWorkspace / freeWorkspace / isClean / commitsBehind / rebaseTo / commitsSinceBase` (~80–150 LOC; jj/sl/git/none are working examples) |
332
332
  | Per-CLI `Detector` | Adding patterns to `detectPiStatus` (vanilla pi `to interrupt)`; pi-meta + every TUI wrapper covered by Braille spinner glyph fallback `[\u2800-\u28FF]`) |
333
333
  | New typed verb | Add an SDK function in the relevant `src/*.ts`; add a `cmd<Verb>` to the matching `src/cli/<namespace>.ts` (or create a new namespace if the verb doesn't fit existing ones); wire one commander block in `src/cli.ts`'s `buildProgram()` (use `handle()` for the exit-code map; route through `printNextSteps` for self-documenting output) |
334
334
  | New schema migration| Bump `CURRENT_SCHEMA_VERSION` in `src/db.ts`; mirror the new shape in `CURRENT_SCHEMA`. Two of the three post-v5 bumps were script-free: v5 → v6 was purely additive (the existing CREATE-TABLE-IF-NOT-EXISTS pass picked up the new `archive_*` tables), and v6 → v7 was a destructive-but-idempotent in-place migration (a `DROP TABLE IF EXISTS approvals` block in `applySchema`). Reach for a one-shot migration script only when the change can't be expressed that way (the v4 → v5 surrogate-PK substrate switch was the canonical example; restore from git history if you need to see the shape). The loud-fail hook in `openDb` rejects pre-current DBs with `SchemaTooOldError` (exit code 4) and a migration instruction. |
package/docs/ROADMAP.md CHANGED
@@ -115,12 +115,14 @@ if it lands, three rules stay non-negotiable:
115
115
  If those three rules hold, mu stays driveable from a shell forever
116
116
  and the extension stays thin.
117
117
 
118
- ### `mu adopt <pane-id> [--name <agent>]` — SHIPPED in v0.2 (`e20af89`)
119
-
120
- Reconciliation surfaces orphan panes; `mu adopt` formally registers
121
- one of them as a managed agent. Promotion was triggered by the
122
- multi-agent dogfood pattern (orchestrator runs in a pane outside
123
- the `mu-<ws>` session and wants to be claimable as a worker).
118
+ ### `mu agent adopt <pane-id> [--name <agent>]` — SHIPPED in v0.2 (`e20af89`)
119
+
120
+ Reconciliation surfaces orphan panes; `mu agent adopt` formally
121
+ registers one of them as a managed agent. Promotion was triggered
122
+ by the multi-agent dogfood pattern (orchestrator runs in a pane
123
+ outside the `mu-<ws>` session and wants to be claimable as a
124
+ worker). Originally shipped at the top level as `mu adopt`; moved
125
+ under `mu agent` to match every other agent-lifecycle verb.
124
126
 
125
127
  ### Heterogeneous CLI status detection (claude, codex, ...)
126
128
 
@@ -561,9 +561,9 @@ mu agent list -w auth-refactor # surfaces orphans at the bottom
561
561
  # Orphan panes (1)
562
562
  # %15 title=worker-2 cli=pi
563
563
 
564
- mu adopt %15 -w auth-refactor # adopt by pane id
565
- mu adopt worker-2 -w auth-refactor # adopt by pane title (same effect)
566
- mu adopt %15 --name investigator -w auth-refactor # adopt and rename the pane
564
+ mu agent adopt %15 -w auth-refactor # adopt by pane id
565
+ mu agent adopt worker-2 -w auth-refactor # adopt by pane title (same effect)
566
+ mu agent adopt %15 --name investigator -w auth-refactor # adopt and rename the pane
567
567
  ```
568
568
 
569
569
  The pane title becomes the agent name (`mu`'s claim protocol
@@ -737,7 +737,7 @@ If the orchestrator tries `mu task claim some-task` directly:
737
737
  conflict: claimer 'pi-mu' (pane %6441) is not a registered mu agent.
738
738
  Working directly? Pass --self to attribute via log instead.
739
739
  Dispatching to a worker? Pass --for <worker> to assign.
740
- Want full registration? Run: mu adopt %6441
740
+ Want full registration? Run: mu agent adopt %6441
741
741
  ```
742
742
 
743
743
  Three actionable next steps. Pick one based on intent:
@@ -754,7 +754,7 @@ mu task claim some-task --for worker-1
754
754
  # -> tasks.owner = 'worker-1'
755
755
 
756
756
  # Orchestrator wants to BE a registered worker (rare):
757
- mu adopt %6441 -w <ws> # only if pane is in mu-<ws> session
757
+ mu agent adopt %6441 -w <ws> # only if pane is in mu-<ws> session
758
758
  mu task claim some-task # now works as a normal worker claim
759
759
  ```
760
760
 
@@ -790,7 +790,31 @@ mu task note design "DECISION: JWT, 24h expiry, refresh via cookie"
790
790
  mu task note design "FILES: src/auth.rs:45-120"
791
791
  ```
792
792
 
793
- Read them via the SQL escape hatch:
793
+ Read them via the typed verb:
794
+
795
+ ```bash
796
+ mu task notes design # all notes, oldest first
797
+ mu task notes design --tail 3 # only the last 3 (alias --last)
798
+ mu task notes design --since 2026-01-01 # only notes after an ISO 8601 cutoff
799
+ mu task notes design --since-claim # only notes since the most recent
800
+ # 'task claim' event for this task
801
+ # (auto-resolved from agent_logs)
802
+ mu task notes design --tail 5 --json # collection envelope {items, count}
803
+ ```
804
+
805
+ Filters compose: `--tail` slices the last N of whatever survived
806
+ the timestamp filter. `--since` and `--since-claim` are mutually
807
+ exclusive (both define a cutoff) — pick one. With no filters the
808
+ output is unchanged from prior versions (every note, oldest-first).
809
+
810
+ `--since-claim` is the orchestrator-friendly form: dispatch flows
811
+ often drop a multi-screen SPEC note BEFORE claiming, then the
812
+ worker appends progress notes AFTER the claim. `--since-claim`
813
+ slices off the SPEC so you see only the worker's reports. If no
814
+ claim event exists for the task, it degrades to no filter (so the
815
+ verb stays useful on un-claimed tasks).
816
+
817
+ Or, for ad-hoc shape, the SQL escape hatch:
794
818
 
795
819
  ```bash
796
820
  mu sql "SELECT author, content, created_at FROM task_notes WHERE task_id='design' ORDER BY id"
@@ -934,6 +958,10 @@ while (( ${#in_flight[@]} > 0 )); do
934
958
  # overrides. Refuses on dirty WC; conflicts exit 5 with a `cd`
935
959
  # hint to resolve in-place.
936
960
  mu workspace refresh "$worker" -w "$ws"
961
+ # Alt: `mu workspace recreate "$worker" -w "$ws"` does free + create
962
+ # atomically — same shortcut, but throws away the worker's local
963
+ # changes (the lossy escape: requires --force on a dirty WC).
964
+ # Use when you don't care about replaying the worker's commits.
937
965
 
938
966
  # 4. Drop $closed from in_flight, dispatch the next task, repeat.
939
967
  in_flight=( "${in_flight[@]/$closed}" )
@@ -1078,7 +1106,32 @@ Reconciliation runs on every `mu agent list` / `mu`. Three steps:
1078
1106
  3. **Surface orphan panes** — panes in the workstream's tmux session
1079
1107
  whose `pane.command` looks like an agent CLI (pi) but
1080
1108
  that aren't in the registry. **Not** auto-adopted; mu shows them
1081
- under "Orphan panes" and tells you `mu adopt <pane-id>` to register
1109
+ under "Orphan panes" and tells you `mu agent adopt <pane-id>` to register
1110
+
1111
+ ### A worker is wedged on an unbounded tool subprocess
1112
+
1113
+ A worker ran `find / -maxdepth 6 ...` (30-60 minutes on a populated
1114
+ home directory) or a busy-wait loop. `mu agent send` queues steering
1115
+ messages until the tool returns; `tmux send-keys C-c` against the
1116
+ pane doesn't propagate (the wrapping pi/claude/codex CLI catches it
1117
+ as TUI input). The escape hatch:
1118
+
1119
+ ```bash
1120
+ mu agent kick worker-1 # SIGINT (graceful, default)
1121
+ mu agent kick worker-1 --signal SIGTERM # polite escalation
1122
+ mu agent kick worker-1 --signal SIGKILL # hammer
1123
+ ```
1124
+
1125
+ `mu agent kick` looks up the pane's TTY via `tmux display-message
1126
+ -p '#{pane_tty}'`, asks `ps -t <tty>` for the foreground process
1127
+ group (the row whose `stat` field contains `+`), and signals the
1128
+ whole pgrp directly. Refuses with `NoForegroundProcessError` when
1129
+ the foreground IS the wrapping CLI itself — use `mu agent close`
1130
+ to close the agent.
1131
+
1132
+ Prevention: don't prompt workers to run filesystem-wide `find`,
1133
+ broad `grep -r /`, or unbounded busy-wait loops. Pass paths
1134
+ explicitly or scope to `$WORKSPACE`.
1082
1135
 
1083
1136
  ### You closed your terminal session
1084
1137
 
@@ -1219,6 +1272,19 @@ mu agent close reviewer-1
1219
1272
  `mu agent close` is idempotent: `killPane` swallows "pane already gone"
1220
1273
  errors; `deleteAgent` returns false (not throws) on a missing row.
1221
1274
 
1275
+ If the agent has a workspace, behaviour depends on its state:
1276
+
1277
+ - **Clean** (no uncommitted changes AND no commits since fork) — the
1278
+ workspace is silently auto-freed alongside the close, so a
1279
+ `--workspace` spawn that did no real work doesn't make you type
1280
+ `--discard-workspace` just to clean up.
1281
+ - **Dirty** (uncommitted changes OR commits since fork) — close refuses
1282
+ with `WorkspacePreservedError` (exit 4). Two resolutions: (a) `mu
1283
+ workspace free <agent>` first (optionally with `--commit` to capture
1284
+ pending changes), then `mu agent close <agent>`; or (b) `mu agent
1285
+ close <agent> --discard-workspace` to free both in one shot (lossy:
1286
+ any work in the workspace is gone).
1287
+
1222
1288
  ### Tear down the whole workstream
1223
1289
 
1224
1290
  `mu workstream destroy` is the symmetric counterpart of `mu workstream init`: it kills the
@@ -1413,8 +1479,6 @@ Key properties:
1413
1479
  - **Forward edge refs are deferred.** `blocked_by` / `blocks`
1414
1480
  arrays are validated against the bucket's id-set up front, then
1415
1481
  inserted after every task in the source-ws is created.
1416
- - **Pre-0.3 layouts refuse.** Buckets without a `bucketVersion: 2`
1417
- manifest throw `ImportLegacyLayoutError` with a re-export hint.
1418
1482
  - **Partial import.** Multi-source buckets accept either a
1419
1483
  per-source-ws subdir path (auto-detected via
1420
1484
  `README.md` + `INDEX.md` + `tasks/` + a parent
@@ -51,6 +51,7 @@ defined here, fix the doc. If you need a new term, add it here first.
51
51
  | **workspace orphan** | A directory under `<state-dir>/workspaces/<workstream>/` with no row in `vcs_workspaces`. Blocks subsequent `--workspace` spawns. Surfaced by `mu workspace orphans -w X` and `mu state -w X`. | "stray dir", "leftover workspace" |
52
52
  | **stale workspace** | A workspace whose `parent_ref` is N commits behind the project's default branch HEAD (per the workspace's local refs cache). Rendered as a color-coded `behind` column (green ≤2, yellow 3–9, red ≥10) in `mu workspace list` and `mu state`; ≥10 triggers a one-line warn in `mu state`. Pure observation — mu never auto-fetches. | "out of date", "drifting" |
53
53
  | **refresh** | `mu workspace refresh <agent>` — rebase the agent's workspace onto a fresh base (default = backend's tracked main; `--from <ref>` overrides) WITHOUT touching the agent or pane. Refuses on dirty WC; surfaces conflicts as exit 5 with a resolve-in-place hint. The `none` backend errors (no VCS to rebase). | "recycle", "reset" (overloaded) |
54
+ | **recreate** | `mu workspace recreate <agent>` — free + create the agent's workspace in one shot. The between-wave "prep this worker for the next dispatch" verb. Reuses the previous backend unless `--backend` overrides; bases on current main unless `--from <ref>` overrides. Refuses on dirty WC the same way `free` does; `--force` discards the dirty edits (lossy). Sibling of **refresh**: refresh PRESERVES the worker's commits (rebases them onto fresh main); recreate THROWS THEM AWAY. | "recycle", "reset" (overloaded), "free+create" (only in commit messages) |
54
55
  | **backend** | Implementation of `AgentBackend` or `VcsBackend` | "driver", "provider" |
55
56
  | **detector** | Per-CLI pattern matcher for busy/permission/ready. Today mu has one (`detectPiStatus` in `src/detect.ts`); covers vanilla pi + any TUI wrapper that uses Braille spinner glyphs. Other CLIs spawned via `--cli <other>` may misclassify; trust scrollback over the emoji. | "matcher", "parser" |
56
57
  | **snapshot** | A whole-DB backup (`<state-dir>/snapshots/<id>.db`) auto-captured before each destructive verb (workstream destroy, agent close, task close/reject/defer/release/delete, workspace free). Indexed by the `snapshots` table; restore via `mu undo`. | "checkpoint", "backup" |
@@ -69,7 +70,7 @@ defined here, fix the doc. If you need a new term, add it here first.
69
70
  | **substrate** | An external system mu depends on (tmux, jj, sl, git, sqlite) | "dependency" (means npm dep), "service" |
70
71
  | **operation** | A canonical mu verb (e.g. `mu task add`). Each verb is a thin CLI wrapper over a typed function in `src/*.ts` — the SDK and the CLI share one surface. | "command" (overloaded), "action" |
71
72
  | **reconcile** | Verb: re-derive registry rows from substrate reality (tmux). Always runs in `mu agent list` and `mu doctor`. | "sync", "refresh" |
72
- | **adopt** | Verb: register an existing tmux pane as a managed **agent**. The inverse of `mu agent list`'s 'orphan' state. Pane must be in the workstream's tmux session. | "import", "absorb" |
73
+ | **adopt** | Verb (`mu agent adopt`): register an existing tmux pane as a managed **agent**. The inverse of `mu agent list`'s 'orphan' state. Pane must be in the workstream's tmux session. | "import", "absorb" |
73
74
  | **pi-subagents** | A different package by Nico Bailon for in-pi focused delegation. Mu and pi-subagents are complementary, not competing. | conflating with mu |
74
75
 
75
76
  ---
@@ -138,6 +139,7 @@ cache; `mu agent list` reconciles on every call.
138
139
  | `mu agent free alice` | Sets `alice.status = 'free'`. Agent stays alive. Means "I'm done with you for now; you're available." |
139
140
  | `mu release feature_a`| Clears `tasks.owner` for `feature_a`. The agent who claimed it is unaffected. |
140
141
  | `mu agent close alice` | Terminates alice's pane and removes from registry. Destructive. |
142
+ | `mu agent kick alice` | Signals (default SIGINT) the foreground process group of alice's pane TTY. For wedged tool subprocesses (`find /`, busy-wait); the wrapping CLI itself is untouched. Refuses when the foreground IS the wrapping CLI. |
141
143
  | `mu detach alice` | (Future) Tmux-detaches alice's pane without killing the process. Not in v1. |
142
144
 
143
145
  **Don't conflate `free` and `release`.** Free is about the *agent*;
@@ -152,6 +154,7 @@ release is about the *task*.
152
154
  | `mu task claim <task> [--for <agent>]` | Atomic: sets `owner`, flips status to `IN_PROGRESS` |
153
155
  | `mu release <task>` | Clears `owner`. Auto-flips `IN_PROGRESS` → `OPEN` (so the task re-enters the ready set); other statuses preserved. `--reopen` forces `OPEN` from `CLOSED`/`REJECTED`/`DEFERRED` |
154
156
  | `mu task note <task> "..."` | Appends to `task_notes`. Never edits prior notes. |
157
+ | `mu task notes <task> [--tail N \| --since <iso> \| --since-claim]` | List notes (oldest first). `--tail N` (alias `--last N`) prints last N; `--since <iso>` filters by `created_at`; `--since-claim` auto-resolves to the most recent `task claim` event timestamp. `--since` and `--since-claim` are mutually exclusive. |
155
158
 
156
159
  ---
157
160
 
@@ -316,8 +319,8 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
316
319
  | `XDG_STATE_HOME` | Inherited; mu uses `<XDG_STATE_HOME>/mu` by default |
317
320
  | `MU_SEND_DELAY_MS` | Delay between bracketed paste and Enter (default `500`) |
318
321
  | `MU_TMUX_SOCKET` | Override tmux socket (`-L <name>`); default uses `$TMUX` |
319
- | `MU_<UPPER_CLI>_COMMAND` | Override the executable launched for `--cli <cli>` (e.g. `MU_PI_COMMAND=pi-alt` makes `--cli pi` exec `pi-alt`). Accepts multi-word strings (`MU_PI_COMMAND="pi-alt --some-flag"`); tmux exec's via a shell. Reconcile also treats the resolved binary as agent-worthy when surfacing orphan panes. |
320
- | `MU_SPAWN_LIVENESS_MS` | After spawn, wait this many ms then verify the pane is still alive. Default 1500. Set to 0 to disable (useful in CI). On detected death, the DB row is rolled back and `AgentDiedOnSpawnError` is thrown with the captured scrollback. |
322
+ | `MU_<UPPER_CLI>_COMMAND` | Override the executable launched for `--cli <cli>` (e.g. `MU_PI_COMMAND=pi-alt` makes `--cli pi` exec `pi-alt`; hyphens in the cli key become underscores in the env var name, so `--cli pi-meta` reads `MU_PI_META_COMMAND`). Accepts multi-word strings (`MU_PI_COMMAND="pi-alt --some-flag"`); tmux exec's via a shell. Reconcile also treats the resolved binary as agent-worthy when surfacing orphan panes. When this env var supplies the override (and `--command` did not), the spawn-success line surfaces the env-var name (`Spawned worker-1 (pi-meta via $MU_PI_META_COMMAND)`) so stale aliases are visible without `mu agent show`. |
323
+ | `MU_SPAWN_LIVENESS_MS` | After spawn, wait this many ms then verify the pane is still alive AND scan the tail of its scrollback for known startup-error patterns (provider auth failures — `No API key found for X`, `401 Unauthorized`, … — plus shell-level `command not found` / `No such file or directory` when the spawned binary vanished post-pre-flight). Default 1500. Set to 0 to disable (useful in CI). On detected death the DB row is rolled back and `AgentDiedOnSpawnError` is thrown with the captured scrollback; on a startup-error match (pane alive but parked at an error prompt) the row is rolled back and `AgentSpawnStartupError` is thrown with the matched line + remediation hints. The complementary pre-flight check (PATH lookup of `--cli`'s resolved binary BEFORE any side effect) is not env-tunable; on miss it throws `AgentSpawnCliNotFoundError` with no orphan workspace / pane / row. |
321
324
 
322
325
  These mirror pi-subagents' `PI_SUBAGENT_*` env vars in spirit but live
323
326
  in a separate namespace so the two can coexist in one pi session.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martintrojer/mu",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A persistent, observable crew of pi agents running in one tmux session per workstream, coordinated through a built-in task DAG.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -86,8 +86,13 @@ track. Don't spawn more agents than there are tracks.
86
86
  For two agents editing the same project, use `--workspace` on spawn.
87
87
  Each gets an isolated working copy under
88
88
  `<state-dir>/workspaces/<workstream>/<agent>/`. Auto-detects
89
- jj/sl/git; `cp -a` for non-VCS. Workspaces are NOT freed when you
90
- close an agent run `mu workspace free <agent>` explicitly. Between
89
+ jj/sl/git; `cp -a` for non-VCS. Workspaces are auto-freed when
90
+ you close an agent **iff the workspace is clean** (no uncommitted
91
+ changes AND no commits since fork); a non-clean workspace blocks
92
+ `mu agent close` with `WorkspacePreservedError` and forces an
93
+ explicit `mu workspace free <agent>` (or
94
+ `mu agent close <agent> --discard-workspace` for the lossy override).
95
+ Between
91
96
  waves, `mu workspace refresh <agent>` rebases the dir onto fresh
92
97
  main without killing the agent's LLM context; `mu workspace commits
93
98
  <agent>` lists since-fork commits for cherry-picking.
@@ -222,6 +227,16 @@ running a wave.
222
227
  un-closing CLOSED/REJECTED/DEFERRED). Tunable via
223
228
  `MU_IDLE_THRESHOLD_MS` (default 5 min).
224
229
 
230
+ - **Wedged on an unbounded tool subprocess (`find /`, busy-wait
231
+ loop)** — `mu agent send` queues until the tool returns;
232
+ `tmux send-keys C-c` doesn't propagate (the wrapping CLI eats
233
+ it as TUI input). Use `mu agent kick <name>` to SIGINT the
234
+ foreground process group of the pane's TTY directly from
235
+ outside. Default `--signal SIGINT` is graceful; escalate to
236
+ `--signal SIGTERM` then `--signal SIGKILL` if the tool
237
+ ignores it. Refuses when the foreground IS the wrapping CLI
238
+ itself — use `mu agent close` for that.
239
+
225
240
  ## Parallelisation decision table
226
241
 
227
242
  | Situation | Action |
@@ -259,10 +274,13 @@ running a wave.
259
274
  `--archive <label>` to preserve graph atomically), `export`,
260
275
  `import`.
261
276
  - **Agents**: `spawn` (`--workspace`, `--role read-only`,
262
- `--command`), `send`, `read`, `show`, `list`, `close`, `free`.
263
- **`mu adopt <pane-id|title>`** registers an orphan pane as a
264
- managed agent.
265
- - **Tasks**: `add`, `list`, `next`, `show`, `tree`, `notes`, `note`,
277
+ `--command`), `send`, `read`, `show`, `list`, `close`, `free`,
278
+ `kick` (signal a wedged foreground tool subprocess from outside
279
+ the pane). **`mu agent adopt <pane-id|title>`** registers an
280
+ orphan pane as a managed agent.
281
+ - **Tasks**: `add`, `list`, `next`, `show`, `tree`, `notes`
282
+ (`--tail N` / `--since <iso>` / `--since-claim` to slice the
283
+ timeline; default = every note, oldest first), `note`,
266
284
  `claim` (`--for | --self`), `release` (`--reopen` to un-close),
267
285
  `close` (`--if-ready` = no-op unless every blocker terminal),
268
286
  `open`, `reject`, `defer`, `block`, `unblock`, `update`,
@@ -270,9 +288,11 @@ running a wave.
270
288
  Edge direction: `block <blocked> --by <blocker>`.
271
289
  - **Self (in-pane)**: `mu me`, `mu me tasks`, `mu me next`.
272
290
  - **Workspace**: `create`, `list` (`behind` column), `refresh`
273
- (rebase onto fresh base, agent stays alive), `commits` (since-fork
274
- `<sha> <subject>`; `--json` for piping), `free`, `path`
275
- (`cd $(mu workspace path X)`), `orphans`.
291
+ (rebase onto fresh base, agent stays alive), `recreate` (free +
292
+ create in one shot for between-wave prep; `--force` to discard
293
+ dirty edits), `commits` (since-fork `<sha> <subject>`; `--json`
294
+ for piping), `free`, `path` (`cd $(mu workspace path X)`),
295
+ `orphans`.
276
296
  - **Activity log**: `mu log "text"` (write), `mu log -n N` (read),
277
297
  `mu log --tail` (subscribe). Don't pipe `--tail` for waits — use
278
298
  `mu task wait`.
@@ -361,10 +381,12 @@ IDs auto-derive from titles via slugify.
361
381
  # Wait for any-of N to close, cherry-pick that one, return.
362
382
  res=$(mu task wait t1 t2 t3 -w ws --any --first --json \
363
383
  --timeout 600 --on-stall exit)
364
- firing=$(jq -r .firing.qualifiedId <<<"$res")
365
- sha=$(mu workspace commits $worker -w ws --json \
366
- | jq -r --arg id "${firing#*/}" \
367
- '.items[] | select(.subject | startswith($id+":")) | .sha' | head -1)
384
+ worker=$(jq -r .firing.owner <<<"$res")
385
+ # Workers run in fresh single-purpose workspaces, so HEAD is the
386
+ # task's commit. Don't filter on subject — workers don't prefix
387
+ # subjects with the task-id, and any such filter silently returns
388
+ # empty (then `git cherry-pick` fails with "empty commit set").
389
+ sha=$(mu workspace commits $worker -w ws --json | jq -r '.items[0].sha')
368
390
  git cherry-pick $sha && cargo test --lib
369
391
  # Next turn: same wait on the smaller set.
370
392
  ```
@@ -439,11 +461,11 @@ Verbs auto-resolve via `$TMUX_PANE` — `mu me`, `mu me next`,
439
461
  (set at spawn) IS the agent identity.
440
462
 
441
463
  - **Worker**: pane was created by `mu agent spawn` (or promoted via
442
- `mu adopt`). Bare `mu task claim <id>` works.
464
+ `mu agent adopt`). Bare `mu task claim <id>` works.
443
465
  - **Orchestrator**: a top-level pi session NOT in `agents`. Bare
444
466
  `mu task claim` errors with `ClaimerNotRegisteredError` whose
445
467
  `errorNextSteps()` lists three options: `--self` (work directly,
446
- owner=NULL), `--for <worker>` (dispatch), or `mu adopt <pane>`.
468
+ owner=NULL), `--for <worker>` (dispatch), or `mu agent adopt <pane>`.
447
469
 
448
470
  Working loop:
449
471
 
@@ -504,6 +526,11 @@ orchestrator's `mu task wait` hang.
504
526
  - **Don't use the `mu_` task-id prefix.** Reserved.
505
527
  - **Don't message agents directly.** Coordinate via task notes and
506
528
  the activity log.
529
+ - **Don't prompt workers to run filesystem-wide `find`, broad
530
+ `grep -r /`, or unbounded busy-wait loops.** Pass paths
531
+ explicitly or scope to `$WORKSPACE`. If a worker wedges on one,
532
+ use `mu agent kick <name>` to SIGINT the foreground tool
533
+ from outside the pane.
507
534
 
508
535
  ## What mu is NOT
509
536