@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/bundle/manifest.json +22 -0
- package/bundle/parall-claude-agent.js +5706 -0
- package/bundle/parall-codex-agent.js +6669 -0
- package/bundle/parall-daemon.js +2880 -0
- package/bundle/parall-openclaw-agent.js +228 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +277 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -2
- package/dist/index.js +15 -10
- package/dist/runtimes.d.ts +8 -1
- package/dist/runtimes.d.ts.map +1 -1
- package/dist/runtimes.js +49 -3
- package/dist/supervisor.d.ts +11 -6
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +121 -10
- package/package.json +18 -8
- package/src/config.ts +0 -146
- package/src/index.ts +0 -132
- package/src/runtimes.ts +0 -91
- package/src/supervisor.ts +0 -480
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
|
-
|
|
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;
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -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:
|
|
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;
|
package/dist/supervisor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,
|
|
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"}
|
package/dist/supervisor.js
CHANGED
|
@@ -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
|
|
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 =
|
|
279
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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:
|
|
315
|
-
claudeHome:
|
|
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) =>
|
|
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.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.29.0",
|
|
4
|
+
"description": "Parall local agent runtime — daemon 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": "./
|
|
12
|
+
"main": "./bundle/parall-daemon.js",
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"bin": {
|
|
15
|
-
"
|
|
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
|
-
"
|
|
25
|
-
"
|
|
28
|
+
"bundle",
|
|
29
|
+
"dist"
|
|
26
30
|
],
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@parall/
|
|
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
|
-
"
|
|
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
|
-
}
|