@parall/daemon 1.28.0 → 1.29.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/runtimes.js CHANGED
@@ -1,7 +1,28 @@
1
+ import * as path from "node:path";
2
+ function llmSource(pc) {
3
+ if (pc?.llm_source)
4
+ return pc.llm_source;
5
+ if (pc?.openai_api_key ||
6
+ pc?.openai_base_url ||
7
+ pc?.anthropic_auth_token ||
8
+ pc?.anthropic_base_url) {
9
+ return "custom";
10
+ }
11
+ return "parall";
12
+ }
13
+ function clearAllProviderCreds(env) {
14
+ delete env.ANTHROPIC_AUTH_TOKEN;
15
+ delete env.ANTHROPIC_BASE_URL;
16
+ delete env.ANTHROPIC_API_KEY;
17
+ delete env.OPENAI_API_KEY;
18
+ delete env.OPENAI_BASE_URL;
19
+ delete env.PRLL_CLAUDE_ALLOW_API_KEY;
20
+ }
1
21
  const claudeCodeAdapter = {
2
22
  bin: "parall-claude-agent",
3
- buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
23
+ buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
4
24
  const env = { ...baseEnv };
25
+ clearAllProviderCreds(env);
5
26
  env.PRLL_API_KEY = apiKey;
6
27
  env.PRLL_ORG_ID = orgId;
7
28
  env.AGENT_ID = agentId;
@@ -9,8 +30,19 @@ const claudeCodeAdapter = {
9
30
  env.PRLL_CLAUDE_HOME = dirs.claudeHome;
10
31
  env.PRLL_CLAUDE_STATE_DIR = dirs.stateDir;
11
32
  env.PRLL_CLAUDE_WORKSPACE_DIR = dirs.workspaceDir;
12
- if ("ANTHROPIC_AUTH_TOKEN" in env) {
33
+ const source = llmSource(pc);
34
+ if (source === "parall") {
13
35
  env.ANTHROPIC_AUTH_TOKEN = apiKey;
36
+ env.ANTHROPIC_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm`;
37
+ env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
38
+ }
39
+ else if (source === "custom") {
40
+ if (pc?.anthropic_auth_token) {
41
+ env.ANTHROPIC_AUTH_TOKEN = pc.anthropic_auth_token;
42
+ env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
43
+ }
44
+ if (pc?.anthropic_base_url)
45
+ env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
14
46
  }
15
47
  delete env.PRLL_DAEMON_MODE;
16
48
  return env;
@@ -18,14 +50,27 @@ const claudeCodeAdapter = {
18
50
  };
19
51
  const codexAdapter = {
20
52
  bin: "parall-codex-agent",
21
- buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
53
+ buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
22
54
  const env = { ...baseEnv };
55
+ clearAllProviderCreds(env);
23
56
  env.PRLL_API_KEY = apiKey;
24
57
  env.PRLL_ORG_ID = orgId;
25
58
  env.AGENT_ID = agentId;
26
59
  env.PRLL_AGENT_ID = agentId;
27
60
  env.PRLL_CODEX_STATE_DIR = dirs.stateDir;
28
61
  env.PRLL_CODEX_WORKSPACE_DIR = dirs.workspaceDir;
62
+ env.PRLL_CODEX_HOME = path.join(dirs.stateDir, ".codex");
63
+ const source = llmSource(pc);
64
+ if (source === "parall") {
65
+ env.OPENAI_API_KEY = apiKey;
66
+ env.OPENAI_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm/v1`;
67
+ }
68
+ else if (source === "custom") {
69
+ if (pc?.openai_api_key)
70
+ env.OPENAI_API_KEY = pc.openai_api_key;
71
+ if (pc?.openai_base_url)
72
+ env.OPENAI_BASE_URL = pc.openai_base_url;
73
+ }
29
74
  delete env.PRLL_DAEMON_MODE;
30
75
  return env;
31
76
  },
@@ -48,6 +93,7 @@ const openclawAdapter = {
48
93
  bin: "parall-openclaw-agent",
49
94
  buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
50
95
  const env = { ...baseEnv };
96
+ clearAllProviderCreds(env);
51
97
  env.PRLL_API_KEY = apiKey;
52
98
  env.PRLL_ORG_ID = orgId;
53
99
  env.AGENT_ID = agentId;
@@ -1,10 +1,6 @@
1
+ import type { GatewayLogger } from "@parall/agent-core";
1
2
  import { ParallClient } from "@parall/sdk";
2
3
  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
4
  /**
9
5
  * Sleep that wakes early on abort. Returns true if the full delay elapsed,
10
6
  * false if aborted. Used by bootstrap retry and the outer keepalive in
@@ -31,16 +27,25 @@ export declare class DaemonSupervisor {
31
27
  private ws;
32
28
  private running;
33
29
  private machineOrgId;
30
+ private machineLlmSource;
34
31
  private stopResolve;
35
- constructor(config: ClaudeDaemonConfig, client: ParallClient, log: DaemonLogger);
32
+ constructor(config: ClaudeDaemonConfig, client: ParallClient, log: GatewayLogger);
36
33
  /** Start the supervisor. Returns a promise that resolves on `stop()`. */
37
34
  run(signal: AbortSignal): Promise<void>;
38
35
  /** Disconnect WS, cancel timers, SIGTERM all children, await exit. */
39
36
  stop(): Promise<void>;
40
37
  private bootstrapWithRetry;
41
38
  private fullReconcile;
39
+ /**
40
+ * Detects a legacy flat state layout (no agents/ subdir) and migrates it
41
+ * into the per-agent directory for the owning agent. Ownership is determined
42
+ * by parsing session state files which embed the agent ID in the runtimeKey.
43
+ */
44
+ private migrateFlatLayout;
42
45
  private handleAgentAttached;
43
46
  private handleAgentDetached;
47
+ private refreshMachineConfig;
48
+ private respawnAllChildren;
44
49
  private restartChildNow;
45
50
  private spawnAgent;
46
51
  private startChild;
@@ -1 +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"}
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAkL,MAAM,aAAa,CAAC;AAC3N,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AASrB;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAyB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IASzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,aAAa;IAGrC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA4E7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Bb,kBAAkB;YAqClB,aAAa;IAqD3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YA6CX,mBAAmB;YA6BnB,mBAAmB;YAgBnB,oBAAoB;YAcpB,kBAAkB;YAYlB,eAAe;YAcf,UAAU;IAgDxB,OAAO,CAAC,UAAU;YAyEJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}
@@ -4,6 +4,11 @@ import * as path from "node:path";
4
4
  import { ParallWs } from "@parall/sdk";
5
5
  import { agentClaudeCredentialsFileFor, agentClaudeHomeFor, agentStateDirFor, agentWorkspaceDirFor, sharedClaudeCredentialsFileFor, } from "./config.js";
6
6
  import { assertAgentKey, getRuntimeAdapter } from "./runtimes.js";
7
+ const RUNTIME_PACKAGES = {
8
+ 'claude-code': '@parall/claude-agent',
9
+ 'codex': '@parall/codex-agent',
10
+ 'openclaw': '@parall/openclaw-agent',
11
+ };
7
12
  /**
8
13
  * Sleep that wakes early on abort. Returns true if the full delay elapsed,
9
14
  * false if aborted. Used by bootstrap retry and the outer keepalive in
@@ -44,6 +49,7 @@ export class DaemonSupervisor {
44
49
  ws = null;
45
50
  running = false;
46
51
  machineOrgId = null;
52
+ machineLlmSource = "parall";
47
53
  stopResolve = null;
48
54
  constructor(config, client, log) {
49
55
  this.config = config;
@@ -71,6 +77,7 @@ export class DaemonSupervisor {
71
77
  this.running = false;
72
78
  throw err;
73
79
  }
80
+ this.migrateFlatLayout();
74
81
  await this.fullReconcile();
75
82
  this.ws = new ParallWs({
76
83
  getTicket: () => this.client.getMachineWsTicket(),
@@ -79,7 +86,10 @@ export class DaemonSupervisor {
79
86
  });
80
87
  this.ws.on("machine.hello", (_data) => {
81
88
  this.log.info("machine WS connected (machine.hello)");
82
- void this.fullReconcile();
89
+ void (async () => {
90
+ await this.refreshMachineConfig();
91
+ await this.fullReconcile();
92
+ })();
83
93
  });
84
94
  this.ws.on("machine.agent.attached", (data) => {
85
95
  this.log.info(`WS: agent ${data.agent_id} attached`);
@@ -89,6 +99,14 @@ export class DaemonSupervisor {
89
99
  this.log.info(`WS: agent ${data.agent_id} detached`);
90
100
  void this.handleAgentDetached(data.agent_id);
91
101
  });
102
+ this.ws.on("machine.config.updated", (data) => {
103
+ const newSource = data.llm_source ?? "parall";
104
+ if (newSource !== this.machineLlmSource) {
105
+ this.log.info(`WS: llm_source changed ${this.machineLlmSource} → ${newSource}, respawning all agents`);
106
+ this.machineLlmSource = newSource;
107
+ void this.respawnAllChildren();
108
+ }
109
+ });
92
110
  this.ws.on("machine.stop", (data) => {
93
111
  this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
94
112
  void this.stop();
@@ -137,6 +155,7 @@ export class DaemonSupervisor {
137
155
  try {
138
156
  const machine = await this.client.getMachineSelf();
139
157
  this.machineOrgId = machine.org_id;
158
+ this.machineLlmSource = machine.llm_source ?? "parall";
140
159
  this.log.info(`daemon online — machine_id=${machine.id} org=${machine.org_id} label=${machine.label}`);
141
160
  return true;
142
161
  }
@@ -209,6 +228,54 @@ export class DaemonSupervisor {
209
228
  }
210
229
  }
211
230
  }
231
+ // ---- Flat layout migration (self-hosted → daemon) ----
232
+ /**
233
+ * Detects a legacy flat state layout (no agents/ subdir) and migrates it
234
+ * into the per-agent directory for the owning agent. Ownership is determined
235
+ * by parsing session state files which embed the agent ID in the runtimeKey.
236
+ */
237
+ migrateFlatLayout() {
238
+ const root = this.config.rootStateDir;
239
+ const agentsDir = path.join(root, "agents");
240
+ const flatWorkspace = path.join(root, "workspace");
241
+ if (!fs.existsSync(flatWorkspace) || fs.existsSync(agentsDir))
242
+ return;
243
+ let ownerAgentId;
244
+ const sessionsDir = path.join(root, "sessions");
245
+ if (fs.existsSync(sessionsDir)) {
246
+ try {
247
+ for (const file of fs.readdirSync(sessionsDir)) {
248
+ if (!file.endsWith(".json"))
249
+ continue;
250
+ const decoded = Buffer.from(file.replace(".json", ""), "base64url").toString();
251
+ // runtimeKey format: "agent:main:{runtime}:{agentId}:orchestrator"
252
+ const parts = decoded.split(":");
253
+ if (parts.length >= 4 && parts[3].startsWith("usr_")) {
254
+ ownerAgentId = parts[3];
255
+ break;
256
+ }
257
+ }
258
+ }
259
+ catch {
260
+ // best-effort scan
261
+ }
262
+ }
263
+ const targetId = ownerAgentId ?? "_orphan";
264
+ const targetDir = path.join(agentsDir, targetId);
265
+ try {
266
+ fs.mkdirSync(targetDir, { recursive: true });
267
+ for (const sub of ["workspace", "sessions", "dispatch-context"]) {
268
+ const src = path.join(root, sub);
269
+ if (fs.existsSync(src)) {
270
+ fs.renameSync(src, path.join(targetDir, sub));
271
+ }
272
+ }
273
+ this.log.info(`migrated legacy flat state → agents/${targetId}/`);
274
+ }
275
+ catch (err) {
276
+ this.log.warn(`flat layout migration failed: ${String(err)}`);
277
+ }
278
+ }
212
279
  // ---- WS event handlers (incremental) ----
213
280
  async handleAgentAttached(agentId) {
214
281
  if (this.children.has(agentId))
@@ -251,6 +318,31 @@ export class DaemonSupervisor {
251
318
  this.children.delete(agentId);
252
319
  }
253
320
  // ---- Spawn / restart ----
321
+ async refreshMachineConfig() {
322
+ try {
323
+ const machine = await this.client.getMachineSelf();
324
+ const newSource = machine.llm_source ?? "parall";
325
+ if (newSource !== this.machineLlmSource) {
326
+ this.log.info(`machine config refreshed: llm_source ${this.machineLlmSource} → ${newSource}`);
327
+ this.machineLlmSource = newSource;
328
+ await this.respawnAllChildren();
329
+ }
330
+ }
331
+ catch (err) {
332
+ this.log.warn(`refreshMachineConfig failed: ${String(err)}`);
333
+ }
334
+ }
335
+ async respawnAllChildren() {
336
+ const states = [...this.children.values()];
337
+ for (const state of states) {
338
+ await this.terminateChild(state);
339
+ }
340
+ for (const state of states) {
341
+ if (!state.shuttingDown && this.running) {
342
+ await this.restartChildNow(state, "llm_source changed");
343
+ }
344
+ }
345
+ }
254
346
  async restartChildNow(state, reason) {
255
347
  if (!this.running || state.shuttingDown || state.child || state.restartTimer) {
256
348
  return;
@@ -275,13 +367,23 @@ export class DaemonSupervisor {
275
367
  return;
276
368
  }
277
369
  const stateDir = agentStateDirFor(this.config.rootStateDir, agentId);
278
- const workspaceDir = agentWorkspaceDirFor(this.config.rootStateDir, agentId);
279
- const claudeHome = agentClaudeHomeFor(this.config.rootClaudeHome, agentId);
370
+ const workspaceDir = attached.daemon_config?.workspace_path
371
+ || agentWorkspaceDirFor(this.config.rootStateDir, agentId);
372
+ const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
373
+ const claudeHome = isK8s
374
+ ? agentClaudeHomeFor(this.config.rootClaudeHome, agentId)
375
+ : this.config.rootClaudeHome;
280
376
  try {
281
377
  fs.mkdirSync(stateDir, { recursive: true });
282
- fs.mkdirSync(workspaceDir, { recursive: true });
283
- fs.mkdirSync(claudeHome, { recursive: true });
284
- this.ensureSharedCredentialLink(claudeHome, agentId);
378
+ // Only create workspace dir when using the default isolated path —
379
+ // user-specified workspace_path should already exist.
380
+ if (!attached.daemon_config?.workspace_path) {
381
+ fs.mkdirSync(workspaceDir, { recursive: true });
382
+ }
383
+ if (isK8s) {
384
+ fs.mkdirSync(claudeHome, { recursive: true });
385
+ this.ensureSharedCredentialLink(claudeHome, agentId);
386
+ }
285
387
  }
286
388
  catch (err) {
287
389
  this.log.warn(`mkdir agent dirs (${agentId}) failed: ${String(err)}`);
@@ -291,6 +393,9 @@ export class DaemonSupervisor {
291
393
  agentId,
292
394
  orgId,
293
395
  runtimeType,
396
+ workspacePath: workspaceDir,
397
+ claudeHome,
398
+ providerConfig: attached.provider_config ?? { llm_source: this.machineLlmSource },
294
399
  child: null,
295
400
  credential,
296
401
  restartAttempts: 0,
@@ -311,10 +416,10 @@ export class DaemonSupervisor {
311
416
  const adapter = getRuntimeAdapter(state.runtimeType);
312
417
  const dirs = {
313
418
  stateDir: agentStateDirFor(this.config.rootStateDir, state.agentId),
314
- workspaceDir: agentWorkspaceDirFor(this.config.rootStateDir, state.agentId),
315
- claudeHome: agentClaudeHomeFor(this.config.rootClaudeHome, state.agentId),
419
+ workspaceDir: state.workspacePath,
420
+ claudeHome: state.claudeHome,
316
421
  };
317
- const env = adapter.buildEnv({ ...process.env, PRLL_API_URL: this.config.apiUrl }, state.agentId, state.orgId, state.credential.api_key, dirs);
422
+ const env = adapter.buildEnv({ ...process.env, PRLL_API_URL: this.config.apiUrl }, state.agentId, state.orgId, state.credential.api_key, dirs, state.providerConfig);
318
423
  this.log.info(`spawning agent ${state.agentId} runtime=${state.runtimeType} bin=${adapter.bin} (attempt ${state.restartAttempts + 1})`);
319
424
  const child = spawn(adapter.bin, [], {
320
425
  env,
@@ -347,7 +452,13 @@ export class DaemonSupervisor {
347
452
  this.startChild(state);
348
453
  }, delay);
349
454
  };
350
- child.once("error", (err) => settleChild("error", null, null, err));
455
+ child.once("error", (err) => {
456
+ if (err.code === 'ENOENT') {
457
+ const pkg = RUNTIME_PACKAGES[state.runtimeType] ?? `@parall/${state.runtimeType}-agent`;
458
+ this.log.error(`Runtime binary "${adapter.bin}" not found in PATH. Install: npm install -g ${pkg}`);
459
+ }
460
+ settleChild("error", null, null, err);
461
+ });
351
462
  child.once("close", (code, signal) => settleChild("close", code, signal));
352
463
  setTimeout(() => {
353
464
  if (state.child === child) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@parall/daemon",
3
- "version": "1.28.0",
4
- "description": "Per-host supervisor for daemon-mode Parall Machines fans one mck_ bearer out into N agent-runtime subprocesses, one per attached agent",
3
+ "version": "1.29.0",
4
+ "description": "Parall local agent runtimedaemon supervisor + bridge runtimes, bundled as standalone JS files",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -9,10 +9,14 @@
9
9
  "directory": "ts/daemon"
10
10
  },
11
11
  "type": "module",
12
- "main": "./dist/index.js",
12
+ "main": "./bundle/parall-daemon.js",
13
13
  "types": "./dist/index.d.ts",
14
14
  "bin": {
15
- "parall-daemon": "./dist/index.js"
15
+ "daemon": "./bundle/parall-daemon.js",
16
+ "parall-daemon": "./bundle/parall-daemon.js",
17
+ "parall-claude-agent": "./bundle/parall-claude-agent.js",
18
+ "parall-codex-agent": "./bundle/parall-codex-agent.js",
19
+ "parall-openclaw-agent": "./bundle/parall-openclaw-agent.js"
16
20
  },
17
21
  "exports": {
18
22
  ".": {
@@ -21,18 +25,24 @@
21
25
  }
22
26
  },
23
27
  "files": [
24
- "dist",
25
- "src"
28
+ "bundle",
29
+ "dist"
26
30
  ],
27
31
  "dependencies": {
28
- "@parall/sdk": "1.28.0"
32
+ "@parall/agent-core": "1.29.0",
33
+ "@parall/sdk": "1.29.0",
34
+ "@parall/claude-agent": "1.29.0",
35
+ "@parall/codex-agent": "1.29.0",
36
+ "@parall/openclaw-agent": "1.29.0"
29
37
  },
30
38
  "devDependencies": {
31
39
  "@types/node": "^22.0.0",
40
+ "esbuild": "^0.25.0",
32
41
  "typescript": "^5.7.0"
33
42
  },
34
43
  "scripts": {
35
44
  "build": "tsc -b",
36
- "start": "node dist/index.js"
45
+ "bundle": "node ../../scripts/bundle-daemon.mjs",
46
+ "start": "node bundle/parall-daemon.js"
37
47
  }
38
48
  }
package/src/config.ts DELETED
@@ -1,146 +0,0 @@
1
- import * as os from "node:os";
2
- import * as path from "node:path";
3
-
4
- /**
5
- * Daemon-mode env. The daemon authenticates to the Parall API with an
6
- * `mck_*` bearer token (PRLL_API_KEY) and supervises N per-agent
7
- * `parall-claude-agent` subprocesses on this host.
8
- *
9
- * Compared to claude-agent's config:
10
- * - `apiKey` is an mck_ bearer (Machine-scoped), not an agk_
11
- * - there is no PRLL_ORG_ID — the Machine row's org_id is implicit;
12
- * each spawned per-agent subprocess is given its own PRLL_ORG_ID
13
- * resolved from the AttachedAgent's profile / org context
14
- * - per-agent state lives at `<rootStateDir>/agents/<agent_id>/`
15
- */
16
- export type ClaudeDaemonConfig = {
17
- apiUrl: string;
18
- /** mck_*-prefixed Machine bearer. */
19
- apiKey: string;
20
- /** @deprecated Superseded by RuntimeAdapter pattern (runtimes.ts). Kept for backward compat / fallback. */
21
- agentBin: string;
22
- /** Root for per-agent state dirs. Each agent gets `<rootStateDir>/agents/<agent_id>`. */
23
- rootStateDir: string;
24
- /** Shared host home root. Per-agent HOME dirs live under `<rootClaudeHome>/agents/<agent_id>`. */
25
- rootClaudeHome: string;
26
- /** Optional WS URL override for the machine control-plane WebSocket. */
27
- wsUrl?: string;
28
- /** @deprecated Superseded by WS event-driven model; kept for backward compat. */
29
- pollIntervalMs: number;
30
- /** @deprecated Superseded by WS event-driven model; kept for backward compat. */
31
- heartbeatIntervalMs: number;
32
- /** Backoff base after a subprocess crash, in ms. */
33
- restartBackoffMs: number;
34
- /** Hard cap on parallel restart attempts per agent before giving up for a cycle. */
35
- restartBackoffMaxMs: number;
36
- /**
37
- * Bootstrap retry: when `getMachineSelf` fails on startup (network blip,
38
- * mck_ key not yet propagated, server warming up), wait this long and
39
- * retry instead of crashing the daemon. Exponential backoff up to
40
- * `bootstrapBackoffMaxMs`. Set 0 to disable (fail-fast on first error).
41
- */
42
- bootstrapBackoffMs: number;
43
- bootstrapBackoffMaxMs: number;
44
- /**
45
- * Outer supervisor keepalive: if `supervisor.run()` rejects with an
46
- * unexpected error, wait this long and reinstantiate. Same exp-backoff
47
- * shape as bootstrap. Set 0 to disable (let main() exit, rely on K8s/PID-1).
48
- */
49
- supervisorRestartBackoffMs: number;
50
- supervisorRestartBackoffMaxMs: number;
51
- };
52
-
53
- function requireEnv(env: NodeJS.ProcessEnv, name: string): string {
54
- const value = env[name]?.trim();
55
- if (!value) {
56
- throw new Error(`Missing required env var: ${name}`);
57
- }
58
- return value;
59
- }
60
-
61
- function resolvePath(value: string): string {
62
- return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
63
- }
64
-
65
- function parseMs(value: string | undefined, fallback: number): number {
66
- if (!value) return fallback;
67
- const n = Number(value);
68
- return Number.isFinite(n) && n > 0 ? n : fallback;
69
- }
70
-
71
- /** Like parseMs but allows 0 (operator opt-out). */
72
- function parseMsAllowZero(value: string | undefined, fallback: number): number {
73
- if (value === undefined) return fallback;
74
- const n = Number(value);
75
- return Number.isFinite(n) && n >= 0 ? n : fallback;
76
- }
77
-
78
- export function resolveClaudeDaemonConfig(env: NodeJS.ProcessEnv = process.env): ClaudeDaemonConfig {
79
- const apiUrl = requireEnv(env, "PRLL_API_URL");
80
- const apiKey = requireEnv(env, "PRLL_API_KEY");
81
- if (!apiKey.startsWith("mck_")) {
82
- // Fatal startup validation: the daemon must never run with an agent or
83
- // human key because child launch credentials are minted from this bearer.
84
- throw new Error(
85
- `PRLL_API_KEY does not look like a Machine bearer (expected prefix "mck_"). ` +
86
- `Daemon mode requires a machine-scoped key issued via POST /machines/{id}/keys.`,
87
- );
88
- }
89
- const rootClaudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os.homedir());
90
- const rootStateDir = resolvePath(env.PRLL_CLAUDE_STATE_DIR?.trim() || path.join(rootClaudeHome, ".parall-agent"));
91
-
92
- return {
93
- apiUrl,
94
- apiKey,
95
- agentBin: env.PRLL_CLAUDE_AGENT_BIN?.trim() || "parall-claude-agent",
96
- rootStateDir,
97
- rootClaudeHome,
98
- wsUrl: env.PRLL_WS_URL?.trim() || undefined,
99
- pollIntervalMs: parseMs(env.PRLL_DAEMON_POLL_INTERVAL_MS, 30_000),
100
- heartbeatIntervalMs: parseMs(env.PRLL_DAEMON_HEARTBEAT_INTERVAL_MS, 30_000),
101
- restartBackoffMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MS, 5_000),
102
- restartBackoffMaxMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MAX_MS, 5 * 60_000),
103
- bootstrapBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MS, 2_000),
104
- bootstrapBackoffMaxMs: parseMs(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MAX_MS, 60_000),
105
- supervisorRestartBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS, 5_000),
106
- supervisorRestartBackoffMaxMs: parseMs(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MAX_MS, 5 * 60_000),
107
- };
108
- }
109
-
110
- function assertSafeAgentId(agentId: string): string {
111
- if (!/^[A-Za-z0-9_-]+$/.test(agentId)) {
112
- throw new Error(`Invalid agentId for filesystem path: ${agentId}`);
113
- }
114
- return agentId;
115
- }
116
-
117
- /** Per-agent state dir under the shared host volume. */
118
- export function agentStateDirFor(rootStateDir: string, agentId: string): string {
119
- return path.join(rootStateDir, "agents", assertSafeAgentId(agentId));
120
- }
121
-
122
- /** Per-agent HOME dir. Claude Code stores project/session state under
123
- * `${HOME}/.claude`, so each agent gets its own HOME root while the daemon
124
- * links shared OAuth credentials into that `.claude` directory. */
125
- export function agentClaudeHomeFor(rootClaudeHome: string, agentId: string): string {
126
- return path.join(rootClaudeHome, "agents", assertSafeAgentId(agentId));
127
- }
128
-
129
- /** Shared Claude Code OAuth credential written by server-side runtime auth. */
130
- export function sharedClaudeCredentialsFileFor(rootClaudeHome: string): string {
131
- return path.join(rootClaudeHome, ".claude", ".credentials.json");
132
- }
133
-
134
- /** Per-agent credential location inside that agent's isolated HOME. */
135
- export function agentClaudeCredentialsFileFor(agentClaudeHome: string): string {
136
- return path.join(agentClaudeHome, ".claude", ".credentials.json");
137
- }
138
-
139
- /** Per-agent workspace dir where the agent runs git commands. */
140
- export function agentWorkspaceDirFor(rootStateDir: string, agentId: string): string {
141
- return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
142
- }
143
-
144
- export function resolveWsUrl(apiUrl: string, explicitWsUrl?: string): string {
145
- return explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
146
- }