@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/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,
|
|
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,
|
|
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
|
|
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: "
|
|
5048
|
-
command: `mu agent send ${this.agentName}
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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)
|
|
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(
|
|
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 ||
|
|
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
|