@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
@@ -1,10 +1,17 @@
1
1
  import type { IpcManifest } from './ipc.js';
2
+ export type DependencyMap = Record<string, {
3
+ package?: string;
4
+ version?: string;
5
+ }>;
2
6
  export interface ClipConfig {
7
+ clipId?: string;
3
8
  name: string;
4
9
  package?: string;
5
10
  version?: string;
6
11
  source: string;
7
12
  path: string;
13
+ sourceType?: string;
14
+ sourceRef?: string;
8
15
  token?: string;
9
16
  manifest?: ManifestCache;
10
17
  }
@@ -17,10 +24,8 @@ export interface ManifestCache {
17
24
  commands: string[];
18
25
  commandDetails: CommandDetail[];
19
26
  hasWeb?: boolean;
20
- dependencies?: Record<string, {
21
- package?: string;
22
- version?: string;
23
- }>;
27
+ dependencies?: DependencyMap;
28
+ dependencySlots?: DependencyMap;
24
29
  patterns?: string[];
25
30
  entities?: Record<string, unknown>;
26
31
  }
@@ -37,6 +42,11 @@ export interface ClipJson {
37
42
  runtime?: string;
38
43
  main?: string;
39
44
  web?: string;
45
+ domain?: string;
46
+ dependencies?: DependencyMap;
47
+ dependency_slots?: DependencyMap;
48
+ patterns?: string[];
49
+ commands?: CommandDetail[];
40
50
  author?: string;
41
51
  license?: string;
42
52
  repository?: string;
@@ -48,10 +58,8 @@ interface ProjectMetadata {
48
58
  domain?: string;
49
59
  main?: string;
50
60
  web?: string;
51
- dependencies?: Record<string, {
52
- package?: string;
53
- version?: string;
54
- }>;
61
+ dependencies?: DependencyMap;
62
+ dependencySlots?: DependencyMap;
55
63
  patterns?: string[];
56
64
  commands?: CommandDetail[];
57
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAQzD;AAYD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAkBrE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,EAAE,CA+C/D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,GAAG,aAAa,CAoBvF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,aAAa,CAevE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAY1D"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/manifest.ts"],"names":[],"mappings":"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"}
@@ -27,8 +27,13 @@ export function loadProjectMetadata(clip) {
27
27
  meta.package = clipJson.name;
28
28
  meta.version = clipJson.version;
29
29
  meta.description = clipJson.description;
30
+ meta.domain = clipJson.domain;
30
31
  meta.main = clipJson.main;
31
32
  meta.web = clipJson.web;
33
+ meta.dependencies = clipJson.dependencies;
34
+ meta.dependencySlots = clipJson.dependency_slots;
35
+ meta.patterns = clipJson.patterns;
36
+ meta.commands = clipJson.commands;
32
37
  }
33
38
  const pkgJson = loadPackageJson(clip.path);
34
39
  if (pkgJson) {
@@ -112,6 +117,13 @@ export function enrichManifest(clip, manifest) {
112
117
  enriched.description = meta.description;
113
118
  if (!enriched.domain && meta.domain)
114
119
  enriched.domain = meta.domain;
120
+ if (!enriched.dependencies && meta.dependencies)
121
+ enriched.dependencies = meta.dependencies;
122
+ if (!enriched.dependencySlots && meta.dependencySlots) {
123
+ enriched.dependencySlots = meta.dependencySlots;
124
+ }
125
+ if (!enriched.patterns && meta.patterns)
126
+ enriched.patterns = meta.patterns;
115
127
  if (meta.commands && meta.commands.length > 0 && enriched.commandDetails.length === 0) {
116
128
  enriched.commandDetails = meta.commands;
117
129
  enriched.commands = meta.commands.map((c) => c.name);
@@ -136,6 +148,7 @@ export function manifestFromIpc(ipcManifest) {
136
148
  commandDetails: details,
137
149
  hasWeb: ipcManifest.has_web,
138
150
  dependencies: ipcManifest.dependencies,
151
+ dependencySlots: ipcManifest.dependency_slots,
139
152
  patterns: ipcManifest.patterns,
140
153
  entities: ipcManifest.entities,
141
154
  };
@@ -1,14 +1,35 @@
1
- import { type ClipProcessStatus } from './process.js';
1
+ import { type ClipInvokeContext, type ClipProcessStatus } from './process.js';
2
2
  import type { ClipConfig, ManifestCache } from './manifest.js';
3
+ import type { BrowserProfileManager } from './browser-profile-manager.js';
4
+ import { type HubClient } from './hub-client.js';
3
5
  export interface ClipProcessManagerOptions {
4
6
  bunPath?: string;
5
7
  clipsDir: string;
6
8
  dataDir: string;
9
+ /**
10
+ * Host-side browser runtime. Present only on daemons that can host a
11
+ * BrowserProfile; drives inbound hub browser InvokeCommands. The execution
12
+ * side never calls this directly — nested browser invokes go out through the
13
+ * hub (see hubClient).
14
+ */
15
+ browserProfileManager?: BrowserProfileManager;
16
+ /**
17
+ * Lazily-supplied hub client used by the execution side to resolve a
18
+ * dependency binding (GetBindings) and forward the dependency invoke
19
+ * (Invoke). Provided by the supervisor once machine identity + the clip
20
+ * service endpoint are resolved; may change on endpoint rollout.
21
+ */
22
+ hubClient?: () => HubClient | null;
23
+ /** Ensure a server-declared clip is installed locally before spawn. */
24
+ ensureInstalled?: (config: ClipConfig) => Promise<ClipConfig>;
7
25
  }
8
26
  export declare class ClipProcessManager {
9
27
  private bunPath;
10
28
  private clipsDir;
11
29
  private dataDir;
30
+ private browserProfileManager?;
31
+ private hubClient?;
32
+ private ensureInstalled?;
12
33
  private processes;
13
34
  private startingUp;
14
35
  private statuses;
@@ -17,6 +38,8 @@ export declare class ClipProcessManager {
17
38
  private statusListeners;
18
39
  private manifestListeners;
19
40
  constructor(opts: ClipProcessManagerOptions);
41
+ /** True when this daemon can host BrowserProfiles (serve inbound hub browser invokes). */
42
+ canHostBrowser(): boolean;
20
43
  /** Backward-compat: set a single status listener (wraps addStatusListener). */
21
44
  set onStatusChange(fn: ((name: string, status: ClipProcessStatus, message: string) => void) | undefined);
22
45
  /** Subscribe to clip status changes. Returns an unsubscribe function. */
@@ -25,6 +48,7 @@ export declare class ClipProcessManager {
25
48
  private notifyManifestUpdate;
26
49
  private resolveBun;
27
50
  registerClip(config: ClipConfig): void;
51
+ replaceClipConfig(config: ClipConfig): Promise<void>;
28
52
  unregisterClip(name: string): Promise<void>;
29
53
  startClip(name: string): Promise<void>;
30
54
  stopClip(name: string): Promise<void>;
@@ -34,8 +58,8 @@ export declare class ClipProcessManager {
34
58
  * Application-level errors (ClipCommandError) are NOT retried.
35
59
  * Corresponds to Pinix ProcessManager.Invoke().
36
60
  */
37
- invoke(name: string, command: string, input?: unknown): Promise<unknown>;
38
- invokeStream(name: string, command: string, input?: unknown, onChunk?: (chunk: unknown) => void): Promise<unknown>;
61
+ invoke(name: string, command: string, input?: unknown, context?: ClipInvokeContext): Promise<unknown>;
62
+ invokeStream(name: string, command: string, input?: unknown, onChunk?: (chunk: unknown) => void, context?: ClipInvokeContext): Promise<unknown>;
39
63
  loadManifest(name: string): Promise<ManifestCache>;
40
64
  clipStatus(name: string): {
41
65
  status: ClipProcessStatus;
@@ -43,6 +67,14 @@ export declare class ClipProcessManager {
43
67
  };
44
68
  isRunning(name: string): boolean;
45
69
  getRegisteredClips(): ClipConfig[];
70
+ /**
71
+ * Clips this daemon advertises to the hub as a provider: the locally
72
+ * installed clips plus, when this machine hosts BrowserProfiles, a synthetic
73
+ * "browser" capability so the hub can route cross-machine browser invokes
74
+ * here. The browser capability has no local subprocess — it is served by
75
+ * invokeBrowserCapability() against bb-browser.
76
+ */
77
+ getProviderClips(): ClipConfig[];
46
78
  /**
47
79
  * Load clip configs from the clips directory.
48
80
  * Reads clip-config.json if present, or scans subdirectories for clip.json files.
@@ -52,6 +84,26 @@ export declare class ClipProcessManager {
52
84
  private spawnProcess;
53
85
  private ensureClipDataDir;
54
86
  private removeIfSame;
87
+ private processKey;
88
+ private processKeyBelongsToName;
89
+ private processNameFromKey;
90
+ /**
91
+ * Execution-side nested dependency invoke (from a child clip's IPC). The
92
+ * browser slot is resolved UNIQUELY via the hub: read the Clip's binding
93
+ * (GetBindings), then forward the invoke through the hub (Invoke) to the bound
94
+ * BrowserProfile's host. No local shortcut, no fallback — unbound is rejected.
95
+ * The execution daemon never verifies clip_token; the hub does.
96
+ * See docs/engineering-design/browser-profile-clip-integration.md §11.3.
97
+ */
98
+ private invokeDependency;
99
+ /**
100
+ * Host-side browser capability handler (inbound hub InvokeCommand for the
101
+ * synthetic "browser" clip). The hub forwards clip_token as the PLAINTEXT
102
+ * browser_profile_id, which the host daemon uses directly as the bb-browser
103
+ * account. Only daemons that host BrowserProfiles register this capability.
104
+ * See docs/engineering-design/browser-profile-clip-integration.md §11.2.
105
+ */
106
+ invokeBrowserCapability(clipToken: string, command: string, input: unknown): Promise<unknown>;
55
107
  private setStatus;
56
108
  }
57
109
  //# sourceMappingURL=process-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAQ/D,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,UAAU,CAA2C;IAC7D,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,eAAe,CAEhB;IACP,OAAO,CAAC,iBAAiB,CAAyC;gBAEtD,IAAI,EAAE,yBAAyB;IAM3C,+EAA+E;IAC/E,IAAI,cAAc,CAAC,EAAE,EACjB,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GACpE,SAAS,EAQZ;IAED,yEAAyE;IACzE,iBAAiB,CACf,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACrE,MAAM,IAAI;IAQb,mBAAmB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAQ/D,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,UAAU;IAOlB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAOhC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBxE,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,OAAO,CAAC,OAAO,CAAC;IAgBb,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAOxD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAMxE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAKhC,kBAAkB,IAAI,UAAU,EAAE;IAIlC;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;YA0D3B,aAAa;YAwBb,YAAY;IAgD1B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,SAAS;CAUlB"}
1
+ {"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"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"}
@@ -2,10 +2,15 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { ClipProcess, ClipCommandError, } from './process.js';
4
4
  import { findBunBinary } from './bun-resolver.js';
5
+ import { BROWSER_CAPABILITY_NAME, BROWSER_CAPABILITY_PACKAGE, browserCapabilityCommands, isBrowserDependencyName, } from './browser-dependency.js';
6
+ import { HubCommandError } from './hub-client.js';
5
7
  export class ClipProcessManager {
6
8
  bunPath;
7
9
  clipsDir;
8
10
  dataDir;
11
+ browserProfileManager;
12
+ hubClient;
13
+ ensureInstalled;
9
14
  processes = new Map();
10
15
  startingUp = new Map();
11
16
  statuses = new Map();
@@ -17,6 +22,13 @@ export class ClipProcessManager {
17
22
  this.bunPath = opts.bunPath ?? '';
18
23
  this.clipsDir = opts.clipsDir;
19
24
  this.dataDir = opts.dataDir;
25
+ this.browserProfileManager = opts.browserProfileManager;
26
+ this.hubClient = opts.hubClient;
27
+ this.ensureInstalled = opts.ensureInstalled;
28
+ }
29
+ /** True when this daemon can host BrowserProfiles (serve inbound hub browser invokes). */
30
+ canHostBrowser() {
31
+ return this.browserProfileManager != null;
20
32
  }
21
33
  /** Backward-compat: set a single status listener (wraps addStatusListener). */
22
34
  set onStatusChange(fn) {
@@ -68,17 +80,26 @@ export class ClipProcessManager {
68
80
  this.setStatus(config.name, 'sleeping', 'registered');
69
81
  }
70
82
  }
83
+ async replaceClipConfig(config) {
84
+ this.registerClip(config);
85
+ await this.stopClip(config.name);
86
+ // An in-flight cold start may have resolved ensureInstalled() for the old
87
+ // config while stopClip was waiting. Re-apply the desired config after the
88
+ // stop window so the next lazy spawn uses the server-declared state.
89
+ this.registerClip(config);
90
+ }
71
91
  async unregisterClip(name) {
72
92
  this.shuttingDown.add(name);
73
93
  try {
74
- const startup = this.startingUp.get(name);
75
- if (startup) {
76
- this.startingUp.delete(name);
77
- await startup.then((proc) => proc.stop()).catch(() => { });
78
- }
79
- if (this.processes.has(name)) {
80
- await this.stopClip(name);
94
+ const startupStops = [];
95
+ for (const [key, startup] of this.startingUp) {
96
+ if (!this.processKeyBelongsToName(key, name))
97
+ continue;
98
+ this.startingUp.delete(key);
99
+ startupStops.push(startup.then((proc) => proc.stop()).catch(() => { }));
81
100
  }
101
+ await Promise.allSettled(startupStops);
102
+ await this.stopClip(name);
82
103
  this.clipConfigs.delete(name);
83
104
  this.statuses.delete(name);
84
105
  }
@@ -92,11 +113,27 @@ export class ClipProcessManager {
92
113
  async stopClip(name) {
93
114
  this.shuttingDown.add(name);
94
115
  try {
95
- const proc = this.processes.get(name);
96
- if (!proc)
116
+ // Also tear down any same-name shards still in their cold-start window.
117
+ // Without this an in-flight startup would complete and re-register into
118
+ // this.processes after stopClip returns, defeating the explicit stop.
119
+ const startupStops = [];
120
+ for (const [key, startup] of this.startingUp) {
121
+ if (!this.processKeyBelongsToName(key, name))
122
+ continue;
123
+ this.startingUp.delete(key);
124
+ startupStops.push(startup.then((proc) => proc.stop()).catch(() => { }));
125
+ }
126
+ const stops = [];
127
+ for (const [key, proc] of this.processes) {
128
+ if (!this.processKeyBelongsToName(key, name))
129
+ continue;
130
+ stops.push(proc.stop().finally(() => {
131
+ this.processes.delete(key);
132
+ }));
133
+ }
134
+ if (stops.length === 0 && startupStops.length === 0)
97
135
  return;
98
- await proc.stop();
99
- this.processes.delete(name);
136
+ await Promise.allSettled([...startupStops, ...stops]);
100
137
  this.setStatus(name, 'sleeping', 'stopped');
101
138
  }
102
139
  finally {
@@ -105,11 +142,19 @@ export class ClipProcessManager {
105
142
  }
106
143
  async stopAll() {
107
144
  // Mark all clips as shutting down before any async work
108
- for (const name of this.processes.keys())
145
+ const names = new Set();
146
+ for (const key of this.processes.keys())
147
+ names.add(this.processNameFromKey(key));
148
+ for (const key of this.startingUp.keys())
149
+ names.add(this.processNameFromKey(key));
150
+ for (const name of names)
109
151
  this.shuttingDown.add(name);
110
- for (const name of this.startingUp.keys())
111
- this.shuttingDown.add(name);
112
- const stops = Array.from(this.processes.keys()).map((name) => this.stopClip(name));
152
+ const stops = [];
153
+ for (const [key, proc] of this.processes) {
154
+ stops.push(proc.stop().finally(() => {
155
+ this.processes.delete(key);
156
+ }));
157
+ }
113
158
  // Also abort any in-flight startups (register window)
114
159
  for (const [, startup] of this.startingUp) {
115
160
  stops.push(startup.then((proc) => proc.stop()).catch(() => { }));
@@ -125,38 +170,40 @@ export class ClipProcessManager {
125
170
  * Application-level errors (ClipCommandError) are NOT retried.
126
171
  * Corresponds to Pinix ProcessManager.Invoke().
127
172
  */
128
- async invoke(name, command, input) {
173
+ async invoke(name, command, input, context = {}) {
174
+ const key = this.processKey(name, context);
129
175
  try {
130
- const proc = await this.ensureProcess(name);
176
+ const proc = await this.ensureProcess(name, context);
131
177
  const result = await proc.invoke(command, input);
132
178
  return result.output;
133
179
  }
134
180
  catch (err) {
135
181
  if (err instanceof ClipCommandError)
136
182
  throw err;
137
- const old = this.processes.get(name);
183
+ const old = this.processes.get(key);
138
184
  if (old?.alive())
139
185
  await old.stop().catch(() => { });
140
- this.processes.delete(name);
141
- const proc = await this.ensureProcess(name);
186
+ this.processes.delete(key);
187
+ const proc = await this.ensureProcess(name, context);
142
188
  const result = await proc.invoke(command, input);
143
189
  return result.output;
144
190
  }
145
191
  }
146
- async invokeStream(name, command, input, onChunk) {
192
+ async invokeStream(name, command, input, onChunk, context = {}) {
193
+ const key = this.processKey(name, context);
147
194
  try {
148
- const proc = await this.ensureProcess(name);
195
+ const proc = await this.ensureProcess(name, context);
149
196
  const result = await proc.invokeStream(command, input, onChunk);
150
197
  return result.output;
151
198
  }
152
199
  catch (err) {
153
200
  if (err instanceof ClipCommandError)
154
201
  throw err;
155
- const old = this.processes.get(name);
202
+ const old = this.processes.get(key);
156
203
  if (old?.alive())
157
204
  await old.stop().catch(() => { });
158
- this.processes.delete(name);
159
- const proc = await this.ensureProcess(name);
205
+ this.processes.delete(key);
206
+ const proc = await this.ensureProcess(name, context);
160
207
  const result = await proc.invokeStream(command, input, onChunk);
161
208
  return result.output;
162
209
  }
@@ -175,12 +222,29 @@ export class ClipProcessManager {
175
222
  return { status: entry.status, message: entry.message };
176
223
  }
177
224
  isRunning(name) {
178
- const proc = this.processes.get(name);
179
- return proc !== undefined && proc.alive();
225
+ for (const [key, proc] of this.processes) {
226
+ if (this.processKeyBelongsToName(key, name) && proc.alive())
227
+ return true;
228
+ }
229
+ return false;
180
230
  }
181
231
  getRegisteredClips() {
182
232
  return Array.from(this.clipConfigs.values());
183
233
  }
234
+ /**
235
+ * Clips this daemon advertises to the hub as a provider: the locally
236
+ * installed clips plus, when this machine hosts BrowserProfiles, a synthetic
237
+ * "browser" capability so the hub can route cross-machine browser invokes
238
+ * here. The browser capability has no local subprocess — it is served by
239
+ * invokeBrowserCapability() against bb-browser.
240
+ */
241
+ getProviderClips() {
242
+ const clips = this.getRegisteredClips();
243
+ if (this.canHostBrowser()) {
244
+ clips.push(browserCapabilityConfig());
245
+ }
246
+ return clips;
247
+ }
184
248
  /**
185
249
  * Load clip configs from the clips directory.
186
250
  * Reads clip-config.json if present, or scans subdirectories for clip.json files.
@@ -210,11 +274,20 @@ export class ClipProcessManager {
210
274
  // Try clip.json first, fall back to package.json
211
275
  const clipJsonPath = path.join(clipDir, 'clip.json');
212
276
  const pkgJsonPath = path.join(clipDir, 'package.json');
277
+ // Key by the install-directory alias, NOT the manifest name. The
278
+ // installer writes to clipsDir/{org alias}/ and the install-time
279
+ // registration (supervisor handleClipInstall) keys by that alias — the
280
+ // hub routes org invokes by it (sess.clips[alias]). Preferring
281
+ // clipJson.name here re-keyed clips by their package name
282
+ // ("@pinix/reddit") after a daemon restart, so org-alias invokes
283
+ // ("reddit") failed with "no online provider" until reinstall (caught
284
+ // on the staging closed-loop E2E, 2026-06-04). The manifest/package
285
+ // identity stays on `package`.
213
286
  if (fs.existsSync(clipJsonPath)) {
214
287
  try {
215
288
  const clipJson = JSON.parse(fs.readFileSync(clipJsonPath, 'utf-8'));
216
289
  this.registerClip({
217
- name: clipJson.name || entry.name,
290
+ name: entry.name,
218
291
  package: clipJson.name,
219
292
  version: clipJson.version,
220
293
  source: clipDir,
@@ -229,7 +302,7 @@ export class ClipProcessManager {
229
302
  try {
230
303
  const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
231
304
  this.registerClip({
232
- name: pkgJson.name || entry.name,
305
+ name: entry.name,
233
306
  package: pkgJson.name,
234
307
  version: pkgJson.version,
235
308
  source: clipDir,
@@ -243,35 +316,43 @@ export class ClipProcessManager {
243
316
  }
244
317
  }
245
318
  // --- internal ---
246
- async ensureProcess(name) {
319
+ async ensureProcess(name, context = {}) {
247
320
  if (this.shuttingDown.has(name)) {
248
321
  throw new Error(`clip "${name}" is shutting down`);
249
322
  }
250
- const existing = this.processes.get(name);
323
+ const key = this.processKey(name, context);
324
+ const existing = this.processes.get(key);
251
325
  if (existing && existing.alive())
252
326
  return existing;
253
327
  // Deduplicate concurrent cold-start requests
254
- const inflight = this.startingUp.get(name);
328
+ const inflight = this.startingUp.get(key);
255
329
  if (inflight)
256
330
  return inflight;
257
- const config = this.clipConfigs.get(name);
258
- if (!config)
259
- throw new Error(`clip "${name}" not registered`);
260
- const startup = this.spawnProcess(config);
261
- this.startingUp.set(name, startup);
331
+ const startup = (async () => {
332
+ let config = this.clipConfigs.get(name);
333
+ if (!config)
334
+ throw new Error(`clip "${name}" not registered`);
335
+ if (this.ensureInstalled) {
336
+ config = await this.ensureInstalled(config);
337
+ this.clipConfigs.set(name, config);
338
+ }
339
+ return this.spawnProcess(config, context);
340
+ })();
341
+ this.startingUp.set(key, startup);
262
342
  try {
263
343
  return await startup;
264
344
  }
265
345
  finally {
266
- this.startingUp.delete(name);
346
+ this.startingUp.delete(key);
267
347
  }
268
348
  }
269
- async spawnProcess(config) {
270
- const proc = new ClipProcess(config);
271
- const clipDataDir = this.ensureClipDataDir(config);
349
+ async spawnProcess(config, context) {
350
+ const proc = new ClipProcess(config, context, (clipName, command, input, invokeContext) => this.invokeDependency(clipName, command, input, invokeContext));
351
+ const clipDataDir = this.ensureClipDataDir(config, context);
272
352
  const bunPath = this.resolveBun();
273
353
  proc.spawn(bunPath, {
274
354
  PINIX_DATA_DIR: clipDataDir,
355
+ PRLL_CLIP_ID: context.clipId ?? '',
275
356
  // Expose the embedded bun's directory on the clip's PATH so a clip that
276
357
  // shells out to `bun` by name resolves it — the daemon's launchd PATH
277
358
  // does not include Frameworks/. Symmetric with installDeps(); relies on
@@ -282,7 +363,7 @@ export class ClipProcessManager {
282
363
  try {
283
364
  await proc.waitReady();
284
365
  // Only expose to concurrent callers after registration completes
285
- this.processes.set(config.name, proc);
366
+ this.processes.set(this.processKey(config.name, context), proc);
286
367
  this.setStatus(config.name, 'running', 'ready');
287
368
  // Always cache manifest so Provider registrations have commands
288
369
  const manifest = proc.getManifest();
@@ -298,7 +379,7 @@ export class ClipProcessManager {
298
379
  }
299
380
  // Monitor process exit in background
300
381
  proc.waitDone().then(() => {
301
- this.removeIfSame(config.name, proc);
382
+ this.removeIfSame(config.name, proc, context);
302
383
  const exitErr = proc.getError();
303
384
  if (exitErr) {
304
385
  this.setStatus(config.name, 'error', exitErr.message);
@@ -309,16 +390,91 @@ export class ClipProcessManager {
309
390
  });
310
391
  return proc;
311
392
  }
312
- ensureClipDataDir(config) {
313
- const dir = path.join(this.dataDir, config.name);
393
+ ensureClipDataDir(config, context = {}) {
394
+ const dir = path.join(this.dataDir, config.name, context.clipId ?? 'default');
314
395
  fs.mkdirSync(dir, { recursive: true });
315
396
  return dir;
316
397
  }
317
- removeIfSame(name, proc) {
318
- if (this.processes.get(name) === proc) {
319
- this.processes.delete(name);
398
+ removeIfSame(name, proc, context = {}) {
399
+ const key = this.processKey(name, context);
400
+ if (this.processes.get(key) === proc) {
401
+ this.processes.delete(key);
402
+ }
403
+ }
404
+ processKey(name, context = {}) {
405
+ return context.clipId ? `${name}::${context.clipId}` : name;
406
+ }
407
+ processKeyBelongsToName(key, name) {
408
+ return key === name || key.startsWith(`${name}::`);
409
+ }
410
+ processNameFromKey(key) {
411
+ const marker = key.indexOf('::');
412
+ return marker === -1 ? key : key.slice(0, marker);
413
+ }
414
+ /**
415
+ * Execution-side nested dependency invoke (from a child clip's IPC). The
416
+ * browser slot is resolved UNIQUELY via the hub: read the Clip's binding
417
+ * (GetBindings), then forward the invoke through the hub (Invoke) to the bound
418
+ * BrowserProfile's host. No local shortcut, no fallback — unbound is rejected.
419
+ * The execution daemon never verifies clip_token; the hub does.
420
+ * See docs/engineering-design/browser-profile-clip-integration.md §11.3.
421
+ */
422
+ async invokeDependency(clipName, command, input, context) {
423
+ if (!isBrowserDependencyName(clipName)) {
424
+ throw new Error(`clip dependency "${clipName}" is not supported`);
425
+ }
426
+ const clipId = context.clipId;
427
+ if (!clipId) {
428
+ // Without the executing Clip identity we cannot resolve the binding. The
429
+ // process key shards by clipId, so this should not happen for a
430
+ // browser-bound Clip.
431
+ throw new ClipCommandError('browser dependency requires a clip binding');
432
+ }
433
+ const hub = this.hubClient?.();
434
+ if (!hub) {
435
+ throw new Error('browser dependency runtime is not configured (no hub client)');
436
+ }
437
+ const bindings = await hub.getBindings(clipId);
438
+ const binding = bindings[BROWSER_CAPABILITY_NAME];
439
+ if (!binding || !binding.clipToken) {
440
+ // Unbound browser slot: surface as a clip-level error so the calling clip
441
+ // gets a recoverable rejection instead of a process crash.
442
+ throw new ClipCommandError(`clip "${clipId}" has no browser binding`, 'CLIP_PROFILE_UNBOUND');
443
+ }
444
+ try {
445
+ return await hub.invoke(BROWSER_CAPABILITY_NAME, command, input, binding.clipToken);
446
+ }
447
+ catch (err) {
448
+ // A hub-flagged application error (e.g. BROWSER_COMMAND_ERROR) is a
449
+ // recoverable clip-level rejection; translate it so the calling clip is
450
+ // not torn down. Transport/internal errors propagate as fatal.
451
+ if (err instanceof HubCommandError) {
452
+ throw new ClipCommandError(err.message, err.code);
453
+ }
454
+ throw err;
320
455
  }
321
456
  }
457
+ /**
458
+ * Host-side browser capability handler (inbound hub InvokeCommand for the
459
+ * synthetic "browser" clip). The hub forwards clip_token as the PLAINTEXT
460
+ * browser_profile_id, which the host daemon uses directly as the bb-browser
461
+ * account. Only daemons that host BrowserProfiles register this capability.
462
+ * See docs/engineering-design/browser-profile-clip-integration.md §11.2.
463
+ */
464
+ async invokeBrowserCapability(clipToken, command, input) {
465
+ if (!this.browserProfileManager) {
466
+ throw new Error('browser capability is not hosted on this machine');
467
+ }
468
+ if (!clipToken) {
469
+ throw new ClipCommandError('browser invoke missing profile identity (clip_token)');
470
+ }
471
+ return this.browserProfileManager.invoke({
472
+ profileId: clipToken,
473
+ account: clipToken,
474
+ command,
475
+ input,
476
+ });
477
+ }
322
478
  setStatus(name, status, message) {
323
479
  this.statuses.set(name, { status, message, since: new Date() });
324
480
  for (const fn of this.statusListeners) {
@@ -331,3 +487,25 @@ export class ClipProcessManager {
331
487
  }
332
488
  }
333
489
  }
490
+ /**
491
+ * Synthetic ClipConfig for the host-side "browser" capability. It has no local
492
+ * subprocess (no `path` to spawn); the hub routes browser invokes here and they
493
+ * are served by ClipProcessManager.invokeBrowserCapability() against bb-browser.
494
+ */
495
+ function browserCapabilityConfig() {
496
+ return {
497
+ name: BROWSER_CAPABILITY_NAME,
498
+ package: BROWSER_CAPABILITY_PACKAGE,
499
+ version: '0.15.0',
500
+ source: BROWSER_CAPABILITY_PACKAGE,
501
+ path: '',
502
+ manifest: {
503
+ name: BROWSER_CAPABILITY_NAME,
504
+ package: BROWSER_CAPABILITY_PACKAGE,
505
+ version: '0.15.0',
506
+ description: 'Profile-bound browser capability (bb-browser-pro)',
507
+ commands: browserCapabilityCommands().map((c) => c.name),
508
+ commandDetails: browserCapabilityCommands(),
509
+ },
510
+ };
511
+ }