@mu-crew/mu 3.6.2 → 3.8.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/dist/cli.js +12652 -12264
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +302 -48
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +59 -13
- package/dist/index.js +1376 -1205
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/architecture/control-socket.md +10 -5
- package/docs/architecture/mux.md +4 -1
- package/docs/guide/delegate.md +25 -3
- package/docs/guide/dispatch.md +22 -5
- package/docs/guide/getting-started.md +2 -2
- package/docs/guide/recovery.md +11 -6
- package/docs/guide/upgrade.md +4 -4
- package/docs/reference/env.md +2 -2
- package/docs/specs/2026-10-03-keep-driving-nudge.md +21 -0
- package/package.json +1 -1
- package/skills/mu/SKILL.md +40 -16
- package/skills/mu/recipes/adversarial-review.md +14 -6
- package/skills/mu/recipes/brief.md +29 -1
- package/skills/mu/recipes/codemode-driver.md +19 -9
- package/skills/mu/recipes/deep-research.md +6 -5
- package/skills/mu/recipes/drift-audit.md +72 -0
- package/skills/mu/recipes/findings.md +24 -15
- package/skills/mu/recipes/hypothesis-panel.md +5 -3
- package/skills/mu/recipes/long-run.md +13 -3
- package/skills/mu/recipes/loop-until-done.md +4 -3
- package/skills/mu/recipes/orchestrator-loop.md +29 -16
- package/skills/mu/recipes/plan.md +10 -6
- package/skills/mu/recipes/refute.md +3 -2
- package/skills/mu/recipes/remote-workers.md +23 -6
- package/skills/mu/recipes/review-panel.md +2 -2
- package/skills/mu/recipes/rules-audit.md +10 -5
- package/skills/mu/recipes/tasks-or-calls.md +40 -23
- package/skills/mu/recipes/tournament.md +4 -3
- package/skills/mu/recipes/ultrathink.md +2 -2
- package/skills/mu/recipes/worker.md +3 -2
package/dist/extension/mu-pi.js
CHANGED
|
@@ -23,6 +23,7 @@ var CTL_OPS = [
|
|
|
23
23
|
"send",
|
|
24
24
|
"wait",
|
|
25
25
|
"abort",
|
|
26
|
+
"interrupt",
|
|
26
27
|
"fresh",
|
|
27
28
|
"command"
|
|
28
29
|
];
|
|
@@ -152,6 +153,23 @@ ${agent.lastText ?? ""}`;
|
|
|
152
153
|
return `${who}: mu agent wait gave no result. ${fate}${ws}${tailBlock}`;
|
|
153
154
|
}
|
|
154
155
|
}
|
|
156
|
+
var RECORD_NOTE_MAX = 4e3;
|
|
157
|
+
var RECORD_FALLBACK_TAIL = 1500;
|
|
158
|
+
var VERDICT_LINE = /^[\s>*_`-]*VERDICT:/;
|
|
159
|
+
var EVIDENCE_LINE = /^[\s>*_`-]*EVIDENCE:/;
|
|
160
|
+
function recordNote(label, name, outcome, lastText, elapsedMs) {
|
|
161
|
+
if (outcome !== "done" || !lastText?.trim())
|
|
162
|
+
return `REFUTER ${label}: no verdict (${outcome === "done" ? "empty" : outcome ?? "no result"})`;
|
|
163
|
+
const lines = lastText.trimEnd().split("\n");
|
|
164
|
+
let at = -1;
|
|
165
|
+
for (let i = lines.length - 1; i >= 0 && at < 0; i--)
|
|
166
|
+
if (VERDICT_LINE.test(lines[i] ?? "")) at = i;
|
|
167
|
+
const body = at >= 0 ? [lines[at] ?? "", ...lines.slice(at + 1).filter((l) => EVIDENCE_LINE.test(l))].join("\n") : `NO VERDICT LINE: ${lastText.trimEnd().slice(-RECORD_FALLBACK_TAIL)}`;
|
|
168
|
+
const note = `REFUTER ${label} (${name}, ${formatElapsed(elapsedMs)}):
|
|
169
|
+
${body}`;
|
|
170
|
+
const mark = "\n[truncated]";
|
|
171
|
+
return note.length <= RECORD_NOTE_MAX ? note : note.slice(0, RECORD_NOTE_MAX - mark.length) + mark;
|
|
172
|
+
}
|
|
155
173
|
function labelStem(label) {
|
|
156
174
|
if (typeof label !== "string") return void 0;
|
|
157
175
|
const stem = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 20).replace(/-+$/, "");
|
|
@@ -200,7 +218,7 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
200
218
|
const q = queue.shift();
|
|
201
219
|
if (!q) break;
|
|
202
220
|
starting++;
|
|
203
|
-
void start(q.params, void 0, q.ctx, q.handle).then(
|
|
221
|
+
void start(q.params, void 0, q.ctx, q.handle, q.record).then(
|
|
204
222
|
() => {
|
|
205
223
|
},
|
|
206
224
|
(e) => {
|
|
@@ -288,7 +306,35 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
288
306
|
};
|
|
289
307
|
}
|
|
290
308
|
}
|
|
291
|
-
|
|
309
|
+
const recordLabel = (params, name) => typeof params.label === "string" && params.label.trim() ? params.label.trim() : name;
|
|
310
|
+
async function resolveRecord(raw) {
|
|
311
|
+
if (raw === void 0) return void 0;
|
|
312
|
+
const r = raw;
|
|
313
|
+
const task = typeof r?.task === "string" ? r.task.trim() : "";
|
|
314
|
+
if (!task) throw new Error("mu_delegate: record.task must name a task");
|
|
315
|
+
const ws = typeof r?.workstream === "string" ? r.workstream.trim() : "";
|
|
316
|
+
const shown = await mu(["task", "show", task, ...ws ? ["-w", ws] : [], "--json"]);
|
|
317
|
+
const out = json(shown);
|
|
318
|
+
const t = out?.task;
|
|
319
|
+
if (shown.code !== 0 || typeof t?.name !== "string" || typeof t.workstreamName !== "string") {
|
|
320
|
+
const why = typeof out?.message === "string" ? out.message : (shown.stderr || shown.stdout).trim();
|
|
321
|
+
throw new Error(
|
|
322
|
+
`mu_delegate: record task ${ws ? `${ws}/` : ""}${task} not found (exit ${shown.code}): ${why}. Nothing spawned.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
return { workstream: t.workstreamName, task: t.name };
|
|
326
|
+
}
|
|
327
|
+
async function writeRecord(rec, name, text) {
|
|
328
|
+
const where = `${rec.workstream}/${rec.task}`;
|
|
329
|
+
try {
|
|
330
|
+
const r = await mu(["task", "note", rec.task, "-w", rec.workstream, "--author", name, text]);
|
|
331
|
+
if (r.code === 0) return `Recorded on ${where} as a note.`;
|
|
332
|
+
return `Recording on ${where} failed (exit ${r.code}): ${(r.stderr || r.stdout).trim()}. Record it yourself.`;
|
|
333
|
+
} catch (e) {
|
|
334
|
+
return `Recording on ${where} failed: ${e instanceof Error ? e.message : String(e)}. Record it yourself.`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function deliver(name, wait, keep, extras, record) {
|
|
292
338
|
const agent = json(wait)?.agents?.[0];
|
|
293
339
|
const outcome = agent?.outcome;
|
|
294
340
|
const tail = outcome === "done" || outcome === "timeout" || outcome === "pending" || outcome === "error" ? void 0 : (await mu(["agent", "read", name, "-n", "50", "-w", W])).stdout;
|
|
@@ -303,10 +349,18 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
303
349
|
failed.add(name);
|
|
304
350
|
showStatus();
|
|
305
351
|
}
|
|
352
|
+
const recorded = record ? await writeRecord(
|
|
353
|
+
record.target,
|
|
354
|
+
name,
|
|
355
|
+
recordNote(record.label, name, outcome, agent?.lastText, extras.elapsedMs ?? 0)
|
|
356
|
+
) : void 0;
|
|
357
|
+
const message = delegateMessage(name, agent, pane, { ...extras, ...tail ? { tail } : {} });
|
|
306
358
|
await pi.sendMessage(
|
|
307
359
|
{
|
|
308
360
|
customType: DELEGATE_MESSAGE_TYPE,
|
|
309
|
-
content:
|
|
361
|
+
content: recorded ? `${message}
|
|
362
|
+
|
|
363
|
+
${recorded}` : message,
|
|
310
364
|
display: true,
|
|
311
365
|
details: {
|
|
312
366
|
name,
|
|
@@ -321,7 +375,7 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
321
375
|
{ deliverAs: "followUp", triggerTurn: true }
|
|
322
376
|
);
|
|
323
377
|
}
|
|
324
|
-
async function start(params, signal, ctx, queuedAs) {
|
|
378
|
+
async function start(params, signal, ctx, queuedAs, record) {
|
|
325
379
|
let slotHeld = true;
|
|
326
380
|
const freeSlot = () => {
|
|
327
381
|
if (slotHeld) {
|
|
@@ -347,35 +401,68 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
347
401
|
if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
|
|
348
402
|
}
|
|
349
403
|
const { name, attach, workspace } = await spawn(params, cwd, signal);
|
|
350
|
-
const entry = {
|
|
404
|
+
const entry = {
|
|
405
|
+
abort: new AbortController(),
|
|
406
|
+
cancelling: false,
|
|
407
|
+
...record ? { record: { target: record, label: recordLabel(params, name) } } : {}
|
|
408
|
+
};
|
|
351
409
|
inflight.set(name, entry);
|
|
352
410
|
freeSlot();
|
|
353
411
|
showStatus();
|
|
354
412
|
const started = Date.now();
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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}
|
|
360
432
|
|
|
361
|
-
${task}` : task,
|
|
433
|
+
${task}` : task,
|
|
434
|
+
"-w",
|
|
435
|
+
W,
|
|
436
|
+
"--json"
|
|
437
|
+
]);
|
|
362
438
|
if (sent.code !== 0) {
|
|
363
|
-
|
|
439
|
+
plain.abort();
|
|
364
440
|
forget(name);
|
|
365
441
|
const c = await mu(["agent", "close", name, "-w", W]);
|
|
366
442
|
const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
|
|
367
443
|
throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
|
|
368
444
|
}
|
|
445
|
+
const runs = json(sent)?.runs;
|
|
446
|
+
if (typeof runs === "number") {
|
|
447
|
+
plain.abort();
|
|
448
|
+
waiting = mu(waitArgs(runs), entry.abort.signal);
|
|
449
|
+
}
|
|
369
450
|
const settle = async (r) => {
|
|
370
451
|
forget(name);
|
|
371
452
|
try {
|
|
372
|
-
await deliver(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
453
|
+
await deliver(
|
|
454
|
+
name,
|
|
455
|
+
r,
|
|
456
|
+
keep,
|
|
457
|
+
{
|
|
458
|
+
timeoutS,
|
|
459
|
+
elapsedMs: Date.now() - started,
|
|
460
|
+
...queuedAs ? { queuedAs } : {},
|
|
461
|
+
...attach ? { attach } : {},
|
|
462
|
+
...workspace ? { workspace } : {}
|
|
463
|
+
},
|
|
464
|
+
entry.record
|
|
465
|
+
);
|
|
379
466
|
} catch (e) {
|
|
380
467
|
await Promise.resolve(
|
|
381
468
|
pi.sendMessage(
|
|
@@ -420,7 +507,7 @@ ${task}` : task, "-w", W]);
|
|
|
420
507
|
pi.registerTool({
|
|
421
508
|
name: DELEGATE_TOOL,
|
|
422
509
|
label: "mu delegate",
|
|
423
|
-
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()} run at once; up to ${DELEGATE_QUEUE_FACTOR * delegateMax()} more are queued and start as slots free). 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.`,
|
|
510
|
+
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, or a refuter of a claim, plan or fix before you act on it; call it several times to fan out in parallel (at most ${delegateMax()} run at once; up to ${DELEGATE_QUEUE_FACTOR * delegateMax()} more are queued and start as slots free). 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. When the check judges a mu task, set record so its verdict lands on that task; leave it out when there is no task.`,
|
|
424
511
|
promptSnippet: "Subagent: delegate a self-contained task to a background pi agent; its answer arrives later as a follow-up",
|
|
425
512
|
parameters: {
|
|
426
513
|
type: "object",
|
|
@@ -454,6 +541,16 @@ ${task}` : task, "-w", W]);
|
|
|
454
541
|
keep: {
|
|
455
542
|
type: "boolean",
|
|
456
543
|
description: "Keep the pane after it finishes, to talk to it again"
|
|
544
|
+
},
|
|
545
|
+
record: {
|
|
546
|
+
type: "object",
|
|
547
|
+
description: "When the answer arrives, write its VERDICT/EVIDENCE onto this task as a note (for a check that judges a mu task; omit with no task); task may be <ws>/<task>.",
|
|
548
|
+
properties: {
|
|
549
|
+
task: { type: "string" },
|
|
550
|
+
workstream: { type: "string" }
|
|
551
|
+
},
|
|
552
|
+
required: ["task"],
|
|
553
|
+
additionalProperties: false
|
|
457
554
|
}
|
|
458
555
|
},
|
|
459
556
|
required: ["task"],
|
|
@@ -472,10 +569,11 @@ ${task}` : task, "-w", W]);
|
|
|
472
569
|
const cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
|
|
473
570
|
if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
|
|
474
571
|
}
|
|
572
|
+
const record = await resolveRecord(params.record);
|
|
475
573
|
const max = delegateMax();
|
|
476
574
|
if (inflight.size + starting < max) {
|
|
477
575
|
starting++;
|
|
478
|
-
return start(params, signal, ctx);
|
|
576
|
+
return start(params, signal, ctx, void 0, record);
|
|
479
577
|
}
|
|
480
578
|
if (queue.length >= DELEGATE_QUEUE_FACTOR * max)
|
|
481
579
|
throw new Error(
|
|
@@ -488,7 +586,8 @@ ${task}` : task, "-w", W]);
|
|
|
488
586
|
...params,
|
|
489
587
|
cwd: typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd()
|
|
490
588
|
},
|
|
491
|
-
ctx
|
|
589
|
+
ctx,
|
|
590
|
+
...record ? { record } : {}
|
|
492
591
|
});
|
|
493
592
|
showStatus();
|
|
494
593
|
return {
|
|
@@ -521,10 +620,13 @@ ${task}` : task, "-w", W]);
|
|
|
521
620
|
if (!name) throw new Error("mu_delegate_cancel needs a name");
|
|
522
621
|
const qi = queue.findIndex((q) => q.handle === name);
|
|
523
622
|
if (qi >= 0) {
|
|
524
|
-
queue.splice(qi, 1);
|
|
623
|
+
const [q] = queue.splice(qi, 1);
|
|
525
624
|
showStatus();
|
|
625
|
+
const recorded = q?.record ? ` ${await writeRecord(q.record, name, recordNote(recordLabel(q.params, name), name, "cancelled", void 0, 0))}` : "";
|
|
526
626
|
return {
|
|
527
|
-
content: [
|
|
627
|
+
content: [
|
|
628
|
+
{ type: "text", text: `Dropped ${name} from the queue; it never started.${recorded}` }
|
|
629
|
+
],
|
|
528
630
|
details: { name, workstream: W }
|
|
529
631
|
};
|
|
530
632
|
}
|
|
@@ -546,6 +648,8 @@ ${task}` : task, "-w", W]);
|
|
|
546
648
|
}
|
|
547
649
|
failed.delete(name);
|
|
548
650
|
let text = `Aborted ${W}/${name}.`;
|
|
651
|
+
if (entry?.record)
|
|
652
|
+
text += ` ${await writeRecord(entry.record.target, name, recordNote(entry.record.label, name, "cancelled", void 0, 0))}`;
|
|
549
653
|
if (params.keep === true) text += " Pane kept.";
|
|
550
654
|
else {
|
|
551
655
|
const c = await mu(["agent", "close", name, "-w", W]);
|
|
@@ -582,9 +686,14 @@ import { join as join2 } from "path";
|
|
|
582
686
|
var NUDGE_MESSAGE_TYPE = "mu-keep-driving";
|
|
583
687
|
var NUDGE_LOG_KIND = "nudge";
|
|
584
688
|
var CLOSE_NUDGE_MESSAGE_TYPE = "mu-close-task";
|
|
689
|
+
var REFUTE_NUDGE_MESSAGE_TYPE = "mu-refute-brief";
|
|
585
690
|
var MARK_START = "<!-- mu:keep-driving -->";
|
|
586
691
|
var MARK_END = "<!-- /mu:keep-driving -->";
|
|
587
692
|
var WAIT_HINT_MAX = 8;
|
|
693
|
+
var notNested = () => false;
|
|
694
|
+
function noUI(ctx) {
|
|
695
|
+
return ctx?.hasUI === false;
|
|
696
|
+
}
|
|
588
697
|
function nudgeEnabled(env = process.env) {
|
|
589
698
|
return env.MU_NUDGE !== "0";
|
|
590
699
|
}
|
|
@@ -604,8 +713,7 @@ function readSkill() {
|
|
|
604
713
|
}
|
|
605
714
|
function dispatchedWorkstreams(command) {
|
|
606
715
|
const found = /* @__PURE__ */ new Set();
|
|
607
|
-
for (const
|
|
608
|
-
const words = seg.trim().split(/\s+/);
|
|
716
|
+
for (const words of shellSegments(command)) {
|
|
609
717
|
const at = words.findIndex((w) => w === "mu" || w.endsWith("/mu"));
|
|
610
718
|
if (at < 0) continue;
|
|
611
719
|
const [noun, verb] = [words[at + 1], words[at + 2]];
|
|
@@ -615,19 +723,78 @@ function dispatchedWorkstreams(command) {
|
|
|
615
723
|
let ws = "";
|
|
616
724
|
for (let k = at + 3; k < words.length; k++) {
|
|
617
725
|
const w = words[k] ?? "";
|
|
618
|
-
if (w === "-w" || w === "--workstream") ws =
|
|
619
|
-
else if (w.startsWith("--workstream=")) ws =
|
|
726
|
+
if (w === "-w" || w === "--workstream") ws = words[k + 1] ?? "";
|
|
727
|
+
else if (w.startsWith("--workstream=")) ws = w.slice("--workstream=".length);
|
|
620
728
|
}
|
|
621
729
|
if (ws === "" && isClaimFor) {
|
|
622
730
|
const ref = words[at + 3] ?? "";
|
|
623
|
-
if (ref.includes("/")) ws =
|
|
731
|
+
if (ref.includes("/")) ws = ref.split("/")[0] ?? "";
|
|
624
732
|
}
|
|
625
733
|
if (ws !== "scratch") found.add(ws);
|
|
626
734
|
}
|
|
627
735
|
return found.size > 0 ? [...found] : void 0;
|
|
628
736
|
}
|
|
629
|
-
|
|
630
|
-
|
|
737
|
+
var CLAIM_VALUE_OPTS = /* @__PURE__ */ new Set(["--for", "-f", "-w", "--workstream", "--evidence", "--actor"]);
|
|
738
|
+
function shellSegments(command) {
|
|
739
|
+
const segs = [];
|
|
740
|
+
let words = [];
|
|
741
|
+
let word;
|
|
742
|
+
const endWord = () => {
|
|
743
|
+
if (word !== void 0) words.push(word);
|
|
744
|
+
word = void 0;
|
|
745
|
+
};
|
|
746
|
+
const endSeg = () => {
|
|
747
|
+
endWord();
|
|
748
|
+
if (words.length > 0) segs.push(words);
|
|
749
|
+
words = [];
|
|
750
|
+
};
|
|
751
|
+
for (let i = 0; i < command.length; i++) {
|
|
752
|
+
const c = command[i] ?? "";
|
|
753
|
+
if (c === "'" || c === '"') {
|
|
754
|
+
const close = command.indexOf(c, i + 1);
|
|
755
|
+
const end = close < 0 ? command.length : close;
|
|
756
|
+
word = (word ?? "") + command.slice(i + 1, end);
|
|
757
|
+
i = end;
|
|
758
|
+
} else if (c === "\\" && i + 1 < command.length) {
|
|
759
|
+
word = (word ?? "") + command[++i];
|
|
760
|
+
} else if (c === ";" || c === "|" || c === "\n" || c === "&" && command[i + 1] === "&") {
|
|
761
|
+
if (c !== ";" && c !== "\n" && command[i + 1] === c) i++;
|
|
762
|
+
endSeg();
|
|
763
|
+
} else if (/\s/.test(c)) {
|
|
764
|
+
endWord();
|
|
765
|
+
} else {
|
|
766
|
+
word = (word ?? "") + c;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
endSeg();
|
|
770
|
+
return segs;
|
|
771
|
+
}
|
|
772
|
+
function dispatchedTasks(command) {
|
|
773
|
+
const out = [];
|
|
774
|
+
for (const words of shellSegments(command)) {
|
|
775
|
+
if (words[0] !== "mu" && !words[0]?.endsWith("/mu")) continue;
|
|
776
|
+
if (words[1] !== "task" || words[2] !== "claim") continue;
|
|
777
|
+
let ws = "";
|
|
778
|
+
let id;
|
|
779
|
+
let isFor = false;
|
|
780
|
+
for (let k = 3; k < words.length; k++) {
|
|
781
|
+
const w = words[k] ?? "";
|
|
782
|
+
const [opt, eq] = w.startsWith("--") && w.includes("=") ? w.split("=", 2) : [w, void 0];
|
|
783
|
+
if (opt === "--for" || opt === "-f") isFor = true;
|
|
784
|
+
if (opt === "-w" || opt === "--workstream") ws = eq ?? words[k + 1] ?? "";
|
|
785
|
+
if (CLAIM_VALUE_OPTS.has(opt ?? "")) {
|
|
786
|
+
if (eq === void 0) k++;
|
|
787
|
+
} else if (id === void 0 && !w.startsWith("-")) id = w;
|
|
788
|
+
}
|
|
789
|
+
if (!isFor || id === void 0) continue;
|
|
790
|
+
const slash = id.indexOf("/");
|
|
791
|
+
if (slash >= 0) {
|
|
792
|
+
if (ws === "") ws = id.slice(0, slash);
|
|
793
|
+
id = id.slice(slash + 1);
|
|
794
|
+
}
|
|
795
|
+
if (ws !== "scratch") out.push({ ws, id });
|
|
796
|
+
}
|
|
797
|
+
return out;
|
|
631
798
|
}
|
|
632
799
|
async function inProgress(run, workstreams) {
|
|
633
800
|
const named = workstreams.filter((w) => w !== "");
|
|
@@ -667,7 +834,7 @@ function bashCommand(event) {
|
|
|
667
834
|
const e = event;
|
|
668
835
|
return e.toolName === "bash" && typeof e.input?.command === "string" ? e.input.command : void 0;
|
|
669
836
|
}
|
|
670
|
-
function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
837
|
+
function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested = notNested) {
|
|
671
838
|
if (!nudgeEnabled()) return;
|
|
672
839
|
const rule = skillMd === void 0 ? void 0 : keepDrivingRule(skillMd);
|
|
673
840
|
if (rule === void 0) return;
|
|
@@ -683,7 +850,7 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
|
683
850
|
for (const ws of dispatchedWorkstreams(cmd) ?? []) armed.add(ws);
|
|
684
851
|
});
|
|
685
852
|
pi.on("agent_before_settle", async (event) => {
|
|
686
|
-
if (fired || armed.size === 0) return;
|
|
853
|
+
if (fired || armed.size === 0 || nested()) return;
|
|
687
854
|
if (outcomeOf(event) !== "completed") return;
|
|
688
855
|
fired = true;
|
|
689
856
|
const refs = await inProgress(run, [...armed]);
|
|
@@ -732,7 +899,7 @@ function closeNudgeText(workstream, names) {
|
|
|
732
899
|
"If you are blocked or need an answer, say so in one line and stop; the orchestrator will read your pane."
|
|
733
900
|
].join("\n");
|
|
734
901
|
}
|
|
735
|
-
function registerCloseNudge(pi, run = defaultRunner(), env = process.env) {
|
|
902
|
+
function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested = notNested) {
|
|
736
903
|
if (!nudgeEnabled(env)) return;
|
|
737
904
|
const me = workerIdentity(env);
|
|
738
905
|
if (me === void 0) return;
|
|
@@ -740,8 +907,8 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env) {
|
|
|
740
907
|
pi.on("input", () => {
|
|
741
908
|
fired = false;
|
|
742
909
|
});
|
|
743
|
-
pi.on("agent_before_settle", async (event) => {
|
|
744
|
-
if (fired) return;
|
|
910
|
+
pi.on("agent_before_settle", async (event, ctx) => {
|
|
911
|
+
if (fired || nested() || noUI(ctx)) return;
|
|
745
912
|
if (outcomeOf(event) !== "completed") return;
|
|
746
913
|
fired = true;
|
|
747
914
|
const names = await ownedInProgress(run, me.agent, me.workstream);
|
|
@@ -767,9 +934,67 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env) {
|
|
|
767
934
|
};
|
|
768
935
|
});
|
|
769
936
|
}
|
|
937
|
+
var REFUTE_MARK = /^(REFUTER |VERDICT:|REFUTE-EXEMPT:)/m;
|
|
938
|
+
async function briefRefuted(run, ws, id) {
|
|
939
|
+
const r = await run(["task", "notes", id, ...ws ? ["-w", ws] : [], "--json"]);
|
|
940
|
+
if (r.code !== 0) return void 0;
|
|
941
|
+
try {
|
|
942
|
+
const json = JSON.parse(r.stdout);
|
|
943
|
+
return (json.items ?? []).some((n) => REFUTE_MARK.test(n.content ?? ""));
|
|
944
|
+
} catch {
|
|
945
|
+
return void 0;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function refuteNudgeText(refs) {
|
|
949
|
+
return `[mu] dispatched ${refs.join(", ")} with an unrefuted brief: refute it (delegate, record) or note REFUTE-EXEMPT: <why>`;
|
|
950
|
+
}
|
|
951
|
+
function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested) {
|
|
952
|
+
if (!nudgeEnabled()) return;
|
|
953
|
+
const armed = /* @__PURE__ */ new Map();
|
|
954
|
+
let fired = false;
|
|
955
|
+
pi.on("input", () => {
|
|
956
|
+
armed.clear();
|
|
957
|
+
fired = false;
|
|
958
|
+
});
|
|
959
|
+
pi.on("tool_call", (event) => {
|
|
960
|
+
const cmd = bashCommand(event);
|
|
961
|
+
if (cmd === void 0) return;
|
|
962
|
+
for (const t of dispatchedTasks(cmd)) {
|
|
963
|
+
if (!t.id.startsWith("review_")) armed.set(`${t.ws}/${t.id}`, t);
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
pi.on("agent_before_settle", async (event, ctx) => {
|
|
967
|
+
if (fired || armed.size === 0 || nested() || noUI(ctx)) return;
|
|
968
|
+
if (outcomeOf(event) !== "completed") return;
|
|
969
|
+
fired = true;
|
|
970
|
+
const refs = [];
|
|
971
|
+
for (const [ref, t] of armed) {
|
|
972
|
+
if (await briefRefuted(run, t.ws, t.id) === false) refs.push(ref);
|
|
973
|
+
}
|
|
974
|
+
if (refs.length === 0) return;
|
|
975
|
+
const logWs = [...armed.values()].find((t) => t.ws !== "")?.ws;
|
|
976
|
+
await run([
|
|
977
|
+
"log",
|
|
978
|
+
...logWs ? ["-w", logWs] : [],
|
|
979
|
+
"--kind",
|
|
980
|
+
NUDGE_LOG_KIND,
|
|
981
|
+
`refute: ${refs.join(", ")}`
|
|
982
|
+
]);
|
|
983
|
+
return {
|
|
984
|
+
entries: [
|
|
985
|
+
{
|
|
986
|
+
type: "custom_message",
|
|
987
|
+
customType: REFUTE_NUDGE_MESSAGE_TYPE,
|
|
988
|
+
content: refuteNudgeText(refs),
|
|
989
|
+
display: true
|
|
990
|
+
}
|
|
991
|
+
]
|
|
992
|
+
};
|
|
993
|
+
});
|
|
994
|
+
}
|
|
770
995
|
|
|
771
996
|
// extension/mu-pi.ts
|
|
772
|
-
var EXT_VERSION = true ? "3.
|
|
997
|
+
var EXT_VERSION = true ? "3.8.0" : void 0;
|
|
773
998
|
var FRESH_COMMAND = "mu-fresh";
|
|
774
999
|
var FRESH_TIMEOUT_MS = 3e4;
|
|
775
1000
|
function commandName(name) {
|
|
@@ -908,16 +1133,17 @@ function registerPrompts(pi) {
|
|
|
908
1133
|
return { promptPaths: paths };
|
|
909
1134
|
});
|
|
910
1135
|
}
|
|
911
|
-
function muPi(pi) {
|
|
912
|
-
registerDelegate(pi);
|
|
1136
|
+
function muPi(pi, run = defaultRunner()) {
|
|
1137
|
+
registerDelegate(pi, run);
|
|
913
1138
|
registerPrompts(pi);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1139
|
+
const nested = serveCtl(pi);
|
|
1140
|
+
registerNudge(pi, run, void 0, nested);
|
|
1141
|
+
registerCloseNudge(pi, run, process.env, nested);
|
|
1142
|
+
registerRefuteNudge(pi, run, nested);
|
|
917
1143
|
}
|
|
918
1144
|
function serveCtl(pi) {
|
|
919
1145
|
const sock = process.env.MU_CTL_SOCK;
|
|
920
|
-
if (!sock) return;
|
|
1146
|
+
if (!sock) return () => false;
|
|
921
1147
|
const sockPath = sock;
|
|
922
1148
|
const piVersion = detectPiVersion();
|
|
923
1149
|
const map = sharedMap();
|
|
@@ -1092,9 +1318,10 @@ function serveCtl(pi) {
|
|
|
1092
1318
|
const text = str(req, "text");
|
|
1093
1319
|
if (text === void 0) return fail("send needs a string text");
|
|
1094
1320
|
const mode = req.mode === "steer" ? "steer" : "followUp";
|
|
1321
|
+
const before = status();
|
|
1095
1322
|
if (g.ctx?.isIdle() ?? g.state === "idle") await g.pi.sendUserMessage(text);
|
|
1096
1323
|
else await g.pi.sendUserMessage(text, { deliverAs: mode });
|
|
1097
|
-
return { v: V, ok: true,
|
|
1324
|
+
return { v: V, ok: true, ...before };
|
|
1098
1325
|
}
|
|
1099
1326
|
case "fresh":
|
|
1100
1327
|
return fresh(req);
|
|
@@ -1106,10 +1333,28 @@ function serveCtl(pi) {
|
|
|
1106
1333
|
if (!g.ctx) return fail("no pi context yet");
|
|
1107
1334
|
g.ctx.abort();
|
|
1108
1335
|
return { v: V, ok: true, state: g.state };
|
|
1336
|
+
case "interrupt":
|
|
1337
|
+
return interrupt(req, conn);
|
|
1109
1338
|
default:
|
|
1110
1339
|
return { ...fail(`${UNKNOWN_OP_PREFIX}${String(req.op)}`), ops: [...CTL_OPS] };
|
|
1111
1340
|
}
|
|
1112
1341
|
}
|
|
1342
|
+
async function interrupt(req, conn) {
|
|
1343
|
+
const text = str(req, "text");
|
|
1344
|
+
if (text === void 0) return fail("interrupt needs a string text");
|
|
1345
|
+
const wasBusy = !(g.ctx?.isIdle() ?? g.state === "idle");
|
|
1346
|
+
const { pending } = status();
|
|
1347
|
+
if (wasBusy) {
|
|
1348
|
+
if (!g.ctx) return fail("no pi context yet");
|
|
1349
|
+
const settled = wait(g.runs, num(req, "timeoutMs"), conn);
|
|
1350
|
+
g.ctx.abort();
|
|
1351
|
+
const w = await settled;
|
|
1352
|
+
if (!w.ok) return w;
|
|
1353
|
+
}
|
|
1354
|
+
const before = status();
|
|
1355
|
+
await g.pi.sendUserMessage(text);
|
|
1356
|
+
return { v: V, ok: true, ...before, pending, wasBusy };
|
|
1357
|
+
}
|
|
1113
1358
|
const settledRun = () => ({
|
|
1114
1359
|
lastText: g.lastText,
|
|
1115
1360
|
...g.lastError !== void 0 ? { lastError: g.lastError } : {}
|
|
@@ -1126,7 +1371,10 @@ function serveCtl(pi) {
|
|
|
1126
1371
|
conn.off("close", onClose);
|
|
1127
1372
|
resolve(r);
|
|
1128
1373
|
};
|
|
1129
|
-
const waiter = () =>
|
|
1374
|
+
const waiter = () => {
|
|
1375
|
+
if (afterRuns !== void 0 && g.runs <= afterRuns) return;
|
|
1376
|
+
done({ v: V, ok: true, ...status(), ...settledRun() });
|
|
1377
|
+
};
|
|
1130
1378
|
const onClose = () => done(fail("client closed"));
|
|
1131
1379
|
g.waiters.add(waiter);
|
|
1132
1380
|
conn.on("close", onClose);
|
|
@@ -1169,8 +1417,9 @@ function serveCtl(pi) {
|
|
|
1169
1417
|
async function bind() {
|
|
1170
1418
|
mkdirSync(dirname(sockPath), { recursive: true, mode: 448 });
|
|
1171
1419
|
if (await socketIsLive(sockPath)) {
|
|
1420
|
+
g.nested = true;
|
|
1172
1421
|
process.stderr.write(
|
|
1173
|
-
`mu: control socket ${sockPath} is served by another pi;
|
|
1422
|
+
`mu: control socket ${sockPath} is served by another pi; nested pi: mu agent features off
|
|
1174
1423
|
`
|
|
1175
1424
|
);
|
|
1176
1425
|
return;
|
|
@@ -1192,14 +1441,18 @@ function serveCtl(pi) {
|
|
|
1192
1441
|
} catch (e) {
|
|
1193
1442
|
s.close();
|
|
1194
1443
|
if (e.code !== "EEXIST") throw e;
|
|
1195
|
-
|
|
1196
|
-
|
|
1444
|
+
g.nested = true;
|
|
1445
|
+
process.stderr.write(
|
|
1446
|
+
`mu: control socket ${sockPath} was taken by another pi; nested pi: mu agent features off
|
|
1447
|
+
`
|
|
1448
|
+
);
|
|
1197
1449
|
return;
|
|
1198
1450
|
} finally {
|
|
1199
1451
|
rmSync(tmp, { force: true });
|
|
1200
1452
|
}
|
|
1201
1453
|
g.server = s;
|
|
1202
1454
|
g.ino = inodeOf(sockPath);
|
|
1455
|
+
g.nested = false;
|
|
1203
1456
|
}
|
|
1204
1457
|
pi.on("session_start", async (e, c) => {
|
|
1205
1458
|
g.ctx = c;
|
|
@@ -1231,6 +1484,7 @@ function serveCtl(pi) {
|
|
|
1231
1484
|
await new Promise((resolve) => s.close(() => resolve()));
|
|
1232
1485
|
if (ino !== void 0 && inodeOf(sockPath) === ino) rmSync(sockPath, { force: true });
|
|
1233
1486
|
});
|
|
1487
|
+
return () => g.nested === true;
|
|
1234
1488
|
}
|
|
1235
1489
|
export {
|
|
1236
1490
|
COMMAND_TIMEOUT_MS,
|