@mu-crew/mu 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/AGENTS.md +208 -599
  2. package/README.md +125 -329
  3. package/dist/cli.js +108 -78
  4. package/dist/cli.js.map +1 -1
  5. package/dist/extension/mu-pi.js +421 -56
  6. package/dist/extension/mu-pi.js.map +1 -1
  7. package/dist/index.d.ts +36 -10
  8. package/dist/index.js +89 -67
  9. package/dist/index.js.map +1 -1
  10. package/docs/ARCHITECTURE.md +176 -1072
  11. package/docs/ROADMAP.md +88 -169
  12. package/docs/VISION.md +205 -415
  13. package/docs/VOCABULARY.md +204 -466
  14. package/docs/architecture/control-socket.md +133 -0
  15. package/docs/architecture/dag.md +113 -0
  16. package/docs/architecture/mux.md +162 -0
  17. package/docs/architecture/ops-log.md +140 -0
  18. package/docs/architecture/sdk.md +185 -0
  19. package/docs/architecture/sync.md +129 -0
  20. package/docs/architecture/tui.md +146 -0
  21. package/docs/guide/README.md +30 -0
  22. package/docs/guide/backends.md +61 -0
  23. package/docs/guide/cleanup.md +74 -0
  24. package/docs/guide/delegate.md +87 -0
  25. package/docs/guide/dispatch.md +144 -0
  26. package/docs/guide/getting-started.md +180 -0
  27. package/docs/guide/recovery.md +135 -0
  28. package/docs/guide/remote.md +74 -0
  29. package/docs/guide/sql.md +76 -0
  30. package/docs/guide/stop-a-worker.md +64 -0
  31. package/docs/guide/sync.md +78 -0
  32. package/docs/guide/tui.md +98 -0
  33. package/docs/guide/upgrade.md +76 -0
  34. package/docs/history/CHANGELOG-pre-3.md +4866 -0
  35. package/docs/reference/env.md +87 -0
  36. package/docs/reference/naming.md +82 -0
  37. package/docs/specs/2026-09-28-agent-state-from-murmur.md +1 -1
  38. package/docs/specs/2026-09-29-task-substates.md +1 -1
  39. package/docs/specs/2026-10-03-keep-driving-nudge.md +142 -0
  40. package/package.json +2 -1
  41. package/prompts/mu-refute.md +8 -0
  42. package/prompts/mu-research.md +8 -0
  43. package/prompts/mu-review.md +8 -0
  44. package/prompts/mu-rules-audit.md +8 -0
  45. package/prompts/mu-tournament.md +9 -0
  46. package/prompts/ultrathink.md +9 -0
  47. package/skills/mu/SKILL.md +126 -255
  48. package/skills/mu/recipes/adversarial-review.md +83 -0
  49. package/skills/mu/recipes/brief.md +53 -0
  50. package/skills/mu/recipes/codemode-driver.md +54 -0
  51. package/skills/mu/recipes/deep-research.md +52 -0
  52. package/skills/mu/recipes/fan-out.md +36 -0
  53. package/skills/mu/recipes/hypothesis-panel.md +40 -0
  54. package/skills/mu/recipes/long-run.md +20 -0
  55. package/skills/mu/recipes/loop-until-done.md +38 -0
  56. package/skills/mu/recipes/orchestrator-loop.md +87 -0
  57. package/skills/mu/recipes/plan.md +54 -0
  58. package/skills/mu/recipes/recovery.md +26 -0
  59. package/skills/mu/recipes/refute.md +49 -0
  60. package/skills/mu/recipes/remote-workers.md +292 -0
  61. package/skills/mu/recipes/review-panel.md +57 -0
  62. package/skills/mu/recipes/rules-audit.md +70 -0
  63. package/skills/mu/recipes/tournament.md +37 -0
  64. package/skills/mu/recipes/triage.md +38 -0
  65. package/skills/mu/recipes/ultrathink.md +73 -0
  66. package/skills/mu/recipes/watcher.md +16 -0
  67. package/skills/mu/recipes/waves.md +19 -0
  68. package/skills/mu/recipes/worker.md +24 -0
  69. package/docs/HANDOVER.md +0 -486
  70. package/docs/USAGE_GUIDE.md +0 -2738
  71. package/skills/mu/REMOTE_WORKERS.md +0 -617
package/dist/cli.js CHANGED
@@ -14,18 +14,20 @@ function encode(msg) {
14
14
  return `${JSON.stringify(msg)}
15
15
  `;
16
16
  }
17
- var CTL_PROTOCOL_VERSION, CTL_OPS, UNKNOWN_OP_PREFIX, LineDecoder;
17
+ var CTL_PROTOCOL_VERSION, CTL_COMMANDS, CTL_OPS, UNKNOWN_OP_PREFIX, LineDecoder;
18
18
  var init_protocol = __esm({
19
19
  "src/ctl/protocol.ts"() {
20
20
  "use strict";
21
21
  CTL_PROTOCOL_VERSION = 1;
22
+ CTL_COMMANDS = ["new", "reload", "compact"];
22
23
  CTL_OPS = [
23
24
  "hello",
24
25
  "status",
25
26
  "send",
26
27
  "wait",
27
28
  "abort",
28
- "fresh"
29
+ "fresh",
30
+ "command"
29
31
  ];
30
32
  UNKNOWN_OP_PREFIX = "unknown op: ";
31
33
  LineDecoder = class {
@@ -44,7 +46,7 @@ var init_protocol = __esm({
44
46
  import { createConnection } from "net";
45
47
  function clientTimeout(req, override) {
46
48
  if (override !== void 0) return override;
47
- if (req.op === "fresh") return CTL_FRESH_TIMEOUT_MS;
49
+ if (req.op === "fresh" || req.op === "command") return CTL_FRESH_TIMEOUT_MS;
48
50
  if (req.op !== "wait") return CTL_DEFAULT_TIMEOUT_MS;
49
51
  return req.timeoutMs === void 0 ? void 0 : req.timeoutMs + CTL_WAIT_SLACK_MS;
50
52
  }
@@ -1289,27 +1291,6 @@ async function awaitPaneQuiescence(paneId, budgetMs) {
1289
1291
  await currentSleep(Math.min(SEND_READINESS_POLL_MS, remaining));
1290
1292
  }
1291
1293
  }
1292
- async function awaitNewSession(paneId, baseline, budgetMs) {
1293
- if (budgetMs <= 0) return true;
1294
- const deadline = Date.now() + budgetMs;
1295
- let previous = baseline;
1296
- let stable = 0;
1297
- for (; ; ) {
1298
- const scrollback = await capturePane(paneId, { lines: 50 }).catch(() => void 0);
1299
- if (scrollback !== void 0 && scrollback !== baseline) {
1300
- if (scrollback === previous) {
1301
- stable++;
1302
- if (stable >= QUIESCENCE_CONFIRMATIONS) return true;
1303
- } else {
1304
- stable = 0;
1305
- }
1306
- previous = scrollback;
1307
- }
1308
- const remaining = deadline - Date.now();
1309
- if (remaining <= 0) return false;
1310
- await currentSleep(Math.min(SEND_READINESS_POLL_MS, remaining));
1311
- }
1312
- }
1313
1294
  function pasteProbe(text2) {
1314
1295
  const firstLine = (text2.split("\n")[0] ?? "").trim();
1315
1296
  return firstLine.slice(0, 24);
@@ -1337,7 +1318,6 @@ async function sendToPane(paneId, text2, opts = {}) {
1337
1318
  assertValidPaneId(paneId);
1338
1319
  const readinessMs = opts.readinessMs ?? defaultSendReadinessMs();
1339
1320
  const quiesced = await awaitPaneQuiescence(paneId, readinessMs);
1340
- const newSessionBaseline = readinessMs > 0 && text2.trim() === "/new" ? await capturePane(paneId, { lines: 50 }).catch(() => void 0) : void 0;
1341
1321
  const copyResult = await currentExecutor(["copy-mode", "-q", "-t", paneId]);
1342
1322
  if (copyResult.exitCode !== 0 && /can't find pane|no current target/i.test(copyResult.stderr)) {
1343
1323
  throw new TmuxError(
@@ -1360,15 +1340,6 @@ async function sendToPane(paneId, text2, opts = {}) {
1360
1340
  if (delay2 > 0) await currentSleep(delay2);
1361
1341
  await tmux(["send-keys", "-t", paneId, "Enter"]);
1362
1342
  if (readinessMs <= 0) return;
1363
- if (newSessionBaseline !== void 0 && !await awaitNewSession(paneId, newSessionBaseline, readinessMs)) {
1364
- const warn3 = opts.onUndelivered ?? defaultUndeliveredWarning;
1365
- warn3({
1366
- paneId,
1367
- reason: "transition-unconfirmed",
1368
- message: `send to ${paneId} submitted /new, but the new session did not become ready within ${readinessMs}ms. Wait for the pane to settle before sending more work.`
1369
- });
1370
- return;
1371
- }
1372
1343
  const probe = pasteProbe(text2);
1373
1344
  if (probe.length === 0) return;
1374
1345
  for (let attempt = 0; attempt <= SUBMIT_RETRIES; attempt++) {
@@ -4401,7 +4372,7 @@ async function spawnAgent(db, opts) {
4401
4372
  agent = created.row;
4402
4373
  const startAgent = (await activeMux()).startAgentInPane;
4403
4374
  if (startAgent === void 0) {
4404
- await awaitSpawnLiveness(paneId, opts.name, opts.workstream, command);
4375
+ await awaitSpawnLiveness(paneId, opts.name, command);
4405
4376
  } else {
4406
4377
  await startAgent({ paneId, name: opts.name, cli, command, commandSource });
4407
4378
  }
@@ -4510,7 +4481,7 @@ function detectSpawnStartupError(scrollback) {
4510
4481
  }
4511
4482
  return void 0;
4512
4483
  }
4513
- async function awaitSpawnLiveness(paneId, agentName, workstreamName, command) {
4484
+ async function awaitSpawnLiveness(paneId, agentName, command) {
4514
4485
  const ms = defaultSpawnLivenessMs();
4515
4486
  if (ms === 0) return;
4516
4487
  await sleep(ms);
@@ -4525,30 +4496,6 @@ async function awaitSpawnLiveness(paneId, agentName, workstreamName, command) {
4525
4496
  throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);
4526
4497
  }
4527
4498
  }
4528
- await awaitSpawnReadiness(paneId, agentName, workstreamName, command);
4529
- }
4530
- function defaultSpawnReadinessMs() {
4531
- const raw = process.env.MU_SPAWN_READINESS_MS;
4532
- if (raw === void 0) return 1e4;
4533
- const parsed = Number.parseInt(raw, 10);
4534
- if (Number.isNaN(parsed) || parsed < 0) return 1e4;
4535
- return parsed;
4536
- }
4537
- async function awaitSpawnReadiness(paneId, agentName, workstreamName, command) {
4538
- const budgetMs = defaultSpawnReadinessMs();
4539
- if (budgetMs === 0 || !murmurAvailable()) return;
4540
- const mux = await activeMux();
4541
- const deadline = Date.now() + budgetMs;
4542
- const agent = { name: agentName, workstreamName, paneId };
4543
- while (Date.now() < deadline) {
4544
- if (!await mux.paneExists(paneId)) {
4545
- const scrollback = await mux.capturePane(paneId, { lines: 50 }).catch(() => void 0);
4546
- throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command);
4547
- }
4548
- const reading = (await readAgentStates([agent])).get(agentKey(agent));
4549
- if (reading?.source === "murmur") return;
4550
- await sleep(Math.min(READINESS_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
4551
- }
4552
4499
  }
4553
4500
  async function createOrReusePane(opts) {
4554
4501
  const mux = await activeMux();
@@ -4594,11 +4541,10 @@ async function firstPaneInWindow(mux, session, windowId) {
4594
4541
  }
4595
4542
  return pane.paneId;
4596
4543
  }
4597
- var AGENT_NAME_CONVENTION_RE, execFileP, activeCommandResolver, CTL_POLL_INTERVAL_MS, STARTUP_ERROR_PATTERNS, STARTUP_ERROR_TAIL_LINES, READINESS_POLL_INTERVAL_MS;
4544
+ var AGENT_NAME_CONVENTION_RE, execFileP, activeCommandResolver, CTL_POLL_INTERVAL_MS, STARTUP_ERROR_PATTERNS, STARTUP_ERROR_TAIL_LINES;
4598
4545
  var init_spawn = __esm({
4599
4546
  "src/agents/spawn.ts"() {
4600
4547
  "use strict";
4601
- init_agent_state();
4602
4548
  init_agents();
4603
4549
  init_client();
4604
4550
  init_path();
@@ -4644,12 +4590,11 @@ var init_spawn = __esm({
4644
4590
  /^(?:.*: )?(?:command not found|No such file or directory)(?::\s*\S+)?$/i
4645
4591
  ];
4646
4592
  STARTUP_ERROR_TAIL_LINES = 30;
4647
- READINESS_POLL_INTERVAL_MS = 250;
4648
4593
  }
4649
4594
  });
4650
4595
 
4651
4596
  // src/agents/errors.ts
4652
- var AgentSpawnCliNotFoundError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, AgentDiedOnSpawnError, AgentSpawnStartupError, WorkspacePreservedError, AgentCtlUnreachableError, AgentAbortNeedsCtlError, AgentAbortTimeoutError, AgentFreshNeedsCtlError, AgentExtensionOutdatedError, AgentBusyError;
4597
+ var AgentSpawnCliNotFoundError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, AgentDiedOnSpawnError, AgentSpawnStartupError, WorkspacePreservedError, AgentCtlUnreachableError, AgentAbortNeedsCtlError, AgentAbortTimeoutError, AgentFreshNeedsCtlError, AgentExtensionOutdatedError, AgentBusyError, AgentSlashCommandUnsupportedError;
4653
4598
  var init_errors = __esm({
4654
4599
  "src/agents/errors.ts"() {
4655
4600
  "use strict";
@@ -5029,23 +4974,47 @@ ${scrollback.trim()}
5029
4974
  }
5030
4975
  };
5031
4976
  AgentBusyError = class extends Error {
5032
- constructor(agentName, workstream) {
5033
- super(`agent ${agentName} is busy: --fresh would abandon its running turn`);
4977
+ constructor(agentName, workstream, command) {
4978
+ super(`agent ${agentName} is busy: ${command ?? "--fresh"} would abandon its running turn`);
5034
4979
  this.agentName = agentName;
5035
4980
  this.workstream = workstream;
4981
+ this.command = command;
5036
4982
  }
5037
4983
  agentName;
5038
4984
  workstream;
4985
+ command;
5039
4986
  name = "AgentBusyError";
5040
4987
  errorNextSteps() {
4988
+ const retry = this.command === void 0 ? `mu agent send ${this.agentName} --fresh --force '...' -w ${this.workstream}` : `mu agent send ${this.agentName} '${this.command}' --force -w ${this.workstream}`;
5041
4989
  return [
5042
4990
  {
5043
4991
  intent: "Stop the turn first, then retry",
5044
4992
  command: `mu agent abort ${this.agentName} -w ${this.workstream}`
5045
4993
  },
4994
+ { intent: "Or abandon the turn in one step", command: retry }
4995
+ ];
4996
+ }
4997
+ };
4998
+ AgentSlashCommandUnsupportedError = class extends Error {
4999
+ constructor(agentName, workstream, text2, supported) {
5000
+ super(
5001
+ `agent ${agentName} runs pi: over the control socket mu runs only ${supported.join(", ")}; ${JSON.stringify(text2.split("\n")[0])} is not one of them. Pass --via mux to type it into the pane`
5002
+ );
5003
+ this.agentName = agentName;
5004
+ this.workstream = workstream;
5005
+ this.text = text2;
5006
+ this.supported = supported;
5007
+ }
5008
+ agentName;
5009
+ workstream;
5010
+ text;
5011
+ supported;
5012
+ name = "AgentSlashCommandUnsupportedError";
5013
+ errorNextSteps() {
5014
+ return [
5046
5015
  {
5047
- intent: "Or abandon the turn in one step",
5048
- command: `mu agent send ${this.agentName} --fresh --force '...' -w ${this.workstream}`
5016
+ intent: "Type it into the pane on purpose (tmux paste; unconfirmed)",
5017
+ command: `mu agent send ${this.agentName} '${this.text.split("\n")[0]}' --via mux -w ${this.workstream}`
5049
5018
  }
5050
5019
  ];
5051
5020
  }
@@ -5062,10 +5031,21 @@ function agentCtlSocket(db, agent) {
5062
5031
  function expectsCtl(agent, sock = ctlSocketPath(agent.workstreamName, agent.name)) {
5063
5032
  return speaksMuCtl(agent.cli, resolveCliCommand(agent.cli)) || existsSync7(sock);
5064
5033
  }
5065
- function chooseTransport(agent, text2, sock) {
5066
- if (text2.startsWith("/")) return "mux";
5034
+ function chooseTransport(agent, sock) {
5067
5035
  return expectsCtl(agent, sock) ? "ctl" : "mux";
5068
5036
  }
5037
+ function parseSessionCommand(text2) {
5038
+ const t = text2.trim();
5039
+ if (t === "/new") return { name: "new" };
5040
+ if (t === "/reload") return { name: "reload" };
5041
+ const m = /^\/compact(?:\s+([\s\S]*))?$/.exec(t);
5042
+ if (!m) return void 0;
5043
+ const instructions = m[1]?.trim();
5044
+ return instructions ? { name: "compact", instructions } : { name: "compact" };
5045
+ }
5046
+ function isSlashCommand(text2) {
5047
+ return /^\/[A-Za-z][\w:-]*(?:\s|$)/.test(text2.trimStart());
5048
+ }
5069
5049
  async function extensionOutdated(agent, sock, e) {
5070
5050
  let extVersion;
5071
5051
  try {
@@ -5094,11 +5074,23 @@ async function sendViaTransport(agent, text2, opts) {
5094
5074
  if (opts.fresh && (opts.via === "mux" || !expectsCtl(agent, sock))) {
5095
5075
  throw new AgentFreshNeedsCtlError(agent.name, agent.workstreamName, agent.cli);
5096
5076
  }
5097
- const transport = opts.fresh ? "ctl" : opts.via ?? chooseTransport(agent, text2, sock);
5077
+ const transport = opts.fresh ? "ctl" : opts.via ?? chooseTransport(agent, sock);
5098
5078
  if (transport === "mux") {
5099
5079
  await (await activeMux()).sendToPane(agent.paneId, text2, opts);
5100
5080
  return { transport };
5101
5081
  }
5082
+ if (!opts.fresh) {
5083
+ const cmd = parseSessionCommand(text2);
5084
+ if (cmd) return runSessionCommand(agent, sock, cmd, opts.force === true);
5085
+ if (isSlashCommand(text2)) {
5086
+ throw new AgentSlashCommandUnsupportedError(
5087
+ agent.name,
5088
+ agent.workstreamName,
5089
+ text2,
5090
+ CTL_SLASH_COMMANDS
5091
+ );
5092
+ }
5093
+ }
5102
5094
  const reply = await ctlRequestFor(
5103
5095
  agent,
5104
5096
  sock,
@@ -5112,14 +5104,36 @@ async function sendViaTransport(agent, text2, opts) {
5112
5104
  }
5113
5105
  return reply.state === void 0 ? { transport } : { transport, state: reply.state };
5114
5106
  }
5107
+ async function runSessionCommand(agent, sock, cmd, force) {
5108
+ const reply = await ctlRequestFor(agent, sock, {
5109
+ op: "command",
5110
+ name: cmd.name,
5111
+ ...cmd.instructions !== void 0 ? { instructions: cmd.instructions } : {},
5112
+ ...force ? { force: true } : {}
5113
+ });
5114
+ if (!reply.ok) {
5115
+ if (reply.error === "busy") {
5116
+ throw new AgentBusyError(agent.name, agent.workstreamName, `/${cmd.name}`);
5117
+ }
5118
+ throw new Error(`pi refused /${cmd.name}: ${reply.error}`);
5119
+ }
5120
+ return {
5121
+ transport: "ctl",
5122
+ command: cmd.name,
5123
+ ...reply.state !== void 0 ? { state: reply.state } : {}
5124
+ };
5125
+ }
5126
+ var CTL_SLASH_COMMANDS;
5115
5127
  var init_transport = __esm({
5116
5128
  "src/agents/transport.ts"() {
5117
5129
  "use strict";
5118
5130
  init_client();
5119
5131
  init_path();
5132
+ init_protocol();
5120
5133
  init_mux2();
5121
5134
  init_errors();
5122
5135
  init_spawn();
5136
+ CTL_SLASH_COMMANDS = CTL_COMMANDS.map((n) => `/${n}`);
5123
5137
  }
5124
5138
  });
5125
5139
 
@@ -9597,6 +9611,15 @@ function murmurDoctorCheck(agentStateSource, opts = {}) {
9597
9611
  detail: version === null ? "agent state from murmur" : `agent state from murmur ${version}`
9598
9612
  };
9599
9613
  }
9614
+ function extFeatures(env) {
9615
+ const features = [
9616
+ ["mu_delegate", "MU_DELEGATE"],
9617
+ ["nudge", "MU_NUDGE"]
9618
+ ];
9619
+ const on = ["ctl", ...features.filter(([, v]) => env[v] !== "0").map(([f]) => f)];
9620
+ const off = features.filter(([, v]) => env[v] === "0").map(([f, v]) => `${f} disabled by ${v}=0`);
9621
+ return `(${on.join(", ")}${off.length ? `; ${off.join("; ")}` : ""})`;
9622
+ }
9600
9623
  function ctlExtensionDoctorCheck(opts) {
9601
9624
  const { extension } = inspectLinks(opts);
9602
9625
  switch (extension.state) {
@@ -9604,7 +9627,7 @@ function ctlExtensionDoctorCheck(opts) {
9604
9627
  return {
9605
9628
  name: "mu ext",
9606
9629
  status: "ok",
9607
- detail: process.env.MU_DELEGATE === "0" ? "(ctl; mu_delegate disabled by MU_DELEGATE=0)" : "(ctl, mu_delegate)"
9630
+ detail: extFeatures(process.env)
9608
9631
  };
9609
9632
  case "stale-copy":
9610
9633
  return { name: "mu ext", status: "warn", detail: `stale copy: ${RELINK}` };
@@ -20117,7 +20140,9 @@ function parseVia(raw) {
20117
20140
  }
20118
20141
  async function cmdSend(db, rawName, text2, opts = {}) {
20119
20142
  const via = parseVia(opts.via);
20120
- if (opts.force && !opts.fresh) throw new UsageError("--force only applies with --fresh");
20143
+ if (opts.force && !opts.fresh && parseSessionCommand(text2) === void 0) {
20144
+ throw new UsageError("--force only applies with --fresh or a /new, /reload, /compact");
20145
+ }
20121
20146
  if (opts.fresh && opts.steer) throw new UsageError("--fresh and --steer are mutually exclusive");
20122
20147
  const { name } = await resolveEntityRef(db, rawName, opts, "agent");
20123
20148
  assertAgentInWorkstream(db, name, opts.workstream);
@@ -20127,7 +20152,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
20127
20152
  });
20128
20153
  let undelivered;
20129
20154
  const agentRow = getAgent(db, name, ws);
20130
- const plainCtl = agentRow !== void 0 && !opts.fresh && !opts.steer && (via ?? chooseTransport(agentRow, text2, agentCtlSocket(db, agentRow))) === "ctl";
20155
+ const plainCtl = agentRow !== void 0 && !opts.fresh && !opts.steer && parseSessionCommand(text2) === void 0 && (via ?? chooseTransport(agentRow, agentCtlSocket(db, agentRow))) === "ctl";
20131
20156
  const before = plainCtl && agentRow !== void 0 ? await ctlProbe(agentCtlSocket(db, agentRow)) : void 0;
20132
20157
  const sent = await sendToAgent(db, name, text2, {
20133
20158
  workstream: ws,
@@ -20161,6 +20186,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
20161
20186
  sentBytes: text2.length,
20162
20187
  transport: sent.transport,
20163
20188
  fresh: opts.fresh === true,
20189
+ ...sent.command !== void 0 ? { command: sent.command } : {},
20164
20190
  ...sent.state !== void 0 ? { state: sent.state } : {},
20165
20191
  delivered: undelivered === void 0,
20166
20192
  ...undelivered !== void 0 ? { undelivered: { reason: undelivered.reason, message: undelivered.message } } : {},
@@ -20172,7 +20198,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
20172
20198
  if (undelivered !== void 0) {
20173
20199
  console.error(pc.yellow(`warning: ${undelivered.message}`));
20174
20200
  } else {
20175
- const how = sent.transport === "mux" ? "via mux paste" : opts.fresh ? "via ctl, fresh session" : `via ctl, pi ${sent.state ?? "?"}`;
20201
+ const how = sent.transport === "mux" ? "via mux paste" : opts.fresh ? "via ctl, fresh session" : sent.command !== void 0 ? `via ctl, pi ran /${sent.command}` : `via ctl, pi ${sent.state ?? "?"}`;
20176
20202
  console.log(pc.dim(`sent ${text2.length} bytes to ${name} (${how})`));
20177
20203
  }
20178
20204
  printNextSteps(nextSteps3);
@@ -20613,14 +20639,17 @@ function wireAgentCommands(program) {
20613
20639
  return handle((db) => cmdSpawn(db, name, opts), this)();
20614
20640
  });
20615
20641
  agent.command("send <name> <text>").description(
20616
- "Send text to an agent. pi agents: through the control socket (fails loud if it does not answer; no paste fallback). Non-pi CLIs and text starting with '/' (slash commands such as /new): pasted into the pane (atomic on herdr; bracketed-paste on tmux). --fresh (pi only): start a new session and send the text into it as one operation; returns once the prompt's run has started, so an immediate next send cannot be lost. Use it instead of sending '/new' then the prompt"
20642
+ "Send text to an agent. pi agents: through the control socket (fails loud if it does not answer; no paste fallback); '/new', '/reload' and '/compact [instructions]' run inside pi over the socket (refused while busy unless --force), any other slash command is refused (--via mux types it into the pane). Non-pi CLIs: pasted into the pane (atomic on herdr; bracketed-paste on tmux). --fresh (pi only): start a new session and send the text into it as one operation; returns once the prompt's run has started, so an immediate next send cannot be lost. Use it instead of sending '/new' then the prompt"
20617
20643
  ).option(
20618
20644
  "--strict-staleness",
20619
20645
  "refuse send when the target agent's workspace is stale (default: warn and proceed)"
20620
20646
  ).option("--steer", "if pi is busy, interrupt the current run (default: queue as a follow-up)").option("--via <transport>", "force the transport: ctl or mux (mux = paste into the pane)").option(
20621
20647
  "--fresh",
20622
20648
  "pi only: new session + this prompt, one operation (refused with exit 4 while pi is busy)"
20623
- ).option("--force", "with --fresh: abandon a running turn instead of refusing").option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(name, text2) {
20649
+ ).option(
20650
+ "--force",
20651
+ "with --fresh or a pi /new, /reload, /compact: abandon a running turn instead of refusing"
20652
+ ).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(name, text2) {
20624
20653
  const opts = this.opts();
20625
20654
  return handle((db) => cmdSend(db, name, text2, opts), this)();
20626
20655
  });
@@ -21348,7 +21377,8 @@ var NameAmbiguousError = class extends Error {
21348
21377
  function classifyError(err) {
21349
21378
  if (err instanceof UsageError || // abort on a non-pi agent: the operator picked the wrong verb (kick).
21350
21379
  err instanceof AgentAbortNeedsCtlError || // send --fresh on an agent with no control socket: wrong recipe.
21351
- err instanceof AgentFreshNeedsCtlError || err instanceof WorkstreamNameInvalidError || err instanceof WorkstreamNameReservedError || // The operator asked the ACTIVE backend for something it cannot do:
21380
+ err instanceof AgentFreshNeedsCtlError || // an unsupported slash command to a pi agent: --via mux is the recipe.
21381
+ err instanceof AgentSlashCommandUnsupportedError || err instanceof WorkstreamNameInvalidError || err instanceof WorkstreamNameReservedError || // The operator asked the ACTIVE backend for something it cannot do:
21352
21382
  // a `--cli` the mux does not recognise as an agent kind, or a
21353
21383
  // `--command` / `MU_<CLI>_COMMAND` override it cannot honour. The
21354
21384
  // substrate is healthy and answered precisely, so this is the usage