@mu-crew/mu 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-5HECDBK3.js → chunk-4ULY3ZKO.js} +16 -16
- package/dist/{chunk-AW2NONYU.js → chunk-7M634JMD.js} +81 -7
- package/dist/chunk-7M634JMD.js.map +1 -0
- package/dist/{chunk-GQNHIPJN.js → chunk-SS7NQSSZ.js} +21 -421
- package/dist/chunk-SS7NQSSZ.js.map +1 -0
- package/dist/{chunk-S424D5EP.js → chunk-UCZPJX7K.js} +628 -141
- package/dist/chunk-UCZPJX7K.js.map +1 -0
- package/dist/{cli-GB2CAVVW.js → cli-TV3N47AZ.js} +340 -42
- package/dist/cli-TV3N47AZ.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{errors-KHOYKD7D.js → errors-GRONKWFS.js} +2 -2
- package/dist/extension/mu-pi.js +28 -14
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +63 -11
- package/dist/index.js +15 -15
- package/dist/{tui-ALPHYTA2.js → tui-FUBQ326D.js} +102 -27
- package/dist/tui-FUBQ326D.js.map +1 -0
- package/docs/ARCHITECTURE.md +5 -4
- package/docs/VOCABULARY.md +6 -5
- package/docs/guide/cleanup.md +8 -0
- package/docs/guide/delegate.md +11 -3
- package/docs/guide/remote.md +19 -12
- package/docs/reference/env.md +1 -0
- package/docs/reference/naming.md +2 -0
- package/package.json +1 -1
- package/skills/mu/recipes/remote-workers.md +7 -6
- package/dist/chunk-AW2NONYU.js.map +0 -1
- package/dist/chunk-GQNHIPJN.js.map +0 -1
- package/dist/chunk-S424D5EP.js.map +0 -1
- package/dist/cli-GB2CAVVW.js.map +0 -1
- package/dist/tui-ALPHYTA2.js.map +0 -1
- /package/dist/{chunk-5HECDBK3.js.map → chunk-4ULY3ZKO.js.map} +0 -0
- /package/dist/{errors-KHOYKD7D.js.map → errors-GRONKWFS.js.map} +0 -0
package/docs/VOCABULARY.md
CHANGED
|
@@ -34,7 +34,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
|
|
|
34
34
|
| --- | --- | --- |
|
|
35
35
|
| **agent** | A named process in a pane with a row in `agents`. Identity is `$MU_AGENT_NAME`; mu never reads or writes pane titles. | "subagent", "worker" (a role) |
|
|
36
36
|
| **worker** | An agent in its role as task claimer. Prefer **agent** when ambiguous. | |
|
|
37
|
-
| **remote worker** | An agent spawned with `--host <profile>`: its pane is local and is the transport (the profile's `shell` template runs there and forwards the remote pi's control socket), its process is remote, and its host, remote cwd and
|
|
37
|
+
| **remote worker** | An agent spawned with `--host <profile>`: its pane is local and is the transport (the profile's `shell` template runs there and forwards the remote pi's control socket), its process is remote, and its host, remote cwd, base and (with `--worktree`) remote repo are `agents.host/cwd/base/repo`; the repo is chosen per spawn (`--root`, else the profile's default). The pane dying is the agent dying. | "REMOTE: note", "remote-env" | "remote agent record", "host registry" |
|
|
38
38
|
| **command gate** | A remote spawn of a non-pi command (`spawn --host dev --command 'npm test'`): a foreground pane, not a worker. Its `agents.gate_token` marks it and keys its remote completion record; `mu wait` returns the command's own numeric exit (event `exited`, `data.exit`). The pane is retained after the command ends until `agent close`. | "remote job", "gate worker" |
|
|
39
39
|
| **collect** | `mu agent collect <agent>`: run a remote worker's `--fetch` template locally and pin the fetched commit as `refs/mu/collect/<ws>/<agent>`; the commit is recorded as `agents.collected_head`, and `agent close` accepts a remote worktree at its base or that HEAD. mu prints the merge, never runs it. | "pull", "sync" (that is `mu sync`) |
|
|
40
40
|
| **delivery unknown** | A send/fresh/interrupt/command request was written to a live agent's control socket but no reply arrived (`AgentSendIndeterminateError`). pi may have acted; read the pane before resending. | "refused" (the request never went out) |
|
|
@@ -43,11 +43,12 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
|
|
|
43
43
|
| **persistent** | An agent that stays alive across tasks. | "long-lived" (prose only) |
|
|
44
44
|
| **one-shot** | An agent that exists for one task. | "ephemeral", "transient" |
|
|
45
45
|
| **nudge** | A pi extension continuation at settle. The keep-driving one is the **settle wait**: every completed settle of an orchestrator runs `mu wait` as its callers and continues the turn with the event, until nothing is owed, a `mu human-needed` stop is open, or the user cancels. The close nudge tells a worker once per prompt to close the task it still owns (never a delegate's own `kind=delegate` task: its call closes it). | "goal", "loop" |
|
|
46
|
-
| **delegate** | A scratch agent started for one check. Its call is a `delegate`-kind task; its answer is that task's report, and the settle wait shows it to pi (a wave's as one **wave summary**). The `mu_delegate` tool does it in one call; the pane stays attachable. With no tmux or herdr the tool runs it as a **delegate process** instead (no pane). | "subagent", "child agent" |
|
|
46
|
+
| **delegate** | A scratch agent started for one check. Its call is a `delegate`-kind task; its answer is that task's report, and the settle wait shows it to pi (a wave's as one **wave summary**). The `mu_delegate` tool does it in one call; the pane stays attachable, and its pi session is kept under `<DB dir>/delegate-sessions/`, out of the caller's `/resume` list. With no tmux or herdr the tool runs it as a **delegate process** instead (no pane). | "subagent", "child agent" |
|
|
47
47
|
| **wake** | The one pi message the delegate side sends for a recorded answer when no settle wait will show it (pi idle, `MU_NUDGE=0`): a pointer per answer (`mu task notes <task> -w <ws>`), no answer text, that starts a turn whose settle shows the report. Losing or repeating one loses or repeats nothing. | "echo", "follow-up answer" |
|
|
48
48
|
| **wave summary** | What the settle wait shows for a delegate of a wave: it waits while siblings run (never on refused ones), then ONE entry with counts per verdict, a line per call and the answers, instead of one per call. | "wave echo" |
|
|
49
49
|
| **envelope** | The one JSON document a `--json` invocation prints on stdout: `{ok:true,data,next}`, or on failure `{ok:false,error,message,next,exit}` with the real non-zero exit (`src/output-contract.ts`). A failed verb that already produced evidence (a spawn whose `--send` failed, a `doctor` report) carries it in `data`. `mu log --tail` is always NDJSON (one item per line), never enveloped. | "wrapper", "result object" |
|
|
50
50
|
| **delegate call** | Using a delegate for one check (a refuter, judge, claim checker): one `mu_delegate` call, or `mu agent spawn -w scratch` + `send --fresh` + `wait --json` without the tool. A call is a task (see **task kind**) with a `call_id` and a state (`queued starting running recording done failed refused cancelled`, `mu delegates`). Its verdict is recorded on the task it judged, then the call's own report and close. Admission control (`MU_DELEGATE_MAX` running, a bounded queue) stays; a call past it is **refused**: durable, parked, reissuable, never dropped. `mu next --refute` makes one per closed attempt (call id `refute:<ws>:<task>:<attempt key>`, wave `refute-<ws>`), launched and recorded by the executor itself. | "subtask" |
|
|
51
|
+
| **delegate prune** | `mu delegates prune`: delete closed **delegate call** tasks older than an age (default 30 days) that this machine's calls made, with their notes, call rows and puts in `ops` (with sync on, only peers' puts: this machine's own rebuild its segment); the task tombstones (one `delegate.prune` op group) sync, so peers drop the rows too. Keeps a task with edges and a `refute:` check of a task with `mu task auto` enabled. The pruned calls' pi session files go too (`delegate-sessions/<ws>/<task>/`, `delegate-process/<hash>/`, and loose first-layout `delegate-sessions/<ws>/*.jsonl` past the age). Dry run by default; `--yes` backs up, VACUUMs and checks drift. `mu doctor` housekeeping reports the candidates as `delegates-old`. | "gc", "cleanup" |
|
|
51
52
|
| **delegate process** | The mux-free way to run a **delegate call** (`mu delegates run <call-id>`, `src/delegate-process.ts`): one child `pi --mode rpc` with its own ctl socket, log and session, no pane and no agents row (`transport: "process"`, `attach: null`). The call and its task stay the authority. Its **attempt receipt** (`<state dir>/delegate-process/<hash>/receipt.json`, machine-local) marks that a child may have run: a call with one left behind finishes `unknown`, never respawned or resent. | "headless agent", "background agent" |
|
|
52
53
|
| **hidden subagent** | A child agent in other tools (pi-subagents, Claude Code and Codex task tools) with no pane to attach or steer. mu's counterpart is the **delegate**. | "subagent" (alone) |
|
|
53
54
|
| **agent state** | `busy`, `needs_input`, `needs_permission`, or `unknown`, from the **state source**. Not persisted; see [Agent state values](#agent-state-values). | "agent status", "lifecycle state" |
|
|
@@ -186,7 +187,7 @@ Terms the [4.0 contract](reference/surface-contract.md) added.
|
|
|
186
187
|
| **wave** | `tasks.wave`: a label grouping the delegate calls of one fan-out so one wait returns one verdict tally. Not a **merge batch**. | "batch" |
|
|
187
188
|
| **call_id** | The durable id of one `mu_delegate` call (`delegate_calls.call_id`), session-qualified: `pi:<session>:<n>`. A repeat of the same call is deduplicated by it; a changed request under it is refused. Not a **msgId**. | "request id" |
|
|
188
189
|
| **attempt key** | Names one task-bound spawn attempt with its task: `agent spawn --task <t> [--attempt <k>]` is the key `manual:<k>` (default `manual:<task id>`). The same key replays its saved outcome and never sends a second prompt; a changed request under it conflicts (exit 4); reopened work takes a new key. The executor's authorised attempts are `auto:<n>` (n = the run it charged) with their latest record in `task_auto.last_spawn`: a different namespace. An `auto` attempt is reserved (and its **run** charged) before any effect, and a failed one is never reset: the next attempt is a new key. The record stores the exact prompt: a replay sends those bytes, never a rebuild. An attempt that cannot be reconciled (task closed, agent gone, UNKNOWN the one probe cannot confirm) is ended on the first `mu next` that sees it: `abandoned` (nothing sent) or `needs_human` (it may have landed: never resent, a human need is recorded); an ended attempt is never replayed or counted again. | "nonce" |
|
|
189
|
-
| **remote recovery** | `remote_recovery`: one machine-local row per ORIGINAL registry incarnation (`agents.id`) of a finalized remote worker (host, worktree path and base all recorded), written by an AFTER DELETE trigger on `agents` (and a BEFORE DELETE one on `workstreams` for the cascade; `src/db-recovery.ts`) in the transaction of ANY delete of its row: the reaper (`reap`, also raw SQL), workstream teardown (`teardown`) and a workstream deletion cascade (`workstream-delete`). It keeps the host, path, base, last collected HEAD, the exact aside path (`asideOf(cwd, agents.id)`, never derived from a later id), the profile's exec
|
|
190
|
+
| **remote recovery** | `remote_recovery`: one machine-local row per ORIGINAL registry incarnation (`agents.id`) of a finalized remote worker (host, worktree path and base all recorded), written by an AFTER DELETE trigger on `agents` (and a BEFORE DELETE one on `workstreams` for the cascade; `src/db-recovery.ts`) in the transaction of ANY delete of its row: the reaper (`reap`, also raw SQL), workstream teardown (`teardown`) and a workstream deletion cascade (`workstream-delete`). It keeps the host, path, base, last collected HEAD, the exact aside path (`asideOf(cwd, agents.id)`, never derived from a later id), the profile's exec when it existed and the worktree's own repo (`agents.repo`), and why. No foreign key (it outlives the agent, its workstream and its host profile), no op, no sync; `mu rebuild` reports it lost. A record of a resource, not a live channel or an agent. `agent close` that itself removed the tree writes none, nor does a failed spawn whose rollback removed it (or found it gone). `agent close <name>` / `agent collect <name>` read it back when no agent holds the name (`--recovery <id>` = the original incarnation, picks one of several; the preview's `kind` is `recovery`, its `recovery_id` is never the registry `id`); a current agent, a shared path (a live agent, or a NEWER record of the same host and path), or a changed profile refuses; a `--recovery <id>` that is gone is never read as the live agent, and only that record is deleted, only once the tree is removed, kept on a branch or gone. | "tombstone", "graveyard" |
|
|
190
191
|
| **dead staging** | A `--worktree` spawn's placeholder row (pane `%pending-<name>`) left for over 60 s by a spawn killed before its pane (SIGKILL, crash): no pane ever ran in its worktree. `mu doctor` lists it (`ws-staging`); the next spawn in the workstream removes the row and its unused worktree. SIGINT/SIGTERM roll the spawn back and leave none. | "orphan", "zombie spawn" |
|
|
191
192
|
| **spawn receipt** | `spawn_receipts`: one machine-local row per (task, attempt key), written BEFORE each effect: `reserved`, `pane` (agent row + pane exist), `claimed`, `sending` (the prompt may be written: never resend), then `sent` or `unknown`; `failed` is definitely unsent and rolled back. It stores the registry id (`agents.id`) and pane with no foreign key, so a replay after the agent was deleted or the name reused says `stale` and never acts on the replacement; the send re-checks that id + pane and the claim just before the write. | "journal row", "ticket" |
|
|
192
193
|
| **delivery unknown** | A prompt that may or may not have landed (written, no reply; a pane that died after the write; a pane paste). Never resent and never reaped: the pane and the claim stay, `mu agent check` asks the extension, and an id the extension no longer remembers (it keeps the last 64) is still unknown, not unsent. | "failed send" |
|
|
@@ -223,13 +224,13 @@ Terms the [4.0 contract](reference/surface-contract.md) added.
|
|
|
223
224
|
| **worker ceiling** | `mu next --workers <n>`: the most workers WORKING at once (an IN_PROGRESS task or an attempt in flight). An idle worker whose task is CLOSED does not count. Enforced where a pane would be created; never holds back an in-place retry. | "concurrency", "slots" |
|
|
224
225
|
| **hold** | `mu next --hold`: after dispatching, wait (bounded) for the first event on the in-flight workers; a death or an API error is recovered and the wait goes on, anything else is returned. | "watch mode", "daemon" |
|
|
225
226
|
| **brief (executor)** | `buildTaskBrief`: the prompt of one attempt, built from the task's first `brief` note and latest `report` note, stored verbatim with the reservation (a replay never rebuilds it). Never re-quotes a previous prompt. | "template" |
|
|
226
|
-
| **host profile** | A machine-local template (`shell`, `exec`, `fetch`, `
|
|
227
|
+
| **host profile** | A machine-local template (`shell`, `exec`, `fetch`, `max_sessions`, an optional default `repo`) for one remote machine (never one per repo: `max_sessions` caps the machine), filled by mu and run with `/bin/sh -c` (`exec`, `fetch`) or as the local pane's command (`shell`); mu never connects. Selected by `spawn --host`. Not the **dispatch host**. | "remote config" |
|
|
227
228
|
| **host template** | One field of a host profile (`shell`, `exec`, `fetch`) with `{env}`, `{cmd}`, `{local_sock}`, `{remote_sock}`, `{cwd}` placeholders. Each value is one shell-quoted word (inside your own quotes, the value itself); `{name:sh}` adds a second-shell quoting layer; `{env}` is whitelisted `KEY='value'` words; `{{` `}}` are literal braces; anything else in braces is refused. | "ssh command" |
|
|
228
229
|
| **host channel** | One connection slot of a host (an attached pane, `--exec`, `--fetch` or `host check`) counted against `max_sessions`: one `host_channels` row per holder. **Transient** rides one invocation (reclaimed only when its pid is proven gone and its template's process group, recorded before it runs, is gone too); **attached** rides an agent's registry id and pane: `spawn --host` reserves it before the pane opens, binds it to the new id + pane, and `deleteAgent` frees it (reclaimed otherwise only when the pane is proven dead). A refusal is one line naming the holder. | "session" |
|
|
229
230
|
| **collected HEAD** | A remote worktree's HEAD as last fetched home (`agents.collected_head`, set by `mu agent collect`; a **remote recovery** record keeps it after the row is gone). A remote close needs HEAD equal to the base or this. | "synced commit" |
|
|
230
231
|
| **identity trio** | Three unrelated identities: **spawner** (who started an agent), **dispatch host** (which machine may dispatch a task), **revision** (`workstreamRevision`, a preview fence). Do not substitute one for another. | |
|
|
231
232
|
| **dashboard** | The human-only read-only five-tab view (Overview, the default card grid; Tasks, DAG, Agents, Log) over a workstream row and the attention strip (on every view, drills included), opened by bare `mu` on a TTY. Enter drills a row (task notes, agent scrollback, one log op); `y` copies the row's command; only `a` on an agent runs anything (the mux's attach steps, falling back to the scrollback with the reason; from a client of another tmux server it nests on purpose and says `C-b C-b d`). Replaces the removed card TUI. Every view is live on one poll loop (3.x's tiers: a fast SQL tick, 1 s by default and set with `+`/`-`, and a 10 s slow tick for agent state and scrollback); `r` only nudges. Colours are the 3.x palette as named theme roles (`src/cli/tui/theme.ts`; NO_COLOR turns them off). Its read model is `loadDashboard` (`src/dashboard-data.ts`); its Next line is `computeNext` for the caller, "unavailable" (never "no authorised work") only when that fails. | "home screen" |
|
|
232
|
-
| **attention item** | One line of the dashboard's trouble list (`DashboardSnapshot.attention`), read over every live workstream regardless of filters: `dead` (pane gone), `permission`, `stalled` (one tool over 15 min), `input` (the agent's `idle` flag), `human` (an OPEN `kind=human` task, any recorder), `next_unavailable`, `sync` (the latest ingest's warnings, until an ingest comes back clean). Not **needs attention**, which is the `task wait` stall. | "alert", "notification" |
|
|
233
|
+
| **attention item** | One line of the dashboard's trouble list (`DashboardSnapshot.attention`), read over every live workstream regardless of filters: `dead` (pane gone), `permission`, `stalled` (one tool over 15 min), `input` (the agent's `idle` flag), `delegate` (a refused **delegate call**, which stays until reissued or cancelled from pi, or one failed in the last 24 h; any caller), `human` (an OPEN `kind=human` task, any recorder), `next_unavailable`, `sync` (the latest ingest's warnings, until an ingest comes back clean). Not **needs attention**, which is the `task wait` stall. | "alert", "notification" |
|
|
233
234
|
|
|
234
235
|
<!-- doc-cli-drift:skip-end -->
|
|
235
236
|
|
package/docs/guide/cleanup.md
CHANGED
|
@@ -110,6 +110,14 @@ It skips your current workstream, `scratch`, and any workstream with a
|
|
|
110
110
|
live agent or a worktree. Teardown hides the plan, not the code: no
|
|
111
111
|
commit or checkout is touched.
|
|
112
112
|
|
|
113
|
+
The same section counts closed delegate tasks older than 30 days
|
|
114
|
+
(`delegates-old`). Prune them:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mu delegates prune # dry run: what goes, and what is kept
|
|
118
|
+
mu delegates prune --yes # backs up beside the DB first; irreversible
|
|
119
|
+
```
|
|
120
|
+
|
|
113
121
|
## Free disk space
|
|
114
122
|
|
|
115
123
|
```bash
|
package/docs/guide/delegate.md
CHANGED
|
@@ -21,7 +21,7 @@ parallel.
|
|
|
21
21
|
| `label` | Names the delegate: `review` gives `delegate-review`. |
|
|
22
22
|
| `brief` | Ad hoc persona or ground rules, sent first. Not enforced. |
|
|
23
23
|
| `cwd` | Start directory. Default: the caller's. Must exist. |
|
|
24
|
-
| `timeout` | Seconds to wait for the answer (default 3600). After it, the answer does not come back to pi; it still lands on the delegate task, whose report names a wait command. |
|
|
24
|
+
| `timeout` | Seconds to wait for the answer (default 3600), counted from the start: a `/reload` does not restart it. After it, the answer does not come back to pi; it still lands on the delegate task, whose report names a wait command. |
|
|
25
25
|
| `workspace` | Own git worktree, for delegates that edit files. The tool result names its path. It isolates repository edits only. |
|
|
26
26
|
| `cli` | Key for `$MU_<CLI>_COMMAND` (default `pi`). Only keys set in pi's environment exist. |
|
|
27
27
|
| `model` | pi `--model` value, appended to the cli's command (`sonnet`, `<provider>/<model>:high`, copied from `pi --list-models`). Varies models across a panel. tmux only: herdr refuses a command override. |
|
|
@@ -81,6 +81,8 @@ parallel.
|
|
|
81
81
|
when it wrote anything, its last message so far, in the report.
|
|
82
82
|
Quitting pi or `/reload` lists each running delegate's session file.
|
|
83
83
|
A call whose pane is gone fails when the next runtime re-adopts it.
|
|
84
|
+
- Delegate pi sessions live under `<state dir>/delegate-sessions/<workstream>/<task>/`,
|
|
85
|
+
not in your `/resume` list. `mu delegates prune` removes them with their calls.
|
|
84
86
|
|
|
85
87
|
`MU_DELEGATE=0` hides the tool ([env vars](../reference/env.md)).
|
|
86
88
|
|
|
@@ -230,8 +232,14 @@ Use delegates when you only need the answer: reviewing someone's PR or
|
|
|
230
232
|
a doc, a fact-check, a go/no-go. Findings stay in the answers and
|
|
231
233
|
never enter your workstream's task graph. Each call's prompt and
|
|
232
234
|
answer are a `scratch` task, which is in the ops log and syncs like any
|
|
233
|
-
task
|
|
234
|
-
|
|
235
|
+
task. They pile up: `mu delegates prune` deletes the closed ones older
|
|
236
|
+
than 30 days (`--older-than <days>`), dry run first, `--yes` to apply
|
|
237
|
+
after a backup. It deletes only calls this machine made (the deletion
|
|
238
|
+
syncs to peers). It keeps a task with edges and a refute check of a task
|
|
239
|
+
with `mu task auto` enabled, and the verdicts stay on the judged tasks.
|
|
240
|
+
The pruned calls' pi session files go with them.
|
|
241
|
+
With sync on, it keeps this machine's own history of them in the ops log.
|
|
242
|
+
`mu doctor` says when there are some to prune.
|
|
235
243
|
|
|
236
244
|
Move to a workstream when the findings become work agents will fix, or
|
|
237
245
|
the job has dependencies or several stages: see
|
package/docs/guide/remote.md
CHANGED
|
@@ -16,7 +16,9 @@ Read it before your first remote spawn.
|
|
|
16
16
|
the evidence first, or close the task yourself.
|
|
17
17
|
2. Allow unix-socket forwards in the host's sshd:
|
|
18
18
|
`AllowStreamLocalForwarding yes`.
|
|
19
|
-
3. Have a git repo there for `--worktree` to branch from.
|
|
19
|
+
3. Have a git repo there for `--worktree` to branch from. One profile serves
|
|
20
|
+
every repo on the host: `--max-sessions` caps the machine, so never add
|
|
21
|
+
the same machine twice.
|
|
20
22
|
|
|
21
23
|
Use one orchestrator DB on your machine. Do not run a second mu on the
|
|
22
24
|
host for the same work: task ownership points into your local `agents`
|
|
@@ -26,14 +28,14 @@ table.
|
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
30
|
mu host init dev --ssh # prints a starter `mu host add`; runs nothing
|
|
29
|
-
mu host add dev --shell '<pane cmd>' --exec '<run cmd>' --fetch '<fetch cmd>' --
|
|
30
|
-
mu host check dev
|
|
31
|
+
mu host add dev --shell '<pane cmd>' --exec '<run cmd>' --fetch '<fetch cmd>' --max-sessions 1
|
|
32
|
+
mu host check dev --root '~/repo' # host answers, repo, pi, mu extension
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## Spawn the agent
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
|
-
mu agent spawn worker-1 -w auth --host dev --worktree --task build --send 'Read: mu task notes build -w auth'
|
|
38
|
+
mu agent spawn worker-1 -w auth --host dev --worktree --root '~/repo' --task build --send 'Read: mu task notes build -w auth'
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
- The pane runs the profile's `--shell` template, filled with the remote
|
|
@@ -42,11 +44,16 @@ mu agent spawn worker-1 -w auth --host dev --worktree --task build --send 'Read:
|
|
|
42
44
|
`--command` (default `pi`), the local and remote socket paths and the
|
|
43
45
|
remote cwd. The template starts the remote pi and forwards its control
|
|
44
46
|
socket to the local derived path.
|
|
45
|
-
- `--worktree` makes a detached `git worktree add` in the
|
|
46
|
-
`--
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
- `--worktree` makes a detached `git worktree add` in the remote repo
|
|
48
|
+
`--root` names (default: the profile's `--repo`; with neither the spawn
|
|
49
|
+
refuses) through its `--exec`, at
|
|
50
|
+
`~/.local/state/mu-next/remote-worktrees/<ws>/<name>` on the host. Its path,
|
|
51
|
+
base commit and the repo's absolute toplevel are the agent's `cwd`, `base`
|
|
52
|
+
and `repo`; close and recovery use that repo, never the profile's.
|
|
53
|
+
`--cwd` names an existing remote directory instead; without either, the
|
|
54
|
+
agent starts in `--root` (default the profile's repo), else the login
|
|
55
|
+
directory. Without a repo, templates get `.` for `{cwd}`. `--root` is part
|
|
56
|
+
of a `--task` attempt's identity: the same attempt in another repo conflicts.
|
|
50
57
|
- `--task` is the same one journaled attempt as for a local agent: claim
|
|
51
58
|
for the new agent, then the brief, never sent twice.
|
|
52
59
|
- mu reserves the host's channel before its first remote command and
|
|
@@ -112,7 +119,7 @@ mu agent close worker-1 -w auth # preview, then --if <rev>
|
|
|
112
119
|
`mu agent close <name> -w <ws>` (or `<ws>/<name>`) previews and closes THAT
|
|
113
120
|
worktree under the rule above (no pane, no task; the recorded host profile only), and
|
|
114
121
|
`mu agent collect <name>` fetches from it. Several records of one name need
|
|
115
|
-
`--recovery <id>`; a current agent of the name or path, a newer record of the same path, or a changed
|
|
122
|
+
`--recovery <id>`; a current agent of the name or path, a newer record of the same path, or a changed `--exec`, refuses (a changed default repo does not).
|
|
116
123
|
|
|
117
124
|
## Host profiles
|
|
118
125
|
|
|
@@ -123,8 +130,8 @@ collect` runs the `--fetch` template.
|
|
|
123
130
|
|
|
124
131
|
```bash
|
|
125
132
|
mu host init dev --ssh # prints a starter `mu host add`; runs nothing
|
|
126
|
-
mu host add dev --shell '<pane cmd>' --exec '<run cmd>' --fetch '<fetch cmd>' --
|
|
127
|
-
mu host check dev
|
|
133
|
+
mu host add dev --shell '<pane cmd>' --exec '<run cmd>' --fetch '<fetch cmd>' --max-sessions 1
|
|
134
|
+
mu host check dev --root '~/repo' # host answers, repo, pi, mu extension
|
|
128
135
|
mu host show dev # templates and the holders of its channels
|
|
129
136
|
```
|
|
130
137
|
|
package/docs/reference/env.md
CHANGED
|
@@ -53,6 +53,7 @@ milliseconds; `0` disables a wait unless noted.
|
|
|
53
53
|
| `MU_CTL_SOCK` | The agent's control socket path, derived from state dir, workstream, and agent. The mu pi extension serves it. |
|
|
54
54
|
| `MU_MANAGED_AGENT` | `1`. Hides `mu_delegate` inside agents, so a helper cannot spawn helpers. |
|
|
55
55
|
| `MU_DB_PATH`, `MU_STATE_DIR` | The absolute DB path and state dir the spawn used, so the worker's `mu` reads the same DB from any cwd. Local agents only. |
|
|
56
|
+
| `PI_CODING_AGENT_SESSION_DIR` | `<DB dir>/delegate-sessions/<workstream>/<task>`, set when `mu agent spawn --task <id> --send` (what `mu_delegate` and `mu next --refute` run) starts a pi agent for a `delegate`-kind task, so delegate sessions stay out of the caller's pi `/resume` list. Not set by a plain `mu agent spawn`. Local agents only. |
|
|
56
57
|
| `SSH_AUTH_SOCK` | The caller's, when it is a socket (a detached tmux session would otherwise hand the pane a stale one). Local agents only. |
|
|
57
58
|
| `MU_REMOTE` | `1`, only for an agent spawned with `--host`. Tells its pi extension there is no mu DB on that host: it skips the close nudge (you close the task) and the settle wait. |
|
|
58
59
|
|
package/docs/reference/naming.md
CHANGED
|
@@ -93,6 +93,8 @@ The state dir is `MU_STATE_DIR`, else `$XDG_STATE_HOME/mu-next`, else
|
|
|
93
93
|
| --- | --- |
|
|
94
94
|
| `<state-dir>/mu.db` | The SQLite DB for every workstream |
|
|
95
95
|
| `<state-dir>/worktrees/<workstream>/<agent>/` | Per-agent git worktree, created by `mu agent spawn --worktree` |
|
|
96
|
+
| `<state-dir>/delegate-sessions/<workstream>/<task>/` | A pane delegate's pi session (kept out of the caller's `/resume` list). `mu delegates prune` removes it with the call |
|
|
97
|
+
| `<state-dir>/delegate-process/<hash>/` | A mux-free delegate's receipt, `pi.log` and pi session. `mu delegates prune` removes it with the call |
|
|
96
98
|
| `<sync-dir>/<machine_id>.jsonl` | This machine's segment; `<sync-dir>` is `MU_SYNC_DIR` |
|
|
97
99
|
| `<sync-dir>/<machine_id>.manifest` | `{count, last_hlc, sha256}` for segment verification |
|
|
98
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mu-crew/mu",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A persistent, observable crew of pi agents running in one tmux or herdr session per workstream, coordinated through a built-in task DAG.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -5,9 +5,9 @@ Use when an agent runs on another machine. A **host profile** holds your command
|
|
|
5
5
|
The pane is the transport and forwards the remote pi's control socket: `send`, `wait`, `abort` work as locally; a dead pane is a dead agent. The host has no mu DB: you close the task, from the worker's `CLOSE:` block.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
mu host init dev --ssh #
|
|
8
|
+
mu host init dev --ssh # a starter to edit into `mu host add`
|
|
9
9
|
mu host check dev
|
|
10
|
-
mu agent spawn worker-1 -w <ws> --host dev --worktree --task <id> --send - <<'EOF'
|
|
10
|
+
mu agent spawn worker-1 -w <ws> --host dev --worktree --root <remote repo> --task <id> --send - <<'EOF'
|
|
11
11
|
<brief>
|
|
12
12
|
EOF
|
|
13
13
|
mu agent wait worker-1 -w <ws> --after-runs <n> --json # n = the spawn's printed "runs" (0 fresh); lastText: its CLOSE block
|
|
@@ -18,7 +18,8 @@ mu agent close worker-1 -w <ws> --if <rev>
|
|
|
18
18
|
|
|
19
19
|
End the brief with: commit, then `CLOSE: task=<id> evidence="<command + result>"`. Cherry-pick the collected commits and re-run the evidence before you close.
|
|
20
20
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
21
|
+
- **One profile per machine** (the cap is per profile); `--root` picks the repo.
|
|
22
|
+
- **`--max-sessions 1`**: the live pane holds the only channel; `collect` and `ssh dev` refuse. Raise the cap to collect mid-run.
|
|
23
|
+
- **Long remote gates are panes** (`--command 'npm test'`), not a held ssh.
|
|
24
|
+
- `close` refuses a dirty or uncollected tree: collect again.
|
|
25
|
+
- A host command's exit 3 is a hand-back (hardware key): never retry or ssh yourself.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dashboard-data.ts"],"sourcesContent":["// SDK seam for the dashboard: ONE read-only projection of everything its tabs\n// show (workstreams, tasks, frontier, agents, log, human needs, delegates,\n// attention).\n//\n// READ-ONLY by construction: it never reconciles, reaps, ingests or writes, and\n// runs on a `readonly: true` connection. The only I/O beyond SQL is the\n// best-effort ctl probe `readLiveAgents` does (a socket read). It composes the\n// merged readers (`listTasks`, `loadOpenTaskFrontier`, `readLiveAgents`,\n// `listLogs`/`logItem`, `openHumanNeeds`, `listDelegates`) rather than\n// re-deriving them, so a dashboard row equals the CLI's. `stateSnapshot` is NOT\n// the source of the task list: it omits OPEN/parked rows and caps closed ones.\n//\n// `next` is `computeNext` (the CLI's one rule set) with no outcome: it only reads.\n// A failed computation stays `next_source: \"unavailable\"` (never \"no authorised\n// work\"). An agent's `tool` comes from the ctl status the agent read already did:\n// an object while a tool runs, null when none, absent for an extension that\n// predates the field (unknown). Elapsed is `now - since`, calculated at read.\n\nimport { type AgentOutput, liveAgentToOutput } from \"./agent-output.js\";\nimport type { StateReading } from \"./agent-state.js\";\nimport { UNKNOWN_REASON } from \"./agent-state.js\";\nimport { readLiveAgents } from \"./agents.js\";\nimport { type LogItem, logItem } from \"./cli/log-records.js\";\nimport type { CtlTool } from \"./ctl/protocol.js\";\nimport { loadOpenTaskFrontier } from \"./dag.js\";\nimport { type Db, tryResolveLiveWorkstreamId, WorkstreamNotFoundError } from \"./db.js\";\nimport { listDelegates } from \"./delegates.js\";\nimport { openHumanNeeds } from \"./human-needed.js\";\nimport { parseOpKey } from \"./log-render.js\";\nimport { latestSeq, listLogs } from \"./logs.js\";\nimport { computeNext } from \"./next-hints.js\";\nimport type { NextPlan } from \"./output-contract.js\";\nimport type { TaskStatus } from \"./tasks/status.js\";\nimport { listTasks, type TaskRow } from \"./tasks.js\";\nimport { isScratchWorkstream } from \"./workstream.js\";\n\n/** A running tool of a pi agent (from the ctl status). */\nexport interface AgentTool {\n name: string;\n /** Epoch ms the tool started. */\n since: number;\n /** Calculated at read time (`now - since`), when known. */\n elapsed_ms?: number;\n}\n\nexport interface DashboardWorkstream {\n name: string;\n scratch: boolean;\n /** Newest visible op seq in the workstream; 0 when it has none, or when `activity: false` skipped the read. */\n latest_seq: number;\n}\n\n/** One task, SQL column names, `key` = `<workstream>/<id>` (unique across workstreams). */\nexport interface DashboardTask {\n key: string;\n workstream: string;\n id: string;\n title: string;\n status: string;\n substate: string;\n impact: number;\n effort_days: number;\n /** Owner's name; only while IN_PROGRESS. */\n owner: string | null;\n /** The owner's registry id (`tasks.owner_id`; the identity: names repeat across workstreams). */\n owner_id: number | null;\n /** The owner's OWN workstream, which can differ from the task's; null when unowned. */\n owner_workstream: string | null;\n kind: string;\n wave: string | null;\n created_at: string;\n updated_at: string;\n /** `MAX(ops.seq)` over the task's own ops, its notes' and its edges' (raw log, db/sql writes included); 0 when none. */\n activity_seq: number;\n}\n\n/** One line of the open DAG (`loadOpenTaskFrontier`, preorder, per workstream). */\nexport interface DashboardEdge {\n key: string;\n workstream: string;\n id: string;\n title: string;\n status: string;\n substate: string;\n depth: number;\n /** Qualified key of the entry it is listed under; null for a root. */\n parent: string | null;\n /** Already listed above (a diamond): not expanded again. */\n again: boolean;\n /** Open neighbours not listed under this entry. */\n kids: number;\n /** CLOSED neighbours collapsed into this count. */\n closed: number;\n cap_cut: number;\n}\n\n/** What the frontier cap left out of one workstream's DAG (per-entry `cap_cut` cannot say it). */\nexport interface DashboardFrontierCut {\n workstream: string;\n /** Open start tasks the node cap left out: open work absent from `edges`. */\n roots_cut: number;\n /** Recurrence (diamond) lines the cap left out; each such task is listed elsewhere. */\n again_cut: number;\n}\n\n/** A registry agent with its computed state; unknown stays unknown. */\nexport interface DashboardAgent extends AgentOutput {\n /** `<workstream>/<name>`. */\n key: string;\n /** Qualified keys of the IN_PROGRESS tasks it owns (empty when none). */\n tasks: string[];\n /** The first of `tasks`; null when none. */\n task: string | null;\n /** The running tool; null = none running; absent = unknown (old extension, no ctl): never read as \"none\". */\n tool?: AgentTool | null;\n}\n\n/** An op of the canonical log, rendered by the one formatter (`logItem`), plus its workstream. */\nexport interface DashboardLogItem extends LogItem {\n /** The workstream the op belongs to; null for a machine-wide op. */\n scope: string | null;\n}\n\n/** An OPEN `human` task of the caller. */\nexport interface DashboardHumanNeed {\n key: string;\n workstream: string;\n id: string;\n reason: string;\n}\n\n/** An unfinished delegate call of the caller. */\nexport interface DashboardDelegate {\n call_id: string;\n workstream: string;\n /** The delegate task's local id. */\n task: string;\n state: string;\n wave: string | null;\n judges: string | null;\n agent: string | null;\n verdict: string | null;\n error: string | null;\n updated_at: string;\n}\n\n/**\n * Something a human has to look at. Read over EVERY live workstream and blind\n * to the status/text filters and the workstream scope, so each surface (the\n * strip under the tabs, the Overview card) agrees.\n *\n * - `dead`: the agent's pane (or gate process) is gone.\n * - `permission`: `needs_permission`; `text` carries how long.\n * - `stalled`: one tool has run over `STALLED_TOOL_MS`.\n * - `input`: `needs_input` past the idle threshold on an agent that owns work\n * (the registry's `idle` flag; `MU_IDLE_THRESHOLD_MS`, 0 disables).\n * - `human`: an OPEN `kind=human` task, whoever recorded it (`human_needs`\n * is caller-scoped and empty for a human shell).\n * - `next_unavailable`: `computeNext` failed.\n * - `sync`: a warning the caller passed as `syncWarnings`.\n */\nexport type AttentionKind =\n | \"dead\"\n | \"permission\"\n | \"input\"\n | \"stalled\"\n | \"human\"\n | \"next_unavailable\"\n | \"sync\";\n\nexport interface DashboardAttention {\n /** Unique within a snapshot (`<kind>:<target>`). */\n key: string;\n /** The workstream it belongs to; null for a machine-wide item (`next_unavailable`, `sync`). */\n workstream: string | null;\n kind: AttentionKind;\n /** One short line, age at read (\"worker-3 permission 20m\"; a human task: its title); bare names, the UI qualifies. */\n text: string;\n /** Qualified key to drill into: the agent (dead/permission/input/stalled) or the task (human); null otherwise. */\n target: string | null;\n}\n\nexport interface DashboardSnapshot {\n /** The identity the caller-scoped panels were read for; null = a human shell. */\n caller: string | null;\n /** `none`: no identity, so `human_needs` and `delegates` are empty BY DESIGN (never another caller's). */\n caller_scope: \"caller\" | \"none\";\n /** Every live workstream, scratch included; never narrowed by a filter (it is the switcher). */\n workstreams: DashboardWorkstream[];\n tasks: DashboardTask[];\n edges: DashboardEdge[];\n /** One per in-scope workstream: the frontier's root/recurrence cuts, passed through. */\n frontier: DashboardFrontierCut[];\n agents: DashboardAgent[];\n /** The newest ops, oldest first. */\n log: DashboardLogItem[];\n /** `computeNext` for the caller set and the workstream filter; null when `next_source` is `unavailable`, or computed and nothing is authorised. */\n next: NextPlan;\n /** `unavailable`: the computation failed; the UI must not read null as \"no work\". */\n next_source: \"unavailable\" | \"computed\";\n human_needs: DashboardHumanNeed[];\n delegates: DashboardDelegate[];\n /** Trouble across every workstream, filters ignored, most urgent first (see `AttentionKind`). */\n attention: DashboardAttention[];\n}\n\nexport interface DashboardOptions {\n /** Narrow tasks, frontier, agents, log and caller panels to one live workstream. */\n workstream?: string;\n /**\n * Read tasks, edges and frontier of this workstream ONLY (a name that is not live,\n * or null: none), leaving agents, log, caller panels and attention machine-wide:\n * the dashboard shows one workstream, and reading every other one's tasks and DAG\n * each tick is what made it costly. Absent: every live workstream. `workstream`, when\n * set, wins.\n */\n scopeWorkstream?: string | null;\n /** Statuses to list; default OPEN + IN_PROGRESS (CLOSED is hidden). */\n status?: readonly TaskStatus[];\n /** Case-insensitive substring over a task's key/title/owner/substate and an agent's key/name/state/task keys. */\n text?: string;\n /** `spawnerFromEnv()`; null = a human shell (caller-scoped panels stay empty). */\n caller: string | null;\n /** Who `next` is computed for (`currentCallers()`); default: `caller` alone, none for a human shell. */\n callers?: readonly string[];\n /** Log tail size (default 100). */\n logLimit?: number;\n /** Read the log tail of this workstream only, leaving every other panel unscoped (not checked: a name that is not live reads an empty log). */\n logWorkstream?: string;\n /**\n * Read each workstream's `latest_seq` (default true). It only ranks the starting\n * pick and costs one log scan per workstream, so the dashboard's poll passes false\n * once the set of live workstreams is known (every `latest_seq` is then 0).\n */\n activity?: boolean;\n /** Sync warnings the caller holds (`ambientIngest`); each becomes a `sync` attention item. */\n syncWarnings?: readonly string[];\n /**\n * The agent-state cache (the dashboard's slow tier, see `readLiveAgents`): with\n * `probe: false` the read is SQL only for every cached agent. Absent: probe all.\n */\n agentStates?: { cache: Map<string, StateReading>; probe: boolean };\n}\n\nconst DEFAULT_STATUS: readonly TaskStatus[] = [\"OPEN\", \"IN_PROGRESS\"];\nconst DEFAULT_LOG_LIMIT = 100;\n/** A tool running this long counts as stalled. */\nexport const STALLED_TOOL_MS = 15 * 60_000;\nconst FINISHED_CALL: readonly string[] = [\"done\", \"failed\", \"cancelled\"];\n\n/** `MAX(ops.seq)` per task id of one workstream over its task, note and edge ops. */\nfunction taskActivity(db: Db, workstream: string): Map<string, number> {\n const prefix = `${workstream}/`;\n const rows = db\n .prepare(\n `SELECT entity, key, MAX(seq) AS seq FROM ops\n WHERE entity IN ('task', 'note', 'edge') AND key >= ? AND key < ?\n GROUP BY entity, key`,\n )\n .all(prefix, `${workstream}0`) as { entity: string; key: string; seq: number }[];\n const out = new Map<string, number>();\n const bump = (id: string | undefined, seq: number): void => {\n if (id !== undefined && seq > (out.get(id) ?? 0)) out.set(id, seq);\n };\n for (const r of rows) {\n if (r.entity === \"task\") bump(r.key.slice(prefix.length), r.seq);\n else if (r.entity === \"note\") bump(r.key.slice(prefix.length).split(\"#\")[0], r.seq);\n else {\n const parsed = parseOpKey(r.key);\n bump(parsed.local, r.seq);\n bump(parsed.to, r.seq);\n }\n }\n return out;\n}\n\nfunction nextOf(\n db: Db,\n callers: readonly string[],\n workstream: string | undefined,\n): { plan: NextPlan; source: DashboardSnapshot[\"next_source\"] } {\n try {\n const plan = computeNext(db, { callers, ...(workstream !== undefined ? { workstream } : {}) });\n return { plan, source: \"computed\" };\n } catch {\n return { plan: null, source: \"unavailable\" };\n }\n}\n\n/** The tool as the dashboard shows it, `elapsed_ms` calculated now; unknown stays absent. */\nfunction toolOf(tool: CtlTool | null | undefined, now: number): { tool?: AgentTool | null } {\n if (tool === undefined) return {};\n return { tool: tool === null ? null : { ...tool, elapsed_ms: Math.max(0, now - tool.since) } };\n}\n\nconst keyOf = (workstream: string, id: string): string => `${workstream}/${id}`;\n\ninterface AgentIdentity {\n name: string;\n workstream: string;\n}\n\nfunction dashboardTask(\n row: TaskRow,\n activity: ReadonlyMap<string, number>,\n agentsById: ReadonlyMap<number, AgentIdentity>,\n): DashboardTask {\n const inProgress = row.status === \"IN_PROGRESS\";\n const owner = inProgress && row.ownerId !== null ? agentsById.get(row.ownerId) : undefined;\n return {\n key: keyOf(row.workstreamName, row.name),\n workstream: row.workstreamName,\n id: row.name,\n title: row.title,\n status: row.status,\n substate: row.substate,\n impact: row.impact,\n effort_days: row.effortDays,\n owner: inProgress ? row.ownerName : null,\n owner_id: inProgress ? row.ownerId : null,\n owner_workstream: owner?.workstream ?? null,\n kind: row.kind,\n wave: row.wave,\n created_at: row.createdAt,\n updated_at: row.updatedAt,\n activity_seq: activity.get(row.name) ?? 0,\n };\n}\n\n/** Newest activity first; no ops (capture suppressed) falls back to `updated_at`, then key. */\nfunction byActivityDesc(a: DashboardTask, b: DashboardTask): number {\n if (a.activity_seq !== b.activity_seq) return b.activity_seq - a.activity_seq;\n if (a.updated_at !== b.updated_at) return a.updated_at < b.updated_at ? 1 : -1;\n return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;\n}\n\n/** `45s`, `1m05s`, `1h05m`: the ONE duration format of the dashboard (strip and tool column). */\nexport function formatElapsed(ms: number): string {\n const s = Math.max(0, Math.floor(ms / 1000));\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n if (m < 60) return `${m}m${String(s % 60).padStart(2, \"0\")}s`;\n return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, \"0\")}m`;\n}\n\n/** The pane (or a gate's process) is gone, as opposed to a state source that merely cannot say. */\nfunction isDead(a: DashboardAgent): boolean {\n if (a.state !== \"unknown\") return false;\n if (a.reason === UNKNOWN_REASON.paneGone) return true;\n return a.gate_token !== null && /^(exited \\d+|killed by signal)/.test(a.reason ?? \"\");\n}\n\nconst ATTENTION_ORDER: readonly AttentionKind[] = [\n \"dead\",\n \"permission\",\n \"stalled\",\n \"input\",\n \"human\",\n \"next_unavailable\",\n \"sync\",\n];\n\nfunction agentAttention(a: DashboardAgent, now: number): DashboardAttention[] {\n const item = (kind: AttentionKind, text: string): DashboardAttention => ({\n key: `${kind}:${a.key}`,\n workstream: a.workstream,\n kind,\n text: `${a.name} ${text}`,\n target: a.key,\n });\n const since = a.since == null ? Number.NaN : Date.parse(a.since);\n const waited = Number.isFinite(since) ? ` ${formatElapsed(now - since)}` : \"\";\n const out: DashboardAttention[] = [];\n if (isDead(a)) out.push(item(\"dead\", \"dead\"));\n if (a.state === \"needs_permission\") out.push(item(\"permission\", `permission${waited}`));\n if (a.idle === true) out.push(item(\"input\", `input${waited}`));\n const ran = a.tool?.elapsed_ms;\n if (a.tool != null && ran !== undefined && ran >= STALLED_TOOL_MS) {\n out.push(item(\"stalled\", `${a.tool.name} ${formatElapsed(ran)}`));\n }\n return out;\n}\n\nexport function matches(\n text: string | undefined,\n ...fields: (string | null | undefined)[]\n): boolean {\n if (text === undefined || text === \"\") return true;\n const needle = text.toLowerCase();\n return fields.some((f) => f?.toLowerCase().includes(needle) === true);\n}\n\n/**\n * Load the dashboard's whole read model in one pass. Read-only (see the file\n * header); async only for the ctl probes. Throws `WorkstreamNotFoundError` for\n * a `workstream` filter that is not a live workstream.\n */\nexport async function loadDashboard(db: Db, opts: DashboardOptions): Promise<DashboardSnapshot> {\n const live = (\n db\n .prepare(\"SELECT name, id FROM workstreams WHERE torn_down_at IS NULL ORDER BY name\")\n .all() as { name: string; id: number }[]\n ).map((w) => ({ ...w, scratch: isScratchWorkstream(w.name) }));\n if (opts.workstream !== undefined && tryResolveLiveWorkstreamId(db, opts.workstream) === null) {\n throw new WorkstreamNotFoundError(opts.workstream);\n }\n const scoped = live.filter((w) => opts.workstream === undefined || w.name === opts.workstream);\n // Whose tasks, edges and frontier are read (`scopeWorkstream`; `workstream` wins).\n const readFrom =\n opts.workstream !== undefined || opts.scopeWorkstream === undefined\n ? scoped\n : live.filter((w) => w.name === opts.scopeWorkstream);\n const statuses =\n opts.status === undefined || opts.status.length === 0 ? DEFAULT_STATUS : opts.status;\n\n const agentsById = new Map<number, AgentIdentity>(\n (\n db\n .prepare(\n \"SELECT a.id, a.name, w.name AS workstream FROM agents a JOIN workstreams w ON w.id = a.workstream_id\",\n )\n .all() as { id: number; name: string; workstream: string }[]\n ).map((a) => [a.id, { name: a.name, workstream: a.workstream }]),\n );\n // IN_PROGRESS tasks by owner registry id, over EVERY live workstream: a task can be\n // claimed by an agent of another workstream, and a filtered read must still show it.\n const owned = new Map<number, string[]>();\n for (const t of db\n .prepare(\n `SELECT ws.name AS ws, t.local_id AS id, t.owner_id AS owner FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id AND ws.torn_down_at IS NULL\n WHERE t.status = 'IN_PROGRESS' AND t.owner_id IS NOT NULL ORDER BY ws.name, t.local_id`,\n )\n .all() as { ws: string; id: string; owner: number }[]) {\n owned.set(t.owner, [...(owned.get(t.owner) ?? []), keyOf(t.ws, t.id)]);\n }\n\n const tasks: DashboardTask[] = [];\n const edges: DashboardEdge[] = [];\n const frontier: DashboardFrontierCut[] = [];\n const agents: DashboardAgent[] = [];\n const now = Date.now();\n for (const ws of readFrom) {\n const activity = taskActivity(db, ws.name);\n tasks.push(\n ...listTasks(db, ws.name, { status: statuses })\n .map((r) => dashboardTask(r, activity, agentsById))\n .filter((t) => matches(opts.text, t.key, t.title, t.owner, t.substate)),\n );\n const front = loadOpenTaskFrontier(db, ws.name);\n frontier.push({\n workstream: ws.name,\n roots_cut: front.rootsCut,\n again_cut: front.againCut,\n });\n for (const e of front.entries) {\n edges.push({\n key: keyOf(ws.name, e.task.name),\n workstream: ws.name,\n id: e.task.name,\n title: e.task.title,\n status: e.task.status,\n substate: e.task.substate,\n depth: e.depth,\n parent: e.parent === null ? null : keyOf(ws.name, e.parent),\n again: e.again,\n kids: e.kids,\n closed: e.closed,\n cap_cut: e.capCut,\n });\n }\n }\n tasks.sort(byActivityDesc);\n\n // Every live workstream's agents, whatever the scope or text: Attention reads them all.\n const everyAgent: DashboardAgent[] = [];\n for (const ws of live) {\n for (const a of await readLiveAgents(db, ws.name, undefined, opts.agentStates)) {\n const mine = owned.get(a.incarnation) ?? [];\n everyAgent.push({\n ...liveAgentToOutput(a, ws.id),\n key: keyOf(ws.name, a.name),\n tasks: mine,\n task: mine[0] ?? null,\n ...toolOf(a.tool, now),\n });\n }\n }\n const inScope = new Set(scoped.map((w) => w.name));\n agents.push(\n ...everyAgent.filter(\n (a) => inScope.has(a.workstream) && matches(opts.text, a.key, a.state ?? null, ...a.tasks),\n ),\n );\n\n const next = nextOf(\n db,\n opts.callers ?? (opts.caller === null ? [] : [opts.caller]),\n opts.workstream,\n );\n const logWs = opts.logWorkstream ?? opts.workstream;\n const log = listLogs(db, {\n ...(logWs !== undefined ? { workstream: logWs } : {}),\n limit: opts.logLimit ?? DEFAULT_LOG_LIMIT,\n }).map((row) => ({\n ...logItem(row, true).item,\n scope: parseOpKey(row.workstreamName).workstream ?? null,\n }));\n\n const callerOwn = opts.caller !== null;\n const human_needs = callerOwn\n ? openHumanNeeds(db, opts.caller as string)\n .filter((h) => inScope.has(h.workstream))\n .map((h) => ({\n key: keyOf(h.workstream, h.id),\n workstream: h.workstream,\n id: h.id,\n reason: h.reason,\n }))\n : [];\n const delegates = callerOwn\n ? listDelegates(db, opts.caller as string)\n .filter((c) => inScope.has(c.workstream) && !FINISHED_CALL.includes(c.state))\n .map((c) => ({\n call_id: c.callId,\n workstream: c.workstream,\n task: c.task,\n state: c.state,\n wave: c.wave,\n judges: c.record ? `${c.record.workstream}/${c.record.task}` : null,\n agent: c.agent,\n verdict: c.verdict,\n error: c.error,\n updated_at: c.updatedAt,\n }))\n : [];\n\n const attention: DashboardAttention[] = everyAgent.flatMap((a) => agentAttention(a, now));\n for (const t of db\n .prepare(\n `SELECT ws.name AS ws, t.local_id AS id, t.title AS title FROM tasks t\n JOIN workstreams ws ON ws.id = t.workstream_id AND ws.torn_down_at IS NULL\n WHERE t.status = 'OPEN' AND t.kind = 'human' ORDER BY ws.name, t.local_id`,\n )\n .all() as { ws: string; id: string; title: string }[]) {\n attention.push({\n key: `human:${keyOf(t.ws, t.id)}`,\n workstream: t.ws,\n kind: \"human\",\n text: t.title,\n target: keyOf(t.ws, t.id),\n });\n }\n if (next.source === \"unavailable\") {\n attention.push({\n key: \"next_unavailable\",\n workstream: null,\n kind: \"next_unavailable\",\n text: \"next unavailable\",\n target: null,\n });\n }\n (opts.syncWarnings ?? []).forEach((w, i) => {\n attention.push({\n key: `sync:${i}`,\n workstream: null,\n kind: \"sync\",\n text: `sync: ${w}`,\n target: null,\n });\n });\n // Most urgent kind first; within a kind the order read (workstream, then name) holds.\n attention.sort((a, b) => ATTENTION_ORDER.indexOf(a.kind) - ATTENTION_ORDER.indexOf(b.kind));\n\n return {\n caller: opts.caller,\n caller_scope: callerOwn ? \"caller\" : \"none\",\n workstreams: live.map((w) => ({\n name: w.name,\n scratch: w.scratch,\n latest_seq: opts.activity === false ? 0 : latestSeq(db, w.name),\n })),\n tasks,\n edges,\n frontier,\n agents,\n log,\n next: next.plan,\n next_source: next.source,\n human_needs,\n delegates,\n attention,\n };\n}\n"],"mappings":"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","names":[]}
|