@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/bundle/parall-daemon.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ts/agent-core/dist/logger.js
|
|
4
|
+
function createLogger(prefix) {
|
|
5
|
+
return {
|
|
6
|
+
info: (msg) => console.log(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
7
|
+
warn: (msg) => console.warn(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`),
|
|
8
|
+
error: (msg) => console.error(`${(/* @__PURE__ */ new Date()).toISOString()} [${prefix}] ${msg}`)
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
// ts/sdk/dist/constants.js
|
|
4
13
|
var API_BASE = "/api/v1";
|
|
5
14
|
var WIKI_BASE = "/wiki/v1";
|
|
@@ -13,6 +22,8 @@ var ENDPOINTS = {
|
|
|
13
22
|
AUTH_CHECK_EMAIL: `${API_BASE}/auth/check-email`,
|
|
14
23
|
AUTH_VERIFY_EMAIL: `${API_BASE}/auth/verify-email`,
|
|
15
24
|
AUTH_RESEND_CODE: `${API_BASE}/auth/resend-code`,
|
|
25
|
+
AUTH_FORGOT_PASSWORD: `${API_BASE}/auth/forgot-password`,
|
|
26
|
+
AUTH_RESET_PASSWORD: `${API_BASE}/auth/reset-password`,
|
|
16
27
|
// Users
|
|
17
28
|
USERS_ME: `${API_BASE}/users/me`,
|
|
18
29
|
USER_AVATAR: `${API_BASE}/users/me/avatar`,
|
|
@@ -46,8 +57,10 @@ var ENDPOINTS = {
|
|
|
46
57
|
CHAT_MESSAGES: (orgId, chatId) => `${API_BASE}/orgs/${orgId}/chats/${chatId}/messages`,
|
|
47
58
|
// Messages (global, by message ID)
|
|
48
59
|
MESSAGE: (id) => `${API_BASE}/messages/${id}`,
|
|
49
|
-
MESSAGE_PATCHES: (id) => `${API_BASE}/messages/${id}/patches`,
|
|
50
60
|
MESSAGE_REPLIES: (id) => `${API_BASE}/messages/${id}/replies`,
|
|
61
|
+
MESSAGE_WATCH: (id) => `${API_BASE}/messages/${id}/watch`,
|
|
62
|
+
MESSAGE_WATCHERS: (id) => `${API_BASE}/messages/${id}/watchers`,
|
|
63
|
+
MESSAGE_WATCHING: (id) => `${API_BASE}/messages/${id}/watching`,
|
|
51
64
|
// Upload (org-scoped)
|
|
52
65
|
UPLOAD_PRESIGN: (orgId) => `${API_BASE}/orgs/${orgId}/upload/presign`,
|
|
53
66
|
UPLOAD_COMPLETE: (orgId) => `${API_BASE}/orgs/${orgId}/upload/complete`,
|
|
@@ -73,6 +86,7 @@ var ENDPOINTS = {
|
|
|
73
86
|
AGENT_SESSION: (orgId, agentId, sessionId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}`,
|
|
74
87
|
AGENT_SESSION_STEPS: (orgId, agentId, sessionId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/steps`,
|
|
75
88
|
AGENT_SESSION_STEP: (orgId, agentId, sessionId, stepId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/sessions/${sessionId}/steps/${stepId}`,
|
|
89
|
+
AGENT_STEP_BY_ID: (orgId, stepId) => `${API_BASE}/orgs/${orgId}/agent-steps/${stepId}`,
|
|
76
90
|
AGENT_TASKS: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/tasks`,
|
|
77
91
|
AGENT_RUNTIME_AUTH: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/runtime-auth`,
|
|
78
92
|
AGENT_RUNTIME_AUTH_SESSIONS: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/runtime-auth-sessions`,
|
|
@@ -97,9 +111,15 @@ var ENDPOINTS = {
|
|
|
97
111
|
// Attach/Detach bind an agent to/from a daemon Machine.
|
|
98
112
|
MACHINE_ATTACH_AGENT: (orgId, machineId, agentId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/agents/${agentId}`,
|
|
99
113
|
MACHINE_DETACH_AGENT: (orgId, machineId, agentId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/agents/${agentId}`,
|
|
114
|
+
MACHINE_WORKSPACE_STATES: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/workspace-states`,
|
|
115
|
+
MACHINE_AGENT_DAEMON_CONFIG: (orgId, machineId, agentId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/agents/${agentId}/daemon-config`,
|
|
116
|
+
MACHINE_AGENT_WORKSPACE_SETUP: (orgId, machineId, agentId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/agents/${agentId}/workspace/setup`,
|
|
117
|
+
MACHINE_LLM_SOURCE: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/llm-source`,
|
|
100
118
|
MACHINE_RUNTIME_AUTH: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth`,
|
|
101
119
|
MACHINE_KEYS: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys`,
|
|
102
120
|
MACHINE_KEY: (orgId, machineId, keyId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys/${keyId}`,
|
|
121
|
+
MACHINE_RUNTIME_AUTH_SESSIONS: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth-sessions`,
|
|
122
|
+
MACHINE_RUNTIME_AUTH_SESSION_COMPLETE: (orgId, machineId, sessionId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth-sessions/${sessionId}/complete`,
|
|
103
123
|
// Machine self-control-plane (mck_-scoped). The bearer token implicitly
|
|
104
124
|
// identifies the Machine, so there is no `:mid` URL parameter — these are
|
|
105
125
|
// "self" routes called by the daemon for its own host.
|
|
@@ -107,6 +127,7 @@ var ENDPOINTS = {
|
|
|
107
127
|
MACHINES_ME_AGENTS: `${API_BASE}/machines/me/agents`,
|
|
108
128
|
MACHINES_ME_HEALTH: `${API_BASE}/machines/me/health`,
|
|
109
129
|
MACHINES_ME_AGENT_LAUNCH_CREDENTIAL: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/launch-credential`,
|
|
130
|
+
MACHINES_ME_AGENT_WORKSPACE_STATE: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/workspace-state`,
|
|
110
131
|
MACHINES_ME_WS_TICKET: `${API_BASE}/machines/me/ws/ticket`,
|
|
111
132
|
// Tasks (org-scoped)
|
|
112
133
|
TASKS: (orgId) => `${API_BASE}/orgs/${orgId}/tasks`,
|
|
@@ -286,7 +307,8 @@ var WS_EVENTS = {
|
|
|
286
307
|
MACHINE_HELLO: "machine.hello",
|
|
287
308
|
MACHINE_AGENT_ATTACHED: "machine.agent.attached",
|
|
288
309
|
MACHINE_AGENT_DETACHED: "machine.agent.detached",
|
|
289
|
-
MACHINE_STOP: "machine.stop"
|
|
310
|
+
MACHINE_STOP: "machine.stop",
|
|
311
|
+
MACHINE_WORKSPACE_SETUP_REQUESTED: "machine.workspace.setup.requested"
|
|
290
312
|
};
|
|
291
313
|
|
|
292
314
|
// ts/sdk/dist/client.js
|
|
@@ -306,7 +328,9 @@ var ParallClient = class _ParallClient {
|
|
|
306
328
|
"/auth/logout",
|
|
307
329
|
"/auth/verify-email",
|
|
308
330
|
"/auth/resend-code",
|
|
309
|
-
"/auth/check-email"
|
|
331
|
+
"/auth/check-email",
|
|
332
|
+
"/auth/forgot-password",
|
|
333
|
+
"/auth/reset-password"
|
|
310
334
|
]);
|
|
311
335
|
/** Proactive refresh when token expires within this window (seconds). */
|
|
312
336
|
static REFRESH_THRESHOLD_S = 5 * 60;
|
|
@@ -367,10 +391,10 @@ var ParallClient = class _ParallClient {
|
|
|
367
391
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
368
392
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
369
393
|
*/
|
|
370
|
-
async ensureFreshToken(
|
|
394
|
+
async ensureFreshToken(path6) {
|
|
371
395
|
if (!this.token || !this.getRefreshToken)
|
|
372
396
|
return;
|
|
373
|
-
const pathSuffix =
|
|
397
|
+
const pathSuffix = path6.replace(/^\/api\/v1/, "");
|
|
374
398
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
375
399
|
return;
|
|
376
400
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -402,11 +426,11 @@ var ParallClient = class _ParallClient {
|
|
|
402
426
|
this.refreshPromise = null;
|
|
403
427
|
}
|
|
404
428
|
}
|
|
405
|
-
async request(method,
|
|
429
|
+
async request(method, path6, body, query, retried = false, opts) {
|
|
406
430
|
if (!retried) {
|
|
407
|
-
await this.ensureFreshToken(
|
|
431
|
+
await this.ensureFreshToken(path6);
|
|
408
432
|
}
|
|
409
|
-
let url = `${this.baseUrl}${
|
|
433
|
+
let url = `${this.baseUrl}${path6}`;
|
|
410
434
|
if (query) {
|
|
411
435
|
const params = new URLSearchParams();
|
|
412
436
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -431,12 +455,12 @@ var ParallClient = class _ParallClient {
|
|
|
431
455
|
throw _ParallClient.normalizeFetchError(err);
|
|
432
456
|
}
|
|
433
457
|
if (res.status === 401) {
|
|
434
|
-
const pathSuffix =
|
|
458
|
+
const pathSuffix = path6.replace(/^\/api\/v1/, "");
|
|
435
459
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
436
460
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
437
461
|
const refreshed = await this.tryRefresh();
|
|
438
462
|
if (refreshed) {
|
|
439
|
-
return this.request(method,
|
|
463
|
+
return this.request(method, path6, body, query, true, opts);
|
|
440
464
|
}
|
|
441
465
|
}
|
|
442
466
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -473,15 +497,15 @@ var ParallClient = class _ParallClient {
|
|
|
473
497
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
474
498
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
475
499
|
*/
|
|
476
|
-
async multipartRequest(method,
|
|
500
|
+
async multipartRequest(method, path6, body, retried = false) {
|
|
477
501
|
if (!retried) {
|
|
478
|
-
await this.ensureFreshToken(
|
|
502
|
+
await this.ensureFreshToken(path6);
|
|
479
503
|
}
|
|
480
504
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
481
505
|
void _drop;
|
|
482
506
|
let res;
|
|
483
507
|
try {
|
|
484
|
-
res = await fetch(`${this.baseUrl}${
|
|
508
|
+
res = await fetch(`${this.baseUrl}${path6}`, {
|
|
485
509
|
method,
|
|
486
510
|
headers,
|
|
487
511
|
body,
|
|
@@ -491,12 +515,12 @@ var ParallClient = class _ParallClient {
|
|
|
491
515
|
throw _ParallClient.normalizeFetchError(err);
|
|
492
516
|
}
|
|
493
517
|
if (res.status === 401) {
|
|
494
|
-
const pathSuffix =
|
|
518
|
+
const pathSuffix = path6.replace(/^\/api\/v1/, "");
|
|
495
519
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
496
520
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
497
521
|
const refreshed = await this.tryRefresh();
|
|
498
522
|
if (refreshed) {
|
|
499
|
-
return this.multipartRequest(method,
|
|
523
|
+
return this.multipartRequest(method, path6, body, true);
|
|
500
524
|
}
|
|
501
525
|
}
|
|
502
526
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -544,6 +568,12 @@ var ParallClient = class _ParallClient {
|
|
|
544
568
|
async resendCode(email) {
|
|
545
569
|
return this.request("POST", ENDPOINTS.AUTH_RESEND_CODE, { email });
|
|
546
570
|
}
|
|
571
|
+
async forgotPassword(email) {
|
|
572
|
+
return this.request("POST", ENDPOINTS.AUTH_FORGOT_PASSWORD, { email });
|
|
573
|
+
}
|
|
574
|
+
async resetPassword(token, newPassword) {
|
|
575
|
+
return this.request("POST", ENDPOINTS.AUTH_RESET_PASSWORD, { token, new_password: newPassword });
|
|
576
|
+
}
|
|
547
577
|
// ---- WebSocket ----
|
|
548
578
|
async getWsTicket() {
|
|
549
579
|
return this.request("POST", ENDPOINTS.WS_TICKET);
|
|
@@ -740,9 +770,6 @@ var ParallClient = class _ParallClient {
|
|
|
740
770
|
async deleteMessage(id) {
|
|
741
771
|
return this.request("DELETE", ENDPOINTS.MESSAGE(id));
|
|
742
772
|
}
|
|
743
|
-
async patchMessage(id, req) {
|
|
744
|
-
return this.request("POST", ENDPOINTS.MESSAGE_PATCHES(id), req);
|
|
745
|
-
}
|
|
746
773
|
async getMessageReplies(id, params) {
|
|
747
774
|
return this.request("GET", ENDPOINTS.MESSAGE_REPLIES(id), void 0, params);
|
|
748
775
|
}
|
|
@@ -753,8 +780,8 @@ var ParallClient = class _ParallClient {
|
|
|
753
780
|
async completeUpload(orgId, attachmentId) {
|
|
754
781
|
return this.request("POST", ENDPOINTS.UPLOAD_COMPLETE(orgId), { attachment_id: attachmentId });
|
|
755
782
|
}
|
|
756
|
-
async getFileUrl(id) {
|
|
757
|
-
return this.request("GET", ENDPOINTS.FILE(id));
|
|
783
|
+
async getFileUrl(id, opts) {
|
|
784
|
+
return this.request("GET", ENDPOINTS.FILE(id), void 0, opts?.download ? { download: true } : void 0);
|
|
758
785
|
}
|
|
759
786
|
// ---- Approvals ----
|
|
760
787
|
async getApproval(id) {
|
|
@@ -835,8 +862,7 @@ var ParallClient = class _ParallClient {
|
|
|
835
862
|
* @param params.status - Comma-separated status filter (e.g., `'open'`).
|
|
836
863
|
*/
|
|
837
864
|
async getAgentSessions(orgId, agentId, params) {
|
|
838
|
-
|
|
839
|
-
return res.data;
|
|
865
|
+
return this.request("GET", ENDPOINTS.AGENT_SESSIONS(orgId, agentId), void 0, params);
|
|
840
866
|
}
|
|
841
867
|
async getAgentSession(orgId, agentId, sessionId) {
|
|
842
868
|
return this.request("GET", ENDPOINTS.AGENT_SESSION(orgId, agentId, sessionId));
|
|
@@ -848,12 +874,14 @@ var ParallClient = class _ParallClient {
|
|
|
848
874
|
return this.request("POST", ENDPOINTS.AGENT_SESSION_STEPS(orgId, agentId, sessionId), req);
|
|
849
875
|
}
|
|
850
876
|
async getAgentSessionSteps(orgId, agentId, sessionId, params) {
|
|
851
|
-
|
|
852
|
-
return res.data;
|
|
877
|
+
return this.request("GET", ENDPOINTS.AGENT_SESSION_STEPS(orgId, agentId, sessionId), void 0, params);
|
|
853
878
|
}
|
|
854
879
|
async getAgentSessionStep(orgId, agentId, sessionId, stepId) {
|
|
855
880
|
return this.request("GET", ENDPOINTS.AGENT_SESSION_STEP(orgId, agentId, sessionId, stepId));
|
|
856
881
|
}
|
|
882
|
+
async getAgentStepById(orgId, stepId) {
|
|
883
|
+
return this.request("GET", ENDPOINTS.AGENT_STEP_BY_ID(orgId, stepId));
|
|
884
|
+
}
|
|
857
885
|
// ---- Agent runtime auth (hosted Claude OAuth) ----
|
|
858
886
|
async getAgentRuntimeAuth(orgId, agentId) {
|
|
859
887
|
return this.request("GET", ENDPOINTS.AGENT_RUNTIME_AUTH(orgId, agentId));
|
|
@@ -950,10 +978,32 @@ var ParallClient = class _ParallClient {
|
|
|
950
978
|
async detachAgent(orgId, machineId, agentId) {
|
|
951
979
|
return this.request("DELETE", ENDPOINTS.MACHINE_DETACH_AGENT(orgId, machineId, agentId));
|
|
952
980
|
}
|
|
981
|
+
async listAgentWorkspaceStates(orgId, machineId) {
|
|
982
|
+
const res = await this.request("GET", ENDPOINTS.MACHINE_WORKSPACE_STATES(orgId, machineId));
|
|
983
|
+
return res.data;
|
|
984
|
+
}
|
|
985
|
+
async patchAgentDaemonConfig(orgId, machineId, agentId, daemonConfig) {
|
|
986
|
+
return this.request("PATCH", ENDPOINTS.MACHINE_AGENT_DAEMON_CONFIG(orgId, machineId, agentId), { daemon_config: daemonConfig });
|
|
987
|
+
}
|
|
988
|
+
async retryAgentWorkspaceSetup(orgId, machineId, agentId) {
|
|
989
|
+
return this.request("POST", ENDPOINTS.MACHINE_AGENT_WORKSPACE_SETUP(orgId, machineId, agentId));
|
|
990
|
+
}
|
|
991
|
+
async patchMachineLLMSource(orgId, machineId, llmSource2) {
|
|
992
|
+
return this.request("PATCH", ENDPOINTS.MACHINE_LLM_SOURCE(orgId, machineId), { llm_source: llmSource2 });
|
|
993
|
+
}
|
|
953
994
|
/** Get machine-level runtime auth state. */
|
|
954
995
|
async getMachineRuntimeAuth(orgId, machineId) {
|
|
955
996
|
return this.request("GET", ENDPOINTS.MACHINE_RUNTIME_AUTH(orgId, machineId));
|
|
956
997
|
}
|
|
998
|
+
async startMachineRuntimeAuthSession(orgId, machineId, req = {}) {
|
|
999
|
+
return this.request("POST", ENDPOINTS.MACHINE_RUNTIME_AUTH_SESSIONS(orgId, machineId), req);
|
|
1000
|
+
}
|
|
1001
|
+
async completeMachineRuntimeAuthSession(orgId, machineId, sessionId, req) {
|
|
1002
|
+
return this.request("POST", ENDPOINTS.MACHINE_RUNTIME_AUTH_SESSION_COMPLETE(orgId, machineId, sessionId), req);
|
|
1003
|
+
}
|
|
1004
|
+
async disconnectMachineRuntimeAuth(orgId, machineId) {
|
|
1005
|
+
return this.request("DELETE", ENDPOINTS.MACHINE_RUNTIME_AUTH(orgId, machineId));
|
|
1006
|
+
}
|
|
957
1007
|
// ---- Machine self-control-plane (mck_-scoped) ----
|
|
958
1008
|
//
|
|
959
1009
|
// The four methods below are intended to be called from a daemon-mode
|
|
@@ -983,6 +1033,12 @@ var ParallClient = class _ParallClient {
|
|
|
983
1033
|
async postMachineHeartbeat() {
|
|
984
1034
|
return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH);
|
|
985
1035
|
}
|
|
1036
|
+
async reportAgentWorkspaceState(agentId, state) {
|
|
1037
|
+
const res = await this.request("PUT", ENDPOINTS.MACHINES_ME_AGENT_WORKSPACE_STATE(agentId), state);
|
|
1038
|
+
if (res?.status === "ignored_stale") {
|
|
1039
|
+
throw new ApiError(409, "Workspace state report ignored as stale", "STALE_WORKSPACE_STATE");
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
986
1042
|
/**
|
|
987
1043
|
* `POST /machines/me/agents/{agentId}/launch-credential` — mint a
|
|
988
1044
|
* short-lived `agk_*` for one of this Machine's attached agents. The
|
|
@@ -1132,6 +1188,20 @@ var ParallClient = class _ParallClient {
|
|
|
1132
1188
|
const res = await this.request("GET", ENDPOINTS.TASK_WATCHERS(orgId, taskId));
|
|
1133
1189
|
return res.data;
|
|
1134
1190
|
}
|
|
1191
|
+
async watchThread(threadRootId) {
|
|
1192
|
+
return this.request("POST", ENDPOINTS.MESSAGE_WATCH(threadRootId));
|
|
1193
|
+
}
|
|
1194
|
+
async unwatchThread(threadRootId) {
|
|
1195
|
+
return this.request("DELETE", ENDPOINTS.MESSAGE_WATCH(threadRootId));
|
|
1196
|
+
}
|
|
1197
|
+
async getThreadWatchers(threadRootId) {
|
|
1198
|
+
const res = await this.request("GET", ENDPOINTS.MESSAGE_WATCHERS(threadRootId));
|
|
1199
|
+
return res.data;
|
|
1200
|
+
}
|
|
1201
|
+
async isWatchingThread(threadRootId) {
|
|
1202
|
+
const res = await this.request("GET", ENDPOINTS.MESSAGE_WATCHING(threadRootId));
|
|
1203
|
+
return res.watching;
|
|
1204
|
+
}
|
|
1135
1205
|
async getSubtasks(orgId, taskId) {
|
|
1136
1206
|
const res = await this.request("GET", ENDPOINTS.TASK_SUBTASKS(orgId, taskId));
|
|
1137
1207
|
return res.data;
|
|
@@ -1334,8 +1404,8 @@ var ParallClient = class _ParallClient {
|
|
|
1334
1404
|
async deleteWikiPathScope(orgId, wikiId, scopeId) {
|
|
1335
1405
|
await this.request("DELETE", ENDPOINTS.WIKI_PATH_SCOPE(orgId, wikiId, scopeId));
|
|
1336
1406
|
}
|
|
1337
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
1338
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
1407
|
+
async getWikiAccessStatus(orgId, wikiId, path6) {
|
|
1408
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path6 ? { path: path6 } : void 0);
|
|
1339
1409
|
}
|
|
1340
1410
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
1341
1411
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -1344,11 +1414,11 @@ var ParallClient = class _ParallClient {
|
|
|
1344
1414
|
async getWikiCommits(orgId, wikiId, params) {
|
|
1345
1415
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
1346
1416
|
}
|
|
1347
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
1348
|
-
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path:
|
|
1417
|
+
async getWikiFileCommits(orgId, wikiId, path6, params) {
|
|
1418
|
+
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path: path6, ...params });
|
|
1349
1419
|
}
|
|
1350
|
-
async getWikiBlame(orgId, wikiId,
|
|
1351
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
1420
|
+
async getWikiBlame(orgId, wikiId, path6, ref) {
|
|
1421
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path6, ref });
|
|
1352
1422
|
}
|
|
1353
1423
|
// ---- Wiki Operations (audit log) ----
|
|
1354
1424
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -1428,14 +1498,28 @@ var ParallClient = class _ParallClient {
|
|
|
1428
1498
|
params.set("machine_id", opts.machine_id);
|
|
1429
1499
|
if (opts?.unresolved_machine)
|
|
1430
1500
|
params.set("unresolved_machine", "true");
|
|
1501
|
+
if (opts?.from)
|
|
1502
|
+
params.set("from", opts.from);
|
|
1503
|
+
if (opts?.to)
|
|
1504
|
+
params.set("to", opts.to);
|
|
1431
1505
|
const qs = params.toString();
|
|
1432
1506
|
return this.request("GET", `${ENDPOINTS.BILLING_TRANSACTIONS(orgId)}${qs ? `?${qs}` : ""}`);
|
|
1433
1507
|
}
|
|
1434
|
-
async listBillingTransactionAgentGroups(orgId) {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1508
|
+
async listBillingTransactionAgentGroups(orgId, opts) {
|
|
1509
|
+
const params = new URLSearchParams({ group_by: "agent" });
|
|
1510
|
+
if (opts?.from)
|
|
1511
|
+
params.set("from", opts.from);
|
|
1512
|
+
if (opts?.to)
|
|
1513
|
+
params.set("to", opts.to);
|
|
1514
|
+
return this.request("GET", `${ENDPOINTS.BILLING_TRANSACTIONS(orgId)}?${params}`);
|
|
1515
|
+
}
|
|
1516
|
+
async listBillingTransactionMachineGroups(orgId, opts) {
|
|
1517
|
+
const params = new URLSearchParams({ group_by: "machine" });
|
|
1518
|
+
if (opts?.from)
|
|
1519
|
+
params.set("from", opts.from);
|
|
1520
|
+
if (opts?.to)
|
|
1521
|
+
params.set("to", opts.to);
|
|
1522
|
+
return this.request("GET", `${ENDPOINTS.BILLING_TRANSACTIONS(orgId)}?${params}`);
|
|
1439
1523
|
}
|
|
1440
1524
|
async createCheckout(orgId, req) {
|
|
1441
1525
|
return this.request("POST", ENDPOINTS.BILLING_CHECKOUT(orgId), req);
|
|
@@ -1486,6 +1570,12 @@ var ApiError = class extends Error {
|
|
|
1486
1570
|
};
|
|
1487
1571
|
|
|
1488
1572
|
// ts/sdk/dist/ws.js
|
|
1573
|
+
function isRetryableNetworkError(err) {
|
|
1574
|
+
return err instanceof Error && err.name === "ApiError" && "status" in err && err.status === 0;
|
|
1575
|
+
}
|
|
1576
|
+
function isBrowserRuntime() {
|
|
1577
|
+
return typeof window !== "undefined";
|
|
1578
|
+
}
|
|
1489
1579
|
var ParallWs = class {
|
|
1490
1580
|
ws = null;
|
|
1491
1581
|
options;
|
|
@@ -1521,7 +1611,11 @@ var ParallWs = class {
|
|
|
1521
1611
|
try {
|
|
1522
1612
|
ticket = await this.options.getTicket();
|
|
1523
1613
|
} catch (err) {
|
|
1524
|
-
|
|
1614
|
+
if (isBrowserRuntime() && isRetryableNetworkError(err)) {
|
|
1615
|
+
console.warn("Failed to get WS ticket:", err);
|
|
1616
|
+
} else {
|
|
1617
|
+
console.error("Failed to get WS ticket:", err);
|
|
1618
|
+
}
|
|
1525
1619
|
if (this.options.reconnect) {
|
|
1526
1620
|
this.scheduleReconnect();
|
|
1527
1621
|
} else {
|
|
@@ -1879,6 +1973,7 @@ function resolveClaudeDaemonConfig(env = process.env) {
|
|
|
1879
1973
|
rootStateDir,
|
|
1880
1974
|
rootClaudeHome,
|
|
1881
1975
|
wsUrl: env.PRLL_WS_URL?.trim() || void 0,
|
|
1976
|
+
swimlaneName: env.PRLL_SWIMLANE_NAME?.trim() || void 0,
|
|
1882
1977
|
pollIntervalMs: parseMs(env.PRLL_DAEMON_POLL_INTERVAL_MS, 3e4),
|
|
1883
1978
|
heartbeatIntervalMs: parseMs(env.PRLL_DAEMON_HEARTBEAT_INTERVAL_MS, 3e4),
|
|
1884
1979
|
restartBackoffMs: parseMs(env.PRLL_DAEMON_RESTART_BACKOFF_MS, 5e3),
|
|
@@ -1910,20 +2005,43 @@ function agentClaudeCredentialsFileFor(agentClaudeHome) {
|
|
|
1910
2005
|
function agentWorkspaceDirFor(rootStateDir, agentId) {
|
|
1911
2006
|
return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
|
|
1912
2007
|
}
|
|
1913
|
-
function resolveWsUrl(apiUrl, explicitWsUrl) {
|
|
1914
|
-
|
|
2008
|
+
function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
|
|
2009
|
+
const base = explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
|
|
2010
|
+
if (!swimlaneName)
|
|
2011
|
+
return base;
|
|
2012
|
+
const url = new URL(base);
|
|
2013
|
+
url.searchParams.set("swimlane", swimlaneName);
|
|
2014
|
+
return url.toString();
|
|
1915
2015
|
}
|
|
1916
2016
|
|
|
1917
2017
|
// ts/daemon/dist/supervisor.js
|
|
1918
|
-
import { spawn } from "node:child_process";
|
|
1919
|
-
import * as
|
|
1920
|
-
import * as
|
|
2018
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2019
|
+
import * as fs3 from "node:fs";
|
|
2020
|
+
import * as path4 from "node:path";
|
|
1921
2021
|
|
|
1922
2022
|
// ts/daemon/dist/runtimes.js
|
|
2023
|
+
import * as path2 from "node:path";
|
|
2024
|
+
function llmSource(pc) {
|
|
2025
|
+
if (pc?.llm_source)
|
|
2026
|
+
return pc.llm_source;
|
|
2027
|
+
if (pc?.openai_api_key || pc?.openai_base_url || pc?.anthropic_auth_token || pc?.anthropic_base_url) {
|
|
2028
|
+
return "custom";
|
|
2029
|
+
}
|
|
2030
|
+
return "parall";
|
|
2031
|
+
}
|
|
2032
|
+
function clearAllProviderCreds(env) {
|
|
2033
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
2034
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
2035
|
+
delete env.ANTHROPIC_API_KEY;
|
|
2036
|
+
delete env.OPENAI_API_KEY;
|
|
2037
|
+
delete env.OPENAI_BASE_URL;
|
|
2038
|
+
delete env.PRLL_CLAUDE_ALLOW_API_KEY;
|
|
2039
|
+
}
|
|
1923
2040
|
var claudeCodeAdapter = {
|
|
1924
2041
|
bin: "parall-claude-agent",
|
|
1925
|
-
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
2042
|
+
buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
|
|
1926
2043
|
const env = { ...baseEnv };
|
|
2044
|
+
clearAllProviderCreds(env);
|
|
1927
2045
|
env.PRLL_API_KEY = apiKey;
|
|
1928
2046
|
env.PRLL_ORG_ID = orgId;
|
|
1929
2047
|
env.AGENT_ID = agentId;
|
|
@@ -1931,8 +2049,18 @@ var claudeCodeAdapter = {
|
|
|
1931
2049
|
env.PRLL_CLAUDE_HOME = dirs.claudeHome;
|
|
1932
2050
|
env.PRLL_CLAUDE_STATE_DIR = dirs.stateDir;
|
|
1933
2051
|
env.PRLL_CLAUDE_WORKSPACE_DIR = dirs.workspaceDir;
|
|
1934
|
-
|
|
2052
|
+
const source = llmSource(pc);
|
|
2053
|
+
if (source === "parall") {
|
|
1935
2054
|
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
2055
|
+
env.ANTHROPIC_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm`;
|
|
2056
|
+
env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
|
|
2057
|
+
} else if (source === "custom") {
|
|
2058
|
+
if (pc?.anthropic_auth_token) {
|
|
2059
|
+
env.ANTHROPIC_AUTH_TOKEN = pc.anthropic_auth_token;
|
|
2060
|
+
env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
|
|
2061
|
+
}
|
|
2062
|
+
if (pc?.anthropic_base_url)
|
|
2063
|
+
env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
|
|
1936
2064
|
}
|
|
1937
2065
|
delete env.PRLL_DAEMON_MODE;
|
|
1938
2066
|
return env;
|
|
@@ -1940,14 +2068,26 @@ var claudeCodeAdapter = {
|
|
|
1940
2068
|
};
|
|
1941
2069
|
var codexAdapter = {
|
|
1942
2070
|
bin: "parall-codex-agent",
|
|
1943
|
-
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
2071
|
+
buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
|
|
1944
2072
|
const env = { ...baseEnv };
|
|
2073
|
+
clearAllProviderCreds(env);
|
|
1945
2074
|
env.PRLL_API_KEY = apiKey;
|
|
1946
2075
|
env.PRLL_ORG_ID = orgId;
|
|
1947
2076
|
env.AGENT_ID = agentId;
|
|
1948
2077
|
env.PRLL_AGENT_ID = agentId;
|
|
1949
2078
|
env.PRLL_CODEX_STATE_DIR = dirs.stateDir;
|
|
1950
2079
|
env.PRLL_CODEX_WORKSPACE_DIR = dirs.workspaceDir;
|
|
2080
|
+
env.PRLL_CODEX_HOME = path2.join(dirs.stateDir, ".codex");
|
|
2081
|
+
const source = llmSource(pc);
|
|
2082
|
+
if (source === "parall") {
|
|
2083
|
+
env.OPENAI_API_KEY = apiKey;
|
|
2084
|
+
env.OPENAI_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm/v1`;
|
|
2085
|
+
} else if (source === "custom") {
|
|
2086
|
+
if (pc?.openai_api_key)
|
|
2087
|
+
env.OPENAI_API_KEY = pc.openai_api_key;
|
|
2088
|
+
if (pc?.openai_base_url)
|
|
2089
|
+
env.OPENAI_BASE_URL = pc.openai_base_url;
|
|
2090
|
+
}
|
|
1951
2091
|
delete env.PRLL_DAEMON_MODE;
|
|
1952
2092
|
return env;
|
|
1953
2093
|
}
|
|
@@ -1970,6 +2110,7 @@ var openclawAdapter = {
|
|
|
1970
2110
|
bin: "parall-openclaw-agent",
|
|
1971
2111
|
buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
|
|
1972
2112
|
const env = { ...baseEnv };
|
|
2113
|
+
clearAllProviderCreds(env);
|
|
1973
2114
|
env.PRLL_API_KEY = apiKey;
|
|
1974
2115
|
env.PRLL_ORG_ID = orgId;
|
|
1975
2116
|
env.AGENT_ID = agentId;
|
|
@@ -1995,23 +2136,448 @@ function assertAgentKey(apiKey) {
|
|
|
1995
2136
|
}
|
|
1996
2137
|
}
|
|
1997
2138
|
|
|
2139
|
+
// ts/daemon/dist/workspace.js
|
|
2140
|
+
import { spawn } from "node:child_process";
|
|
2141
|
+
import { createHash } from "node:crypto";
|
|
2142
|
+
import * as fs2 from "node:fs";
|
|
2143
|
+
import * as path3 from "node:path";
|
|
2144
|
+
var OUTPUT_TAIL_LIMIT = 32 * 1024;
|
|
2145
|
+
var DEFAULT_SETUP_TIMEOUT_SEC = 600;
|
|
2146
|
+
async function prepareWorkspace(opts) {
|
|
2147
|
+
const prior = opts.attached.workspace_state;
|
|
2148
|
+
const plan = buildWorkspacePlan(opts.attached.daemon_config, opts.defaultWorkspaceDir, prior?.config_hash);
|
|
2149
|
+
let forceSetup = false;
|
|
2150
|
+
if (prior?.status === "ready" && prior.config_hash === plan.configHash) {
|
|
2151
|
+
if (await verifyExistingWorkspace(plan, opts.log)) {
|
|
2152
|
+
return plan.workspaceDir;
|
|
2153
|
+
}
|
|
2154
|
+
forceSetup = true;
|
|
2155
|
+
}
|
|
2156
|
+
await opts.client.reportAgentWorkspaceState(opts.agentId, {
|
|
2157
|
+
config_hash: plan.configHash,
|
|
2158
|
+
status: "preparing"
|
|
2159
|
+
});
|
|
2160
|
+
let outputTail = "";
|
|
2161
|
+
try {
|
|
2162
|
+
await ensureWorkspace(plan, opts.log);
|
|
2163
|
+
if (shouldRunSetup(plan.workspace.setup, prior, plan.configHash, forceSetup)) {
|
|
2164
|
+
outputTail = await runSetup(plan.workspaceDir, plan.workspace.setup.command, plan.workspace.setup?.timeout_sec);
|
|
2165
|
+
}
|
|
2166
|
+
await opts.client.reportAgentWorkspaceState(opts.agentId, {
|
|
2167
|
+
config_hash: plan.configHash,
|
|
2168
|
+
status: "ready",
|
|
2169
|
+
output_tail: outputTail || null
|
|
2170
|
+
});
|
|
2171
|
+
return plan.workspaceDir;
|
|
2172
|
+
} catch (err) {
|
|
2173
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2174
|
+
await opts.client.reportAgentWorkspaceState(opts.agentId, {
|
|
2175
|
+
config_hash: plan.configHash,
|
|
2176
|
+
status: "failed",
|
|
2177
|
+
last_error: message,
|
|
2178
|
+
output_tail: outputTail || null
|
|
2179
|
+
}).catch((reportErr) => {
|
|
2180
|
+
opts.log.warn(`workspace state report failed after setup error: ${String(reportErr)}`);
|
|
2181
|
+
});
|
|
2182
|
+
throw err;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
function buildWorkspacePlan(input, defaultWorkspaceDir, serverConfigHash) {
|
|
2186
|
+
const config = normalizeConfig(input, defaultWorkspaceDir);
|
|
2187
|
+
const workspace = config.workspace;
|
|
2188
|
+
const { workspaceDir, customWorkspaceField } = resolveWorkspaceDir(workspace, defaultWorkspaceDir);
|
|
2189
|
+
const configHash = serverConfigHash || createHash("sha256").update(JSON.stringify(config)).digest("hex");
|
|
2190
|
+
return { config, workspace, workspaceDir, defaultWorkspaceDir, customWorkspaceField, configHash };
|
|
2191
|
+
}
|
|
2192
|
+
function normalizeConfig(input, defaultWorkspaceDir) {
|
|
2193
|
+
const cfg = input ? JSON.parse(JSON.stringify(input)) : {};
|
|
2194
|
+
cfg.workspace_path = cfg.workspace_path?.trim() || void 0;
|
|
2195
|
+
if (!cfg.workspace && cfg.workspace_path) {
|
|
2196
|
+
cfg.workspace = { mode: "local_path", path: cfg.workspace_path };
|
|
2197
|
+
}
|
|
2198
|
+
if (!cfg.workspace) {
|
|
2199
|
+
cfg.workspace = { mode: "default" };
|
|
2200
|
+
}
|
|
2201
|
+
const ws = cfg.workspace;
|
|
2202
|
+
ws.mode = ws.mode?.trim() || void 0;
|
|
2203
|
+
ws.path = ws.path?.trim() || void 0;
|
|
2204
|
+
if (ws.git) {
|
|
2205
|
+
ws.git.remote = ws.git.remote?.trim() || void 0;
|
|
2206
|
+
ws.git.ref = ws.git.ref?.trim() || void 0;
|
|
2207
|
+
ws.git.target_path = ws.git.target_path?.trim() || void 0;
|
|
2208
|
+
}
|
|
2209
|
+
ws.mode ||= ws.git ? "git" : ws.path ? "local_path" : "default";
|
|
2210
|
+
if (ws.mode === "local_path" && !ws.path) {
|
|
2211
|
+
ws.mode = "default";
|
|
2212
|
+
}
|
|
2213
|
+
if (ws.mode === "git") {
|
|
2214
|
+
ws.git ||= {};
|
|
2215
|
+
if (!ws.git.target_path && ws.path) {
|
|
2216
|
+
ws.git.target_path = ws.path;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
if (ws.mode === "default") {
|
|
2220
|
+
ws.path = void 0;
|
|
2221
|
+
ws.git = void 0;
|
|
2222
|
+
}
|
|
2223
|
+
if (ws.mode === "local_path") {
|
|
2224
|
+
ws.git = void 0;
|
|
2225
|
+
}
|
|
2226
|
+
if (ws.setup) {
|
|
2227
|
+
ws.setup.command = ws.setup.command?.trim();
|
|
2228
|
+
ws.setup.run_on ||= "first_attach";
|
|
2229
|
+
if (!ws.setup.command) {
|
|
2230
|
+
ws.setup = void 0;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
void defaultWorkspaceDir;
|
|
2234
|
+
return cfg;
|
|
2235
|
+
}
|
|
2236
|
+
function resolveWorkspaceDir(workspace, defaultWorkspaceDir) {
|
|
2237
|
+
if (workspace.mode === "local_path") {
|
|
2238
|
+
if (!workspace.path)
|
|
2239
|
+
return { workspaceDir: defaultWorkspaceDir };
|
|
2240
|
+
return {
|
|
2241
|
+
workspaceDir: requireAbsolute(workspace.path, "workspace.path"),
|
|
2242
|
+
customWorkspaceField: "workspace.path"
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
if (workspace.mode === "git") {
|
|
2246
|
+
return workspace.git?.target_path ? {
|
|
2247
|
+
workspaceDir: requireAbsolute(workspace.git.target_path, "workspace.git.target_path"),
|
|
2248
|
+
customWorkspaceField: "workspace.git.target_path"
|
|
2249
|
+
} : { workspaceDir: defaultWorkspaceDir };
|
|
2250
|
+
}
|
|
2251
|
+
return { workspaceDir: defaultWorkspaceDir };
|
|
2252
|
+
}
|
|
2253
|
+
async function ensureWorkspace(plan, log2) {
|
|
2254
|
+
const ws = plan.workspace;
|
|
2255
|
+
if (ws.mode === "default") {
|
|
2256
|
+
fs2.mkdirSync(plan.workspaceDir, { recursive: true });
|
|
2257
|
+
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
if (ws.mode === "local_path") {
|
|
2261
|
+
assertSafeCustomWorkspacePath(plan);
|
|
2262
|
+
let st;
|
|
2263
|
+
try {
|
|
2264
|
+
st = fs2.statSync(plan.workspaceDir);
|
|
2265
|
+
} catch (err) {
|
|
2266
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
2267
|
+
throw new Error(`workspace path does not exist: ${plan.workspaceDir}`);
|
|
2268
|
+
}
|
|
2269
|
+
throw err;
|
|
2270
|
+
}
|
|
2271
|
+
if (!st.isDirectory()) {
|
|
2272
|
+
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2273
|
+
}
|
|
2274
|
+
assertSafeCustomWorkspacePath(plan, fs2.realpathSync(plan.workspaceDir));
|
|
2275
|
+
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (ws.mode === "git") {
|
|
2279
|
+
const remote = ws.git?.remote?.trim();
|
|
2280
|
+
if (!remote)
|
|
2281
|
+
throw new Error("workspace git remote is required");
|
|
2282
|
+
if (plan.customWorkspaceField) {
|
|
2283
|
+
assertSafeCustomWorkspacePath(plan);
|
|
2284
|
+
}
|
|
2285
|
+
if (!fs2.existsSync(plan.workspaceDir)) {
|
|
2286
|
+
fs2.mkdirSync(path3.dirname(plan.workspaceDir), { recursive: true });
|
|
2287
|
+
assertWritableWorkspaceDir(path3.dirname(plan.workspaceDir));
|
|
2288
|
+
await runCommand("git", ["clone", remote, plan.workspaceDir], process.cwd());
|
|
2289
|
+
} else {
|
|
2290
|
+
const st = fs2.statSync(plan.workspaceDir);
|
|
2291
|
+
if (!st.isDirectory()) {
|
|
2292
|
+
throw new Error(`workspace path is not a directory: ${plan.workspaceDir}`);
|
|
2293
|
+
}
|
|
2294
|
+
if (plan.customWorkspaceField) {
|
|
2295
|
+
assertSafeCustomWorkspacePath(plan, fs2.realpathSync(plan.workspaceDir));
|
|
2296
|
+
}
|
|
2297
|
+
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2298
|
+
await ensureGitWorktree(plan.workspaceDir);
|
|
2299
|
+
await ensureGitRemote(plan.workspaceDir, remote);
|
|
2300
|
+
}
|
|
2301
|
+
if (ws.git?.ref) {
|
|
2302
|
+
const dirty = await commandOutput("git", ["status", "--porcelain"], plan.workspaceDir);
|
|
2303
|
+
if (dirty.trim()) {
|
|
2304
|
+
throw new Error(`workspace git tree has local changes; refusing checkout: ${plan.workspaceDir}`);
|
|
2305
|
+
}
|
|
2306
|
+
await runCommand("git", ["fetch", "origin", "--prune"], plan.workspaceDir);
|
|
2307
|
+
await checkoutGitRef(plan.workspaceDir, ws.git.ref);
|
|
2308
|
+
}
|
|
2309
|
+
log2.info(`workspace ready: git ${remote} -> ${plan.workspaceDir}`);
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
throw new Error(`unsupported workspace mode: ${ws.mode ?? "(empty)"}`);
|
|
2313
|
+
}
|
|
2314
|
+
async function verifyExistingWorkspace(plan, log2) {
|
|
2315
|
+
try {
|
|
2316
|
+
if (plan.customWorkspaceField) {
|
|
2317
|
+
assertSafeCustomWorkspacePath(plan);
|
|
2318
|
+
}
|
|
2319
|
+
const st = fs2.statSync(plan.workspaceDir);
|
|
2320
|
+
if (!st.isDirectory()) {
|
|
2321
|
+
log2.warn(`workspace ready state ignored: path is not a directory: ${plan.workspaceDir}`);
|
|
2322
|
+
return false;
|
|
2323
|
+
}
|
|
2324
|
+
if (plan.customWorkspaceField) {
|
|
2325
|
+
assertSafeCustomWorkspacePath(plan, fs2.realpathSync(plan.workspaceDir));
|
|
2326
|
+
}
|
|
2327
|
+
assertWritableWorkspaceDir(plan.workspaceDir);
|
|
2328
|
+
if (plan.workspace.mode === "git") {
|
|
2329
|
+
await ensureGitWorktree(plan.workspaceDir);
|
|
2330
|
+
const remote = plan.workspace.git?.remote?.trim();
|
|
2331
|
+
if (remote) {
|
|
2332
|
+
await ensureGitRemote(plan.workspaceDir, remote);
|
|
2333
|
+
}
|
|
2334
|
+
if (plan.workspace.git?.ref) {
|
|
2335
|
+
const head = (await commandOutput("git", ["rev-parse", "HEAD"], plan.workspaceDir)).trim();
|
|
2336
|
+
const expected = await resolveConfiguredGitRef(plan.workspaceDir, plan.workspace.git.ref);
|
|
2337
|
+
if (head !== expected) {
|
|
2338
|
+
log2.warn(`workspace ready state ignored: git HEAD ${head} does not match ${plan.workspace.git.ref} (${expected})`);
|
|
2339
|
+
return false;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
return true;
|
|
2344
|
+
} catch (err) {
|
|
2345
|
+
log2.warn(`workspace ready state ignored: ${String(err)}`);
|
|
2346
|
+
return false;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
async function ensureGitWorktree(cwd) {
|
|
2350
|
+
const result = await commandOutput("git", ["rev-parse", "--is-inside-work-tree"], cwd);
|
|
2351
|
+
if (result.trim() !== "true") {
|
|
2352
|
+
throw new Error(`workspace path exists but is not a git worktree: ${cwd}`);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
async function ensureGitRemote(cwd, expectedRemote) {
|
|
2356
|
+
const actualRemote = (await commandOutput("git", ["remote", "get-url", "origin"], cwd)).trim();
|
|
2357
|
+
if (actualRemote !== expectedRemote) {
|
|
2358
|
+
throw new Error(`workspace git remote mismatch: expected ${expectedRemote}, got ${actualRemote}`);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
async function checkoutGitRef(cwd, ref) {
|
|
2362
|
+
const remoteCommit = await resolveRemoteGitRef(cwd, ref);
|
|
2363
|
+
if (remoteCommit) {
|
|
2364
|
+
await runCommand("git", ["checkout", "-B", ref, remoteCommit], cwd);
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
const targetCommit = await resolveGitCommit(cwd, ref);
|
|
2368
|
+
await runCommand("git", ["checkout", "--detach", targetCommit], cwd);
|
|
2369
|
+
}
|
|
2370
|
+
async function resolveConfiguredGitRef(cwd, ref) {
|
|
2371
|
+
const remote = await resolveRemoteGitRef(cwd, ref);
|
|
2372
|
+
return remote || await resolveGitCommit(cwd, ref);
|
|
2373
|
+
}
|
|
2374
|
+
async function resolveRemoteGitRef(cwd, ref) {
|
|
2375
|
+
return (await tryGitOutput("git", ["rev-parse", "--verify", `refs/remotes/origin/${ref}^{commit}`], cwd)).trim();
|
|
2376
|
+
}
|
|
2377
|
+
async function resolveGitCommit(cwd, ref) {
|
|
2378
|
+
const candidates = [`refs/tags/${ref}^{commit}`, `${ref}^{commit}`];
|
|
2379
|
+
for (const candidate of candidates) {
|
|
2380
|
+
const commit = await tryGitOutput("git", ["rev-parse", "--verify", candidate], cwd);
|
|
2381
|
+
if (commit.trim()) {
|
|
2382
|
+
return commit.trim();
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
throw new Error(`workspace git ref not found: ${ref}`);
|
|
2386
|
+
}
|
|
2387
|
+
function shouldRunSetup(setup, prior, configHash, forceSetup = false) {
|
|
2388
|
+
if (!setup?.command)
|
|
2389
|
+
return false;
|
|
2390
|
+
if (forceSetup)
|
|
2391
|
+
return true;
|
|
2392
|
+
if (setup.run_on === "config_change") {
|
|
2393
|
+
return prior?.config_hash !== configHash || prior.status === "pending" || prior.status === "failed";
|
|
2394
|
+
}
|
|
2395
|
+
return prior?.status !== "ready" || !prior.prepared_at;
|
|
2396
|
+
}
|
|
2397
|
+
async function runSetup(cwd, command, timeoutSec) {
|
|
2398
|
+
const effectiveTimeoutSec = timeoutSec && timeoutSec > 0 ? timeoutSec : DEFAULT_SETUP_TIMEOUT_SEC;
|
|
2399
|
+
return runCommand(process.env.SHELL || "/bin/sh", ["-lc", command], cwd, effectiveTimeoutSec * 1e3, scrubSetupEnv());
|
|
2400
|
+
}
|
|
2401
|
+
function scrubSetupEnv() {
|
|
2402
|
+
const env = { ...process.env };
|
|
2403
|
+
clearAllProviderCreds(env);
|
|
2404
|
+
delete env.PRLL_API_KEY;
|
|
2405
|
+
return env;
|
|
2406
|
+
}
|
|
2407
|
+
async function commandOutput(cmd, args, cwd) {
|
|
2408
|
+
return runCommand(cmd, args, cwd);
|
|
2409
|
+
}
|
|
2410
|
+
async function tryGitOutput(cmd, args, cwd) {
|
|
2411
|
+
try {
|
|
2412
|
+
return await runCommand(cmd, args, cwd);
|
|
2413
|
+
} catch {
|
|
2414
|
+
return "";
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
function runCommand(cmd, args, cwd, timeoutMs = 12e4, env = process.env) {
|
|
2418
|
+
return new Promise((resolve4, reject) => {
|
|
2419
|
+
let tail = "";
|
|
2420
|
+
let timedOut = false;
|
|
2421
|
+
let settled = false;
|
|
2422
|
+
let timeoutTimer = null;
|
|
2423
|
+
let killTimer = null;
|
|
2424
|
+
const append = (chunk) => {
|
|
2425
|
+
tail += chunk.toString();
|
|
2426
|
+
if (tail.length > OUTPUT_TAIL_LIMIT) {
|
|
2427
|
+
tail = tail.slice(-OUTPUT_TAIL_LIMIT);
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
const settle = (fn) => {
|
|
2431
|
+
if (settled)
|
|
2432
|
+
return;
|
|
2433
|
+
settled = true;
|
|
2434
|
+
if (timeoutTimer)
|
|
2435
|
+
clearTimeout(timeoutTimer);
|
|
2436
|
+
if (killTimer)
|
|
2437
|
+
clearTimeout(killTimer);
|
|
2438
|
+
fn();
|
|
2439
|
+
};
|
|
2440
|
+
const child = spawn(cmd, args, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
2441
|
+
timeoutTimer = setTimeout(() => {
|
|
2442
|
+
timedOut = true;
|
|
2443
|
+
child.kill("SIGTERM");
|
|
2444
|
+
killTimer = setTimeout(() => {
|
|
2445
|
+
child.kill("SIGKILL");
|
|
2446
|
+
}, 5e3);
|
|
2447
|
+
}, timeoutMs);
|
|
2448
|
+
child.stdout?.on("data", append);
|
|
2449
|
+
child.stderr?.on("data", append);
|
|
2450
|
+
child.once("error", (err) => {
|
|
2451
|
+
settle(() => reject(err));
|
|
2452
|
+
});
|
|
2453
|
+
child.once("close", (code, signal) => {
|
|
2454
|
+
if (timedOut) {
|
|
2455
|
+
settle(() => reject(new Error(`command timed out after ${timeoutMs}ms: ${cmd} ${args.join(" ")}
|
|
2456
|
+
${tail}`)));
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
if (code === 0) {
|
|
2460
|
+
settle(() => resolve4(tail));
|
|
2461
|
+
} else {
|
|
2462
|
+
settle(() => reject(new Error(`command failed (${code ?? signal}): ${cmd} ${args.join(" ")}
|
|
2463
|
+
${tail}`)));
|
|
2464
|
+
}
|
|
2465
|
+
});
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
function requireAbsolute(value, field) {
|
|
2469
|
+
if (!value || !path3.isAbsolute(value)) {
|
|
2470
|
+
throw new Error(`${field} must be an absolute path`);
|
|
2471
|
+
}
|
|
2472
|
+
return path3.resolve(value);
|
|
2473
|
+
}
|
|
2474
|
+
function assertSafeCustomWorkspacePath(plan, candidate = plan.workspaceDir) {
|
|
2475
|
+
if (!plan.customWorkspaceField)
|
|
2476
|
+
return;
|
|
2477
|
+
const normalized = requireAbsolute(candidate, plan.customWorkspaceField);
|
|
2478
|
+
const reason = workspacePathDenyReason(toPolicyPath(normalized));
|
|
2479
|
+
if (reason) {
|
|
2480
|
+
throw new Error(`${plan.customWorkspaceField} must point to a project folder, not ${reason}: ${normalized}`);
|
|
2481
|
+
}
|
|
2482
|
+
const defaultWorkspace = path3.resolve(plan.defaultWorkspaceDir);
|
|
2483
|
+
if (isAncestorPath(normalized, defaultWorkspace) || normalized === defaultWorkspace) {
|
|
2484
|
+
throw new Error(`${plan.customWorkspaceField} must not point at daemon state directories: ${normalized}`);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
function assertWritableWorkspaceDir(dir) {
|
|
2488
|
+
fs2.accessSync(dir, fs2.constants.R_OK | fs2.constants.W_OK | fs2.constants.X_OK);
|
|
2489
|
+
const probe = path3.join(dir, `.parall-workspace-check-${process.pid}-${Date.now()}`);
|
|
2490
|
+
const fd = fs2.openSync(probe, "wx", 384);
|
|
2491
|
+
fs2.closeSync(fd);
|
|
2492
|
+
fs2.unlinkSync(probe);
|
|
2493
|
+
}
|
|
2494
|
+
function workspacePathDenyReason(value) {
|
|
2495
|
+
if (value === "/")
|
|
2496
|
+
return "the filesystem root";
|
|
2497
|
+
if (["/tmp", "/private/tmp", "/var/tmp", "/Users", "/home"].includes(value)) {
|
|
2498
|
+
return "a shared or home root directory";
|
|
2499
|
+
}
|
|
2500
|
+
for (const root of [
|
|
2501
|
+
"/Applications",
|
|
2502
|
+
"/bin",
|
|
2503
|
+
"/boot",
|
|
2504
|
+
"/dev",
|
|
2505
|
+
"/etc",
|
|
2506
|
+
"/Library",
|
|
2507
|
+
"/private/etc",
|
|
2508
|
+
"/private/var/db",
|
|
2509
|
+
"/proc",
|
|
2510
|
+
"/root",
|
|
2511
|
+
"/run",
|
|
2512
|
+
"/sbin",
|
|
2513
|
+
"/System",
|
|
2514
|
+
"/sys",
|
|
2515
|
+
"/usr",
|
|
2516
|
+
"/var/db",
|
|
2517
|
+
"/var/root"
|
|
2518
|
+
]) {
|
|
2519
|
+
if (value === root || value.startsWith(`${root}/`)) {
|
|
2520
|
+
return "a system directory";
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
const parts = value.split("/").filter(Boolean);
|
|
2524
|
+
if (parts.includes(".git"))
|
|
2525
|
+
return "a git metadata directory";
|
|
2526
|
+
if (parts.length === 2 && (parts[0] === "Users" || parts[0] === "home")) {
|
|
2527
|
+
return "a home directory";
|
|
2528
|
+
}
|
|
2529
|
+
if (parts.length >= 3 && (parts[0] === "Users" || parts[0] === "home")) {
|
|
2530
|
+
const homeChild = parts[2];
|
|
2531
|
+
if ([
|
|
2532
|
+
".aws",
|
|
2533
|
+
".azure",
|
|
2534
|
+
".claude",
|
|
2535
|
+
".codex",
|
|
2536
|
+
".config",
|
|
2537
|
+
".docker",
|
|
2538
|
+
".gnupg",
|
|
2539
|
+
".kube",
|
|
2540
|
+
".local",
|
|
2541
|
+
".npm",
|
|
2542
|
+
".ssh",
|
|
2543
|
+
".parall-agent",
|
|
2544
|
+
".parall-daemon",
|
|
2545
|
+
"Library"
|
|
2546
|
+
].includes(homeChild)) {
|
|
2547
|
+
return "a credential or application state directory";
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return "";
|
|
2551
|
+
}
|
|
2552
|
+
function isAncestorPath(parent, child) {
|
|
2553
|
+
const relative2 = path3.relative(parent, child);
|
|
2554
|
+
return relative2 !== "" && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
|
|
2555
|
+
}
|
|
2556
|
+
function toPolicyPath(value) {
|
|
2557
|
+
return path3.resolve(value).split(path3.sep).join("/");
|
|
2558
|
+
}
|
|
2559
|
+
function isNodeError(err) {
|
|
2560
|
+
return err instanceof Error && "code" in err;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
1998
2563
|
// ts/daemon/dist/supervisor.js
|
|
1999
2564
|
var RUNTIME_PACKAGES = {
|
|
2000
2565
|
"claude-code": "@parall/claude-agent",
|
|
2001
2566
|
"codex": "@parall/codex-agent",
|
|
2002
2567
|
"openclaw": "@parall/openclaw-agent"
|
|
2003
2568
|
};
|
|
2569
|
+
var WORKSPACE_SETUP_RETRY_DELAY_MS = 5e3;
|
|
2004
2570
|
function sleepCancellable(ms, signal) {
|
|
2005
2571
|
if (signal.aborted)
|
|
2006
2572
|
return Promise.resolve(false);
|
|
2007
|
-
return new Promise((
|
|
2573
|
+
return new Promise((resolve4) => {
|
|
2008
2574
|
const timer = setTimeout(() => {
|
|
2009
2575
|
signal.removeEventListener("abort", onAbort);
|
|
2010
|
-
|
|
2576
|
+
resolve4(true);
|
|
2011
2577
|
}, ms);
|
|
2012
2578
|
const onAbort = () => {
|
|
2013
2579
|
clearTimeout(timer);
|
|
2014
|
-
|
|
2580
|
+
resolve4(false);
|
|
2015
2581
|
};
|
|
2016
2582
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2017
2583
|
});
|
|
@@ -2021,14 +2587,19 @@ var DaemonSupervisor = class {
|
|
|
2021
2587
|
client;
|
|
2022
2588
|
log;
|
|
2023
2589
|
children = /* @__PURE__ */ new Map();
|
|
2590
|
+
spawningAgents = /* @__PURE__ */ new Set();
|
|
2591
|
+
pendingWorkspaceSetup = /* @__PURE__ */ new Set();
|
|
2592
|
+
workspaceSetupRetryTimers = /* @__PURE__ */ new Map();
|
|
2593
|
+
cancelledSpawns = /* @__PURE__ */ new Set();
|
|
2024
2594
|
ws = null;
|
|
2025
2595
|
running = false;
|
|
2026
2596
|
machineOrgId = null;
|
|
2597
|
+
machineLlmSource = "parall";
|
|
2027
2598
|
stopResolve = null;
|
|
2028
|
-
constructor(config, client,
|
|
2599
|
+
constructor(config, client, log2) {
|
|
2029
2600
|
this.config = config;
|
|
2030
2601
|
this.client = client;
|
|
2031
|
-
this.log =
|
|
2602
|
+
this.log = log2;
|
|
2032
2603
|
}
|
|
2033
2604
|
/** Start the supervisor. Returns a promise that resolves on `stop()`. */
|
|
2034
2605
|
async run(signal) {
|
|
@@ -2059,7 +2630,10 @@ var DaemonSupervisor = class {
|
|
|
2059
2630
|
});
|
|
2060
2631
|
this.ws.on("machine.hello", (_data) => {
|
|
2061
2632
|
this.log.info("machine WS connected (machine.hello)");
|
|
2062
|
-
void
|
|
2633
|
+
void (async () => {
|
|
2634
|
+
await this.refreshMachineConfig();
|
|
2635
|
+
await this.fullReconcile();
|
|
2636
|
+
})();
|
|
2063
2637
|
});
|
|
2064
2638
|
this.ws.on("machine.agent.attached", (data) => {
|
|
2065
2639
|
this.log.info(`WS: agent ${data.agent_id} attached`);
|
|
@@ -2069,6 +2643,18 @@ var DaemonSupervisor = class {
|
|
|
2069
2643
|
this.log.info(`WS: agent ${data.agent_id} detached`);
|
|
2070
2644
|
void this.handleAgentDetached(data.agent_id);
|
|
2071
2645
|
});
|
|
2646
|
+
this.ws.on("machine.config.updated", (data) => {
|
|
2647
|
+
const newSource = data.llm_source ?? "parall";
|
|
2648
|
+
if (newSource !== this.machineLlmSource) {
|
|
2649
|
+
this.log.info(`WS: llm_source changed ${this.machineLlmSource} \u2192 ${newSource}, respawning all agents`);
|
|
2650
|
+
this.machineLlmSource = newSource;
|
|
2651
|
+
void this.respawnAllChildren();
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
this.ws.on("machine.workspace.setup.requested", (data) => {
|
|
2655
|
+
this.log.info(`WS: workspace setup requested for agent ${data.agent_id}`);
|
|
2656
|
+
void this.handleWorkspaceSetupRequested(data.agent_id);
|
|
2657
|
+
});
|
|
2072
2658
|
this.ws.on("machine.stop", (data) => {
|
|
2073
2659
|
this.log.info(`WS: machine.stop received (reason=${data.reason ?? "none"})`);
|
|
2074
2660
|
void this.stop();
|
|
@@ -2079,8 +2665,8 @@ var DaemonSupervisor = class {
|
|
|
2079
2665
|
}
|
|
2080
2666
|
});
|
|
2081
2667
|
await this.ws.connect();
|
|
2082
|
-
await new Promise((
|
|
2083
|
-
this.stopResolve =
|
|
2668
|
+
await new Promise((resolve4) => {
|
|
2669
|
+
this.stopResolve = resolve4;
|
|
2084
2670
|
});
|
|
2085
2671
|
signal.removeEventListener("abort", onAbort);
|
|
2086
2672
|
}
|
|
@@ -2094,6 +2680,10 @@ var DaemonSupervisor = class {
|
|
|
2094
2680
|
this.ws = null;
|
|
2095
2681
|
}
|
|
2096
2682
|
const exits = [];
|
|
2683
|
+
for (const timer of this.workspaceSetupRetryTimers.values()) {
|
|
2684
|
+
clearTimeout(timer);
|
|
2685
|
+
}
|
|
2686
|
+
this.workspaceSetupRetryTimers.clear();
|
|
2097
2687
|
for (const state of this.children.values()) {
|
|
2098
2688
|
state.shuttingDown = true;
|
|
2099
2689
|
if (state.restartTimer) {
|
|
@@ -2117,6 +2707,7 @@ var DaemonSupervisor = class {
|
|
|
2117
2707
|
try {
|
|
2118
2708
|
const machine = await this.client.getMachineSelf();
|
|
2119
2709
|
this.machineOrgId = machine.org_id;
|
|
2710
|
+
this.machineLlmSource = machine.llm_source ?? "parall";
|
|
2120
2711
|
this.log.info(`daemon online \u2014 machine_id=${machine.id} org=${machine.org_id} label=${machine.label}`);
|
|
2121
2712
|
return true;
|
|
2122
2713
|
} catch (err) {
|
|
@@ -2194,15 +2785,15 @@ var DaemonSupervisor = class {
|
|
|
2194
2785
|
*/
|
|
2195
2786
|
migrateFlatLayout() {
|
|
2196
2787
|
const root = this.config.rootStateDir;
|
|
2197
|
-
const agentsDir =
|
|
2198
|
-
const flatWorkspace =
|
|
2199
|
-
if (!
|
|
2788
|
+
const agentsDir = path4.join(root, "agents");
|
|
2789
|
+
const flatWorkspace = path4.join(root, "workspace");
|
|
2790
|
+
if (!fs3.existsSync(flatWorkspace) || fs3.existsSync(agentsDir))
|
|
2200
2791
|
return;
|
|
2201
2792
|
let ownerAgentId;
|
|
2202
|
-
const sessionsDir =
|
|
2203
|
-
if (
|
|
2793
|
+
const sessionsDir = path4.join(root, "sessions");
|
|
2794
|
+
if (fs3.existsSync(sessionsDir)) {
|
|
2204
2795
|
try {
|
|
2205
|
-
for (const file of
|
|
2796
|
+
for (const file of fs3.readdirSync(sessionsDir)) {
|
|
2206
2797
|
if (!file.endsWith(".json"))
|
|
2207
2798
|
continue;
|
|
2208
2799
|
const decoded = Buffer.from(file.replace(".json", ""), "base64url").toString();
|
|
@@ -2216,13 +2807,13 @@ var DaemonSupervisor = class {
|
|
|
2216
2807
|
}
|
|
2217
2808
|
}
|
|
2218
2809
|
const targetId = ownerAgentId ?? "_orphan";
|
|
2219
|
-
const targetDir =
|
|
2810
|
+
const targetDir = path4.join(agentsDir, targetId);
|
|
2220
2811
|
try {
|
|
2221
|
-
|
|
2812
|
+
fs3.mkdirSync(targetDir, { recursive: true });
|
|
2222
2813
|
for (const sub of ["workspace", "sessions", "dispatch-context"]) {
|
|
2223
|
-
const src =
|
|
2224
|
-
if (
|
|
2225
|
-
|
|
2814
|
+
const src = path4.join(root, sub);
|
|
2815
|
+
if (fs3.existsSync(src)) {
|
|
2816
|
+
fs3.renameSync(src, path4.join(targetDir, sub));
|
|
2226
2817
|
}
|
|
2227
2818
|
}
|
|
2228
2819
|
this.log.info(`migrated legacy flat state \u2192 agents/${targetId}/`);
|
|
@@ -2231,23 +2822,30 @@ var DaemonSupervisor = class {
|
|
|
2231
2822
|
}
|
|
2232
2823
|
}
|
|
2233
2824
|
// ---- WS event handlers (incremental) ----
|
|
2234
|
-
async
|
|
2235
|
-
if (this.children.has(agentId))
|
|
2236
|
-
return;
|
|
2825
|
+
async fetchAttachedAgent(agentId) {
|
|
2237
2826
|
let attached;
|
|
2238
2827
|
try {
|
|
2239
2828
|
attached = await this.client.listAttachedAgents();
|
|
2240
2829
|
} catch (err) {
|
|
2241
|
-
this.log.warn(`
|
|
2242
|
-
return;
|
|
2830
|
+
this.log.warn(`fetchAttachedAgent: listAttachedAgents failed: ${String(err)}`);
|
|
2831
|
+
return { kind: "retryable" };
|
|
2243
2832
|
}
|
|
2244
2833
|
const entry = attached.find((a) => (a.user?.id ?? a.profile.user_id) === agentId);
|
|
2245
2834
|
if (!entry) {
|
|
2246
|
-
this.log.warn(`
|
|
2247
|
-
return;
|
|
2835
|
+
this.log.warn(`fetchAttachedAgent: agent ${agentId} not found in attached list`);
|
|
2836
|
+
return { kind: "skip" };
|
|
2248
2837
|
}
|
|
2249
2838
|
if (entry.user && entry.user.status !== "active") {
|
|
2250
2839
|
this.log.info(`agent ${agentId} not active (status=${entry.user.status}) \u2014 skipping`);
|
|
2840
|
+
return { kind: "skip" };
|
|
2841
|
+
}
|
|
2842
|
+
return { kind: "found", entry };
|
|
2843
|
+
}
|
|
2844
|
+
async handleAgentAttached(agentId) {
|
|
2845
|
+
if (this.children.has(agentId) || this.spawningAgents.has(agentId))
|
|
2846
|
+
return;
|
|
2847
|
+
const result = await this.fetchAttachedAgent(agentId);
|
|
2848
|
+
if (result.kind !== "found") {
|
|
2251
2849
|
return;
|
|
2252
2850
|
}
|
|
2253
2851
|
const orgId = this.machineOrgId;
|
|
@@ -2255,9 +2853,14 @@ var DaemonSupervisor = class {
|
|
|
2255
2853
|
this.log.warn(`agent ${agentId}: no org_id available yet; skipping`);
|
|
2256
2854
|
return;
|
|
2257
2855
|
}
|
|
2258
|
-
await this.spawnAgent(agentId, orgId, entry);
|
|
2856
|
+
await this.spawnAgent(agentId, orgId, result.entry);
|
|
2259
2857
|
}
|
|
2260
2858
|
async handleAgentDetached(agentId) {
|
|
2859
|
+
this.pendingWorkspaceSetup.delete(agentId);
|
|
2860
|
+
this.clearWorkspaceSetupRetry(agentId);
|
|
2861
|
+
if (this.spawningAgents.has(agentId)) {
|
|
2862
|
+
this.cancelledSpawns.add(agentId);
|
|
2863
|
+
}
|
|
2261
2864
|
const state = this.children.get(agentId);
|
|
2262
2865
|
if (!state)
|
|
2263
2866
|
return;
|
|
@@ -2270,7 +2873,86 @@ var DaemonSupervisor = class {
|
|
|
2270
2873
|
await this.terminateChild(state);
|
|
2271
2874
|
this.children.delete(agentId);
|
|
2272
2875
|
}
|
|
2876
|
+
async handleWorkspaceSetupRequested(agentId) {
|
|
2877
|
+
if (this.spawningAgents.has(agentId)) {
|
|
2878
|
+
this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
|
|
2879
|
+
this.pendingWorkspaceSetup.add(agentId);
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
const result = await this.fetchAttachedAgent(agentId);
|
|
2883
|
+
if (result.kind === "retryable") {
|
|
2884
|
+
this.pendingWorkspaceSetup.add(agentId);
|
|
2885
|
+
this.scheduleWorkspaceSetupRetry(agentId);
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
if (result.kind === "skip") {
|
|
2889
|
+
this.pendingWorkspaceSetup.delete(agentId);
|
|
2890
|
+
this.clearWorkspaceSetupRetry(agentId);
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
this.clearWorkspaceSetupRetry(agentId);
|
|
2894
|
+
const state = this.children.get(agentId);
|
|
2895
|
+
if (state) {
|
|
2896
|
+
this.log.info(`agent ${agentId}: restarting for workspace setup`);
|
|
2897
|
+
state.shuttingDown = true;
|
|
2898
|
+
if (state.restartTimer) {
|
|
2899
|
+
clearTimeout(state.restartTimer);
|
|
2900
|
+
state.restartTimer = null;
|
|
2901
|
+
}
|
|
2902
|
+
await this.terminateChild(state);
|
|
2903
|
+
this.children.delete(agentId);
|
|
2904
|
+
}
|
|
2905
|
+
const orgId = this.machineOrgId;
|
|
2906
|
+
if (!orgId) {
|
|
2907
|
+
this.log.warn(`agent ${agentId}: no org_id available yet; skipping`);
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
await this.spawnAgent(agentId, orgId, result.entry);
|
|
2911
|
+
}
|
|
2912
|
+
scheduleWorkspaceSetupRetry(agentId) {
|
|
2913
|
+
if (!this.running || this.workspaceSetupRetryTimers.has(agentId))
|
|
2914
|
+
return;
|
|
2915
|
+
const timer = setTimeout(() => {
|
|
2916
|
+
this.workspaceSetupRetryTimers.delete(agentId);
|
|
2917
|
+
if (this.running && this.pendingWorkspaceSetup.has(agentId)) {
|
|
2918
|
+
void this.handleWorkspaceSetupRequested(agentId);
|
|
2919
|
+
}
|
|
2920
|
+
}, WORKSPACE_SETUP_RETRY_DELAY_MS);
|
|
2921
|
+
timer.unref?.();
|
|
2922
|
+
this.workspaceSetupRetryTimers.set(agentId, timer);
|
|
2923
|
+
}
|
|
2924
|
+
clearWorkspaceSetupRetry(agentId) {
|
|
2925
|
+
const timer = this.workspaceSetupRetryTimers.get(agentId);
|
|
2926
|
+
if (timer) {
|
|
2927
|
+
clearTimeout(timer);
|
|
2928
|
+
this.workspaceSetupRetryTimers.delete(agentId);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2273
2931
|
// ---- Spawn / restart ----
|
|
2932
|
+
async refreshMachineConfig() {
|
|
2933
|
+
try {
|
|
2934
|
+
const machine = await this.client.getMachineSelf();
|
|
2935
|
+
const newSource = machine.llm_source ?? "parall";
|
|
2936
|
+
if (newSource !== this.machineLlmSource) {
|
|
2937
|
+
this.log.info(`machine config refreshed: llm_source ${this.machineLlmSource} \u2192 ${newSource}`);
|
|
2938
|
+
this.machineLlmSource = newSource;
|
|
2939
|
+
await this.respawnAllChildren();
|
|
2940
|
+
}
|
|
2941
|
+
} catch (err) {
|
|
2942
|
+
this.log.warn(`refreshMachineConfig failed: ${String(err)}`);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
async respawnAllChildren() {
|
|
2946
|
+
const states = [...this.children.values()];
|
|
2947
|
+
for (const state of states) {
|
|
2948
|
+
await this.terminateChild(state);
|
|
2949
|
+
}
|
|
2950
|
+
for (const state of states) {
|
|
2951
|
+
if (!state.shuttingDown && this.running) {
|
|
2952
|
+
await this.restartChildNow(state, "llm_source changed");
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2274
2956
|
async restartChildNow(state, reason) {
|
|
2275
2957
|
if (!this.running || state.shuttingDown || state.child || state.restartTimer) {
|
|
2276
2958
|
return;
|
|
@@ -2285,6 +2967,25 @@ var DaemonSupervisor = class {
|
|
|
2285
2967
|
}
|
|
2286
2968
|
}
|
|
2287
2969
|
async spawnAgent(agentId, orgId, attached) {
|
|
2970
|
+
if (this.children.has(agentId) || this.spawningAgents.has(agentId)) {
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
this.spawningAgents.add(agentId);
|
|
2974
|
+
let shouldReplaySetupRequest = false;
|
|
2975
|
+
try {
|
|
2976
|
+
await this.spawnAgentOnce(agentId, orgId, attached);
|
|
2977
|
+
} finally {
|
|
2978
|
+
this.spawningAgents.delete(agentId);
|
|
2979
|
+
this.cancelledSpawns.delete(agentId);
|
|
2980
|
+
shouldReplaySetupRequest = this.pendingWorkspaceSetup.delete(agentId);
|
|
2981
|
+
}
|
|
2982
|
+
if (shouldReplaySetupRequest && this.running) {
|
|
2983
|
+
queueMicrotask(() => {
|
|
2984
|
+
void this.handleWorkspaceSetupRequested(agentId);
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
async spawnAgentOnce(agentId, orgId, attached) {
|
|
2288
2989
|
let credential;
|
|
2289
2990
|
try {
|
|
2290
2991
|
credential = await this.client.mintLaunchCredential(agentId);
|
|
@@ -2293,28 +2994,43 @@ var DaemonSupervisor = class {
|
|
|
2293
2994
|
return;
|
|
2294
2995
|
}
|
|
2295
2996
|
const stateDir = agentStateDirFor(this.config.rootStateDir, agentId);
|
|
2296
|
-
const
|
|
2997
|
+
const defaultWorkspaceDir = agentWorkspaceDirFor(this.config.rootStateDir, agentId);
|
|
2297
2998
|
const isK8s = !!process.env.KUBERNETES_SERVICE_HOST;
|
|
2298
2999
|
const claudeHome = isK8s ? agentClaudeHomeFor(this.config.rootClaudeHome, agentId) : this.config.rootClaudeHome;
|
|
2299
3000
|
try {
|
|
2300
|
-
|
|
2301
|
-
if (!attached.daemon_config?.workspace_path) {
|
|
2302
|
-
fs2.mkdirSync(workspaceDir, { recursive: true });
|
|
2303
|
-
}
|
|
3001
|
+
fs3.mkdirSync(stateDir, { recursive: true });
|
|
2304
3002
|
if (isK8s) {
|
|
2305
|
-
|
|
3003
|
+
fs3.mkdirSync(claudeHome, { recursive: true });
|
|
2306
3004
|
this.ensureSharedCredentialLink(claudeHome, agentId);
|
|
2307
3005
|
}
|
|
2308
3006
|
} catch (err) {
|
|
2309
3007
|
this.log.warn(`mkdir agent dirs (${agentId}) failed: ${String(err)}`);
|
|
2310
3008
|
}
|
|
2311
3009
|
const runtimeType = attached.profile.runtime_type ?? "claude-code";
|
|
3010
|
+
let workspaceDir;
|
|
3011
|
+
try {
|
|
3012
|
+
workspaceDir = await prepareWorkspace({
|
|
3013
|
+
client: this.client,
|
|
3014
|
+
attached,
|
|
3015
|
+
agentId,
|
|
3016
|
+
defaultWorkspaceDir,
|
|
3017
|
+
log: this.log
|
|
3018
|
+
});
|
|
3019
|
+
} catch (err) {
|
|
3020
|
+
this.log.error(`agent ${agentId}: workspace setup failed: ${String(err)}`);
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
if (this.cancelledSpawns.delete(agentId)) {
|
|
3024
|
+
this.log.info(`agent ${agentId}: spawn cancelled after detach during workspace setup`);
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
2312
3027
|
const state = {
|
|
2313
3028
|
agentId,
|
|
2314
3029
|
orgId,
|
|
2315
3030
|
runtimeType,
|
|
2316
3031
|
workspacePath: workspaceDir,
|
|
2317
3032
|
claudeHome,
|
|
3033
|
+
providerConfig: attached.provider_config ?? { llm_source: this.machineLlmSource },
|
|
2318
3034
|
child: null,
|
|
2319
3035
|
credential,
|
|
2320
3036
|
restartAttempts: 0,
|
|
@@ -2338,9 +3054,9 @@ var DaemonSupervisor = class {
|
|
|
2338
3054
|
workspaceDir: state.workspacePath,
|
|
2339
3055
|
claudeHome: state.claudeHome
|
|
2340
3056
|
};
|
|
2341
|
-
const env = adapter.buildEnv({ ...process.env, PRLL_API_URL: this.config.apiUrl }, state.agentId, state.orgId, state.credential.api_key, dirs);
|
|
3057
|
+
const env = adapter.buildEnv({ ...process.env, PRLL_API_URL: this.config.apiUrl }, state.agentId, state.orgId, state.credential.api_key, dirs, state.providerConfig);
|
|
2342
3058
|
this.log.info(`spawning agent ${state.agentId} runtime=${state.runtimeType} bin=${adapter.bin} (attempt ${state.restartAttempts + 1})`);
|
|
2343
|
-
const child =
|
|
3059
|
+
const child = spawn2(adapter.bin, [], {
|
|
2344
3060
|
env,
|
|
2345
3061
|
stdio: ["ignore", "inherit", "inherit"],
|
|
2346
3062
|
detached: false
|
|
@@ -2378,25 +3094,26 @@ var DaemonSupervisor = class {
|
|
|
2378
3094
|
settleChild("error", null, null, err);
|
|
2379
3095
|
});
|
|
2380
3096
|
child.once("close", (code, signal) => settleChild("close", code, signal));
|
|
2381
|
-
setTimeout(() => {
|
|
3097
|
+
const stableTimer = setTimeout(() => {
|
|
2382
3098
|
if (state.child === child) {
|
|
2383
3099
|
state.restartAttempts = 0;
|
|
2384
3100
|
}
|
|
2385
3101
|
}, Math.max(this.config.restartBackoffMs, 3e4));
|
|
3102
|
+
stableTimer.unref?.();
|
|
2386
3103
|
}
|
|
2387
3104
|
async terminateChild(state) {
|
|
2388
3105
|
const child = state.child;
|
|
2389
3106
|
if (!child)
|
|
2390
3107
|
return;
|
|
2391
|
-
return new Promise((
|
|
2392
|
-
const onExit = () =>
|
|
3108
|
+
return new Promise((resolve4) => {
|
|
3109
|
+
const onExit = () => resolve4();
|
|
2393
3110
|
child.once("exit", onExit);
|
|
2394
3111
|
try {
|
|
2395
3112
|
child.kill("SIGTERM");
|
|
2396
3113
|
} catch (err) {
|
|
2397
3114
|
this.log.warn(`SIGTERM ${state.agentId} threw: ${String(err)}`);
|
|
2398
3115
|
child.off("exit", onExit);
|
|
2399
|
-
|
|
3116
|
+
resolve4();
|
|
2400
3117
|
return;
|
|
2401
3118
|
}
|
|
2402
3119
|
const hardKill = setTimeout(() => {
|
|
@@ -2409,60 +3126,60 @@ var DaemonSupervisor = class {
|
|
|
2409
3126
|
});
|
|
2410
3127
|
}
|
|
2411
3128
|
ensureSharedCredentialLink(agentClaudeHome, agentId) {
|
|
2412
|
-
const sharedCredentials =
|
|
3129
|
+
const sharedCredentials = path4.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
|
|
2413
3130
|
const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
|
|
2414
|
-
const agentCredentialsDir =
|
|
2415
|
-
|
|
2416
|
-
|
|
3131
|
+
const agentCredentialsDir = path4.dirname(agentCredentials);
|
|
3132
|
+
fs3.mkdirSync(path4.dirname(sharedCredentials), { recursive: true });
|
|
3133
|
+
fs3.mkdirSync(agentCredentialsDir, { recursive: true });
|
|
2417
3134
|
try {
|
|
2418
|
-
const existing =
|
|
3135
|
+
const existing = fs3.lstatSync(agentCredentials);
|
|
2419
3136
|
if (existing.isSymbolicLink()) {
|
|
2420
|
-
const currentTarget =
|
|
2421
|
-
if (
|
|
3137
|
+
const currentTarget = fs3.readlinkSync(agentCredentials);
|
|
3138
|
+
if (path4.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
|
|
2422
3139
|
return;
|
|
2423
3140
|
}
|
|
2424
|
-
|
|
3141
|
+
fs3.unlinkSync(agentCredentials);
|
|
2425
3142
|
} else if (existing.isDirectory()) {
|
|
2426
3143
|
this.log.warn(`agent ${agentId}: credential path is a directory, cannot link ${agentCredentials}`);
|
|
2427
3144
|
return;
|
|
2428
3145
|
} else {
|
|
2429
|
-
|
|
3146
|
+
fs3.unlinkSync(agentCredentials);
|
|
2430
3147
|
}
|
|
2431
3148
|
} catch (err) {
|
|
2432
3149
|
if (err.code !== "ENOENT") {
|
|
2433
3150
|
throw err;
|
|
2434
3151
|
}
|
|
2435
3152
|
}
|
|
2436
|
-
|
|
3153
|
+
fs3.symlinkSync(sharedCredentials, agentCredentials);
|
|
2437
3154
|
}
|
|
2438
3155
|
};
|
|
2439
3156
|
|
|
2440
3157
|
// ts/daemon/dist/cli.js
|
|
2441
|
-
import * as
|
|
2442
|
-
import * as
|
|
3158
|
+
import * as fs4 from "node:fs";
|
|
3159
|
+
import * as path5 from "node:path";
|
|
2443
3160
|
import * as os2 from "node:os";
|
|
2444
3161
|
import * as readline from "node:readline";
|
|
2445
|
-
import { spawn as
|
|
3162
|
+
import { spawn as spawn3, execSync } from "node:child_process";
|
|
2446
3163
|
var CONFIG_DIR = daemonConfigDir();
|
|
2447
3164
|
var CONFIG_PATH = daemonConfigPath();
|
|
2448
3165
|
function readConfig() {
|
|
2449
3166
|
try {
|
|
2450
|
-
return JSON.parse(
|
|
3167
|
+
return JSON.parse(fs4.readFileSync(CONFIG_PATH, "utf-8"));
|
|
2451
3168
|
} catch {
|
|
2452
3169
|
return null;
|
|
2453
3170
|
}
|
|
2454
3171
|
}
|
|
2455
3172
|
function writeConfig(config) {
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
3173
|
+
fs4.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
3174
|
+
fs4.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
|
|
3175
|
+
fs4.chmodSync(CONFIG_PATH, 384);
|
|
2459
3176
|
}
|
|
2460
3177
|
function prompt(question) {
|
|
2461
3178
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2462
|
-
return new Promise((
|
|
3179
|
+
return new Promise((resolve4) => {
|
|
2463
3180
|
rl.question(question, (answer) => {
|
|
2464
3181
|
rl.close();
|
|
2465
|
-
|
|
3182
|
+
resolve4(answer.trim());
|
|
2466
3183
|
});
|
|
2467
3184
|
});
|
|
2468
3185
|
}
|
|
@@ -2474,10 +3191,10 @@ function isLinux() {
|
|
|
2474
3191
|
}
|
|
2475
3192
|
var PLIST_LABEL = "com.parall.daemon";
|
|
2476
3193
|
function plistPath() {
|
|
2477
|
-
return
|
|
3194
|
+
return path5.join(os2.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
2478
3195
|
}
|
|
2479
3196
|
function systemdUnitPath() {
|
|
2480
|
-
return
|
|
3197
|
+
return path5.join(os2.homedir(), ".config", "systemd", "user", "parall-daemon.service");
|
|
2481
3198
|
}
|
|
2482
3199
|
function getDaemonBin() {
|
|
2483
3200
|
try {
|
|
@@ -2487,7 +3204,7 @@ function getDaemonBin() {
|
|
|
2487
3204
|
}
|
|
2488
3205
|
}
|
|
2489
3206
|
function generatePlist(daemonBin) {
|
|
2490
|
-
const logPath =
|
|
3207
|
+
const logPath = path5.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
2491
3208
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2492
3209
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2493
3210
|
<plist version="1.0">
|
|
@@ -2534,16 +3251,16 @@ function installService() {
|
|
|
2534
3251
|
}
|
|
2535
3252
|
const bin = getDaemonBin();
|
|
2536
3253
|
if (isMacOS()) {
|
|
2537
|
-
const dir =
|
|
2538
|
-
|
|
2539
|
-
|
|
3254
|
+
const dir = path5.dirname(plistPath());
|
|
3255
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
3256
|
+
fs4.writeFileSync(plistPath(), generatePlist(bin));
|
|
2540
3257
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
2541
3258
|
execSync(`launchctl bootstrap gui/$(id -u) ${plistPath()}`);
|
|
2542
3259
|
console.log(`launchd agent installed: ${plistPath()}`);
|
|
2543
3260
|
} else if (isLinux()) {
|
|
2544
|
-
const dir =
|
|
2545
|
-
|
|
2546
|
-
|
|
3261
|
+
const dir = path5.dirname(systemdUnitPath());
|
|
3262
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
3263
|
+
fs4.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
|
|
2547
3264
|
execSync("systemctl --user daemon-reload");
|
|
2548
3265
|
execSync("systemctl --user enable --now parall-daemon");
|
|
2549
3266
|
console.log(`systemd service installed: ${systemdUnitPath()}`);
|
|
@@ -2604,31 +3321,31 @@ function cmdStop() {
|
|
|
2604
3321
|
}
|
|
2605
3322
|
function cmdLogs(lines) {
|
|
2606
3323
|
if (isLinux()) {
|
|
2607
|
-
const child2 =
|
|
3324
|
+
const child2 = spawn3("journalctl", ["--user-unit", "parall-daemon", "-n", lines, "-f"], {
|
|
2608
3325
|
stdio: "inherit"
|
|
2609
3326
|
});
|
|
2610
3327
|
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
2611
3328
|
return;
|
|
2612
3329
|
}
|
|
2613
|
-
const logPath =
|
|
2614
|
-
if (!
|
|
3330
|
+
const logPath = path5.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
|
|
3331
|
+
if (!fs4.existsSync(logPath)) {
|
|
2615
3332
|
console.log("No log file found at", logPath);
|
|
2616
3333
|
return;
|
|
2617
3334
|
}
|
|
2618
|
-
const child =
|
|
3335
|
+
const child = spawn3("tail", ["-n", lines, "-f", logPath], { stdio: "inherit" });
|
|
2619
3336
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
2620
3337
|
}
|
|
2621
3338
|
function cmdServiceUninstall() {
|
|
2622
3339
|
if (isMacOS()) {
|
|
2623
3340
|
execSync(`launchctl bootout gui/$(id -u) ${plistPath()} 2>/dev/null || true`);
|
|
2624
|
-
if (
|
|
2625
|
-
|
|
3341
|
+
if (fs4.existsSync(plistPath()))
|
|
3342
|
+
fs4.unlinkSync(plistPath());
|
|
2626
3343
|
console.log("launchd agent uninstalled.");
|
|
2627
3344
|
} else if (isLinux()) {
|
|
2628
3345
|
execSync("systemctl --user stop parall-daemon 2>/dev/null || true");
|
|
2629
3346
|
execSync("systemctl --user disable parall-daemon 2>/dev/null || true");
|
|
2630
|
-
if (
|
|
2631
|
-
|
|
3347
|
+
if (fs4.existsSync(systemdUnitPath()))
|
|
3348
|
+
fs4.unlinkSync(systemdUnitPath());
|
|
2632
3349
|
execSync("systemctl --user daemon-reload");
|
|
2633
3350
|
console.log("systemd service uninstalled.");
|
|
2634
3351
|
} else {
|
|
@@ -2704,41 +3421,35 @@ async function runCLI(args) {
|
|
|
2704
3421
|
}
|
|
2705
3422
|
|
|
2706
3423
|
// ts/daemon/dist/index.js
|
|
2707
|
-
|
|
2708
|
-
return {
|
|
2709
|
-
info: (msg) => console.log(`[${prefix}] ${msg}`),
|
|
2710
|
-
warn: (msg) => console.warn(`[${prefix}] ${msg}`),
|
|
2711
|
-
error: (msg) => console.error(`[${prefix}] ${msg}`)
|
|
2712
|
-
};
|
|
2713
|
-
}
|
|
3424
|
+
var log = createLogger("daemon");
|
|
2714
3425
|
function formatError(reason) {
|
|
2715
3426
|
if (reason instanceof Error) {
|
|
2716
3427
|
return reason.stack ?? reason.message;
|
|
2717
3428
|
}
|
|
2718
3429
|
return String(reason);
|
|
2719
3430
|
}
|
|
2720
|
-
async function runForever(config, client,
|
|
3431
|
+
async function runForever(config, client, log2, signal) {
|
|
2721
3432
|
let attempt = 0;
|
|
2722
3433
|
while (!signal.aborted) {
|
|
2723
|
-
const supervisor = new DaemonSupervisor(config, client,
|
|
3434
|
+
const supervisor = new DaemonSupervisor(config, client, log2);
|
|
2724
3435
|
try {
|
|
2725
3436
|
await supervisor.run(signal);
|
|
2726
3437
|
await supervisor.stop();
|
|
2727
3438
|
return;
|
|
2728
3439
|
} catch (err) {
|
|
2729
|
-
|
|
3440
|
+
log2.error(`supervisor crashed: ${String(err)}`);
|
|
2730
3441
|
try {
|
|
2731
3442
|
await supervisor.stop();
|
|
2732
3443
|
} catch (stopErr) {
|
|
2733
|
-
|
|
3444
|
+
log2.warn(`supervisor.stop() after crash threw: ${String(stopErr)}`);
|
|
2734
3445
|
}
|
|
2735
3446
|
if (config.supervisorRestartBackoffMs === 0) {
|
|
2736
|
-
|
|
3447
|
+
log2.error("supervisor keepalive disabled (PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS=0) \u2014 exiting");
|
|
2737
3448
|
throw err;
|
|
2738
3449
|
}
|
|
2739
3450
|
const delay = Math.min(config.supervisorRestartBackoffMs * Math.pow(2, attempt), config.supervisorRestartBackoffMaxMs);
|
|
2740
3451
|
attempt += 1;
|
|
2741
|
-
|
|
3452
|
+
log2.warn(`restarting supervisor in ${delay}ms (attempt ${attempt})`);
|
|
2742
3453
|
const slept = await sleepCancellable(delay, signal);
|
|
2743
3454
|
if (!slept)
|
|
2744
3455
|
return;
|
|
@@ -2747,12 +3458,12 @@ async function runForever(config, client, log, signal) {
|
|
|
2747
3458
|
}
|
|
2748
3459
|
async function main() {
|
|
2749
3460
|
const config = resolveClaudeDaemonConfig(process.env);
|
|
2750
|
-
const log = createLogger("daemon");
|
|
2751
3461
|
log.info(`boot: api=${config.apiUrl} pollMs=${config.pollIntervalMs} heartbeatMs=${config.heartbeatIntervalMs} agentBin=${config.agentBin}`);
|
|
2752
3462
|
log.info(`keepalive: bootstrapBackoffMs=${config.bootstrapBackoffMs} supervisorRestartBackoffMs=${config.supervisorRestartBackoffMs}`);
|
|
2753
3463
|
const client = new ParallClient({
|
|
2754
3464
|
baseUrl: config.apiUrl,
|
|
2755
|
-
token: config.apiKey
|
|
3465
|
+
token: config.apiKey,
|
|
3466
|
+
swimlaneName: config.swimlaneName
|
|
2756
3467
|
});
|
|
2757
3468
|
const abortController = new AbortController();
|
|
2758
3469
|
const onSignal = (sig) => {
|
|
@@ -2769,7 +3480,7 @@ async function main() {
|
|
|
2769
3480
|
process.exitCode = 1;
|
|
2770
3481
|
process.exit(1);
|
|
2771
3482
|
});
|
|
2772
|
-
config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl);
|
|
3483
|
+
config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
2773
3484
|
await runForever(config, client, log, abortController.signal);
|
|
2774
3485
|
}
|
|
2775
3486
|
var cliArgs = process.argv.slice(2);
|
|
@@ -2777,10 +3488,10 @@ runCLI(cliArgs).then((result) => {
|
|
|
2777
3488
|
if (result === "handled")
|
|
2778
3489
|
return;
|
|
2779
3490
|
main().catch((err) => {
|
|
2780
|
-
|
|
3491
|
+
log.error(`fatal: ${formatError(err)}`);
|
|
2781
3492
|
process.exitCode = 1;
|
|
2782
3493
|
});
|
|
2783
3494
|
}).catch((err) => {
|
|
2784
|
-
|
|
3495
|
+
log.error(`fatal: ${formatError(err)}`);
|
|
2785
3496
|
process.exitCode = 1;
|
|
2786
3497
|
});
|