@mu-crew/mu 3.2.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -5
- package/README.md +83 -22
- package/dist/cli.js +294 -98
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +346 -85
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +68 -13
- package/dist/index.js +159 -71
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +1 -0
- package/docs/VISION.md +3 -1
- package/docs/VOCABULARY.md +6 -1
- package/docs/architecture/control-socket.md +17 -3
- package/docs/architecture/dag.md +3 -3
- package/docs/architecture/mux.md +3 -4
- package/docs/guide/README.md +5 -0
- package/docs/guide/delegate.md +30 -3
- package/docs/guide/dispatch.md +11 -4
- package/docs/guide/getting-started.md +3 -0
- package/docs/guide/recovery.md +3 -1
- package/docs/guide/remote.md +1 -1
- package/docs/history/CHANGELOG-pre-3.md +1 -1
- package/docs/reference/env.md +2 -1
- package/docs/reference/naming.md +18 -0
- 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-debug.md +8 -0
- 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-sweep.md +8 -0
- package/prompts/mu-tournament.md +9 -0
- package/prompts/mu-until.md +8 -0
- package/prompts/ultrathink.md +10 -0
- package/skills/mu/SKILL.md +124 -145
- package/skills/mu/recipes/adversarial-review.md +91 -0
- package/skills/mu/recipes/backlog-triage.md +43 -0
- package/skills/mu/recipes/brief.md +53 -0
- package/skills/mu/recipes/codemode-driver.md +102 -0
- package/skills/mu/recipes/deep-research.md +67 -0
- package/skills/mu/recipes/fan-out.md +40 -0
- package/skills/mu/recipes/findings.md +103 -0
- package/skills/mu/recipes/hypothesis-panel.md +45 -0
- package/skills/mu/recipes/long-run.md +20 -0
- package/skills/mu/recipes/loop-until-done.md +42 -0
- package/skills/mu/recipes/orchestrator-loop.md +98 -0
- package/skills/mu/recipes/plan.md +59 -0
- package/skills/mu/recipes/recovery.md +28 -0
- package/skills/mu/recipes/refute.md +51 -0
- package/skills/mu/{REMOTE_WORKERS.md → recipes/remote-workers.md} +7 -2
- package/skills/mu/recipes/review-panel.md +62 -0
- package/skills/mu/recipes/rules-audit.md +83 -0
- package/skills/mu/recipes/tasks-or-calls.md +67 -0
- package/skills/mu/recipes/tournament.md +40 -0
- package/skills/mu/recipes/ultrathink.md +82 -0
- package/skills/mu/recipes/watcher.md +19 -0
- package/skills/mu/recipes/waves.md +20 -0
- package/skills/mu/recipes/worker.md +26 -0
package/dist/extension/mu-pi.js
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
// extension/mu-pi.ts
|
|
2
2
|
import {
|
|
3
3
|
chmodSync,
|
|
4
|
-
existsSync as
|
|
4
|
+
existsSync as existsSync3,
|
|
5
5
|
linkSync,
|
|
6
6
|
mkdirSync,
|
|
7
|
-
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync as readFileSync2,
|
|
8
9
|
realpathSync,
|
|
9
10
|
rmSync,
|
|
10
11
|
statSync as statSync2
|
|
11
12
|
} from "fs";
|
|
12
13
|
import { connect, createServer } from "net";
|
|
13
|
-
import { dirname, join as
|
|
14
|
+
import { dirname, join as join3 } from "path";
|
|
15
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14
16
|
|
|
15
17
|
// src/ctl/protocol.ts
|
|
16
18
|
var CTL_PROTOCOL_VERSION = 1;
|
|
19
|
+
var CTL_COMMANDS = ["new", "reload", "compact"];
|
|
17
20
|
var CTL_OPS = [
|
|
18
21
|
"hello",
|
|
19
22
|
"status",
|
|
20
23
|
"send",
|
|
21
24
|
"wait",
|
|
22
25
|
"abort",
|
|
23
|
-
"fresh"
|
|
26
|
+
"fresh",
|
|
27
|
+
"command"
|
|
24
28
|
];
|
|
25
29
|
var UNKNOWN_OP_PREFIX = "unknown op: ";
|
|
26
30
|
function encode(msg) {
|
|
@@ -51,6 +55,11 @@ function delegateStatus(n) {
|
|
|
51
55
|
return n > 0 ? `${n} delegate${n === 1 ? "" : "s"} running` : void 0;
|
|
52
56
|
}
|
|
53
57
|
var DELEGATE_TIMEOUT_S = 3600;
|
|
58
|
+
var DELEGATE_MAX_DEFAULT = 16;
|
|
59
|
+
function delegateMax(env = process.env) {
|
|
60
|
+
const n = Number(env.MU_DELEGATE_MAX);
|
|
61
|
+
return Number.isInteger(n) && n > 0 ? n : DELEGATE_MAX_DEFAULT;
|
|
62
|
+
}
|
|
54
63
|
var W = DELEGATE_WORKSTREAM;
|
|
55
64
|
function delegateEnabled(env = process.env) {
|
|
56
65
|
if (env.MU_MANAGED_AGENT) return false;
|
|
@@ -137,6 +146,7 @@ function labelStem(label) {
|
|
|
137
146
|
function registerDelegate(pi, run = defaultRunner()) {
|
|
138
147
|
if (!delegateEnabled()) return;
|
|
139
148
|
const inflight = /* @__PURE__ */ new Map();
|
|
149
|
+
let starting = 0;
|
|
140
150
|
const reserved = /* @__PURE__ */ new Set();
|
|
141
151
|
let ui;
|
|
142
152
|
const showStatus = () => {
|
|
@@ -249,7 +259,7 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
249
259
|
pi.registerTool({
|
|
250
260
|
name: DELEGATE_TOOL,
|
|
251
261
|
label: "mu delegate",
|
|
252
|
-
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. 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.`,
|
|
262
|
+
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()} in flight). 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.`,
|
|
253
263
|
promptSnippet: "Subagent: delegate a self-contained task to a background pi agent; its answer arrives later as a follow-up",
|
|
254
264
|
parameters: {
|
|
255
265
|
type: "object",
|
|
@@ -292,83 +302,106 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
292
302
|
track(ctx);
|
|
293
303
|
const task = typeof params.task === "string" ? params.task.trim() : "";
|
|
294
304
|
if (!task) throw new Error("mu_delegate needs a non-empty task");
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
const t = params.timeout;
|
|
298
|
-
if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
|
|
305
|
+
const max = delegateMax();
|
|
306
|
+
if (inflight.size + starting >= max)
|
|
299
307
|
throw new Error(
|
|
300
|
-
`mu_delegate:
|
|
308
|
+
`mu_delegate: ${inflight.size + starting} delegates already running (MU_DELEGATE_MAX=${max}). End your turn; issue the rest as answers arrive (each answer resumes you).`
|
|
301
309
|
);
|
|
302
|
-
|
|
303
|
-
let
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
const { name, attach, workspace } = await spawn(params, cwd, signal);
|
|
309
|
-
const entry = { abort: new AbortController(), cancelling: false };
|
|
310
|
-
inflight.set(name, entry);
|
|
311
|
-
showStatus();
|
|
312
|
-
const started = Date.now();
|
|
313
|
-
const waiting = mu(
|
|
314
|
-
["agent", "wait", name, "-w", W, "--json", "--timeout", String(timeoutS)],
|
|
315
|
-
entry.abort.signal
|
|
316
|
-
);
|
|
317
|
-
const sent = await mu(["agent", "send", name, brief ? `${brief}
|
|
318
|
-
|
|
319
|
-
${task}` : task, "-w", W]);
|
|
320
|
-
if (sent.code !== 0) {
|
|
321
|
-
entry.abort.abort();
|
|
322
|
-
forget(name);
|
|
323
|
-
const c = await mu(["agent", "close", name, "-w", W]);
|
|
324
|
-
const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
|
|
325
|
-
throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
|
|
326
|
-
}
|
|
327
|
-
const settle = async (r) => {
|
|
328
|
-
forget(name);
|
|
329
|
-
try {
|
|
330
|
-
await deliver(name, r, keep, {
|
|
331
|
-
timeoutS,
|
|
332
|
-
elapsedMs: Date.now() - started,
|
|
333
|
-
...attach ? { attach } : {},
|
|
334
|
-
...workspace ? { workspace } : {}
|
|
335
|
-
});
|
|
336
|
-
} catch (e) {
|
|
337
|
-
await Promise.resolve(
|
|
338
|
-
pi.sendMessage(
|
|
339
|
-
{
|
|
340
|
-
customType: DELEGATE_MESSAGE_TYPE,
|
|
341
|
-
content: `Delegate ${W}/${name} settled, but delivering its answer failed: ${e instanceof Error ? e.message : String(e)}. Read it with: mu agent read ${name} -n 50 -w ${W}`,
|
|
342
|
-
display: true,
|
|
343
|
-
details: { name, workstream: W, outcome: null, closed: false }
|
|
344
|
-
},
|
|
345
|
-
{ deliverAs: "followUp", triggerTurn: true }
|
|
346
|
-
)
|
|
347
|
-
).catch(() => {
|
|
348
|
-
});
|
|
349
|
-
}
|
|
310
|
+
starting++;
|
|
311
|
+
let slotHeld = true;
|
|
312
|
+
const freeSlot = () => {
|
|
313
|
+
if (slotHeld) starting--;
|
|
314
|
+
slotHeld = false;
|
|
350
315
|
};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
316
|
+
try {
|
|
317
|
+
const brief = typeof params.brief === "string" ? params.brief.trim() : "";
|
|
318
|
+
const keep = params.keep === true;
|
|
319
|
+
const t = params.timeout;
|
|
320
|
+
if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
|
|
321
|
+
throw new Error(
|
|
322
|
+
`mu_delegate: timeout must be a positive number of seconds (got ${JSON.stringify(t)})`
|
|
323
|
+
);
|
|
324
|
+
const timeoutS = typeof t === "number" ? Math.ceil(t) : DELEGATE_TIMEOUT_S;
|
|
325
|
+
let cwd;
|
|
326
|
+
if (params.workspace !== true) {
|
|
327
|
+
cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
|
|
328
|
+
if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
|
|
329
|
+
}
|
|
330
|
+
const { name, attach, workspace } = await spawn(params, cwd, signal);
|
|
331
|
+
const entry = { abort: new AbortController(), cancelling: false };
|
|
332
|
+
inflight.set(name, entry);
|
|
333
|
+
freeSlot();
|
|
334
|
+
showStatus();
|
|
335
|
+
const started = Date.now();
|
|
336
|
+
const waiting = mu(
|
|
337
|
+
["agent", "wait", name, "-w", W, "--json", "--timeout", String(timeoutS)],
|
|
338
|
+
entry.abort.signal
|
|
339
|
+
);
|
|
340
|
+
const sent = await mu([
|
|
341
|
+
"agent",
|
|
342
|
+
"send",
|
|
365
343
|
name,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
344
|
+
brief ? `${brief}
|
|
345
|
+
|
|
346
|
+
${task}` : task,
|
|
347
|
+
"-w",
|
|
348
|
+
W
|
|
349
|
+
]);
|
|
350
|
+
if (sent.code !== 0) {
|
|
351
|
+
entry.abort.abort();
|
|
352
|
+
forget(name);
|
|
353
|
+
const c = await mu(["agent", "close", name, "-w", W]);
|
|
354
|
+
const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
|
|
355
|
+
throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
|
|
370
356
|
}
|
|
371
|
-
|
|
357
|
+
const settle = async (r) => {
|
|
358
|
+
forget(name);
|
|
359
|
+
try {
|
|
360
|
+
await deliver(name, r, keep, {
|
|
361
|
+
timeoutS,
|
|
362
|
+
elapsedMs: Date.now() - started,
|
|
363
|
+
...attach ? { attach } : {},
|
|
364
|
+
...workspace ? { workspace } : {}
|
|
365
|
+
});
|
|
366
|
+
} catch (e) {
|
|
367
|
+
await Promise.resolve(
|
|
368
|
+
pi.sendMessage(
|
|
369
|
+
{
|
|
370
|
+
customType: DELEGATE_MESSAGE_TYPE,
|
|
371
|
+
content: `Delegate ${W}/${name} settled, but delivering its answer failed: ${e instanceof Error ? e.message : String(e)}. Read it with: mu agent read ${name} -n 50 -w ${W}`,
|
|
372
|
+
display: true,
|
|
373
|
+
details: { name, workstream: W, outcome: null, closed: false }
|
|
374
|
+
},
|
|
375
|
+
{ deliverAs: "followUp", triggerTurn: true }
|
|
376
|
+
)
|
|
377
|
+
).catch(() => {
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
void waiting.then(async (r) => {
|
|
382
|
+
if (inflight.get(name) !== entry) return;
|
|
383
|
+
if (entry.cancelling) entry.parked = () => settle(r);
|
|
384
|
+
else await settle(r);
|
|
385
|
+
});
|
|
386
|
+
const lines = [
|
|
387
|
+
`Delegated to ${W}/${name}. Its answer arrives as a follow-up message when it finishes; do not wait or poll.`,
|
|
388
|
+
workspace ? `Workspace: ${workspace}` : void 0,
|
|
389
|
+
attach ? `Attach: ${attach}` : void 0,
|
|
390
|
+
`Cancel: ${DELEGATE_CANCEL_TOOL} { name: "${name}" }`
|
|
391
|
+
].filter((x) => x !== void 0);
|
|
392
|
+
return {
|
|
393
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
394
|
+
details: {
|
|
395
|
+
name,
|
|
396
|
+
workstream: W,
|
|
397
|
+
attach: attach ?? null,
|
|
398
|
+
workspace: workspace ?? null,
|
|
399
|
+
keep
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
} finally {
|
|
403
|
+
freeSlot();
|
|
404
|
+
}
|
|
372
405
|
}
|
|
373
406
|
});
|
|
374
407
|
pi.registerTool({
|
|
@@ -427,10 +460,143 @@ ${task}` : task, "-w", W]);
|
|
|
427
460
|
});
|
|
428
461
|
}
|
|
429
462
|
|
|
463
|
+
// extension/nudge.ts
|
|
464
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
465
|
+
import { join as join2 } from "path";
|
|
466
|
+
var NUDGE_MESSAGE_TYPE = "mu-keep-driving";
|
|
467
|
+
var NUDGE_LOG_KIND = "nudge";
|
|
468
|
+
var MARK_START = "<!-- mu:keep-driving -->";
|
|
469
|
+
var MARK_END = "<!-- /mu:keep-driving -->";
|
|
470
|
+
var WAIT_HINT_MAX = 8;
|
|
471
|
+
function nudgeEnabled(env = process.env) {
|
|
472
|
+
return env.MU_NUDGE !== "0";
|
|
473
|
+
}
|
|
474
|
+
function keepDrivingRule(skillMd) {
|
|
475
|
+
const i = skillMd.indexOf(MARK_START);
|
|
476
|
+
const j = skillMd.indexOf(MARK_END);
|
|
477
|
+
if (i < 0 || j < i) return void 0;
|
|
478
|
+
const text = skillMd.slice(i + MARK_START.length, j).trim();
|
|
479
|
+
return text === "" ? void 0 : text;
|
|
480
|
+
}
|
|
481
|
+
function readSkill() {
|
|
482
|
+
for (const up of ["..", "../.."]) {
|
|
483
|
+
const p = join2(import.meta.dirname ?? "", up, "skills", "mu", "SKILL.md");
|
|
484
|
+
if (existsSync2(p)) return readFileSync(p, "utf8");
|
|
485
|
+
}
|
|
486
|
+
return void 0;
|
|
487
|
+
}
|
|
488
|
+
function dispatchedWorkstreams(command) {
|
|
489
|
+
const found = /* @__PURE__ */ new Set();
|
|
490
|
+
for (const seg of command.split(/&&|\|\||;|\n|\|/)) {
|
|
491
|
+
const words = seg.trim().split(/\s+/);
|
|
492
|
+
const at = words.findIndex((w) => w === "mu" || w.endsWith("/mu"));
|
|
493
|
+
if (at < 0) continue;
|
|
494
|
+
const [noun, verb] = [words[at + 1], words[at + 2]];
|
|
495
|
+
const isSendSpawn = noun === "agent" && (verb === "send" || verb === "spawn");
|
|
496
|
+
const isClaimFor = noun === "task" && verb === "claim" && words.includes("--for");
|
|
497
|
+
if (!isSendSpawn && !isClaimFor) continue;
|
|
498
|
+
let ws = "";
|
|
499
|
+
for (let k = at + 3; k < words.length; k++) {
|
|
500
|
+
const w = words[k] ?? "";
|
|
501
|
+
if (w === "-w" || w === "--workstream") ws = unquote(words[k + 1] ?? "");
|
|
502
|
+
else if (w.startsWith("--workstream=")) ws = unquote(w.slice("--workstream=".length));
|
|
503
|
+
}
|
|
504
|
+
if (ws === "" && isClaimFor) {
|
|
505
|
+
const ref = words[at + 3] ?? "";
|
|
506
|
+
if (ref.includes("/")) ws = unquote(ref.split("/")[0] ?? "");
|
|
507
|
+
}
|
|
508
|
+
if (ws !== "scratch") found.add(ws);
|
|
509
|
+
}
|
|
510
|
+
return found.size > 0 ? [...found] : void 0;
|
|
511
|
+
}
|
|
512
|
+
function unquote(s) {
|
|
513
|
+
return s.replace(/^['"]|['"]$/g, "");
|
|
514
|
+
}
|
|
515
|
+
async function inProgress(run, workstreams) {
|
|
516
|
+
const named = workstreams.filter((w) => w !== "");
|
|
517
|
+
const args = ["state", "--json", "--events", "0"];
|
|
518
|
+
for (const w of named) args.push("-w", w);
|
|
519
|
+
const r = await run(args);
|
|
520
|
+
if (r.code !== 0) return void 0;
|
|
521
|
+
let json;
|
|
522
|
+
try {
|
|
523
|
+
json = JSON.parse(r.stdout);
|
|
524
|
+
} catch {
|
|
525
|
+
return void 0;
|
|
526
|
+
}
|
|
527
|
+
const cards = json.workstreams ?? [json];
|
|
528
|
+
const refs = [];
|
|
529
|
+
for (const ws of cards) {
|
|
530
|
+
for (const t of ws.inProgress ?? []) {
|
|
531
|
+
if (t.name) refs.push(`${ws.workstreamName ?? ""}/${t.name}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return refs;
|
|
535
|
+
}
|
|
536
|
+
function nudgeText(rule, refs) {
|
|
537
|
+
const shown = refs.slice(0, WAIT_HINT_MAX).join(" ");
|
|
538
|
+
const more = refs.length > WAIT_HINT_MAX ? ` (+${refs.length - WAIT_HINT_MAX} more)` : "";
|
|
539
|
+
return [
|
|
540
|
+
`[mu] ${rule}`,
|
|
541
|
+
"",
|
|
542
|
+
`${refs.length} task(s) IN_PROGRESS. Next: mu task wait ${shown} --first --on-stall exit --json${more}`
|
|
543
|
+
].join("\n");
|
|
544
|
+
}
|
|
545
|
+
function outcomeOf(event) {
|
|
546
|
+
return typeof event === "object" && event !== null ? event.outcome : void 0;
|
|
547
|
+
}
|
|
548
|
+
function bashCommand(event) {
|
|
549
|
+
if (typeof event !== "object" || event === null) return void 0;
|
|
550
|
+
const e = event;
|
|
551
|
+
return e.toolName === "bash" && typeof e.input?.command === "string" ? e.input.command : void 0;
|
|
552
|
+
}
|
|
553
|
+
function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
554
|
+
if (!nudgeEnabled()) return;
|
|
555
|
+
const rule = skillMd === void 0 ? void 0 : keepDrivingRule(skillMd);
|
|
556
|
+
if (rule === void 0) return;
|
|
557
|
+
const armed = /* @__PURE__ */ new Set();
|
|
558
|
+
let fired = false;
|
|
559
|
+
pi.on("input", () => {
|
|
560
|
+
armed.clear();
|
|
561
|
+
fired = false;
|
|
562
|
+
});
|
|
563
|
+
pi.on("tool_call", (event) => {
|
|
564
|
+
const cmd = bashCommand(event);
|
|
565
|
+
if (cmd === void 0) return;
|
|
566
|
+
for (const ws of dispatchedWorkstreams(cmd) ?? []) armed.add(ws);
|
|
567
|
+
});
|
|
568
|
+
pi.on("agent_before_settle", async (event) => {
|
|
569
|
+
if (fired || armed.size === 0) return;
|
|
570
|
+
if (outcomeOf(event) !== "completed") return;
|
|
571
|
+
fired = true;
|
|
572
|
+
const refs = await inProgress(run, [...armed]);
|
|
573
|
+
if (refs === void 0 || refs.length === 0) return;
|
|
574
|
+
const text = nudgeText(rule, refs);
|
|
575
|
+
const logWs = [...armed].find((w) => w !== "");
|
|
576
|
+
await run([
|
|
577
|
+
"log",
|
|
578
|
+
...logWs ? ["-w", logWs] : [],
|
|
579
|
+
"--kind",
|
|
580
|
+
NUDGE_LOG_KIND,
|
|
581
|
+
`keep-driving: ${refs.length} in progress`
|
|
582
|
+
]);
|
|
583
|
+
return {
|
|
584
|
+
entries: [
|
|
585
|
+
{ type: "custom_message", customType: NUDGE_MESSAGE_TYPE, content: text, display: true }
|
|
586
|
+
],
|
|
587
|
+
continue: true
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
430
592
|
// extension/mu-pi.ts
|
|
431
|
-
var EXT_VERSION = true ? "3.
|
|
593
|
+
var EXT_VERSION = true ? "3.4.0" : void 0;
|
|
432
594
|
var FRESH_COMMAND = "mu-fresh";
|
|
433
595
|
var FRESH_TIMEOUT_MS = 3e4;
|
|
596
|
+
function commandName(name) {
|
|
597
|
+
return `mu-${name}`;
|
|
598
|
+
}
|
|
599
|
+
var COMMAND_TIMEOUT_MS = 3e4;
|
|
434
600
|
var LAST_TEXT_MAX_BYTES = 64 * 1024;
|
|
435
601
|
var LAST_TEXT_TRUNCATED = "[truncated, see pane]";
|
|
436
602
|
var LIVE_PROBE_MS = 500;
|
|
@@ -444,9 +610,9 @@ function detectPiVersion() {
|
|
|
444
610
|
if (!entry) return void 0;
|
|
445
611
|
let dir = dirname(realpathSync(entry));
|
|
446
612
|
for (let i = 0; i < 6; i++) {
|
|
447
|
-
const pkgPath =
|
|
448
|
-
if (
|
|
449
|
-
const pkg = JSON.parse(
|
|
613
|
+
const pkgPath = join3(dir, "package.json");
|
|
614
|
+
if (existsSync3(pkgPath)) {
|
|
615
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
450
616
|
if (typeof pkg === "object" && pkg !== null) {
|
|
451
617
|
const { name, version } = pkg;
|
|
452
618
|
if (typeof name === "string" && /pi/.test(name) && typeof version === "string") {
|
|
@@ -529,8 +695,31 @@ function reasonOf(event) {
|
|
|
529
695
|
const r = typeof event === "object" && event !== null ? event.reason : void 0;
|
|
530
696
|
return typeof r === "string" ? r : void 0;
|
|
531
697
|
}
|
|
698
|
+
function promptsDir(from = import.meta.url) {
|
|
699
|
+
let here;
|
|
700
|
+
try {
|
|
701
|
+
here = dirname(fileURLToPath2(from));
|
|
702
|
+
} catch {
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
for (const up of ["..", "../.."]) {
|
|
706
|
+
const d = join3(here, up, "prompts");
|
|
707
|
+
if (existsSync3(join3(d, "ultrathink.md"))) return d;
|
|
708
|
+
}
|
|
709
|
+
return void 0;
|
|
710
|
+
}
|
|
711
|
+
function registerPrompts(pi) {
|
|
712
|
+
pi.on("resources_discover", () => {
|
|
713
|
+
const d = promptsDir();
|
|
714
|
+
if (!d) return {};
|
|
715
|
+
const paths = readdirSync(d).filter((f) => f.endsWith(".md")).sort().map((f) => join3(d, f));
|
|
716
|
+
return { promptPaths: paths };
|
|
717
|
+
});
|
|
718
|
+
}
|
|
532
719
|
function muPi(pi) {
|
|
533
720
|
registerDelegate(pi);
|
|
721
|
+
registerPrompts(pi);
|
|
722
|
+
registerNudge(pi);
|
|
534
723
|
serveCtl(pi);
|
|
535
724
|
}
|
|
536
725
|
function serveCtl(pi) {
|
|
@@ -565,6 +754,12 @@ function serveCtl(pi) {
|
|
|
565
754
|
g.fresh = void 0;
|
|
566
755
|
f.settle(r);
|
|
567
756
|
};
|
|
757
|
+
const settleCommand = (r) => {
|
|
758
|
+
const c = g.command;
|
|
759
|
+
if (!c) return;
|
|
760
|
+
g.command = void 0;
|
|
761
|
+
c.settle(r);
|
|
762
|
+
};
|
|
568
763
|
pi.on("agent_start", (_e, c) => {
|
|
569
764
|
g.ctx = c;
|
|
570
765
|
g.state = "busy";
|
|
@@ -603,10 +798,69 @@ function serveCtl(pi) {
|
|
|
603
798
|
settleFresh({ v: V, ok: true, ...status() });
|
|
604
799
|
}
|
|
605
800
|
});
|
|
801
|
+
pi.on("session_before_compact", () => {
|
|
802
|
+
if (g.command?.name === "compact") settleCommand({ v: V, ok: true, ...status() });
|
|
803
|
+
});
|
|
804
|
+
async function runCommand(name, ctx) {
|
|
805
|
+
const c = g.command;
|
|
806
|
+
if (c?.name !== name) return;
|
|
807
|
+
const ok = () => settleCommand({ v: V, ok: true, ...status() });
|
|
808
|
+
const failWith = (e) => settleCommand(fail(e instanceof Error ? e.message : String(e)));
|
|
809
|
+
try {
|
|
810
|
+
if (name === "new") {
|
|
811
|
+
const r = await ctx.newSession();
|
|
812
|
+
if (r.cancelled) settleCommand(fail("new session was cancelled"));
|
|
813
|
+
else ok();
|
|
814
|
+
} else if (name === "reload") {
|
|
815
|
+
await ctx.reload();
|
|
816
|
+
if (c.reloaded) ok();
|
|
817
|
+
else settleCommand(fail("pi did not reload (busy or compacting)"));
|
|
818
|
+
} else {
|
|
819
|
+
ctx.compact({
|
|
820
|
+
...c.instructions !== void 0 ? { customInstructions: c.instructions } : {},
|
|
821
|
+
onComplete: ok,
|
|
822
|
+
onError: failWith
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
} catch (e) {
|
|
826
|
+
failWith(e);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
for (const name of CTL_COMMANDS) {
|
|
830
|
+
pi.registerCommand(commandName(name), {
|
|
831
|
+
description: `mu internal: /${name} requested over the control socket (mu agent send '/${name}')`,
|
|
832
|
+
handler: (_args, ctx) => runCommand(name, ctx)
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
function command(req) {
|
|
836
|
+
const name = CTL_COMMANDS.find((n) => n === req.name);
|
|
837
|
+
if (name === void 0) {
|
|
838
|
+
return fail(`command must be one of ${CTL_COMMANDS.join(", ")}`);
|
|
839
|
+
}
|
|
840
|
+
if (g.fresh || g.command) return fail("a fresh send or command is already in progress");
|
|
841
|
+
const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
|
|
842
|
+
if (busy && req.force !== true) return fail("busy");
|
|
843
|
+
const instructions = str(req, "instructions");
|
|
844
|
+
return new Promise((resolve) => {
|
|
845
|
+
const timer = setTimeout(() => settleCommand(fail(`/${name} timed out`)), COMMAND_TIMEOUT_MS);
|
|
846
|
+
g.command = {
|
|
847
|
+
name,
|
|
848
|
+
...instructions !== void 0 ? { instructions } : {},
|
|
849
|
+
reloaded: false,
|
|
850
|
+
settle: (r) => {
|
|
851
|
+
clearTimeout(timer);
|
|
852
|
+
resolve(r);
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
Promise.resolve(
|
|
856
|
+
g.pi.sendUserMessage(`/${commandName(name)}`, { expandPromptTemplates: true })
|
|
857
|
+
).catch((e) => settleCommand(fail(e instanceof Error ? e.message : String(e))));
|
|
858
|
+
});
|
|
859
|
+
}
|
|
606
860
|
function fresh(req) {
|
|
607
861
|
const text = str(req, "text");
|
|
608
862
|
if (text === void 0) return fail("fresh needs a string text");
|
|
609
|
-
if (g.fresh) return fail("a fresh send is already in progress");
|
|
863
|
+
if (g.fresh || g.command) return fail("a fresh send or command is already in progress");
|
|
610
864
|
const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
|
|
611
865
|
if (busy && req.force !== true) return fail("busy");
|
|
612
866
|
return new Promise((resolve) => {
|
|
@@ -648,6 +902,8 @@ function serveCtl(pi) {
|
|
|
648
902
|
}
|
|
649
903
|
case "fresh":
|
|
650
904
|
return fresh(req);
|
|
905
|
+
case "command":
|
|
906
|
+
return command(req);
|
|
651
907
|
case "wait":
|
|
652
908
|
return wait(num(req, "afterRuns"), num(req, "timeoutMs"), conn);
|
|
653
909
|
case "abort":
|
|
@@ -720,7 +976,7 @@ function serveCtl(pi) {
|
|
|
720
976
|
return;
|
|
721
977
|
}
|
|
722
978
|
rmSync(sockPath, { force: true });
|
|
723
|
-
const tmp =
|
|
979
|
+
const tmp = join3(dirname(sockPath), `.${process.pid.toString(36)}${(++bindSeq).toString(36)}`);
|
|
724
980
|
rmSync(tmp, { force: true });
|
|
725
981
|
const s = createServer(onConnection);
|
|
726
982
|
await new Promise((resolve, reject) => {
|
|
@@ -745,8 +1001,9 @@ function serveCtl(pi) {
|
|
|
745
1001
|
g.server = s;
|
|
746
1002
|
g.ino = inodeOf(sockPath);
|
|
747
1003
|
}
|
|
748
|
-
pi.on("session_start", async (
|
|
1004
|
+
pi.on("session_start", async (e, c) => {
|
|
749
1005
|
g.ctx = c;
|
|
1006
|
+
if (reasonOf(e) === "reload" && g.command?.name === "reload") g.command.reloaded = true;
|
|
750
1007
|
const s = g.server;
|
|
751
1008
|
if (s) {
|
|
752
1009
|
if (g.ino !== void 0 && inodeOf(sockPath) === g.ino) return;
|
|
@@ -769,18 +1026,22 @@ function serveCtl(pi) {
|
|
|
769
1026
|
if (!s) return;
|
|
770
1027
|
g.waiters.clear();
|
|
771
1028
|
settleFresh(fail("pi is shutting down"));
|
|
1029
|
+
settleCommand(fail("pi is shutting down"));
|
|
772
1030
|
for (const c of g.conns) c.destroy();
|
|
773
1031
|
await new Promise((resolve) => s.close(() => resolve()));
|
|
774
1032
|
if (ino !== void 0 && inodeOf(sockPath) === ino) rmSync(sockPath, { force: true });
|
|
775
1033
|
});
|
|
776
1034
|
}
|
|
777
1035
|
export {
|
|
1036
|
+
COMMAND_TIMEOUT_MS,
|
|
778
1037
|
FRESH_COMMAND,
|
|
779
1038
|
FRESH_TIMEOUT_MS,
|
|
780
1039
|
LAST_TEXT_MAX_BYTES,
|
|
781
1040
|
LAST_TEXT_TRUNCATED,
|
|
782
1041
|
LIVE_PROBE_MS,
|
|
1042
|
+
commandName,
|
|
783
1043
|
muPi as default,
|
|
784
|
-
lastAssistantText
|
|
1044
|
+
lastAssistantText,
|
|
1045
|
+
promptsDir
|
|
785
1046
|
};
|
|
786
1047
|
//# sourceMappingURL=mu-pi.js.map
|