@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.
@@ -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(path4) {
394
+ async ensureFreshToken(path6) {
371
395
  if (!this.token || !this.getRefreshToken)
372
396
  return;
373
- const pathSuffix = path4.replace(/^\/api\/v1/, "");
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, path4, body, query, retried = false, opts) {
429
+ async request(method, path6, body, query, retried = false, opts) {
406
430
  if (!retried) {
407
- await this.ensureFreshToken(path4);
431
+ await this.ensureFreshToken(path6);
408
432
  }
409
- let url = `${this.baseUrl}${path4}`;
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 = path4.replace(/^\/api\/v1/, "");
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, path4, body, query, true, opts);
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, path4, body, retried = false) {
500
+ async multipartRequest(method, path6, body, retried = false) {
477
501
  if (!retried) {
478
- await this.ensureFreshToken(path4);
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}${path4}`, {
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 = path4.replace(/^\/api\/v1/, "");
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, path4, body, true);
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
- const res = await this.request("GET", ENDPOINTS.AGENT_SESSIONS(orgId, agentId), void 0, params);
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
- const res = await this.request("GET", ENDPOINTS.AGENT_SESSION_STEPS(orgId, agentId, sessionId), void 0, params);
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, path4) {
1338
- return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path4 ? { path: path4 } : 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, path4, params) {
1348
- return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, { path: path4, ...params });
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, path4, ref) {
1351
- return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path4, ref });
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
- return this.request("GET", `${ENDPOINTS.BILLING_TRANSACTIONS(orgId)}?group_by=agent`);
1436
- }
1437
- async listBillingTransactionMachineGroups(orgId) {
1438
- return this.request("GET", `${ENDPOINTS.BILLING_TRANSACTIONS(orgId)}?group_by=machine`);
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
- console.error("Failed to get WS ticket:", err);
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
- return explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
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 fs2 from "node:fs";
1920
- import * as path2 from "node:path";
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
- if ("ANTHROPIC_AUTH_TOKEN" in env) {
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((resolve3) => {
2573
+ return new Promise((resolve4) => {
2008
2574
  const timer = setTimeout(() => {
2009
2575
  signal.removeEventListener("abort", onAbort);
2010
- resolve3(true);
2576
+ resolve4(true);
2011
2577
  }, ms);
2012
2578
  const onAbort = () => {
2013
2579
  clearTimeout(timer);
2014
- resolve3(false);
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, log) {
2599
+ constructor(config, client, log2) {
2029
2600
  this.config = config;
2030
2601
  this.client = client;
2031
- this.log = 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 this.fullReconcile();
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((resolve3) => {
2083
- this.stopResolve = resolve3;
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 = path2.join(root, "agents");
2198
- const flatWorkspace = path2.join(root, "workspace");
2199
- if (!fs2.existsSync(flatWorkspace) || fs2.existsSync(agentsDir))
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 = path2.join(root, "sessions");
2203
- if (fs2.existsSync(sessionsDir)) {
2793
+ const sessionsDir = path4.join(root, "sessions");
2794
+ if (fs3.existsSync(sessionsDir)) {
2204
2795
  try {
2205
- for (const file of fs2.readdirSync(sessionsDir)) {
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 = path2.join(agentsDir, targetId);
2810
+ const targetDir = path4.join(agentsDir, targetId);
2220
2811
  try {
2221
- fs2.mkdirSync(targetDir, { recursive: true });
2812
+ fs3.mkdirSync(targetDir, { recursive: true });
2222
2813
  for (const sub of ["workspace", "sessions", "dispatch-context"]) {
2223
- const src = path2.join(root, sub);
2224
- if (fs2.existsSync(src)) {
2225
- fs2.renameSync(src, path2.join(targetDir, sub));
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 handleAgentAttached(agentId) {
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(`handleAgentAttached: listAttachedAgents failed: ${String(err)}`);
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(`handleAgentAttached: agent ${agentId} not found in attached list`);
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 workspaceDir = attached.daemon_config?.workspace_path || agentWorkspaceDirFor(this.config.rootStateDir, agentId);
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
- fs2.mkdirSync(stateDir, { recursive: true });
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
- fs2.mkdirSync(claudeHome, { recursive: true });
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 = spawn(adapter.bin, [], {
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((resolve3) => {
2392
- const onExit = () => resolve3();
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
- resolve3();
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 = path2.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
3129
+ const sharedCredentials = path4.resolve(sharedClaudeCredentialsFileFor(this.config.rootClaudeHome));
2413
3130
  const agentCredentials = agentClaudeCredentialsFileFor(agentClaudeHome);
2414
- const agentCredentialsDir = path2.dirname(agentCredentials);
2415
- fs2.mkdirSync(path2.dirname(sharedCredentials), { recursive: true });
2416
- fs2.mkdirSync(agentCredentialsDir, { recursive: true });
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 = fs2.lstatSync(agentCredentials);
3135
+ const existing = fs3.lstatSync(agentCredentials);
2419
3136
  if (existing.isSymbolicLink()) {
2420
- const currentTarget = fs2.readlinkSync(agentCredentials);
2421
- if (path2.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
3137
+ const currentTarget = fs3.readlinkSync(agentCredentials);
3138
+ if (path4.resolve(agentCredentialsDir, currentTarget) === sharedCredentials) {
2422
3139
  return;
2423
3140
  }
2424
- fs2.unlinkSync(agentCredentials);
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
- fs2.unlinkSync(agentCredentials);
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
- fs2.symlinkSync(sharedCredentials, agentCredentials);
3153
+ fs3.symlinkSync(sharedCredentials, agentCredentials);
2437
3154
  }
2438
3155
  };
2439
3156
 
2440
3157
  // ts/daemon/dist/cli.js
2441
- import * as fs3 from "node:fs";
2442
- import * as path3 from "node:path";
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 spawn2, execSync } from "node:child_process";
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(fs3.readFileSync(CONFIG_PATH, "utf-8"));
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
- fs3.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
2457
- fs3.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
2458
- fs3.chmodSync(CONFIG_PATH, 384);
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((resolve3) => {
3179
+ return new Promise((resolve4) => {
2463
3180
  rl.question(question, (answer) => {
2464
3181
  rl.close();
2465
- resolve3(answer.trim());
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 path3.join(os2.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
3194
+ return path5.join(os2.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
2478
3195
  }
2479
3196
  function systemdUnitPath() {
2480
- return path3.join(os2.homedir(), ".config", "systemd", "user", "parall-daemon.service");
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 = path3.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
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 = path3.dirname(plistPath());
2538
- fs3.mkdirSync(dir, { recursive: true });
2539
- fs3.writeFileSync(plistPath(), generatePlist(bin));
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 = path3.dirname(systemdUnitPath());
2545
- fs3.mkdirSync(dir, { recursive: true });
2546
- fs3.writeFileSync(systemdUnitPath(), generateSystemdUnit(bin));
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 = spawn2("journalctl", ["--user-unit", "parall-daemon", "-n", lines, "-f"], {
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 = path3.join(os2.homedir(), "Library", "Logs", "parall-daemon.log");
2614
- if (!fs3.existsSync(logPath)) {
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 = spawn2("tail", ["-n", lines, "-f", logPath], { stdio: "inherit" });
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 (fs3.existsSync(plistPath()))
2625
- fs3.unlinkSync(plistPath());
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 (fs3.existsSync(systemdUnitPath()))
2631
- fs3.unlinkSync(systemdUnitPath());
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
- function createLogger(prefix) {
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, log, signal) {
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, log);
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
- log.error(`supervisor crashed: ${String(err)}`);
3440
+ log2.error(`supervisor crashed: ${String(err)}`);
2730
3441
  try {
2731
3442
  await supervisor.stop();
2732
3443
  } catch (stopErr) {
2733
- log.warn(`supervisor.stop() after crash threw: ${String(stopErr)}`);
3444
+ log2.warn(`supervisor.stop() after crash threw: ${String(stopErr)}`);
2734
3445
  }
2735
3446
  if (config.supervisorRestartBackoffMs === 0) {
2736
- log.error("supervisor keepalive disabled (PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS=0) \u2014 exiting");
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
- log.warn(`restarting supervisor in ${delay}ms (attempt ${attempt})`);
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
- console.error(`[daemon] fatal: ${formatError(err)}`);
3491
+ log.error(`fatal: ${formatError(err)}`);
2781
3492
  process.exitCode = 1;
2782
3493
  });
2783
3494
  }).catch((err) => {
2784
- console.error(`[daemon] fatal: ${formatError(err)}`);
3495
+ log.error(`fatal: ${formatError(err)}`);
2785
3496
  process.exitCode = 1;
2786
3497
  });