@parall/daemon 1.28.1 → 1.29.1
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 +10 -10
- package/bundle/parall-claude-agent.js +236 -111
- package/bundle/parall-codex-agent.js +335 -129
- package/bundle/parall-daemon.js +848 -137
- package/bundle/parall-openclaw-agent.js +22 -14
- package/dist/config.d.ts +3 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -2
- package/dist/index.js +6 -11
- package/dist/runtimes.d.ts +9 -1
- package/dist/runtimes.d.ts.map +1 -1
- package/dist/runtimes.js +49 -3
- package/dist/supervisor.d.ts +14 -6
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +171 -18
- package/dist/workspace.d.ts +11 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +436 -0
- package/package.json +7 -5
package/dist/supervisor.js
CHANGED
|
@@ -4,11 +4,13 @@ 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
|
+
import { prepareWorkspace } from "./workspace.js";
|
|
7
8
|
const RUNTIME_PACKAGES = {
|
|
8
9
|
'claude-code': '@parall/claude-agent',
|
|
9
10
|
'codex': '@parall/codex-agent',
|
|
10
11
|
'openclaw': '@parall/openclaw-agent',
|
|
11
12
|
};
|
|
13
|
+
const WORKSPACE_SETUP_RETRY_DELAY_MS = 5_000;
|
|
12
14
|
/**
|
|
13
15
|
* Sleep that wakes early on abort. Returns true if the full delay elapsed,
|
|
14
16
|
* false if aborted. Used by bootstrap retry and the outer keepalive in
|
|
@@ -46,9 +48,14 @@ export class DaemonSupervisor {
|
|
|
46
48
|
client;
|
|
47
49
|
log;
|
|
48
50
|
children = new Map();
|
|
51
|
+
spawningAgents = new Set();
|
|
52
|
+
pendingWorkspaceSetup = new Set();
|
|
53
|
+
workspaceSetupRetryTimers = new Map();
|
|
54
|
+
cancelledSpawns = new Set();
|
|
49
55
|
ws = null;
|
|
50
56
|
running = false;
|
|
51
57
|
machineOrgId = null;
|
|
58
|
+
machineLlmSource = "parall";
|
|
52
59
|
stopResolve = null;
|
|
53
60
|
constructor(config, client, log) {
|
|
54
61
|
this.config = config;
|
|
@@ -85,7 +92,10 @@ export class DaemonSupervisor {
|
|
|
85
92
|
});
|
|
86
93
|
this.ws.on("machine.hello", (_data) => {
|
|
87
94
|
this.log.info("machine WS connected (machine.hello)");
|
|
88
|
-
void
|
|
95
|
+
void (async () => {
|
|
96
|
+
await this.refreshMachineConfig();
|
|
97
|
+
await this.fullReconcile();
|
|
98
|
+
})();
|
|
89
99
|
});
|
|
90
100
|
this.ws.on("machine.agent.attached", (data) => {
|
|
91
101
|
this.log.info(`WS: agent ${data.agent_id} attached`);
|
|
@@ -95,6 +105,18 @@ export class DaemonSupervisor {
|
|
|
95
105
|
this.log.info(`WS: agent ${data.agent_id} detached`);
|
|
96
106
|
void this.handleAgentDetached(data.agent_id);
|
|
97
107
|
});
|
|
108
|
+
this.ws.on("machine.config.updated", (data) => {
|
|
109
|
+
const newSource = data.llm_source ?? "parall";
|
|
110
|
+
if (newSource !== this.machineLlmSource) {
|
|
111
|
+
this.log.info(`WS: llm_source changed ${this.machineLlmSource} → ${newSource}, respawning all agents`);
|
|
112
|
+
this.machineLlmSource = newSource;
|
|
113
|
+
void this.respawnAllChildren();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
this.ws.on("machine.workspace.setup.requested", (data) => {
|
|
117
|
+
this.log.info(`WS: workspace setup requested for agent ${data.agent_id}`);
|
|
118
|
+
void this.handleWorkspaceSetupRequested(data.agent_id);
|
|
119
|
+
});
|
|
98
120
|
this.ws.on("machine.stop", (data) => {
|
|
99
121
|
this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
|
|
100
122
|
void this.stop();
|
|
@@ -120,6 +142,10 @@ export class DaemonSupervisor {
|
|
|
120
142
|
this.ws = null;
|
|
121
143
|
}
|
|
122
144
|
const exits = [];
|
|
145
|
+
for (const timer of this.workspaceSetupRetryTimers.values()) {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
}
|
|
148
|
+
this.workspaceSetupRetryTimers.clear();
|
|
123
149
|
for (const state of this.children.values()) {
|
|
124
150
|
state.shuttingDown = true;
|
|
125
151
|
if (state.restartTimer) {
|
|
@@ -143,6 +169,7 @@ export class DaemonSupervisor {
|
|
|
143
169
|
try {
|
|
144
170
|
const machine = await this.client.getMachineSelf();
|
|
145
171
|
this.machineOrgId = machine.org_id;
|
|
172
|
+
this.machineLlmSource = machine.llm_source ?? "parall";
|
|
146
173
|
this.log.info(`daemon online — machine_id=${machine.id} org=${machine.org_id} label=${machine.label}`);
|
|
147
174
|
return true;
|
|
148
175
|
}
|
|
@@ -264,24 +291,31 @@ export class DaemonSupervisor {
|
|
|
264
291
|
}
|
|
265
292
|
}
|
|
266
293
|
// ---- WS event handlers (incremental) ----
|
|
267
|
-
async
|
|
268
|
-
if (this.children.has(agentId))
|
|
269
|
-
return;
|
|
294
|
+
async fetchAttachedAgent(agentId) {
|
|
270
295
|
let attached;
|
|
271
296
|
try {
|
|
272
297
|
attached = await this.client.listAttachedAgents();
|
|
273
298
|
}
|
|
274
299
|
catch (err) {
|
|
275
|
-
this.log.warn(`
|
|
276
|
-
return;
|
|
300
|
+
this.log.warn(`fetchAttachedAgent: listAttachedAgents failed: ${String(err)}`);
|
|
301
|
+
return { kind: "retryable" };
|
|
277
302
|
}
|
|
278
303
|
const entry = attached.find((a) => (a.user?.id ?? a.profile.user_id) === agentId);
|
|
279
304
|
if (!entry) {
|
|
280
|
-
this.log.warn(`
|
|
281
|
-
return;
|
|
305
|
+
this.log.warn(`fetchAttachedAgent: agent ${agentId} not found in attached list`);
|
|
306
|
+
return { kind: "skip" };
|
|
282
307
|
}
|
|
283
308
|
if (entry.user && entry.user.status !== "active") {
|
|
284
309
|
this.log.info(`agent ${agentId} not active (status=${entry.user.status}) — skipping`);
|
|
310
|
+
return { kind: "skip" };
|
|
311
|
+
}
|
|
312
|
+
return { kind: "found", entry };
|
|
313
|
+
}
|
|
314
|
+
async handleAgentAttached(agentId) {
|
|
315
|
+
if (this.children.has(agentId) || this.spawningAgents.has(agentId))
|
|
316
|
+
return;
|
|
317
|
+
const result = await this.fetchAttachedAgent(agentId);
|
|
318
|
+
if (result.kind !== "found") {
|
|
285
319
|
return;
|
|
286
320
|
}
|
|
287
321
|
const orgId = this.machineOrgId;
|
|
@@ -289,9 +323,14 @@ export class DaemonSupervisor {
|
|
|
289
323
|
this.log.warn(`agent ${agentId}: no org_id available yet; skipping`);
|
|
290
324
|
return;
|
|
291
325
|
}
|
|
292
|
-
await this.spawnAgent(agentId, orgId, entry);
|
|
326
|
+
await this.spawnAgent(agentId, orgId, result.entry);
|
|
293
327
|
}
|
|
294
328
|
async handleAgentDetached(agentId) {
|
|
329
|
+
this.pendingWorkspaceSetup.delete(agentId);
|
|
330
|
+
this.clearWorkspaceSetupRetry(agentId);
|
|
331
|
+
if (this.spawningAgents.has(agentId)) {
|
|
332
|
+
this.cancelledSpawns.add(agentId);
|
|
333
|
+
}
|
|
295
334
|
const state = this.children.get(agentId);
|
|
296
335
|
if (!state)
|
|
297
336
|
return;
|
|
@@ -304,7 +343,87 @@ export class DaemonSupervisor {
|
|
|
304
343
|
await this.terminateChild(state);
|
|
305
344
|
this.children.delete(agentId);
|
|
306
345
|
}
|
|
346
|
+
async handleWorkspaceSetupRequested(agentId) {
|
|
347
|
+
if (this.spawningAgents.has(agentId)) {
|
|
348
|
+
this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
|
|
349
|
+
this.pendingWorkspaceSetup.add(agentId);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const result = await this.fetchAttachedAgent(agentId);
|
|
353
|
+
if (result.kind === "retryable") {
|
|
354
|
+
this.pendingWorkspaceSetup.add(agentId);
|
|
355
|
+
this.scheduleWorkspaceSetupRetry(agentId);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (result.kind === "skip") {
|
|
359
|
+
this.pendingWorkspaceSetup.delete(agentId);
|
|
360
|
+
this.clearWorkspaceSetupRetry(agentId);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.clearWorkspaceSetupRetry(agentId);
|
|
364
|
+
const state = this.children.get(agentId);
|
|
365
|
+
if (state) {
|
|
366
|
+
this.log.info(`agent ${agentId}: restarting for workspace setup`);
|
|
367
|
+
state.shuttingDown = true;
|
|
368
|
+
if (state.restartTimer) {
|
|
369
|
+
clearTimeout(state.restartTimer);
|
|
370
|
+
state.restartTimer = null;
|
|
371
|
+
}
|
|
372
|
+
await this.terminateChild(state);
|
|
373
|
+
this.children.delete(agentId);
|
|
374
|
+
}
|
|
375
|
+
const orgId = this.machineOrgId;
|
|
376
|
+
if (!orgId) {
|
|
377
|
+
this.log.warn(`agent ${agentId}: no org_id available yet; skipping`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
await this.spawnAgent(agentId, orgId, result.entry);
|
|
381
|
+
}
|
|
382
|
+
scheduleWorkspaceSetupRetry(agentId) {
|
|
383
|
+
if (!this.running || this.workspaceSetupRetryTimers.has(agentId))
|
|
384
|
+
return;
|
|
385
|
+
const timer = setTimeout(() => {
|
|
386
|
+
this.workspaceSetupRetryTimers.delete(agentId);
|
|
387
|
+
if (this.running && this.pendingWorkspaceSetup.has(agentId)) {
|
|
388
|
+
void this.handleWorkspaceSetupRequested(agentId);
|
|
389
|
+
}
|
|
390
|
+
}, WORKSPACE_SETUP_RETRY_DELAY_MS);
|
|
391
|
+
timer.unref?.();
|
|
392
|
+
this.workspaceSetupRetryTimers.set(agentId, timer);
|
|
393
|
+
}
|
|
394
|
+
clearWorkspaceSetupRetry(agentId) {
|
|
395
|
+
const timer = this.workspaceSetupRetryTimers.get(agentId);
|
|
396
|
+
if (timer) {
|
|
397
|
+
clearTimeout(timer);
|
|
398
|
+
this.workspaceSetupRetryTimers.delete(agentId);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
307
401
|
// ---- Spawn / restart ----
|
|
402
|
+
async refreshMachineConfig() {
|
|
403
|
+
try {
|
|
404
|
+
const machine = await this.client.getMachineSelf();
|
|
405
|
+
const newSource = machine.llm_source ?? "parall";
|
|
406
|
+
if (newSource !== this.machineLlmSource) {
|
|
407
|
+
this.log.info(`machine config refreshed: llm_source ${this.machineLlmSource} → ${newSource}`);
|
|
408
|
+
this.machineLlmSource = newSource;
|
|
409
|
+
await this.respawnAllChildren();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
this.log.warn(`refreshMachineConfig failed: ${String(err)}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async respawnAllChildren() {
|
|
417
|
+
const states = [...this.children.values()];
|
|
418
|
+
for (const state of states) {
|
|
419
|
+
await this.terminateChild(state);
|
|
420
|
+
}
|
|
421
|
+
for (const state of states) {
|
|
422
|
+
if (!state.shuttingDown && this.running) {
|
|
423
|
+
await this.restartChildNow(state, "llm_source changed");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
308
427
|
async restartChildNow(state, reason) {
|
|
309
428
|
if (!this.running || state.shuttingDown || state.child || state.restartTimer) {
|
|
310
429
|
return;
|
|
@@ -320,6 +439,26 @@ export class DaemonSupervisor {
|
|
|
320
439
|
}
|
|
321
440
|
}
|
|
322
441
|
async spawnAgent(agentId, orgId, attached) {
|
|
442
|
+
if (this.children.has(agentId) || this.spawningAgents.has(agentId)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
this.spawningAgents.add(agentId);
|
|
446
|
+
let shouldReplaySetupRequest = false;
|
|
447
|
+
try {
|
|
448
|
+
await this.spawnAgentOnce(agentId, orgId, attached);
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
this.spawningAgents.delete(agentId);
|
|
452
|
+
this.cancelledSpawns.delete(agentId);
|
|
453
|
+
shouldReplaySetupRequest = this.pendingWorkspaceSetup.delete(agentId);
|
|
454
|
+
}
|
|
455
|
+
if (shouldReplaySetupRequest && this.running) {
|
|
456
|
+
queueMicrotask(() => {
|
|
457
|
+
void this.handleWorkspaceSetupRequested(agentId);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async spawnAgentOnce(agentId, orgId, attached) {
|
|
323
462
|
let credential;
|
|
324
463
|
try {
|
|
325
464
|
credential = await this.client.mintLaunchCredential(agentId);
|
|
@@ -329,19 +468,13 @@ export class DaemonSupervisor {
|
|
|
329
468
|
return;
|
|
330
469
|
}
|
|
331
470
|
const stateDir = agentStateDirFor(this.config.rootStateDir, agentId);
|
|
332
|
-
const
|
|
333
|
-
|| agentWorkspaceDirFor(this.config.rootStateDir, agentId);
|
|
471
|
+
const defaultWorkspaceDir = agentWorkspaceDirFor(this.config.rootStateDir, agentId);
|
|
334
472
|
const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
|
|
335
473
|
const claudeHome = isK8s
|
|
336
474
|
? agentClaudeHomeFor(this.config.rootClaudeHome, agentId)
|
|
337
475
|
: this.config.rootClaudeHome;
|
|
338
476
|
try {
|
|
339
477
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
340
|
-
// Only create workspace dir when using the default isolated path —
|
|
341
|
-
// user-specified workspace_path should already exist.
|
|
342
|
-
if (!attached.daemon_config?.workspace_path) {
|
|
343
|
-
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
344
|
-
}
|
|
345
478
|
if (isK8s) {
|
|
346
479
|
fs.mkdirSync(claudeHome, { recursive: true });
|
|
347
480
|
this.ensureSharedCredentialLink(claudeHome, agentId);
|
|
@@ -351,12 +484,31 @@ export class DaemonSupervisor {
|
|
|
351
484
|
this.log.warn(`mkdir agent dirs (${agentId}) failed: ${String(err)}`);
|
|
352
485
|
}
|
|
353
486
|
const runtimeType = attached.profile.runtime_type ?? "claude-code";
|
|
487
|
+
let workspaceDir;
|
|
488
|
+
try {
|
|
489
|
+
workspaceDir = await prepareWorkspace({
|
|
490
|
+
client: this.client,
|
|
491
|
+
attached,
|
|
492
|
+
agentId,
|
|
493
|
+
defaultWorkspaceDir,
|
|
494
|
+
log: this.log,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
this.log.error(`agent ${agentId}: workspace setup failed: ${String(err)}`);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (this.cancelledSpawns.delete(agentId)) {
|
|
502
|
+
this.log.info(`agent ${agentId}: spawn cancelled after detach during workspace setup`);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
354
505
|
const state = {
|
|
355
506
|
agentId,
|
|
356
507
|
orgId,
|
|
357
508
|
runtimeType,
|
|
358
509
|
workspacePath: workspaceDir,
|
|
359
510
|
claudeHome,
|
|
511
|
+
providerConfig: attached.provider_config ?? { llm_source: this.machineLlmSource },
|
|
360
512
|
child: null,
|
|
361
513
|
credential,
|
|
362
514
|
restartAttempts: 0,
|
|
@@ -380,7 +532,7 @@ export class DaemonSupervisor {
|
|
|
380
532
|
workspaceDir: state.workspacePath,
|
|
381
533
|
claudeHome: state.claudeHome,
|
|
382
534
|
};
|
|
383
|
-
const env = adapter.buildEnv({ ...process.env, PRLL_API_URL: this.config.apiUrl }, state.agentId, state.orgId, state.credential.api_key, dirs);
|
|
535
|
+
const env = adapter.buildEnv({ ...process.env, PRLL_API_URL: this.config.apiUrl }, state.agentId, state.orgId, state.credential.api_key, dirs, state.providerConfig);
|
|
384
536
|
this.log.info(`spawning agent ${state.agentId} runtime=${state.runtimeType} bin=${adapter.bin} (attempt ${state.restartAttempts + 1})`);
|
|
385
537
|
const child = spawn(adapter.bin, [], {
|
|
386
538
|
env,
|
|
@@ -421,11 +573,12 @@ export class DaemonSupervisor {
|
|
|
421
573
|
settleChild("error", null, null, err);
|
|
422
574
|
});
|
|
423
575
|
child.once("close", (code, signal) => settleChild("close", code, signal));
|
|
424
|
-
setTimeout(() => {
|
|
576
|
+
const stableTimer = setTimeout(() => {
|
|
425
577
|
if (state.child === child) {
|
|
426
578
|
state.restartAttempts = 0;
|
|
427
579
|
}
|
|
428
580
|
}, Math.max(this.config.restartBackoffMs, 30_000));
|
|
581
|
+
stableTimer.unref?.();
|
|
429
582
|
}
|
|
430
583
|
async terminateChild(state) {
|
|
431
584
|
const child = state.child;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GatewayLogger } from "@parall/agent-core";
|
|
2
|
+
import type { AttachedAgent, ParallClient } from "@parall/sdk";
|
|
3
|
+
export interface PrepareWorkspaceOpts {
|
|
4
|
+
client: ParallClient;
|
|
5
|
+
attached: AttachedAgent;
|
|
6
|
+
agentId: string;
|
|
7
|
+
defaultWorkspaceDir: string;
|
|
8
|
+
log: GatewayLogger;
|
|
9
|
+
}
|
|
10
|
+
export declare function prepareWorkspace(opts: PrepareWorkspaceOpts): Promise<string>;
|
|
11
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAA4C,YAAY,EAAE,MAAM,aAAa,CAAC;AAiBzG,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,aAAa,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClF"}
|