@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.
- package/dist/main.cjs +236 -50
- 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 =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
10724
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10736
|
-
|
|
10737
|
-
model,
|
|
10738
|
-
|
|
10739
|
-
|
|
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(
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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(`
|
|
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}
|
|
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.
|
|
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
|
|
11188
|
-
|
|
11371
|
+
const startedMessage = { ...queued, state: "started", startedAt: timestamp };
|
|
11372
|
+
const started = persistRunMutation(statePath, run.runId, (fresh) => ({
|
|
11373
|
+
...fresh,
|
|
11189
11374
|
updatedAt: timestamp,
|
|
11190
|
-
messages: [...(
|
|
11191
|
-
|
|
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:
|
|
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: (
|
|
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,
|
|
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.
|
|
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",
|