@martintrojer/mu 0.4.1 → 0.4.3

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/cli.js CHANGED
@@ -645,7 +645,12 @@ var init_logs = __esm({
645
645
  // src/exporting.ts — archive export emits the bucket-render summary
646
646
  // as a machine-wide event (workstream=null; the export spans every
647
647
  // source-ws in the archive).
648
- "archive export"
648
+ "archive export",
649
+ // src/db-sync.ts — emitted per-workstream after a successful
650
+ // `mu db export`. Used as the marker for src/parked.ts (the
651
+ // "presumed parked on another machine" heuristic for `mu workstream
652
+ // list` / TUI tab strip).
653
+ "db export"
649
654
  ];
650
655
  }
651
656
  });
@@ -1078,17 +1083,19 @@ async function reconcile(db, opts) {
1078
1083
  let statusChanges = 0;
1079
1084
  const orphans = [];
1080
1085
  const survivors = [];
1086
+ const pendingSurvivors = [];
1081
1087
  for (const agent of dbAgents) {
1082
- if (tmuxByPaneId.has(agent.paneId)) {
1088
+ if (isPendingPaneId(agent.paneId)) {
1089
+ pendingSurvivors.push(agent);
1090
+ } else if (tmuxByPaneId.has(agent.paneId)) {
1083
1091
  survivors.push(agent);
1084
1092
  } else {
1085
1093
  if (mode === "full") deleteAgent(db, agent.name, agent.workstreamName);
1086
1094
  prunedGhosts++;
1087
1095
  }
1088
1096
  }
1089
- if (mode !== "report-only") {
1097
+ if (mode === "full") {
1090
1098
  for (const agent of survivors) {
1091
- if (isPendingPaneId(agent.paneId)) continue;
1092
1099
  const scrollback = await capturePane(agent.paneId, { lines: 100 });
1093
1100
  const detected = detectPiStatus(scrollback);
1094
1101
  if (shouldOverwriteAgentStatus(agent.status, detected) && detected !== agent.status) {
@@ -5633,6 +5640,37 @@ var init_exporting = __esm({
5633
5640
  }
5634
5641
  });
5635
5642
 
5643
+ // src/parked.ts
5644
+ function parkedStatus(db, workstream, opts = {}) {
5645
+ const wsRow = db.prepare("SELECT id FROM workstreams WHERE name = ?").get(workstream);
5646
+ if (wsRow === void 0) return { parked: false };
5647
+ const latest = db.prepare(
5648
+ "SELECT kind, payload, created_at FROM agent_logs WHERE workstream_id = ? ORDER BY seq DESC LIMIT 1"
5649
+ ).get(wsRow.id);
5650
+ if (latest === void 0) return { parked: false };
5651
+ if (latest.kind !== "event") return { parked: false };
5652
+ if (!latest.payload.startsWith("db export ")) return { parked: false };
5653
+ const aliveAgent = db.prepare("SELECT 1 AS x FROM agents WHERE workstream_id = ? AND status != 'closed' LIMIT 1").get(wsRow.id);
5654
+ if (aliveAgent !== void 0) return { parked: false };
5655
+ const inProgress = db.prepare("SELECT 1 AS x FROM tasks WHERE workstream_id = ? AND status = 'IN_PROGRESS' LIMIT 1").get(wsRow.id);
5656
+ if (inProgress !== void 0) return { parked: false };
5657
+ const threshold = Math.max(0, opts.thresholdDays ?? WORKSTREAM_PARKED_THRESHOLD_DAYS);
5658
+ const exportedAt = Date.parse(latest.created_at);
5659
+ if (Number.isNaN(exportedAt)) return { parked: false };
5660
+ const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
5661
+ const deltaMs = now - exportedAt;
5662
+ const deltaDays = Math.floor(deltaMs / (24 * 60 * 60 * 1e3));
5663
+ if (deltaDays < threshold) return { parked: false };
5664
+ return { parked: true, sinceDays: deltaDays };
5665
+ }
5666
+ var WORKSTREAM_PARKED_THRESHOLD_DAYS;
5667
+ var init_parked = __esm({
5668
+ "src/parked.ts"() {
5669
+ "use strict";
5670
+ WORKSTREAM_PARKED_THRESHOLD_DAYS = 1;
5671
+ }
5672
+ });
5673
+
5636
5674
  // src/workstream.ts
5637
5675
  import { existsSync as existsSync11, readdirSync as readdirSync2, rmdirSync } from "fs";
5638
5676
  import { join as join7, resolve as resolve4 } from "path";
@@ -5715,6 +5753,7 @@ async function listEmptyWorkstreams(db) {
5715
5753
  }
5716
5754
  async function summarizeWorkstream(db, opts) {
5717
5755
  const tmuxSession = opts.tmuxSession ?? `mu-${opts.workstream}`;
5756
+ const parked = parkedStatus(db, opts.workstream);
5718
5757
  return {
5719
5758
  name: opts.workstream,
5720
5759
  tmuxSession,
@@ -5724,7 +5763,8 @@ async function summarizeWorkstream(db, opts) {
5724
5763
  noteCount: countNotes(db, opts.workstream),
5725
5764
  edgeCount: countEdges(db, opts.workstream),
5726
5765
  workspaceCount: listWorkspaces(db, opts.workstream).length,
5727
- registered: isRegistered(db, opts.workstream)
5766
+ registered: isRegistered(db, opts.workstream),
5767
+ ...parked.parked ? { parked: { sinceDays: parked.sinceDays ?? 0 } } : {}
5728
5768
  };
5729
5769
  }
5730
5770
  function isRegistered(db, workstream) {
@@ -5868,6 +5908,7 @@ var init_workstream = __esm({
5868
5908
  init_db();
5869
5909
  init_exporting();
5870
5910
  init_logs();
5911
+ init_parked();
5871
5912
  init_snapshots();
5872
5913
  init_tmux();
5873
5914
  init_vcs2();
@@ -6854,6 +6895,9 @@ function shouldOverwriteAgentStatus(current, detected) {
6854
6895
  }
6855
6896
  return true;
6856
6897
  }
6898
+ function agentStatusGlyph(status) {
6899
+ return STATUS_EMOJI[status] ?? "?";
6900
+ }
6857
6901
  function pendingPaneIdFor(agentName) {
6858
6902
  return `${PENDING_PANE_PREFIX}${agentName}`;
6859
6903
  }
@@ -6865,7 +6909,7 @@ function composeAgentTitle(db, agent) {
6865
6909
  const tasks = listTasksByOwner(db, agent.workstreamName, agent.name);
6866
6910
  let title = agent.name;
6867
6911
  if (showStatus) {
6868
- title += ` \xB7 ${STATUS_EMOJI[agent.status]}`;
6912
+ title += ` \xB7 ${agentStatusGlyph(agent.status)}`;
6869
6913
  }
6870
6914
  if (tasks.length === 1) {
6871
6915
  title += ` \xB7 ${tasks[0]?.name}`;
@@ -7271,7 +7315,7 @@ function loadDoctorSummary(db, snapshot) {
7271
7315
  checks.push({
7272
7316
  name: "agents",
7273
7317
  status: "warn",
7274
- detail: `${ghosts} ghost pane${ghosts === 1 ? "" : "s"}; run \`mu agent list\``
7318
+ detail: `${ghosts} ghost pane${ghosts === 1 ? "" : "s"}; run \`mu state\` or \`mu agent list\` to reap`
7275
7319
  });
7276
7320
  }
7277
7321
  const orphanPanes = snapshot.view.orphans.length;
@@ -7308,7 +7352,7 @@ function loadDoctorChecks(db, snapshot) {
7308
7352
  function yankCommandForCheck(check) {
7309
7353
  switch (check.name) {
7310
7354
  case "agents":
7311
- return "mu agent list";
7355
+ return "mu state";
7312
7356
  case "panes":
7313
7357
  return "mu agent adopt";
7314
7358
  case "workspaces":
@@ -7326,10 +7370,10 @@ function remediationParagraph(check) {
7326
7370
  switch (check.name) {
7327
7371
  case "agents":
7328
7372
  return [
7329
- "A 'ghost pane' is a tmux pane that mu's reconcile pass would",
7330
- "prune on the next mutation. Run `mu agent list` to see the",
7331
- "current state, then `mu agent close <name>` if the agent is",
7332
- "stale. The TUI is read-only \u2014 no auto-prune."
7373
+ "A 'ghost pane' is a registered agent whose tmux pane is gone.",
7374
+ "Doctor only reports the count. Run `mu state` or `mu agent list`",
7375
+ "to reap ghost agents and return their IN_PROGRESS tasks to OPEN.",
7376
+ "The TUI is read-only, but its slow tick uses the same state reap."
7333
7377
  ];
7334
7378
  case "panes":
7335
7379
  return [
@@ -7513,7 +7557,7 @@ async function loadWorkstreamSnapshotFast(db, workstream, opts = {}) {
7513
7557
  };
7514
7558
  }
7515
7559
  async function loadWorkstreamSnapshotSlow(db, workstream, opts = {}, baseSnapshot) {
7516
- const view = await listLiveAgents(db, { workstream, mode: "status-only" });
7560
+ const view = await listLiveAgents(db, { workstream });
7517
7561
  let workspaces = listWorkspaces(db, workstream);
7518
7562
  if (opts.withDirty === true) workspaces = await decorateWithDirty(workspaces);
7519
7563
  const commits = await loadRecentCommits(opts.withRecentCommits);
@@ -7556,7 +7600,7 @@ function emptyLiveAgentsView() {
7556
7600
  return {
7557
7601
  agents: [],
7558
7602
  orphans: [],
7559
- report: { prunedGhosts: 0, statusChanges: 0, orphans: [], mode: "status-only" }
7603
+ report: { prunedGhosts: 0, statusChanges: 0, orphans: [], mode: "full" }
7560
7604
  };
7561
7605
  }
7562
7606
  function minimalSnapshot(workstream) {
@@ -7640,9 +7684,30 @@ var init_state = __esm({
7640
7684
  }
7641
7685
  });
7642
7686
 
7687
+ // src/cli/tui/agent-display.ts
7688
+ function agentByName(snapshot) {
7689
+ const agents = snapshot?.view?.agents ?? [];
7690
+ return new Map(agents.map((a) => [a.name, a]));
7691
+ }
7692
+ function formatKnownAgentDisplayName(agent) {
7693
+ return `${agentStatusGlyph(agent.status)} ${agent.name}`;
7694
+ }
7695
+ function formatAgentRefDisplayName(agentName, agents) {
7696
+ if (agentName === null) return "\u2014";
7697
+ const agent = agents.get(agentName);
7698
+ if (agent === void 0) return agentName;
7699
+ return formatKnownAgentDisplayName(agent);
7700
+ }
7701
+ var init_agent_display = __esm({
7702
+ "src/cli/tui/agent-display.ts"() {
7703
+ "use strict";
7704
+ init_agents();
7705
+ }
7706
+ });
7707
+
7643
7708
  // src/cli/format.ts
7644
7709
  function statusIcon(status) {
7645
- return STATUS_COLORS[status](STATUS_EMOJI[status]);
7710
+ return STATUS_COLORS[status](agentStatusGlyph(status));
7646
7711
  }
7647
7712
  function colorStatus(status) {
7648
7713
  switch (status) {
@@ -7823,17 +7888,19 @@ function printLogRow(row2) {
7823
7888
  }
7824
7889
  function formatWorkstreamsTable(rows) {
7825
7890
  const table = muTable({
7826
- head: ["name", "tmux", "agents", "tasks", "edges", "notes"].map((h) => pc.bold(h)),
7827
- colWidths: [40, null, null, null, null, null]
7891
+ head: ["name", "tmux", "agents", "tasks", "edges", "notes", "parked"].map((h) => pc.bold(h)),
7892
+ colWidths: [40, null, null, null, null, null, null]
7828
7893
  });
7829
7894
  for (const r of rows) {
7895
+ const parkedCell = r.parked ? pc.yellow(`${r.parked.sinceDays}d`) : pc.dim("\u2014");
7830
7896
  table.push([
7831
7897
  r.name,
7832
7898
  r.tmuxAlive ? pc.green("alive") : pc.dim("\u2014"),
7833
7899
  String(r.agentCount),
7834
7900
  String(r.taskCount),
7835
7901
  String(r.edgeCount),
7836
- String(r.noteCount)
7902
+ String(r.noteCount),
7903
+ parkedCell
7837
7904
  ]);
7838
7905
  }
7839
7906
  return table.toString();
@@ -8125,6 +8192,58 @@ function cullCardsForRows(visibleCardIds2, availableRows) {
8125
8192
  }
8126
8193
  return { cards: cards.filter((id) => remaining.has(id)), hidden };
8127
8194
  }
8195
+ function balanceColumns(assignments, dataCountFn) {
8196
+ if (assignments.length < 2) return assignments.map((a) => ({ cards: [...a.cards] }));
8197
+ const totalCards = assignments.reduce((sum, a) => sum + a.cards.length, 0);
8198
+ if (totalCards >= TOTAL_CARD_COUNT) return assignments.map((a) => ({ cards: [...a.cards] }));
8199
+ const cols = assignments.map((a) => [...a.cards]);
8200
+ const heightOf = (id) => {
8201
+ const config = CARD_CONFIGS[id];
8202
+ const data = Math.max(0, Math.floor(dataCountFn(id)));
8203
+ return config.chrome + clamp(data, config.minRows, config.maxRows);
8204
+ };
8205
+ const isAnchored = (id) => id === 0 || id === 3;
8206
+ const heightsOf = (lanes) => lanes.map((lane) => lane.reduce((sum, id) => sum + heightOf(id), 0));
8207
+ const safetyMax = cols.length * TOTAL_CARD_COUNT;
8208
+ for (let iter = 0; iter < safetyMax; iter++) {
8209
+ const heights = heightsOf(cols);
8210
+ const startSpread = Math.max(...heights) - Math.min(...heights);
8211
+ if (startSpread <= 0) break;
8212
+ let best = null;
8213
+ for (let donor = 0; donor < cols.length; donor++) {
8214
+ const donorCards2 = cols[donor];
8215
+ const donorH = heights[donor];
8216
+ if (donorCards2 === void 0 || donorH === void 0) continue;
8217
+ if (donorCards2.length <= 1) continue;
8218
+ for (let cardIndex = 0; cardIndex < donorCards2.length; cardIndex++) {
8219
+ const card = donorCards2[cardIndex];
8220
+ if (card === void 0 || isAnchored(card)) continue;
8221
+ const cardH = heightOf(card);
8222
+ for (let receiver = 0; receiver < cols.length; receiver++) {
8223
+ if (receiver === donor) continue;
8224
+ const receiverH = heights[receiver];
8225
+ if (receiverH === void 0) continue;
8226
+ const newHeights = heights.slice();
8227
+ newHeights[donor] = donorH - cardH;
8228
+ newHeights[receiver] = receiverH + cardH;
8229
+ const newSpread = Math.max(...newHeights) - Math.min(...newHeights);
8230
+ if (newSpread < startSpread && (best === null || newSpread < best.spread)) {
8231
+ best = { donor, receiver, cardIndex, spread: newSpread };
8232
+ }
8233
+ }
8234
+ }
8235
+ }
8236
+ if (best === null) break;
8237
+ const donorCards = cols[best.donor];
8238
+ const receiverCards = cols[best.receiver];
8239
+ if (donorCards === void 0 || receiverCards === void 0) break;
8240
+ const moved = donorCards.splice(best.cardIndex, 1)[0];
8241
+ if (moved === void 0) break;
8242
+ receiverCards.push(moved);
8243
+ receiverCards.sort(compareSlot);
8244
+ }
8245
+ return cols.map((cards) => ({ cards }));
8246
+ }
8128
8247
  function allocateRowBudgets(availableRows, cards) {
8129
8248
  const entries = cards.map((card) => {
8130
8249
  const config = card.config ?? CARD_CONFIGS[card.id];
@@ -8238,7 +8357,7 @@ function tallestMinStackRows(ids) {
8238
8357
  function clamp(value, min, max) {
8239
8358
  return Math.max(min, Math.min(max, value));
8240
8359
  }
8241
- var CARD_CHROME_ROWS, CARD_CONFIGS, CARD_CULL_PRIORITY, CARD_CULL_LAYOUT_COLS;
8360
+ var CARD_CHROME_ROWS, CARD_CONFIGS, CARD_CULL_PRIORITY, CARD_CULL_LAYOUT_COLS, TOTAL_CARD_COUNT;
8242
8361
  var init_layout = __esm({
8243
8362
  "src/cli/tui/layout.ts"() {
8244
8363
  "use strict";
@@ -8327,6 +8446,7 @@ var init_layout = __esm({
8327
8446
  };
8328
8447
  CARD_CULL_PRIORITY = [9, 8, 5, 2, 7, 6, 4, 0, 1, 3];
8329
8448
  CARD_CULL_LAYOUT_COLS = 140;
8449
+ TOTAL_CARD_COUNT = 10;
8330
8450
  }
8331
8451
  });
8332
8452
 
@@ -8588,7 +8708,7 @@ function AgentsCard({ snapshot, rowBudget, cols }) {
8588
8708
  const owned = snapshot.inProgress.filter((t) => t.ownerName === a.name);
8589
8709
  const taskBit = summarizeOwnedTasks(owned).bit;
8590
8710
  const idle = a.idle ? "\u26A0 idle" : "";
8591
- return [STATUS_EMOJI[a.status] ?? "?", a.name, taskBit, idle];
8711
+ return [agentStatusGlyph(a.status), a.name, taskBit, idle];
8592
8712
  });
8593
8713
  const widths = layoutColumns(rows, COLUMN_SPECS, contentWidth);
8594
8714
  return /* @__PURE__ */ jsx6(
@@ -8630,8 +8750,8 @@ var cardConfig, COLUMN_SPECS;
8630
8750
  var init_agents2 = __esm({
8631
8751
  "src/cli/tui/cards/agents.tsx"() {
8632
8752
  "use strict";
8633
- init_agents();
8634
8753
  init_state();
8754
+ init_agent_display();
8635
8755
  init_columns();
8636
8756
  init_layout();
8637
8757
  init_list_row();
@@ -9077,11 +9197,12 @@ function InProgressCard({ snapshot, rowBudget, cols }) {
9077
9197
  const shown = inProgress.slice(0, rowBudget ?? cardConfig5.maxRows);
9078
9198
  const more = inProgress.length - shown.length;
9079
9199
  const bottomLabel = more > 0 ? `+${more} more \xB7 Shift+6` : void 0;
9200
+ const agentLookup = agentByName(snapshot);
9080
9201
  const rows = shown.map((t, i) => [
9081
9202
  GLYPH2,
9082
9203
  t.name,
9083
9204
  t.status,
9084
- t.ownerName ?? "\u2014",
9205
+ formatAgentRefDisplayName(t.ownerName, agentLookup),
9085
9206
  formatSinceClaim(ages[i] ?? null),
9086
9207
  t.title
9087
9208
  ]);
@@ -9132,8 +9253,8 @@ var cardConfig5, GLYPH2, STALE_CLAIM_THRESHOLD_MS, COLUMN_SPECS5, glyphFor3;
9132
9253
  var init_inprogress = __esm({
9133
9254
  "src/cli/tui/cards/inprogress.tsx"() {
9134
9255
  "use strict";
9135
- init_agents();
9136
9256
  init_format();
9257
+ init_agent_display();
9137
9258
  init_columns();
9138
9259
  init_format_helpers();
9139
9260
  init_layout();
@@ -9141,7 +9262,7 @@ var init_inprogress = __esm({
9141
9262
  init_titled_box();
9142
9263
  init_placeholder();
9143
9264
  cardConfig5 = CARD_CONFIGS[6];
9144
- GLYPH2 = STATUS_EMOJI.busy ?? "\u2699";
9265
+ GLYPH2 = agentStatusGlyph("busy");
9145
9266
  STALE_CLAIM_THRESHOLD_MS = 3e5;
9146
9267
  COLUMN_SPECS5 = [
9147
9268
  { kind: "protect" },
@@ -9282,12 +9403,13 @@ function ReadyCard({ snapshot, rowBudget, cols }) {
9282
9403
  const roiText = formatRoi(t.impact, t.effortDays);
9283
9404
  return { bucket, roiText };
9284
9405
  });
9406
+ const agentLookup = agentByName(snapshot);
9285
9407
  const rows = shown.map((t, i) => [
9286
9408
  t.name,
9287
9409
  t.status,
9288
9410
  `ROI ${meta[i]?.roiText ?? ""}`,
9289
9411
  t.title,
9290
- t.ownerName ?? "\u2014"
9412
+ formatAgentRefDisplayName(t.ownerName, agentLookup)
9291
9413
  ]);
9292
9414
  const widths = layoutColumns(rows, COLUMN_SPECS7, contentWidth);
9293
9415
  return /* @__PURE__ */ jsx12(
@@ -9327,6 +9449,7 @@ var init_ready = __esm({
9327
9449
  "use strict";
9328
9450
  init_state();
9329
9451
  init_format();
9452
+ init_agent_display();
9330
9453
  init_columns();
9331
9454
  init_format_helpers();
9332
9455
  init_layout();
@@ -9577,9 +9700,10 @@ function WorkspacesCard({ snapshot, rowBudget, cols }) {
9577
9700
  const shown = workspaces.slice(0, rowBudget ?? cardConfig10.maxRows);
9578
9701
  const more = workspaces.length - shown.length;
9579
9702
  const bottomLabel = more > 0 ? `+${more} more \xB7 Shift+5` : void 0;
9703
+ const agentLookup = agentByName(snapshot);
9580
9704
  const rows = shown.map((w) => [
9581
9705
  glyphFor5(w),
9582
- w.agentName,
9706
+ formatAgentRefDisplayName(w.agentName, agentLookup),
9583
9707
  w.backend,
9584
9708
  formatBehind2(w.commitsBehindMain),
9585
9709
  w.parentRef ? w.parentRef.slice(0, 12) : "\u2014"
@@ -9649,6 +9773,7 @@ var init_workspaces = __esm({
9649
9773
  "src/cli/tui/cards/workspaces.tsx"() {
9650
9774
  "use strict";
9651
9775
  init_workspace();
9776
+ init_agent_display();
9652
9777
  init_columns();
9653
9778
  init_layout();
9654
9779
  init_list_row();
@@ -9790,6 +9915,9 @@ var init_keymap_spec = __esm({
9790
9915
  row("/", "filter/search rows", ["/"]),
9791
9916
  row("Enter", "drill into focused row", ["Enter"]),
9792
9917
  row("y", "yank action for focused row", ["y"]),
9918
+ row("a", "attach to focused agent's tmux pane (Agents popup only; user-driven TUI escape)", [
9919
+ "a"
9920
+ ]),
9793
9921
  row("l", "launch lazygit in the project root (Commits popup only; user-driven TUI escape)", [
9794
9922
  "l"
9795
9923
  ]),
@@ -10287,6 +10415,65 @@ var init_popup_shell = __esm({
10287
10415
  }
10288
10416
  });
10289
10417
 
10418
+ // src/cli/tui/tmux-attach.ts
10419
+ import { spawnSync } from "child_process";
10420
+ function runTmuxAttachInteractive(opts, deps = {}) {
10421
+ const run3 = deps.spawn ?? spawnSync;
10422
+ const write = deps.write ?? ((text2) => process.stdout.write(text2));
10423
+ const env = deps.env ?? process.env;
10424
+ const target = `${opts.session}:${opts.window}`;
10425
+ const insideTmux = typeof env.TMUX === "string" && env.TMUX.length > 0;
10426
+ let result = { ok: true };
10427
+ try {
10428
+ write(ALT_SCREEN_EXIT);
10429
+ if (insideTmux) {
10430
+ const r = run3("tmux", ["switch-client", "-t", target], {
10431
+ stdio: "inherit",
10432
+ env
10433
+ });
10434
+ if (r.error !== void 0) {
10435
+ result = { ok: false, error: tmuxAttachErrorMessage(r.error) };
10436
+ } else if (typeof r.status === "number" && r.status !== 0) {
10437
+ result = { ok: false, error: `tmux switch-client exited ${r.status}` };
10438
+ }
10439
+ } else {
10440
+ const attach = run3("tmux", ["attach-session", "-t", opts.session], {
10441
+ stdio: "inherit",
10442
+ env
10443
+ });
10444
+ if (attach.error !== void 0) {
10445
+ result = { ok: false, error: tmuxAttachErrorMessage(attach.error) };
10446
+ } else if (typeof attach.status === "number" && attach.status !== 0) {
10447
+ result = { ok: false, error: `tmux attach-session exited ${attach.status}` };
10448
+ } else {
10449
+ run3("tmux", ["select-window", "-t", target], { stdio: "inherit", env });
10450
+ }
10451
+ }
10452
+ } catch (err) {
10453
+ result = { ok: false, error: tmuxAttachErrorMessage(err) };
10454
+ } finally {
10455
+ try {
10456
+ write(ALT_SCREEN_ENTER);
10457
+ } catch {
10458
+ }
10459
+ }
10460
+ return result;
10461
+ }
10462
+ function tmuxAttachErrorMessage(err) {
10463
+ if (err instanceof Error) {
10464
+ const code = err.code;
10465
+ if (code === "ENOENT") return "tmux not found \xB7 install tmux";
10466
+ return err.message.length > 0 ? err.message : String(err);
10467
+ }
10468
+ return String(err);
10469
+ }
10470
+ var init_tmux_attach = __esm({
10471
+ "src/cli/tui/tmux-attach.ts"() {
10472
+ "use strict";
10473
+ init_escapes();
10474
+ }
10475
+ });
10476
+
10290
10477
  // src/cli/tui/use-popup-action-queue.ts
10291
10478
  import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
10292
10479
  function usePopupActionQueue(actions, dispatch) {
@@ -10653,6 +10840,7 @@ function AgentsPopup({
10653
10840
  onModeChange,
10654
10841
  onFilterEditingChange,
10655
10842
  popupActions,
10843
+ onFooter,
10656
10844
  db,
10657
10845
  workstream
10658
10846
  }) {
@@ -10771,6 +10959,14 @@ function AgentsPopup({
10771
10959
  void yank2(`mu agent close ${a.name} -w ${ws}`);
10772
10960
  return;
10773
10961
  }
10962
+ if (action.key === "a") {
10963
+ const session = `mu-${ws}`;
10964
+ const window = a.tab ?? a.name;
10965
+ const r = runTmuxAttachInteractive({ session, window });
10966
+ if (!r.ok) onFooter?.(r.error ?? "tmux attach failed", false, "error");
10967
+ else onFooter?.(`tmux switch-client \u2192 ${session}:${window}`, true, "info");
10968
+ return;
10969
+ }
10774
10970
  return;
10775
10971
  }
10776
10972
  }
@@ -10811,13 +11007,13 @@ function AgentsPopup({
10811
11007
  ) }) });
10812
11008
  }
10813
11009
  const { start, visible } = centredVisibleSlice(agents, safeCursor, viewport);
10814
- const rows = visible.map((a) => [STATUS_EMOJI[a.status] ?? "?", a.name, a.status, a.role]);
11010
+ const rows = visible.map((a) => [agentStatusGlyph(a.status), a.name, a.status, a.role]);
10815
11011
  const widths = layoutColumns(rows, COLUMN_SPECS11, contentWidth);
10816
11012
  return /* @__PURE__ */ jsxs10(
10817
11013
  PopupShell,
10818
11014
  {
10819
11015
  title: `Agents \xB7 popup (${safeCursor + 1}/${agents.length})`,
10820
- hint: "f free \xB7 x close \xB7 y yanks `mu agent send`",
11016
+ hint: "a attach \xB7 f free \xB7 x close \xB7 y yanks `mu agent send`",
10821
11017
  children: [
10822
11018
  /* @__PURE__ */ jsx20(Box7, { flexDirection: "column", flexGrow: 1, children: visible.map((a, i) => {
10823
11019
  const sel = start + i === safeCursor;
@@ -10845,10 +11041,12 @@ var init_agents3 = __esm({
10845
11041
  "src/cli/tui/popups/agents.tsx"() {
10846
11042
  "use strict";
10847
11043
  init_agents();
11044
+ init_agent_display();
10848
11045
  init_columns();
10849
11046
  init_keys();
10850
11047
  init_list_row();
10851
11048
  init_popup_shell();
11049
+ init_tmux_attach();
10852
11050
  init_use_popup_action_queue();
10853
11051
  init_use_popup_filter();
10854
11052
  init_drill();
@@ -11201,10 +11399,11 @@ function AllTasksPopup({
11201
11399
  ) }) });
11202
11400
  }
11203
11401
  const { start, visible: windowed } = centredVisibleSlice(visibleTasks, safeCursor, viewport);
11402
+ const agentLookup = agentByName(snapshot);
11204
11403
  const rows = windowed.map((t) => [
11205
11404
  t.name,
11206
11405
  t.status,
11207
- t.ownerName ?? "\u2014",
11406
+ formatAgentRefDisplayName(t.ownerName, agentLookup),
11208
11407
  formatRoi(t.impact, t.effortDays),
11209
11408
  t.title
11210
11409
  ]);
@@ -11297,6 +11496,7 @@ var init_all_tasks = __esm({
11297
11496
  init_tasks();
11298
11497
  init_sort();
11299
11498
  init_format();
11499
+ init_agent_display();
11300
11500
  init_columns();
11301
11501
  init_format_helpers();
11302
11502
  init_keys();
@@ -11534,9 +11734,9 @@ var init_blocked2 = __esm({
11534
11734
  });
11535
11735
 
11536
11736
  // src/cli/tui/lazygit.ts
11537
- import { spawnSync } from "child_process";
11737
+ import { spawnSync as spawnSync2 } from "child_process";
11538
11738
  function runLazygitInteractive(opts, deps = {}) {
11539
- const run3 = deps.spawn ?? spawnSync;
11739
+ const run3 = deps.spawn ?? spawnSync2;
11540
11740
  const write = deps.write ?? ((text2) => process.stdout.write(text2));
11541
11741
  const env = deps.env ?? process.env;
11542
11742
  let result = { ok: true };
@@ -11580,9 +11780,9 @@ var init_lazygit = __esm({
11580
11780
  });
11581
11781
 
11582
11782
  // src/cli/tui/tuicr.ts
11583
- import { spawnSync as spawnSync2 } from "child_process";
11783
+ import { spawnSync as spawnSync3 } from "child_process";
11584
11784
  function runTuicrInteractive(opts, deps = {}) {
11585
- const run3 = deps.spawn ?? spawnSync2;
11785
+ const run3 = deps.spawn ?? spawnSync3;
11586
11786
  const write = deps.write ?? ((text2) => process.stdout.write(text2));
11587
11787
  const env = deps.env ?? process.env;
11588
11788
  let result = { ok: true };
@@ -12306,13 +12506,14 @@ function InProgressPopup({
12306
12506
  const now = Date.now();
12307
12507
  const ages = tasks.map((t) => ageMs(t, now));
12308
12508
  const { start, visible } = centredVisibleSlice(tasks, safeCursor, viewport);
12509
+ const agentLookup = agentByName(snapshot);
12309
12510
  const rows = visible.map((t, i) => {
12310
12511
  const absoluteIndex = start + i;
12311
12512
  return [
12312
12513
  glyphFor3(),
12313
12514
  t.name,
12314
12515
  t.status,
12315
- t.ownerName ?? "\u2014",
12516
+ formatAgentRefDisplayName(t.ownerName, agentLookup),
12316
12517
  formatSinceClaim(ages[absoluteIndex] ?? null),
12317
12518
  formatRoi(t.impact, t.effortDays),
12318
12519
  t.title
@@ -12372,6 +12573,7 @@ var init_inprogress2 = __esm({
12372
12573
  "src/cli/tui/popups/inprogress.tsx"() {
12373
12574
  "use strict";
12374
12575
  init_format();
12576
+ init_agent_display();
12375
12577
  init_inprogress();
12376
12578
  init_columns();
12377
12579
  init_format_helpers();
@@ -12717,7 +12919,13 @@ function ReadyPopup({
12717
12919
  ) }) });
12718
12920
  }
12719
12921
  const { start, visible } = centredVisibleSlice(tasks, safeCursor, viewport);
12720
- const rows = visible.map((t) => [t.name, t.status, t.ownerName ?? "\u2014", t.title]);
12922
+ const agentLookup = agentByName(snapshot);
12923
+ const rows = visible.map((t) => [
12924
+ t.name,
12925
+ t.status,
12926
+ formatAgentRefDisplayName(t.ownerName, agentLookup),
12927
+ t.title
12928
+ ]);
12721
12929
  const widths = layoutColumns(rows, COLUMN_SPECS18, contentWidth);
12722
12930
  return /* @__PURE__ */ jsxs19(
12723
12931
  PopupShell,
@@ -12775,6 +12983,7 @@ var init_ready2 = __esm({
12775
12983
  "src/cli/tui/popups/ready.tsx"() {
12776
12984
  "use strict";
12777
12985
  init_format();
12986
+ init_agent_display();
12778
12987
  init_columns();
12779
12988
  init_keys();
12780
12989
  init_list_row();
@@ -13587,9 +13796,10 @@ function WorkspacesPopup({
13587
13796
  ] });
13588
13797
  }
13589
13798
  const { start, visible } = centredVisibleSlice(workspaces, safeCursor, viewport);
13799
+ const agentLookup = agentByName(snapshot);
13590
13800
  const rows = visible.map((w) => [
13591
13801
  glyphFor5(w),
13592
- w.agentName,
13802
+ formatAgentRefDisplayName(w.agentName, agentLookup),
13593
13803
  w.backend,
13594
13804
  formatBehind2(w.commitsBehindMain),
13595
13805
  formatDirty(w.dirty),
@@ -13730,6 +13940,7 @@ var init_workspaces2 = __esm({
13730
13940
  "use strict";
13731
13941
  init_vcs2();
13732
13942
  init_workspace();
13943
+ init_agent_display();
13733
13944
  init_workspaces();
13734
13945
  init_columns();
13735
13946
  init_keys();
@@ -13784,7 +13995,8 @@ var init_workspaces2 = __esm({
13784
13995
 
13785
13996
  // src/cli/tui/state.ts
13786
13997
  import { useEffect as useEffect9, useRef as useRef7, useState as useState17 } from "react";
13787
- function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0, loaders = DEFAULT_LOADERS) {
13998
+ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0, loaders = DEFAULT_LOADERS, options = {}) {
13999
+ const publishNoopSlowTicks = options.publishNoopSlowTicks === true;
13788
14000
  const [data, setData] = useState17({
13789
14001
  data: null,
13790
14002
  error: null
@@ -13794,11 +14006,15 @@ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0,
13794
14006
  const [slowTickNonce, setSlowTickNonce] = useState17(0);
13795
14007
  const latestFastRef = useRef7(null);
13796
14008
  const slowRef = useRef7(null);
14009
+ const publishedKeyRef = useRef7("");
14010
+ const errorRef = useRef7(null);
13797
14011
  const lastWsRef = useRef7(workstream);
13798
14012
  if (shouldDiscardForWorkstream(lastWsRef.current, workstream)) {
13799
14013
  lastWsRef.current = workstream;
13800
14014
  slowRef.current = null;
13801
14015
  latestFastRef.current = null;
14016
+ publishedKeyRef.current = "";
14017
+ errorRef.current = null;
13802
14018
  setData({ data: null, error: null });
13803
14019
  setLastTickMs(0);
13804
14020
  }
@@ -13808,7 +14024,6 @@ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0,
13808
14024
  let cancelled = false;
13809
14025
  const tick = async () => {
13810
14026
  if (cancelled) return;
13811
- setFastTickNonce((n) => n + 1);
13812
14027
  const t0 = performance.now();
13813
14028
  try {
13814
14029
  const fast = await loaders.fast(db, workstream, FAST_OPTS);
@@ -13816,12 +14031,13 @@ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0,
13816
14031
  latestFastRef.current = fast;
13817
14032
  const fresh = mergeSnapshotFastSlow(fast, slowRef.current);
13818
14033
  const dur = performance.now() - t0;
13819
- setLastTickMs(dur);
13820
- publishSnapshot(fresh, setData);
14034
+ if (publishSnapshot(fresh, setData, publishedKeyRef, errorRef)) {
14035
+ setLastTickMs(dur);
14036
+ setFastTickNonce((n) => n + 1);
14037
+ }
13821
14038
  } catch (err) {
13822
14039
  if (cancelled) return;
13823
- const msg = String(err);
13824
- setData((prev) => prev.error === msg ? prev : { ...prev, error: msg });
14040
+ publishError(String(err), setData, errorRef);
13825
14041
  }
13826
14042
  };
13827
14043
  void tick();
@@ -13837,7 +14053,6 @@ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0,
13837
14053
  let cancelled = false;
13838
14054
  const slowTick = async () => {
13839
14055
  if (cancelled) return;
13840
- setSlowTickNonce((n) => n + 1);
13841
14056
  try {
13842
14057
  const slow = await loaders.slow(
13843
14058
  db,
@@ -13848,11 +14063,13 @@ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0,
13848
14063
  if (cancelled) return;
13849
14064
  slowRef.current = slow;
13850
14065
  const fast = latestFastRef.current;
13851
- if (fast !== null) publishSnapshot(mergeSnapshotFastSlow(fast, slow), setData);
14066
+ const changed = fast !== null && publishSnapshot(mergeSnapshotFastSlow(fast, slow), setData, publishedKeyRef, errorRef);
14067
+ if (changed || fast !== null && publishNoopSlowTicks) {
14068
+ setSlowTickNonce((n) => n + 1);
14069
+ }
13852
14070
  } catch (err) {
13853
14071
  if (cancelled) return;
13854
- const msg = String(err);
13855
- setData((prev) => prev.error === msg ? prev : { ...prev, error: msg });
14072
+ publishError(String(err), setData, errorRef);
13856
14073
  }
13857
14074
  };
13858
14075
  void slowTick();
@@ -13861,19 +14078,24 @@ function useDashboardSnapshot(db, workstream, tickMs, enabled, refreshNonce = 0,
13861
14078
  cancelled = true;
13862
14079
  clearInterval(id);
13863
14080
  };
13864
- }, [db, workstream, enabled, refreshNonce, loaders]);
14081
+ }, [db, workstream, enabled, refreshNonce, loaders, publishNoopSlowTicks]);
13865
14082
  return { data: data.data, fastTickNonce, slowTickNonce, lastTickMs, error: data.error };
13866
14083
  }
13867
14084
  function shouldDiscardForWorkstream(prev, next) {
13868
14085
  return prev !== next;
13869
14086
  }
13870
- function publishSnapshot(fresh, writeData) {
14087
+ function publishSnapshot(fresh, writeData, publishedKeyRef, errorRef) {
13871
14088
  const freshKey = snapshotKeyString(fresh);
13872
- writeData((prev) => {
13873
- const prevKey = prev.data === null ? "" : snapshotKeyString(prev.data);
13874
- if (prev.error === null && prevKey === freshKey) return prev;
13875
- return { data: fresh, error: null };
13876
- });
14089
+ if (errorRef.current === null && publishedKeyRef.current === freshKey) return false;
14090
+ publishedKeyRef.current = freshKey;
14091
+ errorRef.current = null;
14092
+ writeData(() => ({ data: fresh, error: null }));
14093
+ return true;
14094
+ }
14095
+ function publishError(message, writeData, errorRef) {
14096
+ if (errorRef.current === message) return;
14097
+ errorRef.current = message;
14098
+ writeData((prev) => ({ ...prev, error: message }));
13877
14099
  }
13878
14100
  function clampTick(ms) {
13879
14101
  return Math.max(TICK_FLOOR_MS, Math.min(TICK_CEILING_MS, ms));
@@ -13955,8 +14177,7 @@ var init_state2 = __esm({
13955
14177
  slow: loadWorkstreamSnapshotSlow
13956
14178
  };
13957
14179
  FAST_OPTS = {
13958
- eventLimit: 200,
13959
- withAllTasks: true
14180
+ eventLimit: 200
13960
14181
  };
13961
14182
  SLOW_OPTS = {
13962
14183
  withDirty: true,
@@ -14189,22 +14410,25 @@ import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
14189
14410
  function TabStrip({
14190
14411
  workstreams,
14191
14412
  active,
14192
- terminalColumns
14413
+ terminalColumns,
14414
+ parked
14193
14415
  }) {
14194
14416
  if (workstreams.length <= 1) return null;
14195
14417
  const layout = layoutTabStrip(workstreams, active, terminalColumns);
14196
14418
  if (layout === null) return null;
14419
+ const isParked = (name) => parked?.has(name) ?? false;
14420
+ const decorate = (name) => isParked(name) ? `~${name}` : name;
14197
14421
  const tabs = [];
14198
14422
  for (let i = 0; i < layout.visible.length; i++) {
14199
14423
  const tab = layout.visible[i];
14200
14424
  if (tab === void 0) continue;
14201
14425
  if (tab.isActive) {
14202
14426
  tabs.push(
14203
- /* @__PURE__ */ jsx35(Text28, { bold: true, color: "cyan", children: `\u25B8 ${tab.name}` }, `t-${i}`)
14427
+ /* @__PURE__ */ jsx35(Text28, { bold: true, color: "cyan", children: `\u25B8 ${decorate(tab.name)}` }, `t-${i}`)
14204
14428
  );
14205
14429
  } else {
14206
14430
  tabs.push(
14207
- /* @__PURE__ */ jsx35(Text28, { dimColor: true, children: tab.name }, `t-${i}`)
14431
+ /* @__PURE__ */ jsx35(Text28, { dimColor: true, children: decorate(tab.name) }, `t-${i}`)
14208
14432
  );
14209
14433
  }
14210
14434
  if (i < layout.visible.length - 1) {
@@ -14320,7 +14544,7 @@ var init_yank = __esm({
14320
14544
 
14321
14545
  // src/cli/tui/app.tsx
14322
14546
  import { Box as Box22, Text as Text29, useApp, useInput as useInput14, useStdin, useStdout as useStdout16 } from "ink";
14323
- import { useCallback as useCallback7, useEffect as useEffect10, useRef as useRef8, useState as useState18 } from "react";
14547
+ import { useCallback as useCallback7, useEffect as useEffect10, useMemo as useMemo9, useRef as useRef8, useState as useState18 } from "react";
14324
14548
  import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
14325
14549
  function dashboardAvailableRows(rows, opts) {
14326
14550
  const tabRows = opts.hasTabStrip ? 1 : 0;
@@ -14378,7 +14602,17 @@ function App({ db, workstreams, initialActive = 0 }) {
14378
14602
  }, []);
14379
14603
  const safeActive = Math.max(0, Math.min(activeWs, workstreams.length - 1));
14380
14604
  const workstream = workstreams[safeActive] ?? "";
14381
- const snap = useDashboardSnapshot(db, workstream, tickMs, true, refreshNonce);
14605
+ const snap = useDashboardSnapshot(db, workstream, tickMs, true, refreshNonce, void 0, {
14606
+ publishNoopSlowTicks: popup === 0 || popup === 1 || popup === 5
14607
+ });
14608
+ const parkedSet = useMemo9(() => {
14609
+ void snap.slowTickNonce;
14610
+ const set = /* @__PURE__ */ new Set();
14611
+ for (const ws of workstreams) {
14612
+ if (parkedStatus(db, ws).parked) set.add(ws);
14613
+ }
14614
+ return set;
14615
+ }, [db, workstreams, snap.slowTickNonce]);
14382
14616
  const { stdout } = useStdout16();
14383
14617
  const cols = stdout.columns ?? 80;
14384
14618
  const rows = stdout.rows ?? 24;
@@ -14558,7 +14792,15 @@ function App({ db, workstreams, initialActive = 0 }) {
14558
14792
  ] });
14559
14793
  }
14560
14794
  return /* @__PURE__ */ jsxs25(Box22, { flexDirection: "column", height: rows, overflow: "hidden", children: [
14561
- /* @__PURE__ */ jsx36(TabStrip, { workstreams, active: safeActive, terminalColumns: cols }),
14795
+ /* @__PURE__ */ jsx36(
14796
+ TabStrip,
14797
+ {
14798
+ workstreams,
14799
+ active: safeActive,
14800
+ terminalColumns: cols,
14801
+ parked: parkedSet
14802
+ }
14803
+ ),
14562
14804
  hasSnapshotError && /* @__PURE__ */ jsx36(Box22, { borderStyle: "round", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsxs25(Text29, { color: "red", children: [
14563
14805
  "snapshot error: ",
14564
14806
  snap.error
@@ -14615,7 +14857,8 @@ function buildDashboardLayoutModel(cols, rows, visibility, snapshot) {
14615
14857
  const cullBudget = firstCull.hidden.length > 0 ? Math.max(1, rows - 1) : rows;
14616
14858
  const culled = cullBudget === rows ? firstCull : cullCardsForRows(visible, cullBudget);
14617
14859
  const cardsRows = culled.hidden.length > 0 ? Math.max(1, rows - 1) : rows;
14618
- const assignments = layoutColumns2(cols, culled.cards);
14860
+ const packed = layoutColumns2(cols, culled.cards);
14861
+ const assignments = balanceColumns(packed, (id) => dataCountForCard(id, snapshot));
14619
14862
  const widths = columnWidths(cols, assignments.length);
14620
14863
  const budgetsByColumn = assignments.map(
14621
14864
  (assignment) => capRowBudgetsForColumn(
@@ -14693,6 +14936,7 @@ var CARD_REGISTRY, POPUP_REGISTRY, DASHBOARD_MIN_ROWS, POPUP_CHROME_TOP;
14693
14936
  var init_app = __esm({
14694
14937
  "src/cli/tui/app.tsx"() {
14695
14938
  "use strict";
14939
+ init_parked();
14696
14940
  init_agents2();
14697
14941
  init_blocked();
14698
14942
  init_commits();
@@ -16394,10 +16638,10 @@ async function cmdAttach(db, rawName, opts) {
16394
16638
  const { name } = await resolveEntityRef(db, rawName, opts, "agent");
16395
16639
  const workstream = await resolveWorkstream(opts.workstream);
16396
16640
  const sessionName = `mu-${workstream}`;
16641
+ const view = await listLiveAgents(db, { workstream });
16397
16642
  if (!await sessionExists(sessionName)) {
16398
16643
  throw new UsageError(`workstream "${workstream}" has no tmux session yet`);
16399
16644
  }
16400
- const view = await listLiveAgents(db, { workstream, mode: "status-only" });
16401
16645
  const agent = view.agents.find((a) => a.name === name);
16402
16646
  if (!agent) {
16403
16647
  throw new AgentNotFoundError(name);
@@ -17330,6 +17574,10 @@ function exportDb(db, file, opts = {}) {
17330
17574
  const manifestPath = `${target}.manifest.json`;
17331
17575
  const targetExists = existsSync12(target);
17332
17576
  if (targetExists && opts.force !== true) throw new DbExportTargetExistsError(target);
17577
+ const preEventManifest = buildExportManifest(db);
17578
+ for (const ws of preEventManifest.workstreams) {
17579
+ emitEvent(db, ws.name, `db export ${ws.name} seq=${ws.latestSeq}`);
17580
+ }
17333
17581
  const manifest = buildExportManifest(db);
17334
17582
  mkdirSync4(dirname5(target), { recursive: true });
17335
17583
  try {
@@ -18096,7 +18344,10 @@ state (workstream=${ws})`));
18096
18344
  console.log(` agent_logs rows : ${counts.logs}`);
18097
18345
  try {
18098
18346
  const view = await listLiveAgents(db, { workstream: ws, mode: "report-only" });
18099
- const ghostNote = view.report.prunedGhosts > 0 ? pc.yellow(`pruned ${view.report.prunedGhosts} during this check`) : pc.green("none");
18347
+ const ghosts = view.report.prunedGhosts;
18348
+ const ghostNote = ghosts > 0 ? pc.yellow(
18349
+ `${ghosts} ghost pane${ghosts === 1 ? "" : "s"} would be reaped by \`mu state\` or \`mu agent list\``
18350
+ ) : pc.green("none");
18100
18351
  console.log(` ghosts : ${ghostNote}`);
18101
18352
  const orphanColor = view.orphans.length > 0 ? pc.yellow : pc.green;
18102
18353
  console.log(
@@ -18702,9 +18953,9 @@ async function cmdUndo(db, opts = {}) {
18702
18953
  wouldBePrunedGhosts: totalGhostsWouldBePruned,
18703
18954
  orphansSurfaced: totalOrphans,
18704
18955
  // Reconcile mode: "report-only" preserves the snapshot's
18705
- // restored rows verbatim. (Was `dryRun: true` before the
18706
- // status-only/report-only split — BREAKING for SDK consumers
18707
- // reading this field; see CHANGELOG.)
18956
+ // restored rows verbatim. (Was `dryRun: true` before named
18957
+ // modes — BREAKING for SDK consumers reading this field; see
18958
+ // CHANGELOG.)
18708
18959
  mode: "report-only",
18709
18960
  perWorkstream: reconcilePerWorkstream
18710
18961
  },