@parall/daemon 1.32.1 → 1.34.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.
Files changed (46) hide show
  1. package/bundle/bb-browser-daemon.js +15628 -0
  2. package/bundle/buildDomTree.js +1501 -0
  3. package/bundle/manifest.json +19 -11
  4. package/bundle/parall-claude-agent.js +185 -25
  5. package/bundle/parall-codex-agent.js +185 -25
  6. package/bundle/parall-daemon.js +4647 -2856
  7. package/bundle/parall-openclaw-agent.js +4 -3
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +8 -2
  10. package/dist/clip-runtime/browser-dependency.d.ts +20 -0
  11. package/dist/clip-runtime/browser-dependency.d.ts.map +1 -0
  12. package/dist/clip-runtime/browser-dependency.js +52 -0
  13. package/dist/clip-runtime/browser-profile-manager.d.ts +70 -0
  14. package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -0
  15. package/dist/clip-runtime/browser-profile-manager.js +608 -0
  16. package/dist/clip-runtime/clip-provider.d.ts +10 -1
  17. package/dist/clip-runtime/clip-provider.d.ts.map +1 -1
  18. package/dist/clip-runtime/clip-provider.js +63 -21
  19. package/dist/clip-runtime/hub-client.d.ts +79 -0
  20. package/dist/clip-runtime/hub-client.d.ts.map +1 -0
  21. package/dist/clip-runtime/hub-client.js +320 -0
  22. package/dist/clip-runtime/index.d.ts +2 -0
  23. package/dist/clip-runtime/index.d.ts.map +1 -1
  24. package/dist/clip-runtime/index.js +2 -0
  25. package/dist/clip-runtime/ipc.d.ts +6 -0
  26. package/dist/clip-runtime/ipc.d.ts.map +1 -1
  27. package/dist/clip-runtime/manifest.d.ts +16 -8
  28. package/dist/clip-runtime/manifest.d.ts.map +1 -1
  29. package/dist/clip-runtime/manifest.js +13 -0
  30. package/dist/clip-runtime/process-manager.d.ts +55 -3
  31. package/dist/clip-runtime/process-manager.d.ts.map +1 -1
  32. package/dist/clip-runtime/process-manager.js +226 -48
  33. package/dist/clip-runtime/process.d.ts +15 -1
  34. package/dist/clip-runtime/process.d.ts.map +1 -1
  35. package/dist/clip-runtime/process.js +73 -10
  36. package/dist/config.d.ts +9 -0
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +12 -0
  39. package/dist/index.js +46 -5
  40. package/dist/runtime-bin-resolver.d.ts +7 -0
  41. package/dist/runtime-bin-resolver.d.ts.map +1 -0
  42. package/dist/runtime-bin-resolver.js +292 -0
  43. package/dist/supervisor.d.ts +53 -4
  44. package/dist/supervisor.d.ts.map +1 -1
  45. package/dist/supervisor.js +449 -117
  46. package/package.json +7 -6
@@ -12,11 +12,24 @@ export interface InvokeEvent {
12
12
  export interface InvokeResult {
13
13
  output: unknown;
14
14
  }
15
+ export interface ClipInvokeContext {
16
+ /**
17
+ * The executing Clip id. It shards the process pool and fixes the caller
18
+ * identity for nested dependency invokes, so the execution side can resolve
19
+ * the Clip's browser binding via the hub. Browser
20
+ * profile / account identity is NOT carried here — it is resolved from the
21
+ * hub binding (GetBindings) and rides on clip_token end-to-end.
22
+ */
23
+ clipId?: string;
24
+ }
25
+ export type DependencyInvokeResolver = (clipName: string, command: string, input: unknown, context: ClipInvokeContext) => Promise<unknown>;
15
26
  export declare class ClipCommandError extends Error {
16
27
  readonly code?: string;
17
28
  constructor(message: string, code?: string);
18
29
  }
19
30
  export declare class ClipProcess {
31
+ private readonly context;
32
+ private readonly dependencyResolver?;
20
33
  readonly clip: ClipConfig;
21
34
  private child;
22
35
  private reader;
@@ -32,7 +45,7 @@ export declare class ClipProcess {
32
45
  private doneDeferred;
33
46
  private doneResolved;
34
47
  private exitError;
35
- constructor(clip: ClipConfig);
48
+ constructor(clip: ClipConfig, context?: ClipInvokeContext, dependencyResolver?: DependencyInvokeResolver | undefined);
36
49
  /**
37
50
  * Spawn the Bun subprocess and start the IPC read loop.
38
51
  * Corresponds to Pinix startLocked().
@@ -51,6 +64,7 @@ export declare class ClipProcess {
51
64
  private handleRegister;
52
65
  private dispatchInvokeEvent;
53
66
  private handleData;
67
+ private handleDependencyInvoke;
54
68
  private send;
55
69
  private signalReady;
56
70
  private finish;
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAInB,MAAM,eAAe,CAAC;AAevB,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBACX,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAK3C;AAgBD,qBAAa,WAAW;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAE1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAA6B;IAE3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,OAAO,CAAmD;IAClE,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAoB;gBAEzB,IAAI,EAAE,UAAU;IAI5B;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAwCnD,SAAS,CAAC,SAAS,GAAE,MAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;IAe5E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAInB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;IAI/D,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,OAAO,CAAC,YAAY,CAAC;IA2DlB,IAAI,CAAC,SAAS,GAAE,MAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBnE,KAAK,IAAI,OAAO;IAShB,WAAW,IAAI,aAAa,GAAG,IAAI;IAInC,QAAQ,IAAI,KAAK,GAAG,SAAS;YAMf,QAAQ;IAWtB,OAAO,CAAC,aAAa;IA6CrB,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,UAAU;YASJ,IAAI;IAKlB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,MAAM;IAiCd,OAAO,CAAC,KAAK;CAOd"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAInB,MAAM,eAAe,CAAC;AAmBvB,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,wBAAwB,GAAG,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBACX,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAK3C;AAgBD,qBAAa,WAAW;IAsBpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAtBtC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAE1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAA6B;IAE3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,OAAO,CAAmD;IAClE,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAoB;gBAGnC,IAAI,EAAE,UAAU,EACC,OAAO,GAAE,iBAAsB,EAC/B,kBAAkB,CAAC,EAAE,wBAAwB,YAAA;IAKhE;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAwCnD,SAAS,CAAC,SAAS,GAAE,MAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;IAe5E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAInB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;IAI/D,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,OAAO,CAAC,YAAY,CAAC;IA2DlB,IAAI,CAAC,SAAS,GAAE,MAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBnE,KAAK,IAAI,OAAO;IAShB,WAAW,IAAI,aAAa,GAAG,IAAI;IAInC,QAAQ,IAAI,KAAK,GAAG,SAAS;YAMf,QAAQ;IAWtB,OAAO,CAAC,aAAa;IAqDrB,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,UAAU;YASJ,sBAAsB;YAsDtB,IAAI;IAKlB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,MAAM;IAiCd,OAAO,CAAC,KAAK;CAOd"}
@@ -2,7 +2,8 @@ import { spawn } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline';
3
3
  import { clearAllProviderCreds } from '../runtimes.js';
4
4
  import { NdjsonReader, NdjsonWriter, MessageType } from './ipc.js';
5
- import { manifestFromIpc, enrichManifest, resolveEntrypoint, } from './manifest.js';
5
+ import { enrichManifest, manifestFromIpc, resolveEntrypoint, } from './manifest.js';
6
+ import { manifestDeclaresBrowserDependency, manifestMapsDependencyToBrowser, } from './browser-dependency.js';
6
7
  const CLIP_REGISTER_TIMEOUT_MS = 10_000;
7
8
  const CLIP_STOP_TIMEOUT_MS = 5_000;
8
9
  function sanitizeEnvForClip(extra) {
@@ -32,6 +33,8 @@ function makeDeferred() {
32
33
  return { promise, resolve, reject };
33
34
  }
34
35
  export class ClipProcess {
36
+ context;
37
+ dependencyResolver;
35
38
  clip;
36
39
  child = null;
37
40
  reader = null;
@@ -47,7 +50,9 @@ export class ClipProcess {
47
50
  doneDeferred = makeDeferred();
48
51
  doneResolved = false;
49
52
  exitError;
50
- constructor(clip) {
53
+ constructor(clip, context = {}, dependencyResolver) {
54
+ this.context = context;
55
+ this.dependencyResolver = dependencyResolver;
51
56
  this.clip = clip;
52
57
  }
53
58
  /**
@@ -231,20 +236,27 @@ export class ClipProcess {
231
236
  this.handleData(msg);
232
237
  break;
233
238
  case MessageType.ListClips:
234
- // Clip-to-clip list request respond with empty for now (Phase 2)
239
+ // Advertise the "browser" dependency to the child whenever its manifest
240
+ // declares it. Binding resolution + cross-machine routing happen at
241
+ // invoke time via the hub (GetBindings → Invoke); an unbound or
242
+ // unavailable profile surfaces as an invoke-time error, not here.
235
243
  this.send({
236
244
  id: msg.id,
237
245
  type: MessageType.ListClipsResult,
238
- clips: [],
246
+ clips: this.context.clipId && manifestDeclaresBrowserDependency(this.manifest)
247
+ ? [
248
+ {
249
+ name: 'browser',
250
+ package: '@pinixai/bb-browser-pro',
251
+ version: '0.15.0',
252
+ description: 'Profile-bound browser capability',
253
+ },
254
+ ]
255
+ : [],
239
256
  }).catch(() => { });
240
257
  break;
241
258
  case MessageType.Invoke:
242
- // Clip-to-clip invoke — not routed in Phase 1
243
- this.send({
244
- id: msg.id,
245
- type: MessageType.Error,
246
- error: 'clip-to-clip invoke not supported yet',
247
- }).catch(() => { });
259
+ void this.handleDependencyInvoke(msg);
248
260
  break;
249
261
  case MessageType.Heartbeat:
250
262
  break;
@@ -291,6 +303,8 @@ export class ClipProcess {
291
303
  event.output = msg.output;
292
304
  if (msg.error)
293
305
  event.error = { message: msg.error };
306
+ if (msg.processExit || msg.process_exit)
307
+ event.processExit = true;
294
308
  callback(event);
295
309
  }
296
310
  handleData(msg) {
@@ -301,6 +315,55 @@ export class ClipProcess {
301
315
  error: 'data operations not implemented yet',
302
316
  }).catch(() => { });
303
317
  }
318
+ async handleDependencyInvoke(msg) {
319
+ const dependencyName = msg.clip ?? msg.alias ?? '';
320
+ if (!msg.id) {
321
+ console.warn(`[clip:${this.clip.name}] dependency invoke missing id`);
322
+ return;
323
+ }
324
+ if (!dependencyName || !msg.command) {
325
+ await this.send({
326
+ id: msg.id,
327
+ type: MessageType.Error,
328
+ error: 'dependency clip and command are required',
329
+ }).catch(() => { });
330
+ return;
331
+ }
332
+ if (!manifestMapsDependencyToBrowser(this.manifest, dependencyName)) {
333
+ await this.send({
334
+ id: msg.id,
335
+ type: MessageType.Error,
336
+ error: 'browser dependency is not declared by this clip',
337
+ }).catch(() => { });
338
+ return;
339
+ }
340
+ if (!this.dependencyResolver) {
341
+ await this.send({
342
+ id: msg.id,
343
+ type: MessageType.Error,
344
+ error: 'clip dependency invoke is not configured',
345
+ }).catch(() => { });
346
+ return;
347
+ }
348
+ try {
349
+ const output = await this.dependencyResolver('browser', msg.command, msg.input ?? {}, this.context);
350
+ await this.send({
351
+ id: msg.id,
352
+ type: MessageType.Result,
353
+ output,
354
+ });
355
+ }
356
+ catch (err) {
357
+ const isCommandError = err instanceof ClipCommandError;
358
+ await this.send({
359
+ id: msg.id,
360
+ type: MessageType.Error,
361
+ error: err instanceof Error ? err.message : String(err),
362
+ processExit: !isCommandError,
363
+ process_exit: !isCommandError,
364
+ }).catch(() => { });
365
+ }
366
+ }
304
367
  async send(msg) {
305
368
  if (!this.writer)
306
369
  throw new Error('writer not initialized');
package/dist/config.d.ts CHANGED
@@ -54,6 +54,15 @@ export type ClaudeDaemonConfig = {
54
54
  /** Disable self-update entirely (K8s env auto-disables). */
55
55
  updateDisabled: boolean;
56
56
  };
57
+ /**
58
+ * Root dir for all on-disk daemon state: `config.json`, the self-update
59
+ * `bundle/` overlay (versions + current symlink + update-state.json), and
60
+ * bridge-bin resolution all derive from this single path. `PRLL_DAEMON_CONFIG_DIR`
61
+ * overrides it so multiple daemons can coexist on one host without colliding —
62
+ * e.g. a staging desktop install points its launchd agent at
63
+ * `~/.parall-daemon-staging` while prod keeps the default `~/.parall-daemon`.
64
+ * Unset → default (no behavior change for existing single-env installs).
65
+ */
57
66
  export declare function daemonConfigDir(env?: NodeJS.ProcessEnv): string;
58
67
  export declare function daemonConfigPath(env?: NodeJS.ProcessEnv): string;
59
68
  export declare function resolveClaudeDaemonConfig(env?: NodeJS.ProcessEnv): ClaudeDaemonConfig;
@@ -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;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,CACvC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,kBAAkB,CA0DpB;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,CAC1B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAMR"}
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;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI5E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE7E;AAyBD,wBAAgB,yBAAyB,CACvC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,kBAAkB,CA0DpB;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,CAC1B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAMR"}
package/dist/config.js CHANGED
@@ -24,7 +24,19 @@ function parseMsAllowZero(value, fallback) {
24
24
  const n = Number(value);
25
25
  return Number.isFinite(n) && n >= 0 ? n : fallback;
26
26
  }
27
+ /**
28
+ * Root dir for all on-disk daemon state: `config.json`, the self-update
29
+ * `bundle/` overlay (versions + current symlink + update-state.json), and
30
+ * bridge-bin resolution all derive from this single path. `PRLL_DAEMON_CONFIG_DIR`
31
+ * overrides it so multiple daemons can coexist on one host without colliding —
32
+ * e.g. a staging desktop install points its launchd agent at
33
+ * `~/.parall-daemon-staging` while prod keeps the default `~/.parall-daemon`.
34
+ * Unset → default (no behavior change for existing single-env installs).
35
+ */
27
36
  export function daemonConfigDir(env = process.env) {
37
+ const override = env.PRLL_DAEMON_CONFIG_DIR?.trim();
38
+ if (override)
39
+ return resolvePath(override);
28
40
  return path.join(env.HOME || os.homedir(), '.parall-daemon');
29
41
  }
30
42
  export function daemonConfigPath(env = process.env) {
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import * as path from 'node:path';
2
3
  import { createLogger, createOtelLogger, initAgentTelemetry, } from '@parall/agent-core';
3
4
  import { ParallClient } from '@parall/sdk';
4
- import { resolveClaudeDaemonConfig, resolveBundleDir, resolveWsUrl, } from './config.js';
5
+ import { runCLI } from './cli.js';
6
+ import { resolveBundleDir, resolveClaudeDaemonConfig, resolveWsUrl, } from './config.js';
5
7
  import { DaemonSupervisor, sleepCancellable } from './supervisor.js';
6
8
  import { DaemonUpdater } from './updater.js';
7
- import { runCLI } from './cli.js';
8
9
  const UPDATE_EXIT_CODE = 42;
9
10
  let log = createLogger('daemon');
10
11
  function formatError(reason) {
@@ -13,6 +14,39 @@ function formatError(reason) {
13
14
  }
14
15
  return String(reason);
15
16
  }
17
+ /**
18
+ * Self-update should run only when a service manager supervises the daemon —
19
+ * `parall-daemon service install` (launchd/systemd) or the hosted entrypoint.sh
20
+ * keepalive loop. Those wrappers re-exec the overlay bundle on restart, and
21
+ * exit(UPDATE_EXIT_CODE) is precisely what triggers that restart so the new code
22
+ * loads.
23
+ *
24
+ * A bare `npx @parall/daemon` foreground run has neither property: it never
25
+ * exec's the overlay (so a downloaded bundle is dead weight on disk — this very
26
+ * process keeps running the npm launcher code), and nothing restarts it after
27
+ * exit(42). Self-updating there would just kill the daemon with no recovery. So
28
+ * we gate on two positive "managed" signals:
29
+ * - PRLL_DAEMON_MANAGED=1 — injected into the plist / systemd unit by
30
+ * `service install`; the authoritative marker.
31
+ * - launched from the overlay bundle dir — a service wrapper already exec'd
32
+ * `…/.parall-daemon/bundle/current/parall-daemon.js`. Covers service
33
+ * installs predating the PRLL_DAEMON_MANAGED marker.
34
+ * Foreground users stay current via `npx @parall/daemon@latest`, which pulls the
35
+ * newest launcher on every run.
36
+ */
37
+ function isSelfUpdateManaged(env, bundleDir) {
38
+ if (env.PRLL_DAEMON_MANAGED === '1' || env.PRLL_DAEMON_MANAGED === 'true')
39
+ return true;
40
+ const entry = process.argv[1];
41
+ if (entry) {
42
+ const resolvedEntry = path.resolve(entry);
43
+ const resolvedBundle = path.resolve(bundleDir);
44
+ if (resolvedEntry === resolvedBundle || resolvedEntry.startsWith(resolvedBundle + path.sep)) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
16
50
  /**
17
51
  * Outer keepalive: runs `supervisor.run()` in a loop, restarting it with
18
52
  * exponential backoff if it rejects. The daemon process only exits when
@@ -61,7 +95,7 @@ async function runForever(config, client, log, signal, updater) {
61
95
  log.error('supervisor keepalive disabled (PRLL_DAEMON_SUPERVISOR_RESTART_BACKOFF_MS=0) — exiting');
62
96
  throw err;
63
97
  }
64
- const delay = Math.min(config.supervisorRestartBackoffMs * Math.pow(2, attempt), config.supervisorRestartBackoffMaxMs);
98
+ const delay = Math.min(config.supervisorRestartBackoffMs * 2 ** attempt, config.supervisorRestartBackoffMaxMs);
65
99
  attempt += 1;
66
100
  log.warn(`restarting supervisor in ${delay}ms (attempt ${attempt})`);
67
101
  const slept = await sleepCancellable(delay, signal);
@@ -77,9 +111,13 @@ async function main() {
77
111
  log.info(`boot: api=${config.apiUrl} pollMs=${config.pollIntervalMs} heartbeatMs=${config.heartbeatIntervalMs} agentBin=${config.agentBin}`);
78
112
  log.info(`keepalive: bootstrapBackoffMs=${config.bootstrapBackoffMs} supervisorRestartBackoffMs=${config.supervisorRestartBackoffMs}`);
79
113
  // --- Self-update: rollback check (before any network calls) ---
114
+ // Only run when a service manager supervises us (see isSelfUpdateManaged):
115
+ // a bare `npx` foreground run can't load the overlay and won't be restarted
116
+ // after exit(42), so self-update there would just kill the daemon.
80
117
  let updater = null;
81
- if (!config.updateDisabled) {
82
- const bundleDir = resolveBundleDir(process.env);
118
+ const bundleDir = resolveBundleDir(process.env);
119
+ const selfUpdateEnabled = !config.updateDisabled && isSelfUpdateManaged(process.env, bundleDir);
120
+ if (selfUpdateEnabled) {
83
121
  updater = new DaemonUpdater(bundleDir, config.updateCdnUrl, log, true);
84
122
  if (updater.checkRollback()) {
85
123
  log.info('rollback applied — exiting for service manager restart');
@@ -88,6 +126,9 @@ async function main() {
88
126
  }
89
127
  log.info(`update: bundleDir=${bundleDir} cdn=${config.updateCdnUrl} interval=${config.updateIntervalMs}ms`);
90
128
  }
129
+ else if (!config.updateDisabled) {
130
+ log.info('update: skipped (foreground mode) — run `parall-daemon service install` for managed auto-update');
131
+ }
91
132
  // The daemon talks to the API as a Machine — the bearer is mck_*.
92
133
  // No orgId is configured here; per-agent subprocesses get their
93
134
  // own org_id via the spawn env.
@@ -0,0 +1,7 @@
1
+ type RuntimeBinLogger = {
2
+ info: (msg: string) => void;
3
+ warn: (msg: string) => void;
4
+ };
5
+ export declare function applyRuntimeBinaryEnv(runtimeType: string, baseEnv: NodeJS.ProcessEnv, log: RuntimeBinLogger): NodeJS.ProcessEnv;
6
+ export {};
7
+ //# sourceMappingURL=runtime-bin-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-bin-resolver.d.ts","sourceRoot":"","sources":["../src/runtime-bin-resolver.ts"],"names":[],"mappings":"AAUA,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,CAAC;AA0BF,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,UAAU,EAC1B,GAAG,EAAE,gBAAgB,GACpB,MAAM,CAAC,UAAU,CAmCnB"}
@@ -0,0 +1,292 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ const RUNTIME_BINARIES = {
6
+ 'claude-code': { command: 'claude', envVar: 'PRLL_CLAUDE_BIN' },
7
+ codex: { command: 'codex', envVar: 'PRLL_CODEX_BIN' },
8
+ openclaw: { command: 'openclaw', envVar: 'PRLL_OPENCLAW_BIN' },
9
+ };
10
+ const RESOLUTION_CACHE_TTL_MS = 5 * 60_000;
11
+ const resolutionCache = new Map();
12
+ export function applyRuntimeBinaryEnv(runtimeType, baseEnv, log) {
13
+ const env = { ...baseEnv };
14
+ const originalPath = env.PATH;
15
+ const pathPlan = candidatePathPlan(env);
16
+ const primaryPath = mergePath(pathPlan.primaryDirs, originalPath);
17
+ env.PATH = mergePath([...pathPlan.primaryDirs, ...pathPlan.versionedFallbackDirs], originalPath);
18
+ const spec = RUNTIME_BINARIES[runtimeType];
19
+ if (!spec)
20
+ return env;
21
+ const configured = env[spec.envVar]?.trim();
22
+ if (configured) {
23
+ const resolved = resolveRuntimeCommand(configured, env, originalPath, primaryPath, env.PATH);
24
+ if (resolved) {
25
+ env[spec.envVar] = resolved.binaryPath;
26
+ env.PATH = anchorResolvedPath(env.PATH, resolved);
27
+ log.info(`runtime ${runtimeType}: using ${spec.envVar}=${resolved.binaryPath}`);
28
+ }
29
+ else {
30
+ log.warn(`runtime ${runtimeType}: configured ${spec.envVar}=${configured} is not executable`);
31
+ }
32
+ return env;
33
+ }
34
+ const resolved = resolveRuntimeCommand(spec.command, env, originalPath, primaryPath, env.PATH);
35
+ if (resolved) {
36
+ env[spec.envVar] = resolved.binaryPath;
37
+ env.PATH = anchorResolvedPath(env.PATH, resolved);
38
+ log.info(`runtime ${runtimeType}: resolved ${spec.command} at ${resolved.binaryPath}`);
39
+ }
40
+ else {
41
+ log.warn(`runtime ${runtimeType}: could not resolve ${spec.command}; child will rely on PATH ` +
42
+ `(${env.PATH ?? '<empty>'})`);
43
+ }
44
+ return env;
45
+ }
46
+ function resolveRuntimeCommand(command, env, inheritedPath, primaryPath, fallbackPath) {
47
+ const cacheKey = `runtime\0${command}\0${primaryPath}\0${fallbackPath}\0${env.SHELL ?? ''}\0${env.HOME ?? ''}`;
48
+ const cached = getCachedResolution(cacheKey);
49
+ if (cached)
50
+ return cached;
51
+ const direct = resolveDirectPath(command);
52
+ if (direct) {
53
+ const resolved = { binaryPath: direct };
54
+ setCachedResolution(cacheKey, resolved);
55
+ return resolved;
56
+ }
57
+ const fromInheritedPath = resolveFromPath(command, inheritedPath, env);
58
+ if (fromInheritedPath) {
59
+ setCachedResolution(cacheKey, fromInheritedPath);
60
+ return fromInheritedPath;
61
+ }
62
+ const fromShell = resolveFromLoginShell(command, { ...env, PATH: inheritedPath });
63
+ if (fromShell) {
64
+ setCachedResolution(cacheKey, fromShell);
65
+ return fromShell;
66
+ }
67
+ const fromPrimaryPath = resolveFromPath(command, primaryPath, env);
68
+ if (fromPrimaryPath) {
69
+ setCachedResolution(cacheKey, fromPrimaryPath);
70
+ return fromPrimaryPath;
71
+ }
72
+ const fromFallbackPath = fallbackPath === primaryPath ? null : resolveFromPath(command, fallbackPath, env);
73
+ if (fromFallbackPath) {
74
+ setCachedResolution(cacheKey, fromFallbackPath);
75
+ return fromFallbackPath;
76
+ }
77
+ return null;
78
+ }
79
+ function resolveDirectPath(command) {
80
+ if (!command.includes('/') && !command.includes('\\'))
81
+ return null;
82
+ const abs = path.isAbsolute(command) ? command : path.resolve(process.cwd(), command);
83
+ return isExecutable(abs) ? abs : null;
84
+ }
85
+ function resolveFromPath(command, pathValue, env) {
86
+ if (!pathValue || command.includes('/') || command.includes('\\'))
87
+ return null;
88
+ const dirs = pathValue.split(path.delimiter).filter(Boolean);
89
+ for (const dir of dirs) {
90
+ for (const file of commandCandidates(command, env)) {
91
+ const candidate = path.join(dir, file);
92
+ if (isExecutable(candidate))
93
+ return { binaryPath: candidate, pathValue };
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ function resolveFromLoginShell(command, env) {
99
+ if (command.includes('/') || command.includes('\\'))
100
+ return null;
101
+ const shells = unique([
102
+ env.SHELL?.trim(),
103
+ process.platform === 'darwin' ? '/bin/zsh' : undefined,
104
+ '/bin/bash',
105
+ '/bin/sh',
106
+ ]);
107
+ for (const shell of shells) {
108
+ if (!shell || !isExecutable(shell))
109
+ continue;
110
+ try {
111
+ const out = execFileSync(shell, [
112
+ '-lic',
113
+ `resolved=$(command -v ${shellQuote(command)}) || exit $?; printf '__PRLL_BIN__%s\n__PRLL_PATH__%s\n' "$resolved" "$PATH"`,
114
+ ], {
115
+ encoding: 'utf8',
116
+ env,
117
+ stdio: ['ignore', 'pipe', 'pipe'],
118
+ timeout: 2_500,
119
+ });
120
+ let binaryPath = '';
121
+ let pathValue = '';
122
+ for (const line of out.split(/\r?\n/)) {
123
+ if (line.startsWith('__PRLL_BIN__'))
124
+ binaryPath = line.slice('__PRLL_BIN__'.length).trim();
125
+ if (line.startsWith('__PRLL_PATH__'))
126
+ pathValue = line.slice('__PRLL_PATH__'.length);
127
+ }
128
+ if (path.isAbsolute(binaryPath) && isExecutable(binaryPath)) {
129
+ return { binaryPath, pathValue: pathValue || undefined };
130
+ }
131
+ }
132
+ catch {
133
+ // Shell startup files may be noisy or slow. Keep resolution best-effort.
134
+ }
135
+ }
136
+ return null;
137
+ }
138
+ function candidatePathPlan(env) {
139
+ const home = env.HOME || os.homedir();
140
+ const primaryDirs = [
141
+ ...splitPath(env.PRLL_DAEMON_RUNTIME_PATH),
142
+ ...splitPath(env.PRLL_DAEMON_EXTRA_PATH),
143
+ path.dirname(process.execPath),
144
+ path.join(path.dirname(process.execPath), 'bin'),
145
+ path.resolve(path.dirname(process.execPath), '..', 'Resources', 'bin'),
146
+ path.join(home, '.local', 'bin'),
147
+ path.join(home, 'bin'),
148
+ path.join(home, '.npm-global', 'bin'),
149
+ path.join(home, 'Library', 'pnpm'),
150
+ path.join(home, '.local', 'share', 'pnpm'),
151
+ path.join(home, '.volta', 'bin'),
152
+ path.join(home, '.bun', 'bin'),
153
+ path.join(home, '.asdf', 'shims'),
154
+ path.join(home, '.local', 'share', 'mise', 'shims'),
155
+ path.join(home, '.mise', 'shims'),
156
+ path.join(home, '.fnm', 'aliases', 'default', 'bin'),
157
+ path.join(home, 'Library', 'Application Support', 'fnm', 'aliases', 'default', 'bin'),
158
+ '/opt/homebrew/bin',
159
+ '/usr/local/bin',
160
+ '/usr/bin',
161
+ '/bin',
162
+ '/usr/sbin',
163
+ '/sbin',
164
+ '/snap/bin',
165
+ ];
166
+ const versionedFallbackDirs = [
167
+ ...nvmVersionBinDirs(home),
168
+ ...fnmVersionBinDirs(home),
169
+ ];
170
+ return {
171
+ primaryDirs: unique(primaryDirs).filter((dir) => !!dir && isDirectory(dir)),
172
+ versionedFallbackDirs: unique(versionedFallbackDirs).filter((dir) => !!dir && isDirectory(dir)),
173
+ };
174
+ }
175
+ function nvmVersionBinDirs(home) {
176
+ const root = path.join(home, '.nvm', 'versions', 'node');
177
+ let versions;
178
+ try {
179
+ versions = fs.readdirSync(root);
180
+ }
181
+ catch {
182
+ return [];
183
+ }
184
+ return sortVersionNamesDesc(versions).map((version) => path.join(root, version, 'bin'));
185
+ }
186
+ function fnmVersionBinDirs(home) {
187
+ const roots = [
188
+ path.join(home, '.fnm', 'node-versions'),
189
+ path.join(home, 'Library', 'Application Support', 'fnm', 'node-versions'),
190
+ ];
191
+ const dirs = [];
192
+ for (const root of roots) {
193
+ let versions;
194
+ try {
195
+ versions = fs.readdirSync(root);
196
+ }
197
+ catch {
198
+ continue;
199
+ }
200
+ dirs.push(...sortVersionNamesDesc(versions).map((version) => path.join(root, version, 'installation', 'bin')));
201
+ }
202
+ return dirs;
203
+ }
204
+ function sortVersionNamesDesc(values) {
205
+ return [...values].sort((a, b) => compareVersionName(b, a));
206
+ }
207
+ function compareVersionName(a, b) {
208
+ const av = parseVersionName(a);
209
+ const bv = parseVersionName(b);
210
+ const max = Math.max(av.length, bv.length);
211
+ for (let i = 0; i < max; i += 1) {
212
+ const diff = (av[i] ?? 0) - (bv[i] ?? 0);
213
+ if (diff !== 0)
214
+ return diff;
215
+ }
216
+ return a.localeCompare(b);
217
+ }
218
+ function parseVersionName(value) {
219
+ return value
220
+ .replace(/^v/i, '')
221
+ .split('.')
222
+ .map((part) => Number.parseInt(part, 10))
223
+ .filter((part) => Number.isFinite(part));
224
+ }
225
+ function splitPath(value) {
226
+ return value?.split(path.delimiter).filter(Boolean) ?? [];
227
+ }
228
+ function mergePath(prependDirs, existing) {
229
+ return unique([...prependDirs, ...splitPath(existing)]).join(path.delimiter);
230
+ }
231
+ function anchorResolvedPath(pathValue, resolution) {
232
+ return mergePath([path.dirname(resolution.binaryPath), ...splitPath(resolution.pathValue)], pathValue);
233
+ }
234
+ function commandCandidates(command, env) {
235
+ if (process.platform !== 'win32')
236
+ return [command];
237
+ const hasExt = /\.[^\\/]+$/.test(command);
238
+ if (hasExt)
239
+ return [command];
240
+ const exts = (env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
241
+ .split(';')
242
+ .filter(Boolean)
243
+ .map((ext) => ext.toLowerCase());
244
+ return [command, ...exts.map((ext) => `${command}${ext}`)];
245
+ }
246
+ function getCachedResolution(cacheKey) {
247
+ const entry = resolutionCache.get(cacheKey);
248
+ if (!entry)
249
+ return null;
250
+ if (entry.expiresAt <= Date.now()) {
251
+ resolutionCache.delete(cacheKey);
252
+ return null;
253
+ }
254
+ return entry.value;
255
+ }
256
+ function setCachedResolution(cacheKey, value) {
257
+ resolutionCache.set(cacheKey, { value, expiresAt: Date.now() + RESOLUTION_CACHE_TTL_MS });
258
+ }
259
+ function isExecutable(file) {
260
+ try {
261
+ const stat = fs.statSync(file);
262
+ if (!stat.isFile())
263
+ return false;
264
+ fs.accessSync(file, fs.constants.X_OK);
265
+ return true;
266
+ }
267
+ catch {
268
+ return false;
269
+ }
270
+ }
271
+ function isDirectory(dir) {
272
+ try {
273
+ return fs.statSync(dir).isDirectory();
274
+ }
275
+ catch {
276
+ return false;
277
+ }
278
+ }
279
+ function unique(values) {
280
+ const seen = new Set();
281
+ const out = [];
282
+ for (const value of values) {
283
+ if (!value || seen.has(value))
284
+ continue;
285
+ seen.add(value);
286
+ out.push(value);
287
+ }
288
+ return out;
289
+ }
290
+ function shellQuote(value) {
291
+ return `'${value.replaceAll("'", "'\\''")}'`;
292
+ }