@mu-crew/mu 3.9.0 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/chunk-4PACTXAB.js +17 -0
  2. package/dist/chunk-4PACTXAB.js.map +1 -0
  3. package/dist/chunk-77GBZ2KR.js +85 -0
  4. package/dist/chunk-77GBZ2KR.js.map +1 -0
  5. package/dist/chunk-7OQNJWBY.js +391 -0
  6. package/dist/chunk-7OQNJWBY.js.map +1 -0
  7. package/dist/chunk-PZ5AY32C.js +10 -0
  8. package/dist/chunk-PZ5AY32C.js.map +1 -0
  9. package/dist/chunk-WT7IN2OD.js +9887 -0
  10. package/dist/chunk-WT7IN2OD.js.map +1 -0
  11. package/dist/chunk-XXK7FR37.js +640 -0
  12. package/dist/chunk-XXK7FR37.js.map +1 -0
  13. package/dist/chunk-YIF3X32A.js +2792 -0
  14. package/dist/chunk-YIF3X32A.js.map +1 -0
  15. package/dist/cli-KJGVEZW5.js +6266 -0
  16. package/dist/cli-KJGVEZW5.js.map +1 -0
  17. package/dist/cli.js +37 -26127
  18. package/dist/cli.js.map +1 -1
  19. package/dist/extension/mu-pi.js +148 -104
  20. package/dist/extension/mu-pi.js.map +1 -1
  21. package/dist/index.d.ts +108 -53
  22. package/dist/index.js +324 -11728
  23. package/dist/index.js.map +1 -1
  24. package/dist/tui-X2XICJTN.js +6452 -0
  25. package/dist/tui-X2XICJTN.js.map +1 -0
  26. package/dist/tui-sync-worker.js +16 -0
  27. package/dist/tui-sync-worker.js.map +1 -0
  28. package/dist/yank-T3AYQTVR.js +12 -0
  29. package/dist/yank-T3AYQTVR.js.map +1 -0
  30. package/docs/ARCHITECTURE.md +9 -3
  31. package/docs/ROADMAP.md +7 -0
  32. package/docs/architecture/control-socket.md +5 -1
  33. package/docs/architecture/sync.md +18 -4
  34. package/docs/guide/delegate.md +2 -1
  35. package/docs/guide/dispatch.md +4 -0
  36. package/docs/guide/sql.md +1 -1
  37. package/docs/reference/env.md +2 -1
  38. package/package.json +1 -1
  39. package/skills/mu/SKILL.md +1 -1
  40. package/skills/mu/recipes/codemode-driver.md +3 -0
  41. package/skills/mu/recipes/review-panel.md +2 -1
@@ -175,6 +175,12 @@ function labelStem(label) {
175
175
  const stem = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 20).replace(/-+$/, "");
176
176
  return stem || void 0;
177
177
  }
178
+ var MODEL_RE = /^[A-Za-z0-9._:/@+-]+$/;
179
+ function modelCommand(cli, model, env = process.env) {
180
+ const override = env[`MU_${cli.toUpperCase().replace(/-/g, "_")}_COMMAND`];
181
+ const base = override && override.trim() !== "" ? override.trim() : cli;
182
+ return `${base} --model ${model}`;
183
+ }
178
184
  function registerDelegate(pi, run = defaultRunner()) {
179
185
  if (!delegateEnabled()) return;
180
186
  const inflight = /* @__PURE__ */ new Map();
@@ -182,7 +188,6 @@ function registerDelegate(pi, run = defaultRunner()) {
182
188
  const queue = [];
183
189
  let queueSeq = 0;
184
190
  const failed = /* @__PURE__ */ new Set();
185
- const reserved = /* @__PURE__ */ new Set();
186
191
  let ui;
187
192
  let lastPending = 0;
188
193
  const reportPending = () => {
@@ -209,7 +214,6 @@ function registerDelegate(pi, run = defaultRunner()) {
209
214
  };
210
215
  const forget = (name) => {
211
216
  inflight.delete(name);
212
- reserved.delete(name);
213
217
  drain();
214
218
  showStatus();
215
219
  };
@@ -248,63 +252,60 @@ function registerDelegate(pi, run = defaultRunner()) {
248
252
  }
249
253
  };
250
254
  const failure = (what, r) => new Error(`${what} failed (exit ${r.code}): ${(r.stderr || r.stdout).trim()}`);
251
- async function pickName(stem) {
252
- const r = await mu(["agent", "list", "-w", W, "--json"]);
253
- const agents = json(r)?.agents;
254
- const taken = new Set(
255
- Array.isArray(agents) ? agents.map((a) => a.name).filter((n) => n) : []
256
- );
257
- if (Array.isArray(agents)) {
255
+ function pruneFailed() {
256
+ if (failed.size === 0) return;
257
+ void mu(["agent", "list", "-w", W, "--json"]).then((r) => {
258
+ const agents = json(r)?.agents;
259
+ if (!Array.isArray(agents)) return;
260
+ const taken = new Set(agents.map((a) => a.name));
258
261
  for (const f of [...failed]) if (!taken.has(f)) failed.delete(f);
259
262
  showStatus();
260
- }
261
- for (let n = 1; ; n++) {
262
- const name = stem ? n === 1 ? `delegate-${stem}` : `delegate-${stem}-${n}` : `delegate-${n}`;
263
- if (!taken.has(name) && !reserved.has(name)) {
264
- reserved.add(name);
265
- return name;
266
- }
267
- }
263
+ });
268
264
  }
269
- async function spawn(params, cwd, signal) {
265
+ async function spawn(params, text, cwd, signal) {
266
+ signal?.throwIfAborted();
267
+ pruneFailed();
270
268
  const stem = labelStem(params.label);
271
- for (let attempt = 0; ; attempt++) {
272
- signal?.throwIfAborted();
273
- const name = await pickName(stem);
274
- const args = ["agent", "spawn", name, "-w", W, "--json"];
275
- if (params.workspace === true) args.push("--workspace");
276
- else if (cwd) args.push("--cwd", cwd);
277
- if (typeof params.cli === "string" && params.cli) args.push("--cli", params.cli);
278
- const r = await mu(args);
279
- if (signal?.aborted && r.code === 0) {
280
- reserved.delete(name);
281
- await mu(["agent", "close", name, "-w", W]);
282
- signal.throwIfAborted();
283
- }
284
- if (r.code !== 0 && /already exists/.test(r.stderr) && attempt < 3) {
285
- reserved.delete(name);
286
- continue;
287
- }
288
- if (r.code !== 0) {
289
- reserved.delete(name);
290
- throw failure("mu agent spawn", r);
291
- }
292
- const out = json(r) ?? {};
293
- if (out.ctl !== "ok") {
294
- reserved.delete(name);
295
- throw new Error(
296
- `${W}/${name} spawned but its control socket is ${String(out.ctl)}, so its answer cannot come back. Pane kept; close it with: mu agent close ${name} -w ${W}`
297
- );
298
- }
299
- const steps = Array.isArray(out.nextSteps) ? out.nextSteps : [];
300
- const attach = steps.map((s) => s).find((s) => s.intent === "Attach the pane")?.command;
301
- const wsPath = out.workspace?.path;
302
- return {
303
- name,
304
- ...typeof attach === "string" ? { attach } : {},
305
- ...typeof wsPath === "string" ? { workspace: wsPath } : {}
306
- };
269
+ const base = stem ? `delegate-${stem}` : "delegate-1";
270
+ const args = ["agent", "spawn", base, "-w", W, "--next-free", "--send", text, "--json"];
271
+ if (params.workspace === true) args.push("--workspace");
272
+ else if (cwd) args.push("--cwd", cwd);
273
+ const cli = typeof params.cli === "string" && params.cli ? params.cli : void 0;
274
+ if (cli) args.push("--cli", cli);
275
+ if (typeof params.model === "string" && params.model)
276
+ args.push("--command", modelCommand(cli ?? "pi", params.model));
277
+ const r = await mu(args);
278
+ const out = json(r);
279
+ const spawned = out?.agent?.name;
280
+ if (typeof spawned !== "string") throw failure("mu agent spawn", r);
281
+ const name = spawned;
282
+ if (signal?.aborted) {
283
+ await mu(["agent", "close", name, "-w", W]);
284
+ signal.throwIfAborted();
285
+ }
286
+ if (out?.ctl !== "ok") {
287
+ throw new Error(
288
+ `${W}/${name} spawned but its control socket is ${String(out?.ctl)}, so its answer cannot come back. Pane kept; close it with: mu agent close ${name} -w ${W}`
289
+ );
307
290
  }
291
+ const sent = out.send;
292
+ if (sent?.ok !== true) {
293
+ const c = await mu(["agent", "close", name, "-w", W]);
294
+ const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
295
+ const why = typeof sent?.message === "string" ? sent.message : (r.stderr || r.stdout).trim();
296
+ throw new Error(`mu agent send to ${W}/${name} failed (exit ${r.code}): ${why}. ${pane}`);
297
+ }
298
+ const steps = Array.isArray(out.nextSteps) ? out.nextSteps : [];
299
+ const attach = steps.map((s) => s).find((s) => s.intent === "Attach the pane")?.command;
300
+ const wsPath = out.workspace?.path;
301
+ return {
302
+ name,
303
+ // A pi just spawned has settled no run, so 0 is exact when an older
304
+ // extension's reply carries no runs.
305
+ runs: typeof sent.runs === "number" ? sent.runs : 0,
306
+ ...typeof attach === "string" ? { attach } : {},
307
+ ...typeof wsPath === "string" ? { workspace: wsPath } : {}
308
+ };
308
309
  }
309
310
  const recordLabel = (params, name) => typeof params.label === "string" && params.label.trim() ? params.label.trim() : name;
310
311
  async function resolveRecord(raw) {
@@ -400,7 +401,14 @@ ${recorded}` : message,
400
401
  cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
401
402
  if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
402
403
  }
403
- const { name, attach, workspace } = await spawn(params, cwd, signal);
404
+ const { name, runs, attach, workspace } = await spawn(
405
+ params,
406
+ brief ? `${brief}
407
+
408
+ ${task}` : task,
409
+ cwd,
410
+ signal
411
+ );
404
412
  const entry = {
405
413
  abort: new AbortController(),
406
414
  cancelling: false,
@@ -410,43 +418,21 @@ ${recorded}` : message,
410
418
  freeSlot();
411
419
  showStatus();
412
420
  const started = Date.now();
413
- const waitArgs = (after) => [
414
- "agent",
415
- "wait",
416
- name,
417
- "-w",
418
- W,
419
- "--json",
420
- "--timeout",
421
- String(timeoutS),
422
- ...after !== void 0 ? ["--after-runs", String(after)] : []
423
- ];
424
- const plain = new AbortController();
425
- entry.abort.signal.addEventListener("abort", () => plain.abort(), { once: true });
426
- let waiting = mu(waitArgs(), plain.signal);
427
- const sent = await mu([
428
- "agent",
429
- "send",
430
- name,
431
- brief ? `${brief}
432
-
433
- ${task}` : task,
434
- "-w",
435
- W,
436
- "--json"
437
- ]);
438
- if (sent.code !== 0) {
439
- plain.abort();
440
- forget(name);
441
- const c = await mu(["agent", "close", name, "-w", W]);
442
- const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
443
- throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
444
- }
445
- const runs = json(sent)?.runs;
446
- if (typeof runs === "number") {
447
- plain.abort();
448
- waiting = mu(waitArgs(runs), entry.abort.signal);
449
- }
421
+ const waiting = mu(
422
+ [
423
+ "agent",
424
+ "wait",
425
+ name,
426
+ "-w",
427
+ W,
428
+ "--json",
429
+ "--timeout",
430
+ String(timeoutS),
431
+ "--after-runs",
432
+ String(runs)
433
+ ],
434
+ entry.abort.signal
435
+ );
450
436
  const settle = async (r) => {
451
437
  forget(name);
452
438
  try {
@@ -536,7 +522,11 @@ ${task}` : task,
536
522
  },
537
523
  cli: {
538
524
  type: "string",
539
- description: "Agent CLI key (default pi). Name a configured key such as 'pi_fast' to run a cheaper model."
525
+ description: "Agent CLI key (default pi). Only keys with $MU_<KEY>_COMMAND set in pi's environment exist; to pick a model, use model instead."
526
+ },
527
+ model: {
528
+ type: "string",
529
+ description: "Model for the subagent, passed to pi as --model (e.g. 'sonnet', 'openai/gpt-5:high'); appended to the cli's command. Default: the cli's own model. Use it to vary models across a review panel."
540
530
  },
541
531
  keep: {
542
532
  type: "boolean",
@@ -569,6 +559,11 @@ ${task}` : task,
569
559
  const cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
570
560
  if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
571
561
  }
562
+ const m = params.model;
563
+ if (m !== void 0 && !(typeof m === "string" && MODEL_RE.test(m)))
564
+ throw new Error(
565
+ `mu_delegate: model must be a pi model id like 'provider/id:thinking' (got ${JSON.stringify(m)})`
566
+ );
572
567
  const record = await resolveRecord(params.record);
573
568
  const max = delegateMax();
574
569
  if (inflight.size + starting < max) {
@@ -892,6 +887,54 @@ function nudgeText(rule, refs) {
892
887
  `${refs.length} task(s) IN_PROGRESS. Next: mu task wait ${shown} --first --on-stall exit --json${more}`
893
888
  ].join("\n");
894
889
  }
890
+ function breadcrumb(run, args) {
891
+ void run(args).catch(() => {
892
+ });
893
+ }
894
+ function parallelSettle(pi) {
895
+ const handlers = [];
896
+ const api = {
897
+ on(event, handler) {
898
+ if (event === "agent_before_settle") handlers.push(handler);
899
+ else pi.on(event, handler);
900
+ }
901
+ };
902
+ let errors = [];
903
+ return {
904
+ api,
905
+ flush() {
906
+ if (handlers.length === 0) return;
907
+ pi.on("agent_before_settle", async (event, ctx) => {
908
+ errors = handlers.map(() => void 0);
909
+ const results = await Promise.all(
910
+ handlers.map(async (h, i) => {
911
+ try {
912
+ return await h(event, ctx);
913
+ } catch (err) {
914
+ errors[i] = [err];
915
+ return void 0;
916
+ }
917
+ })
918
+ );
919
+ const fired = results.filter((r) => r !== void 0);
920
+ if (fired.length === 0) return void 0;
921
+ const prior = event?.entries ?? [];
922
+ return {
923
+ entries: [...prior, ...fired.flatMap((r) => r.entries ?? [])],
924
+ ...fired.some((r) => r.continue === true) ? { continue: true } : {}
925
+ };
926
+ });
927
+ handlers.forEach((_, i) => {
928
+ pi.on("agent_before_settle", () => {
929
+ const err = errors[i];
930
+ errors[i] = void 0;
931
+ if (err !== void 0) throw err[0];
932
+ return void 0;
933
+ });
934
+ });
935
+ }
936
+ };
937
+ }
895
938
  function outcomeOf(event) {
896
939
  return typeof event === "object" && event !== null ? event.outcome : void 0;
897
940
  }
@@ -923,7 +966,7 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested
923
966
  if (refs === void 0 || refs.length === 0) return;
924
967
  const text = nudgeText(rule, refs);
925
968
  const logWs = [...armed].find((w) => w !== "");
926
- await run([
969
+ breadcrumb(run, [
927
970
  "log",
928
971
  ...logWs ? ["-w", logWs] : [],
929
972
  "--kind",
@@ -979,7 +1022,7 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested
979
1022
  fired = true;
980
1023
  const names = await ownedInProgress(run, me.agent, me.workstream);
981
1024
  if (names === void 0 || names.length === 0) return;
982
- await run([
1025
+ breadcrumb(run, [
983
1026
  "log",
984
1027
  "-w",
985
1028
  me.workstream,
@@ -1033,13 +1076,12 @@ function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested) {
1033
1076
  if (fired || armed.size === 0 || nested() || noUI(ctx)) return;
1034
1077
  if (outcomeOf(event) !== "completed") return;
1035
1078
  fired = true;
1036
- const refs = [];
1037
- for (const [ref, t] of armed) {
1038
- if (await briefRefuted(run, t.ws, t.id) === false) refs.push(ref);
1039
- }
1079
+ const tasks = [...armed];
1080
+ const refuted = await Promise.all(tasks.map(([, t]) => briefRefuted(run, t.ws, t.id)));
1081
+ const refs = tasks.flatMap(([ref], i) => refuted[i] === false ? [ref] : []);
1040
1082
  if (refs.length === 0) return;
1041
1083
  const logWs = [...armed.values()].find((t) => t.ws !== "")?.ws;
1042
- await run([
1084
+ breadcrumb(run, [
1043
1085
  "log",
1044
1086
  ...logWs ? ["-w", logWs] : [],
1045
1087
  "--kind",
@@ -1060,7 +1102,7 @@ function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested) {
1060
1102
  }
1061
1103
 
1062
1104
  // extension/mu-pi.ts
1063
- var EXT_VERSION = true ? "3.9.0" : void 0;
1105
+ var EXT_VERSION = true ? "3.10.1" : void 0;
1064
1106
  var FRESH_COMMAND = "mu-fresh";
1065
1107
  var FRESH_TIMEOUT_MS = 3e4;
1066
1108
  function commandName(name) {
@@ -1203,9 +1245,11 @@ function muPi(pi, run = defaultRunner()) {
1203
1245
  registerDelegate(pi, run);
1204
1246
  registerPrompts(pi);
1205
1247
  const nested = serveCtl(pi);
1206
- registerNudge(pi, run, void 0, nested);
1207
- registerCloseNudge(pi, run, process.env, nested);
1208
- registerRefuteNudge(pi, run, nested);
1248
+ const settle = parallelSettle(pi);
1249
+ registerNudge(settle.api, run, void 0, nested);
1250
+ registerCloseNudge(settle.api, run, process.env, nested);
1251
+ registerRefuteNudge(settle.api, run, nested);
1252
+ settle.flush();
1209
1253
  }
1210
1254
  function serveCtl(pi) {
1211
1255
  const sock = process.env.MU_CTL_SOCK;