@mu-crew/mu 3.11.3 → 3.13.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.
Files changed (60) hide show
  1. package/dist/chunk-3BARPJLY.js +44 -0
  2. package/dist/{chunk-R2SVBZAB.js → chunk-45KQLMEL.js} +193 -93
  3. package/dist/chunk-45KQLMEL.js.map +1 -0
  4. package/dist/{chunk-3L5KO3PX.js → chunk-54WJCMJR.js} +12 -10
  5. package/dist/chunk-54WJCMJR.js.map +1 -0
  6. package/dist/{chunk-LEORAGKV.js → chunk-EPCEEDHG.js} +4 -3
  7. package/dist/{chunk-LEORAGKV.js.map → chunk-EPCEEDHG.js.map} +1 -1
  8. package/dist/{chunk-77GBZ2KR.js → chunk-QITLNZEM.js} +3 -1
  9. package/dist/{chunk-77GBZ2KR.js.map → chunk-QITLNZEM.js.map} +1 -1
  10. package/dist/{chunk-F7ZBF6I7.js → chunk-TXV34VWT.js} +8616 -6030
  11. package/dist/chunk-TXV34VWT.js.map +1 -0
  12. package/dist/{chunk-L2ABBTYG.js → chunk-WI6AWCMK.js} +3 -1
  13. package/dist/{chunk-L2ABBTYG.js.map → chunk-WI6AWCMK.js.map} +1 -1
  14. package/dist/{cli-GJIO7GWD.js → cli-GMJUOXPW.js} +3582 -107
  15. package/dist/cli-GMJUOXPW.js.map +1 -0
  16. package/dist/cli.js +4 -3
  17. package/dist/cli.js.map +1 -1
  18. package/dist/devtools-LS4ZEPWM.js +3772 -0
  19. package/dist/devtools-LS4ZEPWM.js.map +1 -0
  20. package/dist/extension/mu-pi.js +294 -78
  21. package/dist/extension/mu-pi.js.map +1 -1
  22. package/dist/index.d.ts +311 -228
  23. package/dist/index.js +12 -5
  24. package/dist/tui-WSDDTA4K.js +40956 -0
  25. package/dist/tui-WSDDTA4K.js.map +1 -0
  26. package/dist/tui-sync-worker.js +4 -3
  27. package/dist/tui-sync-worker.js.map +1 -1
  28. package/dist/yank-XDOONQSL.js +13 -0
  29. package/docs/ARCHITECTURE.md +4 -1
  30. package/docs/ROADMAP.md +43 -38
  31. package/docs/VISION.md +8 -0
  32. package/docs/VOCABULARY.md +3 -1
  33. package/docs/architecture/control-socket.md +1 -1
  34. package/docs/architecture/dag.md +4 -0
  35. package/docs/architecture/sync.md +8 -0
  36. package/docs/architecture/tui.md +11 -2
  37. package/docs/guide/delegate.md +11 -4
  38. package/docs/guide/recovery.md +4 -0
  39. package/docs/reference/env.md +2 -2
  40. package/docs/reference/naming.md +1 -1
  41. package/package.json +2 -1
  42. package/skills/mu/SKILL.md +12 -2
  43. package/skills/mu/recipes/adversarial-review.md +3 -0
  44. package/skills/mu/recipes/backlog-triage.md +2 -0
  45. package/skills/mu/recipes/findings.md +7 -0
  46. package/skills/mu/recipes/long-run.md +3 -1
  47. package/skills/mu/recipes/loop-until-done.md +3 -1
  48. package/skills/mu/recipes/orchestrator-loop.md +4 -0
  49. package/skills/mu/recipes/ultrathink.md +3 -1
  50. package/skills/mu/recipes/watcher.md +6 -1
  51. package/dist/chunk-3L5KO3PX.js.map +0 -1
  52. package/dist/chunk-F7ZBF6I7.js.map +0 -1
  53. package/dist/chunk-PZ5AY32C.js +0 -10
  54. package/dist/chunk-R2SVBZAB.js.map +0 -1
  55. package/dist/cli-GJIO7GWD.js.map +0 -1
  56. package/dist/tui-WHCLVCYE.js +0 -6452
  57. package/dist/tui-WHCLVCYE.js.map +0 -1
  58. package/dist/yank-T3AYQTVR.js +0 -12
  59. /package/dist/{chunk-PZ5AY32C.js.map → chunk-3BARPJLY.js.map} +0 -0
  60. /package/dist/{yank-T3AYQTVR.js.map → yank-XDOONQSL.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  linkSync,
6
6
  mkdirSync as mkdirSync2,
7
7
  readdirSync,
8
- readFileSync as readFileSync2,
8
+ readFileSync as readFileSync3,
9
9
  realpathSync,
10
10
  rmSync,
11
11
  statSync as statSync2
@@ -44,7 +44,7 @@ var LineDecoder = class {
44
44
 
45
45
  // extension/delegate.ts
46
46
  import { execFile } from "child_process";
47
- import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
47
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
48
48
  import { delimiter, join as join2 } from "path";
49
49
  import { fileURLToPath } from "url";
50
50
 
@@ -75,6 +75,7 @@ function delegateStatus(running, starting = 0, queued = 0, failed = 0, pending =
75
75
  var DELEGATE_TIMEOUT_S = 3600;
76
76
  var DELEGATE_MAX_DEFAULT = 16;
77
77
  var DELEGATE_QUEUE_FACTOR = 4;
78
+ var PRUNE_FAILED_MS = 3e4;
78
79
  function delegateMax(env = process.env) {
79
80
  const n = Number(env.MU_DELEGATE_MAX);
80
81
  return Number.isInteger(n) && n > 0 ? n : DELEGATE_MAX_DEFAULT;
@@ -116,6 +117,37 @@ function defaultRunner() {
116
117
  );
117
118
  });
118
119
  }
120
+ function lastAssistantTextIn(sessionFile) {
121
+ let raw;
122
+ try {
123
+ raw = readFileSync(sessionFile, "utf8");
124
+ } catch {
125
+ return void 0;
126
+ }
127
+ const lines = raw.split("\n");
128
+ for (let i = lines.length - 1; i >= 0; i--) {
129
+ const line = lines[i];
130
+ if (!line?.includes('"assistant"')) continue;
131
+ try {
132
+ const e = JSON.parse(line);
133
+ if (e.message?.role !== "assistant") continue;
134
+ const c = e.message.content;
135
+ const text = typeof c === "string" ? c : Array.isArray(c) ? c.map((p) => {
136
+ const q = p;
137
+ return q.type === "text" && typeof q.text === "string" ? q.text : "";
138
+ }).join("") : "";
139
+ if (text.trim()) return text;
140
+ } catch {
141
+ }
142
+ }
143
+ return void 0;
144
+ }
145
+ function evidenceLines(x) {
146
+ return [
147
+ x.partial ? `Partial answer so far: ${x.partial}` : void 0,
148
+ x.sessionFile ? `Session: ${x.sessionFile}` : void 0
149
+ ].filter((v) => v !== void 0);
150
+ }
119
151
  function formatElapsed(ms) {
120
152
  const t = Math.max(0, Math.round(ms / 1e3));
121
153
  if (t < 60) return `${t}s`;
@@ -137,31 +169,49 @@ function delegateMessage(name, agent, pane, x = {}) {
137
169
  Pane tail:
138
170
  ${x.tail.trimEnd()}` : "";
139
171
  const ws = x.workspace ? ` Workspace: ${x.workspace}` : "";
172
+ const ev = evidenceLines(x);
173
+ const evBlock = ev.length > 0 ? `
174
+ ${ev.join("\n")}` : "";
140
175
  switch (agent?.outcome) {
141
176
  case "done":
142
177
  return `${who} finished${took}. ${fate}${ws}
143
178
 
144
179
  ${agent.lastText ?? ""}`;
145
180
  case "empty":
146
- return `${who} finished${took} without a text answer. ${fate}${ws}${tailBlock}`;
181
+ return `${who} finished${took} without a text answer. ${fate}${ws}${evBlock}${tailBlock}`;
147
182
  case "error":
148
183
  return [
149
184
  `${who} stopped on an API error${took}, after pi's own retries: ${(agent.lastError ?? "unknown error").replace(/[.\s]+$/, "")}. ${fate}${ws}`,
150
185
  "Decide: re-issue the call, or record the check as UNVERIFIED.",
186
+ ...ev,
151
187
  ...look,
152
188
  `Close: mu agent close ${name} -w ${W}`
153
189
  ].join("\n");
154
190
  case "died":
155
- return `${who} died${took} before answering. ${fate}${ws}${tailBlock}`;
191
+ return `${who} died${took} before answering. ${fate}${ws}${evBlock}${tailBlock}`;
192
+ case "blocked": {
193
+ const b = agent.blocked;
194
+ const dialog = `${b?.kind ?? "dialog"}${b?.title ? ` "${b.title}"` : ""}`;
195
+ const lines = [
196
+ `${who} is blocked on a ${dialog} dialog${took} and has NOT finished; stopped waiting, so its answer will not arrive here. ${fate}${ws}`,
197
+ "Answer the dialog in its pane (attach), then wait again; or close it and record the check as UNVERIFIED.",
198
+ ...ev,
199
+ ...look,
200
+ `Wait again: mu agent wait ${name} -w ${W} --json${agent.afterRuns !== void 0 ? ` --after-runs ${agent.afterRuns}` : ""}`,
201
+ `Close: mu agent close ${name} -w ${W}`
202
+ ];
203
+ return `${lines.join("\n")}${tailBlock}`;
204
+ }
156
205
  case "timeout":
157
206
  case "pending":
158
207
  return [
159
208
  `${who} is still running after ${timeoutS}s; stopped waiting, so its answer will not arrive here. ${fate}${ws}`,
209
+ ...ev,
160
210
  ...look,
161
- `Wait again: mu agent wait ${name} -w ${W} --json`
211
+ `Wait again: mu agent wait ${name} -w ${W} --json${agent.afterRuns !== void 0 ? ` --after-runs ${agent.afterRuns}` : ""}`
162
212
  ].join("\n");
163
213
  default:
164
- return `${who}: mu agent wait gave no result. ${fate}${ws}${tailBlock}`;
214
+ return `${who}: mu agent wait gave no result. ${fate}${ws}${evBlock}${tailBlock}`;
165
215
  }
166
216
  }
167
217
  function droppedAnswersDir() {
@@ -228,12 +278,13 @@ function modelCommand(cli, model, env = process.env) {
228
278
  return `${base} --model ${shellArg(model)}`;
229
279
  }
230
280
  function registerDelegate(pi, run = defaultRunner()) {
231
- if (!delegateEnabled()) return;
281
+ if (!delegateEnabled()) return { answerPending: () => false };
232
282
  const inflight = /* @__PURE__ */ new Map();
233
283
  let starting = 0;
234
284
  const queue = [];
235
285
  let queueSeq = 0;
236
286
  const failed = /* @__PURE__ */ new Set();
287
+ let lastPruneAt = 0;
237
288
  const arriving = /* @__PURE__ */ new Map();
238
289
  let deliverySeq = 0;
239
290
  let ui;
@@ -264,12 +315,12 @@ function registerDelegate(pi, run = defaultRunner()) {
264
315
  const a = arriving.get(delivery);
265
316
  if (a) a.queued = true;
266
317
  };
267
- const saveCopy = (name, content) => {
318
+ const saveCopy = (name, content, kind = "") => {
268
319
  try {
269
320
  const dir = droppedAnswersDir();
270
321
  mkdirSync(dir, { recursive: true });
271
322
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
272
- const path = join2(dir, `${stamp}_${name}.md`);
323
+ const path = join2(dir, `${stamp}_${name}${kind}.md`);
273
324
  writeFileSync(path, `${content}
274
325
  `);
275
326
  return path;
@@ -277,6 +328,10 @@ function registerDelegate(pi, run = defaultRunner()) {
277
328
  return void 0;
278
329
  }
279
330
  };
331
+ const partialCopy = (name, sessionFile) => {
332
+ const text = lastAssistantTextIn(sessionFile);
333
+ return text ? saveCopy(name, text, ".partial") : void 0;
334
+ };
280
335
  const forget = (name) => {
281
336
  inflight.delete(name);
282
337
  drain();
@@ -319,6 +374,9 @@ function registerDelegate(pi, run = defaultRunner()) {
319
374
  const failure = (what, r) => new Error(`${what} failed (exit ${r.code}): ${(r.stderr || r.stdout).trim()}`);
320
375
  function pruneFailed() {
321
376
  if (failed.size === 0) return;
377
+ const now = Date.now();
378
+ if (now - lastPruneAt < PRUNE_FAILED_MS) return;
379
+ lastPruneAt = now;
322
380
  void mu(["agent", "list", "-w", W, "--json"]).then((r) => {
323
381
  const agents = json(r)?.agents;
324
382
  if (!Array.isArray(agents)) return;
@@ -354,6 +412,12 @@ function registerDelegate(pi, run = defaultRunner()) {
354
412
  );
355
413
  }
356
414
  const sent = out.send;
415
+ if (sent?.ok !== true && sent?.error === "AgentSendIndeterminateError") {
416
+ const why = typeof sent.message === "string" ? sent.message : "no reply";
417
+ throw new Error(
418
+ `mu agent send to ${W}/${name}: delivery unknown (${why}). Pane kept; do NOT resend blindly. Check with: mu agent read ${name} -w ${W} -n 20 (or attach), then close it with: mu agent close ${name} -w ${W}`
419
+ );
420
+ }
357
421
  if (sent?.ok !== true) {
358
422
  const c = await mu(["agent", "close", name, "-w", W]);
359
423
  const pane = c.code === 0 ? "Pane closed." : `Pane kept; close it with: mu agent close ${name} -w ${W}`;
@@ -369,7 +433,8 @@ function registerDelegate(pi, run = defaultRunner()) {
369
433
  // extension's reply carries no runs.
370
434
  runs: typeof sent.runs === "number" ? sent.runs : 0,
371
435
  ...typeof attach === "string" ? { attach } : {},
372
- ...typeof wsPath === "string" ? { workspace: wsPath } : {}
436
+ ...typeof wsPath === "string" ? { workspace: wsPath } : {},
437
+ ...typeof sent.sessionFile === "string" ? { sessionFile: sent.sessionFile } : {}
373
438
  };
374
439
  }
375
440
  const recordLabel = (params, name) => typeof params.label === "string" && params.label.trim() ? params.label.trim() : name;
@@ -405,21 +470,32 @@ function registerDelegate(pi, run = defaultRunner()) {
405
470
  const outcome = agent?.outcome;
406
471
  const tail = outcome === "done" || outcome === "timeout" || outcome === "pending" || outcome === "error" ? void 0 : (await mu(["agent", "read", name, "-n", "50", "-w", W])).stdout;
407
472
  let pane = { closed: false, why: "keep: true" };
408
- if (outcome !== "done" && outcome !== "empty")
473
+ let closing;
474
+ if (outcome === "blocked") pane = { closed: false, why: "waiting on a dialog" };
475
+ else if (outcome !== "done" && outcome !== "empty")
409
476
  pane = { closed: false, why: "kept as evidence" };
410
- else if (!keep) {
411
- const c = await mu(["agent", "close", name, "-w", W]);
412
- pane = c.code === 0 ? { closed: true } : { closed: false, why: `close refused: ${(c.stderr || c.stdout).trim()}` };
413
- }
414
- if (outcome === "error" && !pane.closed) {
415
- failed.add(name);
416
- showStatus();
417
- }
418
- const recorded = record ? await writeRecord(
477
+ else if (!keep) closing = mu(["agent", "close", name, "-w", W]);
478
+ const recording = record ? writeRecord(
419
479
  record.target,
420
480
  name,
421
481
  recordNote(record.label, name, outcome, agent?.lastText, extras.elapsedMs ?? 0)
422
482
  ) : void 0;
483
+ const [closed, recorded] = await Promise.all([closing, recording]);
484
+ if (closed)
485
+ pane = closed.code === 0 ? { closed: true } : { closed: false, why: `close refused: ${(closed.stderr || closed.stdout).trim()}` };
486
+ if (outcome === "error" && !pane.closed) {
487
+ failed.add(name);
488
+ showStatus();
489
+ }
490
+ if (outcome !== "done") {
491
+ const sessionFile = agent?.sessionFile ?? extras.sessionFile;
492
+ const partial = sessionFile && outcome !== "blocked" ? partialCopy(name, sessionFile) : void 0;
493
+ extras = {
494
+ ...extras,
495
+ ...sessionFile ? { sessionFile } : {},
496
+ ...partial ? { partial } : {}
497
+ };
498
+ }
423
499
  const message = delegateMessage(name, agent, pane, { ...extras, ...tail ? { tail } : {} });
424
500
  const content = recorded ? `${message}
425
501
 
@@ -445,6 +521,8 @@ ${recorded}` : message;
445
521
  answer: outcome === "done" ? agent?.lastText ?? null : null,
446
522
  elapsedMs: extras.elapsedMs ?? null,
447
523
  workspace: extras.workspace ?? null,
524
+ sessionFile: agent?.sessionFile ?? extras.sessionFile ?? null,
525
+ partial: extras.partial ?? null,
448
526
  delivery
449
527
  }
450
528
  },
@@ -476,7 +554,7 @@ ${recorded}` : message;
476
554
  cwd = typeof params.cwd === "string" && params.cwd ? params.cwd : ctx?.cwd ?? process.cwd();
477
555
  if (!isDir(cwd)) throw new Error(`mu_delegate: cwd ${cwd} is not a directory`);
478
556
  }
479
- const { name, runs, attach, workspace } = await spawn(
557
+ const { name, runs, attach, workspace, sessionFile } = await spawn(
480
558
  params,
481
559
  brief ? `${brief}
482
560
 
@@ -487,6 +565,7 @@ ${task}` : task,
487
565
  const entry = {
488
566
  abort: new AbortController(),
489
567
  cancelling: false,
568
+ ...sessionFile ? { sessionFile } : {},
490
569
  ...record ? { record: { target: record, label: recordLabel(params, name) } } : {}
491
570
  };
492
571
  inflight.set(name, entry);
@@ -522,7 +601,8 @@ ${task}` : task,
522
601
  elapsedMs: Date.now() - started,
523
602
  ...queuedAs ? { queuedAs } : {},
524
603
  ...attach ? { attach } : {},
525
- ...workspace ? { workspace } : {}
604
+ ...workspace ? { workspace } : {},
605
+ ...entry.sessionFile ? { sessionFile: entry.sessionFile } : {}
526
606
  },
527
607
  delivery,
528
608
  entry.record
@@ -733,6 +813,12 @@ ${task}` : task,
733
813
  }
734
814
  failed.delete(name);
735
815
  let text = `Aborted ${W}/${name}.`;
816
+ const sessionFile = entry?.sessionFile;
817
+ const partial = sessionFile ? partialCopy(name, sessionFile) : void 0;
818
+ const evidence = evidenceLines({
819
+ ...sessionFile ? { sessionFile } : {},
820
+ ...partial ? { partial } : {}
821
+ });
736
822
  if (entry?.record)
737
823
  text += ` ${await writeRecord(entry.record.target, name, recordNote(entry.record.label, name, "cancelled", void 0, 0))}`;
738
824
  if (params.keep === true) text += " Pane kept.";
@@ -740,6 +826,8 @@ ${task}` : task,
740
826
  const c = await mu(["agent", "close", name, "-w", W]);
741
827
  text += c.code === 0 ? " Pane closed." : ` Pane kept: close refused: ${(c.stderr || c.stdout).trim()}`;
742
828
  }
829
+ if (evidence.length > 0) text += `
830
+ ${evidence.join("\n")}`;
743
831
  return { content: [{ type: "text", text }], details: { name, workstream: W } };
744
832
  }
745
833
  });
@@ -785,6 +873,9 @@ ${task}` : task,
785
873
  }
786
874
  if (inflight.size === 0 && queue.length === 0) return;
787
875
  const names = [...inflight.keys()];
876
+ const sessions = [...inflight].flatMap(
877
+ ([n, e]) => e.sessionFile ? [`${n}: ${e.sessionFile}`] : []
878
+ );
788
879
  const never = queue.splice(0).map((q) => q.handle);
789
880
  for (const e of inflight.values()) e.abort.abort();
790
881
  inflight.clear();
@@ -795,6 +886,7 @@ ${task}` : task,
795
886
  parts.push(
796
887
  `${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.`
797
888
  );
889
+ if (sessions.length > 0) parts.push(`Sessions: ${sessions.join("; ")}.`);
798
890
  if (never.length > 0)
799
891
  parts.push(`${never.length} queued, never started: ${never.join(", ")}. Re-issue them.`);
800
892
  const msg = parts.join(" ");
@@ -802,10 +894,11 @@ ${task}` : task,
802
894
  else process.stderr.write(`mu: ${msg}
803
895
  `);
804
896
  });
897
+ return { answerPending: () => arriving.size > 0 };
805
898
  }
806
899
 
807
900
  // extension/nudge.ts
808
- import { existsSync as existsSync2, readFileSync } from "fs";
901
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
809
902
  import { join as join3 } from "path";
810
903
  var NUDGE_MESSAGE_TYPE = "mu-keep-driving";
811
904
  var NUDGE_LOG_KIND = "nudge";
@@ -815,6 +908,10 @@ var MARK_START = "<!-- mu:keep-driving -->";
815
908
  var MARK_END = "<!-- /mu:keep-driving -->";
816
909
  var WAIT_HINT_MAX = 8;
817
910
  var notNested = () => false;
911
+ var noHold = () => void 0;
912
+ function withQueuedInput(hold) {
913
+ return (ctx) => ctx?.hasPendingMessages?.() ? "answer" : hold(ctx);
914
+ }
818
915
  function noUI(ctx) {
819
916
  return ctx?.hasUI === false;
820
917
  }
@@ -831,7 +928,7 @@ function keepDrivingRule(skillMd) {
831
928
  function readSkill() {
832
929
  for (const up of ["..", "../.."]) {
833
930
  const p = join3(import.meta.dirname ?? "", up, "skills", "mu", "SKILL.md");
834
- if (existsSync2(p)) return readFileSync(p, "utf8");
931
+ if (existsSync2(p)) return readFileSync2(p, "utf8");
835
932
  }
836
933
  return void 0;
837
934
  }
@@ -987,25 +1084,29 @@ function dispatchedTasks(command) {
987
1084
  return out;
988
1085
  }
989
1086
  async function inProgress(run, workstreams) {
990
- const named = workstreams.filter((w) => w !== "");
991
- const args = ["state", "--json", "--events", "0"];
992
- for (const w of named) args.push("-w", w);
993
- const r = await run(args);
994
- if (r.code !== 0) return void 0;
995
- let json;
996
- try {
997
- json = JSON.parse(r.stdout);
998
- } catch {
999
- return void 0;
1000
- }
1001
- const cards = json.workstreams ?? [json];
1002
- const refs = [];
1003
- for (const ws of cards) {
1004
- for (const t of ws.inProgress ?? []) {
1005
- if (t.name) refs.push(`${ws.workstreamName ?? ""}/${t.name}`);
1006
- }
1007
- }
1008
- return refs;
1087
+ const results = await Promise.all(
1088
+ workstreams.map(async (w) => {
1089
+ const r = await run([
1090
+ "task",
1091
+ "list",
1092
+ "--status",
1093
+ "IN_PROGRESS",
1094
+ "--json",
1095
+ "--sort",
1096
+ "recency",
1097
+ ...w ? ["-w", w] : []
1098
+ ]);
1099
+ if (r.code !== 0) return void 0;
1100
+ try {
1101
+ const items = JSON.parse(r.stdout).items ?? [];
1102
+ return items.flatMap((t) => t.name ? [`${t.workstreamName ?? w}/${t.name}`] : []);
1103
+ } catch {
1104
+ return void 0;
1105
+ }
1106
+ })
1107
+ );
1108
+ if (results.some((r) => r === void 0)) return void 0;
1109
+ return [...new Set(results.flatMap((r) => r ?? []))];
1009
1110
  }
1010
1111
  function nudgeText(rule, refs) {
1011
1112
  const shown = refs.slice(0, WAIT_HINT_MAX).join(" ");
@@ -1020,7 +1121,7 @@ function breadcrumb(run, args) {
1020
1121
  void run(args).catch(() => {
1021
1122
  });
1022
1123
  }
1023
- function parallelSettle(pi) {
1124
+ function parallelSettle(pi, hold = noHold) {
1024
1125
  const handlers = [];
1025
1126
  const api = {
1026
1127
  on(event, handler) {
@@ -1045,7 +1146,15 @@ function parallelSettle(pi) {
1045
1146
  }
1046
1147
  })
1047
1148
  );
1048
- const fired = results.filter((r) => r !== void 0);
1149
+ const late = hold(ctx);
1150
+ const fired = results.flatMap((r) => {
1151
+ if (r === void 0) return [];
1152
+ if (late === "answer" || late === "blocked" && r.continue === true) {
1153
+ r.yielded?.();
1154
+ return [];
1155
+ }
1156
+ return [r];
1157
+ });
1049
1158
  if (fired.length === 0) return void 0;
1050
1159
  const prior = event?.entries ?? [];
1051
1160
  return {
@@ -1072,7 +1181,7 @@ function bashCommand(event) {
1072
1181
  const e = event;
1073
1182
  return e.toolName === "bash" && typeof e.input?.command === "string" ? e.input.command : void 0;
1074
1183
  }
1075
- function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested = notNested) {
1184
+ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested = notNested, hold = noHold) {
1076
1185
  if (!nudgeEnabled()) return;
1077
1186
  const rule = skillMd === void 0 ? void 0 : keepDrivingRule(skillMd);
1078
1187
  if (rule === void 0) return;
@@ -1087,12 +1196,16 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested
1087
1196
  if (cmd === void 0) return;
1088
1197
  for (const ws of dispatchedWorkstreams(cmd) ?? []) armed.add(ws);
1089
1198
  });
1090
- pi.on("agent_before_settle", async (event) => {
1091
- if (fired || armed.size === 0 || nested()) return;
1199
+ pi.on("agent_before_settle", async (event, ctx) => {
1200
+ if (fired || armed.size === 0 || nested() || hold(ctx)) return;
1092
1201
  if (outcomeOf(event) !== "completed") return;
1093
1202
  fired = true;
1094
1203
  const refs = await inProgress(run, [...armed]);
1095
1204
  if (refs === void 0 || refs.length === 0) return;
1205
+ if (hold(ctx)) {
1206
+ fired = false;
1207
+ return;
1208
+ }
1096
1209
  const text = nudgeText(rule, refs);
1097
1210
  const logWs = [...armed].find((w) => w !== "");
1098
1211
  breadcrumb(run, [
@@ -1106,7 +1219,10 @@ function registerNudge(pi, run = defaultRunner(), skillMd = readSkill(), nested
1106
1219
  entries: [
1107
1220
  { type: "custom_message", customType: NUDGE_MESSAGE_TYPE, content: text, display: true }
1108
1221
  ],
1109
- continue: true
1222
+ continue: true,
1223
+ yielded: () => {
1224
+ fired = false;
1225
+ }
1110
1226
  };
1111
1227
  });
1112
1228
  }
@@ -1137,7 +1253,7 @@ function closeNudgeText(workstream, names) {
1137
1253
  "If you are blocked or need an answer, say so in one line and stop; the orchestrator will read your pane."
1138
1254
  ].join("\n");
1139
1255
  }
1140
- function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested = notNested) {
1256
+ function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested = notNested, hold = noHold) {
1141
1257
  if (!nudgeEnabled(env)) return;
1142
1258
  const me = workerIdentity(env);
1143
1259
  if (me === void 0) return;
@@ -1146,11 +1262,15 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested
1146
1262
  fired = false;
1147
1263
  });
1148
1264
  pi.on("agent_before_settle", async (event, ctx) => {
1149
- if (fired || nested() || noUI(ctx)) return;
1265
+ if (fired || nested() || noUI(ctx) || hold(ctx)) return;
1150
1266
  if (outcomeOf(event) !== "completed") return;
1151
1267
  fired = true;
1152
1268
  const names = await ownedInProgress(run, me.agent, me.workstream);
1153
1269
  if (names === void 0 || names.length === 0) return;
1270
+ if (hold(ctx)) {
1271
+ fired = false;
1272
+ return;
1273
+ }
1154
1274
  breadcrumb(run, [
1155
1275
  "log",
1156
1276
  "-w",
@@ -1168,7 +1288,10 @@ function registerCloseNudge(pi, run = defaultRunner(), env = process.env, nested
1168
1288
  display: true
1169
1289
  }
1170
1290
  ],
1171
- continue: true
1291
+ continue: true,
1292
+ yielded: () => {
1293
+ fired = false;
1294
+ }
1172
1295
  };
1173
1296
  });
1174
1297
  }
@@ -1186,8 +1309,9 @@ async function briefRefuted(run, ws, id) {
1186
1309
  function refuteNudgeText(refs) {
1187
1310
  return `[mu] dispatched ${refs.join(", ")} with an unrefuted brief: refute it (delegate, record) or note REFUTE-EXEMPT: <why>`;
1188
1311
  }
1189
- function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested) {
1312
+ function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested, hold = noHold) {
1190
1313
  if (!nudgeEnabled()) return;
1314
+ const answerDue = (ctx) => hold(ctx) === "answer";
1191
1315
  const armed = /* @__PURE__ */ new Map();
1192
1316
  let fired = false;
1193
1317
  pi.on("input", () => {
@@ -1202,13 +1326,17 @@ function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested) {
1202
1326
  }
1203
1327
  });
1204
1328
  pi.on("agent_before_settle", async (event, ctx) => {
1205
- if (fired || armed.size === 0 || nested() || noUI(ctx)) return;
1329
+ if (fired || armed.size === 0 || nested() || noUI(ctx) || answerDue(ctx)) return;
1206
1330
  if (outcomeOf(event) !== "completed") return;
1207
1331
  fired = true;
1208
1332
  const tasks = [...armed];
1209
1333
  const refuted = await Promise.all(tasks.map(([, t]) => briefRefuted(run, t.ws, t.id)));
1210
1334
  const refs = tasks.flatMap(([ref], i) => refuted[i] === false ? [ref] : []);
1211
1335
  if (refs.length === 0) return;
1336
+ if (answerDue(ctx)) {
1337
+ fired = false;
1338
+ return;
1339
+ }
1212
1340
  const logWs = [...armed.values()].find((t) => t.ws !== "")?.ws;
1213
1341
  breadcrumb(run, [
1214
1342
  "log",
@@ -1225,13 +1353,16 @@ function registerRefuteNudge(pi, run = defaultRunner(), nested = notNested) {
1225
1353
  content: refuteNudgeText(refs),
1226
1354
  display: true
1227
1355
  }
1228
- ]
1356
+ ],
1357
+ yielded: () => {
1358
+ fired = false;
1359
+ }
1229
1360
  };
1230
1361
  });
1231
1362
  }
1232
1363
 
1233
1364
  // extension/mu-pi.ts
1234
- var EXT_VERSION = true ? "3.11.3" : void 0;
1365
+ var EXT_VERSION = true ? "3.13.0" : void 0;
1235
1366
  var FRESH_COMMAND = "mu-fresh";
1236
1367
  var FRESH_TIMEOUT_MS = 3e4;
1237
1368
  function commandName(name) {
@@ -1253,7 +1384,7 @@ function detectPiVersion() {
1253
1384
  for (let i = 0; i < 6; i++) {
1254
1385
  const pkgPath = join4(dir, "package.json");
1255
1386
  if (existsSync3(pkgPath)) {
1256
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
1387
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
1257
1388
  if (typeof pkg === "object" && pkg !== null) {
1258
1389
  const { name, version } = pkg;
1259
1390
  if (typeof name === "string" && /pi/.test(name) && typeof version === "string") {
@@ -1321,6 +1452,50 @@ function capText(text) {
1321
1452
  return `${cut.replace(/\uFFFD$/, "")}
1322
1453
  ${LAST_TEXT_TRUNCATED}`;
1323
1454
  }
1455
+ var UI_PROMPTS = /* @__PURE__ */ Symbol.for("mu.pi.uiPrompts");
1456
+ function uiPrompts() {
1457
+ const g = globalThis;
1458
+ g[UI_PROMPTS] ??= { open: [], since: 0 };
1459
+ return g[UI_PROMPTS];
1460
+ }
1461
+ function uiBlocked() {
1462
+ const u = uiPrompts();
1463
+ const top = u.open[u.open.length - 1];
1464
+ if (!top) return void 0;
1465
+ return {
1466
+ kind: top.kind,
1467
+ ...top.title !== void 0 ? { title: top.title } : {},
1468
+ since: u.since
1469
+ };
1470
+ }
1471
+ function isUiBlocked() {
1472
+ return uiPrompts().open.length > 0;
1473
+ }
1474
+ function promptOf(event) {
1475
+ const e = typeof event === "object" && event !== null ? event : {};
1476
+ const kind = typeof e.kind === "string" ? e.kind : "custom";
1477
+ const title = typeof e.title === "string" && e.title !== "" ? e.title : void 0;
1478
+ return { kind, ...title !== void 0 ? { title } : {} };
1479
+ }
1480
+ function trackUiPrompts(pi, onStart = () => {
1481
+ }) {
1482
+ pi.on("ui_prompt_start", (e) => {
1483
+ const u = uiPrompts();
1484
+ if (u.open.length === 0) u.since = Date.now();
1485
+ u.open.push(promptOf(e));
1486
+ onStart();
1487
+ });
1488
+ pi.on("session_start", () => {
1489
+ uiPrompts().open.length = 0;
1490
+ });
1491
+ pi.on("ui_prompt_end", (e) => {
1492
+ const u = uiPrompts();
1493
+ const { kind } = promptOf(e);
1494
+ let i = u.open.length - 1;
1495
+ while (i >= 0 && u.open[i]?.kind !== kind) i--;
1496
+ u.open.splice(i >= 0 ? i : u.open.length - 1, 1);
1497
+ });
1498
+ }
1324
1499
  function inodeOf(path) {
1325
1500
  try {
1326
1501
  return statSync2(path).ino;
@@ -1371,18 +1546,24 @@ function registerPrompts(pi) {
1371
1546
  });
1372
1547
  }
1373
1548
  function muPi(pi, run = defaultRunner()) {
1374
- registerDelegate(pi, run);
1549
+ const delegates = registerDelegate(pi, run);
1375
1550
  registerPrompts(pi);
1376
1551
  const nested = serveCtl(pi);
1377
- const settle = parallelSettle(pi);
1378
- registerNudge(settle.api, run, void 0, nested);
1379
- registerCloseNudge(settle.api, run, process.env, nested);
1380
- registerRefuteNudge(settle.api, run, nested);
1552
+ const hold = withQueuedInput(
1553
+ () => delegates.answerPending() ? "answer" : isUiBlocked() ? "blocked" : void 0
1554
+ );
1555
+ const settle = parallelSettle(pi, hold);
1556
+ registerNudge(settle.api, run, void 0, nested, hold);
1557
+ registerCloseNudge(settle.api, run, process.env, nested, hold);
1558
+ registerRefuteNudge(settle.api, run, nested, hold);
1381
1559
  settle.flush();
1382
1560
  }
1383
1561
  function serveCtl(pi) {
1384
1562
  const sock = process.env.MU_CTL_SOCK;
1385
- if (!sock) return () => false;
1563
+ if (!sock) {
1564
+ trackUiPrompts(pi);
1565
+ return () => false;
1566
+ }
1386
1567
  const sockPath = sock;
1387
1568
  const piVersion = detectPiVersion();
1388
1569
  const map = sharedMap();
@@ -1404,7 +1585,16 @@ function serveCtl(pi) {
1404
1585
  state: g.state,
1405
1586
  since: g.since,
1406
1587
  runs: g.runs,
1407
- pending: g.ctx?.hasPendingMessages() ?? false
1588
+ pending: g.ctx?.hasPendingMessages() ?? false,
1589
+ ...sessionFileOf(g.ctx),
1590
+ ...blockedField()
1591
+ });
1592
+ const blockedField = () => {
1593
+ const b = uiBlocked();
1594
+ return b ? { blocked: b } : {};
1595
+ };
1596
+ trackUiPrompts(pi, () => {
1597
+ for (const w of [...g.waiters]) w("blocked");
1408
1598
  });
1409
1599
  const settleFresh = (r) => {
1410
1600
  const f = g.fresh;
@@ -1428,6 +1618,14 @@ function serveCtl(pi) {
1428
1618
  g.endText = lastAssistantText(e);
1429
1619
  g.endError = lastAssistantError(e);
1430
1620
  });
1621
+ function sessionFileOf(c) {
1622
+ try {
1623
+ const f = c?.sessionManager?.getSessionFile();
1624
+ return f ? { sessionFile: f } : {};
1625
+ } catch {
1626
+ return {};
1627
+ }
1628
+ }
1431
1629
  pi.on("agent_settled", (_e, c) => {
1432
1630
  g.ctx = c;
1433
1631
  g.state = "idle";
@@ -1437,12 +1635,8 @@ function serveCtl(pi) {
1437
1635
  g.endText = void 0;
1438
1636
  g.lastError = g.endError;
1439
1637
  g.endError = void 0;
1440
- try {
1441
- g.sessionFile = c.sessionManager?.getSessionFile();
1442
- } catch {
1443
- g.sessionFile = void 0;
1444
- }
1445
- for (const w of [...g.waiters]) w();
1638
+ g.sessionFile = sessionFileOf(c).sessionFile;
1639
+ for (const w of [...g.waiters]) w("settle");
1446
1640
  });
1447
1641
  pi.registerCommand(FRESH_COMMAND, {
1448
1642
  description: "mu internal: new session + the prompt mu queued (run by mu agent send --fresh)",
@@ -1572,11 +1766,19 @@ function serveCtl(pi) {
1572
1766
  case "command":
1573
1767
  return command(req);
1574
1768
  case "wait":
1575
- return wait(num(req, "afterRuns"), num(req, "timeoutMs"), conn);
1769
+ return wait(
1770
+ num(req, "afterRuns"),
1771
+ num(req, "timeoutMs"),
1772
+ conn,
1773
+ req.returnOnBlocked === true
1774
+ );
1576
1775
  case "abort":
1577
1776
  if (!g.ctx) return fail("no pi context yet");
1578
- g.ctx.abort();
1579
- return { v: V, ok: true, state: g.state };
1777
+ {
1778
+ const wasBusy = !g.ctx.isIdle();
1779
+ if (wasBusy) g.ctx.abort();
1780
+ return { v: V, ok: true, state: g.state, wasBusy };
1781
+ }
1580
1782
  case "interrupt":
1581
1783
  return interrupt(req, conn);
1582
1784
  default:
@@ -1604,10 +1806,18 @@ function serveCtl(pi) {
1604
1806
  ...g.lastError !== void 0 ? { lastError: g.lastError } : {},
1605
1807
  ...g.sessionFile !== void 0 ? { sessionFile: g.sessionFile } : {}
1606
1808
  });
1607
- function wait(afterRuns, timeoutMs, conn) {
1809
+ function wait(afterRuns, timeoutMs, conn, onBlocked = false) {
1608
1810
  if (afterRuns !== void 0 && g.runs > afterRuns && g.state === "idle") {
1609
- return Promise.resolve({ v: V, ok: true, ...status(), ...settledRun() });
1811
+ return Promise.resolve({
1812
+ v: V,
1813
+ ok: true,
1814
+ ...status(),
1815
+ ...settledRun(),
1816
+ ended: "settled"
1817
+ });
1610
1818
  }
1819
+ const blockedReply = () => ({ v: V, ok: true, ...status(), ended: "blocked" });
1820
+ if (onBlocked && isUiBlocked()) return Promise.resolve(blockedReply());
1611
1821
  return new Promise((resolve) => {
1612
1822
  let timer;
1613
1823
  const done = (r) => {
@@ -1616,9 +1826,13 @@ function serveCtl(pi) {
1616
1826
  conn.off("close", onClose);
1617
1827
  resolve(r);
1618
1828
  };
1619
- const waiter = () => {
1829
+ const waiter = (why) => {
1830
+ if (why === "blocked") {
1831
+ if (onBlocked) done(blockedReply());
1832
+ return;
1833
+ }
1620
1834
  if (afterRuns !== void 0 && g.runs <= afterRuns) return;
1621
- done({ v: V, ok: true, ...status(), ...settledRun() });
1835
+ done({ v: V, ok: true, ...status(), ...settledRun(), ended: "settled" });
1622
1836
  };
1623
1837
  const onClose = () => done(fail("client closed"));
1624
1838
  g.waiters.add(waiter);
@@ -1740,8 +1954,10 @@ export {
1740
1954
  LIVE_PROBE_MS,
1741
1955
  commandName,
1742
1956
  muPi as default,
1957
+ isUiBlocked,
1743
1958
  lastAssistantError,
1744
1959
  lastAssistantText,
1745
- promptsDir
1960
+ promptsDir,
1961
+ uiBlocked
1746
1962
  };
1747
1963
  //# sourceMappingURL=mu-pi.js.map