@mu-crew/mu 3.3.0 → 3.5.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/README.md +45 -31
- package/dist/cli.js +812 -429
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +221 -55
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +33 -4
- package/dist/index.js +76 -5
- package/dist/index.js.map +1 -1
- package/docs/VISION.md +5 -1
- package/docs/VOCABULARY.md +4 -1
- package/docs/architecture/dag.md +3 -3
- package/docs/architecture/ops-log.md +26 -7
- package/docs/guide/cleanup.md +19 -0
- package/docs/guide/delegate.md +43 -4
- package/docs/reference/env.md +1 -0
- package/docs/reference/naming.md +18 -0
- package/docs/specs/2026-09-29-task-substates.md +1 -1
- package/package.json +1 -1
- package/prompts/mu-debug.md +8 -0
- package/prompts/mu-sweep.md +8 -0
- package/prompts/mu-until.md +8 -0
- package/prompts/ultrathink.md +2 -1
- package/skills/mu/SKILL.md +70 -31
- package/skills/mu/recipes/adversarial-review.md +27 -19
- package/skills/mu/recipes/{triage.md → backlog-triage.md} +9 -4
- package/skills/mu/recipes/codemode-driver.md +68 -20
- package/skills/mu/recipes/deep-research.md +23 -8
- package/skills/mu/recipes/fan-out.md +15 -11
- package/skills/mu/recipes/findings.md +103 -0
- package/skills/mu/recipes/hypothesis-panel.md +18 -13
- package/skills/mu/recipes/long-run.md +4 -4
- package/skills/mu/recipes/loop-until-done.md +7 -3
- package/skills/mu/recipes/orchestrator-loop.md +44 -27
- package/skills/mu/recipes/plan.md +8 -3
- package/skills/mu/recipes/recovery.md +5 -0
- package/skills/mu/recipes/refute.md +38 -36
- package/skills/mu/recipes/remote-workers.md +7 -2
- package/skills/mu/recipes/review-panel.md +36 -31
- package/skills/mu/recipes/rules-audit.md +30 -17
- package/skills/mu/recipes/tasks-or-calls.md +71 -0
- package/skills/mu/recipes/tournament.md +13 -10
- package/skills/mu/recipes/ultrathink.md +20 -11
- package/skills/mu/recipes/watcher.md +4 -1
- package/skills/mu/recipes/waves.md +6 -5
- package/skills/mu/recipes/worker.md +3 -1
package/dist/extension/mu-pi.js
CHANGED
|
@@ -51,10 +51,22 @@ var DELEGATE_CANCEL_TOOL = "mu_delegate_cancel";
|
|
|
51
51
|
var DELEGATE_MESSAGE_TYPE = "mu-delegate";
|
|
52
52
|
var DELEGATE_WORKSTREAM = "scratch";
|
|
53
53
|
var DELEGATE_STATUS_KEY = "mu-delegate";
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
var PENDING_CHANNEL = "murmur:pending";
|
|
55
|
+
function delegateStatus(running, starting = 0, queued = 0, failed = 0) {
|
|
56
|
+
const parts = [];
|
|
57
|
+
if (running > 0) parts.push(`${running} delegate${running === 1 ? "" : "s"} running`);
|
|
58
|
+
if (starting > 0) parts.push(`${starting} starting`);
|
|
59
|
+
if (queued > 0) parts.push(`${queued} queued`);
|
|
60
|
+
if (failed > 0) parts.push(`${failed} failed`);
|
|
61
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
56
62
|
}
|
|
57
63
|
var DELEGATE_TIMEOUT_S = 3600;
|
|
64
|
+
var DELEGATE_MAX_DEFAULT = 16;
|
|
65
|
+
var DELEGATE_QUEUE_FACTOR = 4;
|
|
66
|
+
function delegateMax(env = process.env) {
|
|
67
|
+
const n = Number(env.MU_DELEGATE_MAX);
|
|
68
|
+
return Number.isInteger(n) && n > 0 ? n : DELEGATE_MAX_DEFAULT;
|
|
69
|
+
}
|
|
58
70
|
var W = DELEGATE_WORKSTREAM;
|
|
59
71
|
function delegateEnabled(env = process.env) {
|
|
60
72
|
if (env.MU_MANAGED_AGENT) return false;
|
|
@@ -101,7 +113,7 @@ function formatElapsed(ms) {
|
|
|
101
113
|
}
|
|
102
114
|
function delegateMessage(name, agent, pane, x = {}) {
|
|
103
115
|
const timeoutS = x.timeoutS ?? DELEGATE_TIMEOUT_S;
|
|
104
|
-
const who = `Delegate ${W}/${name}`;
|
|
116
|
+
const who = `Delegate ${W}/${name}${x.queuedAs ? ` (queued as ${x.queuedAs})` : ""}`;
|
|
105
117
|
const took = x.elapsedMs !== void 0 ? ` after ${formatElapsed(x.elapsedMs)}` : "";
|
|
106
118
|
const fate = pane.closed ? "Pane closed." : `Pane kept (${pane.why}).`;
|
|
107
119
|
const look = [
|
|
@@ -120,6 +132,13 @@ ${x.tail.trimEnd()}` : "";
|
|
|
120
132
|
${agent.lastText ?? ""}`;
|
|
121
133
|
case "empty":
|
|
122
134
|
return `${who} finished${took} without a text answer. ${fate}${ws}${tailBlock}`;
|
|
135
|
+
case "error":
|
|
136
|
+
return [
|
|
137
|
+
`${who} stopped on an API error${took}, after pi's own retries: ${(agent.lastError ?? "unknown error").replace(/[.\s]+$/, "")}. ${fate}${ws}`,
|
|
138
|
+
"Decide: re-issue the call, or record the check as UNVERIFIED.",
|
|
139
|
+
...look,
|
|
140
|
+
`Close: mu agent close ${name} -w ${W}`
|
|
141
|
+
].join("\n");
|
|
123
142
|
case "died":
|
|
124
143
|
return `${who} died${took} before answering. ${fate}${ws}${tailBlock}`;
|
|
125
144
|
case "timeout":
|
|
@@ -141,11 +160,29 @@ function labelStem(label) {
|
|
|
141
160
|
function registerDelegate(pi, run = defaultRunner()) {
|
|
142
161
|
if (!delegateEnabled()) return;
|
|
143
162
|
const inflight = /* @__PURE__ */ new Map();
|
|
163
|
+
let starting = 0;
|
|
164
|
+
const queue = [];
|
|
165
|
+
let queueSeq = 0;
|
|
166
|
+
const failed = /* @__PURE__ */ new Set();
|
|
144
167
|
const reserved = /* @__PURE__ */ new Set();
|
|
145
168
|
let ui;
|
|
169
|
+
let lastPending = 0;
|
|
170
|
+
const reportPending = () => {
|
|
171
|
+
const count = inflight.size + starting + queue.length;
|
|
172
|
+
if (count === lastPending) return;
|
|
173
|
+
lastPending = count;
|
|
174
|
+
try {
|
|
175
|
+
pi.events.emit(PENDING_CHANNEL, { source: DELEGATE_TOOL, count });
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
};
|
|
146
179
|
const showStatus = () => {
|
|
180
|
+
reportPending();
|
|
147
181
|
try {
|
|
148
|
-
ui?.setStatus?.(
|
|
182
|
+
ui?.setStatus?.(
|
|
183
|
+
DELEGATE_STATUS_KEY,
|
|
184
|
+
delegateStatus(inflight.size, starting, queue.length, failed.size)
|
|
185
|
+
);
|
|
149
186
|
} catch {
|
|
150
187
|
}
|
|
151
188
|
};
|
|
@@ -155,8 +192,34 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
155
192
|
const forget = (name) => {
|
|
156
193
|
inflight.delete(name);
|
|
157
194
|
reserved.delete(name);
|
|
195
|
+
drain();
|
|
158
196
|
showStatus();
|
|
159
197
|
};
|
|
198
|
+
function drain() {
|
|
199
|
+
while (queue.length > 0 && inflight.size + starting < delegateMax()) {
|
|
200
|
+
const q = queue.shift();
|
|
201
|
+
if (!q) break;
|
|
202
|
+
starting++;
|
|
203
|
+
void start(q.params, void 0, q.ctx, q.handle).then(
|
|
204
|
+
() => {
|
|
205
|
+
},
|
|
206
|
+
(e) => {
|
|
207
|
+
void Promise.resolve(
|
|
208
|
+
pi.sendMessage(
|
|
209
|
+
{
|
|
210
|
+
customType: DELEGATE_MESSAGE_TYPE,
|
|
211
|
+
content: `Queued delegate ${q.handle} could not start: ${e instanceof Error ? e.message : String(e)}`,
|
|
212
|
+
display: true,
|
|
213
|
+
details: { name: q.handle, workstream: W, outcome: null, closed: false }
|
|
214
|
+
},
|
|
215
|
+
{ deliverAs: "followUp", triggerTurn: true }
|
|
216
|
+
)
|
|
217
|
+
).catch(() => {
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
160
223
|
const mu = run;
|
|
161
224
|
const json = (r) => {
|
|
162
225
|
try {
|
|
@@ -173,6 +236,10 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
173
236
|
const taken = new Set(
|
|
174
237
|
Array.isArray(agents) ? agents.map((a) => a.name).filter((n) => n) : []
|
|
175
238
|
);
|
|
239
|
+
if (Array.isArray(agents)) {
|
|
240
|
+
for (const f of [...failed]) if (!taken.has(f)) failed.delete(f);
|
|
241
|
+
showStatus();
|
|
242
|
+
}
|
|
176
243
|
for (let n = 1; ; n++) {
|
|
177
244
|
const name = stem ? n === 1 ? `delegate-${stem}` : `delegate-${stem}-${n}` : `delegate-${n}`;
|
|
178
245
|
if (!taken.has(name) && !reserved.has(name)) {
|
|
@@ -224,7 +291,7 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
224
291
|
async function deliver(name, wait, keep, extras) {
|
|
225
292
|
const agent = json(wait)?.agents?.[0];
|
|
226
293
|
const outcome = agent?.outcome;
|
|
227
|
-
const tail = outcome === "done" || outcome === "timeout" || outcome === "pending" ? void 0 : (await mu(["agent", "read", name, "-n", "50", "-w", W])).stdout;
|
|
294
|
+
const tail = outcome === "done" || outcome === "timeout" || outcome === "pending" || outcome === "error" ? void 0 : (await mu(["agent", "read", name, "-n", "50", "-w", W])).stdout;
|
|
228
295
|
let pane = { closed: false, why: "keep: true" };
|
|
229
296
|
if (outcome !== "done" && outcome !== "empty")
|
|
230
297
|
pane = { closed: false, why: "kept as evidence" };
|
|
@@ -232,6 +299,10 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
232
299
|
const c = await mu(["agent", "close", name, "-w", W]);
|
|
233
300
|
pane = c.code === 0 ? { closed: true } : { closed: false, why: `close refused: ${(c.stderr || c.stdout).trim()}` };
|
|
234
301
|
}
|
|
302
|
+
if (outcome === "error" && !pane.closed) {
|
|
303
|
+
failed.add(name);
|
|
304
|
+
showStatus();
|
|
305
|
+
}
|
|
235
306
|
await pi.sendMessage(
|
|
236
307
|
{
|
|
237
308
|
customType: DELEGATE_MESSAGE_TYPE,
|
|
@@ -250,52 +321,18 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
250
321
|
{ deliverAs: "followUp", triggerTurn: true }
|
|
251
322
|
);
|
|
252
323
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
description: "Short label naming the delegate, e.g. 'review' \u2192 delegate-review. Use one per subtask when fanning out."
|
|
265
|
-
},
|
|
266
|
-
brief: {
|
|
267
|
-
type: "string",
|
|
268
|
-
description: "Optional standing role or rules, sent before the task, e.g. 'You are a reviewer; read only.' The rules are instructions, not enforced. The job itself goes in task."
|
|
269
|
-
},
|
|
270
|
-
cwd: {
|
|
271
|
-
type: "string",
|
|
272
|
-
description: "Working directory for the delegate (default: yours; ignored with workspace)"
|
|
273
|
-
},
|
|
274
|
-
timeout: {
|
|
275
|
-
type: "number",
|
|
276
|
-
exclusiveMinimum: 0,
|
|
277
|
-
description: `Seconds to wait for the answer (default ${DELEGATE_TIMEOUT_S}). On timeout you get a "still running" follow-up instead of the answer, and the answer will not arrive later; the subagent keeps running in its pane.`
|
|
278
|
-
},
|
|
279
|
-
workspace: {
|
|
280
|
-
type: "boolean",
|
|
281
|
-
description: "For tasks that edit files: its own VCS checkout (jj workspace / git worktree / sl share) branched from the current commit. The result and follow-up name the path; changes stay there until you merge them. This isolates repository edits only: the subagent runs as you, with your files, environment and network."
|
|
282
|
-
},
|
|
283
|
-
cli: {
|
|
284
|
-
type: "string",
|
|
285
|
-
description: "Agent CLI key (default pi). Name a configured key such as 'pi_fast' to run a cheaper model."
|
|
286
|
-
},
|
|
287
|
-
keep: {
|
|
288
|
-
type: "boolean",
|
|
289
|
-
description: "Keep the pane after it finishes, to talk to it again"
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
required: ["task"],
|
|
293
|
-
additionalProperties: false
|
|
294
|
-
},
|
|
295
|
-
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
296
|
-
track(ctx);
|
|
324
|
+
async function start(params, signal, ctx, queuedAs) {
|
|
325
|
+
let slotHeld = true;
|
|
326
|
+
const freeSlot = () => {
|
|
327
|
+
if (slotHeld) {
|
|
328
|
+
starting--;
|
|
329
|
+
showStatus();
|
|
330
|
+
}
|
|
331
|
+
slotHeld = false;
|
|
332
|
+
};
|
|
333
|
+
showStatus();
|
|
334
|
+
try {
|
|
297
335
|
const task = typeof params.task === "string" ? params.task.trim() : "";
|
|
298
|
-
if (!task) throw new Error("mu_delegate needs a non-empty task");
|
|
299
336
|
const brief = typeof params.brief === "string" ? params.brief.trim() : "";
|
|
300
337
|
const keep = params.keep === true;
|
|
301
338
|
const t = params.timeout;
|
|
@@ -312,6 +349,7 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
312
349
|
const { name, attach, workspace } = await spawn(params, cwd, signal);
|
|
313
350
|
const entry = { abort: new AbortController(), cancelling: false };
|
|
314
351
|
inflight.set(name, entry);
|
|
352
|
+
freeSlot();
|
|
315
353
|
showStatus();
|
|
316
354
|
const started = Date.now();
|
|
317
355
|
const waiting = mu(
|
|
@@ -334,6 +372,7 @@ ${task}` : task, "-w", W]);
|
|
|
334
372
|
await deliver(name, r, keep, {
|
|
335
373
|
timeoutS,
|
|
336
374
|
elapsedMs: Date.now() - started,
|
|
375
|
+
...queuedAs ? { queuedAs } : {},
|
|
337
376
|
...attach ? { attach } : {},
|
|
338
377
|
...workspace ? { workspace } : {}
|
|
339
378
|
});
|
|
@@ -373,6 +412,94 @@ ${task}` : task, "-w", W]);
|
|
|
373
412
|
keep
|
|
374
413
|
}
|
|
375
414
|
};
|
|
415
|
+
} finally {
|
|
416
|
+
freeSlot();
|
|
417
|
+
if (queuedAs) drain();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
pi.registerTool({
|
|
421
|
+
name: DELEGATE_TOOL,
|
|
422
|
+
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.`,
|
|
424
|
+
promptSnippet: "Subagent: delegate a self-contained task to a background pi agent; its answer arrives later as a follow-up",
|
|
425
|
+
parameters: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
task: { type: "string", description: "The complete task, with all context it needs" },
|
|
429
|
+
label: {
|
|
430
|
+
type: "string",
|
|
431
|
+
description: "Short label naming the delegate, e.g. 'review' \u2192 delegate-review. Use one per subtask when fanning out."
|
|
432
|
+
},
|
|
433
|
+
brief: {
|
|
434
|
+
type: "string",
|
|
435
|
+
description: "Optional standing role or rules, sent before the task, e.g. 'You are a reviewer; read only.' The rules are instructions, not enforced. The job itself goes in task."
|
|
436
|
+
},
|
|
437
|
+
cwd: {
|
|
438
|
+
type: "string",
|
|
439
|
+
description: "Working directory for the delegate (default: yours; ignored with workspace)"
|
|
440
|
+
},
|
|
441
|
+
timeout: {
|
|
442
|
+
type: "number",
|
|
443
|
+
exclusiveMinimum: 0,
|
|
444
|
+
description: `Seconds to wait for the answer (default ${DELEGATE_TIMEOUT_S}). On timeout you get a "still running" follow-up instead of the answer, and the answer will not arrive later; the subagent keeps running in its pane.`
|
|
445
|
+
},
|
|
446
|
+
workspace: {
|
|
447
|
+
type: "boolean",
|
|
448
|
+
description: "For tasks that edit files: its own VCS checkout (jj workspace / git worktree / sl share) branched from the current commit. The result and follow-up name the path; changes stay there until you merge them. This isolates repository edits only: the subagent runs as you, with your files, environment and network."
|
|
449
|
+
},
|
|
450
|
+
cli: {
|
|
451
|
+
type: "string",
|
|
452
|
+
description: "Agent CLI key (default pi). Name a configured key such as 'pi_fast' to run a cheaper model."
|
|
453
|
+
},
|
|
454
|
+
keep: {
|
|
455
|
+
type: "boolean",
|
|
456
|
+
description: "Keep the pane after it finishes, to talk to it again"
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
required: ["task"],
|
|
460
|
+
additionalProperties: false
|
|
461
|
+
},
|
|
462
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
463
|
+
track(ctx);
|
|
464
|
+
const task = typeof params.task === "string" ? params.task.trim() : "";
|
|
465
|
+
if (!task) throw new Error("mu_delegate needs a non-empty task");
|
|
466
|
+
const t = params.timeout;
|
|
467
|
+
if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
|
|
468
|
+
throw new Error(
|
|
469
|
+
`mu_delegate: timeout must be a positive number of seconds (got ${JSON.stringify(t)})`
|
|
470
|
+
);
|
|
471
|
+
if (params.workspace !== true) {
|
|
472
|
+
const cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
|
|
473
|
+
if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
|
|
474
|
+
}
|
|
475
|
+
const max = delegateMax();
|
|
476
|
+
if (inflight.size + starting < max) {
|
|
477
|
+
starting++;
|
|
478
|
+
return start(params, signal, ctx);
|
|
479
|
+
}
|
|
480
|
+
if (queue.length >= DELEGATE_QUEUE_FACTOR * max)
|
|
481
|
+
throw new Error(
|
|
482
|
+
`mu_delegate: ${inflight.size + starting} running and ${queue.length} queued (MU_DELEGATE_MAX=${max}). End your turn; issue the rest as answers arrive (each answer resumes you).`
|
|
483
|
+
);
|
|
484
|
+
const handle = `queued-${++queueSeq}`;
|
|
485
|
+
queue.push({
|
|
486
|
+
handle,
|
|
487
|
+
params: {
|
|
488
|
+
...params,
|
|
489
|
+
cwd: typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd()
|
|
490
|
+
},
|
|
491
|
+
ctx
|
|
492
|
+
});
|
|
493
|
+
showStatus();
|
|
494
|
+
return {
|
|
495
|
+
content: [
|
|
496
|
+
{
|
|
497
|
+
type: "text",
|
|
498
|
+
text: `Queued as ${handle} (#${queue.length}; ${inflight.size + starting} running, MU_DELEGATE_MAX=${max}). It starts when a slot frees, and its answer arrives as a follow-up message like any delegate's; do not wait or poll. Cancel: ${DELEGATE_CANCEL_TOOL} { name: "${handle}" }`
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
details: { name: handle, workstream: W, queued: true, position: queue.length }
|
|
502
|
+
};
|
|
376
503
|
}
|
|
377
504
|
});
|
|
378
505
|
pi.registerTool({
|
|
@@ -392,6 +519,15 @@ ${task}` : task, "-w", W]);
|
|
|
392
519
|
track(ctx);
|
|
393
520
|
const name = typeof params.name === "string" ? params.name : "";
|
|
394
521
|
if (!name) throw new Error("mu_delegate_cancel needs a name");
|
|
522
|
+
const qi = queue.findIndex((q) => q.handle === name);
|
|
523
|
+
if (qi >= 0) {
|
|
524
|
+
queue.splice(qi, 1);
|
|
525
|
+
showStatus();
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: "text", text: `Dropped ${name} from the queue; it never started.` }],
|
|
528
|
+
details: { name, workstream: W }
|
|
529
|
+
};
|
|
530
|
+
}
|
|
395
531
|
const entry = inflight.get(name);
|
|
396
532
|
if (entry) entry.cancelling = true;
|
|
397
533
|
const ab = await mu(["agent", "abort", name, "-w", W]);
|
|
@@ -408,6 +544,7 @@ ${task}` : task, "-w", W]);
|
|
|
408
544
|
entry.abort.abort();
|
|
409
545
|
forget(name);
|
|
410
546
|
}
|
|
547
|
+
failed.delete(name);
|
|
411
548
|
let text = `Aborted ${W}/${name}.`;
|
|
412
549
|
if (params.keep === true) text += " Pane kept.";
|
|
413
550
|
else {
|
|
@@ -418,13 +555,21 @@ ${task}` : task, "-w", W]);
|
|
|
418
555
|
}
|
|
419
556
|
});
|
|
420
557
|
pi.on("session_shutdown", (_e, ctx) => {
|
|
421
|
-
if (inflight.size === 0) return;
|
|
558
|
+
if (inflight.size === 0 && queue.length === 0) return;
|
|
422
559
|
const names = [...inflight.keys()];
|
|
560
|
+
const never = queue.splice(0).map((q) => q.handle);
|
|
423
561
|
for (const e of inflight.values()) e.abort.abort();
|
|
424
562
|
inflight.clear();
|
|
425
563
|
showStatus();
|
|
426
564
|
ui = void 0;
|
|
427
|
-
const
|
|
565
|
+
const parts = [];
|
|
566
|
+
if (names.length > 0)
|
|
567
|
+
parts.push(
|
|
568
|
+
`${names.length} delegate${names.length === 1 ? "" : "s"} still running: ${names.join(", ")}. Their answers will not come back here; use mu agent wait <name> -w ${W} --json.`
|
|
569
|
+
);
|
|
570
|
+
if (never.length > 0)
|
|
571
|
+
parts.push(`${never.length} queued, never started: ${never.join(", ")}. Re-issue them.`);
|
|
572
|
+
const msg = parts.join(" ");
|
|
428
573
|
if (ctx?.hasUI && ctx.ui) ctx.ui.notify(msg, "warning");
|
|
429
574
|
else process.stderr.write(`mu: ${msg}
|
|
430
575
|
`);
|
|
@@ -561,7 +706,7 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
|
561
706
|
}
|
|
562
707
|
|
|
563
708
|
// extension/mu-pi.ts
|
|
564
|
-
var EXT_VERSION = true ? "3.
|
|
709
|
+
var EXT_VERSION = true ? "3.5.0" : void 0;
|
|
565
710
|
var FRESH_COMMAND = "mu-fresh";
|
|
566
711
|
var FRESH_TIMEOUT_MS = 3e4;
|
|
567
712
|
function commandName(name) {
|
|
@@ -632,6 +777,19 @@ function lastAssistantText(event) {
|
|
|
632
777
|
}
|
|
633
778
|
return "";
|
|
634
779
|
}
|
|
780
|
+
function lastAssistantError(event) {
|
|
781
|
+
const msgs = typeof event === "object" && event !== null ? event.messages : void 0;
|
|
782
|
+
if (!Array.isArray(msgs)) return void 0;
|
|
783
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
784
|
+
const m = msgs[i];
|
|
785
|
+
if (typeof m !== "object" || m === null) continue;
|
|
786
|
+
const { role, stopReason, errorMessage } = m;
|
|
787
|
+
if (role !== "assistant") continue;
|
|
788
|
+
if (stopReason !== "error") return void 0;
|
|
789
|
+
return typeof errorMessage === "string" && errorMessage !== "" ? capText(errorMessage) : "unknown error";
|
|
790
|
+
}
|
|
791
|
+
return void 0;
|
|
792
|
+
}
|
|
635
793
|
function capText(text) {
|
|
636
794
|
if (Buffer.byteLength(text, "utf8") <= LAST_TEXT_MAX_BYTES) return text;
|
|
637
795
|
const cut = Buffer.from(text, "utf8").subarray(0, LAST_TEXT_MAX_BYTES).toString("utf8");
|
|
@@ -739,6 +897,7 @@ function serveCtl(pi) {
|
|
|
739
897
|
});
|
|
740
898
|
pi.on("agent_end", (e) => {
|
|
741
899
|
g.endText = lastAssistantText(e);
|
|
900
|
+
g.endError = lastAssistantError(e);
|
|
742
901
|
});
|
|
743
902
|
pi.on("agent_settled", (_e, c) => {
|
|
744
903
|
g.ctx = c;
|
|
@@ -747,6 +906,8 @@ function serveCtl(pi) {
|
|
|
747
906
|
g.runs++;
|
|
748
907
|
g.lastText = g.endText ?? "";
|
|
749
908
|
g.endText = void 0;
|
|
909
|
+
g.lastError = g.endError;
|
|
910
|
+
g.endError = void 0;
|
|
750
911
|
for (const w of [...g.waiters]) w();
|
|
751
912
|
});
|
|
752
913
|
pi.registerCommand(FRESH_COMMAND, {
|
|
@@ -885,9 +1046,13 @@ function serveCtl(pi) {
|
|
|
885
1046
|
return { ...fail(`${UNKNOWN_OP_PREFIX}${String(req.op)}`), ops: [...CTL_OPS] };
|
|
886
1047
|
}
|
|
887
1048
|
}
|
|
1049
|
+
const settledRun = () => ({
|
|
1050
|
+
lastText: g.lastText,
|
|
1051
|
+
...g.lastError !== void 0 ? { lastError: g.lastError } : {}
|
|
1052
|
+
});
|
|
888
1053
|
function wait(afterRuns, timeoutMs, conn) {
|
|
889
1054
|
if (afterRuns !== void 0 && g.runs > afterRuns && g.state === "idle") {
|
|
890
|
-
return Promise.resolve({ v: V, ok: true, ...status(),
|
|
1055
|
+
return Promise.resolve({ v: V, ok: true, ...status(), ...settledRun() });
|
|
891
1056
|
}
|
|
892
1057
|
return new Promise((resolve) => {
|
|
893
1058
|
let timer;
|
|
@@ -897,7 +1062,7 @@ function serveCtl(pi) {
|
|
|
897
1062
|
conn.off("close", onClose);
|
|
898
1063
|
resolve(r);
|
|
899
1064
|
};
|
|
900
|
-
const waiter = () => done({ v: V, ok: true, ...status(),
|
|
1065
|
+
const waiter = () => done({ v: V, ok: true, ...status(), ...settledRun() });
|
|
901
1066
|
const onClose = () => done(fail("client closed"));
|
|
902
1067
|
g.waiters.add(waiter);
|
|
903
1068
|
conn.on("close", onClose);
|
|
@@ -1012,6 +1177,7 @@ export {
|
|
|
1012
1177
|
LIVE_PROBE_MS,
|
|
1013
1178
|
commandName,
|
|
1014
1179
|
muPi as default,
|
|
1180
|
+
lastAssistantError,
|
|
1015
1181
|
lastAssistantText,
|
|
1016
1182
|
promptsDir
|
|
1017
1183
|
};
|