@mutmutco/cli 2.57.0 → 2.58.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 (2) hide show
  1. package/dist/main.cjs +236 -50
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -10282,8 +10282,10 @@ var OVERLORD_DEFAULT_ENGINE = "fugu-api";
10282
10282
  var FUGU_API_DEFAULT_BASE_URL = "https://api.sakana.ai/v1";
10283
10283
  var FUGU_API_DEFAULT_MODEL = "fugu";
10284
10284
  var FUGU_API_DEFAULT_ULTRA_MODEL = "fugu-ultra";
10285
- var FUGU_API_DEFAULT_TIMEOUT_MS = 9e4;
10285
+ var FUGU_API_DEFAULT_TIMEOUT_MS = 48e4;
10286
10286
  var FUGU_API_DEFAULT_STARTUP_TIMEOUT_MS = 45e3;
10287
+ var FUGU_API_DEFAULT_IDLE_MS = 6e4;
10288
+ var FUGU_API_DEFAULT_MAX_TOKENS = 16e3;
10287
10289
  var FUGU_API_DEFAULT_MAX_ATTEMPTS = 3;
10288
10290
  var FUGU_API_RETRY_BASE_MS = 250;
10289
10291
  var FUGU_API_RETRY_CAP_MS = 8e3;
@@ -10493,6 +10495,81 @@ function writeOverlordRegistry(statePath, registry2) {
10493
10495
  atomicWriteFileSync(statePath, `${JSON.stringify(registry2, null, 2)}
10494
10496
  `);
10495
10497
  }
10498
+ function sleepSyncMs(ms) {
10499
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(1, ms));
10500
+ }
10501
+ var OVERLORD_REGISTRY_LOCK_TIMEOUT_MS = 5e3;
10502
+ var OVERLORD_REGISTRY_LOCK_STALE_MS = 3e4;
10503
+ function withRegistryLock(statePath, fn) {
10504
+ const lockPath = `${statePath}.lock`;
10505
+ (0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(statePath), { recursive: true });
10506
+ const deadline = Date.now() + OVERLORD_REGISTRY_LOCK_TIMEOUT_MS;
10507
+ let fd;
10508
+ for (; ; ) {
10509
+ try {
10510
+ fd = (0, import_node_fs15.openSync)(lockPath, "wx");
10511
+ break;
10512
+ } catch {
10513
+ let stale = false;
10514
+ try {
10515
+ stale = Date.now() - (0, import_node_fs15.statSync)(lockPath).mtimeMs > OVERLORD_REGISTRY_LOCK_STALE_MS;
10516
+ } catch {
10517
+ stale = false;
10518
+ }
10519
+ if (stale) {
10520
+ try {
10521
+ (0, import_node_fs15.rmSync)(lockPath, { force: true });
10522
+ } catch {
10523
+ }
10524
+ continue;
10525
+ }
10526
+ if (Date.now() > deadline) throw new Error(`Overlord registry lock is busy: ${lockPath}`);
10527
+ sleepSyncMs(25);
10528
+ }
10529
+ }
10530
+ try {
10531
+ return fn();
10532
+ } finally {
10533
+ if (fd !== void 0) (0, import_node_fs15.closeSync)(fd);
10534
+ try {
10535
+ (0, import_node_fs15.rmSync)(lockPath, { force: true });
10536
+ } catch {
10537
+ }
10538
+ }
10539
+ }
10540
+ function persistRunMutation(statePath, runId, mutate) {
10541
+ return withRegistryLock(statePath, () => {
10542
+ const fresh = readOverlordRegistry(statePath);
10543
+ const current = fresh.runs[runId];
10544
+ if (!current) throw new Error(`Overlord run ${runId} is no longer in the registry`);
10545
+ const next = mutate(current);
10546
+ writeOverlordRegistry(statePath, { ...fresh, runs: { ...fresh.runs, [runId]: next } });
10547
+ return next;
10548
+ });
10549
+ }
10550
+ function resolveOverlordSendMessage(messageArgs, messageFile) {
10551
+ const inline = messageArgs.join(" ").trim();
10552
+ if (inline) return inline;
10553
+ if (messageFile) {
10554
+ const raw = messageFile === "-" ? (0, import_node_fs15.readFileSync)(0, "utf8") : (0, import_node_fs15.readFileSync)(messageFile, "utf8");
10555
+ return raw.trim();
10556
+ }
10557
+ return "";
10558
+ }
10559
+ function computeAffectedSlots(servants, normalized, settled) {
10560
+ const targetSlots = normalized === "all" ? servants.map((s) => s.slotId) : servants.filter((s) => s.slotId === normalized || normalizeServantTarget(s.name) === normalized).map((s) => s.slotId);
10561
+ return /* @__PURE__ */ new Set([...(settled?.servantResults ?? []).map((r) => r.slotId), ...targetSlots]);
10562
+ }
10563
+ function mergeConcurrentDispatch(fresh, dispatched, settled, affected, messageId) {
10564
+ const dispatchedBySlot = new Map(dispatched.servants.map((s) => [s.slotId, s]));
10565
+ const updatedAt = [fresh.updatedAt, dispatched.updatedAt].filter(Boolean).sort().at(-1) ?? dispatched.updatedAt;
10566
+ return {
10567
+ ...fresh,
10568
+ updatedAt,
10569
+ servants: fresh.servants.map((s) => affected.has(s.slotId) ? dispatchedBySlot.get(s.slotId) ?? s : s),
10570
+ messages: [...(fresh.messages ?? []).filter((m) => m.id !== messageId), ...settled ? [settled] : []]
10571
+ };
10572
+ }
10496
10573
  function buildOverlordRun(options) {
10497
10574
  const runId = options.runId?.() ?? defaultRunId();
10498
10575
  const runToken = options.runToken?.() ?? defaultRunToken();
@@ -10603,8 +10680,18 @@ function defaultKillPid(pid) {
10603
10680
  function fuguApiTimeoutMs() {
10604
10681
  const parsed = Number(process.env.MMI_OVERLORD_LLM_TIMEOUT_MS);
10605
10682
  if (!Number.isFinite(parsed) || parsed <= 0) return FUGU_API_DEFAULT_TIMEOUT_MS;
10683
+ return Math.min(Math.max(Math.trunc(parsed), 5e3), 12e5);
10684
+ }
10685
+ function fuguApiIdleMs() {
10686
+ const parsed = Number(process.env.MMI_OVERLORD_LLM_IDLE_MS);
10687
+ if (!Number.isFinite(parsed) || parsed <= 0) return FUGU_API_DEFAULT_IDLE_MS;
10606
10688
  return Math.min(Math.max(Math.trunc(parsed), 5e3), 6e5);
10607
10689
  }
10690
+ function fuguApiMaxTokens() {
10691
+ const parsed = Number(process.env.MMI_OVERLORD_LLM_MAX_TOKENS);
10692
+ if (!Number.isFinite(parsed) || parsed <= 0) return FUGU_API_DEFAULT_MAX_TOKENS;
10693
+ return Math.min(Math.max(Math.trunc(parsed), 256), 2e5);
10694
+ }
10608
10695
  function fuguApiStartupTimeoutMs() {
10609
10696
  const workTimeout = fuguApiTimeoutMs();
10610
10697
  const parsed = Number(process.env.MMI_OVERLORD_LLM_STARTUP_TIMEOUT_MS);
@@ -10617,7 +10704,9 @@ function fuguApiConfig() {
10617
10704
  apiKey: process.env.MMI_OVERLORD_LLM_API_KEY ?? process.env.SAKANA_API_KEY,
10618
10705
  model: process.env.MMI_OVERLORD_LLM_MODEL ?? FUGU_API_DEFAULT_MODEL,
10619
10706
  ultraModel: process.env.MMI_OVERLORD_LLM_ULTRA_MODEL ?? FUGU_API_DEFAULT_ULTRA_MODEL,
10620
- timeoutMs: fuguApiTimeoutMs()
10707
+ timeoutMs: fuguApiTimeoutMs(),
10708
+ idleMs: fuguApiIdleMs(),
10709
+ maxTokens: fuguApiMaxTokens()
10621
10710
  };
10622
10711
  }
10623
10712
  function fuguApiModelForServant(config, servant) {
@@ -10685,6 +10774,37 @@ function mapFuguApiUsage(usage) {
10685
10774
  };
10686
10775
  return Object.values(mapped).some((value) => typeof value === "number") ? mapped : void 0;
10687
10776
  }
10777
+ function buildFuguRequestBody(model, messages, maxTokens) {
10778
+ return {
10779
+ model,
10780
+ messages,
10781
+ temperature: 0.2,
10782
+ max_tokens: maxTokens,
10783
+ stream: true,
10784
+ stream_options: { include_usage: true }
10785
+ };
10786
+ }
10787
+ function foldFuguSse(payloads) {
10788
+ let text = "";
10789
+ let finishReason;
10790
+ let rawUsage;
10791
+ for (const payload of payloads) {
10792
+ const data = payload.trim();
10793
+ if (!data || data === "[DONE]") continue;
10794
+ let parsed;
10795
+ try {
10796
+ parsed = JSON.parse(data);
10797
+ } catch {
10798
+ continue;
10799
+ }
10800
+ const choice = parsed?.choices?.[0];
10801
+ const piece = choice?.delta?.content ?? choice?.message?.content;
10802
+ if (typeof piece === "string") text += piece;
10803
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
10804
+ if (parsed?.usage) rawUsage = parsed.usage;
10805
+ }
10806
+ return { text, finishReason, usage: mapFuguApiUsage(rawUsage) };
10807
+ }
10688
10808
  async function defaultRunFuguApi(run, servant, message, options) {
10689
10809
  const config = fuguApiConfig();
10690
10810
  if (!config.apiKey) return { ok: false, error: "SAKANA_API_KEY or MMI_OVERLORD_LLM_API_KEY is not available" };
@@ -10693,8 +10813,24 @@ async function defaultRunFuguApi(run, servant, message, options) {
10693
10813
  ...servant.llmMessages ?? [{ role: "system", content: fuguApiSystemMessage(servant, run) }],
10694
10814
  { role: "user", content: message }
10695
10815
  ];
10816
+ const totalMs = options?.timeoutMs ?? config.timeoutMs;
10817
+ const idleMs = Math.min(config.idleMs, totalMs);
10696
10818
  const controller = new AbortController();
10697
- const timeout = setTimeout(() => controller.abort(), options?.timeoutMs ?? config.timeoutMs);
10819
+ let abortKind;
10820
+ const totalTimer = setTimeout(() => {
10821
+ abortKind = "total";
10822
+ controller.abort();
10823
+ }, totalMs);
10824
+ let idleTimer;
10825
+ let streamReader;
10826
+ const armIdle = () => {
10827
+ if (idleTimer) clearTimeout(idleTimer);
10828
+ idleTimer = setTimeout(() => {
10829
+ abortKind = "idle";
10830
+ controller.abort();
10831
+ }, idleMs);
10832
+ };
10833
+ armIdle();
10698
10834
  try {
10699
10835
  const response = await fetch(`${config.baseUrl}/chat/completions`, {
10700
10836
  method: "POST",
@@ -10702,44 +10838,71 @@ async function defaultRunFuguApi(run, servant, message, options) {
10702
10838
  Authorization: `Bearer ${config.apiKey}`,
10703
10839
  "Content-Type": "application/json"
10704
10840
  },
10705
- body: JSON.stringify({
10706
- model,
10707
- messages,
10708
- temperature: 0.2
10709
- }),
10841
+ body: JSON.stringify(buildFuguRequestBody(model, messages, config.maxTokens)),
10710
10842
  signal: controller.signal
10711
10843
  });
10712
10844
  const requestId = response.headers.get("x-request-id") ?? response.headers.get("request-id") ?? void 0;
10713
- const json = await response.json().catch(() => void 0);
10714
10845
  if (!response.ok) {
10846
+ const errBody = await response.json().catch(() => void 0);
10715
10847
  return {
10716
10848
  ok: false,
10717
10849
  model,
10718
10850
  requestId,
10719
- error: json?.error?.message ?? `Fugu API returned HTTP ${response.status}`,
10851
+ error: errBody?.error?.message ?? `Fugu API returned HTTP ${response.status}`,
10720
10852
  retryable: response.status === 429 || response.status >= 500
10721
10853
  };
10722
10854
  }
10723
- const text = json?.choices?.[0]?.message?.content?.trim() ?? "";
10724
- if (!text) return { ok: false, model, requestId, error: "Fugu API returned no assistant text" };
10855
+ if (!response.body) return { ok: false, model, requestId, error: "Fugu API returned no response body", retryable: true };
10856
+ const reader = response.body.getReader();
10857
+ streamReader = reader;
10858
+ const decoder = new TextDecoder();
10859
+ const payloads = [];
10860
+ let buffer = "";
10861
+ for (; ; ) {
10862
+ const { done, value } = await reader.read();
10863
+ if (done) break;
10864
+ armIdle();
10865
+ buffer += decoder.decode(value, { stream: true });
10866
+ const lines = buffer.split("\n");
10867
+ buffer = lines.pop() ?? "";
10868
+ for (const line of lines) {
10869
+ const trimmed = line.trim();
10870
+ if (trimmed.startsWith("data:")) payloads.push(trimmed.slice(5).trim());
10871
+ }
10872
+ }
10873
+ const tail = buffer.trim();
10874
+ if (tail.startsWith("data:")) payloads.push(tail.slice(5).trim());
10875
+ const { text: rawText, finishReason, usage } = foldFuguSse(payloads);
10876
+ const text = rawText.trim();
10877
+ if (!text) {
10878
+ const reason = finishReason === "length" ? "Fugu API hit max_tokens before emitting content (raise MMI_OVERLORD_LLM_MAX_TOKENS)" : "Fugu API returned no assistant text";
10879
+ return { ok: false, model, requestId, error: reason, finishReason, usage, retryable: false };
10880
+ }
10725
10881
  return {
10726
10882
  ok: true,
10727
10883
  text,
10728
10884
  model,
10729
10885
  requestId,
10730
- usage: mapFuguApiUsage(json?.usage),
10886
+ finishReason,
10887
+ usage,
10731
10888
  messages: [...messages, { role: "assistant", content: text }]
10732
10889
  };
10733
10890
  } catch (e) {
10734
10891
  const aborted = e instanceof Error && e.name === "AbortError";
10735
- return {
10736
- ok: false,
10737
- model,
10738
- error: aborted ? "Fugu API request timed out" : e instanceof Error ? e.message : String(e),
10739
- retryable: true
10740
- };
10892
+ if (aborted) {
10893
+ const bound = abortKind === "idle" ? `${Math.round(idleMs / 1e3)}s with no data` : `${Math.round(totalMs / 1e3)}s total`;
10894
+ return { ok: false, model, error: `Fugu API request timed out (${bound})`, retryable: false };
10895
+ }
10896
+ return { ok: false, model, error: e instanceof Error ? e.message : String(e), retryable: true };
10741
10897
  } finally {
10742
- clearTimeout(timeout);
10898
+ clearTimeout(totalTimer);
10899
+ if (idleTimer) clearTimeout(idleTimer);
10900
+ if (streamReader) {
10901
+ try {
10902
+ streamReader.releaseLock();
10903
+ } catch {
10904
+ }
10905
+ }
10743
10906
  }
10744
10907
  }
10745
10908
  function renderOverlordPreflightFailure(report) {
@@ -10935,18 +11098,44 @@ function hasServantTarget(run, target) {
10935
11098
  (servant) => servant.slotId === normalized || normalizeServantTarget(servant.name) === normalized
10936
11099
  );
10937
11100
  }
10938
- function messageProgress(message, now = /* @__PURE__ */ new Date(), timeoutMs = OVERLORD_HANDOFF_TIMEOUT_MS) {
11101
+ function messageProgress(message, now = /* @__PURE__ */ new Date(), timeoutMs = OVERLORD_HANDOFF_TIMEOUT_MS, ownerAlive) {
10939
11102
  if (message.state === "partial") return "partial";
10940
11103
  if (message.completedAt || message.state === "completed") return "completed";
10941
11104
  if (message.failedAt || message.state === "failed") return "failed";
10942
11105
  const startedAt = message.startedAt ?? message.deliveredAt;
10943
11106
  if (!startedAt) return "queued";
10944
11107
  const elapsed = now.getTime() - new Date(startedAt).getTime();
10945
- return Number.isFinite(elapsed) && elapsed >= timeoutMs ? "stalled" : "started";
11108
+ if (!(Number.isFinite(elapsed) && elapsed >= timeoutMs)) return "started";
11109
+ return ownerAlive === false ? "orphaned" : "stalled";
11110
+ }
11111
+ function messageElapsedMs(message, now = /* @__PURE__ */ new Date()) {
11112
+ const startedAt = message.startedAt ?? message.deliveredAt;
11113
+ if (!startedAt) return void 0;
11114
+ const elapsed = now.getTime() - new Date(startedAt).getTime();
11115
+ return Number.isFinite(elapsed) && elapsed >= 0 ? elapsed : void 0;
11116
+ }
11117
+ function reconcileOverlordRun(run, ownerAlive, now = /* @__PURE__ */ new Date()) {
11118
+ let changed = 0;
11119
+ const messages = (run.messages ?? []).map((message) => {
11120
+ if (messageProgress(message, now, OVERLORD_HANDOFF_TIMEOUT_MS, ownerAlive) === "orphaned") {
11121
+ changed += 1;
11122
+ return {
11123
+ ...message,
11124
+ state: "failed",
11125
+ failedAt: now.toISOString(),
11126
+ failureReason: message.failureReason ?? "orphaned: Overlord controller is not alive"
11127
+ };
11128
+ }
11129
+ return message;
11130
+ });
11131
+ return changed > 0 ? { run: { ...run, messages }, changed } : { run, changed: 0 };
10946
11132
  }
10947
11133
  function servantProgress(run, servant, now = /* @__PURE__ */ new Date()) {
10948
11134
  const relevant = (run.messages ?? []).filter((message) => message.target === "all" || servant.slotId === message.target || normalizeServantTarget(servant.name) === message.target);
10949
- return relevant.some((message) => messageProgress(message, now) === "stalled") ? "stalled-after-delivery" : servant.state;
11135
+ return relevant.some((message) => {
11136
+ const progress = messageProgress(message, now);
11137
+ return progress === "stalled" || progress === "orphaned";
11138
+ }) ? "stalled-after-delivery" : servant.state;
10950
11139
  }
10951
11140
  function humanSafeText(text, maxLength = 160) {
10952
11141
  const redacted = (text ?? "").replace(/\bBearer\s+\S+/gi, "[redacted]").replace(/\b(api[_-]?key|token|secret)=\S+/gi, "$1=[redacted]").replace(/\b(sk|sakana|mmi)[-_][A-Za-z0-9_-]{10,}\b/g, "[redacted]").replace(/\s+/g, " ").trim();
@@ -11106,7 +11295,7 @@ function registerOverlordCommands(program3, deps = {}) {
11106
11295
  const plan2 = buildOverlordStartupPlan(args, root);
11107
11296
  const engine = `${options.engine ?? OVERLORD_DEFAULT_ENGINE}`;
11108
11297
  if (engine !== "fugu-api") throw new Error("--engine must be fugu-api");
11109
- if (!options.json) out("Starting Overlord.\nLoading run registry...\nChecking Fugu API...\n");
11298
+ if (!options.json) out("Starting Overlord...\n");
11110
11299
  const preflightReport = await fuguApiPreflight();
11111
11300
  if (!preflightReport.ok) {
11112
11301
  err(`${renderOverlordPreflightFailure(preflightReport)}
@@ -11129,8 +11318,7 @@ function registerOverlordCommands(program3, deps = {}) {
11129
11318
  runToken: deps.runToken
11130
11319
  });
11131
11320
  writeOverlordRegistry(plan2.statePath, { activeRunId: run.runId, runs: { ...registry2.runs, [run.runId]: run } });
11132
- if (!options.json) out(`Summoning ${run.servants.length} Fugu servants...
11133
- Preparing conversations...
11321
+ if (!options.json) out(`Spawning ${run.servants.length} Fugu (1 ultra, ${run.servants.length - 1} normal)...
11134
11322
  `);
11135
11323
  run = markServantsStarting(run, isoNow(now));
11136
11324
  writeOverlordRegistry(plan2.statePath, { activeRunId: run.runId, runs: { ...registry2.runs, [run.runId]: run } });
@@ -11141,13 +11329,9 @@ Preparing conversations...
11141
11329
  else {
11142
11330
  const ready = run.servants.filter((servant) => servant.state === "ready").length;
11143
11331
  out(`${[
11144
- `Ready: ${ready}/${run.servants.length} servants ready.`,
11332
+ `Ready: ${ready}/${run.servants.length}.`,
11145
11333
  `Mission: ${humanSafeText(run.task || "not provided yet")}`,
11146
- `Run: ${run.runId}`,
11147
- "Engine: Fugu API",
11148
- "",
11149
- "Overlord is idle and ready for your instruction.",
11150
- "Next: I will consult the servants and interview you, then propose a todo list for your approval."
11334
+ `Run: ${run.runId}`
11151
11335
  ].join("\n")}
11152
11336
  `);
11153
11337
  }
@@ -11160,7 +11344,7 @@ Preparing conversations...
11160
11344
  process.exitCode = 1;
11161
11345
  }
11162
11346
  });
11163
- overlord.command("send").description("queue an assignment or redirect for active Overlord servant conversations").argument("<target>", "servant slot id/name, or all").argument("[message...]", "message to deliver to the servant session").option("--json", "print machine-readable output").action(async (target, message = [], options, command) => {
11347
+ overlord.command("send").description("queue an assignment or redirect for active Overlord servant conversations").argument("<target>", "servant slot id/name, or all").argument("[message...]", "message to deliver to the servant session").option("--message-file <path>", "read the message from a UTF-8 file, or - for stdin (prefer a path; #1511)").option("--json", "print machine-readable output").action(async (target, message = [], options, command) => {
11164
11348
  const json = wantsJson(options, command);
11165
11349
  try {
11166
11350
  const statePath = defaultOverlordStatePath(cwd());
@@ -11169,8 +11353,9 @@ Preparing conversations...
11169
11353
  if (!run) throw new Error("no active Overlord run found");
11170
11354
  const normalized = normalizeServantTarget(target);
11171
11355
  if (!hasServantTarget(run, normalized)) throw new Error(`unknown Overlord servant target: ${target}`);
11172
- const text = message.join(" ").trim();
11173
- if (!text) throw new Error("message is required");
11356
+ const text = resolveOverlordSendMessage(message, typeof options.messageFile === "string" ? options.messageFile : void 0);
11357
+ if (!text) throw new Error("message is required (pass it inline or via --message-file)");
11358
+ if (run.engine !== "fugu-api") throw new Error("active Overlord run does not use the fugu-api engine");
11174
11359
  const timestamp = isoNow(now);
11175
11360
  const queued = {
11176
11361
  id: deps.runId?.() ?? defaultMessageId(),
@@ -11180,23 +11365,19 @@ Preparing conversations...
11180
11365
  queuedAt: timestamp,
11181
11366
  state: "queued"
11182
11367
  };
11183
- if (run.engine !== "fugu-api") throw new Error("active Overlord run does not use the fugu-api engine");
11184
11368
  if (!json) out(`Sending assignment to ${normalized}...
11185
11369
  Awaiting Fugu responses...
11186
11370
  `);
11187
- const started = {
11188
- ...run,
11371
+ const startedMessage = { ...queued, state: "started", startedAt: timestamp };
11372
+ const started = persistRunMutation(statePath, run.runId, (fresh) => ({
11373
+ ...fresh,
11189
11374
  updatedAt: timestamp,
11190
- messages: [...(run.messages ?? []).filter((m) => m.id !== queued.id), {
11191
- ...queued,
11192
- state: "started",
11193
- startedAt: timestamp
11194
- }]
11195
- };
11196
- writeOverlordRegistry(statePath, { ...registry2, runs: { ...registry2.runs, [run.runId]: started } });
11375
+ messages: [...(fresh.messages ?? []).filter((m) => m.id !== queued.id), startedMessage]
11376
+ }));
11197
11377
  const dispatched = await dispatchFuguApiMessage(started, queued, runFuguApi, now, fuguApiRetry);
11198
- writeOverlordRegistry(statePath, { ...registry2, runs: { ...registry2.runs, [run.runId]: dispatched } });
11199
11378
  const settled = (dispatched.messages ?? []).find((m) => m.id === queued.id);
11379
+ const affected = computeAffectedSlots(dispatched.servants, normalized, settled);
11380
+ persistRunMutation(statePath, run.runId, (fresh) => mergeConcurrentDispatch(fresh, dispatched, settled, affected, queued.id));
11200
11381
  const ok = settled?.state === "completed" || settled?.state === "partial";
11201
11382
  const payload = { ok, runId: run.runId, target: normalized, messageId: queued.id, state: settled?.state, responseText: settled?.responseText, failureReason: settled?.failureReason, servantResults: settled?.servantResults, statePath };
11202
11383
  if (json) out(`${JSON.stringify(payload, null, 2)}
@@ -11229,10 +11410,13 @@ State: ${payload2.statePath}
11229
11410
  }
11230
11411
  const summary = summarizeOverlordRun(run, { isPidAlive, now });
11231
11412
  const current = now();
11413
+ const ownerAlive = summary.controller !== "lost";
11414
+ const { run: reconciledRun, changed } = reconcileOverlordRun(run, ownerAlive, current);
11415
+ if (changed > 0) persistRunMutation(statePath, run.runId, (fresh) => reconcileOverlordRun(fresh, ownerAlive, current).run);
11232
11416
  const payload = {
11233
11417
  ...summary,
11234
11418
  statePath,
11235
- task: run.task,
11419
+ task: reconciledRun.task,
11236
11420
  engine: run.engine,
11237
11421
  ledgerPath: run.ledgerPath,
11238
11422
  sessions: run.servants.map((servant) => ({
@@ -11248,10 +11432,12 @@ State: ${payload2.statePath}
11248
11432
  lastEventAt: servant.lastEventAt,
11249
11433
  lastMessageCompletedAt: servant.lastMessageCompletedAt
11250
11434
  })),
11251
- messages: (run.messages ?? []).map((message) => ({
11435
+ messages: (reconciledRun.messages ?? []).map((message) => ({
11252
11436
  id: message.id,
11253
11437
  target: message.target,
11254
- state: messageProgress(message, current),
11438
+ state: messageProgress(message, current, OVERLORD_HANDOFF_TIMEOUT_MS, ownerAlive),
11439
+ elapsedMs: messageElapsedMs(message, current),
11440
+ boundMs: OVERLORD_HANDOFF_TIMEOUT_MS,
11255
11441
  queuedAt: message.queuedAt ?? message.createdAt,
11256
11442
  startedAt: message.startedAt ?? message.deliveredAt,
11257
11443
  completedAt: message.completedAt,
@@ -11264,7 +11450,7 @@ State: ${payload2.statePath}
11264
11450
  };
11265
11451
  if (json) out(`${JSON.stringify(payload, null, 2)}
11266
11452
  `);
11267
- else out(`${renderOverlordStatus(summary, run, current)}
11453
+ else out(`${renderOverlordStatus(summary, reconciledRun, current)}
11268
11454
  State: ${statePath}
11269
11455
  `);
11270
11456
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.57.0",
3
+ "version": "2.58.0",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), Jervaise-only continuity, and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",