@parall/daemon 1.29.3 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ function env(name) {
23
23
  var PRLL_API_URL = env("PRLL_API_URL");
24
24
  var PRLL_API_KEY = env("PRLL_API_KEY");
25
25
  var PRLL_ORG_ID = env("PRLL_ORG_ID");
26
- var stateDir = env("PRLL_OPENCLAW_STATE_DIR");
26
+ var stateDir = env("PRLL_STATE_DIR");
27
27
  var PRLL_WS_URL = process.env.PRLL_WS_URL?.trim() || "";
28
28
  var PRLL_SWIMLANE_NAME = process.env.PRLL_SWIMLANE_NAME?.trim() || "";
29
29
  var gatewayPort = process.env.OPENCLAW_GATEWAY_PORT?.trim() || "0";
@@ -187,7 +187,7 @@ try {
187
187
  } catch {
188
188
  }
189
189
  log.info("Starting OpenClaw gateway...");
190
- var workspaceDir = process.env.PRLL_OPENCLAW_WORKSPACE_DIR?.trim() || "";
190
+ var workspaceDir = process.env.PRLL_WORKSPACE_DIR?.trim() || "";
191
191
  var gatewayEnv = {
192
192
  ...process.env,
193
193
  OPENCLAW_STATE_DIR: openclawStateDir
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiPA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAoD9E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiSA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuD9E"}
package/dist/cli.js CHANGED
@@ -51,6 +51,8 @@ function getDaemonBin() {
51
51
  }
52
52
  function generatePlist(daemonBin) {
53
53
  const logPath = path.join(os.homedir(), "Library", "Logs", "parall-daemon.log");
54
+ // Use a shell wrapper so the service manager loads the self-updated overlay
55
+ // bundle when it exists, falling back to the original npm-installed binary.
54
56
  return `<?xml version="1.0" encoding="UTF-8"?>
55
57
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
56
58
  <plist version="1.0">
@@ -59,7 +61,9 @@ function generatePlist(daemonBin) {
59
61
  <string>${PLIST_LABEL}</string>
60
62
  <key>ProgramArguments</key>
61
63
  <array>
62
- <string>${daemonBin}</string>
64
+ <string>/bin/sh</string>
65
+ <string>-c</string>
66
+ <string>OVERLAY="$HOME/.parall-daemon/bundle/current/parall-daemon.js"; if [ -f "$OVERLAY" ]; then exec node "$OVERLAY"; else exec ${daemonBin}; fi</string>
63
67
  </array>
64
68
  <key>RunAtLoad</key>
65
69
  <true/>
@@ -75,6 +79,8 @@ function generatePlist(daemonBin) {
75
79
  </plist>`;
76
80
  }
77
81
  function generateSystemdUnit(daemonBin) {
82
+ // Use a shell wrapper so the service manager loads the self-updated overlay
83
+ // bundle when it exists, falling back to the original npm-installed binary.
78
84
  return `[Unit]
79
85
  Description=Parall Daemon
80
86
  After=network-online.target
@@ -82,7 +88,7 @@ Wants=network-online.target
82
88
 
83
89
  [Service]
84
90
  Type=simple
85
- ExecStart=${daemonBin}
91
+ ExecStart=/bin/sh -c 'OVERLAY="$HOME/.parall-daemon/bundle/current/parall-daemon.js"; if [ -f "$OVERLAY" ]; then exec node "$OVERLAY"; else exec ${daemonBin}; fi'
86
92
  Restart=always
87
93
  RestartSec=5
88
94
 
@@ -206,6 +212,46 @@ function cmdServiceUninstall() {
206
212
  console.log("Unsupported platform.");
207
213
  }
208
214
  }
215
+ async function cmdUpdate(checkOnly) {
216
+ const { resolveClaudeDaemonConfig, resolveBundleDir } = await import("./config.js");
217
+ const { DaemonUpdater } = await import("./updater.js");
218
+ const config = resolveClaudeDaemonConfig(process.env);
219
+ const bundleDir = resolveBundleDir(process.env);
220
+ const signingEnabled = !!process.env.PRLL_DAEMON_SIGNING_PUBLIC_KEY;
221
+ const updater = new DaemonUpdater(bundleDir, config.updateCdnUrl, {
222
+ info: (msg) => console.log(msg),
223
+ warn: (msg) => console.warn(msg),
224
+ error: (msg) => console.error(msg),
225
+ }, signingEnabled);
226
+ const local = updater.getLocalVersion();
227
+ console.log(`Current version: ${local ?? "unknown"}`);
228
+ console.log(`CDN: ${config.updateCdnUrl}`);
229
+ console.log(`Bundle dir: ${bundleDir}`);
230
+ if (checkOnly) {
231
+ console.log("\nChecking for updates...");
232
+ const result = await updater.checkAvailable();
233
+ if (result.available) {
234
+ console.log(`Update available: ${result.currentVersion ?? "unknown"} → ${result.remoteVersion}`);
235
+ }
236
+ else if (result.remoteVersion) {
237
+ console.log(`Already up to date (${result.currentVersion}).`);
238
+ }
239
+ else {
240
+ console.log("Could not check for updates.");
241
+ }
242
+ return;
243
+ }
244
+ else {
245
+ console.log("\nChecking and applying updates...");
246
+ }
247
+ const applied = await updater.checkAndApply();
248
+ if (applied) {
249
+ console.log("Update applied. Restart the daemon to use the new version.");
250
+ }
251
+ else {
252
+ console.log("Already up to date.");
253
+ }
254
+ }
209
255
  function printUsage() {
210
256
  console.log(`
211
257
  parall-daemon — Parall local agent runtime
@@ -215,6 +261,7 @@ Usage:
215
261
  parall-daemon init Configure the daemon (interactive)
216
262
  parall-daemon status Show daemon service status
217
263
  parall-daemon stop Stop the background service
264
+ parall-daemon update [--check] Check for / apply daemon updates
218
265
  parall-daemon logs [-n LINES] Tail daemon logs
219
266
  parall-daemon service install Install as background service (launchd/systemd)
220
267
  parall-daemon service uninstall Uninstall background service
@@ -246,6 +293,9 @@ export async function runCLI(args) {
246
293
  cmdLogs(lines);
247
294
  return "handled";
248
295
  }
296
+ case "update":
297
+ await cmdUpdate(args.includes("--check"));
298
+ return "handled";
249
299
  case "service": {
250
300
  const sub = args[1];
251
301
  if (sub === "install") {
package/dist/config.d.ts CHANGED
@@ -47,6 +47,12 @@ export type ClaudeDaemonConfig = {
47
47
  */
48
48
  supervisorRestartBackoffMs: number;
49
49
  supervisorRestartBackoffMaxMs: number;
50
+ /** CDN base URL for daemon bundle manifest (includes channel prefix). */
51
+ updateCdnUrl: string;
52
+ /** Update check interval in ms. Default 6h. Set 0 to disable periodic check. */
53
+ updateIntervalMs: number;
54
+ /** Disable self-update entirely (K8s env auto-disables). */
55
+ updateDisabled: boolean;
50
56
  };
51
57
  export declare function daemonConfigDir(env?: NodeJS.ProcessEnv): string;
52
58
  export declare function daemonConfigPath(env?: NodeJS.ProcessEnv): string;
@@ -63,5 +69,12 @@ export declare function sharedClaudeCredentialsFileFor(rootClaudeHome: string):
63
69
  export declare function agentClaudeCredentialsFileFor(agentClaudeHome: string): string;
64
70
  /** Per-agent workspace dir where the agent runs git commands. */
65
71
  export declare function agentWorkspaceDirFor(rootStateDir: string, agentId: string): string;
72
+ /**
73
+ * Resolve the bundle directory for self-update storage.
74
+ * Desktop: ~/Library/Application Support/Parall/daemon/ (set via env)
75
+ * Default: ~/.parall-daemon/bundle/ — always a writable overlay directory,
76
+ * never the npm global install dir (which may be root-owned or read-only).
77
+ */
78
+ export declare function resolveBundleDir(env?: NodeJS.ProcessEnv): string;
66
79
  export declare function resolveWsUrl(apiUrl: string, explicitWsUrl?: string, swimlaneName?: string): string;
67
80
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;CACvC,CAAC;AA2BF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CA6ClG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC,6BAA6B,EAAE,MAAM,CAAC;IACtC,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AA2BF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,CAmDlG;AASD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;mEAEmE;AACnE,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG"}
package/dist/config.js CHANGED
@@ -91,6 +91,12 @@ export function resolveClaudeDaemonConfig(env = process.env) {
91
91
  bootstrapBackoffMaxMs: parseMs(env.PRLL_DAEMON_BOOTSTRAP_BACKOFF_MAX_MS, 60_000),
92
92
  supervisorRestartBackoffMs: parseMsAllowZero(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS, 5_000),
93
93
  supervisorRestartBackoffMaxMs: parseMs(env.PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MAX_MS, 5 * 60_000),
94
+ updateCdnUrl: env.PRLL_DAEMON_UPDATE_CDN_URL?.trim() ||
95
+ ((env.PRLL_DAEMON_UPDATE_CHANNEL?.trim() ?? "production") === "staging"
96
+ ? "https://releases.staging.prll.sh/daemon/staging"
97
+ : "https://releases.parall.com/daemon/production"),
98
+ updateIntervalMs: parseMsAllowZero(env.PRLL_DAEMON_UPDATE_INTERVAL_MS, 6 * 60 * 60_000),
99
+ updateDisabled: env.PRLL_DAEMON_UPDATE_DISABLED === "true" || !!env.KUBERNETES_SERVICE_HOST,
94
100
  };
95
101
  }
96
102
  function assertSafeAgentId(agentId) {
@@ -121,6 +127,17 @@ export function agentClaudeCredentialsFileFor(agentClaudeHome) {
121
127
  export function agentWorkspaceDirFor(rootStateDir, agentId) {
122
128
  return path.join(rootStateDir, "agents", assertSafeAgentId(agentId), "workspace");
123
129
  }
130
+ /**
131
+ * Resolve the bundle directory for self-update storage.
132
+ * Desktop: ~/Library/Application Support/Parall/daemon/ (set via env)
133
+ * Default: ~/.parall-daemon/bundle/ — always a writable overlay directory,
134
+ * never the npm global install dir (which may be root-owned or read-only).
135
+ */
136
+ export function resolveBundleDir(env = process.env) {
137
+ if (env.PRLL_DAEMON_BUNDLE_DIR)
138
+ return resolvePath(env.PRLL_DAEMON_BUNDLE_DIR);
139
+ return path.join(daemonConfigDir(env), "bundle");
140
+ }
124
141
  export function resolveWsUrl(apiUrl, explicitWsUrl, swimlaneName) {
125
142
  const base = explicitWsUrl || `${apiUrl.replace(/\/$/, "").replace(/^http/, "ws")}/ws`;
126
143
  if (!swimlaneName)
@@ -0,0 +1,7 @@
1
+ import type { FilesystemEntry } from "@parall/sdk";
2
+ export declare function browseDenyReason(value: string): string;
3
+ export declare function listDirectory(dirPath: string): Promise<{
4
+ entries: FilesystemEntry[];
5
+ error?: string;
6
+ }>;
7
+ //# sourceMappingURL=filesystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../src/filesystem.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAqCnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBtD;AAoBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8C5G"}
@@ -0,0 +1,118 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ const MAX_ENTRIES = 200;
5
+ const SYSTEM_DIR_PREFIXES = [
6
+ "/Applications",
7
+ "/bin",
8
+ "/boot",
9
+ "/dev",
10
+ "/etc",
11
+ "/Library",
12
+ "/private",
13
+ "/proc",
14
+ "/root",
15
+ "/run",
16
+ "/sbin",
17
+ "/System",
18
+ "/sys",
19
+ "/usr",
20
+ "/var",
21
+ ];
22
+ const CREDENTIAL_DIR_NAMES = new Set([
23
+ ".aws",
24
+ ".azure",
25
+ ".claude",
26
+ ".codex",
27
+ ".config",
28
+ ".docker",
29
+ ".gnupg",
30
+ ".kube",
31
+ ".npm",
32
+ ".ssh",
33
+ ".parall-agent",
34
+ ".parall-daemon",
35
+ ]);
36
+ export function browseDenyReason(value) {
37
+ const normalized = path.resolve(value).split(path.sep).join("/");
38
+ if (normalized === "/")
39
+ return "";
40
+ for (const prefix of SYSTEM_DIR_PREFIXES) {
41
+ if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
42
+ return "a system directory";
43
+ }
44
+ }
45
+ const parts = normalized.split("/").filter(Boolean);
46
+ for (const part of parts) {
47
+ if (CREDENTIAL_DIR_NAMES.has(part)) {
48
+ return "a credential or application state directory";
49
+ }
50
+ }
51
+ return "";
52
+ }
53
+ function syntheticRoots() {
54
+ const roots = [];
55
+ const platform = os.platform();
56
+ const candidates = platform === "darwin"
57
+ ? ["/Users", os.homedir()]
58
+ : ["/home", os.homedir()];
59
+ for (const dir of [...new Set(candidates)]) {
60
+ try {
61
+ fs.accessSync(dir, fs.constants.R_OK);
62
+ roots.push({ name: dir, type: "dir" });
63
+ }
64
+ catch {
65
+ // not accessible
66
+ }
67
+ }
68
+ return roots;
69
+ }
70
+ export async function listDirectory(dirPath) {
71
+ const resolved = path.resolve(dirPath);
72
+ const normalized = resolved.split(path.sep).join("/");
73
+ if (normalized === "/") {
74
+ return { entries: syntheticRoots() };
75
+ }
76
+ const deny = browseDenyReason(normalized);
77
+ if (deny) {
78
+ return { entries: [], error: `Access denied: ${deny}` };
79
+ }
80
+ let realPath;
81
+ try {
82
+ realPath = fs.realpathSync(resolved);
83
+ }
84
+ catch (err) {
85
+ const code = err.code;
86
+ if (code === "ENOENT")
87
+ return { entries: [], error: "Directory not found" };
88
+ return { entries: [], error: "Permission denied" };
89
+ }
90
+ const realDeny = browseDenyReason(realPath.split(path.sep).join("/"));
91
+ if (realDeny) {
92
+ return { entries: [], error: `Access denied: ${realDeny}` };
93
+ }
94
+ let dirents;
95
+ try {
96
+ dirents = fs.readdirSync(realPath, { withFileTypes: true });
97
+ }
98
+ catch (err) {
99
+ const code = err.code;
100
+ if (code === "ENOENT")
101
+ return { entries: [], error: "Directory not found" };
102
+ if (code === "EACCES" || code === "EPERM")
103
+ return { entries: [], error: "Permission denied" };
104
+ return { entries: [], error: `Failed to read directory: ${code ?? String(err)}` };
105
+ }
106
+ const entries = [];
107
+ for (const d of dirents) {
108
+ if (!d.isDirectory())
109
+ continue;
110
+ if (d.name.startsWith("."))
111
+ continue;
112
+ entries.push({ name: d.name, type: "dir" });
113
+ if (entries.length >= MAX_ENTRIES)
114
+ break;
115
+ }
116
+ entries.sort((a, b) => a.name.localeCompare(b.name));
117
+ return { entries };
118
+ }
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createLogger } from "@parall/agent-core";
3
3
  import { ParallClient } from "@parall/sdk";
4
- import { resolveClaudeDaemonConfig, resolveWsUrl } from "./config.js";
4
+ import { resolveClaudeDaemonConfig, resolveBundleDir, resolveWsUrl } from "./config.js";
5
5
  import { DaemonSupervisor, sleepCancellable } from "./supervisor.js";
6
+ import { DaemonUpdater } from "./updater.js";
6
7
  import { runCLI } from "./cli.js";
8
+ const UPDATE_EXIT_CODE = 42;
7
9
  const log = createLogger("daemon");
8
10
  function formatError(reason) {
9
11
  if (reason instanceof Error) {
@@ -30,10 +32,12 @@ function formatError(reason) {
30
32
  * any supervisor.run() rejection and rely entirely on the shell wrapper +
31
33
  * K8s for restart.
32
34
  */
33
- async function runForever(config, client, log, signal) {
35
+ async function runForever(config, client, log, signal, updater) {
34
36
  let attempt = 0;
35
37
  while (!signal.aborted) {
36
38
  const supervisor = new DaemonSupervisor(config, client, log);
39
+ if (updater)
40
+ supervisor.setUpdater(updater);
37
41
  try {
38
42
  await supervisor.run(signal);
39
43
  // Clean exit (signal aborted) — done.
@@ -70,6 +74,17 @@ async function main() {
70
74
  const config = resolveClaudeDaemonConfig(process.env);
71
75
  log.info(`boot: api=${config.apiUrl} pollMs=${config.pollIntervalMs} heartbeatMs=${config.heartbeatIntervalMs} agentBin=${config.agentBin}`);
72
76
  log.info(`keepalive: bootstrapBackoffMs=${config.bootstrapBackoffMs} supervisorRestartBackoffMs=${config.supervisorRestartBackoffMs}`);
77
+ // --- Self-update: rollback check (before any network calls) ---
78
+ let updater = null;
79
+ if (!config.updateDisabled) {
80
+ const bundleDir = resolveBundleDir(process.env);
81
+ updater = new DaemonUpdater(bundleDir, config.updateCdnUrl, log, true);
82
+ if (updater.checkRollback()) {
83
+ log.info("rollback applied — exiting for service manager restart");
84
+ process.exit(UPDATE_EXIT_CODE);
85
+ }
86
+ log.info(`update: bundleDir=${bundleDir} cdn=${config.updateCdnUrl} interval=${config.updateIntervalMs}ms`);
87
+ }
73
88
  // The daemon talks to the API as a Machine — the bearer is mck_*.
74
89
  // No orgId is configured here; per-agent subprocesses get their
75
90
  // own org_id via the spawn env.
@@ -97,7 +112,19 @@ async function main() {
97
112
  process.exit(1);
98
113
  });
99
114
  config.wsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
100
- await runForever(config, client, log, abortController.signal);
115
+ // --- Self-update: boot check + periodic timer ---
116
+ if (updater) {
117
+ const applied = await updater.checkAndApply().catch((err) => {
118
+ log.warn(`boot update check failed: ${String(err)}`);
119
+ return false;
120
+ });
121
+ if (applied) {
122
+ log.info("boot update applied — exiting for restart");
123
+ process.exit(UPDATE_EXIT_CODE);
124
+ }
125
+ updater.startPeriodicCheck(config.updateIntervalMs);
126
+ }
127
+ await runForever(config, client, log, abortController.signal, updater);
101
128
  }
102
129
  const cliArgs = process.argv.slice(2);
103
130
  runCLI(cliArgs)
@@ -1,10 +1,7 @@
1
- export interface ProviderConfig {
2
- llm_source?: string;
3
- openai_api_key?: string;
4
- openai_base_url?: string;
5
- anthropic_auth_token?: string;
6
- anthropic_base_url?: string;
7
- }
1
+ import { clearAllProviderCreds } from "@parall/agent-core";
2
+ import type { ProviderConfig } from "@parall/agent-core";
3
+ export type { ProviderConfig };
4
+ export { clearAllProviderCreds };
8
5
  export interface RuntimeAdapter {
9
6
  bin: string;
10
7
  buildEnv(baseEnv: NodeJS.ProcessEnv, agentId: string, orgId: string, apiKey: string, dirs: AgentDirs, pc?: ProviderConfig): NodeJS.ProcessEnv;
@@ -14,7 +11,11 @@ export interface AgentDirs {
14
11
  workspaceDir: string;
15
12
  claudeHome: string;
16
13
  }
17
- export declare function clearAllProviderCreds(env: NodeJS.ProcessEnv): void;
14
+ /**
15
+ * Resolve a runtime adapter, preferring overlay bundle paths when available.
16
+ * When the daemon has self-updated into ~/.parall-daemon/bundle/, bridge
17
+ * binaries should also load from overlay to keep versions in sync.
18
+ */
18
19
  export declare function getRuntimeAdapter(runtimeType: string): RuntimeAdapter;
19
20
  export declare function assertAgentKey(apiKey: string): void;
20
21
  //# sourceMappingURL=runtimes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;CAC/I;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAeD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAOlE;AAgGD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAErE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAInD"}
1
+ {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,YAAY,EAAE,cAAc,EAAE,CAAC;AAC/B,OAAO,EAAE,qBAAqB,EAAE,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;CAC/I;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AA4DD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAerE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAInD"}
package/dist/runtimes.js CHANGED
@@ -1,107 +1,47 @@
1
+ import * as fs from "node:fs";
1
2
  import * as path from "node:path";
2
- function llmSource(pc) {
3
- if (pc?.llm_source)
4
- return pc.llm_source;
5
- if (pc?.openai_api_key ||
6
- pc?.openai_base_url ||
7
- pc?.anthropic_auth_token ||
8
- pc?.anthropic_base_url) {
9
- return "custom";
10
- }
11
- return "parall";
12
- }
13
- export function clearAllProviderCreds(env) {
14
- delete env.ANTHROPIC_AUTH_TOKEN;
15
- delete env.ANTHROPIC_BASE_URL;
16
- delete env.ANTHROPIC_API_KEY;
17
- delete env.OPENAI_API_KEY;
18
- delete env.OPENAI_BASE_URL;
19
- delete env.PRLL_CLAUDE_ALLOW_API_KEY;
3
+ import { clearAllProviderCreds } from "@parall/agent-core";
4
+ import { resolveBundleDir } from "./config.js";
5
+ export { clearAllProviderCreds };
6
+ function buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
7
+ const env = { ...baseEnv };
8
+ clearAllProviderCreds(env);
9
+ env.PRLL_API_KEY = apiKey;
10
+ env.PRLL_ORG_ID = orgId;
11
+ env.AGENT_ID = agentId;
12
+ env.PRLL_AGENT_ID = agentId;
13
+ env.PRLL_STATE_DIR = dirs.stateDir;
14
+ env.PRLL_WORKSPACE_DIR = dirs.workspaceDir;
15
+ if (pc)
16
+ env.PRLL_PROVIDER_CONFIG = JSON.stringify(pc);
17
+ delete env.PRLL_DAEMON_MODE;
18
+ return env;
20
19
  }
21
20
  const claudeCodeAdapter = {
22
21
  bin: "parall-claude-agent",
23
22
  buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
24
- const env = { ...baseEnv };
25
- clearAllProviderCreds(env);
26
- env.PRLL_API_KEY = apiKey;
27
- env.PRLL_ORG_ID = orgId;
28
- env.AGENT_ID = agentId;
29
- env.PRLL_AGENT_ID = agentId;
23
+ const env = buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc);
30
24
  env.PRLL_CLAUDE_HOME = dirs.claudeHome;
31
- env.PRLL_CLAUDE_STATE_DIR = dirs.stateDir;
32
- env.PRLL_CLAUDE_WORKSPACE_DIR = dirs.workspaceDir;
33
- const source = llmSource(pc);
34
- if (source === "parall") {
35
- env.ANTHROPIC_AUTH_TOKEN = apiKey;
36
- env.ANTHROPIC_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm`;
37
- env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
38
- }
39
- else if (source === "custom") {
40
- if (pc?.anthropic_auth_token) {
41
- env.ANTHROPIC_AUTH_TOKEN = pc.anthropic_auth_token;
42
- env.PRLL_CLAUDE_ALLOW_API_KEY = "1";
43
- }
44
- if (pc?.anthropic_base_url)
45
- env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
46
- }
47
- delete env.PRLL_DAEMON_MODE;
48
25
  return env;
49
26
  },
50
27
  };
51
28
  const codexAdapter = {
52
29
  bin: "parall-codex-agent",
53
30
  buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
54
- const env = { ...baseEnv };
55
- clearAllProviderCreds(env);
56
- env.PRLL_API_KEY = apiKey;
57
- env.PRLL_ORG_ID = orgId;
58
- env.AGENT_ID = agentId;
59
- env.PRLL_AGENT_ID = agentId;
60
- env.PRLL_CODEX_STATE_DIR = dirs.stateDir;
61
- env.PRLL_CODEX_WORKSPACE_DIR = dirs.workspaceDir;
31
+ const env = buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc);
62
32
  env.PRLL_CODEX_HOME = path.join(dirs.stateDir, ".codex");
63
- const source = llmSource(pc);
64
- if (source === "parall") {
65
- env.OPENAI_API_KEY = apiKey;
66
- env.OPENAI_BASE_URL = `${baseEnv.PRLL_API_URL}/api/llm/v1`;
67
- }
68
- else if (source === "custom") {
69
- if (pc?.openai_api_key)
70
- env.OPENAI_API_KEY = pc.openai_api_key;
71
- if (pc?.openai_base_url)
72
- env.OPENAI_BASE_URL = pc.openai_base_url;
73
- }
74
- delete env.PRLL_DAEMON_MODE;
75
33
  return env;
76
34
  },
77
35
  };
78
36
  const defaultAdapter = {
79
37
  bin: "parall-agent",
80
- buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
81
- const env = { ...baseEnv };
82
- env.PRLL_API_KEY = apiKey;
83
- env.PRLL_ORG_ID = orgId;
84
- env.AGENT_ID = agentId;
85
- env.PRLL_AGENT_ID = agentId;
86
- env.PRLL_STATE_DIR = dirs.stateDir;
87
- env.PRLL_WORKSPACE_DIR = dirs.workspaceDir;
88
- delete env.PRLL_DAEMON_MODE;
89
- return env;
90
- },
38
+ buildEnv: buildStandardEnv,
91
39
  };
92
40
  const openclawAdapter = {
93
41
  bin: "parall-openclaw-agent",
94
- buildEnv(baseEnv, agentId, orgId, apiKey, dirs) {
95
- const env = { ...baseEnv };
96
- clearAllProviderCreds(env);
97
- env.PRLL_API_KEY = apiKey;
98
- env.PRLL_ORG_ID = orgId;
99
- env.AGENT_ID = agentId;
100
- env.PRLL_AGENT_ID = agentId;
101
- env.PRLL_OPENCLAW_STATE_DIR = dirs.stateDir;
102
- env.PRLL_OPENCLAW_WORKSPACE_DIR = dirs.workspaceDir;
42
+ buildEnv(baseEnv, agentId, orgId, apiKey, dirs, pc) {
43
+ const env = buildStandardEnv(baseEnv, agentId, orgId, apiKey, dirs, pc);
103
44
  env.OPENCLAW_GATEWAY_PORT = env.OPENCLAW_GATEWAY_PORT || "0";
104
- delete env.PRLL_DAEMON_MODE;
105
45
  return env;
106
46
  },
107
47
  };
@@ -110,8 +50,32 @@ const RUNTIME_ADAPTERS = {
110
50
  "codex": codexAdapter,
111
51
  "openclaw": openclawAdapter,
112
52
  };
53
+ const OVERLAY_BIN_NAMES = {
54
+ "claude-code": "parall-claude-agent.js",
55
+ "codex": "parall-codex-agent.js",
56
+ "openclaw": "parall-openclaw-agent.js",
57
+ };
58
+ /**
59
+ * Resolve a runtime adapter, preferring overlay bundle paths when available.
60
+ * When the daemon has self-updated into ~/.parall-daemon/bundle/, bridge
61
+ * binaries should also load from overlay to keep versions in sync.
62
+ */
113
63
  export function getRuntimeAdapter(runtimeType) {
114
- return RUNTIME_ADAPTERS[runtimeType] ?? defaultAdapter;
64
+ const base = RUNTIME_ADAPTERS[runtimeType] ?? defaultAdapter;
65
+ const overlayName = OVERLAY_BIN_NAMES[runtimeType];
66
+ if (!overlayName)
67
+ return base;
68
+ try {
69
+ const bundleDir = resolveBundleDir();
70
+ const overlayBin = path.join(bundleDir, "current", overlayName);
71
+ if (fs.existsSync(overlayBin)) {
72
+ return { ...base, bin: overlayBin };
73
+ }
74
+ }
75
+ catch {
76
+ // resolveBundleDir may fail in unusual setups; fall through
77
+ }
78
+ return base;
115
79
  }
116
80
  export function assertAgentKey(apiKey) {
117
81
  if (apiKey.startsWith("mck_")) {
@@ -1,5 +1,6 @@
1
1
  import type { GatewayLogger } from "@parall/agent-core";
2
2
  import { ParallClient } from "@parall/sdk";
3
+ import type { DaemonUpdater } from "./updater.js";
3
4
  import { type ClaudeDaemonConfig } from "./config.js";
4
5
  /**
5
6
  * Sleep that wakes early on abort. Returns true if the full delay elapsed,
@@ -33,7 +34,10 @@ export declare class DaemonSupervisor {
33
34
  private machineOrgId;
34
35
  private machineLlmSource;
35
36
  private stopResolve;
37
+ private updater;
38
+ private healthConfirmed;
36
39
  constructor(config: ClaudeDaemonConfig, client: ParallClient, log: GatewayLogger);
40
+ setUpdater(updater: DaemonUpdater): void;
37
41
  /** Start the supervisor. Returns a promise that resolves on `stop()`. */
38
42
  run(signal: AbortSignal): Promise<void>;
39
43
  /** Disconnect WS, cancel timers, SIGTERM all children, await exit. */
@@ -49,6 +53,7 @@ export declare class DaemonSupervisor {
49
53
  private fetchAttachedAgent;
50
54
  private handleAgentAttached;
51
55
  private handleAgentDetached;
56
+ private handleFilesystemBrowse;
52
57
  private handleWorkspaceSetupRequested;
53
58
  private scheduleWorkspaceSetupRetry;
54
59
  private clearWorkspaceSetupRetry;
@@ -1 +1 @@
1
- {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAA2N,MAAM,aAAa,CAAC;AACpQ,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AAiBrB;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAyB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAazB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqC;IAC/E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,WAAW,CAA6B;gBAG7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,aAAa;IAGrC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiF7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAiCb,kBAAkB;YAqClB,aAAa;IAqD3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YA6CX,kBAAkB;YAqBlB,mBAAmB;YAgBnB,mBAAmB;YAmBnB,6BAA6B;IAqC3C,OAAO,CAAC,2BAA2B;IAYnC,OAAO,CAAC,wBAAwB;YAUlB,oBAAoB;YAcpB,kBAAkB;YAYlB,eAAe;YAcf,UAAU;YAoBV,cAAc;IA2D5B,OAAO,CAAC,UAAU;YA0EJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAqR,MAAM,aAAa,CAAC;AAC9T,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EACL,KAAK,kBAAkB,EAMxB,MAAM,aAAa,CAAC;AAiBrB;;;;GAIG;AACH,iBAAS,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAyB5B;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAezB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAhBtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqC;IAC/E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,eAAe,CAAS;gBAGb,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,aAAa;IAGrC,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAIxC,yEAAyE;IACnE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAsH7C,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAiCb,kBAAkB;YAqClB,aAAa;IAqD3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YA6CX,kBAAkB;YAqBlB,mBAAmB;YAgBnB,mBAAmB;YAmBnB,sBAAsB;YAwBtB,6BAA6B;IAqC3C,OAAO,CAAC,2BAA2B;IAYnC,OAAO,CAAC,wBAAwB;YAUlB,oBAAoB;YAcpB,kBAAkB;YAYlB,eAAe;YAcf,UAAU;YAoBV,cAAc;IA2D5B,OAAO,CAAC,UAAU;YA0EJ,cAAc;IAyB5B,OAAO,CAAC,0BAA0B;CA8BnC"}