@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.
- package/AGENTS.md +208 -599
- package/README.md +125 -329
- package/dist/cli.js +108 -78
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +421 -56
- 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 +176 -1072
- package/docs/ROADMAP.md +88 -169
- package/docs/VISION.md +205 -415
- package/docs/VOCABULARY.md +204 -466
- package/docs/architecture/control-socket.md +133 -0
- package/docs/architecture/dag.md +113 -0
- package/docs/architecture/mux.md +162 -0
- package/docs/architecture/ops-log.md +140 -0
- package/docs/architecture/sdk.md +185 -0
- package/docs/architecture/sync.md +129 -0
- package/docs/architecture/tui.md +146 -0
- package/docs/guide/README.md +30 -0
- package/docs/guide/backends.md +61 -0
- package/docs/guide/cleanup.md +74 -0
- package/docs/guide/delegate.md +87 -0
- package/docs/guide/dispatch.md +144 -0
- package/docs/guide/getting-started.md +180 -0
- package/docs/guide/recovery.md +135 -0
- package/docs/guide/remote.md +74 -0
- package/docs/guide/sql.md +76 -0
- package/docs/guide/stop-a-worker.md +64 -0
- package/docs/guide/sync.md +78 -0
- package/docs/guide/tui.md +98 -0
- package/docs/guide/upgrade.md +76 -0
- package/docs/history/CHANGELOG-pre-3.md +4866 -0
- package/docs/reference/env.md +87 -0
- package/docs/reference/naming.md +82 -0
- package/docs/specs/2026-09-28-agent-state-from-murmur.md +1 -1
- package/docs/specs/2026-09-29-task-substates.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 +126 -255
- 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/remote-workers.md +292 -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/docs/HANDOVER.md +0 -486
- package/docs/USAGE_GUIDE.md +0 -2738
- package/skills/mu/REMOTE_WORKERS.md +0 -617
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { createConnection } from "net";
|
|
|
19
19
|
|
|
20
20
|
// src/ctl/protocol.ts
|
|
21
21
|
var CTL_PROTOCOL_VERSION = 1;
|
|
22
|
+
var CTL_COMMANDS = ["new", "reload", "compact"];
|
|
22
23
|
var UNKNOWN_OP_PREFIX = "unknown op: ";
|
|
23
24
|
function encode(msg) {
|
|
24
25
|
return `${JSON.stringify(msg)}
|
|
@@ -66,7 +67,7 @@ var CtlUnknownOpError = class extends Error {
|
|
|
66
67
|
};
|
|
67
68
|
function clientTimeout(req, override) {
|
|
68
69
|
if (override !== void 0) return override;
|
|
69
|
-
if (req.op === "fresh") return CTL_FRESH_TIMEOUT_MS;
|
|
70
|
+
if (req.op === "fresh" || req.op === "command") return CTL_FRESH_TIMEOUT_MS;
|
|
70
71
|
if (req.op !== "wait") return CTL_DEFAULT_TIMEOUT_MS;
|
|
71
72
|
return req.timeoutMs === void 0 ? void 0 : req.timeoutMs + CTL_WAIT_SLACK_MS;
|
|
72
73
|
}
|
|
@@ -1339,27 +1340,6 @@ async function awaitPaneQuiescence(paneId, budgetMs) {
|
|
|
1339
1340
|
await currentSleep(Math.min(SEND_READINESS_POLL_MS, remaining));
|
|
1340
1341
|
}
|
|
1341
1342
|
}
|
|
1342
|
-
async function awaitNewSession(paneId, baseline, budgetMs) {
|
|
1343
|
-
if (budgetMs <= 0) return true;
|
|
1344
|
-
const deadline = Date.now() + budgetMs;
|
|
1345
|
-
let previous = baseline;
|
|
1346
|
-
let stable = 0;
|
|
1347
|
-
for (; ; ) {
|
|
1348
|
-
const scrollback = await capturePane(paneId, { lines: 50 }).catch(() => void 0);
|
|
1349
|
-
if (scrollback !== void 0 && scrollback !== baseline) {
|
|
1350
|
-
if (scrollback === previous) {
|
|
1351
|
-
stable++;
|
|
1352
|
-
if (stable >= QUIESCENCE_CONFIRMATIONS) return true;
|
|
1353
|
-
} else {
|
|
1354
|
-
stable = 0;
|
|
1355
|
-
}
|
|
1356
|
-
previous = scrollback;
|
|
1357
|
-
}
|
|
1358
|
-
const remaining = deadline - Date.now();
|
|
1359
|
-
if (remaining <= 0) return false;
|
|
1360
|
-
await currentSleep(Math.min(SEND_READINESS_POLL_MS, remaining));
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
1343
|
function pasteProbe(text) {
|
|
1364
1344
|
const firstLine = (text.split("\n")[0] ?? "").trim();
|
|
1365
1345
|
return firstLine.slice(0, 24);
|
|
@@ -1387,7 +1367,6 @@ async function sendToPane(paneId, text, opts = {}) {
|
|
|
1387
1367
|
assertValidPaneId(paneId);
|
|
1388
1368
|
const readinessMs = opts.readinessMs ?? defaultSendReadinessMs();
|
|
1389
1369
|
const quiesced = await awaitPaneQuiescence(paneId, readinessMs);
|
|
1390
|
-
const newSessionBaseline = readinessMs > 0 && text.trim() === "/new" ? await capturePane(paneId, { lines: 50 }).catch(() => void 0) : void 0;
|
|
1391
1370
|
const copyResult = await currentExecutor(["copy-mode", "-q", "-t", paneId]);
|
|
1392
1371
|
if (copyResult.exitCode !== 0 && /can't find pane|no current target/i.test(copyResult.stderr)) {
|
|
1393
1372
|
throw new TmuxError(
|
|
@@ -1410,15 +1389,6 @@ async function sendToPane(paneId, text, opts = {}) {
|
|
|
1410
1389
|
if (delay2 > 0) await currentSleep(delay2);
|
|
1411
1390
|
await tmux(["send-keys", "-t", paneId, "Enter"]);
|
|
1412
1391
|
if (readinessMs <= 0) return;
|
|
1413
|
-
if (newSessionBaseline !== void 0 && !await awaitNewSession(paneId, newSessionBaseline, readinessMs)) {
|
|
1414
|
-
const warn3 = opts.onUndelivered ?? defaultUndeliveredWarning;
|
|
1415
|
-
warn3({
|
|
1416
|
-
paneId,
|
|
1417
|
-
reason: "transition-unconfirmed",
|
|
1418
|
-
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.`
|
|
1419
|
-
});
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
1392
|
const probe = pasteProbe(text);
|
|
1423
1393
|
if (probe.length === 0) return;
|
|
1424
1394
|
for (let attempt = 0; attempt <= SUBMIT_RETRIES; attempt++) {
|
|
@@ -6485,7 +6455,7 @@ async function spawnAgent(db, opts) {
|
|
|
6485
6455
|
agent = created.row;
|
|
6486
6456
|
const startAgent = (await activeMux()).startAgentInPane;
|
|
6487
6457
|
if (startAgent === void 0) {
|
|
6488
|
-
await awaitSpawnLiveness(paneId, opts.name,
|
|
6458
|
+
await awaitSpawnLiveness(paneId, opts.name, command);
|
|
6489
6459
|
} else {
|
|
6490
6460
|
await startAgent({ paneId, name: opts.name, cli, command, commandSource });
|
|
6491
6461
|
}
|
|
@@ -6625,7 +6595,7 @@ function detectSpawnStartupError(scrollback) {
|
|
|
6625
6595
|
}
|
|
6626
6596
|
return void 0;
|
|
6627
6597
|
}
|
|
6628
|
-
async function awaitSpawnLiveness(paneId, agentName,
|
|
6598
|
+
async function awaitSpawnLiveness(paneId, agentName, command) {
|
|
6629
6599
|
const ms = defaultSpawnLivenessMs();
|
|
6630
6600
|
if (ms === 0) return;
|
|
6631
6601
|
await sleep(ms);
|
|
@@ -6640,31 +6610,6 @@ async function awaitSpawnLiveness(paneId, agentName, workstreamName, command) {
|
|
|
6640
6610
|
throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);
|
|
6641
6611
|
}
|
|
6642
6612
|
}
|
|
6643
|
-
await awaitSpawnReadiness(paneId, agentName, workstreamName, command);
|
|
6644
|
-
}
|
|
6645
|
-
function defaultSpawnReadinessMs() {
|
|
6646
|
-
const raw = process.env.MU_SPAWN_READINESS_MS;
|
|
6647
|
-
if (raw === void 0) return 1e4;
|
|
6648
|
-
const parsed = Number.parseInt(raw, 10);
|
|
6649
|
-
if (Number.isNaN(parsed) || parsed < 0) return 1e4;
|
|
6650
|
-
return parsed;
|
|
6651
|
-
}
|
|
6652
|
-
var READINESS_POLL_INTERVAL_MS = 250;
|
|
6653
|
-
async function awaitSpawnReadiness(paneId, agentName, workstreamName, command) {
|
|
6654
|
-
const budgetMs = defaultSpawnReadinessMs();
|
|
6655
|
-
if (budgetMs === 0 || !murmurAvailable()) return;
|
|
6656
|
-
const mux = await activeMux();
|
|
6657
|
-
const deadline = Date.now() + budgetMs;
|
|
6658
|
-
const agent = { name: agentName, workstreamName, paneId };
|
|
6659
|
-
while (Date.now() < deadline) {
|
|
6660
|
-
if (!await mux.paneExists(paneId)) {
|
|
6661
|
-
const scrollback = await mux.capturePane(paneId, { lines: 50 }).catch(() => void 0);
|
|
6662
|
-
throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command);
|
|
6663
|
-
}
|
|
6664
|
-
const reading = (await readAgentStates([agent])).get(agentKey(agent));
|
|
6665
|
-
if (reading?.source === "murmur") return;
|
|
6666
|
-
await sleep(Math.min(READINESS_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
6667
|
-
}
|
|
6668
6613
|
}
|
|
6669
6614
|
async function createOrReusePane(opts) {
|
|
6670
6615
|
const mux = await activeMux();
|
|
@@ -7087,23 +7032,47 @@ var AgentExtensionOutdatedError = class extends Error {
|
|
|
7087
7032
|
}
|
|
7088
7033
|
};
|
|
7089
7034
|
var AgentBusyError = class extends Error {
|
|
7090
|
-
constructor(agentName, workstream) {
|
|
7091
|
-
super(`agent ${agentName} is busy: --fresh would abandon its running turn`);
|
|
7035
|
+
constructor(agentName, workstream, command) {
|
|
7036
|
+
super(`agent ${agentName} is busy: ${command ?? "--fresh"} would abandon its running turn`);
|
|
7092
7037
|
this.agentName = agentName;
|
|
7093
7038
|
this.workstream = workstream;
|
|
7039
|
+
this.command = command;
|
|
7094
7040
|
}
|
|
7095
7041
|
agentName;
|
|
7096
7042
|
workstream;
|
|
7043
|
+
command;
|
|
7097
7044
|
name = "AgentBusyError";
|
|
7098
7045
|
errorNextSteps() {
|
|
7046
|
+
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}`;
|
|
7099
7047
|
return [
|
|
7100
7048
|
{
|
|
7101
7049
|
intent: "Stop the turn first, then retry",
|
|
7102
7050
|
command: `mu agent abort ${this.agentName} -w ${this.workstream}`
|
|
7103
7051
|
},
|
|
7052
|
+
{ intent: "Or abandon the turn in one step", command: retry }
|
|
7053
|
+
];
|
|
7054
|
+
}
|
|
7055
|
+
};
|
|
7056
|
+
var AgentSlashCommandUnsupportedError = class extends Error {
|
|
7057
|
+
constructor(agentName, workstream, text, supported) {
|
|
7058
|
+
super(
|
|
7059
|
+
`agent ${agentName} runs pi: over the control socket mu runs only ${supported.join(", ")}; ${JSON.stringify(text.split("\n")[0])} is not one of them. Pass --via mux to type it into the pane`
|
|
7060
|
+
);
|
|
7061
|
+
this.agentName = agentName;
|
|
7062
|
+
this.workstream = workstream;
|
|
7063
|
+
this.text = text;
|
|
7064
|
+
this.supported = supported;
|
|
7065
|
+
}
|
|
7066
|
+
agentName;
|
|
7067
|
+
workstream;
|
|
7068
|
+
text;
|
|
7069
|
+
supported;
|
|
7070
|
+
name = "AgentSlashCommandUnsupportedError";
|
|
7071
|
+
errorNextSteps() {
|
|
7072
|
+
return [
|
|
7104
7073
|
{
|
|
7105
|
-
intent: "
|
|
7106
|
-
command: `mu agent send ${this.agentName}
|
|
7074
|
+
intent: "Type it into the pane on purpose (tmux paste; unconfirmed)",
|
|
7075
|
+
command: `mu agent send ${this.agentName} '${this.text.split("\n")[0]}' --via mux -w ${this.workstream}`
|
|
7107
7076
|
}
|
|
7108
7077
|
];
|
|
7109
7078
|
}
|
|
@@ -7116,10 +7085,22 @@ function agentCtlSocket(db, agent) {
|
|
|
7116
7085
|
function expectsCtl(agent, sock = ctlSocketPath(agent.workstreamName, agent.name)) {
|
|
7117
7086
|
return speaksMuCtl(agent.cli, resolveCliCommand(agent.cli)) || existsSync8(sock);
|
|
7118
7087
|
}
|
|
7119
|
-
function chooseTransport(agent,
|
|
7120
|
-
if (text.startsWith("/")) return "mux";
|
|
7088
|
+
function chooseTransport(agent, sock) {
|
|
7121
7089
|
return expectsCtl(agent, sock) ? "ctl" : "mux";
|
|
7122
7090
|
}
|
|
7091
|
+
var CTL_SLASH_COMMANDS = CTL_COMMANDS.map((n) => `/${n}`);
|
|
7092
|
+
function parseSessionCommand(text) {
|
|
7093
|
+
const t = text.trim();
|
|
7094
|
+
if (t === "/new") return { name: "new" };
|
|
7095
|
+
if (t === "/reload") return { name: "reload" };
|
|
7096
|
+
const m = /^\/compact(?:\s+([\s\S]*))?$/.exec(t);
|
|
7097
|
+
if (!m) return void 0;
|
|
7098
|
+
const instructions = m[1]?.trim();
|
|
7099
|
+
return instructions ? { name: "compact", instructions } : { name: "compact" };
|
|
7100
|
+
}
|
|
7101
|
+
function isSlashCommand(text) {
|
|
7102
|
+
return /^\/[A-Za-z][\w:-]*(?:\s|$)/.test(text.trimStart());
|
|
7103
|
+
}
|
|
7123
7104
|
async function extensionOutdated(agent, sock, e) {
|
|
7124
7105
|
let extVersion;
|
|
7125
7106
|
try {
|
|
@@ -7148,11 +7129,23 @@ async function sendViaTransport(agent, text, opts) {
|
|
|
7148
7129
|
if (opts.fresh && (opts.via === "mux" || !expectsCtl(agent, sock))) {
|
|
7149
7130
|
throw new AgentFreshNeedsCtlError(agent.name, agent.workstreamName, agent.cli);
|
|
7150
7131
|
}
|
|
7151
|
-
const transport = opts.fresh ? "ctl" : opts.via ?? chooseTransport(agent,
|
|
7132
|
+
const transport = opts.fresh ? "ctl" : opts.via ?? chooseTransport(agent, sock);
|
|
7152
7133
|
if (transport === "mux") {
|
|
7153
7134
|
await (await activeMux()).sendToPane(agent.paneId, text, opts);
|
|
7154
7135
|
return { transport };
|
|
7155
7136
|
}
|
|
7137
|
+
if (!opts.fresh) {
|
|
7138
|
+
const cmd = parseSessionCommand(text);
|
|
7139
|
+
if (cmd) return runSessionCommand(agent, sock, cmd, opts.force === true);
|
|
7140
|
+
if (isSlashCommand(text)) {
|
|
7141
|
+
throw new AgentSlashCommandUnsupportedError(
|
|
7142
|
+
agent.name,
|
|
7143
|
+
agent.workstreamName,
|
|
7144
|
+
text,
|
|
7145
|
+
CTL_SLASH_COMMANDS
|
|
7146
|
+
);
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7156
7149
|
const reply = await ctlRequestFor(
|
|
7157
7150
|
agent,
|
|
7158
7151
|
sock,
|
|
@@ -7166,6 +7159,25 @@ async function sendViaTransport(agent, text, opts) {
|
|
|
7166
7159
|
}
|
|
7167
7160
|
return reply.state === void 0 ? { transport } : { transport, state: reply.state };
|
|
7168
7161
|
}
|
|
7162
|
+
async function runSessionCommand(agent, sock, cmd, force) {
|
|
7163
|
+
const reply = await ctlRequestFor(agent, sock, {
|
|
7164
|
+
op: "command",
|
|
7165
|
+
name: cmd.name,
|
|
7166
|
+
...cmd.instructions !== void 0 ? { instructions: cmd.instructions } : {},
|
|
7167
|
+
...force ? { force: true } : {}
|
|
7168
|
+
});
|
|
7169
|
+
if (!reply.ok) {
|
|
7170
|
+
if (reply.error === "busy") {
|
|
7171
|
+
throw new AgentBusyError(agent.name, agent.workstreamName, `/${cmd.name}`);
|
|
7172
|
+
}
|
|
7173
|
+
throw new Error(`pi refused /${cmd.name}: ${reply.error}`);
|
|
7174
|
+
}
|
|
7175
|
+
return {
|
|
7176
|
+
transport: "ctl",
|
|
7177
|
+
command: cmd.name,
|
|
7178
|
+
...reply.state !== void 0 ? { state: reply.state } : {}
|
|
7179
|
+
};
|
|
7180
|
+
}
|
|
7169
7181
|
|
|
7170
7182
|
// src/agent-state.ts
|
|
7171
7183
|
var CTL_STATE_PROBE_MS = 1e3;
|
|
@@ -8948,6 +8960,15 @@ function murmurDoctorCheck(agentStateSource, opts = {}) {
|
|
|
8948
8960
|
};
|
|
8949
8961
|
}
|
|
8950
8962
|
var RELINK = "run mu link pi";
|
|
8963
|
+
function extFeatures(env) {
|
|
8964
|
+
const features = [
|
|
8965
|
+
["mu_delegate", "MU_DELEGATE"],
|
|
8966
|
+
["nudge", "MU_NUDGE"]
|
|
8967
|
+
];
|
|
8968
|
+
const on = ["ctl", ...features.filter(([, v]) => env[v] !== "0").map(([f]) => f)];
|
|
8969
|
+
const off = features.filter(([, v]) => env[v] === "0").map(([f, v]) => `${f} disabled by ${v}=0`);
|
|
8970
|
+
return `(${on.join(", ")}${off.length ? `; ${off.join("; ")}` : ""})`;
|
|
8971
|
+
}
|
|
8951
8972
|
function ctlExtensionDoctorCheck(opts) {
|
|
8952
8973
|
const { extension } = inspectLinks(opts);
|
|
8953
8974
|
switch (extension.state) {
|
|
@@ -8955,7 +8976,7 @@ function ctlExtensionDoctorCheck(opts) {
|
|
|
8955
8976
|
return {
|
|
8956
8977
|
name: "mu ext",
|
|
8957
8978
|
status: "ok",
|
|
8958
|
-
detail: process.env
|
|
8979
|
+
detail: extFeatures(process.env)
|
|
8959
8980
|
};
|
|
8960
8981
|
case "stale-copy":
|
|
8961
8982
|
return { name: "mu ext", status: "warn", detail: `stale copy: ${RELINK}` };
|
|
@@ -10919,6 +10940,7 @@ export {
|
|
|
10919
10940
|
AgentFreshNeedsCtlError,
|
|
10920
10941
|
AgentNotFoundError,
|
|
10921
10942
|
AgentNotInWorkstreamError,
|
|
10943
|
+
AgentSlashCommandUnsupportedError,
|
|
10922
10944
|
AgentSpawnCliNotFoundError,
|
|
10923
10945
|
AgentSpawnStartupError,
|
|
10924
10946
|
CAPTURE_TRIGGER_DDL,
|