@mu-crew/mu 3.3.0 → 3.4.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 (43) hide show
  1. package/README.md +45 -31
  2. package/dist/cli.js +186 -20
  3. package/dist/cli.js.map +1 -1
  4. package/dist/extension/mu-pi.js +103 -74
  5. package/dist/extension/mu-pi.js.map +1 -1
  6. package/dist/index.d.ts +33 -4
  7. package/dist/index.js +70 -4
  8. package/dist/index.js.map +1 -1
  9. package/docs/VISION.md +3 -1
  10. package/docs/VOCABULARY.md +4 -1
  11. package/docs/architecture/dag.md +3 -3
  12. package/docs/guide/delegate.md +30 -3
  13. package/docs/reference/env.md +1 -0
  14. package/docs/reference/naming.md +18 -0
  15. package/docs/specs/2026-09-29-task-substates.md +1 -1
  16. package/package.json +1 -1
  17. package/prompts/mu-debug.md +8 -0
  18. package/prompts/mu-sweep.md +8 -0
  19. package/prompts/mu-until.md +8 -0
  20. package/prompts/ultrathink.md +2 -1
  21. package/skills/mu/SKILL.md +70 -31
  22. package/skills/mu/recipes/adversarial-review.md +27 -19
  23. package/skills/mu/recipes/{triage.md → backlog-triage.md} +9 -4
  24. package/skills/mu/recipes/codemode-driver.md +68 -20
  25. package/skills/mu/recipes/deep-research.md +23 -8
  26. package/skills/mu/recipes/fan-out.md +15 -11
  27. package/skills/mu/recipes/findings.md +103 -0
  28. package/skills/mu/recipes/hypothesis-panel.md +18 -13
  29. package/skills/mu/recipes/long-run.md +4 -4
  30. package/skills/mu/recipes/loop-until-done.md +7 -3
  31. package/skills/mu/recipes/orchestrator-loop.md +38 -27
  32. package/skills/mu/recipes/plan.md +8 -3
  33. package/skills/mu/recipes/recovery.md +2 -0
  34. package/skills/mu/recipes/refute.md +38 -36
  35. package/skills/mu/recipes/remote-workers.md +7 -2
  36. package/skills/mu/recipes/review-panel.md +36 -31
  37. package/skills/mu/recipes/rules-audit.md +30 -17
  38. package/skills/mu/recipes/tasks-or-calls.md +67 -0
  39. package/skills/mu/recipes/tournament.md +13 -10
  40. package/skills/mu/recipes/ultrathink.md +20 -11
  41. package/skills/mu/recipes/watcher.md +4 -1
  42. package/skills/mu/recipes/waves.md +6 -5
  43. package/skills/mu/recipes/worker.md +3 -1
@@ -55,6 +55,11 @@ function delegateStatus(n) {
55
55
  return n > 0 ? `${n} delegate${n === 1 ? "" : "s"} running` : void 0;
56
56
  }
57
57
  var DELEGATE_TIMEOUT_S = 3600;
58
+ var DELEGATE_MAX_DEFAULT = 16;
59
+ function delegateMax(env = process.env) {
60
+ const n = Number(env.MU_DELEGATE_MAX);
61
+ return Number.isInteger(n) && n > 0 ? n : DELEGATE_MAX_DEFAULT;
62
+ }
58
63
  var W = DELEGATE_WORKSTREAM;
59
64
  function delegateEnabled(env = process.env) {
60
65
  if (env.MU_MANAGED_AGENT) return false;
@@ -141,6 +146,7 @@ function labelStem(label) {
141
146
  function registerDelegate(pi, run = defaultRunner()) {
142
147
  if (!delegateEnabled()) return;
143
148
  const inflight = /* @__PURE__ */ new Map();
149
+ let starting = 0;
144
150
  const reserved = /* @__PURE__ */ new Set();
145
151
  let ui;
146
152
  const showStatus = () => {
@@ -253,7 +259,7 @@ function registerDelegate(pi, run = defaultRunner()) {
253
259
  pi.registerTool({
254
260
  name: DELEGATE_TOOL,
255
261
  label: "mu delegate",
256
- description: `Subagent: run one self-contained task in a fresh pi agent (its own mux pane in mu's ${W} workstream) while you keep working. Use it whenever you would reach for a subagent: research, a review, a draft, an investigation; call it several times to fan out in parallel. The subagent starts with no context, in your working directory: put everything it needs in task. The call returns at once and the answer arrives later as a follow-up message: carry on with other work, or end your turn if your next step needs the answer (the follow-up resumes you). The pane closes after a clean finish; keep: true leaves it open.`,
262
+ description: `Subagent: run one self-contained task in a fresh pi agent (its own mux pane in mu's ${W} workstream) while you keep working. Use it whenever you would reach for a subagent: research, a review, a draft, an investigation; call it several times to fan out in parallel (at most ${delegateMax()} in flight). The subagent starts with no context, in your working directory: put everything it needs in task. The call returns at once and the answer arrives later as a follow-up message: carry on with other work, or end your turn if your next step needs the answer (the follow-up resumes you). The pane closes after a clean finish; keep: true leaves it open.`,
257
263
  promptSnippet: "Subagent: delegate a self-contained task to a background pi agent; its answer arrives later as a follow-up",
258
264
  parameters: {
259
265
  type: "object",
@@ -296,83 +302,106 @@ function registerDelegate(pi, run = defaultRunner()) {
296
302
  track(ctx);
297
303
  const task = typeof params.task === "string" ? params.task.trim() : "";
298
304
  if (!task) throw new Error("mu_delegate needs a non-empty task");
299
- const brief = typeof params.brief === "string" ? params.brief.trim() : "";
300
- const keep = params.keep === true;
301
- const t = params.timeout;
302
- if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
305
+ const max = delegateMax();
306
+ if (inflight.size + starting >= max)
303
307
  throw new Error(
304
- `mu_delegate: timeout must be a positive number of seconds (got ${JSON.stringify(t)})`
308
+ `mu_delegate: ${inflight.size + starting} delegates already running (MU_DELEGATE_MAX=${max}). End your turn; issue the rest as answers arrive (each answer resumes you).`
305
309
  );
306
- const timeoutS = typeof t === "number" ? Math.ceil(t) : DELEGATE_TIMEOUT_S;
307
- let cwd;
308
- if (params.workspace !== true) {
309
- cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
310
- if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
311
- }
312
- const { name, attach, workspace } = await spawn(params, cwd, signal);
313
- const entry = { abort: new AbortController(), cancelling: false };
314
- inflight.set(name, entry);
315
- showStatus();
316
- const started = Date.now();
317
- const waiting = mu(
318
- ["agent", "wait", name, "-w", W, "--json", "--timeout", String(timeoutS)],
319
- entry.abort.signal
320
- );
321
- const sent = await mu(["agent", "send", name, brief ? `${brief}
322
-
323
- ${task}` : task, "-w", W]);
324
- if (sent.code !== 0) {
325
- entry.abort.abort();
326
- forget(name);
327
- const c = await mu(["agent", "close", name, "-w", W]);
328
- const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
329
- throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
330
- }
331
- const settle = async (r) => {
332
- forget(name);
333
- try {
334
- await deliver(name, r, keep, {
335
- timeoutS,
336
- elapsedMs: Date.now() - started,
337
- ...attach ? { attach } : {},
338
- ...workspace ? { workspace } : {}
339
- });
340
- } catch (e) {
341
- await Promise.resolve(
342
- pi.sendMessage(
343
- {
344
- customType: DELEGATE_MESSAGE_TYPE,
345
- content: `Delegate ${W}/${name} settled, but delivering its answer failed: ${e instanceof Error ? e.message : String(e)}. Read it with: mu agent read ${name} -n 50 -w ${W}`,
346
- display: true,
347
- details: { name, workstream: W, outcome: null, closed: false }
348
- },
349
- { deliverAs: "followUp", triggerTurn: true }
350
- )
351
- ).catch(() => {
352
- });
353
- }
310
+ starting++;
311
+ let slotHeld = true;
312
+ const freeSlot = () => {
313
+ if (slotHeld) starting--;
314
+ slotHeld = false;
354
315
  };
355
- void waiting.then(async (r) => {
356
- if (inflight.get(name) !== entry) return;
357
- if (entry.cancelling) entry.parked = () => settle(r);
358
- else await settle(r);
359
- });
360
- const lines = [
361
- `Delegated to ${W}/${name}. Its answer arrives as a follow-up message when it finishes; do not wait or poll.`,
362
- workspace ? `Workspace: ${workspace}` : void 0,
363
- attach ? `Attach: ${attach}` : void 0,
364
- `Cancel: ${DELEGATE_CANCEL_TOOL} { name: "${name}" }`
365
- ].filter((x) => x !== void 0);
366
- return {
367
- content: [{ type: "text", text: lines.join("\n") }],
368
- details: {
316
+ try {
317
+ const brief = typeof params.brief === "string" ? params.brief.trim() : "";
318
+ const keep = params.keep === true;
319
+ const t = params.timeout;
320
+ if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
321
+ throw new Error(
322
+ `mu_delegate: timeout must be a positive number of seconds (got ${JSON.stringify(t)})`
323
+ );
324
+ const timeoutS = typeof t === "number" ? Math.ceil(t) : DELEGATE_TIMEOUT_S;
325
+ let cwd;
326
+ if (params.workspace !== true) {
327
+ cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
328
+ if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
329
+ }
330
+ const { name, attach, workspace } = await spawn(params, cwd, signal);
331
+ const entry = { abort: new AbortController(), cancelling: false };
332
+ inflight.set(name, entry);
333
+ freeSlot();
334
+ showStatus();
335
+ const started = Date.now();
336
+ const waiting = mu(
337
+ ["agent", "wait", name, "-w", W, "--json", "--timeout", String(timeoutS)],
338
+ entry.abort.signal
339
+ );
340
+ const sent = await mu([
341
+ "agent",
342
+ "send",
369
343
  name,
370
- workstream: W,
371
- attach: attach ?? null,
372
- workspace: workspace ?? null,
373
- keep
344
+ brief ? `${brief}
345
+
346
+ ${task}` : task,
347
+ "-w",
348
+ W
349
+ ]);
350
+ if (sent.code !== 0) {
351
+ entry.abort.abort();
352
+ forget(name);
353
+ const c = await mu(["agent", "close", name, "-w", W]);
354
+ const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
355
+ throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
374
356
  }
375
- };
357
+ const settle = async (r) => {
358
+ forget(name);
359
+ try {
360
+ await deliver(name, r, keep, {
361
+ timeoutS,
362
+ elapsedMs: Date.now() - started,
363
+ ...attach ? { attach } : {},
364
+ ...workspace ? { workspace } : {}
365
+ });
366
+ } catch (e) {
367
+ await Promise.resolve(
368
+ pi.sendMessage(
369
+ {
370
+ customType: DELEGATE_MESSAGE_TYPE,
371
+ content: `Delegate ${W}/${name} settled, but delivering its answer failed: ${e instanceof Error ? e.message : String(e)}. Read it with: mu agent read ${name} -n 50 -w ${W}`,
372
+ display: true,
373
+ details: { name, workstream: W, outcome: null, closed: false }
374
+ },
375
+ { deliverAs: "followUp", triggerTurn: true }
376
+ )
377
+ ).catch(() => {
378
+ });
379
+ }
380
+ };
381
+ void waiting.then(async (r) => {
382
+ if (inflight.get(name) !== entry) return;
383
+ if (entry.cancelling) entry.parked = () => settle(r);
384
+ else await settle(r);
385
+ });
386
+ const lines = [
387
+ `Delegated to ${W}/${name}. Its answer arrives as a follow-up message when it finishes; do not wait or poll.`,
388
+ workspace ? `Workspace: ${workspace}` : void 0,
389
+ attach ? `Attach: ${attach}` : void 0,
390
+ `Cancel: ${DELEGATE_CANCEL_TOOL} { name: "${name}" }`
391
+ ].filter((x) => x !== void 0);
392
+ return {
393
+ content: [{ type: "text", text: lines.join("\n") }],
394
+ details: {
395
+ name,
396
+ workstream: W,
397
+ attach: attach ?? null,
398
+ workspace: workspace ?? null,
399
+ keep
400
+ }
401
+ };
402
+ } finally {
403
+ freeSlot();
404
+ }
376
405
  }
377
406
  });
378
407
  pi.registerTool({
@@ -561,7 +590,7 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
561
590
  }
562
591
 
563
592
  // extension/mu-pi.ts
564
- var EXT_VERSION = true ? "3.3.0" : void 0;
593
+ var EXT_VERSION = true ? "3.4.0" : void 0;
565
594
  var FRESH_COMMAND = "mu-fresh";
566
595
  var FRESH_TIMEOUT_MS = 3e4;
567
596
  function commandName(name) {