@martintrojer/mu 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -342,6 +342,19 @@ declare function enableMuPaneBordersForPane(paneId: string): Promise<void>;
342
342
  * in hud_visual_cue_impl.
343
343
  */
344
344
  declare function enableMuPaneBorders(target: string): Promise<void>;
345
+ /**
346
+ * Look up the TTY device path for a pane (e.g. `/dev/ttys012` on macOS,
347
+ * `/dev/pts/3` on Linux). Used by `mu agent kick` to find the
348
+ * foreground process group on the pane's TTY so it can be signalled
349
+ * directly — `tmux send-keys C-c` does NOT propagate to wrapped
350
+ * subprocesses inside a CLI like pi/claude/codex (the CLI catches it
351
+ * itself and treats it as a UI input). The escape hatch is signalling
352
+ * the foreground pgid of the underlying TTY from outside the pane.
353
+ *
354
+ * Throws `PaneNotFoundError` when the pane id is invalid or the pane
355
+ * has vanished. Throws `TmuxError` on any other tmux failure.
356
+ */
357
+ declare function paneTTY(paneId: string): Promise<string>;
345
358
  declare function getPaneTitle(paneId: string): Promise<string | undefined>;
346
359
  /**
347
360
  * Read the title of the *current* pane (the one whose shell is running this
@@ -451,6 +464,7 @@ declare const tmux$1_newSession: typeof newSession;
451
464
  declare const tmux$1_newSessionWithPane: typeof newSessionWithPane;
452
465
  declare const tmux$1_newWindow: typeof newWindow;
453
466
  declare const tmux$1_paneExists: typeof paneExists;
467
+ declare const tmux$1_paneTTY: typeof paneTTY;
454
468
  declare const tmux$1_parseAgentNameFromTitle: typeof parseAgentNameFromTitle;
455
469
  declare const tmux$1_resetSleep: typeof resetSleep;
456
470
  declare const tmux$1_resetTmuxExecutor: typeof resetTmuxExecutor;
@@ -464,7 +478,7 @@ declare const tmux$1_sleep: typeof sleep;
464
478
  declare const tmux$1_splitWindow: typeof splitWindow;
465
479
  declare const tmux$1_tmux: typeof tmux;
466
480
  declare namespace tmux$1 {
467
- export { type tmux$1_CaptureOptions as CaptureOptions, type tmux$1_NewSessionOptions as NewSessionOptions, type tmux$1_NewSessionWithPaneOptions as NewSessionWithPaneOptions, type tmux$1_NewWindowOptions as NewWindowOptions, tmux$1_PANE_ID_RE as PANE_ID_RE, tmux$1_PaneNotFoundError as PaneNotFoundError, type tmux$1_SendOptions as SendOptions, type tmux$1_SplitWindowOptions as SplitWindowOptions, tmux$1_TmuxError as TmuxError, type tmux$1_TmuxExecResult as TmuxExecResult, type tmux$1_TmuxExecutor as TmuxExecutor, type tmux$1_TmuxPane as TmuxPane, type tmux$1_TmuxSession as TmuxSession, type tmux$1_TmuxWindow as TmuxWindow, tmux$1_assertValidPaneId as assertValidPaneId, tmux$1_capturePane as capturePane, tmux$1_currentAgentName as currentAgentName, tmux$1_currentPaneSize as currentPaneSize, tmux$1_currentPaneTitle as currentPaneTitle, tmux$1_defaultSendDelayMs as defaultSendDelayMs, tmux$1_enableMuPaneBorders as enableMuPaneBorders, tmux$1_enableMuPaneBordersForPane as enableMuPaneBordersForPane, tmux$1_enableMuPaneBordersForSession as enableMuPaneBordersForSession, tmux$1_getPaneTitle as getPaneTitle, tmux$1_getWindowIdForPane as getWindowIdForPane, tmux$1_isValidPaneId as isValidPaneId, tmux$1_killPane as killPane, tmux$1_killSession as killSession, tmux$1_listPanes as listPanes, tmux$1_listPanesInSession as listPanesInSession, tmux$1_listSessions as listSessions, tmux$1_listWindows as listWindows, tmux$1_newSession as newSession, tmux$1_newSessionWithPane as newSessionWithPane, tmux$1_newWindow as newWindow, tmux$1_paneExists as paneExists, tmux$1_parseAgentNameFromTitle as parseAgentNameFromTitle, tmux$1_resetSleep as resetSleep, tmux$1_resetTmuxExecutor as resetTmuxExecutor, tmux$1_selectLayout as selectLayout, tmux$1_sendToPane as sendToPane, tmux$1_sessionExists as sessionExists, tmux$1_setPaneTitle as setPaneTitle, tmux$1_setSleepForTests as setSleepForTests, tmux$1_setTmuxExecutor as setTmuxExecutor, tmux$1_sleep as sleep, tmux$1_splitWindow as splitWindow, tmux$1_tmux as tmux };
481
+ export { type tmux$1_CaptureOptions as CaptureOptions, type tmux$1_NewSessionOptions as NewSessionOptions, type tmux$1_NewSessionWithPaneOptions as NewSessionWithPaneOptions, type tmux$1_NewWindowOptions as NewWindowOptions, tmux$1_PANE_ID_RE as PANE_ID_RE, tmux$1_PaneNotFoundError as PaneNotFoundError, type tmux$1_SendOptions as SendOptions, type tmux$1_SplitWindowOptions as SplitWindowOptions, tmux$1_TmuxError as TmuxError, type tmux$1_TmuxExecResult as TmuxExecResult, type tmux$1_TmuxExecutor as TmuxExecutor, type tmux$1_TmuxPane as TmuxPane, type tmux$1_TmuxSession as TmuxSession, type tmux$1_TmuxWindow as TmuxWindow, tmux$1_assertValidPaneId as assertValidPaneId, tmux$1_capturePane as capturePane, tmux$1_currentAgentName as currentAgentName, tmux$1_currentPaneSize as currentPaneSize, tmux$1_currentPaneTitle as currentPaneTitle, tmux$1_defaultSendDelayMs as defaultSendDelayMs, tmux$1_enableMuPaneBorders as enableMuPaneBorders, tmux$1_enableMuPaneBordersForPane as enableMuPaneBordersForPane, tmux$1_enableMuPaneBordersForSession as enableMuPaneBordersForSession, tmux$1_getPaneTitle as getPaneTitle, tmux$1_getWindowIdForPane as getWindowIdForPane, tmux$1_isValidPaneId as isValidPaneId, tmux$1_killPane as killPane, tmux$1_killSession as killSession, tmux$1_listPanes as listPanes, tmux$1_listPanesInSession as listPanesInSession, tmux$1_listSessions as listSessions, tmux$1_listWindows as listWindows, tmux$1_newSession as newSession, tmux$1_newSessionWithPane as newSessionWithPane, tmux$1_newWindow as newWindow, tmux$1_paneExists as paneExists, tmux$1_paneTTY as paneTTY, tmux$1_parseAgentNameFromTitle as parseAgentNameFromTitle, tmux$1_resetSleep as resetSleep, tmux$1_resetTmuxExecutor as resetTmuxExecutor, tmux$1_selectLayout as selectLayout, tmux$1_sendToPane as sendToPane, tmux$1_sessionExists as sessionExists, tmux$1_setPaneTitle as setPaneTitle, tmux$1_setSleepForTests as setSleepForTests, tmux$1_setTmuxExecutor as setTmuxExecutor, tmux$1_sleep as sleep, tmux$1_splitWindow as splitWindow, tmux$1_tmux as tmux };
468
482
  }
469
483
 
470
484
  /**
@@ -542,6 +556,52 @@ interface ReconcileReport {
542
556
  }
543
557
  declare function reconcile(db: Db, opts: ReconcileOptions): Promise<ReconcileReport>;
544
558
 
559
+ /**
560
+ * Pre-flight failure: the command mu would have spawned in the new
561
+ * pane doesn't resolve to a binary on PATH (and isn't an absolute /
562
+ * relative path that exists + is executable). Thrown by `spawnAgent`
563
+ * BEFORE `prestageWorkspace` so a typo in `--cli` never leaves an
564
+ * orphan workspace dir behind.
565
+ *
566
+ * Source: feedback ws task `fb_agent_spawn_no_validation`. Live
567
+ * dogfood report: `mu agent spawn worker-1 --cli pi-meta` on a host
568
+ * where the `pi-meta` binary wasn't on PATH printed `Spawned worker-1
569
+ * (pi-meta)` and the pane immediately died with `command not found`;
570
+ * the existing 1.5s liveness check sometimes missed it (the shell
571
+ * stays alive after the failed exec). Pre-flighting the PATH lookup
572
+ * surfaces the typo before any side effects (workspace, pane, DB row).
573
+ *
574
+ * Distinct from `AgentSpawnStartupError` (pane alive but parked at an
575
+ * error prompt) and `AgentDiedOnSpawnError` (pane vanished within the
576
+ * liveness window). All three carry different remediation hints, so
577
+ * they're separate types.
578
+ */
579
+ declare class AgentSpawnCliNotFoundError extends Error implements HasNextSteps {
580
+ readonly cli: string;
581
+ /** First whitespace-separated token of the resolved command — the
582
+ * thing actually missing on PATH. Surfaced verbatim in the
583
+ * message so the operator sees what mu searched for (which may
584
+ * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */
585
+ readonly binary: string;
586
+ /** Name of the env var that mu consulted before falling back to
587
+ * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set
588
+ * to the conventional name so the nextSteps hint can recommend
589
+ * exporting it. */
590
+ readonly envVarChecked: string;
591
+ readonly name = "AgentSpawnCliNotFoundError";
592
+ constructor(cli: string,
593
+ /** First whitespace-separated token of the resolved command — the
594
+ * thing actually missing on PATH. Surfaced verbatim in the
595
+ * message so the operator sees what mu searched for (which may
596
+ * differ from `cli` when `$MU_<UPPER_CLI>_COMMAND` rewrites it). */
597
+ binary: string,
598
+ /** Name of the env var that mu consulted before falling back to
599
+ * the bare `cli` value (e.g. `MU_PI_META_COMMAND`). Always set
600
+ * to the conventional name so the nextSteps hint can recommend
601
+ * exporting it. */
602
+ envVarChecked: string);
603
+ errorNextSteps(): NextStep[];
604
+ }
545
605
  declare class AgentExistsError extends Error implements HasNextSteps {
546
606
  readonly agentName: string;
547
607
  readonly name = "AgentExistsError";
@@ -600,6 +660,52 @@ declare class AgentDiedOnSpawnError extends Error implements HasNextSteps {
600
660
  constructor(agentName: string, paneId: string, scrollback: string | undefined);
601
661
  errorNextSteps(): NextStep[];
602
662
  }
663
+ /**
664
+ * Thrown when an agent's pane is alive AND staying alive after the
665
+ * liveness window, but its first burst of output matches a known
666
+ * provider-startup-failure pattern (missing API key, auth rejected, …).
667
+ * Source: feedback ws task `agent_spawn_model_auth_failure_counts_as_live`.
668
+ * Live dogfood report: `pi-meta --no-solo --model sonnet:high` printed
669
+ * `Error: No API key found for amazon-bedrock` and parked at a prompt.
670
+ * The pane stayed alive (1.5s liveness check passed) but the worker
671
+ * could never do work — the orchestrator only discovered this when
672
+ * `mu task wait` stalled minutes later.
673
+ *
674
+ * Distinct from `AgentDiedOnSpawnError`:
675
+ * - `AgentDiedOnSpawnError` → pane vanished within the liveness window
676
+ * (CLI exited fast).
677
+ * - `AgentSpawnStartupError` → pane alive, but the captured scrollback
678
+ * tail contains a curated provider-auth-failure pattern.
679
+ * The two carry different remediation hints (CLI override vs. fix the
680
+ * env var), so they're separate types instead of one with a flag.
681
+ *
682
+ * The pattern list is curated and short to keep false-positive risk low
683
+ * — the scan only looks at the last ~30 lines of the 50-line capture
684
+ * taken right after the liveness sleep, so matches naturally come from
685
+ * the CLI's first ~1.5s of output (not arbitrary later prompts the
686
+ * agent might type into).
687
+ */
688
+ declare class AgentSpawnStartupError extends Error implements HasNextSteps {
689
+ readonly agentName: string;
690
+ readonly paneId: string;
691
+ /** The single scrollback line that matched a known startup-error
692
+ * pattern. Surfaced verbatim in the message so the operator sees
693
+ * what mu saw. */
694
+ readonly matchedLine: string;
695
+ /** Full captured scrollback (tail-trimmed already by
696
+ * awaitSpawnLiveness). Attached to the message for context. */
697
+ readonly scrollback: string;
698
+ readonly name = "AgentSpawnStartupError";
699
+ constructor(agentName: string, paneId: string,
700
+ /** The single scrollback line that matched a known startup-error
701
+ * pattern. Surfaced verbatim in the message so the operator sees
702
+ * what mu saw. */
703
+ matchedLine: string,
704
+ /** Full captured scrollback (tail-trimmed already by
705
+ * awaitSpawnLiveness). Attached to the message for context. */
706
+ scrollback: string);
707
+ errorNextSteps(): NextStep[];
708
+ }
603
709
  /**
604
710
  * Thrown when `closeAgent` is called on an agent that has an associated
605
711
  * workspace AND the caller didn't explicitly opt into discarding it.
@@ -740,6 +846,31 @@ interface VcsBackend {
740
846
  * the on-disk dir without touching the agent or pane.
741
847
  */
742
848
  rebaseTo(workspacePath: string, fromRef?: string): Promise<RebaseResult>;
849
+ /**
850
+ * Cheap "is the working copy clean?" probe used by close-auto-free
851
+ * (allow_mu_agent_close_without_discard). Definition: ZERO uncommitted
852
+ * changes (no working-tree modifications, no staged changes, no
853
+ * untracked-not-ignored files). Pure observation; no fetch, no commit.
854
+ *
855
+ * Backend-specific:
856
+ * - git: empty `git status --porcelain` output.
857
+ * - jj: jj is auto-snapshotted, so the @ commit IS the WC; clean
858
+ * here means @ has no diff from its parent (empty `jj diff
859
+ * -r @ --summary`). A description-only difference still
860
+ * counts as clean.
861
+ * - sl: empty `sl status` output.
862
+ * - none: meaningless (cp -a snapshot has no notion of
863
+ * "committed" vs "uncommitted"); always returns true so the
864
+ * close-auto-free path treats every none-workspace as
865
+ * eligible for silent free (no commits can be lost; the only
866
+ * loss is local file edits, which the operator implicitly
867
+ * accepts by closing the agent).
868
+ *
869
+ * Returns false on any backend command failure — be conservative
870
+ * (we'd rather refuse a close than auto-free a workspace whose
871
+ * cleanliness we couldn't verify).
872
+ */
873
+ isClean(workspacePath: string): Promise<boolean>;
743
874
  /**
744
875
  * List commits the workspace has on top of `baseRef`, oldest-first.
745
876
  * Used by `mu workspace commits` (fb_workspace_commits_verb) to
@@ -754,6 +885,27 @@ interface VcsBackend {
754
885
  * on backend command failure (unknown ref, missing repo).
755
886
  */
756
887
  commitsSinceBase(workspacePath: string, baseRef: string): Promise<CommitSummary[]>;
888
+ /**
889
+ * Return the list of dirty (uncommitted / unstaged / untracked-not-
890
+ * ignored) paths in the workspace. Empty array = clean.
891
+ *
892
+ * Used by `mu workspace recreate` to refuse a free+create cycle on
893
+ * a dirty workspace unless the operator passes `--force` (the lossy
894
+ * escape hatch). Mirrors the dirty-check `rebaseTo` does internally.
895
+ *
896
+ * Backend semantics:
897
+ * - git: `git status --porcelain` (working-tree + staged +
898
+ * untracked-not-ignored, mirroring the rebaseTo path).
899
+ * - sl: `sl status` parsed for non-empty output.
900
+ * - jj: always-snapshotted, so no concept of "dirty" — returns [].
901
+ * - none: cp -a snapshots have no VCS, so we can't decide "dirty";
902
+ * returns [] so the caller doesn't refuse for an unanswerable
903
+ * question.
904
+ *
905
+ * Throws on backend command failure (the operator should see a
906
+ * real error, not a silent "clean").
907
+ */
908
+ listDirtyFiles(workspacePath: string): Promise<string[]>;
757
909
  }
758
910
  declare const noneBackend: VcsBackend;
759
911
  declare const gitBackend: VcsBackend;
@@ -779,6 +931,48 @@ declare function backendByName(name: VcsBackendName): VcsBackend;
779
931
  * are still recognised as agents.
780
932
  */
781
933
  declare function resolveCliCommand(cli: string): string;
934
+ /**
935
+ * Compute the `MU_<UPPER_CLI>_COMMAND` env var name mu consults when
936
+ * resolving `--cli <key>`. Hyphens in the cli key become underscores
937
+ * (env var names can't contain `-`); this matches the operator-aliases
938
+ * convention documented in the mu skill (e.g. `--cli pi-meta` →
939
+ * `MU_PI_META_COMMAND`).
940
+ */
941
+ declare function envVarNameForCli(cli: string): string;
942
+ /**
943
+ * Resolve `--cli <key>` to its actual command string AND tell the
944
+ * caller whether the resolution came from a `MU_<UPPER_CLI>_COMMAND`
945
+ * env var or fell through to the bare cli name. The CLI uses this to
946
+ * surface env-var attribution in the spawn-success line so config
947
+ * issues are visible without `mu agent show`
948
+ * (fb_agent_spawn_no_validation, part C).
949
+ */
950
+ declare function resolveCliCommandWithSource(cli: string): {
951
+ command: string;
952
+ envVar: string;
953
+ resolvedFromEnv: boolean;
954
+ };
955
+ interface CommandResolutionResult {
956
+ ok: boolean;
957
+ /** First whitespace-separated token of the command — the binary
958
+ * whose presence on PATH we checked. */
959
+ binary: string;
960
+ /** Absolute path of the resolved binary on PATH, when ok=true. */
961
+ resolvedPath?: string;
962
+ }
963
+ type CommandResolver = (command: string) => Promise<CommandResolutionResult>;
964
+ /** Override the PATH resolver. Tests use this to simulate "binary
965
+ * absent" / "binary present" without depending on what's actually
966
+ * installed. Production callers should never touch this. */
967
+ declare function setCommandResolverForTests(resolver: CommandResolver): void;
968
+ /** Restore the default PATH resolver. */
969
+ declare function resetCommandResolverForTests(): void;
970
+ /**
971
+ * Verify the first token of `command` resolves to a binary on PATH.
972
+ * Public so tests can call it directly; spawnAgent calls it before
973
+ * prestageWorkspace so a bad --cli never creates an orphan workspace.
974
+ */
975
+ declare function checkCommandResolvable(command: string): Promise<CommandResolutionResult>;
782
976
  interface SpawnAgentOptions {
783
977
  name: string;
784
978
  workstream: string;
@@ -899,6 +1093,119 @@ interface AdoptAgentResult {
899
1093
  */
900
1094
  declare function adoptAgent(db: Db, opts: AdoptAgentOptions): Promise<AdoptAgentResult>;
901
1095
 
1096
+ /** The signal set kick supports. SIGINT is graceful (matches Ctrl-C
1097
+ * semantics — what the operator probably wanted in the first place);
1098
+ * SIGTERM is the polite escalation; SIGKILL is the unblockable
1099
+ * hammer. We deliberately don't expose arbitrary signals — the
1100
+ * three above are the actionable ones for "interrupt a wedged
1101
+ * foreground tool subprocess." */
1102
+ type KickSignal = "SIGINT" | "SIGTERM" | "SIGKILL";
1103
+ declare function isKickSignal(s: string): s is KickSignal;
1104
+ /**
1105
+ * Thrown when the foreground pgid lookup on a pane's TTY yields
1106
+ * either no rows at all (the pane is sitting at an idle shell with
1107
+ * no foreground job) or only the wrapping shell itself (the LLM CLI
1108
+ * — pi/claude/codex — is the foreground; signalling it would close
1109
+ * the agent, which is what `mu agent close` is for).
1110
+ *
1111
+ * Maps to the generic exit code 1 in handle.ts (this is a
1112
+ * runtime-state condition, not a typed not-found / conflict).
1113
+ */
1114
+ declare class NoForegroundProcessError extends Error implements HasNextSteps {
1115
+ readonly agentName: string;
1116
+ readonly tty: string;
1117
+ readonly reason: "no-foreground" | "shell-only";
1118
+ readonly name = "NoForegroundProcessError";
1119
+ constructor(agentName: string, tty: string, reason: "no-foreground" | "shell-only");
1120
+ errorNextSteps(): NextStep[];
1121
+ }
1122
+ interface KickProcessExecResult {
1123
+ stdout: string;
1124
+ stderr: string;
1125
+ exitCode: number | null;
1126
+ }
1127
+ type KickProcessExecutor = (cmd: string, args: readonly string[]) => Promise<KickProcessExecResult>;
1128
+ /** Install a custom executor (for tests). Returns the previous one so
1129
+ * tests can restore cleanly. */
1130
+ declare function setKickProcessExecutor(executor: KickProcessExecutor): KickProcessExecutor;
1131
+ /** Restore the real executor. */
1132
+ declare function resetKickProcessExecutor(): void;
1133
+ interface PsRow {
1134
+ pid: number;
1135
+ pgid: number;
1136
+ /** ps's `stat` (or `state`) field. The presence of `+` means
1137
+ * "foreground process group on its controlling tty". */
1138
+ stat: string;
1139
+ /** Process command (just the comm; truncated, used for diagnostics). */
1140
+ comm: string;
1141
+ }
1142
+ /**
1143
+ * Parse `ps -t <tty> -o pid=,pgid=,stat=,comm=` output. Each non-blank
1144
+ * line is one process: four whitespace-separated fields. Defensive
1145
+ * about leading whitespace and command names with embedded spaces
1146
+ * (the comm is the LAST field — join the tail).
1147
+ */
1148
+ declare function parsePsTtyOutput(output: string): PsRow[];
1149
+ /**
1150
+ * Resolve the foreground process group id for a TTY device path. The
1151
+ * canonical signal `ps`'s `stat` field uses is `+` (BSD/Darwin AND
1152
+ * Linux procps). We pick the first row whose stat contains `+`; its
1153
+ * `pgid` is the foreground pgid of that controlling terminal.
1154
+ *
1155
+ * Returns:
1156
+ * - `{ kind: "ok", pgid, fgRow }` on success
1157
+ * - `{ kind: "no-foreground" }` when no row carries `+` AND there
1158
+ * are no candidate rows at all
1159
+ * - `{ kind: "shell-only", pgid, fgRow }` when the foreground pgid
1160
+ * resolves to a shell whose comm is the agent's wrapping CLI
1161
+ * (caller decides whether to refuse — kick refuses)
1162
+ *
1163
+ * The wrapping-CLI guard is intentionally narrow: we only refuse
1164
+ * when the foreground process command matches one of the known
1165
+ * pi/claude/codex/zsh/bash shapes. Anything else (a `find`, a
1166
+ * `cargo build`, a `python script.py`) is exactly what we want to
1167
+ * signal — that's the unbounded-tool case the verb was built for.
1168
+ */
1169
+ interface ForegroundLookup {
1170
+ kind: "ok" | "shell-only" | "no-foreground";
1171
+ pgid?: number;
1172
+ fgRow?: PsRow;
1173
+ /** All rows ps returned for the tty, for diagnostics / tests. */
1174
+ rows: PsRow[];
1175
+ }
1176
+ declare function foregroundPgid(tty: string): Promise<ForegroundLookup>;
1177
+ interface KickAgentOptions {
1178
+ workstream: string;
1179
+ /** Defaults to SIGINT (matches Ctrl-C semantics). */
1180
+ signal?: KickSignal;
1181
+ }
1182
+ interface KickAgentResult {
1183
+ agentName: string;
1184
+ paneId: string;
1185
+ /** TTY device path the foreground pgid was resolved against. */
1186
+ tty: string;
1187
+ /** The pgid we signalled. */
1188
+ signaledPgid: number;
1189
+ signal: KickSignal;
1190
+ /** The comm of the foreground process at the time of signal — useful
1191
+ * diagnostic in the event log ("we kicked a `find`, not a `cargo`"). */
1192
+ foregroundComm: string;
1193
+ }
1194
+ /**
1195
+ * Send `signal` to the foreground process group of an agent's pane
1196
+ * TTY. Default signal is SIGINT.
1197
+ *
1198
+ * Errors:
1199
+ * - `AgentNotFoundError` — the agent doesn't exist in this workstream.
1200
+ * - `PaneNotFoundError` (from paneTTY) — the agent's pane has vanished.
1201
+ * - `NoForegroundProcessError` — pane has no foreground job, OR the
1202
+ * foreground is the wrapping CLI itself (refuse; use `mu agent close`).
1203
+ *
1204
+ * Emits an `agent kick <name> (signal=..., pgid=..., comm=...)` event
1205
+ * on success.
1206
+ */
1207
+ declare function kickAgent(db: Db, name: string, opts: KickAgentOptions): Promise<KickAgentResult>;
1208
+
902
1209
  interface AgentRow {
903
1210
  name: string;
904
1211
  /** Foreign-name reference to the owning workstream. */
@@ -1025,14 +1332,17 @@ interface FreeAgentResult {
1025
1332
  declare function freeAgent(db: Db, name: string, workstream: string): FreeAgentResult;
1026
1333
  interface CloseAgentOptions {
1027
1334
  /**
1028
- * When true, free the agent's workspace BEFORE deleting the agent
1029
- * (so we control the order rather than relying on FK cascade, which
1030
- * leaves the on-disk dir orphaned). Lossy: any pending changes in
1031
- * the workspace are gone unless the caller frees with `--commit`
1032
- * separately first.
1335
+ * Lossy override: when true, free the agent's workspace BEFORE
1336
+ * deleting the agent regardless of whether it's clean. (We control
1337
+ * the order rather than relying on FK cascade, which leaves the
1338
+ * on-disk dir orphaned.) Any pending changes / commits since fork
1339
+ * are gone unless the caller frees with `--commit` separately first.
1033
1340
  *
1034
- * When false (default) and a workspace exists, throws
1035
- * WorkspacePreservedError so the caller has to decide explicitly.
1341
+ * When false (default), behaviour depends on workspace state:
1342
+ * - clean (no uncommitted changes AND no commits since fork):
1343
+ * silently auto-free. allow_mu_agent_close_without_discard.
1344
+ * - dirty (uncommitted changes OR commits since fork): throw
1345
+ * WorkspacePreservedError so the caller decides explicitly.
1036
1346
  * Surfaced as a real bug in the multi-agent dogfood teardown.
1037
1347
  */
1038
1348
  discardWorkspace?: boolean;
@@ -1040,11 +1350,20 @@ interface CloseAgentOptions {
1040
1350
  interface CloseAgentResult {
1041
1351
  killedPane: boolean;
1042
1352
  deletedRow: boolean;
1043
- /** True iff the agent had an associated workspace AND the caller
1044
- * passed `discardWorkspace: true` so we proactively freed it.
1045
- * False on the no-workspace path (nothing to free) and on the
1046
- * refused path (we threw before doing anything). */
1353
+ /** True iff the agent had an associated workspace AND we proactively
1354
+ * freed it either because the caller passed `discardWorkspace:
1355
+ * true` (lossy) or because the workspace was clean and we
1356
+ * auto-freed (allow_mu_agent_close_without_discard). False on the
1357
+ * no-workspace path (nothing to free) and on the refused path (we
1358
+ * threw before doing anything). */
1047
1359
  workspaceFreed: boolean;
1360
+ /** True iff `workspaceFreed` was triggered by the clean-workspace
1361
+ * auto-free path (no uncommitted changes AND no commits since
1362
+ * fork) rather than the explicit `discardWorkspace: true` override.
1363
+ * Lets the CLI render an accurate message ("auto-freed (clean)"
1364
+ * vs "workspace discarded") and gives JSON consumers a stable
1365
+ * signal. False on every other path. */
1366
+ workspaceAutoFreedClean: boolean;
1048
1367
  }
1049
1368
  /**
1050
1369
  * Close an agent: kill its tmux pane and remove its DB row. Idempotent:
@@ -1052,15 +1371,22 @@ interface CloseAgentResult {
1052
1371
  * - if the tmux pane is already gone, killPane swallows the error
1053
1372
  *
1054
1373
  * Workspace handling: closing an agent and freeing its workspace are
1055
- * separate concerns (agent lifecycle vs disk artifacts), so by default
1056
- * `closeAgent` REFUSES if the agent has a workspace — you'd otherwise
1057
- * orphan the on-disk dir (the FK cascade drops the registry row but
1058
- * not the directory). Two ways to proceed:
1059
- *
1060
- * 1. `freeWorkspace(db, name)` first, then `closeAgent(db, name)`.
1061
- * Preserves the option to `--commit` pending changes.
1062
- * 2. `closeAgent(db, name, { discardWorkspace: true })`. One-shot;
1063
- * lossy.
1374
+ * separate concerns (agent lifecycle vs disk artifacts). Three cases:
1375
+ *
1376
+ * - No workspace: close proceeds normally.
1377
+ * - Workspace exists AND is CLEAN (no uncommitted changes, no
1378
+ * commits since fork): silently auto-free (so a workspace that
1379
+ * contains nothing worth preserving doesn't make the operator
1380
+ * type --discard-workspace just to clean it up). Surfaced by
1381
+ * allow_mu_agent_close_without_discard a misconfigured-spawn
1382
+ * teardown was needlessly forced through the lossy flag.
1383
+ * - Workspace exists AND has either uncommitted changes OR commits
1384
+ * since fork: REFUSE with WorkspacePreservedError so the operator
1385
+ * decides explicitly. Two resolutions:
1386
+ * 1. `freeWorkspace(db, name)` first, then `closeAgent(db, name)`.
1387
+ * Preserves the option to `--commit` pending changes.
1388
+ * 2. `closeAgent(db, name, { discardWorkspace: true })`.
1389
+ * One-shot; lossy.
1064
1390
  *
1065
1391
  * The CLI surfaces these as the two actionable nextSteps on the
1066
1392
  * `WorkspacePreservedError` thrown by the refuse path.
@@ -1435,7 +1761,7 @@ declare class TaskHasOpenDependentsError extends Error implements HasNextSteps {
1435
1761
  * The FK on `tasks.owner` references `agents.name`; without this guard
1436
1762
  * the claim attempt would fail with the unhelpful 'FOREIGN KEY constraint
1437
1763
  * failed' from SQLite. This typed error gives the user actionable next
1438
- * steps (run `mu adopt <pane-id>` to register, or use --for to pick a
1764
+ * steps (run `mu agent adopt <pane-id>` to register, or use --for to pick a
1439
1765
  * different agent).
1440
1766
  *
1441
1767
  * Maps to exit code 4 (conflict) via the cli.ts handler.
@@ -1449,7 +1775,7 @@ declare class ClaimerNotRegisteredError extends Error implements HasNextSteps {
1449
1775
  * Three actionable resolutions in expected-frequency order:
1450
1776
  * 1. --self : orchestrator pattern (working directly)
1451
1777
  * 2. --for : dispatcher pattern (assigning to a worker)
1452
- * 3. mu adopt: registration pattern (promote pane to worker)
1778
+ * 3. mu agent adopt: registration pattern (promote pane to worker)
1453
1779
  */
1454
1780
  errorNextSteps(): NextStep[];
1455
1781
  }
@@ -1571,17 +1897,14 @@ interface TaskWaitTaskState {
1571
1897
  stuck: boolean;
1572
1898
  }
1573
1899
  interface TaskWaitResult {
1574
- /** Per-task state at exit time. Same length and order as the input list. */
1575
- tasks: TaskWaitTaskState[];
1576
- /** True when EVERY task reached the target (the --all condition). */
1577
- allReached: boolean;
1578
- /** True when AT LEAST ONE task reached the target (the --any condition). */
1579
- anyReached: boolean;
1580
- /** Wall-clock time spent waiting, in ms (always >= 0). */
1581
- elapsedMs: number;
1900
+ /** Per-task state at exit time. Same length and order as the input
1901
+ * list. The caller derives all-reached / any-reached / elapsed
1902
+ * from this list (count `r.reachedTarget`) and from its own
1903
+ * startedAt clock — keeping the SDK return minimal. */
1904
+ refs: TaskWaitTaskState[];
1582
1905
  /** True when we exited because of the timeout, not because the wait
1583
- * condition was met. allReached / anyReached can still be true on
1584
- * partial progress when timedOut is true. */
1906
+ * condition was met. Refs that did reach the target are still
1907
+ * reflected in `refs[i].reachedTarget` on partial-progress timeout. */
1585
1908
  timedOut: boolean;
1586
1909
  }
1587
1910
  /**
@@ -1663,6 +1986,14 @@ interface CloseTaskOptions extends EvidenceOption {
1663
1986
  * When false / omitted, behaves as bare `closeTask` (closes
1664
1987
  * regardless of blocker status). */
1665
1988
  ifReady?: boolean;
1989
+ /** Optional actor identity attributed to the synthetic `CLOSE: …`
1990
+ * note auto-inserted when `evidence` is non-empty (see closeTask
1991
+ * body). The CLI resolves this via `resolveActorIdentity()` so the
1992
+ * note carries the closing worker's name; SDK callers (tests,
1993
+ * internal use) may omit it (the note then carries no author, same
1994
+ * as a bare `addNote` without `--author`). Surfaced in mufeedback
1995
+ * task_close_evidence_does_not_append_the. */
1996
+ author?: string;
1666
1997
  }
1667
1998
  /** Convenience: setTaskStatus(db, id, "CLOSED"). Accepts evidence.
1668
1999
  * Pre-snapshots the DB (snap_design §CAPTURE STRATEGY > WHEN). Skipped
@@ -1888,11 +2219,6 @@ declare function resolveActorIdentity(): Promise<string>;
1888
2219
  interface TaskRow {
1889
2220
  /** Per-workstream-unique TEXT name. The operator-facing identifier. */
1890
2221
  name: string;
1891
- /** Alias for `name` — the per-workstream-unique TEXT id. Emitted alongside
1892
- * `name` so JSON consumers can dot-access the canonical field name without
1893
- * having to know that, for tasks specifically, `name` plays the localId
1894
- * role. Always equal to `name`. */
1895
- localId: string;
1896
2222
  /** Foreign-name reference to the owning workstream. */
1897
2223
  workstreamName: string;
1898
2224
  title: string;
@@ -1930,6 +2256,14 @@ declare function slugifyTitle(title: string): string;
1930
2256
  * exceeds the SLUG_SOFT_CAP the verbose form had to
1931
2257
  * cut at a word boundary (or hard-truncate); the
1932
2258
  * cut clauses are gone with no in-band signal.
2259
+ * originalSlug — what the slug WOULD have been without the
2260
+ * SLUG_SOFT_CAP cut: full stripped slug with the
2261
+ * same `t_` digit-prefix correction and the same
2262
+ * SLUG_HARD_CAP ceiling, but no word-boundary
2263
+ * truncation. Equal to `slug` when nothing was
2264
+ * cut. The CLI surfaces this in `mu task add
2265
+ * --json` so scripted callers can detect the
2266
+ * truncation without grepping stderr.
1933
2267
  * truncated — true iff `slug.length < strippedLength` AFTER the
1934
2268
  * `t_` digit-prefix correction, i.e. real bytes were
1935
2269
  * dropped. False for any title that fits under the
@@ -1937,12 +2271,14 @@ declare function slugifyTitle(title: string): string;
1937
2271
  * is the `t_` prefix.
1938
2272
  *
1939
2273
  * The CLI's `mu task add` uses `truncated` to print a one-line stderr
1940
- * hint pointing at the `<id>` positional override
1941
- * (slugifytitle_silently_drops_clauses).
2274
+ * hint pointing at the `<id>` positional override and (under --json)
2275
+ * to surface `originalSlug` alongside `truncated:true`
2276
+ * (slugifytitle_silently_drops_clauses; task_add_slugify_silently_truncates_ids).
1942
2277
  */
1943
2278
  interface SlugifyResult {
1944
2279
  slug: string;
1945
2280
  strippedLength: number;
2281
+ originalSlug: string;
1946
2282
  truncated: boolean;
1947
2283
  }
1948
2284
  /**
@@ -1965,15 +2301,29 @@ declare function idFromTitle(db: Db, workstream: string, title: string): string;
1965
2301
  * Result of `idFromTitleVerbose`: the unique-in-workstream id plus the
1966
2302
  * truncated flag from the underlying slugify pass. Used by `mu task
1967
2303
  * add` to decide whether to surface the stderr hint about lost clauses
1968
- * (slugifytitle_silently_drops_clauses).
2304
+ * (slugifytitle_silently_drops_clauses) and to surface the un-truncated
2305
+ * slug in `--json` (task_add_slugify_silently_truncates_ids).
2306
+ *
2307
+ * id — the unique-in-workstream task id.
2308
+ * truncated — true iff the underlying slugify pass cut real
2309
+ * characters (collision-suffixing does NOT flip
2310
+ * this).
2311
+ * originalSlug — what the slug would have been without the
2312
+ * SLUG_SOFT_CAP cut. Equal to `id` when nothing was
2313
+ * cut AND no collision suffix was appended; for
2314
+ * the truncation-detection use case the only thing
2315
+ * the CLI cares about is the lossy-vs-not
2316
+ * comparison surfaced via `truncated`.
1969
2317
  */
1970
2318
  interface IdFromTitleResult {
1971
2319
  id: string;
1972
2320
  truncated: boolean;
2321
+ originalSlug: string;
1973
2322
  }
1974
2323
  /**
1975
- * Verbose sibling of `idFromTitle`: returns both the unique id and the
1976
- * `truncated` flag from the slugify pass. Collision-suffixing (`_2`,
2324
+ * Verbose sibling of `idFromTitle`: returns the unique id, the
2325
+ * `truncated` flag from the slugify pass, and the un-truncated
2326
+ * `originalSlug` for `--json` consumers. Collision-suffixing (`_2`,
1977
2327
  * `_3`, …) does not flip `truncated` — the underlying slug's lossiness
1978
2328
  * is what the CLI hint cares about.
1979
2329
  */
@@ -2012,9 +2362,42 @@ declare function listInProgress(db: Db, workstream: string): TaskRow[];
2012
2362
  * raw-row type that was duplicating RawTaskRow
2013
2363
  * (review_code_raw_task_state_duplicate). */
2014
2364
  declare function listRecentClosed(db: Db, workstream: string, limit?: number): TaskRow[];
2365
+ /** Optional filter knobs for `listNotes`. Default-everything-undefined
2366
+ * preserves the historical "return every note, oldest-first" shape so
2367
+ * every existing caller (cmdTaskShow's notes block, exporting.ts's
2368
+ * bucket renderer, agents.test.ts) keeps working unchanged.
2369
+ *
2370
+ * Filters compose multiplicatively when both apply (`since` AND
2371
+ * `tail`): the timestamp filter is applied first, then `tail` slices
2372
+ * the last N of what survived. The CLI surface (`mu task notes
2373
+ * --tail / --since / --since-claim`) lives in src/cli/tasks/edit.ts;
2374
+ * the mutex between `--since` and `--since-claim` is a CLI concern,
2375
+ * not enforced here — if both arrive at the SDK, `since` wins (it's
2376
+ * the explicit one) and `sinceClaim` is ignored. The auto-resolve
2377
+ * for `sinceClaim` (look up the most recent `task claim` event in
2378
+ * agent_logs) happens here so the SDK is self-contained for scripted
2379
+ * callers. */
2380
+ interface ListNotesOptions {
2381
+ /** Print only the last N notes (after any timestamp filter). Must
2382
+ * be a positive integer; a value of 0 returns no rows but is not
2383
+ * an error here — CLI-side validation rejects `--tail 0`. */
2384
+ tail?: number;
2385
+ /** ISO-8601 cutoff: only notes with `created_at > since` survive.
2386
+ * Comparison is lexicographic on the ISO string (matches the way
2387
+ * the rest of the codebase compares ISO timestamps). */
2388
+ since?: string;
2389
+ /** When true and `since` is unset, look up the `created_at` of the
2390
+ * most recent `task claim` event for this task and use it as the
2391
+ * cutoff. Falls back to no filter when no claim event exists
2392
+ * (equivalent to `--since-beginning`). */
2393
+ sinceClaim?: boolean;
2394
+ }
2015
2395
  /** List notes for a task. Operator-facing local_id; resolves to the
2016
- * surrogate task id via taskIdFor (with optional workstream scope). */
2017
- declare function listNotes(db: Db, taskLocalId: string, workstream: string): TaskNoteRow[];
2396
+ * surrogate task id via taskIdFor (with optional workstream scope).
2397
+ *
2398
+ * Optional filters: see {@link ListNotesOptions}. Default behaviour
2399
+ * (no opts) is unchanged — every note, oldest-first. */
2400
+ declare function listNotes(db: Db, taskLocalId: string, workstream: string, opts?: ListNotesOptions): TaskNoteRow[];
2018
2401
  /**
2019
2402
  * All tasks currently owned by `agent` in a given workstream
2020
2403
  * (v5: agents.name is per-workstream unique). Sorted by local_id.
@@ -2295,8 +2678,8 @@ interface ExportSourceManifest {
2295
2678
  tasks: ExportTaskEntry[];
2296
2679
  }
2297
2680
  /** Top-level bucket manifest. `bucketVersion: 2` — the v0.3 shape.
2298
- * v1 (bucketVersion absent + top-level `workstream` field) is the
2299
- * legacy single-source shape and is rejected at write time. */
2681
+ * Manifests without `bucketVersion: 2` fall through to the
2682
+ * `corrupt` lane in `readManifest`. */
2300
2683
  interface ExportManifest {
2301
2684
  /** Schema discriminator. Always 2 in this codebase. */
2302
2685
  bucketVersion: 2;
@@ -2345,17 +2728,6 @@ interface RenderBucketResult {
2345
2728
  manifestPath: string;
2346
2729
  manifest: ExportManifest;
2347
2730
  }
2348
- /** Thrown when the operator points an export at a directory whose
2349
- * existing manifest predates bucket layout (v1, single-source). The
2350
- * fix is destructive (remove and re-export) so we refuse to touch
2351
- * it in-place — the legacy directory may be checked into git and
2352
- * the operator should choose between rebuilding it and picking a
2353
- * new --out. */
2354
- declare class LegacyExportLayoutError extends Error {
2355
- readonly outDir: string;
2356
- readonly name = "LegacyExportLayoutError";
2357
- constructor(outDir: string);
2358
- }
2359
2731
  /**
2360
2732
  * Render `input.sources` to disk under `input.outDir` in the v0.3
2361
2733
  * bucket layout. Idempotent + additive:
@@ -2364,8 +2736,6 @@ declare class LegacyExportLayoutError extends Error {
2364
2736
  * `input.sources` either appends (new) or refreshes (existing)
2365
2737
  * its subdirectory; sources NOT in `input.sources` are left
2366
2738
  * untouched.
2367
- * - If it exists but with a legacy (v1) manifest, throw
2368
- * `LegacyExportLayoutError`.
2369
2739
  *
2370
2740
  * Per-task idempotency is sha256-keyed: a re-export of the same
2371
2741
  * source against an unchanged DB rewrites zero task files. Tasks
@@ -2547,9 +2917,6 @@ interface ExportResult {
2547
2917
  * exporting a different workstream into the same bucket appends a
2548
2918
  * sibling subdir.
2549
2919
  *
2550
- * Throws:
2551
- * - `LegacyExportLayoutError` if `outDir` already contains a
2552
- * pre-0.3 (single-source) manifest.json.
2553
2920
  */
2554
2921
  declare function exportWorkstream(db: Db, opts: ExportWorkstreamOptions): ExportResult;
2555
2922
 
@@ -2560,12 +2927,6 @@ declare class ImportBucketInvalidError extends Error implements HasNextSteps {
2560
2927
  constructor(bucketDir: string, reason: string);
2561
2928
  errorNextSteps(): NextStep[];
2562
2929
  }
2563
- declare class ImportLegacyLayoutError extends Error implements HasNextSteps {
2564
- readonly bucketDir: string;
2565
- readonly name = "ImportLegacyLayoutError";
2566
- constructor(bucketDir: string);
2567
- errorNextSteps(): NextStep[];
2568
- }
2569
2930
  declare class WorkstreamAlreadyExistsError extends Error implements HasNextSteps {
2570
2931
  readonly workstream: string;
2571
2932
  readonly name = "WorkstreamAlreadyExistsError";
@@ -2627,8 +2988,8 @@ interface ImportBucketResult {
2627
2988
  * Per source-ws transactional: a failure in source A rolls back A
2628
2989
  * but leaves source B's import committed.
2629
2990
  *
2630
- * Throws on unrecoverable bucket-level errors (no manifest, legacy
2631
- * layout, --workstream override against multi-source). Per-source
2991
+ * Throws on unrecoverable bucket-level errors (no manifest,
2992
+ * --workstream override against multi-source). Per-source
2632
2993
  * errors (frontmatter parse, edge ref, target name collision) leave
2633
2994
  * the failing source's `errors` array populated and that source's
2634
2995
  * counts at zero; siblings still attempt their own import.
@@ -2790,6 +3151,11 @@ interface CreateWorkspaceOptions {
2790
3151
  backend?: VcsBackendName | VcsBackend;
2791
3152
  /** Optional ref to base the workspace on. Backend-specific. */
2792
3153
  parentRef?: string;
3154
+ /** INTERNAL. When false, suppress the `workspace create` system
3155
+ * event. Used by `recreateWorkspace` so the audit trail records
3156
+ * ONE atomic `workspace recreate` line instead of separate
3157
+ * free + create entries. Defaults to true. */
3158
+ _suppressEvent?: boolean;
2793
3159
  }
2794
3160
  /**
2795
3161
  * Create a fresh workspace for an agent. Allocates the on-disk
@@ -2806,6 +3172,12 @@ interface FreeWorkspaceOptions {
2806
3172
  /** If true, attempt to commit pending changes before tearing down.
2807
3173
  * Backend-specific; see VcsBackend.freeWorkspace. */
2808
3174
  commit?: boolean;
3175
+ /** INTERNAL. When false, suppress the `workspace free` system
3176
+ * event AND skip the pre-mutation snapshot capture. Used by
3177
+ * `recreateWorkspace` so the audit trail records ONE atomic
3178
+ * `workspace recreate` line and one snapshot for the whole
3179
+ * free+create cycle. Defaults to true. */
3180
+ _suppressEvent?: boolean;
2809
3181
  }
2810
3182
  interface FreeWorkspaceResult {
2811
3183
  /** The committed ref, when `commit` was true and there was something
@@ -2824,6 +3196,67 @@ interface FreeWorkspaceResult {
2824
3196
  declare function freeWorkspace(db: Db, agent: string, opts: FreeWorkspaceOptions & {
2825
3197
  workstream: string;
2826
3198
  }): Promise<FreeWorkspaceResult>;
3199
+ interface RecreateWorkspaceOptions {
3200
+ /** Same as createWorkspace; defaults to cwd. */
3201
+ projectRoot?: string;
3202
+ /** Same as createWorkspace; if undefined the previous backend is
3203
+ * reused (auto-detection re-runs only when --backend was passed). */
3204
+ backend?: VcsBackendName | VcsBackend;
3205
+ /** Same as createWorkspace; if undefined the new workspace bases on
3206
+ * the backend's current head (for git/jj/sl: the project's main),
3207
+ * which is the whole point of the verb. */
3208
+ parentRef?: string;
3209
+ /** When true, skip the dirty-check refusal and discard any
3210
+ * uncommitted changes in the existing workspace. The lossy escape
3211
+ * hatch — mirrors the implicit semantics of `mu workspace free`
3212
+ * without --commit. */
3213
+ force?: boolean;
3214
+ }
3215
+ interface RecreateWorkspaceResult {
3216
+ /** The freshly-created workspace row (the previous row is already
3217
+ * gone by the time we return). */
3218
+ workspace: WorkspaceRow;
3219
+ /** parent_ref of the WORKSPACE BEFORE recreate, so callers (and the
3220
+ * CLI's success message) can show "bumped from <old> -> <new>". */
3221
+ previousParentRef: string | null;
3222
+ }
3223
+ /**
3224
+ * Free + create in one atomic-ish verb. The whole point: between
3225
+ * waves the operator wants the SAME agent name with a fresh workspace
3226
+ * pinned to current main; doing `free` then `create` manually was the
3227
+ * dogfood-painful pattern (mufeedback note add_mu_workspace_recreate_free_create).
3228
+ *
3229
+ * Behaviour:
3230
+ * - Refuses with WorkspaceDirtyError if the existing workspace has
3231
+ * uncommitted changes (git/sl), UNLESS `force: true` is passed
3232
+ * (lossy escape hatch). jj is always-snapshotted so dirty is never
3233
+ * an issue; `none` has no VCS to consult so it never refuses.
3234
+ * - Reuses the SAME backend the previous workspace had unless
3235
+ * `backend` is explicitly overridden — a between-wave refresh
3236
+ * should not silently swap from git to none because the operator
3237
+ * happened to cd into a non-VCS dir. The override path matches
3238
+ * `mu workspace create --backend ...` semantics.
3239
+ * - Emits ONE `workspace recreate <agent>` event (with both old and
3240
+ * new parent_ref in the payload) instead of separate free + create
3241
+ * events. One pre-mutation snapshot is captured under the same
3242
+ * label so the undo trail shows one step.
3243
+ *
3244
+ * Throws:
3245
+ * - WorkspaceNotFoundError — no row for this (agent, workstream).
3246
+ * - AgentNotFoundError — propagated from createWorkspace's
3247
+ * typed agent check (the agent row
3248
+ * was deleted between the lookup and
3249
+ * the re-INSERT; vanishingly rare).
3250
+ * - WorkspaceDirtyError — dirty + !force.
3251
+ * - any backend-level Error — free or create failed.
3252
+ *
3253
+ * On a free-side failure the row + on-disk dir are best-effort gone;
3254
+ * on a create-side failure we surface the create error and the row is
3255
+ * already deleted (the operator can re-run `mu workspace create`).
3256
+ */
3257
+ declare function recreateWorkspace(db: Db, agent: string, opts: RecreateWorkspaceOptions & {
3258
+ workstream: string;
3259
+ }): Promise<RecreateWorkspaceResult>;
2827
3260
 
2828
3261
  type LogKind = "message" | "event" | "broadcast" | string;
2829
3262
  interface LogRow {
@@ -3127,4 +3560,4 @@ declare function deleteSnapshot(db: Db, snapshotId: number): DeleteSnapshotResul
3127
3560
  * the file is missing. Useful for `mu snapshot list --json` output. */
3128
3561
  declare function snapshotFileSize(snapshot: SnapshotRow): number | null;
3129
3562
 
3130
- export { type AddNoteOptions, type AddTaskOptions, type AddToArchiveResult, type AdoptAgentOptions, type AdoptAgentResult, AgentDiedOnSpawnError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, type AgentRow, type AgentStatus, type AppendLogOptions, type Archive, ArchiveAlreadyExistsError, ArchiveLabelInvalidError, ArchiveNotFoundError, type ArchiveSearchHit, type ArchiveSourceSummary, type ArchiveSummary, type ArchivedTaskRow, type BlockEdgeResult, CURRENT_SCHEMA_VERSION, type CaptureOptions, type CaptureSnapshotResult, type ClaimResult, type ClaimTaskOptions, ClaimerNotRegisteredError, type CloseAgentOptions, type CloseAgentResult, CrossWorkstreamEdgeError, CycleError, type Db, type DeleteSnapshotResult, type DeleteTaskResult, type DestroyResult, type DetectedStatus, EXPECTED_TABLES, type EvidenceOption, type ExportArchiveOptions, type ExportArchiveResult, type ExportManifest, type ExportResult, type ExportSource, type ExportSourceManifest, type ExportTaskEntry, type ExportWorkstreamOptions, type FreeAgentResult, HomeDirAsProjectRootError, type IdFromTitleResult, ImportBucketInvalidError, type ImportBucketOptions, type ImportBucketResult, ImportEdgeRefMissingError, ImportFrontmatterParseError, ImportLegacyLayoutError, type ImportSourceResult, type InsertAgentInput, LegacyExportLayoutError, type ListArchivedTasksOptions, type ListLiveAgentsOptions, type ListLogsOptions, type ListReadyOptions, type ListSnapshotsOptions, type ListTasksOptions, type LiveAgentsView, type LogKind, type LogRow, type NewSessionOptions, type NewWindowOptions, type OpenDbOptions, PANE_ID_RE, PaneNotFoundError, type PruneMode, type PruneOptions, PruneOptionsInvalidError, type PruneResult, type ReconcileMode, type ReconcileOptions, type ReconcileReport, type RejectDeferOptions, type RejectDeferResult, type ReleaseResult, type ReleaseTaskOptions, type RemoveBlockEdgeResult, type RemoveFromArchiveResult, type RenderBucketInput, type RenderBucketResult, type ReparentTaskResult, type RestoreSnapshotResult, STATUSES_TERMINAL_OR_PARKED, STATUS_EMOJI, SchemaTooOldError, type SearchArchivesOptions, type SearchTasksOptions, type SendOptions, type SetStatusResult, type SlugifyResult, SnapshotFileMissingError, SnapshotNotFoundError, type SnapshotRow, SnapshotVersionMismatchError, type SpawnAgentOptions, type SplitWindowOptions, type StrandedWorkspaceOrphan, TASK_STATUSES, TASK_STATUS_LIST, TaskAlreadyOwnedError, type TaskEdgeWithStatus, type TaskEdges, type TaskEdgesWithStatus, TaskExistsError, TaskHasOpenDependentsError, TaskNotFoundError, TaskNotInWorkstreamError, type TaskNoteRow, type TaskRow, type TaskStatus, type TaskWaitOptions, type TaskWaitRef, type TaskWaitResult, type TaskWaitTaskState, TmuxError, type TmuxExecResult, type TmuxExecutor, type TmuxPane, type TmuxSession, type TmuxWindow, type Track, type UpdateTaskOptions, type UpdateTaskResult, type VcsBackend, type VcsBackendName, type CreateWorkspaceOptions$1 as VcsCreateWorkspaceOptions, type CreateWorkspaceResult as VcsCreateWorkspaceResult, type FreeWorkspaceOptions$1 as VcsFreeWorkspaceOptions, type FreeWorkspaceResult$1 as VcsFreeWorkspaceResult, type CreateWorkspaceOptions as WorkspaceCreateOptions, WorkspaceExistsError, type WorkspaceFailure, type FreeWorkspaceOptions as WorkspaceFreeOptions, type FreeWorkspaceResult as WorkspaceFreeResult, WorkspaceNotFoundError, type WorkspaceOrphan, WorkspacePathNotEmptyError, WorkspacePreservedError, type WorkspaceRow, WorkstreamAlreadyExistsError, WorkstreamNameInvalidError, type WorkstreamOptions, type WorkstreamSummary, addBlockEdge, addNote, addTask, addToArchive, adoptAgent, appendLog, assertValidPaneId, backendByName, capturePane, captureSnapshot, claimTask, closeAgent, closeTask, composeAgentTitle, createArchive, createWorkspace, currentAgentName, currentPaneTitle, decorateWithStaleness, defaultDbPath, defaultSendDelayMs, defaultSpawnLivenessMs, defaultStateDir, deferTask, deleteAgent, deleteArchive, deleteSnapshot, deleteTask, destroyWorkstream, detectBackend, detectPiStatus, emitEvent, ensureWorkstream, ensureWorkstreamStateDir, exportArchive, exportSourceForWorkstream, exportSourcesForArchive, exportWorkstream, extractTail, freeAgent, freeWorkspace, gcMaxAgeDays, gcMaxCount, gcSnapshots, getAgent, getAgentByPane, getArchive, getParallelTracks, getPrerequisites, getTask, getTaskEdges, getTaskEdgesWithStatus, getWaitPollCount, getWorkspaceForAgent, gitBackend, idFromTitle, idFromTitleVerbose, importBucket, insertAgent, isStaleVersion, isTaskStatus, isValidAgentName, isValidArchiveLabel, isValidPaneId, isValidTaskId, isValidWorkstreamName, jjBackend, killPane, killSession, latestSeq, listAgents, listAllOrphanWorkspaces, listArchivedTasks, listArchives, listBlocked, listGoals, listInProgress, listLiveAgents, listLogs, listNotes, listPanes, listPanesInSession, listReady, listRecentClosed, listSessions, listSnapshots, listTasks, listTasksByOwner, listWindows, listWorkspaceOrphans, listWorkspaces, listWorkstreams, newSession, newSessionWithPane, newWindow, noneBackend, openDb, openTask, paneExists, parseAgentNameFromTitle, pruneSnapshots, readAgent, reconcile, refreshAgentTitle, rejectTask, releaseTask, removeBlockEdge, removeFromArchive, renderToBucket, reparentTask, resetSleep, resetTmuxExecutor, resetWaitPollCount, resolveActorIdentity, resolveCliCommand, restoreSnapshot, searchArchives, searchTasks, selectLayout, sendToAgent, sendToPane, sessionExists, setPaneTitle, setSleepForTests, setTaskStatus, setTmuxExecutor, setWaitSleepForTests, setWaitStuckWarnForTests, slBackend, sleep, slugifyTitle, slugifyTitleVerbose, snapshotFileSize, snapshotsDir, spawnAgent, splitWindow, summarizeWorkstream, tmux$1 as tmux, updateAgentStatus, updateTask, waitForTasks, workspacePath, workspacesRoot, workstreamStateDir };
3563
+ export { type AddNoteOptions, type AddTaskOptions, type AddToArchiveResult, type AdoptAgentOptions, type AdoptAgentResult, AgentDiedOnSpawnError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, type AgentRow, AgentSpawnCliNotFoundError, AgentSpawnStartupError, type AgentStatus, type AppendLogOptions, type Archive, ArchiveAlreadyExistsError, ArchiveLabelInvalidError, ArchiveNotFoundError, type ArchiveSearchHit, type ArchiveSourceSummary, type ArchiveSummary, type ArchivedTaskRow, type BlockEdgeResult, CURRENT_SCHEMA_VERSION, type CaptureOptions, type CaptureSnapshotResult, type ClaimResult, type ClaimTaskOptions, ClaimerNotRegisteredError, type CloseAgentOptions, type CloseAgentResult, type CommandResolutionResult, type CommandResolver, CrossWorkstreamEdgeError, CycleError, type Db, type DeleteSnapshotResult, type DeleteTaskResult, type DestroyResult, type DetectedStatus, EXPECTED_TABLES, type EvidenceOption, type ExportArchiveOptions, type ExportArchiveResult, type ExportManifest, type ExportResult, type ExportSource, type ExportSourceManifest, type ExportTaskEntry, type ExportWorkstreamOptions, type FreeAgentResult, HomeDirAsProjectRootError, type IdFromTitleResult, ImportBucketInvalidError, type ImportBucketOptions, type ImportBucketResult, ImportEdgeRefMissingError, ImportFrontmatterParseError, type ImportSourceResult, type InsertAgentInput, type KickAgentOptions, type KickAgentResult, type KickProcessExecutor, type KickSignal, type ListArchivedTasksOptions, type ListLiveAgentsOptions, type ListLogsOptions, type ListNotesOptions, type ListReadyOptions, type ListSnapshotsOptions, type ListTasksOptions, type LiveAgentsView, type LogKind, type LogRow, type NewSessionOptions, type NewWindowOptions, NoForegroundProcessError, type OpenDbOptions, PANE_ID_RE, PaneNotFoundError, type PruneMode, type PruneOptions, PruneOptionsInvalidError, type PruneResult, type ReconcileMode, type ReconcileOptions, type ReconcileReport, type RejectDeferOptions, type RejectDeferResult, type ReleaseResult, type ReleaseTaskOptions, type RemoveBlockEdgeResult, type RemoveFromArchiveResult, type RenderBucketInput, type RenderBucketResult, type ReparentTaskResult, type RestoreSnapshotResult, STATUSES_TERMINAL_OR_PARKED, STATUS_EMOJI, SchemaTooOldError, type SearchArchivesOptions, type SearchTasksOptions, type SendOptions, type SetStatusResult, type SlugifyResult, SnapshotFileMissingError, SnapshotNotFoundError, type SnapshotRow, SnapshotVersionMismatchError, type SpawnAgentOptions, type SplitWindowOptions, type StrandedWorkspaceOrphan, TASK_STATUSES, TASK_STATUS_LIST, TaskAlreadyOwnedError, type TaskEdgeWithStatus, type TaskEdges, type TaskEdgesWithStatus, TaskExistsError, TaskHasOpenDependentsError, TaskNotFoundError, TaskNotInWorkstreamError, type TaskNoteRow, type TaskRow, type TaskStatus, type TaskWaitOptions, type TaskWaitRef, type TaskWaitResult, type TaskWaitTaskState, TmuxError, type TmuxExecResult, type TmuxExecutor, type TmuxPane, type TmuxSession, type TmuxWindow, type Track, type UpdateTaskOptions, type UpdateTaskResult, type VcsBackend, type VcsBackendName, type CreateWorkspaceOptions$1 as VcsCreateWorkspaceOptions, type CreateWorkspaceResult as VcsCreateWorkspaceResult, type FreeWorkspaceOptions$1 as VcsFreeWorkspaceOptions, type FreeWorkspaceResult$1 as VcsFreeWorkspaceResult, type CreateWorkspaceOptions as WorkspaceCreateOptions, WorkspaceExistsError, type WorkspaceFailure, type FreeWorkspaceOptions as WorkspaceFreeOptions, type FreeWorkspaceResult as WorkspaceFreeResult, WorkspaceNotFoundError, type WorkspaceOrphan, WorkspacePathNotEmptyError, WorkspacePreservedError, type RecreateWorkspaceOptions as WorkspaceRecreateOptions, type RecreateWorkspaceResult as WorkspaceRecreateResult, type WorkspaceRow, WorkstreamAlreadyExistsError, WorkstreamNameInvalidError, type WorkstreamOptions, type WorkstreamSummary, addBlockEdge, addNote, addTask, addToArchive, adoptAgent, appendLog, assertValidPaneId, backendByName, capturePane, captureSnapshot, checkCommandResolvable, claimTask, closeAgent, closeTask, composeAgentTitle, createArchive, createWorkspace, currentAgentName, currentPaneTitle, decorateWithStaleness, defaultDbPath, defaultSendDelayMs, defaultSpawnLivenessMs, defaultStateDir, deferTask, deleteAgent, deleteArchive, deleteSnapshot, deleteTask, destroyWorkstream, detectBackend, detectPiStatus, emitEvent, ensureWorkstream, ensureWorkstreamStateDir, envVarNameForCli, exportArchive, exportSourceForWorkstream, exportSourcesForArchive, exportWorkstream, extractTail, foregroundPgid, freeAgent, freeWorkspace, gcMaxAgeDays, gcMaxCount, gcSnapshots, getAgent, getAgentByPane, getArchive, getParallelTracks, getPrerequisites, getTask, getTaskEdges, getTaskEdgesWithStatus, getWaitPollCount, getWorkspaceForAgent, gitBackend, idFromTitle, idFromTitleVerbose, importBucket, insertAgent, isKickSignal, isStaleVersion, isTaskStatus, isValidAgentName, isValidArchiveLabel, isValidPaneId, isValidTaskId, isValidWorkstreamName, jjBackend, kickAgent, killPane, killSession, latestSeq, listAgents, listAllOrphanWorkspaces, listArchivedTasks, listArchives, listBlocked, listGoals, listInProgress, listLiveAgents, listLogs, listNotes, listPanes, listPanesInSession, listReady, listRecentClosed, listSessions, listSnapshots, listTasks, listTasksByOwner, listWindows, listWorkspaceOrphans, listWorkspaces, listWorkstreams, newSession, newSessionWithPane, newWindow, noneBackend, openDb, openTask, paneExists, paneTTY, parseAgentNameFromTitle, parsePsTtyOutput, pruneSnapshots, readAgent, reconcile, recreateWorkspace, refreshAgentTitle, rejectTask, releaseTask, removeBlockEdge, removeFromArchive, renderToBucket, reparentTask, resetCommandResolverForTests, resetKickProcessExecutor, resetSleep, resetTmuxExecutor, resetWaitPollCount, resolveActorIdentity, resolveCliCommand, resolveCliCommandWithSource, restoreSnapshot, searchArchives, searchTasks, selectLayout, sendToAgent, sendToPane, sessionExists, setCommandResolverForTests, setKickProcessExecutor, setPaneTitle, setSleepForTests, setTaskStatus, setTmuxExecutor, setWaitSleepForTests, setWaitStuckWarnForTests, slBackend, sleep, slugifyTitle, slugifyTitleVerbose, snapshotFileSize, snapshotsDir, spawnAgent, splitWindow, summarizeWorkstream, tmux$1 as tmux, updateAgentStatus, updateTask, waitForTasks, workspacePath, workspacesRoot, workstreamStateDir };