@mu-crew/mu 3.2.1 → 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.
- package/AGENTS.md +10 -5
- package/README.md +49 -2
- package/dist/cli.js +108 -78
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +244 -12
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +36 -10
- package/dist/index.js +89 -67
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +1 -0
- package/docs/VOCABULARY.md +2 -0
- package/docs/architecture/control-socket.md +17 -3
- package/docs/architecture/mux.md +3 -4
- package/docs/guide/README.md +5 -0
- package/docs/guide/dispatch.md +11 -4
- package/docs/guide/getting-started.md +3 -0
- package/docs/guide/recovery.md +3 -1
- package/docs/guide/remote.md +1 -1
- package/docs/history/CHANGELOG-pre-3.md +1 -1
- package/docs/reference/env.md +1 -1
- package/docs/specs/2026-10-03-keep-driving-nudge.md +142 -0
- package/package.json +2 -1
- package/prompts/mu-refute.md +8 -0
- package/prompts/mu-research.md +8 -0
- package/prompts/mu-review.md +8 -0
- package/prompts/mu-rules-audit.md +8 -0
- package/prompts/mu-tournament.md +9 -0
- package/prompts/ultrathink.md +9 -0
- package/skills/mu/SKILL.md +71 -131
- package/skills/mu/recipes/adversarial-review.md +83 -0
- package/skills/mu/recipes/brief.md +53 -0
- package/skills/mu/recipes/codemode-driver.md +54 -0
- package/skills/mu/recipes/deep-research.md +52 -0
- package/skills/mu/recipes/fan-out.md +36 -0
- package/skills/mu/recipes/hypothesis-panel.md +40 -0
- package/skills/mu/recipes/long-run.md +20 -0
- package/skills/mu/recipes/loop-until-done.md +38 -0
- package/skills/mu/recipes/orchestrator-loop.md +87 -0
- package/skills/mu/recipes/plan.md +54 -0
- package/skills/mu/recipes/recovery.md +26 -0
- package/skills/mu/recipes/refute.md +49 -0
- package/skills/mu/recipes/review-panel.md +57 -0
- package/skills/mu/recipes/rules-audit.md +70 -0
- package/skills/mu/recipes/tournament.md +37 -0
- package/skills/mu/recipes/triage.md +38 -0
- package/skills/mu/recipes/ultrathink.md +73 -0
- package/skills/mu/recipes/watcher.md +16 -0
- package/skills/mu/recipes/waves.md +19 -0
- package/skills/mu/recipes/worker.md +24 -0
- /package/skills/mu/{REMOTE_WORKERS.md → recipes/remote-workers.md} +0 -0
package/dist/extension/mu-pi.js
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
// extension/mu-pi.ts
|
|
2
2
|
import {
|
|
3
3
|
chmodSync,
|
|
4
|
-
existsSync as
|
|
4
|
+
existsSync as existsSync3,
|
|
5
5
|
linkSync,
|
|
6
6
|
mkdirSync,
|
|
7
|
-
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync as readFileSync2,
|
|
8
9
|
realpathSync,
|
|
9
10
|
rmSync,
|
|
10
11
|
statSync as statSync2
|
|
11
12
|
} from "fs";
|
|
12
13
|
import { connect, createServer } from "net";
|
|
13
|
-
import { dirname, join as
|
|
14
|
+
import { dirname, join as join3 } from "path";
|
|
15
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14
16
|
|
|
15
17
|
// src/ctl/protocol.ts
|
|
16
18
|
var CTL_PROTOCOL_VERSION = 1;
|
|
19
|
+
var CTL_COMMANDS = ["new", "reload", "compact"];
|
|
17
20
|
var CTL_OPS = [
|
|
18
21
|
"hello",
|
|
19
22
|
"status",
|
|
20
23
|
"send",
|
|
21
24
|
"wait",
|
|
22
25
|
"abort",
|
|
23
|
-
"fresh"
|
|
26
|
+
"fresh",
|
|
27
|
+
"command"
|
|
24
28
|
];
|
|
25
29
|
var UNKNOWN_OP_PREFIX = "unknown op: ";
|
|
26
30
|
function encode(msg) {
|
|
@@ -427,10 +431,143 @@ ${task}` : task, "-w", W]);
|
|
|
427
431
|
});
|
|
428
432
|
}
|
|
429
433
|
|
|
434
|
+
// extension/nudge.ts
|
|
435
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
436
|
+
import { join as join2 } from "path";
|
|
437
|
+
var NUDGE_MESSAGE_TYPE = "mu-keep-driving";
|
|
438
|
+
var NUDGE_LOG_KIND = "nudge";
|
|
439
|
+
var MARK_START = "<!-- mu:keep-driving -->";
|
|
440
|
+
var MARK_END = "<!-- /mu:keep-driving -->";
|
|
441
|
+
var WAIT_HINT_MAX = 8;
|
|
442
|
+
function nudgeEnabled(env = process.env) {
|
|
443
|
+
return env.MU_NUDGE !== "0";
|
|
444
|
+
}
|
|
445
|
+
function keepDrivingRule(skillMd) {
|
|
446
|
+
const i = skillMd.indexOf(MARK_START);
|
|
447
|
+
const j = skillMd.indexOf(MARK_END);
|
|
448
|
+
if (i < 0 || j < i) return void 0;
|
|
449
|
+
const text = skillMd.slice(i + MARK_START.length, j).trim();
|
|
450
|
+
return text === "" ? void 0 : text;
|
|
451
|
+
}
|
|
452
|
+
function readSkill() {
|
|
453
|
+
for (const up of ["..", "../.."]) {
|
|
454
|
+
const p = join2(import.meta.dirname ?? "", up, "skills", "mu", "SKILL.md");
|
|
455
|
+
if (existsSync2(p)) return readFileSync(p, "utf8");
|
|
456
|
+
}
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
459
|
+
function dispatchedWorkstreams(command) {
|
|
460
|
+
const found = /* @__PURE__ */ new Set();
|
|
461
|
+
for (const seg of command.split(/&&|\|\||;|\n|\|/)) {
|
|
462
|
+
const words = seg.trim().split(/\s+/);
|
|
463
|
+
const at = words.findIndex((w) => w === "mu" || w.endsWith("/mu"));
|
|
464
|
+
if (at < 0) continue;
|
|
465
|
+
const [noun, verb] = [words[at + 1], words[at + 2]];
|
|
466
|
+
const isSendSpawn = noun === "agent" && (verb === "send" || verb === "spawn");
|
|
467
|
+
const isClaimFor = noun === "task" && verb === "claim" && words.includes("--for");
|
|
468
|
+
if (!isSendSpawn && !isClaimFor) continue;
|
|
469
|
+
let ws = "";
|
|
470
|
+
for (let k = at + 3; k < words.length; k++) {
|
|
471
|
+
const w = words[k] ?? "";
|
|
472
|
+
if (w === "-w" || w === "--workstream") ws = unquote(words[k + 1] ?? "");
|
|
473
|
+
else if (w.startsWith("--workstream=")) ws = unquote(w.slice("--workstream=".length));
|
|
474
|
+
}
|
|
475
|
+
if (ws === "" && isClaimFor) {
|
|
476
|
+
const ref = words[at + 3] ?? "";
|
|
477
|
+
if (ref.includes("/")) ws = unquote(ref.split("/")[0] ?? "");
|
|
478
|
+
}
|
|
479
|
+
if (ws !== "scratch") found.add(ws);
|
|
480
|
+
}
|
|
481
|
+
return found.size > 0 ? [...found] : void 0;
|
|
482
|
+
}
|
|
483
|
+
function unquote(s) {
|
|
484
|
+
return s.replace(/^['"]|['"]$/g, "");
|
|
485
|
+
}
|
|
486
|
+
async function inProgress(run, workstreams) {
|
|
487
|
+
const named = workstreams.filter((w) => w !== "");
|
|
488
|
+
const args = ["state", "--json", "--events", "0"];
|
|
489
|
+
for (const w of named) args.push("-w", w);
|
|
490
|
+
const r = await run(args);
|
|
491
|
+
if (r.code !== 0) return void 0;
|
|
492
|
+
let json;
|
|
493
|
+
try {
|
|
494
|
+
json = JSON.parse(r.stdout);
|
|
495
|
+
} catch {
|
|
496
|
+
return void 0;
|
|
497
|
+
}
|
|
498
|
+
const cards = json.workstreams ?? [json];
|
|
499
|
+
const refs = [];
|
|
500
|
+
for (const ws of cards) {
|
|
501
|
+
for (const t of ws.inProgress ?? []) {
|
|
502
|
+
if (t.name) refs.push(`${ws.workstreamName ?? ""}/${t.name}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return refs;
|
|
506
|
+
}
|
|
507
|
+
function nudgeText(rule, refs) {
|
|
508
|
+
const shown = refs.slice(0, WAIT_HINT_MAX).join(" ");
|
|
509
|
+
const more = refs.length > WAIT_HINT_MAX ? ` (+${refs.length - WAIT_HINT_MAX} more)` : "";
|
|
510
|
+
return [
|
|
511
|
+
`[mu] ${rule}`,
|
|
512
|
+
"",
|
|
513
|
+
`${refs.length} task(s) IN_PROGRESS. Next: mu task wait ${shown} --first --on-stall exit --json${more}`
|
|
514
|
+
].join("\n");
|
|
515
|
+
}
|
|
516
|
+
function outcomeOf(event) {
|
|
517
|
+
return typeof event === "object" && event !== null ? event.outcome : void 0;
|
|
518
|
+
}
|
|
519
|
+
function bashCommand(event) {
|
|
520
|
+
if (typeof event !== "object" || event === null) return void 0;
|
|
521
|
+
const e = event;
|
|
522
|
+
return e.toolName === "bash" && typeof e.input?.command === "string" ? e.input.command : void 0;
|
|
523
|
+
}
|
|
524
|
+
function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
525
|
+
if (!nudgeEnabled()) return;
|
|
526
|
+
const rule = skillMd === void 0 ? void 0 : keepDrivingRule(skillMd);
|
|
527
|
+
if (rule === void 0) return;
|
|
528
|
+
const armed = /* @__PURE__ */ new Set();
|
|
529
|
+
let fired = false;
|
|
530
|
+
pi.on("input", () => {
|
|
531
|
+
armed.clear();
|
|
532
|
+
fired = false;
|
|
533
|
+
});
|
|
534
|
+
pi.on("tool_call", (event) => {
|
|
535
|
+
const cmd = bashCommand(event);
|
|
536
|
+
if (cmd === void 0) return;
|
|
537
|
+
for (const ws of dispatchedWorkstreams(cmd) ?? []) armed.add(ws);
|
|
538
|
+
});
|
|
539
|
+
pi.on("agent_before_settle", async (event) => {
|
|
540
|
+
if (fired || armed.size === 0) return;
|
|
541
|
+
if (outcomeOf(event) !== "completed") return;
|
|
542
|
+
fired = true;
|
|
543
|
+
const refs = await inProgress(run, [...armed]);
|
|
544
|
+
if (refs === void 0 || refs.length === 0) return;
|
|
545
|
+
const text = nudgeText(rule, refs);
|
|
546
|
+
const logWs = [...armed].find((w) => w !== "");
|
|
547
|
+
await run([
|
|
548
|
+
"log",
|
|
549
|
+
...logWs ? ["-w", logWs] : [],
|
|
550
|
+
"--kind",
|
|
551
|
+
NUDGE_LOG_KIND,
|
|
552
|
+
`keep-driving: ${refs.length} in progress`
|
|
553
|
+
]);
|
|
554
|
+
return {
|
|
555
|
+
entries: [
|
|
556
|
+
{ type: "custom_message", customType: NUDGE_MESSAGE_TYPE, content: text, display: true }
|
|
557
|
+
],
|
|
558
|
+
continue: true
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
430
563
|
// extension/mu-pi.ts
|
|
431
|
-
var EXT_VERSION = true ? "3.
|
|
564
|
+
var EXT_VERSION = true ? "3.3.0" : void 0;
|
|
432
565
|
var FRESH_COMMAND = "mu-fresh";
|
|
433
566
|
var FRESH_TIMEOUT_MS = 3e4;
|
|
567
|
+
function commandName(name) {
|
|
568
|
+
return `mu-${name}`;
|
|
569
|
+
}
|
|
570
|
+
var COMMAND_TIMEOUT_MS = 3e4;
|
|
434
571
|
var LAST_TEXT_MAX_BYTES = 64 * 1024;
|
|
435
572
|
var LAST_TEXT_TRUNCATED = "[truncated, see pane]";
|
|
436
573
|
var LIVE_PROBE_MS = 500;
|
|
@@ -444,9 +581,9 @@ function detectPiVersion() {
|
|
|
444
581
|
if (!entry) return void 0;
|
|
445
582
|
let dir = dirname(realpathSync(entry));
|
|
446
583
|
for (let i = 0; i < 6; i++) {
|
|
447
|
-
const pkgPath =
|
|
448
|
-
if (
|
|
449
|
-
const pkg = JSON.parse(
|
|
584
|
+
const pkgPath = join3(dir, "package.json");
|
|
585
|
+
if (existsSync3(pkgPath)) {
|
|
586
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
450
587
|
if (typeof pkg === "object" && pkg !== null) {
|
|
451
588
|
const { name, version } = pkg;
|
|
452
589
|
if (typeof name === "string" && /pi/.test(name) && typeof version === "string") {
|
|
@@ -529,8 +666,31 @@ function reasonOf(event) {
|
|
|
529
666
|
const r = typeof event === "object" && event !== null ? event.reason : void 0;
|
|
530
667
|
return typeof r === "string" ? r : void 0;
|
|
531
668
|
}
|
|
669
|
+
function promptsDir(from = import.meta.url) {
|
|
670
|
+
let here;
|
|
671
|
+
try {
|
|
672
|
+
here = dirname(fileURLToPath2(from));
|
|
673
|
+
} catch {
|
|
674
|
+
return void 0;
|
|
675
|
+
}
|
|
676
|
+
for (const up of ["..", "../.."]) {
|
|
677
|
+
const d = join3(here, up, "prompts");
|
|
678
|
+
if (existsSync3(join3(d, "ultrathink.md"))) return d;
|
|
679
|
+
}
|
|
680
|
+
return void 0;
|
|
681
|
+
}
|
|
682
|
+
function registerPrompts(pi) {
|
|
683
|
+
pi.on("resources_discover", () => {
|
|
684
|
+
const d = promptsDir();
|
|
685
|
+
if (!d) return {};
|
|
686
|
+
const paths = readdirSync(d).filter((f) => f.endsWith(".md")).sort().map((f) => join3(d, f));
|
|
687
|
+
return { promptPaths: paths };
|
|
688
|
+
});
|
|
689
|
+
}
|
|
532
690
|
function muPi(pi) {
|
|
533
691
|
registerDelegate(pi);
|
|
692
|
+
registerPrompts(pi);
|
|
693
|
+
registerNudge(pi);
|
|
534
694
|
serveCtl(pi);
|
|
535
695
|
}
|
|
536
696
|
function serveCtl(pi) {
|
|
@@ -565,6 +725,12 @@ function serveCtl(pi) {
|
|
|
565
725
|
g.fresh = void 0;
|
|
566
726
|
f.settle(r);
|
|
567
727
|
};
|
|
728
|
+
const settleCommand = (r) => {
|
|
729
|
+
const c = g.command;
|
|
730
|
+
if (!c) return;
|
|
731
|
+
g.command = void 0;
|
|
732
|
+
c.settle(r);
|
|
733
|
+
};
|
|
568
734
|
pi.on("agent_start", (_e, c) => {
|
|
569
735
|
g.ctx = c;
|
|
570
736
|
g.state = "busy";
|
|
@@ -603,10 +769,69 @@ function serveCtl(pi) {
|
|
|
603
769
|
settleFresh({ v: V, ok: true, ...status() });
|
|
604
770
|
}
|
|
605
771
|
});
|
|
772
|
+
pi.on("session_before_compact", () => {
|
|
773
|
+
if (g.command?.name === "compact") settleCommand({ v: V, ok: true, ...status() });
|
|
774
|
+
});
|
|
775
|
+
async function runCommand(name, ctx) {
|
|
776
|
+
const c = g.command;
|
|
777
|
+
if (c?.name !== name) return;
|
|
778
|
+
const ok = () => settleCommand({ v: V, ok: true, ...status() });
|
|
779
|
+
const failWith = (e) => settleCommand(fail(e instanceof Error ? e.message : String(e)));
|
|
780
|
+
try {
|
|
781
|
+
if (name === "new") {
|
|
782
|
+
const r = await ctx.newSession();
|
|
783
|
+
if (r.cancelled) settleCommand(fail("new session was cancelled"));
|
|
784
|
+
else ok();
|
|
785
|
+
} else if (name === "reload") {
|
|
786
|
+
await ctx.reload();
|
|
787
|
+
if (c.reloaded) ok();
|
|
788
|
+
else settleCommand(fail("pi did not reload (busy or compacting)"));
|
|
789
|
+
} else {
|
|
790
|
+
ctx.compact({
|
|
791
|
+
...c.instructions !== void 0 ? { customInstructions: c.instructions } : {},
|
|
792
|
+
onComplete: ok,
|
|
793
|
+
onError: failWith
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
} catch (e) {
|
|
797
|
+
failWith(e);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for (const name of CTL_COMMANDS) {
|
|
801
|
+
pi.registerCommand(commandName(name), {
|
|
802
|
+
description: `mu internal: /${name} requested over the control socket (mu agent send '/${name}')`,
|
|
803
|
+
handler: (_args, ctx) => runCommand(name, ctx)
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
function command(req) {
|
|
807
|
+
const name = CTL_COMMANDS.find((n) => n === req.name);
|
|
808
|
+
if (name === void 0) {
|
|
809
|
+
return fail(`command must be one of ${CTL_COMMANDS.join(", ")}`);
|
|
810
|
+
}
|
|
811
|
+
if (g.fresh || g.command) return fail("a fresh send or command is already in progress");
|
|
812
|
+
const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
|
|
813
|
+
if (busy && req.force !== true) return fail("busy");
|
|
814
|
+
const instructions = str(req, "instructions");
|
|
815
|
+
return new Promise((resolve) => {
|
|
816
|
+
const timer = setTimeout(() => settleCommand(fail(`/${name} timed out`)), COMMAND_TIMEOUT_MS);
|
|
817
|
+
g.command = {
|
|
818
|
+
name,
|
|
819
|
+
...instructions !== void 0 ? { instructions } : {},
|
|
820
|
+
reloaded: false,
|
|
821
|
+
settle: (r) => {
|
|
822
|
+
clearTimeout(timer);
|
|
823
|
+
resolve(r);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
Promise.resolve(
|
|
827
|
+
g.pi.sendUserMessage(`/${commandName(name)}`, { expandPromptTemplates: true })
|
|
828
|
+
).catch((e) => settleCommand(fail(e instanceof Error ? e.message : String(e))));
|
|
829
|
+
});
|
|
830
|
+
}
|
|
606
831
|
function fresh(req) {
|
|
607
832
|
const text = str(req, "text");
|
|
608
833
|
if (text === void 0) return fail("fresh needs a string text");
|
|
609
|
-
if (g.fresh) return fail("a fresh send is already in progress");
|
|
834
|
+
if (g.fresh || g.command) return fail("a fresh send or command is already in progress");
|
|
610
835
|
const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
|
|
611
836
|
if (busy && req.force !== true) return fail("busy");
|
|
612
837
|
return new Promise((resolve) => {
|
|
@@ -648,6 +873,8 @@ function serveCtl(pi) {
|
|
|
648
873
|
}
|
|
649
874
|
case "fresh":
|
|
650
875
|
return fresh(req);
|
|
876
|
+
case "command":
|
|
877
|
+
return command(req);
|
|
651
878
|
case "wait":
|
|
652
879
|
return wait(num(req, "afterRuns"), num(req, "timeoutMs"), conn);
|
|
653
880
|
case "abort":
|
|
@@ -720,7 +947,7 @@ function serveCtl(pi) {
|
|
|
720
947
|
return;
|
|
721
948
|
}
|
|
722
949
|
rmSync(sockPath, { force: true });
|
|
723
|
-
const tmp =
|
|
950
|
+
const tmp = join3(dirname(sockPath), `.${process.pid.toString(36)}${(++bindSeq).toString(36)}`);
|
|
724
951
|
rmSync(tmp, { force: true });
|
|
725
952
|
const s = createServer(onConnection);
|
|
726
953
|
await new Promise((resolve, reject) => {
|
|
@@ -745,8 +972,9 @@ function serveCtl(pi) {
|
|
|
745
972
|
g.server = s;
|
|
746
973
|
g.ino = inodeOf(sockPath);
|
|
747
974
|
}
|
|
748
|
-
pi.on("session_start", async (
|
|
975
|
+
pi.on("session_start", async (e, c) => {
|
|
749
976
|
g.ctx = c;
|
|
977
|
+
if (reasonOf(e) === "reload" && g.command?.name === "reload") g.command.reloaded = true;
|
|
750
978
|
const s = g.server;
|
|
751
979
|
if (s) {
|
|
752
980
|
if (g.ino !== void 0 && inodeOf(sockPath) === g.ino) return;
|
|
@@ -769,18 +997,22 @@ function serveCtl(pi) {
|
|
|
769
997
|
if (!s) return;
|
|
770
998
|
g.waiters.clear();
|
|
771
999
|
settleFresh(fail("pi is shutting down"));
|
|
1000
|
+
settleCommand(fail("pi is shutting down"));
|
|
772
1001
|
for (const c of g.conns) c.destroy();
|
|
773
1002
|
await new Promise((resolve) => s.close(() => resolve()));
|
|
774
1003
|
if (ino !== void 0 && inodeOf(sockPath) === ino) rmSync(sockPath, { force: true });
|
|
775
1004
|
});
|
|
776
1005
|
}
|
|
777
1006
|
export {
|
|
1007
|
+
COMMAND_TIMEOUT_MS,
|
|
778
1008
|
FRESH_COMMAND,
|
|
779
1009
|
FRESH_TIMEOUT_MS,
|
|
780
1010
|
LAST_TEXT_MAX_BYTES,
|
|
781
1011
|
LAST_TEXT_TRUNCATED,
|
|
782
1012
|
LIVE_PROBE_MS,
|
|
1013
|
+
commandName,
|
|
783
1014
|
muPi as default,
|
|
784
|
-
lastAssistantText
|
|
1015
|
+
lastAssistantText,
|
|
1016
|
+
promptsDir
|
|
785
1017
|
};
|
|
786
1018
|
//# sourceMappingURL=mu-pi.js.map
|