@parall/daemon 1.27.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/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/runtimes.d.ts +12 -0
- package/dist/runtimes.d.ts.map +1 -0
- package/dist/runtimes.js +55 -0
- package/dist/supervisor.d.ts +50 -0
- package/dist/supervisor.d.ts.map +1 -0
- package/dist/supervisor.js +415 -0
- package/package.json +38 -0
- package/src/config.ts +142 -0
- package/src/index.ts +130 -0
- package/src/runtimes.ts +71 -0
- package/src/supervisor.ts +480 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon-mode env. The daemon authenticates to the Parall API with an
|
|
3
|
+
* `mck_*` bearer token (PRLL_API_KEY) and supervises N per-agent
|
|
4
|
+
* `parall-claude-agent` subprocesses on this host.
|
|
5
|
+
*
|
|
6
|
+
* Compared to claude-agent's config:
|
|
7
|
+
* - `apiKey` is an mck_ bearer (Machine-scoped), not an agk_
|
|
8
|
+
* - there is no PRLL_ORG_ID — the Machine row's org_id is implicit;
|
|
9
|
+
* each spawned per-agent subprocess is given its own PRLL_ORG_ID
|
|
10
|
+
* resolved from the AttachedAgent's profile / org context
|
|
11
|
+
* - per-agent state lives at `<rootStateDir>/agents/<agent_id>/`
|
|
12
|
+
*/
|
|
13
|
+
export type ClaudeDaemonConfig = {
|
|
14
|
+
apiUrl: string;
|
|
15
|
+
/** mck_*-prefixed Machine bearer. */
|
|
16
|
+
apiKey: string;
|
|
17
|
+
/** @deprecated Superseded by RuntimeAdapter pattern (runtimes.ts). Kept for backward compat / fallback. */
|
|
18
|
+
agentBin: string;
|
|
19
|
+
/** Root for per-agent state dirs. Each agent gets `<rootStateDir>/agents/<agent_id>`. */
|
|
20
|
+
rootStateDir: string;
|
|
21
|
+
/** Shared host home root. Per-agent HOME dirs live under `<rootClaudeHome>/agents/<agent_id>`. */
|
|
22
|
+
rootClaudeHome: string;
|
|
23
|
+
/** Optional WS URL override for the machine control-plane WebSocket. */
|
|
24
|
+
wsUrl?: string;
|
|
25
|
+
/** @deprecated Superseded by WS event-driven model; kept for backward compat. */
|
|
26
|
+
pollIntervalMs: number;
|
|
27
|
+
/** @deprecated Superseded by WS event-driven model; kept for backward compat. */
|
|
28
|
+
heartbeatIntervalMs: number;
|
|
29
|
+
/** Backoff base after a subprocess crash, in ms. */
|
|
30
|
+
restartBackoffMs: number;
|
|
31
|
+
/** Hard cap on parallel restart attempts per agent before giving up for a cycle. */
|
|
32
|
+
restartBackoffMaxMs: number;
|
|
33
|
+
/**
|
|
34
|
+
* Bootstrap retry: when `getMachineSelf` fails on startup (network blip,
|
|
35
|
+
* mck_ key not yet propagated, server warming up), wait this long and
|
|
36
|
+
* retry instead of crashing the daemon. Exponential backoff up to
|
|
37
|
+
* `bootstrapBackoffMaxMs`. Set 0 to disable (fail-fast on first error).
|
|
38
|
+
*/
|
|
39
|
+
bootstrapBackoffMs: number;
|
|
40
|
+
bootstrapBackoffMaxMs: number;
|
|
41
|
+
/**
|
|
42
|
+
* Outer supervisor keepalive: if `supervisor.run()` rejects with an
|
|
43
|
+
* unexpected error, wait this long and reinstantiate. Same exp-backoff
|
|
44
|
+
* shape as bootstrap. Set 0 to disable (let main() exit, rely on K8s/PID-1).
|
|
45
|
+
*/
|
|
46
|
+
supervisorRestartBackoffMs: number;
|
|
47
|
+
supervisorRestartBackoffMaxMs: number;
|
|
48
|
+
};
|
|
49
|
+
export declare function resolveClaudeDaemonConfig(env?: NodeJS.ProcessEnv): ClaudeDaemonConfig;
|
|
50
|
+
/** Per-agent state dir under the shared host volume. */
|
|
51
|
+
export declare function agentStateDirFor(rootStateDir: string, agentId: string): string;
|
|
52
|
+
/** Per-agent HOME dir. Claude Code stores project/session state under
|
|
53
|
+
* `${HOME}/.claude`, so each agent gets its own HOME root while the daemon
|
|
54
|
+
* links shared OAuth credentials into that `.claude` directory. */
|
|
55
|
+
export declare function agentClaudeHomeFor(rootClaudeHome: string, agentId: string): string;
|
|
56
|
+
/** Shared Claude Code OAuth credential written by server-side runtime auth. */
|
|
57
|
+
export declare function sharedClaudeCredentialsFileFor(rootClaudeHome: string): string;
|
|
58
|
+
/** Per-agent credential location inside that agent's isolated HOME. */
|
|
59
|
+
export declare function agentClaudeCredentialsFileFor(agentClaudeHome: string): string;
|
|
60
|
+
/** Per-agent workspace dir where the agent runs git commands. */
|
|
61
|
+
export declare function agentWorkspaceDirFor(rootStateDir: string, agentId: string): string;
|
|
62
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAAC;AA2BF,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA8BlG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
function requireEnv(env, name) {
|
|
4
|
+
const value = env[name]?.trim();
|
|
5
|
+
if (!value) {
|
|
6
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
function resolvePath(value) {
|
|
11
|
+
return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
|
|
12
|
+
}
|
|
13
|
+
function parseMs(value, fallback) {
|
|
14
|
+
if (!value)
|
|
15
|
+
return fallback;
|
|
16
|
+
const n = Number(value);
|
|
17
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
18
|
+
}
|
|
19
|
+
/** Like parseMs but allows 0 (operator opt-out). */
|
|
20
|
+
function parseMsAllowZero(value, fallback) {
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return fallback;
|
|
23
|
+
const n = Number(value);
|
|
24
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
25
|
+
}
|
|
26
|
+
export function resolveClaudeDaemonConfig(env = process.env) {
|
|
27
|
+
const apiUrl = requireEnv(env, "PRLL_API_URL");
|
|
28
|
+
const apiKey = requireEnv(env, "PRLL_API_KEY");
|
|
29
|
+
if (!apiKey.startsWith("mck_")) {
|
|
30
|
+
// Fatal startup validation: the daemon must never run with an agent or
|
|
31
|
+
// human key because child launch credentials are minted from this bearer.
|
|
32
|
+
throw new Error(`PRLL_API_KEY does not look like a Machine bearer (expected prefix "mck_"). ` +
|
|
33
|
+
`Daemon mode requires a machine-scoped key issued via POST /machines/{id}/keys.`);
|
|
34
|
+
}
|
|
35
|
+
const rootClaudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
|
|
36
|
+
const rootStateDir = resolvePath(env.PRLL_CLAUDE_STATE_DIR?.trim() || path.join(rootClaudeHome, ".parall-agent"));
|
|
37
|
+
return {
|
|
38
|
+
apiUrl,
|
|
39
|
+
apiKey,
|
|
40
|
+
agentBin: env.PRLL_CLAUDE_AGENT_BIN?.trim() || "parall-claude-agent",
|
|
41
|
+
rootStateDir,
|
|
42
|
+
rootClaudeHome,
|
|
43
|
+
wsUrl: env.PRLL_WS_URL?.trim() || undefined,
|
|
44
|
+
pollIntervalMs: parseMs(env.PRLL_DAEMON_POLL_INTERVAL_MS, 30_000),
|
|
45
|
+
heartbeatIntervalMs: parseMs(env.PRLL_DAEMON_HEARTBEAT_INTERVAL_MS, 30_000),
|
|
46
|
+
restartBackoffMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MS, 5_000),
|
|
47
|
+
restartBackoffMaxMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MAX_MS, 5 * 60_000),
|
|
48
|
+
bootstrapBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MS, 2_000),
|
|
49
|
+
bootstrapBackoffMaxMs: parseMs(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MAX_MS, 60_000),
|
|
50
|
+
supervisorRestartBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS, 5_000),
|
|
51
|
+
supervisorRestartBackoffMaxMs: parseMs(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MAX_MS, 5 * 60_000),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function assertSafeAgentId(agentId) {
|
|
55
|
+
if (!/^[A-Za-z0-9_-]+$/.test(agentId)) {
|
|
56
|
+
throw new Error(`Invalid agentId for filesystem path: ${agentId}`);
|
|
57
|
+
}
|
|
58
|
+
return agentId;
|
|
59
|
+
}
|
|
60
|
+
/** Per-agent state dir under the shared host volume. */
|
|
61
|
+
export function agentStateDirFor(rootStateDir, agentId) {
|
|
62
|
+
return path.join(rootStateDir, "agents", assertSafeAgentId(agentId));
|
|
63
|
+
}
|
|
64
|
+
/** Per-agent HOME dir. Claude Code stores project/session state under
|
|
65
|
+
* `${HOME}/.claude`, so each agent gets its own HOME root while the daemon
|
|
66
|
+
* links shared OAuth credentials into that `.claude` directory. */
|
|
67
|
+
export function agentClaudeHomeFor(rootClaudeHome, agentId) {
|
|
68
|
+
return path.join(rootClaudeHome, "agents", assertSafeAgentId(agentId));
|
|
69
|
+
}
|
|
70
|
+
/** Shared Claude Code OAuth credential written by server-side runtime auth. */
|
|
71
|
+
export function sharedClaudeCredentialsFileFor(rootClaudeHome) {
|
|
72
|
+
return path.join(rootClaudeHome, ".claude", ".credentials.json");
|
|
73
|
+
}
|
|
74
|
+
/** Per-agent credential location inside that agent's isolated HOME. */
|
|
75
|
+
export function agentClaudeCredentialsFileFor(agentClaudeHome) {
|
|
76
|
+
return path.join(agentClaudeHome, ".claude", ".credentials.json");
|
|
77
|
+
}
|
|
78
|
+
/** Per-agent workspace dir where the agent runs git commands. */
|
|
79
|
+
export function agentWorkspaceDirFor(rootStateDir, agentId) {
|
|
80
|
+
return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
|
|
81
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ParallClient } from "@parall/sdk";
|
|
3
|
+
import { resolveClaudeDaemonConfig } from "./config.js";
|
|
4
|
+
import { DaemonSupervisor, sleepCancellable } from "./supervisor.js";
|
|
5
|
+
function createLogger(prefix) {
|
|
6
|
+
return {
|
|
7
|
+
info: (msg) => console.log(`[${prefix}] ${msg}`),
|
|
8
|
+
warn: (msg) => console.warn(`[${prefix}] ${msg}`),
|
|
9
|
+
error: (msg) => console.error(`[${prefix}] ${msg}`),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function formatError(reason) {
|
|
13
|
+
if (reason instanceof Error) {
|
|
14
|
+
return reason.stack ?? reason.message;
|
|
15
|
+
}
|
|
16
|
+
return String(reason);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Outer keepalive: runs `supervisor.run()` in a loop, restarting it with
|
|
20
|
+
* exponential backoff if it rejects. The daemon process only exits when
|
|
21
|
+
* the abort signal fires (SIGINT/SIGTERM) — anything else is treated as
|
|
22
|
+
* a transient failure that we recover from in-process.
|
|
23
|
+
*
|
|
24
|
+
* Why this exists in addition to entrypoint.sh's `while :; do parall-daemon; done`:
|
|
25
|
+
* - In-process restart preserves the `client` (= same TCP/TLS pool) and
|
|
26
|
+
* skips the ~hundreds-of-ms cost of Node startup + module loading per
|
|
27
|
+
* cycle, which matters when the API is flapping.
|
|
28
|
+
* - The shell wrapper is the "process really crashed" backstop —
|
|
29
|
+
* segfault, OOM, uncaughtException, etc. The two layers are
|
|
30
|
+
* complementary: in-process for soft failures, shell for hard failures.
|
|
31
|
+
*
|
|
32
|
+
* Operators can disable the in-process loop by setting
|
|
33
|
+
* PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS=0 — main() will then exit on
|
|
34
|
+
* any supervisor.run() rejection and rely entirely on the shell wrapper +
|
|
35
|
+
* K8s for restart.
|
|
36
|
+
*/
|
|
37
|
+
async function runForever(config, client, log, signal) {
|
|
38
|
+
let attempt = 0;
|
|
39
|
+
while (!signal.aborted) {
|
|
40
|
+
const supervisor = new DaemonSupervisor(config, client, log);
|
|
41
|
+
try {
|
|
42
|
+
await supervisor.run(signal);
|
|
43
|
+
// Clean exit (signal aborted) — done.
|
|
44
|
+
await supervisor.stop();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
// supervisor.run() rejected. The supervisor's per-tick handlers
|
|
49
|
+
// already swallow individual failures, so the only paths that reach
|
|
50
|
+
// here are (a) bootstrap fail-fast mode and (b) genuine programmer
|
|
51
|
+
// bugs. Restart anyway — operators rely on this daemon as the only
|
|
52
|
+
// thing keeping per-agent children alive on this host.
|
|
53
|
+
log.error(`supervisor crashed: ${String(err)}`);
|
|
54
|
+
try {
|
|
55
|
+
await supervisor.stop();
|
|
56
|
+
}
|
|
57
|
+
catch (stopErr) {
|
|
58
|
+
log.warn(`supervisor.stop() after crash threw: ${String(stopErr)}`);
|
|
59
|
+
}
|
|
60
|
+
if (config.supervisorRestartBackoffMs === 0) {
|
|
61
|
+
log.error("supervisor keepalive disabled (PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS=0) — exiting");
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
const delay = Math.min(config.supervisorRestartBackoffMs * Math.pow(2, attempt), config.supervisorRestartBackoffMaxMs);
|
|
65
|
+
attempt += 1;
|
|
66
|
+
log.warn(`restarting supervisor in ${delay}ms (attempt ${attempt})`);
|
|
67
|
+
const slept = await sleepCancellable(delay, signal);
|
|
68
|
+
if (!slept)
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function main() {
|
|
74
|
+
const config = resolveClaudeDaemonConfig(process.env);
|
|
75
|
+
const log = createLogger("daemon");
|
|
76
|
+
log.info(`boot: api=${config.apiUrl} pollMs=${config.pollIntervalMs} heartbeatMs=${config.heartbeatIntervalMs} agentBin=${config.agentBin}`);
|
|
77
|
+
log.info(`keepalive: bootstrapBackoffMs=${config.bootstrapBackoffMs} supervisorRestartBackoffMs=${config.supervisorRestartBackoffMs}`);
|
|
78
|
+
// The daemon talks to the API as a Machine — the bearer is mck_*.
|
|
79
|
+
// No orgId is configured here; per-agent subprocesses get their
|
|
80
|
+
// own org_id via the spawn env.
|
|
81
|
+
const client = new ParallClient({
|
|
82
|
+
baseUrl: config.apiUrl,
|
|
83
|
+
token: config.apiKey,
|
|
84
|
+
});
|
|
85
|
+
const abortController = new AbortController();
|
|
86
|
+
const onSignal = (sig) => {
|
|
87
|
+
log.info(`received ${sig} — initiating shutdown`);
|
|
88
|
+
abortController.abort();
|
|
89
|
+
};
|
|
90
|
+
process.on("SIGINT", () => onSignal("SIGINT"));
|
|
91
|
+
process.on("SIGTERM", () => onSignal("SIGTERM"));
|
|
92
|
+
// Defensive: unexpected async failures are logged explicitly. Rejections
|
|
93
|
+
// stay in-process so the supervisor loop can recover; uncaught exceptions
|
|
94
|
+
// exit so entrypoint.sh's shell-level keepalive restarts from a clean VM.
|
|
95
|
+
process.on("unhandledRejection", (reason) => {
|
|
96
|
+
log.error(`unhandledRejection: ${formatError(reason)}`);
|
|
97
|
+
});
|
|
98
|
+
process.on("uncaughtException", (err) => {
|
|
99
|
+
log.error(`uncaughtException: ${formatError(err)}`);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
103
|
+
await runForever(config, client, log, abortController.signal);
|
|
104
|
+
}
|
|
105
|
+
main().catch((err) => {
|
|
106
|
+
console.error(`[daemon] fatal: ${formatError(err)}`);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RuntimeAdapter {
|
|
2
|
+
bin: string;
|
|
3
|
+
buildEnv(baseEnv: NodeJS.ProcessEnv, agentId: string, orgId: string, apiKey: string, dirs: AgentDirs): NodeJS.ProcessEnv;
|
|
4
|
+
}
|
|
5
|
+
export interface AgentDirs {
|
|
6
|
+
stateDir: string;
|
|
7
|
+
workspaceDir: string;
|
|
8
|
+
claudeHome: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getRuntimeAdapter(runtimeType: string): RuntimeAdapter;
|
|
11
|
+
export declare function assertAgentKey(apiKey: string): void;
|
|
12
|
+
//# sourceMappingURL=runtimes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC;CAC1H;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAqDD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAErE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAInD"}
|
package/dist/runtimes.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const claudeCodeAdapter = {
|
|
2
|
+
bin: "parall-claude-agent",
|
|
3
|
+
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
4
|
+
const env = { ...baseEnv };
|
|
5
|
+
env.PRLL_API_KEY = apiKey;
|
|
6
|
+
env.PRLL_ORG_ID = orgId;
|
|
7
|
+
env.AGENT_ID = agentId;
|
|
8
|
+
env.PRLL_AGENT_ID = agentId;
|
|
9
|
+
env.PRLL_CLAUDE_HOME = dirs.claudeHome;
|
|
10
|
+
env.PRLL_CLAUDE_STATE_DIR = dirs.stateDir;
|
|
11
|
+
env.PRLL_CLAUDE_WORKSPACE_DIR = dirs.workspaceDir;
|
|
12
|
+
delete env.PRLL_DAEMON_MODE;
|
|
13
|
+
return env;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
const codexAdapter = {
|
|
17
|
+
bin: "parall-codex-agent",
|
|
18
|
+
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
19
|
+
const env = { ...baseEnv };
|
|
20
|
+
env.PRLL_API_KEY = apiKey;
|
|
21
|
+
env.PRLL_ORG_ID = orgId;
|
|
22
|
+
env.AGENT_ID = agentId;
|
|
23
|
+
env.PRLL_AGENT_ID = agentId;
|
|
24
|
+
env.PRLL_CODEX_STATE_DIR = dirs.stateDir;
|
|
25
|
+
env.PRLL_CODEX_WORKSPACE_DIR = dirs.workspaceDir;
|
|
26
|
+
delete env.PRLL_DAEMON_MODE;
|
|
27
|
+
return env;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const defaultAdapter = {
|
|
31
|
+
bin: "parall-agent",
|
|
32
|
+
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
33
|
+
const env = { ...baseEnv };
|
|
34
|
+
env.PRLL_API_KEY = apiKey;
|
|
35
|
+
env.PRLL_ORG_ID = orgId;
|
|
36
|
+
env.AGENT_ID = agentId;
|
|
37
|
+
env.PRLL_AGENT_ID = agentId;
|
|
38
|
+
env.PRLL_STATE_DIR = dirs.stateDir;
|
|
39
|
+
env.PRLL_WORKSPACE_DIR = dirs.workspaceDir;
|
|
40
|
+
delete env.PRLL_DAEMON_MODE;
|
|
41
|
+
return env;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const RUNTIME_ADAPTERS = {
|
|
45
|
+
"claude-code": claudeCodeAdapter,
|
|
46
|
+
"codex": codexAdapter,
|
|
47
|
+
};
|
|
48
|
+
export function getRuntimeAdapter(runtimeType) {
|
|
49
|
+
return RUNTIME_ADAPTERS[runtimeType] ?? defaultAdapter;
|
|
50
|
+
}
|
|
51
|
+
export function assertAgentKey(apiKey) {
|
|
52
|
+
if (apiKey.startsWith("mck_")) {
|
|
53
|
+
throw new Error("BUG: machine key leaked to child process — expected agk_, got mck_");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ParallClient } from "@parall/sdk";
|
|
2
|
+
import { type ClaudeDaemonConfig } from "./config.js";
|
|
3
|
+
export interface DaemonLogger {
|
|
4
|
+
info(msg: string): void;
|
|
5
|
+
warn(msg: string): void;
|
|
6
|
+
error(msg: string): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Sleep that wakes early on abort. Returns true if the full delay elapsed,
|
|
10
|
+
* false if aborted. Used by bootstrap retry and the outer keepalive in
|
|
11
|
+
* `runForever` so SIGTERM during a long backoff doesn't stall shutdown.
|
|
12
|
+
*/
|
|
13
|
+
declare function sleepCancellable(ms: number, signal: AbortSignal): Promise<boolean>;
|
|
14
|
+
export { sleepCancellable };
|
|
15
|
+
/**
|
|
16
|
+
* Supervisor orchestrates one mck_ machine bearer into N
|
|
17
|
+
* `parall-claude-agent` subprocesses, one per AttachedAgent.
|
|
18
|
+
*
|
|
19
|
+
* Lifecycle is WS event-driven:
|
|
20
|
+
* 1. Bootstrap: confirm machine identity with retry.
|
|
21
|
+
* 2. Full reconcile: list attached agents, diff with children, spawn/kill.
|
|
22
|
+
* 3. Connect WS: receive machine.agent.attached / .detached / .stop
|
|
23
|
+
* events for incremental updates; full reconcile on every reconnect
|
|
24
|
+
* (machine.hello) to catch events missed while disconnected.
|
|
25
|
+
*/
|
|
26
|
+
export declare class DaemonSupervisor {
|
|
27
|
+
private readonly config;
|
|
28
|
+
private readonly client;
|
|
29
|
+
private readonly log;
|
|
30
|
+
private readonly children;
|
|
31
|
+
private ws;
|
|
32
|
+
private running;
|
|
33
|
+
private machineOrgId;
|
|
34
|
+
private stopResolve;
|
|
35
|
+
constructor(config: ClaudeDaemonConfig, client: ParallClient, log: DaemonLogger);
|
|
36
|
+
/** Start the supervisor. Returns a promise that resolves on `stop()`. */
|
|
37
|
+
run(signal: AbortSignal): Promise<void>;
|
|
38
|
+
/** Disconnect WS, cancel timers, SIGTERM all children, await exit. */
|
|
39
|
+
stop(): Promise<void>;
|
|
40
|
+
private bootstrapWithRetry;
|
|
41
|
+
private fullReconcile;
|
|
42
|
+
private handleAgentAttached;
|
|
43
|
+
private handleAgentDetached;
|
|
44
|
+
private restartChildNow;
|
|
45
|
+
private spawnAgent;
|
|
46
|
+
private startChild;
|
|
47
|
+
private terminateChild;
|
|
48
|
+
private ensureSharedCredentialLink;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=supervisor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAmJ,MAAM,aAAa,CAAC;AAC5L,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAmB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAQzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,YAAY;IAGpC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA+D7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Bb,kBAAkB;YAoClB,aAAa;YAqDb,mBAAmB;YA6BnB,mBAAmB;YAgBnB,eAAe;YAcf,UAAU;IAmCxB,OAAO,CAAC,UAAU;YAgEJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}
|