@mutmutco/cli 2.54.1 → 2.55.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.
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/overlord-controller.ts
26
+ var import_node_fs3 = require("node:fs");
27
+ var import_node_path2 = require("node:path");
28
+
29
+ // src/overlord.ts
30
+ var import_node_fs2 = require("node:fs");
31
+ var import_node_path = require("node:path");
32
+
33
+ // src/atomic-write.ts
34
+ var import_node_fs = require("node:fs");
35
+ function atomicWriteFileSync(path, content) {
36
+ const tmp = `${path}.${process.pid}.tmp`;
37
+ (0, import_node_fs.writeFileSync)(tmp, content, "utf8");
38
+ (0, import_node_fs.renameSync)(tmp, path);
39
+ }
40
+
41
+ // src/overlord.ts
42
+ function isoNow(now = () => /* @__PURE__ */ new Date()) {
43
+ return now().toISOString();
44
+ }
45
+ function buildCodexFuguLaunch(slot, options) {
46
+ const args = [];
47
+ if (slot.role === "ultra") args.push("--model", "fugu-ultra");
48
+ args.push("--no-alt-screen");
49
+ if (options.profile === "full-trust-repair") {
50
+ args.push("-a", "never", "-s", "danger-full-access");
51
+ if (options.cwd) args.push("-C", options.cwd);
52
+ return { command: "codex-fugu", args };
53
+ }
54
+ const sandbox = options.profile === "implementation" ? "workspace-write" : "read-only";
55
+ if (options.profile === "implementation" && !options.cwd) {
56
+ throw new Error("implementation servant launch requires an owned worktree cwd");
57
+ }
58
+ args.push(
59
+ "-a",
60
+ "never",
61
+ "-s",
62
+ sandbox,
63
+ "-c",
64
+ 'sandbox_permissions=["disk-full-read-access"]'
65
+ );
66
+ if (options.cwd) args.push("-C", options.cwd);
67
+ return { command: "codex-fugu", args };
68
+ }
69
+ function readOverlordRegistry(statePath2) {
70
+ if (!(0, import_node_fs2.existsSync)(statePath2)) return { runs: {} };
71
+ try {
72
+ const parsed = JSON.parse((0, import_node_fs2.readFileSync)(statePath2, "utf8"));
73
+ return { activeRunId: parsed.activeRunId, runs: parsed.runs ?? {} };
74
+ } catch {
75
+ return { runs: {} };
76
+ }
77
+ }
78
+ function writeOverlordRegistry(statePath2, registry) {
79
+ (0, import_node_fs2.mkdirSync)((0, import_node_path.dirname)(statePath2), { recursive: true });
80
+ atomicWriteFileSync(statePath2, `${JSON.stringify(registry, null, 2)}
81
+ `);
82
+ }
83
+ function recordOverlordHeartbeat(run, options) {
84
+ const timestamp = isoNow(options.now);
85
+ const controllerResource = {
86
+ kind: "process",
87
+ pid: options.controllerPid,
88
+ commandName: "mmi-cli overlord controller",
89
+ runId: run.runId,
90
+ runToken: run.runToken,
91
+ fingerprint: options.fingerprint
92
+ };
93
+ const others = run.ownedResources.filter((resource) => resource.commandName !== "mmi-cli overlord controller");
94
+ return {
95
+ ...run,
96
+ state: run.state === "starting" ? "active" : run.state,
97
+ updatedAt: timestamp,
98
+ controllerPid: options.controllerPid,
99
+ controllerFingerprint: options.fingerprint,
100
+ lastControllerHeartbeatAt: timestamp,
101
+ ownedResources: [controllerResource, ...others]
102
+ };
103
+ }
104
+ function recordOverlordControllerHeartbeat(runId2, statePath2, fingerprint2, controllerPid, now = () => /* @__PURE__ */ new Date()) {
105
+ const registry = readOverlordRegistry(statePath2);
106
+ const run = registry.runs[runId2];
107
+ if (!run || run.state === "stopped" || run.state === "failed") return false;
108
+ const next = recordOverlordHeartbeat(run, { controllerPid, fingerprint: fingerprint2, now });
109
+ writeOverlordRegistry(statePath2, {
110
+ ...registry,
111
+ activeRunId: registry.activeRunId ?? runId2,
112
+ runs: { ...registry.runs, [runId2]: next }
113
+ });
114
+ return true;
115
+ }
116
+
117
+ // src/overlord-controller.ts
118
+ var [runId, statePath, fingerprint] = process.argv.slice(2);
119
+ if (!runId || !statePath || !fingerprint) {
120
+ console.error("mmi-cli overlord controller: missing run id, state path, or fingerprint");
121
+ process.exit(2);
122
+ }
123
+ var intervalMs = Number(process.env.MMI_OVERLORD_HEARTBEAT_MS ?? 1e4);
124
+ var servants = /* @__PURE__ */ new Map();
125
+ function heartbeat() {
126
+ const alive = recordOverlordControllerHeartbeat(runId, statePath, fingerprint, process.pid);
127
+ if (!alive) process.exit(0);
128
+ }
129
+ function servantPrompt(servant, run) {
130
+ const roleLine = servant.role === "ultra" ? "You are the single Ultra Fugu: take the hardest, highest-uncertainty questions and report calibrated judgment." : "You are a normal Fugu servant: take one bounded mission at a time and report concise evidence.";
131
+ return [
132
+ `You are ${servant.name} in Overlord run ${run.runId}.`,
133
+ roleLine,
134
+ "First respond with exactly: ACK " + servant.name + " ready",
135
+ "After the ACK, wait for the Overlord to assign bounded work.",
136
+ "Do not start dev servers, browsers, Playwright, PRs, merges, releases, or worktree changes unless the Overlord explicitly assigns that scope.",
137
+ "When assigned work, gather evidence before editing, verify before claiming done, and escalate blockers instead of looping."
138
+ ].join("\n");
139
+ }
140
+ function quoteCmdArg(arg) {
141
+ const normalized = arg.replace(/\r?\n/g, " ");
142
+ return `"${normalized.replace(/"/g, '\\"')}"`;
143
+ }
144
+ function ptyLaunchCommand(command, args) {
145
+ if (process.platform !== "win32") return { file: command, args };
146
+ return {
147
+ file: "cmd.exe",
148
+ args: `/d /s /c ${command} ${args.map(quoteCmdArg).join(" ")}`
149
+ };
150
+ }
151
+ function servantFingerprint(run, servant) {
152
+ return `mmi-overlord-servant:${run.runId}:${servant.slotId}`;
153
+ }
154
+ function writeJournal(servant, text) {
155
+ try {
156
+ (0, import_node_fs3.mkdirSync)((0, import_node_path2.dirname)(servant.journalPath), { recursive: true });
157
+ (0, import_node_fs3.appendFileSync)(servant.journalPath, text, "utf8");
158
+ } catch {
159
+ }
160
+ }
161
+ function upsertOwnedResource(resources, resource) {
162
+ return [
163
+ resource,
164
+ ...resources.filter(
165
+ (existing) => !(existing.kind === resource.kind && existing.pid === resource.pid && existing.fingerprint === resource.fingerprint)
166
+ )
167
+ ];
168
+ }
169
+ function updateServant(slotId, mutate) {
170
+ const registry = readOverlordRegistry(statePath);
171
+ const run = registry.runs[runId];
172
+ if (!run) return;
173
+ const nextServants = run.servants.map((servant) => servant.slotId === slotId ? mutate(run, servant) : servant);
174
+ writeOverlordRegistry(statePath, {
175
+ ...registry,
176
+ runs: {
177
+ ...registry.runs,
178
+ [runId]: {
179
+ ...run,
180
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
181
+ servants: nextServants
182
+ }
183
+ }
184
+ });
185
+ }
186
+ function recordServantStart(run, servant, pid, servantFp) {
187
+ const registry = readOverlordRegistry(statePath);
188
+ const current = registry.runs[run.runId] ?? run;
189
+ const resource = {
190
+ kind: "process",
191
+ pid,
192
+ commandName: "codex-fugu",
193
+ runId: run.runId,
194
+ runToken: run.runToken,
195
+ fingerprint: servantFp
196
+ };
197
+ writeOverlordRegistry(statePath, {
198
+ ...registry,
199
+ activeRunId: registry.activeRunId ?? run.runId,
200
+ runs: {
201
+ ...registry.runs,
202
+ [run.runId]: {
203
+ ...current,
204
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
205
+ servants: current.servants.map((item) => item.slotId === servant.slotId ? {
206
+ ...item,
207
+ state: "starting",
208
+ pid,
209
+ fingerprint: servantFp,
210
+ composerSubmitMode: "crlf",
211
+ lastLivenessCheckAt: (/* @__PURE__ */ new Date()).toISOString()
212
+ } : item),
213
+ ownedResources: upsertOwnedResource(current.ownedResources, resource)
214
+ }
215
+ }
216
+ });
217
+ }
218
+ function markServantExit(slotId) {
219
+ updateServant(slotId, (_run, servant) => ({
220
+ ...servant,
221
+ state: servant.state === "stopped" ? "stopped" : "lost",
222
+ lastLivenessCheckAt: (/* @__PURE__ */ new Date()).toISOString()
223
+ }));
224
+ }
225
+ function markServantAck(slotId) {
226
+ updateServant(slotId, (_run, servant) => ({
227
+ ...servant,
228
+ state: "ready",
229
+ lastAckAt: (/* @__PURE__ */ new Date()).toISOString(),
230
+ lastUsefulSignalAt: (/* @__PURE__ */ new Date()).toISOString()
231
+ }));
232
+ }
233
+ function messageTargets(run, target) {
234
+ if (target === "all") return run.servants;
235
+ return run.servants.filter((servant) => servant.slotId === target || servant.name.toLowerCase().replace(/\s+/g, "-") === target);
236
+ }
237
+ function deliverPendingMessages() {
238
+ const registry = readOverlordRegistry(statePath);
239
+ const run = registry.runs[runId];
240
+ if (!run?.messages?.length) return;
241
+ let changed = false;
242
+ const nextMessages = run.messages.map((message) => {
243
+ if (message.deliveredAt) return message;
244
+ const targets = messageTargets(run, message.target);
245
+ if (!targets.length) return message;
246
+ const liveTargets = targets.map((servant) => servants.get(servant.slotId));
247
+ if (liveTargets.some((servant) => !servant)) return message;
248
+ for (const child of liveTargets) child?.write(`${message.text}\r
249
+ `);
250
+ changed = true;
251
+ return { ...message, deliveredAt: (/* @__PURE__ */ new Date()).toISOString() };
252
+ });
253
+ if (!changed) return;
254
+ writeOverlordRegistry(statePath, {
255
+ ...registry,
256
+ runs: {
257
+ ...registry.runs,
258
+ [runId]: {
259
+ ...run,
260
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
261
+ messages: nextMessages
262
+ }
263
+ }
264
+ });
265
+ }
266
+ async function launchServants() {
267
+ const registry = readOverlordRegistry(statePath);
268
+ const run = registry.runs[runId];
269
+ if (!run || run.state === "stopped" || run.state === "failed") return;
270
+ const pty = await import("@homebridge/node-pty-prebuilt-multiarch");
271
+ (0, import_node_fs3.mkdirSync)(run.journalDir, { recursive: true });
272
+ for (const servant of run.servants) {
273
+ if (servants.has(servant.slotId) || servant.state === "ready" || servant.state === "starting") continue;
274
+ const launch = buildCodexFuguLaunch(servant, { profile: servant.profile, cwd: run.worktree });
275
+ const command = ptyLaunchCommand(launch.command, [...launch.args, servantPrompt(servant, run)]);
276
+ const child = pty.spawn(command.file, command.args, {
277
+ name: "xterm-256color",
278
+ cols: 120,
279
+ rows: 40,
280
+ cwd: run.worktree,
281
+ env: {
282
+ ...process.env,
283
+ TERM: "xterm-256color",
284
+ CODEX_FUGU_NO_NOTICE: "1",
285
+ CODEX_FUGU_NO_UPDATE: "1"
286
+ }
287
+ });
288
+ servants.set(servant.slotId, child);
289
+ const servantFp = servantFingerprint(run, servant);
290
+ writeJournal(servant, `
291
+ [overlord] launched ${launch.command} ${launch.args.join(" ")}
292
+ `);
293
+ recordServantStart(run, servant, child.pid, servantFp);
294
+ child.onData((data) => {
295
+ writeJournal(servant, data);
296
+ if (/ACK\s+.+\s+ready/i.test(data)) markServantAck(servant.slotId);
297
+ });
298
+ child.onExit(() => {
299
+ servants.delete(servant.slotId);
300
+ markServantExit(servant.slotId);
301
+ });
302
+ }
303
+ }
304
+ function shutdown() {
305
+ for (const child of servants.values()) {
306
+ try {
307
+ child.kill();
308
+ } catch {
309
+ }
310
+ }
311
+ process.exit(0);
312
+ }
313
+ process.on("SIGTERM", shutdown);
314
+ process.on("SIGINT", shutdown);
315
+ heartbeat();
316
+ if (process.env.MMI_OVERLORD_SKIP_SERVANT_LAUNCH !== "1") {
317
+ void launchServants().then(deliverPendingMessages).catch(() => void 0);
318
+ }
319
+ setInterval(() => {
320
+ heartbeat();
321
+ deliverPendingMessages();
322
+ }, Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 1e4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.54.1",
3
+ "version": "2.55.0",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -35,6 +35,7 @@
35
35
  "typecheck": "tsc --noEmit"
36
36
  },
37
37
  "dependencies": {
38
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
38
39
  "commander": "^15.0.0"
39
40
  },
40
41
  "devDependencies": {