@rubytech/create-maxy-code 0.1.392 → 0.1.394
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/__tests__/launchd-bootstrap.test.js +85 -0
- package/dist/__tests__/launchd-plist.test.js +41 -1
- package/dist/index.js +91 -19
- package/dist/launchd-bootstrap.js +57 -0
- package/dist/launchd-plist.js +27 -0
- package/dist/uninstall.js +56 -20
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -1
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +6 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +8 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +17 -2
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js +22 -4
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +20 -0
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +18 -0
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -1
- package/payload/server/server.js +15 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// acceptance grid for launchd-bootstrap.ts (Task 1401).
|
|
2
|
+
//
|
|
3
|
+
// Locks the darwin session-manager bootstrap contract: bootout (idempotent,
|
|
4
|
+
// rc 3 "No such process" ignored) then bootstrap, with a bounded retry on
|
|
5
|
+
// exit 5 ("Input/output error" — a known transient/racey launchctl failure on
|
|
6
|
+
// re-install). Pure logic — the launchctl runner and the retry sleep are both
|
|
7
|
+
// injected, so no real launchctl or wall-clock delay runs here.
|
|
8
|
+
//
|
|
9
|
+
// Compiles to dist/__tests__/launchd-bootstrap.test.js so
|
|
10
|
+
// `node --test dist/__tests__/*.test.js` picks it up after build.
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { bootstrapLaunchAgent } from "../launchd-bootstrap.js";
|
|
14
|
+
const OPTS = { gui: "gui/502", label: "com.rubytech.host-claude-session-manager", plistPath: "/tmp/sm.plist" };
|
|
15
|
+
const noSleep = () => { };
|
|
16
|
+
test("first bootstrap succeeds — one bootout, one bootstrap, attempts=1", () => {
|
|
17
|
+
const calls = [];
|
|
18
|
+
const run = (args) => {
|
|
19
|
+
calls.push(args);
|
|
20
|
+
return args[0] === "bootstrap" ? { status: 0, stderr: "" } : { status: 3, stderr: "No such process" };
|
|
21
|
+
};
|
|
22
|
+
const r = bootstrapLaunchAgent(run, { ...OPTS, sleepFn: noSleep });
|
|
23
|
+
assert.equal(r.ok, true);
|
|
24
|
+
assert.equal(r.attempts, 1);
|
|
25
|
+
assert.deepEqual(calls[0], ["bootout", "gui/502/com.rubytech.host-claude-session-manager"]);
|
|
26
|
+
assert.deepEqual(calls[1], ["bootstrap", "gui/502", "/tmp/sm.plist"]);
|
|
27
|
+
assert.equal(calls.length, 2);
|
|
28
|
+
});
|
|
29
|
+
test("exit 5 twice then 0 — retries and reports the winning attempt", () => {
|
|
30
|
+
let bootstraps = 0;
|
|
31
|
+
const sleeps = [];
|
|
32
|
+
const run = (args) => {
|
|
33
|
+
if (args[0] !== "bootstrap")
|
|
34
|
+
return { status: 3, stderr: "No such process" };
|
|
35
|
+
bootstraps += 1;
|
|
36
|
+
return bootstraps < 3 ? { status: 5, stderr: "Bootstrap failed: 5: Input/output error" } : { status: 0, stderr: "" };
|
|
37
|
+
};
|
|
38
|
+
const r = bootstrapLaunchAgent(run, { ...OPTS, sleepFn: (ms) => { sleeps.push(ms); } });
|
|
39
|
+
assert.equal(r.ok, true);
|
|
40
|
+
assert.equal(r.attempts, 3);
|
|
41
|
+
assert.equal(bootstraps, 3);
|
|
42
|
+
assert.equal(sleeps.length, 2); // slept before each of the two retries
|
|
43
|
+
});
|
|
44
|
+
test("exit 5 on every attempt — fails after the attempt cap, not before", () => {
|
|
45
|
+
let bootstraps = 0;
|
|
46
|
+
const run = (args) => {
|
|
47
|
+
if (args[0] !== "bootstrap")
|
|
48
|
+
return { status: 3, stderr: "No such process" };
|
|
49
|
+
bootstraps += 1;
|
|
50
|
+
return { status: 5, stderr: "Bootstrap failed: 5: Input/output error" };
|
|
51
|
+
};
|
|
52
|
+
const r = bootstrapLaunchAgent(run, { ...OPTS, maxAttempts: 3, sleepFn: noSleep });
|
|
53
|
+
assert.equal(r.ok, false);
|
|
54
|
+
assert.equal(r.attempts, 3);
|
|
55
|
+
assert.equal(r.bootstrapStatus, 5);
|
|
56
|
+
assert.equal(bootstraps, 3);
|
|
57
|
+
});
|
|
58
|
+
test("non-5 non-zero bootstrap aborts immediately — no retry", () => {
|
|
59
|
+
let bootstraps = 0;
|
|
60
|
+
const run = (args) => {
|
|
61
|
+
if (args[0] !== "bootstrap")
|
|
62
|
+
return { status: 3, stderr: "No such process" };
|
|
63
|
+
bootstraps += 1;
|
|
64
|
+
return { status: 9, stderr: "some other launchd failure" };
|
|
65
|
+
};
|
|
66
|
+
const r = bootstrapLaunchAgent(run, { ...OPTS, maxAttempts: 3, sleepFn: noSleep });
|
|
67
|
+
assert.equal(r.ok, false);
|
|
68
|
+
assert.equal(r.attempts, 1);
|
|
69
|
+
assert.equal(r.bootstrapStatus, 9);
|
|
70
|
+
assert.equal(bootstraps, 1);
|
|
71
|
+
});
|
|
72
|
+
test("emits one structured log line per attempt with bootout and bootstrap rc", () => {
|
|
73
|
+
let bootstraps = 0;
|
|
74
|
+
const lines = [];
|
|
75
|
+
const run = (args) => {
|
|
76
|
+
if (args[0] !== "bootstrap")
|
|
77
|
+
return { status: 3, stderr: "No such process" };
|
|
78
|
+
bootstraps += 1;
|
|
79
|
+
return bootstraps < 2 ? { status: 5, stderr: "eio" } : { status: 0, stderr: "" };
|
|
80
|
+
};
|
|
81
|
+
bootstrapLaunchAgent(run, { ...OPTS, sleepFn: noSleep, logger: (l) => lines.push(l) });
|
|
82
|
+
assert.equal(lines.length, 2);
|
|
83
|
+
assert.match(lines[0], /agent=com\.rubytech\.host-claude-session-manager bootout=3 bootstrap=5 attempt=1/);
|
|
84
|
+
assert.match(lines[1], /bootout=3 bootstrap=0 attempt=2/);
|
|
85
|
+
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// package so `node --test dist/__tests__/*.test.js` picks it up after build.
|
|
11
11
|
import test from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
|
-
import { renderPlist, buildSessionManagerWrapper } from "../launchd-plist.js";
|
|
13
|
+
import { renderPlist, buildSessionManagerWrapper, buildHeartbeatWrapper } from "../launchd-plist.js";
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Standard render — every required field present, no escaping needed.
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
@@ -213,6 +213,46 @@ test("buildSessionManagerWrapper sources .env and execs the manager", () => {
|
|
|
213
213
|
assert.match(w, /cd "\/Users\/x\/maxy-code\/platform\/services\/claude-session-manager"/);
|
|
214
214
|
assert.match(w, /exec \/usr\/local\/bin\/node dist\/index\.js\n$/);
|
|
215
215
|
});
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// StartInterval — the darwin heartbeat timer (Task 1403). Emitted as
|
|
218
|
+
// <integer> only when set; absent from the standard server/manager plists.
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
test("renderPlist emits StartInterval as <integer> when set", () => {
|
|
221
|
+
const xml = renderPlist({
|
|
222
|
+
label: "com.rubytech.maxy-code-heartbeat",
|
|
223
|
+
programArguments: ["/bin/bash", "/Users/x/.maxy-code/heartbeat-wrapper.sh"],
|
|
224
|
+
stdoutPath: "/Users/x/.maxy-code/logs/check-due-events.log",
|
|
225
|
+
stderrPath: "/Users/x/.maxy-code/logs/check-due-events.log",
|
|
226
|
+
keepAlive: false,
|
|
227
|
+
runAtLoad: false,
|
|
228
|
+
startInterval: 60,
|
|
229
|
+
});
|
|
230
|
+
assert.match(xml, /<key>StartInterval<\/key>\s*<integer>60<\/integer>/);
|
|
231
|
+
assert.match(xml, /<key>KeepAlive<\/key>\s*<false\/>/);
|
|
232
|
+
assert.match(xml, /<key>RunAtLoad<\/key>\s*<false\/>/);
|
|
233
|
+
});
|
|
234
|
+
test("renderPlist omits StartInterval key when absent (standard plists unchanged)", () => {
|
|
235
|
+
const xml = renderPlist({
|
|
236
|
+
label: "com.rubytech.maxy-code",
|
|
237
|
+
programArguments: ["/bin/bash", "/Users/x/.maxy-code/launchd-wrapper.sh"],
|
|
238
|
+
stdoutPath: "/dev/null",
|
|
239
|
+
stderrPath: "/dev/null",
|
|
240
|
+
keepAlive: true,
|
|
241
|
+
runAtLoad: true,
|
|
242
|
+
});
|
|
243
|
+
assert.ok(!/StartInterval/.test(xml), "StartInterval must not appear when omitted");
|
|
244
|
+
});
|
|
245
|
+
test("buildHeartbeatWrapper sources .env, exports PLATFORM_ROOT, execs the tick", () => {
|
|
246
|
+
const w = buildHeartbeatWrapper({
|
|
247
|
+
envPath: "/Users/x/.maxy-code/.env",
|
|
248
|
+
installDir: "/Users/x/maxy-code",
|
|
249
|
+
nodeBin: "/opt/homebrew/bin/node",
|
|
250
|
+
});
|
|
251
|
+
assert.match(w, /^#!\/bin\/bash\n/);
|
|
252
|
+
assert.match(w, /set -a; \[ -f "\/Users\/x\/\.maxy-code\/\.env" \] && \. "\/Users\/x\/\.maxy-code\/\.env"; set \+a/);
|
|
253
|
+
assert.match(w, /export PLATFORM_ROOT="\/Users\/x\/maxy-code\/platform"/);
|
|
254
|
+
assert.match(w, /exec \/opt\/homebrew\/bin\/node \/Users\/x\/maxy-code\/platform\/plugins\/scheduling\/mcp\/dist\/scripts\/check-due-events\.js\n$/);
|
|
255
|
+
});
|
|
216
256
|
test("session-manager plist renders bash wrapper as sole ProgramArguments", () => {
|
|
217
257
|
const xml = renderPlist({
|
|
218
258
|
label: "com.rubytech.maxy-code-claude-session-manager",
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,8 @@ import { requireSupportedPlatform, detectPlatform } from "./platform-detect.js";
|
|
|
16
16
|
import { decideClaudeUpgradePrivilege } from "./claude-upgrade-privilege.js";
|
|
17
17
|
import { resolveNeo4jDataDir } from "./neo4j-datadir.js";
|
|
18
18
|
import { protectCommands, unprotectCommands, lsattrShowsImmutable } from "./install-immutability.js";
|
|
19
|
-
import { renderPlist, buildSessionManagerWrapper } from "./launchd-plist.js";
|
|
19
|
+
import { renderPlist, buildSessionManagerWrapper, buildHeartbeatWrapper } from "./launchd-plist.js";
|
|
20
|
+
import { bootstrapLaunchAgent } from "./launchd-bootstrap.js";
|
|
20
21
|
import { enumerateClaudeCandidates, pickCanonicalClaude } from "./claude-bin.js";
|
|
21
22
|
import { installAllBrewPackages } from "./brew-install.js";
|
|
22
23
|
import { parseSwVers, isSupportedMacosVersion } from "./macos-version.js";
|
|
@@ -1898,7 +1899,16 @@ function capOllamaServiceCpu() {
|
|
|
1898
1899
|
function installOllama(embedModel) {
|
|
1899
1900
|
if (!commandExists("ollama")) {
|
|
1900
1901
|
log("5", TOTAL, "Installing Ollama...");
|
|
1901
|
-
|
|
1902
|
+
// The upstream curl|sh installer only places a Linux systemd daemon; on
|
|
1903
|
+
// darwin it leaves no `ollama` CLI, so ensureOllamaServing()'s `ollama serve`
|
|
1904
|
+
// spawn would fail. Install via Homebrew instead, mirroring installNeo4j /
|
|
1905
|
+
// installCloudflared (Task 1403).
|
|
1906
|
+
if (requireSupportedPlatform(process.platform) === "darwin") {
|
|
1907
|
+
installAllBrewPackages(["ollama"], logFile);
|
|
1908
|
+
}
|
|
1909
|
+
else {
|
|
1910
|
+
spawnSync("bash", ["-c", "curl -fsSL https://ollama.ai/install.sh | sh"], { stdio: "inherit" });
|
|
1911
|
+
}
|
|
1902
1912
|
}
|
|
1903
1913
|
else {
|
|
1904
1914
|
log("5", TOTAL, "Ollama already installed.");
|
|
@@ -3300,6 +3310,15 @@ function installServiceDarwin() {
|
|
|
3300
3310
|
`setupAccount() (setup-account.sh) should have created one. Refusing to write .env ` +
|
|
3301
3311
|
`without ACCOUNT_ID; the boot validator would FATAL on every kickstart.`);
|
|
3302
3312
|
}
|
|
3313
|
+
// Resolve node once, up front: the launchd wrapper execs it (below) and the
|
|
3314
|
+
// .env pins it as NODE_BIN so the session-manager's per-session channel MCP
|
|
3315
|
+
// descriptors invoke node by absolute path. The bare launchd PATH excludes an
|
|
3316
|
+
// nvm node, so a bare `node` command in an MCP descriptor fails to spawn and
|
|
3317
|
+
// the channel never boots (Task 1402). Intel `/usr/local/bin/node`, Apple
|
|
3318
|
+
// Silicon `/opt/homebrew/bin/node`, or nvm's versioned path — whatever the
|
|
3319
|
+
// installer's own shell resolves.
|
|
3320
|
+
const nodeProbe = spawnSync("command", ["-v", "node"], { encoding: "utf-8", shell: true });
|
|
3321
|
+
const nodeBin = (nodeProbe.stdout ?? "").trim() || "/usr/local/bin/node";
|
|
3303
3322
|
const envPath = join(persistDir, ".env");
|
|
3304
3323
|
try {
|
|
3305
3324
|
let envContent = "";
|
|
@@ -3350,8 +3369,19 @@ function installServiceDarwin() {
|
|
|
3350
3369
|
else
|
|
3351
3370
|
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `CLAUDE_BIN=${canonicalClaude}\n`;
|
|
3352
3371
|
}
|
|
3372
|
+
// Task 1402 — pin the resolved node the same way. The session-manager reads
|
|
3373
|
+
// NODE_BIN and stamps it as the `command` of every per-session channel MCP
|
|
3374
|
+
// descriptor, so a webchat/WhatsApp/Telegram channel spawns node by absolute
|
|
3375
|
+
// path instead of a bare `node` the launchd PATH can't resolve.
|
|
3376
|
+
{
|
|
3377
|
+
const re = /^NODE_BIN=.*$/m;
|
|
3378
|
+
if (re.test(envContent))
|
|
3379
|
+
envContent = envContent.replace(re, `NODE_BIN=${nodeBin}`);
|
|
3380
|
+
else
|
|
3381
|
+
envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `NODE_BIN=${nodeBin}\n`;
|
|
3382
|
+
}
|
|
3353
3383
|
writeFileSync(envPath, envContent);
|
|
3354
|
-
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}, MAXY_UI_INTERNAL_PORT=${PORT} (darwin-collapsed), CLAUDE_SESSION_MANAGER_PORT=${BRAND.claudeSessionManagerPort}, HOSTNAME=0.0.0.0, ACCOUNT_ID=${installAccountId.slice(0, 8)}…, CLAUDE_CONFIG_DIR=${persistDir}/.claude, CLAUDE_BIN=${canonicalClaude ?? "unresolved(PATH)"}`);
|
|
3384
|
+
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}, MAXY_UI_INTERNAL_PORT=${PORT} (darwin-collapsed), CLAUDE_SESSION_MANAGER_PORT=${BRAND.claudeSessionManagerPort}, HOSTNAME=0.0.0.0, ACCOUNT_ID=${installAccountId.slice(0, 8)}…, CLAUDE_CONFIG_DIR=${persistDir}/.claude, CLAUDE_BIN=${canonicalClaude ?? "unresolved(PATH)"}, NODE_BIN=${nodeBin}`);
|
|
3355
3385
|
}
|
|
3356
3386
|
catch (err) {
|
|
3357
3387
|
console.error(` WARNING: failed to write .env to ${envPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3361,11 +3391,7 @@ function installServiceDarwin() {
|
|
|
3361
3391
|
// in the child env. Without this, ProgramArguments executes node directly
|
|
3362
3392
|
// and the .env values are unread — server binds the wrong port.
|
|
3363
3393
|
const wrapperPath = join(persistDir, "launchd-wrapper.sh");
|
|
3364
|
-
//
|
|
3365
|
-
// path on both Intel (/usr/local/bin/node) and Apple Silicon
|
|
3366
|
-
// (/opt/homebrew/bin/node) — Homebrew's prefix differs by arch.
|
|
3367
|
-
const nodeProbe = spawnSync("command", ["-v", "node"], { encoding: "utf-8", shell: true });
|
|
3368
|
-
const nodeBin = (nodeProbe.stdout ?? "").trim() || "/usr/local/bin/node";
|
|
3394
|
+
// `nodeBin` resolved once above (also pinned as NODE_BIN in .env).
|
|
3369
3395
|
const wrapperBody = [
|
|
3370
3396
|
"#!/bin/bash",
|
|
3371
3397
|
"# generated by create-maxy installService(). Reads.env then",
|
|
@@ -3441,21 +3467,28 @@ function installServiceDarwin() {
|
|
|
3441
3467
|
});
|
|
3442
3468
|
writeFileSync(smPlistPath, smPlist);
|
|
3443
3469
|
logFile(` ${smPlistPath} written (${smPlist.length} bytes)`);
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3470
|
+
// Task 1401 — `launchctl bootstrap` intermittently returns exit 5 ("Input/
|
|
3471
|
+
// output error") on a re-install even when the label is not loaded (its
|
|
3472
|
+
// bootout returns 3). A single bare bootstrap aborted phase 11 on that first
|
|
3473
|
+
// EIO; bootstrapLaunchAgent bootout-then-bootstraps with a bounded retry on
|
|
3474
|
+
// exit 5, so a transient failure that clears on retry no longer fails install.
|
|
3475
|
+
const smBoot = bootstrapLaunchAgent((args) => {
|
|
3476
|
+
const r = spawnSync("launchctl", args, { stdio: "pipe", encoding: "utf-8", timeout: 15_000 });
|
|
3477
|
+
return { status: r.status, stderr: (r.stderr ?? "").toString() };
|
|
3478
|
+
}, {
|
|
3479
|
+
gui: gui(),
|
|
3480
|
+
label: smLabel,
|
|
3481
|
+
plistPath: smPlistPath,
|
|
3482
|
+
logger: (line) => { console.log(` ${line}`); logFile(` ${line}`); },
|
|
3449
3483
|
});
|
|
3450
|
-
if (
|
|
3484
|
+
if (smBoot.ok) {
|
|
3451
3485
|
console.log(` [launchd] bootstrap ${gui()}/${smLabel} ok`);
|
|
3452
|
-
logFile(` [create-maxy] launchd-plist=${smLabel} loaded=true`);
|
|
3486
|
+
logFile(` [create-maxy] launchd-plist=${smLabel} loaded=true attempts=${smBoot.attempts}`);
|
|
3453
3487
|
}
|
|
3454
3488
|
else {
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
throw new Error(`launchctl bootstrap ${smLabel} failed (exit ${smBootstrap.status}): ${smStderr}`);
|
|
3489
|
+
console.error(` [launchd] bootstrap ${smLabel} returned ${smBoot.bootstrapStatus} after ${smBoot.attempts} attempt(s): ${smBoot.stderr}`);
|
|
3490
|
+
logFile(` [create-maxy] launchd-plist=${smLabel} loaded=false exit=${smBoot.bootstrapStatus} attempts=${smBoot.attempts}`);
|
|
3491
|
+
throw new Error(`launchctl bootstrap ${smLabel} failed (exit ${smBoot.bootstrapStatus}) after ${smBoot.attempts} attempt(s): ${smBoot.stderr}`);
|
|
3459
3492
|
}
|
|
3460
3493
|
// Health-probe the manager (/healthz) so a silent boot failure surfaces at
|
|
3461
3494
|
// install time, not at first session spawn as `manager-unreachable`.
|
|
@@ -3473,6 +3506,45 @@ function installServiceDarwin() {
|
|
|
3473
3506
|
if (!smUp) {
|
|
3474
3507
|
console.log(` Session manager not yet healthy on ${BRAND.claudeSessionManagerPort} — check ${join(logsDir, "server.log")}.`);
|
|
3475
3508
|
}
|
|
3509
|
+
// Task 1403 — the scheduling heartbeat. On Linux a per-minute cron ticks
|
|
3510
|
+
// check-due-events.js (installCrons/buildHeartbeatCronBlock); darwin has no
|
|
3511
|
+
// cron and installCrons early-returns, so operator-created bookings would
|
|
3512
|
+
// never dispatch. Author a StartInterval=60 LaunchAgent as the timer analogue:
|
|
3513
|
+
// keepAlive=false (a periodic one-shot, not a supervised daemon). A bootstrap
|
|
3514
|
+
// failure warns rather than aborting install — the server + manager (core
|
|
3515
|
+
// function) are already up; scheduling retries on the next installer run.
|
|
3516
|
+
const hbLabel = `${launchdLabel()}-heartbeat`;
|
|
3517
|
+
const hbPlistPath = join(launchAgentsDir(), `${hbLabel}.plist`);
|
|
3518
|
+
const hbWrapperPath = join(persistDir, "heartbeat-wrapper.sh");
|
|
3519
|
+
writeFileSync(hbWrapperPath, buildHeartbeatWrapper({ envPath, installDir: INSTALL_DIR, nodeBin }));
|
|
3520
|
+
chmodSync(hbWrapperPath, 0o755);
|
|
3521
|
+
const hbPlist = renderPlist({
|
|
3522
|
+
label: hbLabel,
|
|
3523
|
+
programArguments: ["/bin/bash", hbWrapperPath],
|
|
3524
|
+
stdoutPath: join(logsDir, "check-due-events.log"),
|
|
3525
|
+
stderrPath: join(logsDir, "check-due-events.log"),
|
|
3526
|
+
keepAlive: false,
|
|
3527
|
+
runAtLoad: false,
|
|
3528
|
+
startInterval: 60,
|
|
3529
|
+
});
|
|
3530
|
+
writeFileSync(hbPlistPath, hbPlist);
|
|
3531
|
+
logFile(` ${hbPlistPath} written (${hbPlist.length} bytes)`);
|
|
3532
|
+
const hbBoot = bootstrapLaunchAgent((args) => {
|
|
3533
|
+
const r = spawnSync("launchctl", args, { stdio: "pipe", encoding: "utf-8", timeout: 15_000 });
|
|
3534
|
+
return { status: r.status, stderr: (r.stderr ?? "").toString() };
|
|
3535
|
+
}, {
|
|
3536
|
+
gui: gui(),
|
|
3537
|
+
label: hbLabel,
|
|
3538
|
+
plistPath: hbPlistPath,
|
|
3539
|
+
logger: (line) => { console.log(` ${line}`); logFile(` ${line}`); },
|
|
3540
|
+
});
|
|
3541
|
+
if (hbBoot.ok) {
|
|
3542
|
+
logFile(` [create-maxy] launchd-plist=${hbLabel} loaded=true attempts=${hbBoot.attempts}`);
|
|
3543
|
+
}
|
|
3544
|
+
else {
|
|
3545
|
+
console.error(` WARNING: heartbeat LaunchAgent ${hbLabel} bootstrap returned ${hbBoot.bootstrapStatus} after ${hbBoot.attempts} attempt(s): ${hbBoot.stderr} — scheduling ticks will not fire until a re-install succeeds.`);
|
|
3546
|
+
logFile(` [create-maxy] launchd-plist=${hbLabel} loaded=false exit=${hbBoot.bootstrapStatus} attempts=${hbBoot.attempts}`);
|
|
3547
|
+
}
|
|
3476
3548
|
// Wait for the server to come up.
|
|
3477
3549
|
console.log(" Waiting for web server...");
|
|
3478
3550
|
let webServerUp = false;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Task 1401 — darwin session-manager LaunchAgent bootstrap, made idempotent and
|
|
2
|
+
// EIO-tolerant.
|
|
3
|
+
//
|
|
4
|
+
// `launchctl bootstrap` intermittently returns exit 5 ("Bootstrap failed: 5:
|
|
5
|
+
// Input/output error") on a re-install even when the target label is not
|
|
6
|
+
// loaded (a `bootout` of it returns 3, "No such process"). It is a known
|
|
7
|
+
// transient/racey launchd failure: bootstrapping the same plist from a clean
|
|
8
|
+
// state succeeds. The earlier phase-11 code issued a single bare `bootstrap`
|
|
9
|
+
// and treated any non-zero exit as fatal, so one EIO aborted the whole install.
|
|
10
|
+
//
|
|
11
|
+
// This helper bootout-then-bootstraps with a bounded retry on exit 5. Both the
|
|
12
|
+
// launchctl runner and the retry sleep are injected, so the logic is unit-tested
|
|
13
|
+
// with no real launchctl and no wall-clock delay. Kept synchronous to match the
|
|
14
|
+
// spawnSync-based installer body (installService is a synchronous `void`).
|
|
15
|
+
/** `launchctl bootstrap` exit code for "Input/output error" — the transient,
|
|
16
|
+
* racey failure this retry loop exists to absorb. */
|
|
17
|
+
const BOOTSTRAP_EIO = 5;
|
|
18
|
+
/** Blocking sleep with no imports, safe inside the synchronous installer. */
|
|
19
|
+
function blockingSleep(ms) {
|
|
20
|
+
if (ms <= 0)
|
|
21
|
+
return;
|
|
22
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23
|
+
}
|
|
24
|
+
export function bootstrapLaunchAgent(run, o) {
|
|
25
|
+
const maxAttempts = Math.max(1, o.maxAttempts ?? 3);
|
|
26
|
+
const retryDelayMs = o.retryDelayMs ?? 500;
|
|
27
|
+
const sleepFn = o.sleepFn ?? blockingSleep;
|
|
28
|
+
const log = o.logger ?? (() => { });
|
|
29
|
+
let attempt = 0;
|
|
30
|
+
let last = { status: null, stderr: "" };
|
|
31
|
+
for (attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
32
|
+
// Idempotent: bootout any prior instance so the following bootstrap starts
|
|
33
|
+
// clean. rc 3 ("No such process") is the expected fresh case; it and any
|
|
34
|
+
// other bootout rc are ignored — bootout is cleanup, not the verified op.
|
|
35
|
+
const bootout = run(["bootout", `${o.gui}/${o.label}`]);
|
|
36
|
+
const bootstrap = run(["bootstrap", o.gui, o.plistPath]);
|
|
37
|
+
last = { status: bootstrap.status, stderr: (bootstrap.stderr ?? "").trim() };
|
|
38
|
+
log(`[launchd] agent=${o.label} bootout=${bootout.status ?? "null"} bootstrap=${bootstrap.status ?? "null"} attempt=${attempt}`);
|
|
39
|
+
if (bootstrap.status === 0) {
|
|
40
|
+
return { ok: true, attempts: attempt, bootstrapStatus: 0, stderr: last.stderr };
|
|
41
|
+
}
|
|
42
|
+
// Only exit 5 (EIO) is retried, and only while attempts remain. Every other
|
|
43
|
+
// non-zero status is a real failure that aborts now rather than burning the
|
|
44
|
+
// retry budget on a deterministic error.
|
|
45
|
+
if (bootstrap.status === BOOTSTRAP_EIO && attempt < maxAttempts) {
|
|
46
|
+
sleepFn(retryDelayMs);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
attempts: Math.min(attempt, maxAttempts),
|
|
54
|
+
bootstrapStatus: last.status,
|
|
55
|
+
stderr: last.stderr,
|
|
56
|
+
};
|
|
57
|
+
}
|
package/dist/launchd-plist.js
CHANGED
|
@@ -52,6 +52,29 @@ export function buildSessionManagerWrapper(o) {
|
|
|
52
52
|
"",
|
|
53
53
|
].join("\n");
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Bash wrapper for the darwin scheduling-heartbeat LaunchAgent (Task 1403).
|
|
57
|
+
* launchd's StartInterval timer is the analogue of the Linux per-minute cron
|
|
58
|
+
* (buildHeartbeatCronBlock) that ticks `check-due-events.js`. The wrapper
|
|
59
|
+
* sources the brand `.env` for NEO4J_URI + MAXY_UI_INTERNAL_PORT, exports
|
|
60
|
+
* PLATFORM_ROOT (the tick's own env contract, matching the cron line's
|
|
61
|
+
* `PLATFORM_ROOT=` prefix), and execs the tick once. launchd re-invokes it
|
|
62
|
+
* every StartInterval seconds; there is no long-running process to supervise.
|
|
63
|
+
*/
|
|
64
|
+
export function buildHeartbeatWrapper(o) {
|
|
65
|
+
const script = `${o.installDir}/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js`;
|
|
66
|
+
return [
|
|
67
|
+
"#!/bin/bash",
|
|
68
|
+
"# generated by create-maxy installServiceDarwin() — Task 1403 heartbeat.",
|
|
69
|
+
"# launchd StartInterval analogue of the Linux per-minute scheduling cron.",
|
|
70
|
+
"# Sources .env (NEO4J_URI, MAXY_UI_INTERNAL_PORT), exports PLATFORM_ROOT,",
|
|
71
|
+
"# execs one check-due-events.js tick.",
|
|
72
|
+
`set -a; [ -f "${o.envPath}" ] && . "${o.envPath}"; set +a`,
|
|
73
|
+
`export PLATFORM_ROOT="${o.installDir}/platform"`,
|
|
74
|
+
`exec ${o.nodeBin} ${script}`,
|
|
75
|
+
"",
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
55
78
|
export function renderPlist(spec) {
|
|
56
79
|
const lines = [];
|
|
57
80
|
lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
|
|
@@ -78,6 +101,10 @@ export function renderPlist(spec) {
|
|
|
78
101
|
lines.push(` ${spec.keepAlive ? "<true/>" : "<false/>"}`);
|
|
79
102
|
lines.push(` <key>RunAtLoad</key>`);
|
|
80
103
|
lines.push(` ${spec.runAtLoad ? "<true/>" : "<false/>"}`);
|
|
104
|
+
if (spec.startInterval !== undefined) {
|
|
105
|
+
lines.push(` <key>StartInterval</key>`);
|
|
106
|
+
lines.push(` <integer>${spec.startInterval}</integer>`);
|
|
107
|
+
}
|
|
81
108
|
lines.push(`</dict>`);
|
|
82
109
|
lines.push(`</plist>`);
|
|
83
110
|
return lines.join("\n") + "\n";
|
package/dist/uninstall.js
CHANGED
|
@@ -78,8 +78,21 @@ function isDarwin() {
|
|
|
78
78
|
function launchdLabel() {
|
|
79
79
|
return `com.rubytech.${BRAND.hostname}`;
|
|
80
80
|
}
|
|
81
|
+
/** Every LaunchAgent label installServiceDarwin() creates for this brand: the
|
|
82
|
+
* main server, the claude-session-manager (Task 1395), and the scheduling
|
|
83
|
+
* heartbeat (Task 1403). Uninstall boots out and removes all three. */
|
|
84
|
+
function darwinLaunchdLabels() {
|
|
85
|
+
return [
|
|
86
|
+
launchdLabel(),
|
|
87
|
+
`${launchdLabel()}-claude-session-manager`,
|
|
88
|
+
`${launchdLabel()}-heartbeat`,
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function plistPathFor(label) {
|
|
92
|
+
return resolve(HOME, "Library/LaunchAgents", `${label}.plist`);
|
|
93
|
+
}
|
|
81
94
|
function plistPath() {
|
|
82
|
-
return
|
|
95
|
+
return plistPathFor(launchdLabel());
|
|
83
96
|
}
|
|
84
97
|
function gui() {
|
|
85
98
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
@@ -163,13 +176,18 @@ function peerBrandPresent() {
|
|
|
163
176
|
// ---------------------------------------------------------------------------
|
|
164
177
|
function stopServices() {
|
|
165
178
|
log("1", "Stopping services...");
|
|
166
|
-
// darwin: bootout
|
|
167
|
-
// 113 ("Unknown service") when the agent isn't loaded — both are
|
|
179
|
+
// darwin: bootout every LaunchAgent this install created. bootout exits 0 on
|
|
180
|
+
// success, 113 ("Unknown service") when the agent isn't loaded — both are
|
|
168
181
|
// acceptable end-states for "service stopped". The plist removal in
|
|
169
|
-
// removeSystemdService() finishes the cleanup.
|
|
182
|
+
// removeSystemdService() finishes the cleanup. Three labels share the brand
|
|
183
|
+
// prefix: the main server, the claude-session-manager (Task 1395), and the
|
|
184
|
+
// scheduling heartbeat (Task 1403). The latter two were previously never
|
|
185
|
+
// booted out on uninstall.
|
|
170
186
|
if (isDarwin()) {
|
|
171
|
-
|
|
172
|
-
|
|
187
|
+
for (const label of darwinLaunchdLabels()) {
|
|
188
|
+
spawnSync("launchctl", ["bootout", `${gui()}/${label}`], { stdio: "pipe", timeout: 15_000 });
|
|
189
|
+
console.log(` Booted out ${label}`);
|
|
190
|
+
}
|
|
173
191
|
return;
|
|
174
192
|
}
|
|
175
193
|
// Stop platform user service
|
|
@@ -465,6 +483,14 @@ function removeAppDirs() {
|
|
|
465
483
|
// ---------------------------------------------------------------------------
|
|
466
484
|
function removeNeo4jData() {
|
|
467
485
|
log("5", "Removing Neo4j data...");
|
|
486
|
+
// This function's teardown is Linux-shaped: `sudo systemctl daemon-reload`
|
|
487
|
+
// and `sudo rm -rf /var/lib/neo4j/*`. On darwin that raises a spurious sudo
|
|
488
|
+
// prompt and logs a FAILED. Skip here (Task 1403); darwin Neo4j data teardown
|
|
489
|
+
// belongs to Task 1396 (dedicated Neo4j on darwin).
|
|
490
|
+
if (!isLinux()) {
|
|
491
|
+
console.log(" Not Linux — skipping.");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
468
494
|
// Brand isolation: remove only THIS brand's Neo4j data. Shared
|
|
469
495
|
// 7687 data is skipped entirely when a peer brand is present. Dedicated
|
|
470
496
|
// branded instances live at /var/lib/neo4j-<hostname>/ and are always
|
|
@@ -666,24 +692,26 @@ function removeSystemConfig() {
|
|
|
666
692
|
function removeSystemdService() {
|
|
667
693
|
log("8", "Removing systemd service...");
|
|
668
694
|
// darwin: bootout (idempotent — already done in stopServices,
|
|
669
|
-
// re-running is a no-op exit 113) and delete the plist
|
|
670
|
-
// shell
|
|
671
|
-
// so no separate cleanup is needed.
|
|
695
|
+
// re-running is a no-op exit 113) and delete the plist for every LaunchAgent
|
|
696
|
+
// this brand created. The wrapper shell scripts live in CONFIG_DIR which
|
|
697
|
+
// step 4 (removeAppDirs) wipes, so no separate cleanup is needed.
|
|
672
698
|
if (isDarwin()) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
699
|
+
for (const label of darwinLaunchdLabels()) {
|
|
700
|
+
spawnSync("launchctl", ["bootout", `${gui()}/${label}`], { stdio: "pipe", timeout: 15_000 });
|
|
701
|
+
const plist = plistPathFor(label);
|
|
702
|
+
if (existsSync(plist)) {
|
|
703
|
+
try {
|
|
704
|
+
rmSync(plist);
|
|
705
|
+
console.log(` Removed ${plist}`);
|
|
706
|
+
}
|
|
707
|
+
catch (err) {
|
|
708
|
+
console.log(` Failed to remove ${plist}: ${err instanceof Error ? err.message : String(err)}`);
|
|
709
|
+
}
|
|
679
710
|
}
|
|
680
|
-
|
|
681
|
-
console.log(`
|
|
711
|
+
else {
|
|
712
|
+
console.log(` ${plist} not found — skipping`);
|
|
682
713
|
}
|
|
683
714
|
}
|
|
684
|
-
else {
|
|
685
|
-
console.log(` ${plist} not found — skipping`);
|
|
686
|
-
}
|
|
687
715
|
return;
|
|
688
716
|
}
|
|
689
717
|
// Disable the service
|
|
@@ -737,6 +765,14 @@ function removeSystemdService() {
|
|
|
737
765
|
// ---------------------------------------------------------------------------
|
|
738
766
|
function removeOllama() {
|
|
739
767
|
log("9", "Removing Ollama...");
|
|
768
|
+
// The stop/disable below is `sudo systemctl` — Linux-only. On darwin it would
|
|
769
|
+
// raise a spurious sudo prompt and log a FAILED. Skip like removeSamba /
|
|
770
|
+
// removeSystemConfig (Task 1403); brew-ollama teardown on darwin is out of
|
|
771
|
+
// scope here.
|
|
772
|
+
if (!isLinux()) {
|
|
773
|
+
console.log(" Not Linux — skipping.");
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
740
776
|
// Brand isolation: the Ollama binary and systemd service are
|
|
741
777
|
// device-wide singletons — every brand on this device shares one Ollama
|
|
742
778
|
// process. Leave both in place when a peer brand is still installed;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:bbee5596607064b556bd814bce3ebe7578ae948a7117ee0dd6bff5e5fbe4b209
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -620,6 +620,8 @@ cat ~/Library/LaunchAgents/com.rubytech.maxy-code.plist
|
|
|
620
620
|
|
|
621
621
|
The plist points at the wrapper script the installer wrote and at log files under `$HOME/.maxy-code/logs/`. `launchctl bootstrap`'s exit code is recorded in the install log as `[create-maxy] launchd-plist=… loaded=true|false`.
|
|
622
622
|
|
|
623
|
+
Each brand runs two LaunchAgents: the main service (`com.rubytech.maxy-code`) and a session manager (`com.rubytech.maxy-code-claude-session-manager`). On a re-install `launchctl bootstrap` of the session-manager agent can return a transient `exit 5: Input/output error` even when the agent was not already loaded; the installer boots the agent out and retries automatically. Each attempt is logged as `[launchd] agent=<label> bootout=<rc> bootstrap=<rc> attempt=<n>`, and only an EIO that persists across every attempt aborts the install.
|
|
624
|
+
|
|
623
625
|
## 4. Open the admin UI
|
|
624
626
|
|
|
625
627
|
The install log's final block prints the URL. For the default brand on a default install:
|
|
@@ -8,6 +8,11 @@ export interface ChannelMcpServerEntry {
|
|
|
8
8
|
export declare function buildChannelMcpServers(p: {
|
|
9
9
|
sessionId: string;
|
|
10
10
|
targets: ChannelTargetSet;
|
|
11
|
+
/** Absolute path to the node binary that runs the channel server. On darwin
|
|
12
|
+
* the launchd server PATH excludes an nvm node, so a bare `node` command
|
|
13
|
+
* fails to spawn and the channel never boots (Task 1402); callers pass the
|
|
14
|
+
* installer-resolved NODE_BIN. */
|
|
15
|
+
nodeBin: string;
|
|
11
16
|
}): Record<string, ChannelMcpServerEntry>;
|
|
12
17
|
export declare function channelDevChannelsArgv(): string[];
|
|
13
18
|
export declare function channelMcpConfigPath(sessionId: string, dir: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-mcp.d.ts","sourceRoot":"","sources":["../src/channel-mcp.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,eAAO,MAAM,mBAAmB,YAAY,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC5B;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"channel-mcp.d.ts","sourceRoot":"","sources":["../src/channel-mcp.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,eAAO,MAAM,mBAAmB,YAAY,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC5B;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,gBAAgB,CAAA;IACzB;;;uCAGmC;IACnC,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAexC;AAED,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhE"}
|
|
@@ -13,7 +13,7 @@ export function buildChannelMcpServers(p) {
|
|
|
13
13
|
throw new Error('buildChannelMcpServers: empty target set');
|
|
14
14
|
return {
|
|
15
15
|
[CHANNEL_SERVER_NAME]: {
|
|
16
|
-
command:
|
|
16
|
+
command: p.nodeBin,
|
|
17
17
|
args: [anyTarget.serverPath],
|
|
18
18
|
env: {
|
|
19
19
|
CHANNEL_SESSION_ID: p.sessionId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-mcp.js","sourceRoot":"","sources":["../src/channel-mcp.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,iFAAiF;AAEjF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,MAAM,CAAC,MAAM,mBAAmB,GAAG,SAAS,CAAA;AAQ5C,MAAM,UAAU,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"channel-mcp.js","sourceRoot":"","sources":["../src/channel-mcp.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,iFAAiF;AAEjF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,MAAM,CAAC,MAAM,mBAAmB,GAAG,SAAS,CAAA;AAQ5C,MAAM,UAAU,sBAAsB,CAAC,CAQtC;IACC,mEAAmE;IACnE,wDAAwD;IACxD,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAA;IACzD,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;IAC3E,OAAO;QACL,CAAC,mBAAmB,CAAC,EAAE;YACrB,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,IAAI,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC;YAC5B,GAAG,EAAE;gBACH,kBAAkB,EAAE,CAAC,CAAC,SAAS;gBAC/B,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;aAC3C;SACF;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,CAAC,yCAAyC,EAAE,UAAU,mBAAmB,EAAE,CAAC,CAAA;AACrF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE,GAAW;IACjE,OAAO,IAAI,CAAC,GAAG,EAAE,gBAAgB,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;AACpF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,aAAqB;IACpD,OAAO,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,sBAAsB,EAAE,CAAC,CAAA;AACrE,CAAC"}
|
|
@@ -10,6 +10,12 @@ export interface ManagerConfig {
|
|
|
10
10
|
port: number;
|
|
11
11
|
persistDir: string;
|
|
12
12
|
claudeBin: string;
|
|
13
|
+
/** Absolute node binary for spawned channel-MCP children. On darwin the
|
|
14
|
+
* launchd server PATH excludes an nvm node, so channel descriptors must name
|
|
15
|
+
* node explicitly rather than relying on a bare `node` on PATH (Task 1402).
|
|
16
|
+
* Falls back to bare `node` when NODE_BIN is unset (Linux, where node is on
|
|
17
|
+
* the base PATH). */
|
|
18
|
+
nodeBin: string;
|
|
13
19
|
spawnCwd: string;
|
|
14
20
|
urlCaptureTimeoutMs: number;
|
|
15
21
|
killGraceMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAA;AAGhC,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD;;4DAEwD;IACxD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB;;4EAEwE;IACxE,aAAa,EAAE,MAAM,CAAA;IACrB;;;gEAG4D;IAC5D,eAAe,EAAE,MAAM,CAAA;IACvB;;2EAEuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB;;0EAEsE;IACtE,iBAAiB,EAAE,MAAM,CAAA;IACzB,IAAI,EAAE,cAAc,CAAA;IACpB;;;;;;;8BAO0B;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB;;;qDAGiD;IACjD,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;gFAE4E;IAC5E,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAwLD;;;;;;;6EAO6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,CAyBvE;AAED;;eAEe;AACf,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAE9D;AAED;;;;;;;kCAOkC;AAClC,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI/F;AAUD,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAA;AAGhC,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD;;4DAEwD;IACxD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB;;;;0BAIsB;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB;;4EAEwE;IACxE,aAAa,EAAE,MAAM,CAAA;IACrB;;;gEAG4D;IAC5D,eAAe,EAAE,MAAM,CAAA;IACvB;;2EAEuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB;;0EAEsE;IACtE,iBAAiB,EAAE,MAAM,CAAA;IACzB,IAAI,EAAE,cAAc,CAAA;IACpB;;;;;;;8BAO0B;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB;;;qDAGiD;IACjD,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;gFAE4E;IAC5E,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAwLD;;;;;;;6EAO6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,CAyBvE;AAED;;eAEe;AACf,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAE9D;AAED;;;;;;;kCAOkC;AAClC,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI/F;AAUD,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,aAAa,CAyE9E"}
|
|
@@ -241,6 +241,7 @@ export function loadConfig(env = process.env) {
|
|
|
241
241
|
}
|
|
242
242
|
const persistDir = env.CLAUDE_SESSION_MANAGER_PERSIST_DIR ?? join(homedir(), '.maxy');
|
|
243
243
|
const claudeBin = env.CLAUDE_BIN ?? 'claude';
|
|
244
|
+
const nodeBin = env.NODE_BIN ?? 'node';
|
|
244
245
|
const brand = loadBrand(env);
|
|
245
246
|
const platformRoot = env.PLATFORM_ROOT ?? env.MAXY_PLATFORM_ROOT;
|
|
246
247
|
if (!platformRoot) {
|
|
@@ -281,6 +282,7 @@ export function loadConfig(env = process.env) {
|
|
|
281
282
|
port,
|
|
282
283
|
persistDir,
|
|
283
284
|
claudeBin,
|
|
285
|
+
nodeBin,
|
|
284
286
|
spawnCwd,
|
|
285
287
|
urlCaptureTimeoutMs: DEFAULT_URL_TIMEOUT_MS,
|
|
286
288
|
killGraceMs: DEFAULT_KILL_GRACE_MS,
|