@mu-crew/mu 3.4.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/dist/cli.js +466 -249
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +244 -107
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/docs/VISION.md +3 -1
- package/docs/architecture/ops-log.md +26 -7
- package/docs/guide/cleanup.md +19 -0
- package/docs/guide/delegate.md +15 -3
- package/docs/reference/env.md +1 -1
- package/package.json +1 -1
- package/skills/mu/SKILL.md +2 -2
- package/skills/mu/recipes/orchestrator-loop.md +8 -2
- package/skills/mu/recipes/recovery.md +3 -0
- package/skills/mu/recipes/tasks-or-calls.md +9 -5
package/dist/extension/mu-pi.js
CHANGED
|
@@ -51,11 +51,18 @@ 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;
|
|
58
64
|
var DELEGATE_MAX_DEFAULT = 16;
|
|
65
|
+
var DELEGATE_QUEUE_FACTOR = 4;
|
|
59
66
|
function delegateMax(env = process.env) {
|
|
60
67
|
const n = Number(env.MU_DELEGATE_MAX);
|
|
61
68
|
return Number.isInteger(n) && n > 0 ? n : DELEGATE_MAX_DEFAULT;
|
|
@@ -106,7 +113,7 @@ function formatElapsed(ms) {
|
|
|
106
113
|
}
|
|
107
114
|
function delegateMessage(name, agent, pane, x = {}) {
|
|
108
115
|
const timeoutS = x.timeoutS ?? DELEGATE_TIMEOUT_S;
|
|
109
|
-
const who = `Delegate ${W}/${name}`;
|
|
116
|
+
const who = `Delegate ${W}/${name}${x.queuedAs ? ` (queued as ${x.queuedAs})` : ""}`;
|
|
110
117
|
const took = x.elapsedMs !== void 0 ? ` after ${formatElapsed(x.elapsedMs)}` : "";
|
|
111
118
|
const fate = pane.closed ? "Pane closed." : `Pane kept (${pane.why}).`;
|
|
112
119
|
const look = [
|
|
@@ -125,6 +132,13 @@ ${x.tail.trimEnd()}` : "";
|
|
|
125
132
|
${agent.lastText ?? ""}`;
|
|
126
133
|
case "empty":
|
|
127
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");
|
|
128
142
|
case "died":
|
|
129
143
|
return `${who} died${took} before answering. ${fate}${ws}${tailBlock}`;
|
|
130
144
|
case "timeout":
|
|
@@ -147,11 +161,28 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
147
161
|
if (!delegateEnabled()) return;
|
|
148
162
|
const inflight = /* @__PURE__ */ new Map();
|
|
149
163
|
let starting = 0;
|
|
164
|
+
const queue = [];
|
|
165
|
+
let queueSeq = 0;
|
|
166
|
+
const failed = /* @__PURE__ */ new Set();
|
|
150
167
|
const reserved = /* @__PURE__ */ new Set();
|
|
151
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
|
+
};
|
|
152
179
|
const showStatus = () => {
|
|
180
|
+
reportPending();
|
|
153
181
|
try {
|
|
154
|
-
ui?.setStatus?.(
|
|
182
|
+
ui?.setStatus?.(
|
|
183
|
+
DELEGATE_STATUS_KEY,
|
|
184
|
+
delegateStatus(inflight.size, starting, queue.length, failed.size)
|
|
185
|
+
);
|
|
155
186
|
} catch {
|
|
156
187
|
}
|
|
157
188
|
};
|
|
@@ -161,8 +192,34 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
161
192
|
const forget = (name) => {
|
|
162
193
|
inflight.delete(name);
|
|
163
194
|
reserved.delete(name);
|
|
195
|
+
drain();
|
|
164
196
|
showStatus();
|
|
165
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
|
+
}
|
|
166
223
|
const mu = run;
|
|
167
224
|
const json = (r) => {
|
|
168
225
|
try {
|
|
@@ -179,6 +236,10 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
179
236
|
const taken = new Set(
|
|
180
237
|
Array.isArray(agents) ? agents.map((a) => a.name).filter((n) => n) : []
|
|
181
238
|
);
|
|
239
|
+
if (Array.isArray(agents)) {
|
|
240
|
+
for (const f of [...failed]) if (!taken.has(f)) failed.delete(f);
|
|
241
|
+
showStatus();
|
|
242
|
+
}
|
|
182
243
|
for (let n = 1; ; n++) {
|
|
183
244
|
const name = stem ? n === 1 ? `delegate-${stem}` : `delegate-${stem}-${n}` : `delegate-${n}`;
|
|
184
245
|
if (!taken.has(name) && !reserved.has(name)) {
|
|
@@ -230,7 +291,7 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
230
291
|
async function deliver(name, wait, keep, extras) {
|
|
231
292
|
const agent = json(wait)?.agents?.[0];
|
|
232
293
|
const outcome = agent?.outcome;
|
|
233
|
-
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;
|
|
234
295
|
let pane = { closed: false, why: "keep: true" };
|
|
235
296
|
if (outcome !== "done" && outcome !== "empty")
|
|
236
297
|
pane = { closed: false, why: "kept as evidence" };
|
|
@@ -238,6 +299,10 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
238
299
|
const c = await mu(["agent", "close", name, "-w", W]);
|
|
239
300
|
pane = c.code === 0 ? { closed: true } : { closed: false, why: `close refused: ${(c.stderr || c.stdout).trim()}` };
|
|
240
301
|
}
|
|
302
|
+
if (outcome === "error" && !pane.closed) {
|
|
303
|
+
failed.add(name);
|
|
304
|
+
showStatus();
|
|
305
|
+
}
|
|
241
306
|
await pi.sendMessage(
|
|
242
307
|
{
|
|
243
308
|
customType: DELEGATE_MESSAGE_TYPE,
|
|
@@ -256,10 +321,106 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
256
321
|
{ deliverAs: "followUp", triggerTurn: true }
|
|
257
322
|
);
|
|
258
323
|
}
|
|
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 {
|
|
335
|
+
const task = typeof params.task === "string" ? params.task.trim() : "";
|
|
336
|
+
const brief = typeof params.brief === "string" ? params.brief.trim() : "";
|
|
337
|
+
const keep = params.keep === true;
|
|
338
|
+
const t = params.timeout;
|
|
339
|
+
if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
|
|
340
|
+
throw new Error(
|
|
341
|
+
`mu_delegate: timeout must be a positive number of seconds (got ${JSON.stringify(t)})`
|
|
342
|
+
);
|
|
343
|
+
const timeoutS = typeof t === "number" ? Math.ceil(t) : DELEGATE_TIMEOUT_S;
|
|
344
|
+
let cwd;
|
|
345
|
+
if (params.workspace !== true) {
|
|
346
|
+
cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
|
|
347
|
+
if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
|
|
348
|
+
}
|
|
349
|
+
const { name, attach, workspace } = await spawn(params, cwd, signal);
|
|
350
|
+
const entry = { abort: new AbortController(), cancelling: false };
|
|
351
|
+
inflight.set(name, entry);
|
|
352
|
+
freeSlot();
|
|
353
|
+
showStatus();
|
|
354
|
+
const started = Date.now();
|
|
355
|
+
const waiting = mu(
|
|
356
|
+
["agent", "wait", name, "-w", W, "--json", "--timeout", String(timeoutS)],
|
|
357
|
+
entry.abort.signal
|
|
358
|
+
);
|
|
359
|
+
const sent = await mu(["agent", "send", name, brief ? `${brief}
|
|
360
|
+
|
|
361
|
+
${task}` : task, "-w", W]);
|
|
362
|
+
if (sent.code !== 0) {
|
|
363
|
+
entry.abort.abort();
|
|
364
|
+
forget(name);
|
|
365
|
+
const c = await mu(["agent", "close", name, "-w", W]);
|
|
366
|
+
const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
|
|
367
|
+
throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
|
|
368
|
+
}
|
|
369
|
+
const settle = async (r) => {
|
|
370
|
+
forget(name);
|
|
371
|
+
try {
|
|
372
|
+
await deliver(name, r, keep, {
|
|
373
|
+
timeoutS,
|
|
374
|
+
elapsedMs: Date.now() - started,
|
|
375
|
+
...queuedAs ? { queuedAs } : {},
|
|
376
|
+
...attach ? { attach } : {},
|
|
377
|
+
...workspace ? { workspace } : {}
|
|
378
|
+
});
|
|
379
|
+
} catch (e) {
|
|
380
|
+
await Promise.resolve(
|
|
381
|
+
pi.sendMessage(
|
|
382
|
+
{
|
|
383
|
+
customType: DELEGATE_MESSAGE_TYPE,
|
|
384
|
+
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}`,
|
|
385
|
+
display: true,
|
|
386
|
+
details: { name, workstream: W, outcome: null, closed: false }
|
|
387
|
+
},
|
|
388
|
+
{ deliverAs: "followUp", triggerTurn: true }
|
|
389
|
+
)
|
|
390
|
+
).catch(() => {
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
void waiting.then(async (r) => {
|
|
395
|
+
if (inflight.get(name) !== entry) return;
|
|
396
|
+
if (entry.cancelling) entry.parked = () => settle(r);
|
|
397
|
+
else await settle(r);
|
|
398
|
+
});
|
|
399
|
+
const lines = [
|
|
400
|
+
`Delegated to ${W}/${name}. Its answer arrives as a follow-up message when it finishes; do not wait or poll.`,
|
|
401
|
+
workspace ? `Workspace: ${workspace}` : void 0,
|
|
402
|
+
attach ? `Attach: ${attach}` : void 0,
|
|
403
|
+
`Cancel: ${DELEGATE_CANCEL_TOOL} { name: "${name}" }`
|
|
404
|
+
].filter((x) => x !== void 0);
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
407
|
+
details: {
|
|
408
|
+
name,
|
|
409
|
+
workstream: W,
|
|
410
|
+
attach: attach ?? null,
|
|
411
|
+
workspace: workspace ?? null,
|
|
412
|
+
keep
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
} finally {
|
|
416
|
+
freeSlot();
|
|
417
|
+
if (queuedAs) drain();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
259
420
|
pi.registerTool({
|
|
260
421
|
name: DELEGATE_TOOL,
|
|
261
422
|
label: "mu delegate",
|
|
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()}
|
|
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.`,
|
|
263
424
|
promptSnippet: "Subagent: delegate a self-contained task to a background pi agent; its answer arrives later as a follow-up",
|
|
264
425
|
parameters: {
|
|
265
426
|
type: "object",
|
|
@@ -302,106 +463,43 @@ function registerDelegate(pi, run = defaultRunner()) {
|
|
|
302
463
|
track(ctx);
|
|
303
464
|
const task = typeof params.task === "string" ? params.task.trim() : "";
|
|
304
465
|
if (!task) throw new Error("mu_delegate needs a non-empty task");
|
|
305
|
-
const
|
|
306
|
-
if (
|
|
466
|
+
const t = params.timeout;
|
|
467
|
+
if (t !== void 0 && !(typeof t === "number" && Number.isFinite(t) && t > 0))
|
|
307
468
|
throw new Error(
|
|
308
|
-
`mu_delegate:
|
|
469
|
+
`mu_delegate: timeout must be a positive number of seconds (got ${JSON.stringify(t)})`
|
|
309
470
|
);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
|
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).`
|
|
339
483
|
);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
throw new Error(`${failure(`mu agent send to ${W}/${name}`, sent).message}. ${pane}`);
|
|
356
|
-
}
|
|
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
|
|
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}" }`
|
|
400
499
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
500
|
+
],
|
|
501
|
+
details: { name: handle, workstream: W, queued: true, position: queue.length }
|
|
502
|
+
};
|
|
405
503
|
}
|
|
406
504
|
});
|
|
407
505
|
pi.registerTool({
|
|
@@ -421,6 +519,15 @@ ${task}` : task,
|
|
|
421
519
|
track(ctx);
|
|
422
520
|
const name = typeof params.name === "string" ? params.name : "";
|
|
423
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
|
+
}
|
|
424
531
|
const entry = inflight.get(name);
|
|
425
532
|
if (entry) entry.cancelling = true;
|
|
426
533
|
const ab = await mu(["agent", "abort", name, "-w", W]);
|
|
@@ -437,6 +544,7 @@ ${task}` : task,
|
|
|
437
544
|
entry.abort.abort();
|
|
438
545
|
forget(name);
|
|
439
546
|
}
|
|
547
|
+
failed.delete(name);
|
|
440
548
|
let text = `Aborted ${W}/${name}.`;
|
|
441
549
|
if (params.keep === true) text += " Pane kept.";
|
|
442
550
|
else {
|
|
@@ -447,13 +555,21 @@ ${task}` : task,
|
|
|
447
555
|
}
|
|
448
556
|
});
|
|
449
557
|
pi.on("session_shutdown", (_e, ctx) => {
|
|
450
|
-
if (inflight.size === 0) return;
|
|
558
|
+
if (inflight.size === 0 && queue.length === 0) return;
|
|
451
559
|
const names = [...inflight.keys()];
|
|
560
|
+
const never = queue.splice(0).map((q) => q.handle);
|
|
452
561
|
for (const e of inflight.values()) e.abort.abort();
|
|
453
562
|
inflight.clear();
|
|
454
563
|
showStatus();
|
|
455
564
|
ui = void 0;
|
|
456
|
-
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(" ");
|
|
457
573
|
if (ctx?.hasUI && ctx.ui) ctx.ui.notify(msg, "warning");
|
|
458
574
|
else process.stderr.write(`mu: ${msg}
|
|
459
575
|
`);
|
|
@@ -590,7 +706,7 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill()) {
|
|
|
590
706
|
}
|
|
591
707
|
|
|
592
708
|
// extension/mu-pi.ts
|
|
593
|
-
var EXT_VERSION = true ? "3.
|
|
709
|
+
var EXT_VERSION = true ? "3.5.0" : void 0;
|
|
594
710
|
var FRESH_COMMAND = "mu-fresh";
|
|
595
711
|
var FRESH_TIMEOUT_MS = 3e4;
|
|
596
712
|
function commandName(name) {
|
|
@@ -661,6 +777,19 @@ function lastAssistantText(event) {
|
|
|
661
777
|
}
|
|
662
778
|
return "";
|
|
663
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
|
+
}
|
|
664
793
|
function capText(text) {
|
|
665
794
|
if (Buffer.byteLength(text, "utf8") <= LAST_TEXT_MAX_BYTES) return text;
|
|
666
795
|
const cut = Buffer.from(text, "utf8").subarray(0, LAST_TEXT_MAX_BYTES).toString("utf8");
|
|
@@ -768,6 +897,7 @@ function serveCtl(pi) {
|
|
|
768
897
|
});
|
|
769
898
|
pi.on("agent_end", (e) => {
|
|
770
899
|
g.endText = lastAssistantText(e);
|
|
900
|
+
g.endError = lastAssistantError(e);
|
|
771
901
|
});
|
|
772
902
|
pi.on("agent_settled", (_e, c) => {
|
|
773
903
|
g.ctx = c;
|
|
@@ -776,6 +906,8 @@ function serveCtl(pi) {
|
|
|
776
906
|
g.runs++;
|
|
777
907
|
g.lastText = g.endText ?? "";
|
|
778
908
|
g.endText = void 0;
|
|
909
|
+
g.lastError = g.endError;
|
|
910
|
+
g.endError = void 0;
|
|
779
911
|
for (const w of [...g.waiters]) w();
|
|
780
912
|
});
|
|
781
913
|
pi.registerCommand(FRESH_COMMAND, {
|
|
@@ -914,9 +1046,13 @@ function serveCtl(pi) {
|
|
|
914
1046
|
return { ...fail(`${UNKNOWN_OP_PREFIX}${String(req.op)}`), ops: [...CTL_OPS] };
|
|
915
1047
|
}
|
|
916
1048
|
}
|
|
1049
|
+
const settledRun = () => ({
|
|
1050
|
+
lastText: g.lastText,
|
|
1051
|
+
...g.lastError !== void 0 ? { lastError: g.lastError } : {}
|
|
1052
|
+
});
|
|
917
1053
|
function wait(afterRuns, timeoutMs, conn) {
|
|
918
1054
|
if (afterRuns !== void 0 && g.runs > afterRuns && g.state === "idle") {
|
|
919
|
-
return Promise.resolve({ v: V, ok: true, ...status(),
|
|
1055
|
+
return Promise.resolve({ v: V, ok: true, ...status(), ...settledRun() });
|
|
920
1056
|
}
|
|
921
1057
|
return new Promise((resolve) => {
|
|
922
1058
|
let timer;
|
|
@@ -926,7 +1062,7 @@ function serveCtl(pi) {
|
|
|
926
1062
|
conn.off("close", onClose);
|
|
927
1063
|
resolve(r);
|
|
928
1064
|
};
|
|
929
|
-
const waiter = () => done({ v: V, ok: true, ...status(),
|
|
1065
|
+
const waiter = () => done({ v: V, ok: true, ...status(), ...settledRun() });
|
|
930
1066
|
const onClose = () => done(fail("client closed"));
|
|
931
1067
|
g.waiters.add(waiter);
|
|
932
1068
|
conn.on("close", onClose);
|
|
@@ -1041,6 +1177,7 @@ export {
|
|
|
1041
1177
|
LIVE_PROBE_MS,
|
|
1042
1178
|
commandName,
|
|
1043
1179
|
muPi as default,
|
|
1180
|
+
lastAssistantError,
|
|
1044
1181
|
lastAssistantText,
|
|
1045
1182
|
promptsDir
|
|
1046
1183
|
};
|