@mu-crew/mu 3.0.0 → 3.2.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,653 @@
1
+ // extension/mu-pi.ts
2
+ import {
3
+ chmodSync,
4
+ existsSync as existsSync2,
5
+ linkSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ realpathSync,
9
+ rmSync,
10
+ statSync
11
+ } from "fs";
12
+ import { connect, createServer } from "net";
13
+ import { dirname, join as join2 } from "path";
14
+
15
+ // src/ctl/protocol.ts
16
+ var CTL_PROTOCOL_VERSION = 1;
17
+ var CTL_OPS = [
18
+ "hello",
19
+ "status",
20
+ "send",
21
+ "wait",
22
+ "abort",
23
+ "fresh"
24
+ ];
25
+ var UNKNOWN_OP_PREFIX = "unknown op: ";
26
+ function encode(msg) {
27
+ return `${JSON.stringify(msg)}
28
+ `;
29
+ }
30
+ var LineDecoder = class {
31
+ buf = "";
32
+ push(chunk) {
33
+ this.buf += chunk;
34
+ const parts = this.buf.split("\n");
35
+ this.buf = parts.pop() ?? "";
36
+ return parts;
37
+ }
38
+ };
39
+
40
+ // extension/delegate.ts
41
+ import { execFile } from "child_process";
42
+ import { existsSync } from "fs";
43
+ import { delimiter, join } from "path";
44
+ import { fileURLToPath } from "url";
45
+ var DELEGATE_TOOL = "mu_delegate";
46
+ var DELEGATE_CANCEL_TOOL = "mu_delegate_cancel";
47
+ var DELEGATE_MESSAGE_TYPE = "mu-delegate";
48
+ var DELEGATE_WORKSTREAM = "scratch";
49
+ var DELEGATE_TIMEOUT_S = 3600;
50
+ var W = DELEGATE_WORKSTREAM;
51
+ function delegateEnabled(env = process.env) {
52
+ if (env.MU_MANAGED_AGENT) return false;
53
+ if (env.MU_DELEGATE === "0") return false;
54
+ if (env.MU_MUX || env.HERDR_ENV === "1" || env.TMUX || env.TMUX_PANE) return true;
55
+ return onPath("tmux", env) || onPath("herdr", env);
56
+ }
57
+ function onPath(bin, env) {
58
+ return (env.PATH ?? "").split(delimiter).some((d) => d !== "" && existsSync(join(d, bin)));
59
+ }
60
+ function defaultRunner() {
61
+ let cli;
62
+ try {
63
+ const p = fileURLToPath(new URL("../cli.js", import.meta.url));
64
+ if (existsSync(p)) cli = p;
65
+ } catch {
66
+ }
67
+ return (args, signal) => new Promise((resolve) => {
68
+ const [cmd, argv] = cli ? [process.execPath, [cli, ...args]] : ["mu", [...args]];
69
+ execFile(
70
+ cmd,
71
+ argv,
72
+ { maxBuffer: 16 * 1024 * 1024, ...signal ? { signal } : {} },
73
+ (err, stdout, stderr) => {
74
+ const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
75
+ resolve({ code, stdout: String(stdout), stderr: String(stderr || err?.message || "") });
76
+ }
77
+ );
78
+ });
79
+ }
80
+ function delegateMessage(name, agent, pane, tail, attach) {
81
+ const who = `Delegate ${W}/${name}`;
82
+ const fate = pane.closed ? "Pane closed." : `Pane kept (${pane.why}).`;
83
+ const look = [
84
+ attach ? `Attach: ${attach}` : void 0,
85
+ `Read: mu agent read ${name} -n 50 -w ${W}`
86
+ ].filter((x) => x !== void 0);
87
+ const tailBlock = tail?.trim() ? `
88
+
89
+ Pane tail:
90
+ ${tail.trimEnd()}` : "";
91
+ switch (agent?.outcome) {
92
+ case "done":
93
+ return `${who} finished. ${fate}
94
+
95
+ ${agent.lastText ?? ""}`;
96
+ case "empty":
97
+ return `${who} finished without a text answer. ${fate}${tailBlock}`;
98
+ case "died":
99
+ return `${who} died before answering. ${fate}${tailBlock}`;
100
+ case "timeout":
101
+ case "pending":
102
+ return [
103
+ `${who} is still running after ${DELEGATE_TIMEOUT_S}s; stopped waiting. ${fate}`,
104
+ ...look,
105
+ `Wait again: mu agent wait ${name} -w ${W} --json`
106
+ ].join("\n");
107
+ default:
108
+ return `${who}: mu agent wait gave no result. ${fate}${tailBlock}`;
109
+ }
110
+ }
111
+ function registerDelegate(pi, run = defaultRunner()) {
112
+ if (!delegateEnabled()) return;
113
+ const inflight = /* @__PURE__ */ new Map();
114
+ const reserved = /* @__PURE__ */ new Set();
115
+ const mu = run;
116
+ const json = (r) => {
117
+ try {
118
+ const v = JSON.parse(r.stdout);
119
+ return typeof v === "object" && v !== null ? v : void 0;
120
+ } catch {
121
+ return void 0;
122
+ }
123
+ };
124
+ const failure = (what, r) => new Error(`${what} failed (exit ${r.code}): ${(r.stderr || r.stdout).trim()}`);
125
+ async function pickName() {
126
+ const r = await mu(["agent", "list", "-w", W, "--json"]);
127
+ const agents = json(r)?.agents;
128
+ const taken = new Set(
129
+ Array.isArray(agents) ? agents.map((a) => a.name).filter((n) => n) : []
130
+ );
131
+ for (let n = 1; ; n++) {
132
+ const name = `delegate-${n}`;
133
+ if (!taken.has(name) && !reserved.has(name)) {
134
+ reserved.add(name);
135
+ return name;
136
+ }
137
+ }
138
+ }
139
+ async function spawn(params) {
140
+ for (let attempt = 0; ; attempt++) {
141
+ const name = await pickName();
142
+ const args = ["agent", "spawn", name, "-w", W, "--json"];
143
+ if (params.workspace === true) args.push("--workspace");
144
+ if (typeof params.cli === "string" && params.cli) args.push("--cli", params.cli);
145
+ const r = await mu(args);
146
+ if (r.code !== 0 && /already exists/.test(r.stderr) && attempt < 3) continue;
147
+ if (r.code !== 0) {
148
+ reserved.delete(name);
149
+ throw failure("mu agent spawn", r);
150
+ }
151
+ const out = json(r) ?? {};
152
+ if (out.ctl !== "ok") {
153
+ reserved.delete(name);
154
+ throw new Error(
155
+ `${W}/${name} spawned but its control socket is ${String(out.ctl)}, so its answer cannot come back. Pane kept; close it with: mu agent close ${name} -w ${W}`
156
+ );
157
+ }
158
+ const steps = Array.isArray(out.nextSteps) ? out.nextSteps : [];
159
+ const attach = steps.map((s) => s).find((s) => s.intent === "Attach the pane")?.command;
160
+ return { name, ...typeof attach === "string" ? { attach } : {} };
161
+ }
162
+ }
163
+ async function deliver(name, wait, keep, attach) {
164
+ const agent = json(wait)?.agents?.[0];
165
+ const outcome = agent?.outcome;
166
+ const tail = outcome === "done" || outcome === "timeout" || outcome === "pending" ? void 0 : (await mu(["agent", "read", name, "-n", "50", "-w", W])).stdout;
167
+ let pane = { closed: false, why: "keep: true" };
168
+ if (outcome !== "done" && outcome !== "empty")
169
+ pane = { closed: false, why: "kept as evidence" };
170
+ else if (!keep) {
171
+ const c = await mu(["agent", "close", name, "-w", W]);
172
+ pane = c.code === 0 ? { closed: true } : { closed: false, why: `close refused: ${(c.stderr || c.stdout).trim()}` };
173
+ }
174
+ await pi.sendMessage(
175
+ {
176
+ customType: DELEGATE_MESSAGE_TYPE,
177
+ content: delegateMessage(name, agent, pane, tail, attach),
178
+ display: true,
179
+ details: { name, workstream: W, outcome: outcome ?? null, closed: pane.closed }
180
+ },
181
+ { deliverAs: "followUp", triggerTurn: true }
182
+ );
183
+ }
184
+ pi.registerTool({
185
+ name: DELEGATE_TOOL,
186
+ label: "mu delegate",
187
+ description: `Hand one self-contained task to a fresh pi agent in its own mux pane (mu's ${W} workstream) and keep working. Returns at once; the delegate's final answer arrives later as a follow-up message, so do not wait or poll for it. Use it for independent side work (research, a review, a draft, an investigation) you would otherwise do serially. The delegate starts with no context: put everything it needs in task. Several calls run in parallel. The pane is closed after a clean finish unless keep is true; the user can attach to it meanwhile.`,
188
+ promptSnippet: "Delegate a self-contained task to a background pi agent; its answer arrives later",
189
+ parameters: {
190
+ type: "object",
191
+ properties: {
192
+ task: { type: "string", description: "The complete task, with all context it needs" },
193
+ brief: {
194
+ type: "string",
195
+ description: "Optional persona or ground rules, sent before the task"
196
+ },
197
+ workspace: {
198
+ type: "boolean",
199
+ description: "Give it its own VCS workspace (for tasks that edit files)"
200
+ },
201
+ cli: {
202
+ type: "string",
203
+ description: "Agent CLI key; resolves $MU_<CLI>_COMMAND (default pi)"
204
+ },
205
+ keep: {
206
+ type: "boolean",
207
+ description: "Keep the pane after it finishes, to talk to it again"
208
+ }
209
+ },
210
+ required: ["task"],
211
+ additionalProperties: false
212
+ },
213
+ async execute(_id, params) {
214
+ const task = typeof params.task === "string" ? params.task.trim() : "";
215
+ if (!task) throw new Error("mu_delegate needs a non-empty task");
216
+ const brief = typeof params.brief === "string" ? params.brief.trim() : "";
217
+ const keep = params.keep === true;
218
+ const { name, attach } = await spawn(params);
219
+ const entry = { abort: new AbortController(), cancelled: false };
220
+ inflight.set(name, entry);
221
+ const waiting = mu(
222
+ ["agent", "wait", name, "-w", W, "--json", "--timeout", String(DELEGATE_TIMEOUT_S)],
223
+ entry.abort.signal
224
+ );
225
+ const sent = await mu(["agent", "send", name, brief ? `${brief}
226
+
227
+ ${task}` : task, "-w", W]);
228
+ if (sent.code !== 0) {
229
+ entry.abort.abort();
230
+ inflight.delete(name);
231
+ throw failure(`mu agent send to ${W}/${name}`, sent);
232
+ }
233
+ void waiting.then(async (r) => {
234
+ if (inflight.get(name) !== entry || entry.cancelled) return;
235
+ inflight.delete(name);
236
+ reserved.delete(name);
237
+ await deliver(name, r, keep, attach);
238
+ });
239
+ const lines = [
240
+ `Delegated to ${W}/${name}. Its answer arrives as a follow-up message when it finishes; do not wait or poll.`,
241
+ attach ? `Attach: ${attach}` : void 0,
242
+ `Cancel: ${DELEGATE_CANCEL_TOOL} { name: "${name}" }`
243
+ ].filter((x) => x !== void 0);
244
+ return {
245
+ content: [{ type: "text", text: lines.join("\n") }],
246
+ details: { name, workstream: W, attach: attach ?? null, keep }
247
+ };
248
+ }
249
+ });
250
+ pi.registerTool({
251
+ name: DELEGATE_CANCEL_TOOL,
252
+ label: "mu delegate cancel",
253
+ description: `Stop a delegate started by ${DELEGATE_TOOL}: abort its turn and close its pane (keep: true leaves the pane open). No follow-up message is sent for it afterwards.`,
254
+ parameters: {
255
+ type: "object",
256
+ properties: {
257
+ name: { type: "string", description: "Delegate name, e.g. delegate-1" },
258
+ keep: { type: "boolean", description: "Leave the pane open after aborting" }
259
+ },
260
+ required: ["name"],
261
+ additionalProperties: false
262
+ },
263
+ async execute(_id, params) {
264
+ const name = typeof params.name === "string" ? params.name : "";
265
+ if (!name) throw new Error("mu_delegate_cancel needs a name");
266
+ const entry = inflight.get(name);
267
+ if (entry) entry.cancelled = true;
268
+ const ab = await mu(["agent", "abort", name, "-w", W]);
269
+ if (ab.code !== 0) {
270
+ if (entry) entry.cancelled = false;
271
+ throw failure(`mu agent abort ${W}/${name}`, ab);
272
+ }
273
+ entry?.abort.abort();
274
+ inflight.delete(name);
275
+ reserved.delete(name);
276
+ let text = `Aborted ${W}/${name}.`;
277
+ if (params.keep === true) text += " Pane kept.";
278
+ else {
279
+ const c = await mu(["agent", "close", name, "-w", W]);
280
+ text += c.code === 0 ? " Pane closed." : ` Pane kept: close refused: ${(c.stderr || c.stdout).trim()}`;
281
+ }
282
+ return { content: [{ type: "text", text }], details: { name, workstream: W } };
283
+ }
284
+ });
285
+ pi.on("session_shutdown", (_e, ctx) => {
286
+ if (inflight.size === 0) return;
287
+ const names = [...inflight.keys()];
288
+ for (const e of inflight.values()) e.abort.abort();
289
+ inflight.clear();
290
+ const msg = `${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.`;
291
+ if (ctx?.hasUI && ctx.ui) ctx.ui.notify(msg, "warning");
292
+ else process.stderr.write(`mu: ${msg}
293
+ `);
294
+ });
295
+ }
296
+
297
+ // extension/mu-pi.ts
298
+ var EXT_VERSION = true ? "3.2.0" : void 0;
299
+ var FRESH_COMMAND = "mu-fresh";
300
+ var FRESH_TIMEOUT_MS = 3e4;
301
+ var LAST_TEXT_MAX_BYTES = 64 * 1024;
302
+ var LAST_TEXT_TRUNCATED = "[truncated, see pane]";
303
+ var LIVE_PROBE_MS = 500;
304
+ var V = CTL_PROTOCOL_VERSION;
305
+ function fail(error) {
306
+ return { v: V, ok: false, error };
307
+ }
308
+ function detectPiVersion() {
309
+ try {
310
+ const entry = process.argv[1];
311
+ if (!entry) return void 0;
312
+ let dir = dirname(realpathSync(entry));
313
+ for (let i = 0; i < 6; i++) {
314
+ const pkgPath = join2(dir, "package.json");
315
+ if (existsSync2(pkgPath)) {
316
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
317
+ if (typeof pkg === "object" && pkg !== null) {
318
+ const { name, version } = pkg;
319
+ if (typeof name === "string" && /pi/.test(name) && typeof version === "string") {
320
+ return version;
321
+ }
322
+ }
323
+ }
324
+ const up = dirname(dir);
325
+ if (up === dir) break;
326
+ dir = up;
327
+ }
328
+ } catch {
329
+ }
330
+ return void 0;
331
+ }
332
+ function str(o, k) {
333
+ const x = o[k];
334
+ return typeof x === "string" ? x : void 0;
335
+ }
336
+ function num(o, k) {
337
+ const x = o[k];
338
+ return typeof x === "number" && Number.isFinite(x) ? x : void 0;
339
+ }
340
+ var SHARED = /* @__PURE__ */ Symbol.for("mu.pi.ctl");
341
+ function sharedMap() {
342
+ const g = globalThis;
343
+ g[SHARED] ??= /* @__PURE__ */ new Map();
344
+ return g[SHARED];
345
+ }
346
+ function lastAssistantText(event) {
347
+ const msgs = typeof event === "object" && event !== null ? event.messages : void 0;
348
+ if (!Array.isArray(msgs)) return "";
349
+ for (let i = msgs.length - 1; i >= 0; i--) {
350
+ const m = msgs[i];
351
+ if (typeof m !== "object" || m === null) continue;
352
+ const { role, content } = m;
353
+ if (role !== "assistant") continue;
354
+ if (typeof content === "string") return capText(content);
355
+ if (!Array.isArray(content)) return "";
356
+ const parts = content.flatMap((c) => {
357
+ if (typeof c !== "object" || c === null) return [];
358
+ const { type, text } = c;
359
+ return type === "text" && typeof text === "string" ? [text] : [];
360
+ });
361
+ return capText(parts.join(""));
362
+ }
363
+ return "";
364
+ }
365
+ function capText(text) {
366
+ if (Buffer.byteLength(text, "utf8") <= LAST_TEXT_MAX_BYTES) return text;
367
+ const cut = Buffer.from(text, "utf8").subarray(0, LAST_TEXT_MAX_BYTES).toString("utf8");
368
+ return `${cut.replace(/\uFFFD$/, "")}
369
+ ${LAST_TEXT_TRUNCATED}`;
370
+ }
371
+ function inodeOf(path) {
372
+ try {
373
+ return statSync(path).ino;
374
+ } catch {
375
+ return void 0;
376
+ }
377
+ }
378
+ function socketIsLive(path) {
379
+ return new Promise((resolve) => {
380
+ const c = connect(path);
381
+ const done = (live) => {
382
+ clearTimeout(timer);
383
+ c.destroy();
384
+ resolve(live);
385
+ };
386
+ const timer = setTimeout(() => done(true), LIVE_PROBE_MS);
387
+ c.once("connect", () => done(true));
388
+ c.once(
389
+ "error",
390
+ (e) => done(e.code !== "ENOENT" && e.code !== "ECONNREFUSED")
391
+ );
392
+ });
393
+ }
394
+ var bindSeq = 0;
395
+ function reasonOf(event) {
396
+ const r = typeof event === "object" && event !== null ? event.reason : void 0;
397
+ return typeof r === "string" ? r : void 0;
398
+ }
399
+ function muPi(pi) {
400
+ registerDelegate(pi);
401
+ serveCtl(pi);
402
+ }
403
+ function serveCtl(pi) {
404
+ const sock = process.env.MU_CTL_SOCK;
405
+ if (!sock) return;
406
+ const sockPath = sock;
407
+ const piVersion = detectPiVersion();
408
+ const map = sharedMap();
409
+ const existing = map.get(sockPath);
410
+ const g = existing ?? {
411
+ conns: /* @__PURE__ */ new Set(),
412
+ waiters: /* @__PURE__ */ new Set(),
413
+ state: "idle",
414
+ since: Date.now(),
415
+ runs: 0,
416
+ lastText: "",
417
+ pi,
418
+ handle
419
+ };
420
+ g.pi = pi;
421
+ g.handle = handle;
422
+ map.set(sockPath, g);
423
+ const status = () => ({
424
+ state: g.state,
425
+ since: g.since,
426
+ runs: g.runs,
427
+ pending: g.ctx?.hasPendingMessages() ?? false
428
+ });
429
+ const settleFresh = (r) => {
430
+ const f = g.fresh;
431
+ if (!f) return;
432
+ g.fresh = void 0;
433
+ f.settle(r);
434
+ };
435
+ pi.on("agent_start", (_e, c) => {
436
+ g.ctx = c;
437
+ g.state = "busy";
438
+ g.since = Date.now();
439
+ if (g.fresh?.armed) settleFresh({ v: V, ok: true, ...status() });
440
+ });
441
+ pi.on("agent_end", (e) => {
442
+ g.endText = lastAssistantText(e);
443
+ });
444
+ pi.on("agent_settled", (_e, c) => {
445
+ g.ctx = c;
446
+ g.state = "idle";
447
+ g.since = Date.now();
448
+ g.runs++;
449
+ g.lastText = g.endText ?? "";
450
+ g.endText = void 0;
451
+ for (const w of [...g.waiters]) w();
452
+ });
453
+ pi.registerCommand(FRESH_COMMAND, {
454
+ description: "mu internal: new session + the prompt mu queued (run by mu agent send --fresh)",
455
+ handler: async (_args, ctx) => {
456
+ const f = g.fresh;
457
+ if (!f) return;
458
+ try {
459
+ const r = await ctx.newSession({
460
+ withSession: async (c) => {
461
+ g.ctx = c;
462
+ f.armed = true;
463
+ await c.sendUserMessage(f.text);
464
+ }
465
+ });
466
+ if (r.cancelled) settleFresh(fail("new session was cancelled"));
467
+ } catch (e) {
468
+ settleFresh(fail(e instanceof Error ? e.message : String(e)));
469
+ }
470
+ settleFresh({ v: V, ok: true, ...status() });
471
+ }
472
+ });
473
+ function fresh(req) {
474
+ const text = str(req, "text");
475
+ if (text === void 0) return fail("fresh needs a string text");
476
+ if (g.fresh) return fail("a fresh send is already in progress");
477
+ const busy = !(g.ctx?.isIdle() ?? g.state === "idle");
478
+ if (busy && req.force !== true) return fail("busy");
479
+ return new Promise((resolve) => {
480
+ const timer = setTimeout(() => settleFresh(fail("fresh timed out")), FRESH_TIMEOUT_MS);
481
+ g.fresh = {
482
+ text,
483
+ armed: false,
484
+ settle: (r) => {
485
+ clearTimeout(timer);
486
+ resolve(r);
487
+ }
488
+ };
489
+ Promise.resolve(
490
+ g.pi.sendUserMessage(`/${FRESH_COMMAND}`, { expandPromptTemplates: true })
491
+ ).catch((e) => settleFresh(fail(e instanceof Error ? e.message : String(e))));
492
+ });
493
+ }
494
+ async function handle(req, conn) {
495
+ switch (req.op) {
496
+ case "hello":
497
+ return {
498
+ v: V,
499
+ ok: true,
500
+ agent: process.env.MU_AGENT_NAME,
501
+ workstream: process.env.MU_WORKSTREAM,
502
+ piVersion,
503
+ ops: [...CTL_OPS],
504
+ ...EXT_VERSION !== void 0 ? { extVersion: EXT_VERSION } : {}
505
+ };
506
+ case "status":
507
+ return { v: V, ok: true, ...status() };
508
+ case "send": {
509
+ const text = str(req, "text");
510
+ if (text === void 0) return fail("send needs a string text");
511
+ const mode = req.mode === "steer" ? "steer" : "followUp";
512
+ if (g.ctx?.isIdle() ?? g.state === "idle") await g.pi.sendUserMessage(text);
513
+ else await g.pi.sendUserMessage(text, { deliverAs: mode });
514
+ return { v: V, ok: true, state: g.state };
515
+ }
516
+ case "fresh":
517
+ return fresh(req);
518
+ case "wait":
519
+ return wait(num(req, "afterRuns"), num(req, "timeoutMs"), conn);
520
+ case "abort":
521
+ if (!g.ctx) return fail("no pi context yet");
522
+ g.ctx.abort();
523
+ return { v: V, ok: true, state: g.state };
524
+ default:
525
+ return { ...fail(`${UNKNOWN_OP_PREFIX}${String(req.op)}`), ops: [...CTL_OPS] };
526
+ }
527
+ }
528
+ function wait(afterRuns, timeoutMs, conn) {
529
+ if (afterRuns !== void 0 && g.runs > afterRuns && g.state === "idle") {
530
+ return Promise.resolve({ v: V, ok: true, ...status(), lastText: g.lastText });
531
+ }
532
+ return new Promise((resolve) => {
533
+ let timer;
534
+ const done = (r) => {
535
+ g.waiters.delete(waiter);
536
+ if (timer) clearTimeout(timer);
537
+ conn.off("close", onClose);
538
+ resolve(r);
539
+ };
540
+ const waiter = () => done({ v: V, ok: true, ...status(), lastText: g.lastText });
541
+ const onClose = () => done(fail("client closed"));
542
+ g.waiters.add(waiter);
543
+ conn.on("close", onClose);
544
+ if (timeoutMs !== void 0) timer = setTimeout(() => done(fail("timeout")), timeoutMs);
545
+ });
546
+ }
547
+ function onConnection(conn) {
548
+ g.conns.add(conn);
549
+ conn.setEncoding("utf8");
550
+ const dec = new LineDecoder();
551
+ let seen = false;
552
+ conn.on("close", () => g.conns.delete(conn));
553
+ conn.on("error", () => {
554
+ });
555
+ conn.on("data", (chunk) => {
556
+ const line = dec.push(chunk)[0];
557
+ if (line === void 0 || seen) return;
558
+ seen = true;
559
+ let req;
560
+ try {
561
+ req = JSON.parse(line);
562
+ } catch {
563
+ conn.end(encode(fail("bad json")));
564
+ return;
565
+ }
566
+ if (typeof req !== "object" || req === null) {
567
+ conn.end(encode(fail("request must be an object")));
568
+ return;
569
+ }
570
+ g.handle(req, conn).then(
571
+ (r) => {
572
+ if (!conn.destroyed) conn.end(encode(r));
573
+ },
574
+ (e) => {
575
+ if (!conn.destroyed) conn.end(encode(fail(e instanceof Error ? e.message : String(e))));
576
+ }
577
+ );
578
+ });
579
+ }
580
+ async function bind() {
581
+ mkdirSync(dirname(sockPath), { recursive: true, mode: 448 });
582
+ if (await socketIsLive(sockPath)) {
583
+ process.stderr.write(
584
+ `mu: control socket ${sockPath} is served by another pi; this pi will not take it
585
+ `
586
+ );
587
+ return;
588
+ }
589
+ rmSync(sockPath, { force: true });
590
+ const tmp = join2(dirname(sockPath), `.${process.pid.toString(36)}${(++bindSeq).toString(36)}`);
591
+ rmSync(tmp, { force: true });
592
+ const s = createServer(onConnection);
593
+ await new Promise((resolve, reject) => {
594
+ s.once("error", reject);
595
+ s.listen(tmp, () => {
596
+ s.off("error", reject);
597
+ resolve();
598
+ });
599
+ });
600
+ chmodSync(tmp, 384);
601
+ try {
602
+ linkSync(tmp, sockPath);
603
+ } catch (e) {
604
+ s.close();
605
+ if (e.code !== "EEXIST") throw e;
606
+ process.stderr.write(`mu: control socket ${sockPath} was taken by another pi
607
+ `);
608
+ return;
609
+ } finally {
610
+ rmSync(tmp, { force: true });
611
+ }
612
+ g.server = s;
613
+ g.ino = inodeOf(sockPath);
614
+ }
615
+ pi.on("session_start", async (_e, c) => {
616
+ g.ctx = c;
617
+ const s = g.server;
618
+ if (s) {
619
+ if (g.ino !== void 0 && inodeOf(sockPath) === g.ino) return;
620
+ g.server = void 0;
621
+ g.ino = void 0;
622
+ s.close();
623
+ }
624
+ await bind();
625
+ });
626
+ pi.on("session_shutdown", async (e) => {
627
+ const reason = reasonOf(e);
628
+ if (reason === "new" || reason === "resume" || reason === "fork" || reason === "reload") {
629
+ return;
630
+ }
631
+ const s = g.server;
632
+ const ino = g.ino;
633
+ g.server = void 0;
634
+ g.ino = void 0;
635
+ map.delete(sockPath);
636
+ if (!s) return;
637
+ g.waiters.clear();
638
+ settleFresh(fail("pi is shutting down"));
639
+ for (const c of g.conns) c.destroy();
640
+ await new Promise((resolve) => s.close(() => resolve()));
641
+ if (ino !== void 0 && inodeOf(sockPath) === ino) rmSync(sockPath, { force: true });
642
+ });
643
+ }
644
+ export {
645
+ FRESH_COMMAND,
646
+ FRESH_TIMEOUT_MS,
647
+ LAST_TEXT_MAX_BYTES,
648
+ LAST_TEXT_TRUNCATED,
649
+ LIVE_PROBE_MS,
650
+ muPi as default,
651
+ lastAssistantText
652
+ };
653
+ //# sourceMappingURL=mu-pi.js.map