@mu-crew/mu 3.1.0 → 3.2.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.
Files changed (43) hide show
  1. package/AGENTS.md +203 -588
  2. package/README.md +80 -308
  3. package/dist/cli.js +6106 -4716
  4. package/dist/cli.js.map +1 -1
  5. package/dist/extension/mu-pi.js +786 -0
  6. package/dist/extension/mu-pi.js.map +1 -0
  7. package/dist/index.d.ts +738 -536
  8. package/dist/index.js +6617 -5785
  9. package/dist/index.js.map +1 -1
  10. package/docs/ARCHITECTURE.md +178 -1008
  11. package/docs/ROADMAP.md +88 -169
  12. package/docs/VISION.md +206 -407
  13. package/docs/VOCABULARY.md +202 -455
  14. package/docs/architecture/control-socket.md +119 -0
  15. package/docs/architecture/dag.md +113 -0
  16. package/docs/architecture/mux.md +163 -0
  17. package/docs/architecture/ops-log.md +140 -0
  18. package/docs/architecture/sdk.md +185 -0
  19. package/docs/architecture/sync.md +129 -0
  20. package/docs/architecture/tui.md +146 -0
  21. package/docs/guide/README.md +25 -0
  22. package/docs/guide/backends.md +61 -0
  23. package/docs/guide/cleanup.md +74 -0
  24. package/docs/guide/delegate.md +87 -0
  25. package/docs/guide/dispatch.md +137 -0
  26. package/docs/guide/getting-started.md +177 -0
  27. package/docs/guide/recovery.md +133 -0
  28. package/docs/guide/remote.md +74 -0
  29. package/docs/guide/sql.md +76 -0
  30. package/docs/guide/stop-a-worker.md +64 -0
  31. package/docs/guide/sync.md +78 -0
  32. package/docs/guide/tui.md +98 -0
  33. package/docs/guide/upgrade.md +76 -0
  34. package/docs/history/CHANGELOG-pre-3.md +4866 -0
  35. package/docs/reference/env.md +87 -0
  36. package/docs/reference/naming.md +82 -0
  37. package/docs/specs/2026-09-28-agent-state-from-murmur.md +1 -1
  38. package/docs/specs/2026-09-29-task-substates.md +1 -1
  39. package/package.json +4 -4
  40. package/skills/mu/REMOTE_WORKERS.md +186 -491
  41. package/skills/mu/SKILL.md +142 -217
  42. package/docs/HANDOVER.md +0 -486
  43. package/docs/USAGE_GUIDE.md +0 -2637
package/dist/index.d.ts CHANGED
@@ -1,19 +1,29 @@
1
1
  import { Database } from 'better-sqlite3';
2
2
 
3
+ type CtlState = "busy" | "idle" | "needs_input";
4
+
3
5
  type RuntimeState = "busy" | "needs_input" | "needs_permission" | "unknown";
4
- type StateSource = "murmur" | "herdr" | "none";
6
+ type StateSource = "murmur" | "herdr" | "ctl" | "none";
7
+ /** How a pi agent's control socket answered; "n/a" for non-pi agents. */
8
+ type CtlLink = "ok" | "missing" | "refused" | "n/a";
5
9
  interface StateReading {
6
10
  state: RuntimeState;
7
11
  source: StateSource;
8
12
  since: number | null;
9
13
  alive: boolean;
10
14
  reason?: string;
15
+ /** Set for agents that expect a control socket. */
16
+ ctl?: Exclude<CtlLink, "n/a">;
11
17
  }
12
18
  interface StateAgentRef {
13
19
  name: string;
14
20
  workstreamName: string;
15
21
  paneId: string;
22
+ /** When set and it names pi, the control socket is the state source. */
23
+ cli?: string;
16
24
  }
25
+ /** ctl's idle maps to needs_input, the same as murmur's idle. */
26
+ declare function ctlRuntimeState(state: CtlState): RuntimeState;
17
27
  declare const UNKNOWN_REASON: {
18
28
  readonly murmurMissing: "murmur not installed";
19
29
  readonly extensionMissing: "murmur pi extension not linked";
@@ -21,14 +31,24 @@ declare const UNKNOWN_REASON: {
21
31
  readonly stale: "remote snapshot stale";
22
32
  readonly herdrNone: "herdr reports no state";
23
33
  readonly paneGone: "pane gone";
34
+ readonly ctlMissing: "ctl missing";
35
+ readonly ctlRefused: "ctl refused";
24
36
  };
25
37
  declare function agentKey(a: {
26
38
  name: string;
27
39
  workstreamName: string;
28
40
  }): string;
29
41
  declare function murmurAvailable(): boolean;
42
+ /**
43
+ * Read the runtime state of each agent. Order per agent: control socket
44
+ * (pi agents) → murmur pane option → murmur remote → herdr. A pi agent
45
+ * whose socket does not answer reads `unknown` with reason `ctl missing`
46
+ * or `ctl refused`, never a murmur reading, so the gap stays visible.
47
+ * `stateDir` roots the derived socket path (spawn uses the DB's directory).
48
+ */
30
49
  declare function readAgentStates(agents: readonly StateAgentRef[], opts?: {
31
50
  now?: number;
51
+ stateDir?: string;
32
52
  }): Promise<Map<string, StateReading>>;
33
53
 
34
54
  /**
@@ -1101,429 +1121,96 @@ interface ReconcileReport {
1101
1121
  }
1102
1122
  declare function reconcile(db: Db, opts: ReconcileOptions): Promise<ReconcileReport>;
1103
1123
 
1104
- interface AdoptAgentOptions {
1105
- /** tmux pane id (e.g. '%15'). Must already exist on the tmux server. */
1106
- paneId: string;
1107
- /** Workstream to adopt the pane into. The pane MUST be in the
1108
- * matching tmux session (`mu-<workstream>`); cross-session adopt is
1109
- * rejected. */
1124
+ type AbortResult = {
1125
+ agent: string;
1110
1126
  workstream: string;
1111
- /** Override the pane's title with this name. When omitted, the pane's
1112
- * current title becomes the agent name (zero-config adoption). */
1113
- name?: string;
1114
- /** Defaults to 'pi' via the schema DEFAULT. */
1115
- cli?: string;
1116
- /** 'full-access' (default) or 'read-only'. */
1117
- role?: string;
1118
- /** Override the tmux session lookup. Defaults to `mu-<workstream>`. */
1119
- tmuxSession?: string;
1127
+ before: CtlState;
1128
+ after: CtlState;
1129
+ /** True when pi settled (or was never busy). */
1130
+ settled: boolean;
1131
+ /** False when the agent was not busy, so no abort was sent. */
1132
+ aborted: boolean;
1133
+ elapsedMs: number;
1134
+ };
1135
+ type AbortAgentOptions = {
1136
+ workstream: string;
1137
+ timeoutMs?: number;
1138
+ /** Control socket path. Default: derived from the agent's identity. */
1139
+ socket?: string;
1140
+ };
1141
+ declare function abortAgent(db: Db, name: string, opts: AbortAgentOptions): Promise<AbortResult>;
1142
+
1143
+ type VcsBackendName = "jj" | "sl" | "git" | "none";
1144
+ interface RebaseResult {
1145
+ /** The ref the workspace was actually rebased onto (resolved
1146
+ * symbolic-or-revset → concrete name). For git that is the
1147
+ * resolveGitMainRef() symbolic ref; for jj/sl it's the literal
1148
+ * `trunk()` revset (or whatever the operator passed via fromRef). */
1149
+ fromRef: string;
1150
+ /** Commit subjects (or descriptions) that got replayed, oldest-first.
1151
+ * Empty when the workspace was already at fromRef (no-op). */
1152
+ replayed: string[];
1153
+ /** Files / commits that conflicted during the rebase. Always
1154
+ * empty for a successful rebase — a non-empty conflicts list
1155
+ * means we threw WorkspaceConflictError before returning. The
1156
+ * field exists so the error's serialised payload can carry it. */
1157
+ conflicts: string[];
1120
1158
  }
1121
- interface AdoptAgentResult {
1122
- agent: AgentRow;
1123
- /** True when the pane already had a matching agents row — the call
1124
- * was a no-op (idempotent). */
1125
- alreadyAdopted: boolean;
1126
- /** The pane title before adopt, or null if the pane had no title. */
1127
- previousTitle: string | null;
1128
- /** The title the pane was set to (== agent.name post-adopt). Equal to
1129
- * previousTitle when no retitle happened. */
1130
- paneTitleSetTo: string;
1159
+ interface CommitSummary {
1160
+ /** Full commit / change id. */
1161
+ sha: string;
1162
+ /** First-line description / subject. */
1163
+ subject: string;
1164
+ /** Remainder of the commit message (may be empty). */
1165
+ body: string;
1166
+ /** Author display name, when the backend exposes one. */
1167
+ author: string;
1168
+ /** ISO-8601 author / commit timestamp. */
1169
+ authorDate: string;
1170
+ /** Compact relative author time (e.g. "3m", "2d"). */
1171
+ relTime: string;
1131
1172
  }
1132
- /**
1133
- * Register an existing tmux pane as a managed mu agent. Inverse of the
1134
- * 'orphan' state surfaced by `mu agent list`: a pane that looks like an
1135
- * agent (running pi/claude/codex) but has no DB row.
1136
- *
1137
- * Identity contract (matches the claim protocol invariant):
1138
- * - Post-adopt, the pane's title equals the agent's name.
1139
- * - When `name` is omitted, the pane's existing title becomes the
1140
- * agent name verbatim. Adopting a pane titled 'pi' would fail name
1141
- * validation — caller must supply --name in that case.
1142
- *
1143
- * Idempotent: adopting the same pane twice with the same name is a
1144
- * no-op (returns alreadyAdopted=true). Adopting a different pane under
1145
- * an existing agent name throws AgentExistsError.
1146
- *
1147
- * Validation order (matches the design in note #100):
1148
- * 1. Pane id format -> assertValidPaneId via paneExists / setPaneTitle
1149
- * 2. Pane exists -> PaneNotFoundError
1150
- * 3. Pane is in session -> AgentNotInWorkstreamError (cross-session)
1151
- * 4. Resolved name OK -> isValidAgentName / Error('agent name invalid')
1152
- * 5. Idempotent check -> if pane already owned by an agent of this
1153
- * name, return alreadyAdopted=true
1154
- * 6. Name not taken -> AgentExistsError (else)
1155
- * 7. Insert + retitle.
1156
- *
1157
- * Status starts at 'free' — reconcile/detect will update it on the next
1158
- * `mu agent list` based on actual pane content (the pi prompt yields
1159
- * 'free'; an agent mid-thought yields 'busy'; etc.). We don't run
1160
- * detection inline here because the caller may not have $TMUX, and
1161
- * adoption shouldn't depend on a captured-pane probe succeeding.
1162
- */
1163
- declare function adoptAgent(db: Db, opts: AdoptAgentOptions): Promise<AdoptAgentResult>;
1164
-
1165
- /**
1166
- * Pre-flight failure: the command mu would have spawned in the new
1167
- * pane doesn't resolve to a binary on PATH (and isn't an absolute /
1168
- * relative path that exists + is executable). Thrown by `spawnAgent`
1169
- * BEFORE `prestageWorkspace` so a typo in `--cli` never leaves an
1170
- * orphan workspace dir behind.
1171
- *
1172
- * Source: feedback ws task `fb_agent_spawn_no_validation`. Live
1173
- * dogfood report: `mu agent spawn worker-1 --cli pi-meta` on a host
1174
- * where the `pi-meta` binary wasn't on PATH printed `Spawned worker-1
1175
- * (pi-meta)` and the pane immediately died with `command not found`;
1176
- * the existing 1.5s liveness check sometimes missed it (the shell
1177
- * stays alive after the failed exec). Pre-flighting the PATH lookup
1178
- * surfaces the typo before any side effects (workspace, pane, DB row).
1179
- *
1180
- * Distinct from `AgentSpawnStartupError` (pane alive but parked at an
1181
- * error prompt) and `AgentDiedOnSpawnError` (pane vanished within the
1182
- * liveness window). All three carry different remediation hints, so
1183
- * they're separate types.
1184
- */
1185
- declare class AgentSpawnCliNotFoundError extends Error implements HasNextSteps {
1186
- readonly cli: string;
1187
- /** First whitespace-separated token of the resolved command — the
1188
- * thing actually missing on PATH. Surfaced verbatim in the
1189
- * message so the operator sees what mu searched for (which may
1190
- * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */
1191
- readonly binary: string;
1192
- /** Name of the env var that mu consulted before falling back to
1193
- * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set
1194
- * to the conventional name so the nextSteps hint can recommend
1195
- * exporting it. */
1196
- readonly envVarChecked: string;
1197
- readonly name = "AgentSpawnCliNotFoundError";
1198
- constructor(cli: string,
1199
- /** First whitespace-separated token of the resolved command — the
1200
- * thing actually missing on PATH. Surfaced verbatim in the
1201
- * message so the operator sees what mu searched for (which may
1202
- * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */
1203
- binary: string,
1204
- /** Name of the env var that mu consulted before falling back to
1205
- * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set
1206
- * to the conventional name so the nextSteps hint can recommend
1207
- * exporting it. */
1208
- envVarChecked: string);
1209
- errorNextSteps(): NextStep[];
1173
+ interface ShowCommitResult {
1174
+ /** Captured VCS show output (possibly truncated). Empty string on error. */
1175
+ text: string;
1176
+ /** True when stdout exceeded SHOW_COMMIT_MAX_CHARS and was clipped. */
1177
+ truncated: boolean;
1178
+ /** Human-readable error message; omitted on success. */
1179
+ error?: string;
1210
1180
  }
1211
- declare class AgentExistsError extends Error implements HasNextSteps {
1212
- readonly agentName: string;
1213
- readonly name = "AgentExistsError";
1214
- constructor(agentName: string);
1215
- errorNextSteps(): NextStep[];
1181
+ interface CreateWorkspaceOptions$1 {
1182
+ /** The repository being branched from. Absolute path. */
1183
+ projectRoot: string;
1184
+ /** Where to place the new workspace. Absolute path; must NOT exist. */
1185
+ workspacePath: string;
1186
+ /** Optional commit / branch / changeset id to base off. Backend-specific:
1187
+ * git uses it as a `git worktree add`'s ref, jj as a revset, sl as a
1188
+ * rev. Undefined = current head. */
1189
+ parentRef?: string;
1216
1190
  }
1217
- declare class AgentNotFoundError extends Error implements HasNextSteps {
1218
- readonly agentName: string;
1219
- /** Optional workstream context. When set, the message is enriched
1220
- * with `(in workstream <ws>)` so the verb that hit the miss
1221
- * (e.g. `mu workspace path <agent> -w <ws>`) doesn't leave the
1222
- * operator guessing which scope was searched. Optional so existing
1223
- * call sites that only know the agent name keep their original
1224
- * one-line message. */
1225
- readonly workstream?: string | undefined;
1226
- readonly name = "AgentNotFoundError";
1227
- constructor(agentName: string,
1228
- /** Optional workstream context. When set, the message is enriched
1229
- * with `(in workstream <ws>)` so the verb that hit the miss
1230
- * (e.g. `mu workspace path <agent> -w <ws>`) doesn't leave the
1231
- * operator guessing which scope was searched. Optional so existing
1232
- * call sites that only know the agent name keep their original
1233
- * one-line message. */
1234
- workstream?: string | undefined);
1235
- errorNextSteps(): NextStep[];
1191
+ interface CreateWorkspaceResult {
1192
+ /** The actual ref the workspace points at (resolved to a stable id
1193
+ * when possible). Stored on the row; useful for `mu workspace list`
1194
+ * and for `--commit` flows. May be null for backends that don't
1195
+ * expose a meaningful parent (e.g. `none`). */
1196
+ parentRef: string | null;
1236
1197
  }
1237
- /**
1238
- * Thrown when an entity-targeted verb is invoked with `-w/--workstream
1239
- * <name>` but the named agent lives in a different workstream.
1240
- * Mirrors `TaskNotInWorkstreamError`. Maps to exit code 4 (conflict /
1241
- * wrong scope). Distinguishes "the user typo'd the workstream" from
1242
- * "the agent doesn't exist anywhere" (which surfaces as
1243
- * `AgentNotFoundError`).
1244
- */
1245
- declare class AgentNotInWorkstreamError extends Error implements HasNextSteps {
1246
- readonly agentName: string;
1247
- readonly expectedWorkstream: string;
1248
- readonly actualWorkstream: string;
1249
- readonly name = "AgentNotInWorkstreamError";
1250
- constructor(agentName: string, expectedWorkstream: string, actualWorkstream: string);
1251
- errorNextSteps(): NextStep[];
1198
+ interface FreeWorkspaceOptions$1 {
1199
+ workspacePath: string;
1200
+ /** If true, attempt to commit any pending changes BEFORE removal.
1201
+ * Backend-specific: jj auto-commits via `jj describe + jj new`, git
1202
+ * needs an explicit commit on the worktree, sl needs `sl commit`,
1203
+ * none has nothing to commit. If pending changes exist and `commit`
1204
+ * is false, the on-disk directory still gets removed and changes are
1205
+ * lost — the verb prints a clear warning. */
1206
+ commit: boolean;
1252
1207
  }
1253
- /**
1254
- * Thrown when an agent's pane is created and titled successfully but the
1255
- * spawned process exits within the liveness window (default 1500ms;
1256
- * configurable via `MU_SPAWN_LIVENESS_MS`). The most common cause is the
1257
- * underlying CLI failing fast: a wrapper CLI blocking on a single-instance
1258
- * lock, `claude` rejecting an invalid API key, etc. The agent's last
1259
- * scrollback (when capturable) is attached to help diagnose.
1260
- */
1261
- declare class AgentDiedOnSpawnError extends Error implements HasNextSteps {
1262
- readonly agentName: string;
1263
- readonly paneId: string;
1264
- readonly scrollback: string | undefined;
1265
- readonly name = "AgentDiedOnSpawnError";
1266
- constructor(agentName: string, paneId: string, scrollback: string | undefined);
1267
- errorNextSteps(): NextStep[];
1268
- }
1269
- /**
1270
- * Thrown when an agent's pane is alive AND staying alive after the
1271
- * liveness window, but its first burst of output matches a known
1272
- * provider-startup-failure pattern (missing API key, auth rejected, …).
1273
- * Source: feedback ws task `agent_spawn_model_auth_failure_counts_as_live`.
1274
- * Live dogfood report: `pi-meta --no-solo --model sonnet:high` printed
1275
- * `Error: No API key found for amazon-bedrock` and parked at a prompt.
1276
- * The pane stayed alive (1.5s liveness check passed) but the worker
1277
- * could never do work — the orchestrator only discovered this when
1278
- * `mu task wait` stalled minutes later.
1279
- *
1280
- * Distinct from `AgentDiedOnSpawnError`:
1281
- * - `AgentDiedOnSpawnError` → pane vanished within the liveness window
1282
- * (CLI exited fast).
1283
- * - `AgentSpawnStartupError` → pane alive, but the captured scrollback
1284
- * tail contains a curated provider-auth-failure pattern.
1285
- * The two carry different remediation hints (CLI override vs. fix the
1286
- * env var), so they're separate types instead of one with a flag.
1287
- *
1288
- * The pattern list is curated and short to keep false-positive risk low
1289
- * — the scan only looks at the last ~30 lines of the 50-line capture
1290
- * taken right after the liveness sleep, so matches naturally come from
1291
- * the CLI's first ~1.5s of output (not arbitrary later prompts the
1292
- * agent might type into).
1293
- */
1294
- declare class AgentSpawnStartupError extends Error implements HasNextSteps {
1295
- readonly agentName: string;
1296
- readonly paneId: string;
1297
- /** The single scrollback line that matched a known startup-error
1298
- * pattern. Surfaced verbatim in the message so the operator sees
1299
- * what mu saw. */
1300
- readonly matchedLine: string;
1301
- /** Full captured scrollback (tail-trimmed already by
1302
- * awaitSpawnLiveness). Attached to the message for context. */
1303
- readonly scrollback: string;
1304
- readonly name = "AgentSpawnStartupError";
1305
- constructor(agentName: string, paneId: string,
1306
- /** The single scrollback line that matched a known startup-error
1307
- * pattern. Surfaced verbatim in the message so the operator sees
1308
- * what mu saw. */
1309
- matchedLine: string,
1310
- /** Full captured scrollback (tail-trimmed already by
1311
- * awaitSpawnLiveness). Attached to the message for context. */
1312
- scrollback: string);
1313
- errorNextSteps(): NextStep[];
1314
- }
1315
- /**
1316
- * Thrown when `closeAgent` is called on an agent that has an associated
1317
- * workspace AND the caller didn't explicitly opt into discarding it.
1318
- *
1319
- * Background: the FK on `vcs_workspaces.agent` cascades on agent
1320
- * delete, so a naive `closeAgent` drops the workspace registry row
1321
- * but leaves the on-disk dir orphaned (mu can't see it via
1322
- * `mu workspace list / free / path` afterwards). Surfaced during
1323
- * the multi-agent dogfood teardown when three workspaces went
1324
- * orphaned silently.
1325
- *
1326
- * The fix: refuse close if a workspace exists; force the caller to
1327
- * decide. Two actionable resolutions:
1328
- * - `mu workspace free <agent>` first, then close cleanly.
1329
- * - `mu agent close <agent> --discard-workspace` to free the
1330
- * workspace AND close the agent in one shot (lossy: pending
1331
- * changes in the workspace are gone).
1332
- *
1333
- * Maps to exit code 4 (conflict) via the cli.ts handler.
1334
- */
1335
- declare class WorkspacePreservedError extends Error implements HasNextSteps {
1336
- readonly agentName: string;
1337
- readonly workspacePath: string;
1338
- readonly name = "WorkspacePreservedError";
1339
- constructor(agentName: string, workspacePath: string);
1340
- errorNextSteps(): NextStep[];
1341
- }
1342
-
1343
- /** The signal set kick supports. SIGINT is graceful (matches Ctrl-C
1344
- * semantics — what the operator probably wanted in the first place);
1345
- * SIGTERM is the polite escalation; SIGKILL is the unblockable
1346
- * hammer. We deliberately don't expose arbitrary signals — the
1347
- * three above are the actionable ones for "interrupt a wedged
1348
- * foreground tool subprocess." */
1349
- type KickSignal = "SIGINT" | "SIGTERM" | "SIGKILL";
1350
- declare function isKickSignal(s: string): s is KickSignal;
1351
- /**
1352
- * Thrown when the foreground pgid lookup on a pane's TTY yields
1353
- * either no rows at all (the pane is sitting at an idle shell with
1354
- * no foreground job) or only the wrapping shell itself (the LLM CLI
1355
- * — pi/claude/codex — is the foreground; signalling it would close
1356
- * the agent, which is what `mu agent close` is for).
1357
- *
1358
- * Maps to the generic exit code 1 in handle.ts (this is a
1359
- * runtime-state condition, not a typed not-found / conflict).
1360
- */
1361
- declare class NoForegroundProcessError extends Error implements HasNextSteps {
1362
- readonly agentName: string;
1363
- readonly tty: string;
1364
- readonly reason: "no-foreground" | "shell-only";
1365
- readonly name = "NoForegroundProcessError";
1366
- constructor(agentName: string, tty: string, reason: "no-foreground" | "shell-only");
1367
- errorNextSteps(): NextStep[];
1368
- }
1369
- interface KickProcessExecResult {
1370
- stdout: string;
1371
- stderr: string;
1372
- exitCode: number | null;
1373
- }
1374
- type KickProcessExecutor = (cmd: string, args: readonly string[]) => Promise<KickProcessExecResult>;
1375
- /** Install a custom executor (for tests). Returns the previous one so
1376
- * tests can restore cleanly. */
1377
- declare function setKickProcessExecutor(executor: KickProcessExecutor): KickProcessExecutor;
1378
- /** Restore the real executor. */
1379
- declare function resetKickProcessExecutor(): void;
1380
- interface PsRow {
1381
- pid: number;
1382
- pgid: number;
1383
- /** ps's `stat` (or `state`) field. The presence of `+` means
1384
- * "foreground process group on its controlling tty". */
1385
- stat: string;
1386
- /** Process command (just the comm; truncated, used for diagnostics). */
1387
- comm: string;
1388
- }
1389
- /**
1390
- * Parse `ps -t <tty> -o pid=,pgid=,stat=,comm=` output. Each non-blank
1391
- * line is one process: four whitespace-separated fields. Defensive
1392
- * about leading whitespace and command names with embedded spaces
1393
- * (the comm is the LAST field — join the tail).
1394
- */
1395
- declare function parsePsTtyOutput(output: string): PsRow[];
1396
- /**
1397
- * Resolve the foreground process group id for a TTY device path. The
1398
- * canonical signal `ps`'s `stat` field uses is `+` (BSD/Darwin AND
1399
- * Linux procps). We pick the first row whose stat contains `+`; its
1400
- * `pgid` is the foreground pgid of that controlling terminal.
1401
- *
1402
- * Returns:
1403
- * - `{ kind: "ok", pgid, fgRow }` on success
1404
- * - `{ kind: "no-foreground" }` when no row carries `+` AND there
1405
- * are no candidate rows at all
1406
- * - `{ kind: "shell-only", pgid, fgRow }` when the foreground pgid
1407
- * resolves to a shell whose comm is the agent's wrapping CLI
1408
- * (caller decides whether to refuse — kick refuses)
1409
- *
1410
- * The wrapping-CLI guard is intentionally narrow: we only refuse
1411
- * when the foreground process command matches one of the known
1412
- * pi/claude/codex/zsh/bash shapes. Anything else (a `find`, a
1413
- * `cargo build`, a `python script.py`) is exactly what we want to
1414
- * signal — that's the unbounded-tool case the verb was built for.
1415
- */
1416
- interface ForegroundLookup {
1417
- kind: "ok" | "shell-only" | "no-foreground";
1418
- pgid?: number;
1419
- fgRow?: PsRow;
1420
- /** All rows ps returned for the tty, for diagnostics / tests. */
1421
- rows: PsRow[];
1422
- }
1423
- declare function foregroundPgid(tty: string): Promise<ForegroundLookup>;
1424
- interface KickAgentOptions {
1425
- workstream: string;
1426
- /** Defaults to SIGINT (matches Ctrl-C semantics). */
1427
- signal?: KickSignal;
1428
- }
1429
- interface KickAgentResult {
1430
- agentName: string;
1431
- paneId: string;
1432
- /** TTY device path the foreground pgid was resolved against. */
1433
- tty: string;
1434
- /** The pgid we signalled. */
1435
- signaledPgid: number;
1436
- signal: KickSignal;
1437
- /** The comm of the foreground process at the time of signal — useful
1438
- * diagnostic in the event log ("we kicked a `find`, not a `cargo`"). */
1439
- foregroundComm: string;
1440
- }
1441
- /**
1442
- * Send `signal` to the foreground process group of an agent's pane
1443
- * TTY. Default signal is SIGINT.
1444
- *
1445
- * Errors:
1446
- * - `AgentNotFoundError` — the agent doesn't exist in this workstream.
1447
- * - `PaneNotFoundError` (from paneTTY) — the agent's pane has vanished.
1448
- * - `NoForegroundProcessError` — pane has no foreground job, OR the
1449
- * foreground is the wrapping CLI itself (refuse; use `mu agent close`).
1450
- *
1451
- * Emits an `agent kick <name> (signal=..., pgid=..., comm=...)` event
1452
- * on success.
1453
- */
1454
- declare function kickAgent(db: Db, name: string, opts: KickAgentOptions): Promise<KickAgentResult>;
1455
-
1456
- type VcsBackendName = "jj" | "sl" | "git" | "none";
1457
- interface RebaseResult {
1458
- /** The ref the workspace was actually rebased onto (resolved
1459
- * symbolic-or-revset → concrete name). For git that is the
1460
- * resolveGitMainRef() symbolic ref; for jj/sl it's the literal
1461
- * `trunk()` revset (or whatever the operator passed via fromRef). */
1462
- fromRef: string;
1463
- /** Commit subjects (or descriptions) that got replayed, oldest-first.
1464
- * Empty when the workspace was already at fromRef (no-op). */
1465
- replayed: string[];
1466
- /** Files / commits that conflicted during the rebase. Always
1467
- * empty for a successful rebase — a non-empty conflicts list
1468
- * means we threw WorkspaceConflictError before returning. The
1469
- * field exists so the error's serialised payload can carry it. */
1470
- conflicts: string[];
1471
- }
1472
- interface CommitSummary {
1473
- /** Full commit / change id. */
1474
- sha: string;
1475
- /** First-line description / subject. */
1476
- subject: string;
1477
- /** Remainder of the commit message (may be empty). */
1478
- body: string;
1479
- /** Author display name, when the backend exposes one. */
1480
- author: string;
1481
- /** ISO-8601 author / commit timestamp. */
1482
- authorDate: string;
1483
- /** Compact relative author time (e.g. "3m", "2d"). */
1484
- relTime: string;
1485
- }
1486
- interface ShowCommitResult {
1487
- /** Captured VCS show output (possibly truncated). Empty string on error. */
1488
- text: string;
1489
- /** True when stdout exceeded SHOW_COMMIT_MAX_CHARS and was clipped. */
1490
- truncated: boolean;
1491
- /** Human-readable error message; omitted on success. */
1492
- error?: string;
1493
- }
1494
- interface CreateWorkspaceOptions$1 {
1495
- /** The repository being branched from. Absolute path. */
1496
- projectRoot: string;
1497
- /** Where to place the new workspace. Absolute path; must NOT exist. */
1498
- workspacePath: string;
1499
- /** Optional commit / branch / changeset id to base off. Backend-specific:
1500
- * git uses it as a `git worktree add`'s ref, jj as a revset, sl as a
1501
- * rev. Undefined = current head. */
1502
- parentRef?: string;
1503
- }
1504
- interface CreateWorkspaceResult {
1505
- /** The actual ref the workspace points at (resolved to a stable id
1506
- * when possible). Stored on the row; useful for `mu workspace list`
1507
- * and for `--commit` flows. May be null for backends that don't
1508
- * expose a meaningful parent (e.g. `none`). */
1509
- parentRef: string | null;
1510
- }
1511
- interface FreeWorkspaceOptions$1 {
1512
- workspacePath: string;
1513
- /** If true, attempt to commit any pending changes BEFORE removal.
1514
- * Backend-specific: jj auto-commits via `jj describe + jj new`, git
1515
- * needs an explicit commit on the worktree, sl needs `sl commit`,
1516
- * none has nothing to commit. If pending changes exist and `commit`
1517
- * is false, the on-disk directory still gets removed and changes are
1518
- * lost — the verb prints a clear warning. */
1519
- commit: boolean;
1520
- }
1521
- interface FreeWorkspaceResult$1 {
1522
- /** The commit id that captured the pending changes, when `commit` was
1523
- * true and there was something to commit. Otherwise undefined. */
1524
- committedRef?: string;
1525
- /** True iff the on-disk path was actually removed (vs. already gone). */
1526
- removed: boolean;
1208
+ interface FreeWorkspaceResult$1 {
1209
+ /** The commit id that captured the pending changes, when `commit` was
1210
+ * true and there was something to commit. Otherwise undefined. */
1211
+ committedRef?: string;
1212
+ /** True iff the on-disk path was actually removed (vs. already gone). */
1213
+ removed: boolean;
1527
1214
  }
1528
1215
  interface VcsBackend {
1529
1216
  readonly name: VcsBackendName;
@@ -1644,136 +1331,597 @@ interface VcsBackend {
1644
1331
  */
1645
1332
  listDirtyFiles(workspacePath: string): Promise<string[]>;
1646
1333
  }
1647
-
1648
- declare const gitBackend: VcsBackend;
1649
-
1650
- declare const jjBackend: VcsBackend;
1651
-
1652
- declare const noneBackend: VcsBackend;
1653
-
1654
- declare const slBackend: VcsBackend;
1655
-
1656
- /** Return the backend that should handle projectRoot. Walks BACKENDS
1657
- * in precedence order; never returns undefined because noneBackend
1658
- * always claims. */
1659
- declare function detectBackend(projectRoot: string): Promise<VcsBackend>;
1660
- /** Look up a backend by name. Throws on unknown name. Used by
1661
- * `mu agent spawn --workspace-backend ...` to honour an explicit
1662
- * override. */
1663
- declare function backendByName(name: VcsBackendName): VcsBackend;
1664
-
1334
+
1335
+ declare const gitBackend: VcsBackend;
1336
+
1337
+ declare const jjBackend: VcsBackend;
1338
+
1339
+ declare const noneBackend: VcsBackend;
1340
+
1341
+ declare const slBackend: VcsBackend;
1342
+
1343
+ /** Return the backend that should handle projectRoot. Walks BACKENDS
1344
+ * in precedence order; never returns undefined because noneBackend
1345
+ * always claims. */
1346
+ declare function detectBackend(projectRoot: string): Promise<VcsBackend>;
1347
+ /** Look up a backend by name. Throws on unknown name. Used by
1348
+ * `mu agent spawn --workspace-backend ...` to honour an explicit
1349
+ * override. */
1350
+ declare function backendByName(name: VcsBackendName): VcsBackend;
1351
+
1352
+ /**
1353
+ * Resolve the actual executable to launch in an agent's pane for a given
1354
+ * `cli`. Honours the env var `MU_<UPPER_CLI>_COMMAND` (e.g. `MU_PI_COMMAND=
1355
+ * pi-alt` makes `--cli pi` actually exec `pi-alt`). Falls back to the cli
1356
+ * name itself, which is what users expect when their `pi` binary is on
1357
+ * `$PATH` under that exact name.
1358
+ *
1359
+ * Used by `spawnAgent` to pick the spawned command, and by reconcile's
1360
+ * orphan detector so externally-spawned panes running the resolved binary
1361
+ * are still recognised as agents.
1362
+ */
1363
+ declare function resolveCliCommand(cli: string): string;
1364
+ /**
1365
+ * Compute the `MU_<UPPER_CLI>_COMMAND` env var name mu consults when
1366
+ * resolving `--cli <key>`. Hyphens in the cli key become underscores
1367
+ * (env var names can't contain `-`); this matches the operator-aliases
1368
+ * convention documented in the mu skill (e.g. `--cli pi-meta` →
1369
+ * `MU_PI_META_COMMAND`).
1370
+ */
1371
+ declare function envVarNameForCli(cli: string): string;
1372
+ /**
1373
+ * Resolve `--cli <key>` to its actual command string AND tell the
1374
+ * caller whether the resolution came from a `MU_<UPPER_CLI>_COMMAND`
1375
+ * env var or fell through to the bare cli name. The CLI uses this to
1376
+ * surface env-var attribution in the spawn-success line so config
1377
+ * issues are visible without `mu agent show`
1378
+ * (fb_agent_spawn_no_validation, part C).
1379
+ */
1380
+ declare function resolveCliCommandWithSource(cli: string): {
1381
+ command: string;
1382
+ envVar: string;
1383
+ resolvedFromEnv: boolean;
1384
+ };
1385
+ interface CommandResolutionResult {
1386
+ ok: boolean;
1387
+ /** First whitespace-separated token of the command — the binary
1388
+ * whose presence on PATH we checked. */
1389
+ binary: string;
1390
+ /** Absolute path of the resolved binary on PATH, when ok=true. */
1391
+ resolvedPath?: string;
1392
+ }
1393
+ type CommandResolver = (command: string) => Promise<CommandResolutionResult>;
1394
+ /** Override the PATH resolver. Tests use this to simulate "binary
1395
+ * absent" / "binary present" without depending on what's actually
1396
+ * installed. Production callers should never touch this. */
1397
+ declare function setCommandResolverForTests(resolver: CommandResolver): void;
1398
+ /** Restore the default PATH resolver. */
1399
+ declare function resetCommandResolverForTests(): void;
1400
+ /**
1401
+ * Verify the first token of `command` resolves to a binary on PATH.
1402
+ * Public so tests can call it directly; spawnAgent calls it before
1403
+ * prestageWorkspace so a bad --cli never creates an orphan workspace.
1404
+ */
1405
+ declare function checkCommandResolvable(command: string): Promise<CommandResolutionResult>;
1406
+ interface SpawnAgentOptions {
1407
+ name: string;
1408
+ workstream: string;
1409
+ /** Defaults to "pi". 0.1.0 only really supports "pi" but the column
1410
+ * accepts any string for forward-compat with future multi-CLI support
1411
+ * (claude/codex). */
1412
+ cli?: string;
1413
+ /** The actual command to run in the pane. Defaults to the cli value. */
1414
+ command?: string;
1415
+ /** Window name to group this agent under. Defaults to the agent's name
1416
+ * (so each agent gets its own window). Multiple agents sharing a `tab`
1417
+ * share a window with multiple panes. */
1418
+ tab?: string;
1419
+ /** "full-access" (default) or "read-only". The schema stores it; today
1420
+ * the role isn't enforced (deferred to a future capabilities pass). */
1421
+ role?: string;
1422
+ /** Initial working directory for the spawned pane (`tmux -c <path>`).
1423
+ * When `workspace: true` is passed, this is ignored — the workspace
1424
+ * path is used instead. */
1425
+ cwd?: string;
1426
+ /** Override the tmux session name. Defaults to `mu-<workstream>`. */
1427
+ tmuxSession?: string;
1428
+ /** Auto-create a VCS workspace for this agent before spawning the
1429
+ * pane and use the workspace path as cwd. Backend defaults to
1430
+ * detection (jj > sl > git > none). */
1431
+ workspace?: boolean;
1432
+ /** Force a specific VCS backend (only meaningful with `workspace: true`). */
1433
+ workspaceBackend?: VcsBackendName;
1434
+ /** Optional ref to base the workspace on (only meaningful with
1435
+ * `workspace: true`). Backend-specific. */
1436
+ workspaceFrom?: string;
1437
+ /** Project root the workspace branches from (only meaningful with
1438
+ * `workspace: true`). Defaults to `process.cwd()`. */
1439
+ workspaceProjectRoot?: string;
1440
+ /** Run the control-socket handshake for pi agents (default true).
1441
+ * `false` skips it and reports `ctl: "skipped"`. */
1442
+ ctl?: boolean;
1443
+ }
1444
+ /** Outcome of the spawn-time control-socket handshake. */
1445
+ type SpawnCtl = "ok" | "missing" | "refused" | "skipped";
1446
+ /** What spawnAgent returns: the registered row plus the handshake outcome. */
1447
+ type SpawnedAgent = AgentRow & {
1448
+ ctl: SpawnCtl;
1449
+ ctlSocket: string;
1450
+ };
1451
+ /**
1452
+ * Spawn a new agent in its tmux pane and register it in the DB.
1453
+ *
1454
+ * Phases:
1455
+ * 1. Validate name + uniqueness.
1456
+ * 2. If --workspace: prestageWorkspace() (placeholder agent row +
1457
+ * workspace dir + workspace row).
1458
+ * 3. createOrReusePane() in the workspace path (or opts.cwd).
1459
+ * 4. setPaneTitle + enableMuPaneBordersForPane.
1460
+ * 5. finalizeAgentRow() — patch placeholder pane_id to real (workspace
1461
+ * path), or insert a fresh agent row (no-workspace path).
1462
+ * 6. awaitSpawnLiveness().
1463
+ *
1464
+ * Failure between any of (3)–(6) calls rollbackSpawn() to undo the
1465
+ * pane + row + workspace. The caller-visible error is preserved.
1466
+ */
1467
+ declare function spawnAgent(db: Db, opts: SpawnAgentOptions): Promise<SpawnedAgent>;
1468
+ /**
1469
+ * Default liveness window in milliseconds. 0 disables the check (useful
1470
+ * for fast tests that don't want to wait). Override via env var
1471
+ * `MU_SPAWN_LIVENESS_MS`.
1472
+ */
1473
+ declare function defaultSpawnLivenessMs(): number;
1474
+
1475
+ interface AdoptAgentOptions {
1476
+ /** tmux pane id (e.g. '%15'). Must already exist on the tmux server. */
1477
+ paneId: string;
1478
+ /** Workstream to adopt the pane into. The pane MUST be in the
1479
+ * matching tmux session (`mu-<workstream>`); cross-session adopt is
1480
+ * rejected. */
1481
+ workstream: string;
1482
+ /** Override the pane's title with this name. When omitted, the pane's
1483
+ * current title becomes the agent name (zero-config adoption). */
1484
+ name?: string;
1485
+ /** Defaults to 'pi' via the schema DEFAULT. */
1486
+ cli?: string;
1487
+ /** 'full-access' (default) or 'read-only'. */
1488
+ role?: string;
1489
+ /** Override the tmux session lookup. Defaults to `mu-<workstream>`. */
1490
+ tmuxSession?: string;
1491
+ }
1492
+ interface AdoptAgentResult {
1493
+ agent: AgentRow;
1494
+ /** True when the pane already had a matching agents row — the call
1495
+ * was a no-op (idempotent). */
1496
+ alreadyAdopted: boolean;
1497
+ /** The pane title before adopt, or null if the pane had no title. */
1498
+ previousTitle: string | null;
1499
+ /** The title the pane was set to (== agent.name post-adopt). Equal to
1500
+ * previousTitle when no retitle happened. */
1501
+ paneTitleSetTo: string;
1502
+ /** One probe of the derived control socket; "skipped" for non-pi CLIs.
1503
+ * An adopted pi pane usually lacks MU_CTL_SOCK, so this is "missing". */
1504
+ ctl: SpawnCtl;
1505
+ ctlSocket: string;
1506
+ }
1507
+ /**
1508
+ * Register an existing tmux pane as a managed mu agent. Inverse of the
1509
+ * 'orphan' state surfaced by `mu agent list`: a pane that looks like an
1510
+ * agent (running pi/claude/codex) but has no DB row.
1511
+ *
1512
+ * Identity contract (matches the claim protocol invariant):
1513
+ * - Post-adopt, the pane's title equals the agent's name.
1514
+ * - When `name` is omitted, the pane's existing title becomes the
1515
+ * agent name verbatim. Adopting a pane titled 'pi' would fail name
1516
+ * validation — caller must supply --name in that case.
1517
+ *
1518
+ * Idempotent: adopting the same pane twice with the same name is a
1519
+ * no-op (returns alreadyAdopted=true). Adopting a different pane under
1520
+ * an existing agent name throws AgentExistsError.
1521
+ *
1522
+ * Validation order (matches the design in note #100):
1523
+ * 1. Pane id format -> assertValidPaneId via paneExists / setPaneTitle
1524
+ * 2. Pane exists -> PaneNotFoundError
1525
+ * 3. Pane is in session -> AgentNotInWorkstreamError (cross-session)
1526
+ * 4. Resolved name OK -> isValidAgentName / Error('agent name invalid')
1527
+ * 5. Idempotent check -> if pane already owned by an agent of this
1528
+ * name, return alreadyAdopted=true
1529
+ * 6. Name not taken -> AgentExistsError (else)
1530
+ * 7. Insert + retitle.
1531
+ *
1532
+ * Status starts at 'free' — reconcile/detect will update it on the next
1533
+ * `mu agent list` based on actual pane content (the pi prompt yields
1534
+ * 'free'; an agent mid-thought yields 'busy'; etc.). We don't run
1535
+ * detection inline here because the caller may not have $TMUX, and
1536
+ * adoption shouldn't depend on a captured-pane probe succeeding.
1537
+ */
1538
+ declare function adoptAgent(db: Db, opts: AdoptAgentOptions): Promise<AdoptAgentResult>;
1539
+
1540
+ /**
1541
+ * Pre-flight failure: the command mu would have spawned in the new
1542
+ * pane doesn't resolve to a binary on PATH (and isn't an absolute /
1543
+ * relative path that exists + is executable). Thrown by `spawnAgent`
1544
+ * BEFORE `prestageWorkspace` so a typo in `--cli` never leaves an
1545
+ * orphan workspace dir behind.
1546
+ *
1547
+ * Source: feedback ws task `fb_agent_spawn_no_validation`. Live
1548
+ * dogfood report: `mu agent spawn worker-1 --cli pi-meta` on a host
1549
+ * where the `pi-meta` binary wasn't on PATH printed `Spawned worker-1
1550
+ * (pi-meta)` and the pane immediately died with `command not found`;
1551
+ * the existing 1.5s liveness check sometimes missed it (the shell
1552
+ * stays alive after the failed exec). Pre-flighting the PATH lookup
1553
+ * surfaces the typo before any side effects (workspace, pane, DB row).
1554
+ *
1555
+ * Distinct from `AgentSpawnStartupError` (pane alive but parked at an
1556
+ * error prompt) and `AgentDiedOnSpawnError` (pane vanished within the
1557
+ * liveness window). All three carry different remediation hints, so
1558
+ * they're separate types.
1559
+ */
1560
+ declare class AgentSpawnCliNotFoundError extends Error implements HasNextSteps {
1561
+ readonly cli: string;
1562
+ /** First whitespace-separated token of the resolved command — the
1563
+ * thing actually missing on PATH. Surfaced verbatim in the
1564
+ * message so the operator sees what mu searched for (which may
1565
+ * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */
1566
+ readonly binary: string;
1567
+ /** Name of the env var that mu consulted before falling back to
1568
+ * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set
1569
+ * to the conventional name so the nextSteps hint can recommend
1570
+ * exporting it. */
1571
+ readonly envVarChecked: string;
1572
+ readonly name = "AgentSpawnCliNotFoundError";
1573
+ constructor(cli: string,
1574
+ /** First whitespace-separated token of the resolved command — the
1575
+ * thing actually missing on PATH. Surfaced verbatim in the
1576
+ * message so the operator sees what mu searched for (which may
1577
+ * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */
1578
+ binary: string,
1579
+ /** Name of the env var that mu consulted before falling back to
1580
+ * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set
1581
+ * to the conventional name so the nextSteps hint can recommend
1582
+ * exporting it. */
1583
+ envVarChecked: string);
1584
+ errorNextSteps(): NextStep[];
1585
+ }
1586
+ declare class AgentExistsError extends Error implements HasNextSteps {
1587
+ readonly agentName: string;
1588
+ readonly name = "AgentExistsError";
1589
+ constructor(agentName: string);
1590
+ errorNextSteps(): NextStep[];
1591
+ }
1592
+ declare class AgentNotFoundError extends Error implements HasNextSteps {
1593
+ readonly agentName: string;
1594
+ /** Optional workstream context. When set, the message is enriched
1595
+ * with `(in workstream <ws>)` so the verb that hit the miss
1596
+ * (e.g. `mu workspace path <agent> -w <ws>`) doesn't leave the
1597
+ * operator guessing which scope was searched. Optional so existing
1598
+ * call sites that only know the agent name keep their original
1599
+ * one-line message. */
1600
+ readonly workstream?: string | undefined;
1601
+ readonly name = "AgentNotFoundError";
1602
+ constructor(agentName: string,
1603
+ /** Optional workstream context. When set, the message is enriched
1604
+ * with `(in workstream <ws>)` so the verb that hit the miss
1605
+ * (e.g. `mu workspace path <agent> -w <ws>`) doesn't leave the
1606
+ * operator guessing which scope was searched. Optional so existing
1607
+ * call sites that only know the agent name keep their original
1608
+ * one-line message. */
1609
+ workstream?: string | undefined);
1610
+ errorNextSteps(): NextStep[];
1611
+ }
1612
+ /**
1613
+ * Thrown when an entity-targeted verb is invoked with `-w/--workstream
1614
+ * <name>` but the named agent lives in a different workstream.
1615
+ * Mirrors `TaskNotInWorkstreamError`. Maps to exit code 4 (conflict /
1616
+ * wrong scope). Distinguishes "the user typo'd the workstream" from
1617
+ * "the agent doesn't exist anywhere" (which surfaces as
1618
+ * `AgentNotFoundError`).
1619
+ */
1620
+ declare class AgentNotInWorkstreamError extends Error implements HasNextSteps {
1621
+ readonly agentName: string;
1622
+ readonly expectedWorkstream: string;
1623
+ readonly actualWorkstream: string;
1624
+ readonly name = "AgentNotInWorkstreamError";
1625
+ constructor(agentName: string, expectedWorkstream: string, actualWorkstream: string);
1626
+ errorNextSteps(): NextStep[];
1627
+ }
1628
+ /**
1629
+ * Thrown when an agent's pane is created and titled successfully but the
1630
+ * spawned process exits within the liveness window (default 1500ms;
1631
+ * configurable via `MU_SPAWN_LIVENESS_MS`). The most common cause is the
1632
+ * underlying CLI failing fast: a wrapper CLI blocking on a single-instance
1633
+ * lock, `claude` rejecting an invalid API key, etc. The agent's last
1634
+ * scrollback (when capturable) is attached to help diagnose.
1635
+ */
1636
+ declare class AgentDiedOnSpawnError extends Error implements HasNextSteps {
1637
+ readonly agentName: string;
1638
+ readonly paneId: string;
1639
+ readonly scrollback: string | undefined;
1640
+ /** The command the pane ran; an `ssh ...` command gets remote hints. */
1641
+ readonly command?: string | undefined;
1642
+ readonly name = "AgentDiedOnSpawnError";
1643
+ /** The spawned command is an ssh hop: remote causes, not wrapper locks. */
1644
+ readonly remote: boolean;
1645
+ constructor(agentName: string, paneId: string, scrollback: string | undefined,
1646
+ /** The command the pane ran; an `ssh ...` command gets remote hints. */
1647
+ command?: string | undefined);
1648
+ errorNextSteps(): NextStep[];
1649
+ }
1650
+ /**
1651
+ * Thrown when an agent's pane is alive AND staying alive after the
1652
+ * liveness window, but its first burst of output matches a known
1653
+ * provider-startup-failure pattern (missing API key, auth rejected, …).
1654
+ * Source: feedback ws task `agent_spawn_model_auth_failure_counts_as_live`.
1655
+ * Live dogfood report: `pi-meta --no-solo --model sonnet:high` printed
1656
+ * `Error: No API key found for amazon-bedrock` and parked at a prompt.
1657
+ * The pane stayed alive (1.5s liveness check passed) but the worker
1658
+ * could never do work — the orchestrator only discovered this when
1659
+ * `mu task wait` stalled minutes later.
1660
+ *
1661
+ * Distinct from `AgentDiedOnSpawnError`:
1662
+ * - `AgentDiedOnSpawnError` → pane vanished within the liveness window
1663
+ * (CLI exited fast).
1664
+ * - `AgentSpawnStartupError` → pane alive, but the captured scrollback
1665
+ * tail contains a curated provider-auth-failure pattern.
1666
+ * The two carry different remediation hints (CLI override vs. fix the
1667
+ * env var), so they're separate types instead of one with a flag.
1668
+ *
1669
+ * The pattern list is curated and short to keep false-positive risk low
1670
+ * — the scan only looks at the last ~30 lines of the 50-line capture
1671
+ * taken right after the liveness sleep, so matches naturally come from
1672
+ * the CLI's first ~1.5s of output (not arbitrary later prompts the
1673
+ * agent might type into).
1674
+ */
1675
+ declare class AgentSpawnStartupError extends Error implements HasNextSteps {
1676
+ readonly agentName: string;
1677
+ readonly paneId: string;
1678
+ /** The single scrollback line that matched a known startup-error
1679
+ * pattern. Surfaced verbatim in the message so the operator sees
1680
+ * what mu saw. */
1681
+ readonly matchedLine: string;
1682
+ /** Full captured scrollback (tail-trimmed already by
1683
+ * awaitSpawnLiveness). Attached to the message for context. */
1684
+ readonly scrollback: string;
1685
+ readonly name = "AgentSpawnStartupError";
1686
+ constructor(agentName: string, paneId: string,
1687
+ /** The single scrollback line that matched a known startup-error
1688
+ * pattern. Surfaced verbatim in the message so the operator sees
1689
+ * what mu saw. */
1690
+ matchedLine: string,
1691
+ /** Full captured scrollback (tail-trimmed already by
1692
+ * awaitSpawnLiveness). Attached to the message for context. */
1693
+ scrollback: string);
1694
+ errorNextSteps(): NextStep[];
1695
+ }
1696
+ /**
1697
+ * Thrown when `closeAgent` is called on an agent that has an associated
1698
+ * workspace AND the caller didn't explicitly opt into discarding it.
1699
+ *
1700
+ * Background: the FK on `vcs_workspaces.agent` cascades on agent
1701
+ * delete, so a naive `closeAgent` drops the workspace registry row
1702
+ * but leaves the on-disk dir orphaned (mu can't see it via
1703
+ * `mu workspace list / free / path` afterwards). Surfaced during
1704
+ * the multi-agent dogfood teardown when three workspaces went
1705
+ * orphaned silently.
1706
+ *
1707
+ * The fix: refuse close if a workspace exists; force the caller to
1708
+ * decide. Two actionable resolutions:
1709
+ * - `mu workspace free <agent>` first, then close cleanly.
1710
+ * - `mu agent close <agent> --discard-workspace` to free the
1711
+ * workspace AND close the agent in one shot (lossy: pending
1712
+ * changes in the workspace are gone).
1713
+ *
1714
+ * Maps to exit code 4 (conflict) via the cli.ts handler.
1715
+ */
1716
+ declare class WorkspacePreservedError extends Error implements HasNextSteps {
1717
+ readonly agentName: string;
1718
+ readonly workspacePath: string;
1719
+ readonly name = "WorkspacePreservedError";
1720
+ constructor(agentName: string, workspacePath: string);
1721
+ errorNextSteps(): NextStep[];
1722
+ }
1723
+ /**
1724
+ * The agent's pane and pi are up, but nothing answers on its control
1725
+ * socket (`MU_CTL_SOCK`): the mu pi extension is not loaded, or, for a
1726
+ * remote agent, the ssh command lacks the `-L` forward. Spawn does not
1727
+ * throw it — the agent is usable by hand — but prints it as a warning;
1728
+ * verbs that need the socket throw it.
1729
+ */
1730
+ declare class AgentCtlUnreachableError extends Error implements HasNextSteps {
1731
+ readonly agentName: string;
1732
+ readonly workstream: string;
1733
+ readonly socket: string;
1734
+ readonly kind: "missing" | "refused";
1735
+ readonly name = "AgentCtlUnreachableError";
1736
+ constructor(agentName: string, workstream: string, socket: string, kind: "missing" | "refused");
1737
+ errorNextSteps(): NextStep[];
1738
+ }
1665
1739
  /**
1666
- * Resolve the actual executable to launch in an agent's pane for a given
1667
- * `cli`. Honours the env var `MU_<UPPER_CLI>_COMMAND` (e.g. `MU_PI_COMMAND=
1668
- * pi-alt` makes `--cli pi` actually exec `pi-alt`). Falls back to the cli
1669
- * name itself, which is what users expect when their `pi` binary is on
1670
- * `$PATH` under that exact name.
1671
- *
1672
- * Used by `spawnAgent` to pick the spawned command, and by reconcile's
1673
- * orphan detector so externally-spawned panes running the resolved binary
1674
- * are still recognised as agents.
1740
+ * `mu agent abort` on an agent that does not run pi: there is no control
1741
+ * socket to carry the abort, and mu does not silently signal the pane.
1675
1742
  */
1676
- declare function resolveCliCommand(cli: string): string;
1743
+ declare class AgentAbortNeedsCtlError extends Error implements HasNextSteps {
1744
+ readonly agentName: string;
1745
+ readonly workstream: string;
1746
+ readonly cli: string;
1747
+ readonly name = "AgentAbortNeedsCtlError";
1748
+ constructor(agentName: string, workstream: string, cli: string);
1749
+ errorNextSteps(): NextStep[];
1750
+ }
1751
+ /** The abort was delivered but pi did not settle within the timeout. */
1752
+ declare class AgentAbortTimeoutError extends Error implements HasNextSteps {
1753
+ readonly agentName: string;
1754
+ readonly workstream: string;
1755
+ readonly timeoutMs: number;
1756
+ readonly name = "AgentAbortTimeoutError";
1757
+ constructor(agentName: string, workstream: string, timeoutMs: number);
1758
+ errorNextSteps(): NextStep[];
1759
+ }
1677
1760
  /**
1678
- * Compute the `MU_<UPPER_CLI>_COMMAND` env var name mu consults when
1679
- * resolving `--cli <key>`. Hyphens in the cli key become underscores
1680
- * (env var names can't contain `-`); this matches the operator-aliases
1681
- * convention documented in the mu skill (e.g. `--cli pi-meta` →
1682
- * `MU_PI_META_COMMAND`).
1761
+ * `mu agent send --fresh` on an agent without a control socket (non-pi
1762
+ * CLI, or forced `--via mux`): there is no one-operation new session.
1683
1763
  */
1684
- declare function envVarNameForCli(cli: string): string;
1764
+ declare class AgentFreshNeedsCtlError extends Error implements HasNextSteps {
1765
+ readonly agentName: string;
1766
+ readonly workstream: string;
1767
+ readonly cli: string;
1768
+ readonly name = "AgentFreshNeedsCtlError";
1769
+ constructor(agentName: string, workstream: string, cli: string);
1770
+ errorNextSteps(): NextStep[];
1771
+ }
1772
+ /** `send --fresh` refused: pi is mid-turn, and a fresh session would abandon it. */
1773
+ declare class AgentBusyError extends Error implements HasNextSteps {
1774
+ readonly agentName: string;
1775
+ readonly workstream: string;
1776
+ readonly name = "AgentBusyError";
1777
+ constructor(agentName: string, workstream: string);
1778
+ errorNextSteps(): NextStep[];
1779
+ }
1780
+
1781
+ /** The signal set kick supports. SIGINT is graceful (matches Ctrl-C
1782
+ * semantics — what the operator probably wanted in the first place);
1783
+ * SIGTERM is the polite escalation; SIGKILL is the unblockable
1784
+ * hammer. We deliberately don't expose arbitrary signals — the
1785
+ * three above are the actionable ones for "interrupt a wedged
1786
+ * foreground tool subprocess." */
1787
+ type KickSignal = "SIGINT" | "SIGTERM" | "SIGKILL";
1788
+ declare function isKickSignal(s: string): s is KickSignal;
1685
1789
  /**
1686
- * Resolve `--cli <key>` to its actual command string AND tell the
1687
- * caller whether the resolution came from a `MU_<UPPER_CLI>_COMMAND`
1688
- * env var or fell through to the bare cli name. The CLI uses this to
1689
- * surface env-var attribution in the spawn-success line so config
1690
- * issues are visible without `mu agent show`
1691
- * (fb_agent_spawn_no_validation, part C).
1790
+ * Thrown when the foreground pgid lookup on a pane's TTY yields
1791
+ * either no rows at all (the pane is sitting at an idle shell with
1792
+ * no foreground job) or only the wrapping shell itself (the LLM CLI
1793
+ * — pi/claude/codex — is the foreground; signalling it would close
1794
+ * the agent, which is what `mu agent close` is for).
1795
+ *
1796
+ * Maps to the generic exit code 1 in handle.ts (this is a
1797
+ * runtime-state condition, not a typed not-found / conflict).
1692
1798
  */
1693
- declare function resolveCliCommandWithSource(cli: string): {
1694
- command: string;
1695
- envVar: string;
1696
- resolvedFromEnv: boolean;
1697
- };
1698
- interface CommandResolutionResult {
1699
- ok: boolean;
1700
- /** First whitespace-separated token of the command — the binary
1701
- * whose presence on PATH we checked. */
1702
- binary: string;
1703
- /** Absolute path of the resolved binary on PATH, when ok=true. */
1704
- resolvedPath?: string;
1799
+ declare class NoForegroundProcessError extends Error implements HasNextSteps {
1800
+ readonly agentName: string;
1801
+ readonly tty: string;
1802
+ readonly reason: "no-foreground" | "shell-only";
1803
+ readonly name = "NoForegroundProcessError";
1804
+ constructor(agentName: string, tty: string, reason: "no-foreground" | "shell-only");
1805
+ errorNextSteps(): NextStep[];
1806
+ }
1807
+ interface KickProcessExecResult {
1808
+ stdout: string;
1809
+ stderr: string;
1810
+ exitCode: number | null;
1811
+ }
1812
+ type KickProcessExecutor = (cmd: string, args: readonly string[]) => Promise<KickProcessExecResult>;
1813
+ /** Install a custom executor (for tests). Returns the previous one so
1814
+ * tests can restore cleanly. */
1815
+ declare function setKickProcessExecutor(executor: KickProcessExecutor): KickProcessExecutor;
1816
+ /** Restore the real executor. */
1817
+ declare function resetKickProcessExecutor(): void;
1818
+ interface PsRow {
1819
+ pid: number;
1820
+ pgid: number;
1821
+ /** ps's `stat` (or `state`) field. The presence of `+` means
1822
+ * "foreground process group on its controlling tty". */
1823
+ stat: string;
1824
+ /** Process command (just the comm; truncated, used for diagnostics). */
1825
+ comm: string;
1705
1826
  }
1706
- type CommandResolver = (command: string) => Promise<CommandResolutionResult>;
1707
- /** Override the PATH resolver. Tests use this to simulate "binary
1708
- * absent" / "binary present" without depending on what's actually
1709
- * installed. Production callers should never touch this. */
1710
- declare function setCommandResolverForTests(resolver: CommandResolver): void;
1711
- /** Restore the default PATH resolver. */
1712
- declare function resetCommandResolverForTests(): void;
1713
1827
  /**
1714
- * Verify the first token of `command` resolves to a binary on PATH.
1715
- * Public so tests can call it directly; spawnAgent calls it before
1716
- * prestageWorkspace so a bad --cli never creates an orphan workspace.
1828
+ * Parse `ps -t <tty> -o pid=,pgid=,stat=,comm=` output. Each non-blank
1829
+ * line is one process: four whitespace-separated fields. Defensive
1830
+ * about leading whitespace and command names with embedded spaces
1831
+ * (the comm is the LAST field — join the tail).
1717
1832
  */
1718
- declare function checkCommandResolvable(command: string): Promise<CommandResolutionResult>;
1719
- interface SpawnAgentOptions {
1720
- name: string;
1833
+ declare function parsePsTtyOutput(output: string): PsRow[];
1834
+ /**
1835
+ * Resolve the foreground process group id for a TTY device path. The
1836
+ * canonical signal `ps`'s `stat` field uses is `+` (BSD/Darwin AND
1837
+ * Linux procps). We pick the first row whose stat contains `+`; its
1838
+ * `pgid` is the foreground pgid of that controlling terminal.
1839
+ *
1840
+ * Returns:
1841
+ * - `{ kind: "ok", pgid, fgRow }` on success
1842
+ * - `{ kind: "no-foreground" }` when no row carries `+` AND there
1843
+ * are no candidate rows at all
1844
+ * - `{ kind: "shell-only", pgid, fgRow }` when the foreground pgid
1845
+ * resolves to a shell whose comm is the agent's wrapping CLI
1846
+ * (caller decides whether to refuse — kick refuses)
1847
+ *
1848
+ * The wrapping-CLI guard is intentionally narrow: we only refuse
1849
+ * when the foreground process command matches one of the known
1850
+ * pi/claude/codex/zsh/bash shapes. Anything else (a `find`, a
1851
+ * `cargo build`, a `python script.py`) is exactly what we want to
1852
+ * signal — that's the unbounded-tool case the verb was built for.
1853
+ */
1854
+ interface ForegroundLookup {
1855
+ kind: "ok" | "shell-only" | "no-foreground";
1856
+ pgid?: number;
1857
+ fgRow?: PsRow;
1858
+ /** All rows ps returned for the tty, for diagnostics / tests. */
1859
+ rows: PsRow[];
1860
+ }
1861
+ declare function foregroundPgid(tty: string): Promise<ForegroundLookup>;
1862
+ interface KickAgentOptions {
1721
1863
  workstream: string;
1722
- /** Defaults to "pi". 0.1.0 only really supports "pi" but the column
1723
- * accepts any string for forward-compat with future multi-CLI support
1724
- * (claude/codex). */
1725
- cli?: string;
1726
- /** The actual command to run in the pane. Defaults to the cli value. */
1727
- command?: string;
1728
- /** Window name to group this agent under. Defaults to the agent's name
1729
- * (so each agent gets its own window). Multiple agents sharing a `tab`
1730
- * share a window with multiple panes. */
1731
- tab?: string;
1732
- /** "full-access" (default) or "read-only". The schema stores it; today
1733
- * the role isn't enforced (deferred to a future capabilities pass). */
1734
- role?: string;
1735
- /** Initial working directory for the spawned pane (`tmux -c <path>`).
1736
- * When `workspace: true` is passed, this is ignored — the workspace
1737
- * path is used instead. */
1738
- cwd?: string;
1739
- /** Override the tmux session name. Defaults to `mu-<workstream>`. */
1740
- tmuxSession?: string;
1741
- /** Auto-create a VCS workspace for this agent before spawning the
1742
- * pane and use the workspace path as cwd. Backend defaults to
1743
- * detection (jj > sl > git > none). */
1744
- workspace?: boolean;
1745
- /** Force a specific VCS backend (only meaningful with `workspace: true`). */
1746
- workspaceBackend?: VcsBackendName;
1747
- /** Optional ref to base the workspace on (only meaningful with
1748
- * `workspace: true`). Backend-specific. */
1749
- workspaceFrom?: string;
1750
- /** Project root the workspace branches from (only meaningful with
1751
- * `workspace: true`). Defaults to `process.cwd()`. */
1752
- workspaceProjectRoot?: string;
1864
+ /** Defaults to SIGINT (matches Ctrl-C semantics). */
1865
+ signal?: KickSignal;
1866
+ }
1867
+ interface KickAgentResult {
1868
+ agentName: string;
1869
+ paneId: string;
1870
+ /** TTY device path the foreground pgid was resolved against. */
1871
+ tty: string;
1872
+ /** The pgid we signalled. */
1873
+ signaledPgid: number;
1874
+ signal: KickSignal;
1875
+ /** The comm of the foreground process at the time of signal — useful
1876
+ * diagnostic in the event log ("we kicked a `find`, not a `cargo`"). */
1877
+ foregroundComm: string;
1753
1878
  }
1754
1879
  /**
1755
- * Spawn a new agent in its tmux pane and register it in the DB.
1880
+ * Send `signal` to the foreground process group of an agent's pane
1881
+ * TTY. Default signal is SIGINT.
1756
1882
  *
1757
- * Phases:
1758
- * 1. Validate name + uniqueness.
1759
- * 2. If --workspace: prestageWorkspace() (placeholder agent row +
1760
- * workspace dir + workspace row).
1761
- * 3. createOrReusePane() in the workspace path (or opts.cwd).
1762
- * 4. setPaneTitle + enableMuPaneBordersForPane.
1763
- * 5. finalizeAgentRow() — patch placeholder pane_id to real (workspace
1764
- * path), or insert a fresh agent row (no-workspace path).
1765
- * 6. awaitSpawnLiveness().
1883
+ * Errors:
1884
+ * - `AgentNotFoundError` — the agent doesn't exist in this workstream.
1885
+ * - `PaneNotFoundError` (from paneTTY) — the agent's pane has vanished.
1886
+ * - `NoForegroundProcessError` — pane has no foreground job, OR the
1887
+ * foreground is the wrapping CLI itself (refuse; use `mu agent close`).
1766
1888
  *
1767
- * Failure between any of (3)–(6) calls rollbackSpawn() to undo the
1768
- * pane + row + workspace. The caller-visible error is preserved.
1889
+ * Emits an `agent kick <name> (signal=..., pgid=..., comm=...)` event
1890
+ * on success.
1769
1891
  */
1770
- declare function spawnAgent(db: Db, opts: SpawnAgentOptions): Promise<AgentRow>;
1892
+ declare function kickAgent(db: Db, name: string, opts: KickAgentOptions): Promise<KickAgentResult>;
1893
+
1894
+ type Transport = "ctl" | "mux";
1895
+ type SendResult = {
1896
+ transport: Transport;
1897
+ state?: CtlState;
1898
+ };
1899
+ type TransportSendOptions = SendOptions & {
1900
+ /** How a busy pi queues the message. Default followUp. ctl only. */
1901
+ mode?: "steer" | "followUp";
1902
+ /** Force a transport instead of choosing by agent and text. */
1903
+ via?: Transport;
1904
+ /** Control socket path. Default: agentCtlSocket(db, agent). */
1905
+ socket?: string;
1906
+ /** Start a new pi session and send the text into it, as one ctl op. */
1907
+ fresh?: boolean;
1908
+ /** With fresh: abandon a running turn instead of refusing. */
1909
+ force?: boolean;
1910
+ };
1771
1911
  /**
1772
- * Default liveness window in milliseconds. 0 disables the check (useful
1773
- * for fast tests that don't want to wait). Override via env var
1774
- * `MU_SPAWN_LIVENESS_MS`.
1775
- */
1776
- declare function defaultSpawnLivenessMs(): number;
1912
+ * True when the agent runs pi, so sends go through its control socket.
1913
+ * The cli key decides when it names pi (or MU_<KEY>_COMMAND here runs
1914
+ * pi). Otherwise the agent's own socket file decides: spawn removes any
1915
+ * stale file before the pane starts, so a file at the derived path was
1916
+ * bound by this agent's extension (or its ssh forward). That covers
1917
+ * `--cli helper --command "pi-meta ..."`, whose command is not stored,
1918
+ * without a schema change. `sock` defaults to the derived path; pass
1919
+ * agentCtlSocket(db, agent) when a DB is at hand.
1920
+ */
1921
+ declare function expectsCtl(agent: Pick<AgentRow, "cli" | "name" | "workstreamName">, sock?: string): boolean;
1922
+ declare function sendViaTransport(agent: AgentRow, text: string, opts: TransportSendOptions & {
1923
+ socket: string;
1924
+ }): Promise<SendResult>;
1777
1925
 
1778
1926
  interface AgentRow {
1779
1927
  name: string;
@@ -1792,6 +1940,8 @@ interface AgentRow {
1792
1940
  interface LiveAgent extends AgentRow {
1793
1941
  state: RuntimeState;
1794
1942
  source: StateSource;
1943
+ /** Control-socket link: ok / missing / refused for pi agents, n/a otherwise. */
1944
+ ctl?: CtlLink;
1795
1945
  /** ISO 8601 time when the source entered this state. */
1796
1946
  since: string | null;
1797
1947
  reason?: string;
@@ -1846,12 +1996,14 @@ declare function refreshAgentTitle(db: Db, agentName: string, workstream: string
1846
1996
  declare function deleteAgent(db: Db, name: string, workstream: string): boolean;
1847
1997
  declare function isValidAgentName(name: string): boolean;
1848
1998
  /**
1849
- * Send a single line of text to an agent's pane and submit it. Uses the
1850
- * canonical bracketed-paste protocol from src/tmux.ts.
1999
+ * Send text to an agent and submit it. A pi agent gets it through its
2000
+ * control socket (AgentCtlUnreachableError when that does not answer —
2001
+ * never a silent paste); a non-pi CLI, a slash command, or `via: "mux"`
2002
+ * goes through the mux paste path. See src/agents/transport.ts.
1851
2003
  */
1852
- declare function sendToAgent(db: Db, name: string, text: string, opts: SendOptions & {
2004
+ declare function sendToAgent(db: Db, name: string, text: string, opts: TransportSendOptions & {
1853
2005
  workstream: string;
1854
- }): Promise<void>;
2006
+ }): Promise<SendResult>;
1855
2007
  /**
1856
2008
  * Read scrollback from an agent's pane. With no options, returns the full
1857
2009
  * scrollback (`-S - -E -`); with `lines: N`, returns only the last N lines.
@@ -3348,6 +3500,56 @@ declare function loadFullDag(db: Db, workstream: string, opts?: LoadFullDagOptio
3348
3500
  declare function renderForest(roots: readonly TaskRow[], edges: ReadonlyMap<string, readonly string[]>, statusFn: TaskStatusLabelFn, tasksByName?: ReadonlyMap<string, TaskRow>, opts?: RenderTreeOptions): string;
3349
3501
  declare function renderTaskTree(db: Db, workstream: string, root: TaskRow, direction: "blockers" | "dependents", statusFn: TaskStatusLabelFn, opts?: RenderTreeOptions): string;
3350
3502
 
3503
+ /**
3504
+ * Identifies a generated shim on its own line, so re-linking (and
3505
+ * `inspectLinks`) can tell it from an older inlined copy without
3506
+ * depending on the import statement's exact shape.
3507
+ */
3508
+ declare const MU_SHIM_MARKER = "// mu:shim";
3509
+ type LinkState = "ok" | "missing" | "stale-copy" | "foreign" | "dangling";
3510
+ interface LinkStatus {
3511
+ path: string;
3512
+ state: LinkState;
3513
+ target?: string;
3514
+ }
3515
+ interface LinkOptions {
3516
+ /** Root under which `.pi/` and `.agents/` live. Default `MU_PI_HOME ?? homedir()`. */
3517
+ home?: string;
3518
+ }
3519
+ /** Raised when the destination holds something mu did not put there
3520
+ * (a real directory, or a symlink to another checkout without
3521
+ * `--force`). mu never deletes user files to make room. */
3522
+ declare class LinkConflictError extends Error implements HasNextSteps {
3523
+ readonly path: string;
3524
+ readonly reason: "not-a-symlink" | "foreign-symlink";
3525
+ readonly current?: string | undefined;
3526
+ constructor(path: string, reason: "not-a-symlink" | "foreign-symlink", current?: string | undefined);
3527
+ errorNextSteps(): NextStep[];
3528
+ }
3529
+ /** Install the extension shim (or, with `copy`, an inlined copy pinned
3530
+ * to this version). `replacedCopy` is true when an existing file without
3531
+ * the shim marker was overwritten. */
3532
+ declare function linkPi(opts?: LinkOptions & {
3533
+ copy?: boolean;
3534
+ }): {
3535
+ path: string;
3536
+ replacedCopy: boolean;
3537
+ };
3538
+ /** Symlink `<home>/.agents/skills/mu` to the package's `skills/mu`.
3539
+ * `previous` is the old link target when one was replaced. */
3540
+ declare function linkSkill(opts?: LinkOptions & {
3541
+ force?: boolean;
3542
+ }): {
3543
+ path: string;
3544
+ target: string;
3545
+ previous?: string;
3546
+ };
3547
+ /** Report the state of both installs without changing anything. */
3548
+ declare function inspectLinks(opts?: LinkOptions): {
3549
+ extension: LinkStatus;
3550
+ skill: LinkStatus;
3551
+ };
3552
+
3351
3553
  type LogKind = "message" | "event" | "broadcast" | string;
3352
3554
  interface LogRow {
3353
3555
  /** Monotonic AUTOINCREMENT id. Use as the cursor for `--since`. */
@@ -4979,4 +5181,4 @@ declare function summarizeWorkstream(db: Db, opts: WorkstreamOptions): Promise<W
4979
5181
  */
4980
5182
  declare function teardownWorkstream(db: Db, opts: TeardownWorkstreamOptions): Promise<TeardownResult>;
4981
5183
 
4982
- export { AGENT_STATE_GLYPH, type AddNoteOptions, type AddTaskOptions, type AdoptAgentOptions, type AdoptAgentResult, AgentDiedOnSpawnError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, type AgentRow, AgentSpawnCliNotFoundError, AgentSpawnStartupError, type AmbientOptions, type AmbientResult, type AppendLogOptions, type ApplyResult, type BlockEdgeResult, CAPTURE_TRIGGER_DDL, CURRENT_SCHEMA_VERSION, type CaptureDb, type CaptureIntent, type CaptureOptions, type CaseCollision, type CheapDriftReport, type ClaimResult, type ClaimTaskOptions, ClaimerNotRegisteredError, type CloseAgentOptions, type CloseAgentResult, type CloseSubstate, type CloseTaskResult, type CommandResolutionResult, type CommandResolver, CrossWorkstreamEdgeError, CycleError, DEFAULT_SUBSTATE, DRIFT_REPORT_CAP, type Db, type DeleteTaskResult, type DoctorCheck, type DoctorStatus, type DoctorSummary, DriftDetectedError, type DriftRecord, type DriftReport, EXPECTED_TABLES, type EvidenceOption, type FileLockOptions, type FleetHazard, type FlushResult, type FsProbe, type FullDag, GLYPH, type GroupSummary, type HazardSeverity, type Hlc, HlcOverflowError, HlcParseError, HomeDirAsProjectRootError, type IdFromTitleResult, type IngestFromDbResult, type IngestResult, type InsertAgentInput, InvalidSubstateError, type InverseOp, KNOWN_INTENTS, KNOWN_VERBS, type KickAgentOptions, type KickAgentResult, type KickProcessExecutor, type KickSignal, type KnownIntent, type ListLiveAgentsOptions, type ListLogsOptions, type ListNotesOptions, type ListReadyOptions, type ListTasksOptions, type LiveAgent, type LiveAgentsView, type LoadFullDagOptions, type LoadWorkstreamSnapshotOptions, type LocalIntent, type LogKind, type LogRow, MACHINE_LOCAL_ENTITIES, MACHINE_LOCAL_TABLES, MachineIdentityMissingError, type MachineLocalEntity, type MachineLocalLoss, type MachineLocalTable, type MuxBackend, type MuxBackendName, MuxError, type MuxPane, type MuxPaneStatus, type MuxSession, type MuxWindow, type NewSessionOptions, type NewWindowOptions, NoForegroundProcessError, NoMultiplexerError, NothingToUndoError, OP_CTX_DDL, type Op, type OpContext, OpEntityNotSyncedError, OpKeyMalformedError, type OpenDbOptions, type OwnedTasksSummary, PANE_ID_RE, PEER_SHORT_LEN, PEER_STALE_MS, PORTABLE_TABLES, PaneNotFoundError, type ParkTaskOptions, type PeerSegment, type PeerStatus, type PortableTable, type RebuildOptions, type RebuildReport, RebuildTargetExistsError, RebuildTargetIsSourceError, type ReconcileMode, type ReconcileOptions, type ReconcileReport, type ReleaseResult, type ReleaseTaskOptions, type RemoveBlockEdgeResult, type RenderableOp, type RenderedOp, type ReparentTaskResult, type RoiBucket, type RuntimeState, SEGMENT_FORMAT_VERSION, SYNCED_ENTITIES, SchemaTooOldError, type SegmentDefect, type SegmentDefectKind, type SegmentLine, type SegmentManifest, type SendOptions, type SendWarning, type SetStatusOptions, type SetStatusResult, type SlugifyResult, type SpawnAgentOptions, type SplitWindowOptions, type StateReading, type StateSource, type StrandedWorkspaceOrphan, SubstateReasonRequiredError, type SyncPassResult, SyncPeerNotFoundError, SyncPeerRefAmbiguousError, SyncSourceNotFoundError, type SyncedEntity, TASK_STATUSES, TASK_STATUS_LIST, TASK_SUBSTATES, TASK_SUBSTATE_ROWS, TaskAlreadyOwnedError, type TaskEdgeWithStatus, type TaskEdges, type TaskEdgesWithStatus, TaskExistsError, TaskNotFoundError, TaskNotInWorkstreamError, type TaskNoteRow, type TaskPair, TaskParkStateError, TaskParkedError, type TaskRow, type TaskStatus, type TaskSubstate, type TaskWaitOptions, type TaskWaitRef, type TaskWaitResult, type TaskWaitTaskState, type TeardownResult, TmuxError, type TmuxExecResult, type TmuxExecutor, type TmuxPane, type TmuxSession, type TmuxWindow, type Track, UNKNOWN_REASON, UndoGroupNotFoundError, type UndoOptions, type UndoPlan, type UndoResult, UndoSupersededError, type UpdateTaskOptions, type UpdateTaskResult, type VcsBackend, type VcsBackendName, WORKSPACE_STALE_THRESHOLD, WorkspaceExistsError, type WorkspaceFailure, WorkspaceNotFoundError, type WorkspaceOrphan, WorkspacePathNotEmptyError, WorkspacePreservedError, type WorkspaceRow, type WorkspaceStaleness, WorkstreamExistsError, WorkstreamNameInvalidError, type WorkstreamOptions, type WorkstreamSnapshot, type WorkstreamSnapshotSlowFields, type WorkstreamSummary, activeMux, addBlockEdge, addNote, addTask, adoptAgent, agentKey, agentStateGlyph, agentStateHistogram, ambientFlush, ambientIngest, ambientSyncPass, appendLog, applyIncomingOp, applyOp, applyOps, assertValidPaneId, awaitPaneQuiescence, backendByName, capturePane, checkCaseCollisions, checkCheapDriftInvariant, checkCommandResolvable, checkDbInsideSyncDir, checkDrift, checkFleetHazards, checkNetworkMount, claimTask, classifyFsType, closeAgent, closeTask, compareHlc, composeAgentTitle, countProblems, createWorkspace, currentAgentName, currentOpContext, currentPaneTitle, decorateWithDirty, decorateWithStaleness, defaultDbPath, defaultSendDelayMs, defaultSendReadinessMs, defaultSpawnLivenessMs, defaultStateDir, deleteAgent, deleteTask, detectBackend, detectMux, discoverPeers, driftRemediation, emitEvent, ensureWorkstream, envVarNameForCli, findCaseCollisions, flushSegment, foregroundPgid, formatDriftRecord, formatHlc, formatPair, freeWorkspace, getAgent, getAgentByPane, getParallelTracks, getPrerequisites, getTask, getTaskEdges, getTaskEdgesWithStatus, getWaitPollCount, getWatermark, getWorkspaceForAgent, getWorkspaceStaleness, gitBackend, hasWorkMarker, idFromTitle, idFromTitleVerbose, ingestFromDb, ingestSegment, insertAgent, installCapture, isKickSignal, isPathInside, isTaskStatus, isValidAgentName, isValidPair, isValidPaneId, isValidTaskId, isValidWorkstreamName, isWorkspaceStale, jjBackend, kickAgent, killPane, killSession, latestSeq, listAgents, listAllOrphanWorkspaces, listBlocked, listGoals, listInProgress, listLiveAgents, listLogs, listNotes, listPanes, listPanesInSession, listReady, listRecentClosed, listRecentGroups, listSessions, listTasks, listTasksByOwner, listWindows, listWorkspaceOrphans, listWorkspaces, listWorkstreams, loadDoctorChecks, loadDoctorSummary, loadFullDag, loadWorkstreamSnapshot, loadWorkstreamSnapshotFast, loadWorkstreamSnapshotSlow, localMachineId, locksDir, mapLegacyStatus, mergeSnapshotFastSlow, mostRecentGroup, murmurAvailable, mux, muxByName, newSession, newSessionWithPane, newWindow, nextHlc, noneBackend, opSubject, openDb, openTask, paneExists, paneTTY, parkTask, parseAgentNameFromTitle, parseHlc, parseOpKey, parsePsTtyOutput, peerStatuses, planUndo, priorFieldValue, probeFilesystem, readAgent, readAgentStates, readFileLockMeta, readManifest, rebuildInto, receiveHlc, reconcile, refreshAgentTitle, releaseTask, remediationParagraph, removeBlockEdge, renderForest, renderOp, renderOpLine, renderTaskTree, repairPeer, reparentTask, resetCommandResolverForTests, resetKickProcessExecutor, resetMux, resetSleep, resetTmuxExecutor, resetWaitPollCount, resetWatermark, resolveActorIdentity, resolveCliCommand, resolveCliCommandWithSource, resolveGroupId, resolvePair, resolvePeerRef, roiBucket, segmentLineCount, segmentPath, selectLayout, sendToAgent, sendToPane, sessionExists, setCommandResolverForTests, setKickProcessExecutor, setMuxForTests, setPaneTitle, setSleepForTests, setTaskStatus, setTmuxExecutor, setWaitSleepForTests, setWaitStuckWarnForTests, setWatermark, slBackend, sleep, slugifyTitle, slugifyTitleVerbose, spawnAgent, splitWindow, summarizeOwnedTasks, summarizeWorkstream, syncDir, syncEnabled, syncPass, teardownWorkstream, tmux, tmuxBackend, transportNextSteps, undoGroup, unparkTask, updateTask, verifyAgainstManifest, waitForTasks, withCaptureSuppressed, withFileLock, withOpContext, workspacePath, workspacesRoot, yankCommandForCheck };
5184
+ export { AGENT_STATE_GLYPH, type AbortAgentOptions, type AbortResult, type AddNoteOptions, type AddTaskOptions, type AdoptAgentOptions, type AdoptAgentResult, AgentAbortNeedsCtlError, AgentAbortTimeoutError, AgentBusyError, AgentCtlUnreachableError, AgentDiedOnSpawnError, AgentExistsError, AgentFreshNeedsCtlError, AgentNotFoundError, AgentNotInWorkstreamError, type AgentRow, AgentSpawnCliNotFoundError, AgentSpawnStartupError, type AmbientOptions, type AmbientResult, type AppendLogOptions, type ApplyResult, type BlockEdgeResult, CAPTURE_TRIGGER_DDL, CURRENT_SCHEMA_VERSION, type CaptureDb, type CaptureIntent, type CaptureOptions, type CaseCollision, type CheapDriftReport, type ClaimResult, type ClaimTaskOptions, ClaimerNotRegisteredError, type CloseAgentOptions, type CloseAgentResult, type CloseSubstate, type CloseTaskResult, type CommandResolutionResult, type CommandResolver, CrossWorkstreamEdgeError, type CtlLink, CycleError, DEFAULT_SUBSTATE, DRIFT_REPORT_CAP, type Db, type DeleteTaskResult, type DoctorCheck, type DoctorStatus, type DoctorSummary, DriftDetectedError, type DriftRecord, type DriftReport, EXPECTED_TABLES, type EvidenceOption, type FileLockOptions, type FleetHazard, type FlushResult, type FsProbe, type FullDag, GLYPH, type GroupSummary, type HazardSeverity, type Hlc, HlcOverflowError, HlcParseError, HomeDirAsProjectRootError, type IdFromTitleResult, type IngestFromDbResult, type IngestResult, type InsertAgentInput, InvalidSubstateError, type InverseOp, KNOWN_INTENTS, KNOWN_VERBS, type KickAgentOptions, type KickAgentResult, type KickProcessExecutor, type KickSignal, type KnownIntent, LinkConflictError, type LinkState, type LinkStatus, type ListLiveAgentsOptions, type ListLogsOptions, type ListNotesOptions, type ListReadyOptions, type ListTasksOptions, type LiveAgent, type LiveAgentsView, type LoadFullDagOptions, type LoadWorkstreamSnapshotOptions, type LocalIntent, type LogKind, type LogRow, MACHINE_LOCAL_ENTITIES, MACHINE_LOCAL_TABLES, MU_SHIM_MARKER, MachineIdentityMissingError, type MachineLocalEntity, type MachineLocalLoss, type MachineLocalTable, type MuxBackend, type MuxBackendName, MuxError, type MuxPane, type MuxPaneStatus, type MuxSession, type MuxWindow, type NewSessionOptions, type NewWindowOptions, NoForegroundProcessError, NoMultiplexerError, NothingToUndoError, OP_CTX_DDL, type Op, type OpContext, OpEntityNotSyncedError, OpKeyMalformedError, type OpenDbOptions, type OwnedTasksSummary, PANE_ID_RE, PEER_SHORT_LEN, PEER_STALE_MS, PORTABLE_TABLES, PaneNotFoundError, type ParkTaskOptions, type PeerSegment, type PeerStatus, type PortableTable, type RebuildOptions, type RebuildReport, RebuildTargetExistsError, RebuildTargetIsSourceError, type ReconcileMode, type ReconcileOptions, type ReconcileReport, type ReleaseResult, type ReleaseTaskOptions, type RemoveBlockEdgeResult, type RenderableOp, type RenderedOp, type ReparentTaskResult, type RoiBucket, type RuntimeState, SEGMENT_FORMAT_VERSION, SYNCED_ENTITIES, SchemaTooOldError, type SegmentDefect, type SegmentDefectKind, type SegmentLine, type SegmentManifest, type SendOptions, type SendResult, type SendWarning, type SetStatusOptions, type SetStatusResult, type SlugifyResult, type SpawnAgentOptions, type SplitWindowOptions, type StateReading, type StateSource, type StrandedWorkspaceOrphan, SubstateReasonRequiredError, type SyncPassResult, SyncPeerNotFoundError, SyncPeerRefAmbiguousError, SyncSourceNotFoundError, type SyncedEntity, TASK_STATUSES, TASK_STATUS_LIST, TASK_SUBSTATES, TASK_SUBSTATE_ROWS, TaskAlreadyOwnedError, type TaskEdgeWithStatus, type TaskEdges, type TaskEdgesWithStatus, TaskExistsError, TaskNotFoundError, TaskNotInWorkstreamError, type TaskNoteRow, type TaskPair, TaskParkStateError, TaskParkedError, type TaskRow, type TaskStatus, type TaskSubstate, type TaskWaitOptions, type TaskWaitRef, type TaskWaitResult, type TaskWaitTaskState, type TeardownResult, TmuxError, type TmuxExecResult, type TmuxExecutor, type TmuxPane, type TmuxSession, type TmuxWindow, type Track, type Transport, UNKNOWN_REASON, UndoGroupNotFoundError, type UndoOptions, type UndoPlan, type UndoResult, UndoSupersededError, type UpdateTaskOptions, type UpdateTaskResult, type VcsBackend, type VcsBackendName, WORKSPACE_STALE_THRESHOLD, WorkspaceExistsError, type WorkspaceFailure, WorkspaceNotFoundError, type WorkspaceOrphan, WorkspacePathNotEmptyError, WorkspacePreservedError, type WorkspaceRow, type WorkspaceStaleness, WorkstreamExistsError, WorkstreamNameInvalidError, type WorkstreamOptions, type WorkstreamSnapshot, type WorkstreamSnapshotSlowFields, type WorkstreamSummary, abortAgent, activeMux, addBlockEdge, addNote, addTask, adoptAgent, agentKey, agentStateGlyph, agentStateHistogram, ambientFlush, ambientIngest, ambientSyncPass, appendLog, applyIncomingOp, applyOp, applyOps, assertValidPaneId, awaitPaneQuiescence, backendByName, capturePane, checkCaseCollisions, checkCheapDriftInvariant, checkCommandResolvable, checkDbInsideSyncDir, checkDrift, checkFleetHazards, checkNetworkMount, claimTask, classifyFsType, closeAgent, closeTask, compareHlc, composeAgentTitle, countProblems, createWorkspace, ctlRuntimeState, currentAgentName, currentOpContext, currentPaneTitle, decorateWithDirty, decorateWithStaleness, defaultDbPath, defaultSendDelayMs, defaultSendReadinessMs, defaultSpawnLivenessMs, defaultStateDir, deleteAgent, deleteTask, detectBackend, detectMux, discoverPeers, driftRemediation, emitEvent, ensureWorkstream, envVarNameForCli, expectsCtl, findCaseCollisions, flushSegment, foregroundPgid, formatDriftRecord, formatHlc, formatPair, freeWorkspace, getAgent, getAgentByPane, getParallelTracks, getPrerequisites, getTask, getTaskEdges, getTaskEdgesWithStatus, getWaitPollCount, getWatermark, getWorkspaceForAgent, getWorkspaceStaleness, gitBackend, hasWorkMarker, idFromTitle, idFromTitleVerbose, ingestFromDb, ingestSegment, insertAgent, inspectLinks, installCapture, isKickSignal, isPathInside, isTaskStatus, isValidAgentName, isValidPair, isValidPaneId, isValidTaskId, isValidWorkstreamName, isWorkspaceStale, jjBackend, kickAgent, killPane, killSession, latestSeq, linkPi, linkSkill, listAgents, listAllOrphanWorkspaces, listBlocked, listGoals, listInProgress, listLiveAgents, listLogs, listNotes, listPanes, listPanesInSession, listReady, listRecentClosed, listRecentGroups, listSessions, listTasks, listTasksByOwner, listWindows, listWorkspaceOrphans, listWorkspaces, listWorkstreams, loadDoctorChecks, loadDoctorSummary, loadFullDag, loadWorkstreamSnapshot, loadWorkstreamSnapshotFast, loadWorkstreamSnapshotSlow, localMachineId, locksDir, mapLegacyStatus, mergeSnapshotFastSlow, mostRecentGroup, murmurAvailable, mux, muxByName, newSession, newSessionWithPane, newWindow, nextHlc, noneBackend, opSubject, openDb, openTask, paneExists, paneTTY, parkTask, parseAgentNameFromTitle, parseHlc, parseOpKey, parsePsTtyOutput, peerStatuses, planUndo, priorFieldValue, probeFilesystem, readAgent, readAgentStates, readFileLockMeta, readManifest, rebuildInto, receiveHlc, reconcile, refreshAgentTitle, releaseTask, remediationParagraph, removeBlockEdge, renderForest, renderOp, renderOpLine, renderTaskTree, repairPeer, reparentTask, resetCommandResolverForTests, resetKickProcessExecutor, resetMux, resetSleep, resetTmuxExecutor, resetWaitPollCount, resetWatermark, resolveActorIdentity, resolveCliCommand, resolveCliCommandWithSource, resolveGroupId, resolvePair, resolvePeerRef, roiBucket, segmentLineCount, segmentPath, selectLayout, sendToAgent, sendToPane, sendViaTransport, sessionExists, setCommandResolverForTests, setKickProcessExecutor, setMuxForTests, setPaneTitle, setSleepForTests, setTaskStatus, setTmuxExecutor, setWaitSleepForTests, setWaitStuckWarnForTests, setWatermark, slBackend, sleep, slugifyTitle, slugifyTitleVerbose, spawnAgent, splitWindow, summarizeOwnedTasks, summarizeWorkstream, syncDir, syncEnabled, syncPass, teardownWorkstream, tmux, tmuxBackend, transportNextSteps, undoGroup, unparkTask, updateTask, verifyAgainstManifest, waitForTasks, withCaptureSuppressed, withFileLock, withOpContext, workspacePath, workspacesRoot, yankCommandForCheck };