@mu-crew/mu 3.7.0 → 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 +10797 -10500
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +266 -39
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +39 -9
- package/dist/index.js +1678 -1580
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/architecture/control-socket.md +8 -3
- package/docs/guide/delegate.md +18 -0
- package/docs/guide/dispatch.md +17 -3
- package/docs/guide/getting-started.md +1 -1
- package/docs/reference/env.md +1 -1
- package/docs/specs/2026-10-03-keep-driving-nudge.md +21 -0
- package/package.json +1 -1
- package/skills/mu/SKILL.md +36 -15
- 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 +19 -10
- package/skills/mu/recipes/plan.md +10 -6
- package/skills/mu/recipes/refute.md +3 -2
- 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 +39 -26
- 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,7 +401,11 @@ 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();
|
|
@@ -392,13 +450,19 @@ ${task}` : task,
|
|
|
392
450
|
const settle = async (r) => {
|
|
393
451
|
forget(name);
|
|
394
452
|
try {
|
|
395
|
-
await deliver(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
);
|
|
402
466
|
} catch (e) {
|
|
403
467
|
await Promise.resolve(
|
|
404
468
|
pi.sendMessage(
|
|
@@ -443,7 +507,7 @@ ${task}` : task,
|
|
|
443
507
|
pi.registerTool({
|
|
444
508
|
name: DELEGATE_TOOL,
|
|
445
509
|
label: "mu delegate",
|
|
446
|
-
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.`,
|
|
447
511
|
promptSnippet: "Subagent: delegate a self-contained task to a background pi agent; its answer arrives later as a follow-up",
|
|
448
512
|
parameters: {
|
|
449
513
|
type: "object",
|
|
@@ -477,6 +541,16 @@ ${task}` : task,
|
|
|
477
541
|
keep: {
|
|
478
542
|
type: "boolean",
|
|
479
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
|
|
480
554
|
}
|
|
481
555
|
},
|
|
482
556
|
required: ["task"],
|
|
@@ -495,10 +569,11 @@ ${task}` : task,
|
|
|
495
569
|
const cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
|
|
496
570
|
if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
|
|
497
571
|
}
|
|
572
|
+
const record = await resolveRecord(params.record);
|
|
498
573
|
const max = delegateMax();
|
|
499
574
|
if (inflight.size + starting < max) {
|
|
500
575
|
starting++;
|
|
501
|
-
return start(params, signal, ctx);
|
|
576
|
+
return start(params, signal, ctx, void 0, record);
|
|
502
577
|
}
|
|
503
578
|
if (queue.length >= DELEGATE_QUEUE_FACTOR * max)
|
|
504
579
|
throw new Error(
|
|
@@ -511,7 +586,8 @@ ${task}` : task,
|
|
|
511
586
|
...params,
|
|
512
587
|
cwd: typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd()
|
|
513
588
|
},
|
|
514
|
-
ctx
|
|
589
|
+
ctx,
|
|
590
|
+
...record ? { record } : {}
|
|
515
591
|
});
|
|
516
592
|
showStatus();
|
|
517
593
|
return {
|
|
@@ -544,10 +620,13 @@ ${task}` : task,
|
|
|
544
620
|
if (!name) throw new Error("mu_delegate_cancel needs a name");
|
|
545
621
|
const qi = queue.findIndex((q) => q.handle === name);
|
|
546
622
|
if (qi >= 0) {
|
|
547
|
-
queue.splice(qi, 1);
|
|
623
|
+
const [q] = queue.splice(qi, 1);
|
|
548
624
|
showStatus();
|
|
625
|
+
const recorded = q?.record ? ` ${await writeRecord(q.record, name, recordNote(recordLabel(q.params, name), name, "cancelled", void 0, 0))}` : "";
|
|
549
626
|
return {
|
|
550
|
-
content: [
|
|
627
|
+
content: [
|
|
628
|
+
{ type: "text", text: `Dropped ${name} from the queue; it never started.${recorded}` }
|
|
629
|
+
],
|
|
551
630
|
details: { name, workstream: W }
|
|
552
631
|
};
|
|
553
632
|
}
|
|
@@ -569,6 +648,8 @@ ${task}` : task,
|
|
|
569
648
|
}
|
|
570
649
|
failed.delete(name);
|
|
571
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))}`;
|
|
572
653
|
if (params.keep === true) text += " Pane kept.";
|
|
573
654
|
else {
|
|
574
655
|
const c = await mu(["agent", "close", name, "-w", W]);
|
|
@@ -605,9 +686,14 @@ import { join as join2 } from "path";
|
|
|
605
686
|
var NUDGE_MESSAGE_TYPE = "mu-keep-driving";
|
|
606
687
|
var NUDGE_LOG_KIND = "nudge";
|
|
607
688
|
var CLOSE_NUDGE_MESSAGE_TYPE = "mu-close-task";
|
|
689
|
+
var REFUTE_NUDGE_MESSAGE_TYPE = "mu-refute-brief";
|
|
608
690
|
var MARK_START = "<!-- mu:keep-driving -->";
|
|
609
691
|
var MARK_END = "<!-- /mu:keep-driving -->";
|
|
610
692
|
var WAIT_HINT_MAX = 8;
|
|
693
|
+
var notNested = () => false;
|
|
694
|
+
function noUI(ctx) {
|
|
695
|
+
return ctx?.hasUI === false;
|
|
696
|
+
}
|
|
611
697
|
function nudgeEnabled(env = process.env) {
|
|
612
698
|
return env.MU_NUDGE !== "0";
|
|
613
699
|
}
|
|
@@ -627,8 +713,7 @@ function readSkill() {
|
|
|
627
713
|
}
|
|
628
714
|
function dispatchedWorkstreams(command) {
|
|
629
715
|
const found = /* @__PURE__ */ new Set();
|
|
630
|
-
for (const
|
|
631
|
-
const words = seg.trim().split(/\s+/);
|
|
716
|
+
for (const words of shellSegments(command)) {
|
|
632
717
|
const at = words.findIndex((w) => w === "mu" || w.endsWith("/mu"));
|
|
633
718
|
if (at < 0) continue;
|
|
634
719
|
const [noun, verb] = [words[at + 1], words[at + 2]];
|
|
@@ -638,19 +723,78 @@ function dispatchedWorkstreams(command) {
|
|
|
638
723
|
let ws = "";
|
|
639
724
|
for (let k = at + 3; k < words.length; k++) {
|
|
640
725
|
const w = words[k] ?? "";
|
|
641
|
-
if (w === "-w" || w === "--workstream") ws =
|
|
642
|
-
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);
|
|
643
728
|
}
|
|
644
729
|
if (ws === "" && isClaimFor) {
|
|
645
730
|
const ref = words[at + 3] ?? "";
|
|
646
|
-
if (ref.includes("/")) ws =
|
|
731
|
+
if (ref.includes("/")) ws = ref.split("/")[0] ?? "";
|
|
647
732
|
}
|
|
648
733
|
if (ws !== "scratch") found.add(ws);
|
|
649
734
|
}
|
|
650
735
|
return found.size > 0 ? [...found] : void 0;
|
|
651
736
|
}
|
|
652
|
-
|
|
653
|
-
|
|
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;
|
|
654
798
|
}
|
|
655
799
|
async function inProgress(run, workstreams) {
|
|
656
800
|
const named = workstreams.filter((w) => w !== "");
|
|
@@ -690,7 +834,7 @@ function bashCommand(event) {
|
|
|
690
834
|
const e = event;
|
|
691
835
|
return e.toolName === "bash" && typeof e.input?.command === "string" ? e.input.command : void 0;
|
|
692
836
|
}
|
|
693
|
-
function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
837
|
+
function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested = notNested) {
|
|
694
838
|
if (!nudgeEnabled()) return;
|
|
695
839
|
const rule = skillMd === void 0 ? void 0 : keepDrivingRule(skillMd);
|
|
696
840
|
if (rule === void 0) return;
|
|
@@ -706,7 +850,7 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
|
706
850
|
for (const ws of dispatchedWorkstreams(cmd) ?? []) armed.add(ws);
|
|
707
851
|
});
|
|
708
852
|
pi.on("agent_before_settle", async (event) => {
|
|
709
|
-
if (fired || armed.size === 0) return;
|
|
853
|
+
if (fired || armed.size === 0 || nested()) return;
|
|
710
854
|
if (outcomeOf(event) !== "completed") return;
|
|
711
855
|
fired = true;
|
|
712
856
|
const refs = await inProgress(run, [...armed]);
|
|
@@ -755,7 +899,7 @@ function closeNudgeText(workstream, names) {
|
|
|
755
899
|
"If you are blocked or need an answer, say so in one line and stop; the orchestrator will read your pane."
|
|
756
900
|
].join("\n");
|
|
757
901
|
}
|
|
758
|
-
function registerCloseNudge(pi, run = defaultRunner(), env = process.env) {
|
|
902
|
+
function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested = notNested) {
|
|
759
903
|
if (!nudgeEnabled(env)) return;
|
|
760
904
|
const me = workerIdentity(env);
|
|
761
905
|
if (me === void 0) return;
|
|
@@ -763,8 +907,8 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env) {
|
|
|
763
907
|
pi.on("input", () => {
|
|
764
908
|
fired = false;
|
|
765
909
|
});
|
|
766
|
-
pi.on("agent_before_settle", async (event) => {
|
|
767
|
-
if (fired) return;
|
|
910
|
+
pi.on("agent_before_settle", async (event, ctx) => {
|
|
911
|
+
if (fired || nested() || noUI(ctx)) return;
|
|
768
912
|
if (outcomeOf(event) !== "completed") return;
|
|
769
913
|
fired = true;
|
|
770
914
|
const names = await ownedInProgress(run, me.agent, me.workstream);
|
|
@@ -790,9 +934,67 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env) {
|
|
|
790
934
|
};
|
|
791
935
|
});
|
|
792
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
|
+
}
|
|
793
995
|
|
|
794
996
|
// extension/mu-pi.ts
|
|
795
|
-
var EXT_VERSION = true ? "3.
|
|
997
|
+
var EXT_VERSION = true ? "3.8.0" : void 0;
|
|
796
998
|
var FRESH_COMMAND = "mu-fresh";
|
|
797
999
|
var FRESH_TIMEOUT_MS = 3e4;
|
|
798
1000
|
function commandName(name) {
|
|
@@ -931,16 +1133,17 @@ function registerPrompts(pi) {
|
|
|
931
1133
|
return { promptPaths: paths };
|
|
932
1134
|
});
|
|
933
1135
|
}
|
|
934
|
-
function muPi(pi) {
|
|
935
|
-
registerDelegate(pi);
|
|
1136
|
+
function muPi(pi, run = defaultRunner()) {
|
|
1137
|
+
registerDelegate(pi, run);
|
|
936
1138
|
registerPrompts(pi);
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1139
|
+
const nested = serveCtl(pi);
|
|
1140
|
+
registerNudge(pi, run, void 0, nested);
|
|
1141
|
+
registerCloseNudge(pi, run, process.env, nested);
|
|
1142
|
+
registerRefuteNudge(pi, run, nested);
|
|
940
1143
|
}
|
|
941
1144
|
function serveCtl(pi) {
|
|
942
1145
|
const sock = process.env.MU_CTL_SOCK;
|
|
943
|
-
if (!sock) return;
|
|
1146
|
+
if (!sock) return () => false;
|
|
944
1147
|
const sockPath = sock;
|
|
945
1148
|
const piVersion = detectPiVersion();
|
|
946
1149
|
const map = sharedMap();
|
|
@@ -1130,10 +1333,28 @@ function serveCtl(pi) {
|
|
|
1130
1333
|
if (!g.ctx) return fail("no pi context yet");
|
|
1131
1334
|
g.ctx.abort();
|
|
1132
1335
|
return { v: V, ok: true, state: g.state };
|
|
1336
|
+
case "interrupt":
|
|
1337
|
+
return interrupt(req, conn);
|
|
1133
1338
|
default:
|
|
1134
1339
|
return { ...fail(`${UNKNOWN_OP_PREFIX}${String(req.op)}`), ops: [...CTL_OPS] };
|
|
1135
1340
|
}
|
|
1136
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
|
+
}
|
|
1137
1358
|
const settledRun = () => ({
|
|
1138
1359
|
lastText: g.lastText,
|
|
1139
1360
|
...g.lastError !== void 0 ? { lastError: g.lastError } : {}
|
|
@@ -1196,8 +1417,9 @@ function serveCtl(pi) {
|
|
|
1196
1417
|
async function bind() {
|
|
1197
1418
|
mkdirSync(dirname(sockPath), { recursive: true, mode: 448 });
|
|
1198
1419
|
if (await socketIsLive(sockPath)) {
|
|
1420
|
+
g.nested = true;
|
|
1199
1421
|
process.stderr.write(
|
|
1200
|
-
`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
|
|
1201
1423
|
`
|
|
1202
1424
|
);
|
|
1203
1425
|
return;
|
|
@@ -1219,14 +1441,18 @@ function serveCtl(pi) {
|
|
|
1219
1441
|
} catch (e) {
|
|
1220
1442
|
s.close();
|
|
1221
1443
|
if (e.code !== "EEXIST") throw e;
|
|
1222
|
-
|
|
1223
|
-
|
|
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
|
+
);
|
|
1224
1449
|
return;
|
|
1225
1450
|
} finally {
|
|
1226
1451
|
rmSync(tmp, { force: true });
|
|
1227
1452
|
}
|
|
1228
1453
|
g.server = s;
|
|
1229
1454
|
g.ino = inodeOf(sockPath);
|
|
1455
|
+
g.nested = false;
|
|
1230
1456
|
}
|
|
1231
1457
|
pi.on("session_start", async (e, c) => {
|
|
1232
1458
|
g.ctx = c;
|
|
@@ -1258,6 +1484,7 @@ function serveCtl(pi) {
|
|
|
1258
1484
|
await new Promise((resolve) => s.close(() => resolve()));
|
|
1259
1485
|
if (ino !== void 0 && inodeOf(sockPath) === ino) rmSync(sockPath, { force: true });
|
|
1260
1486
|
});
|
|
1487
|
+
return () => g.nested === true;
|
|
1261
1488
|
}
|
|
1262
1489
|
export {
|
|
1263
1490
|
COMMAND_TIMEOUT_MS,
|