@parall/daemon 1.36.1 → 1.37.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 (56) hide show
  1. package/bundle/manifest.json +11 -11
  2. package/bundle/parall-browser-pod.js +307 -27
  3. package/bundle/parall-claude-agent.js +363 -50
  4. package/bundle/parall-codex-agent.js +362 -48
  5. package/bundle/parall-daemon.js +33473 -31706
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +2 -2
  8. package/dist/clip-runtime/browser-daemon-env.d.ts +29 -0
  9. package/dist/clip-runtime/browser-daemon-env.d.ts.map +1 -0
  10. package/dist/clip-runtime/browser-daemon-env.js +70 -0
  11. package/dist/clip-runtime/browser-profile-manager.d.ts +18 -5
  12. package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
  13. package/dist/clip-runtime/browser-profile-manager.js +149 -24
  14. package/dist/clip-runtime/browser-profile-pool.d.ts +250 -0
  15. package/dist/clip-runtime/browser-profile-pool.d.ts.map +1 -0
  16. package/dist/clip-runtime/browser-profile-pool.js +581 -0
  17. package/dist/clip-runtime/index.d.ts +2 -2
  18. package/dist/clip-runtime/index.d.ts.map +1 -1
  19. package/dist/clip-runtime/index.js +2 -1
  20. package/dist/clip-runtime/process-manager.d.ts +5 -3
  21. package/dist/clip-runtime/process-manager.d.ts.map +1 -1
  22. package/dist/clip-runtime/subprocess.d.ts +14 -0
  23. package/dist/clip-runtime/subprocess.d.ts.map +1 -1
  24. package/dist/clip-runtime/subprocess.js +49 -0
  25. package/dist/config.d.ts +14 -15
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +8 -26
  28. package/dist/daemon-main.d.ts +8 -0
  29. package/dist/daemon-main.d.ts.map +1 -0
  30. package/dist/daemon-main.js +165 -0
  31. package/dist/daemon-paths.d.ts +14 -0
  32. package/dist/daemon-paths.d.ts.map +1 -0
  33. package/dist/daemon-paths.js +26 -0
  34. package/dist/daemon-update-mode.d.ts +8 -0
  35. package/dist/daemon-update-mode.d.ts.map +1 -0
  36. package/dist/daemon-update-mode.js +18 -0
  37. package/dist/index.js +41 -167
  38. package/dist/runtime-bin-resolver.d.ts +4 -0
  39. package/dist/runtime-bin-resolver.d.ts.map +1 -1
  40. package/dist/runtime-bin-resolver.js +40 -7
  41. package/dist/runtime-detector.d.ts +30 -0
  42. package/dist/runtime-detector.d.ts.map +1 -0
  43. package/dist/runtime-detector.js +100 -0
  44. package/dist/supervisor.d.ts +53 -2
  45. package/dist/supervisor.d.ts.map +1 -1
  46. package/dist/supervisor.js +604 -67
  47. package/dist/update-health-gate.d.ts +66 -0
  48. package/dist/update-health-gate.d.ts.map +1 -0
  49. package/dist/update-health-gate.js +93 -0
  50. package/dist/updater-manifest.d.ts +2 -1
  51. package/dist/updater-manifest.d.ts.map +1 -1
  52. package/dist/updater-manifest.js +38 -7
  53. package/dist/updater.d.ts +13 -2
  54. package/dist/updater.d.ts.map +1 -1
  55. package/dist/updater.js +126 -17
  56. package/package.json +6 -6
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.36.1",
3
- "built_at": "2026-06-26T10:09:22.541Z",
2
+ "version": "1.37.0",
3
+ "built_at": "2026-07-05T18:08:07.005Z",
4
4
  "min_node_version": "20.0.0",
5
5
  "files": {
6
6
  "bb-browser-daemon.js": {
@@ -16,25 +16,25 @@
16
16
  "size": 18
17
17
  },
18
18
  "parall-browser-pod.js": {
19
- "sha256": "a9f602f001380c7fdfd2db7e7109343f07267a1acd6c020f5802a949769cd9fb",
20
- "size": 1682059
19
+ "sha256": "2c6b8429e4c071ca05115399012eedaf23da69962cd693fd05a65fde959f0e0c",
20
+ "size": 1694822
21
21
  },
22
22
  "parall-claude-agent.js": {
23
- "sha256": "f38cb2274682403d735e9ea3772d56fcfeee9636a1866eb599e41b4a23075ea6",
24
- "size": 1574204
23
+ "sha256": "4e48c965fa659f8a6780229f17dfdfecb612378dfe207f0c68a50060c5d57976",
24
+ "size": 1591442
25
25
  },
26
26
  "parall-codex-agent.js": {
27
- "sha256": "ceee4b4852cb9cc058dbb70022c8fcf81fd802d1bff709d063386b95a9d501e4",
28
- "size": 1605214
27
+ "sha256": "2da2dfdb17ceee2ac3e7da7ffd61bd2e9f3c437142378560c03108f87d681ee6",
28
+ "size": 1622518
29
29
  },
30
30
  "parall-daemon.js": {
31
- "sha256": "a7cae49ec5efb258f60e35f9a03b71a9edbd947886b0b037c761aaa36bf9af79",
32
- "size": 1612971
31
+ "sha256": "fe5a959f1b828721f6c5e667fbf0154b10e0c51c291658538e5d91263cf95dce",
32
+ "size": 1714111
33
33
  },
34
34
  "parall-openclaw-agent.js": {
35
35
  "sha256": "505433b4bf3a4b8dbb7b96978d8693d6c48079f4513282ff1ee59a6d13b3eddf",
36
36
  "size": 9801
37
37
  }
38
38
  },
39
- "signature": "P/Lgycpu5bMlOGiMzEto4lrKQuoz4c17B0IJ0xmyP6n6DBpuUIEEGJZKs9lQtkz1kVhEgQ4dN9IipxLIZ6hhCg=="
39
+ "signature": "Wgi0cpQWhO2PzfVA73VYOkOCWc6NJJKLiC780OhOA4ttfY+qVXq3KWgZjU2v3GfVbV3Ukh2X0q1WnlshNyJVDQ=="
40
40
  }
@@ -8055,8 +8055,8 @@ var init_requestBuilder = __esm({
8055
8055
  async build() {
8056
8056
  const { hostname, protocol = "https", port, path: basePath } = await this.context.endpoint();
8057
8057
  this.path = basePath;
8058
- for (const resolvePath of this.resolvePathStack) {
8059
- resolvePath(this.path);
8058
+ for (const resolvePath2 of this.resolvePathStack) {
8059
+ resolvePath2(this.path);
8060
8060
  }
8061
8061
  return new import_protocol_http5.HttpRequest({
8062
8062
  protocol,
@@ -35113,6 +35113,7 @@ var ENDPOINTS = {
35113
35113
  // Org-scoped
35114
35114
  ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
35115
35115
  ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
35116
+ ORG_MEMBERS_FORMER: (orgId) => `${API_BASE}/orgs/${orgId}/members/former`,
35116
35117
  TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
35117
35118
  ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
35118
35119
  ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
@@ -35262,6 +35263,19 @@ var ENDPOINTS = {
35262
35263
  EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
35263
35264
  EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
35264
35265
  EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
35266
+ // External IM channel (org-scoped, platform-mediated Feishu/Slack)
35267
+ CHANNEL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/channel-connections`,
35268
+ CHANNEL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}`,
35269
+ CHANNEL_CONNECTION_CREDENTIALS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/credentials`,
35270
+ CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/ingress-token/regenerate`,
35271
+ CHANNEL_CONNECTION_CONVERSATIONS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/conversations`,
35272
+ CHANNEL_CONVERSATION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}`,
35273
+ CHANNEL_CONVERSATION_MESSAGES: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/messages`,
35274
+ CHANNEL_CONVERSATION_SESSION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/session`,
35275
+ CHANNEL_MESSAGE: (orgId, messageId) => `${API_BASE}/orgs/${orgId}/channel-messages/${messageId}`,
35276
+ CHANNEL_PROVISIONING: (orgId) => `${API_BASE}/orgs/${orgId}/channel-provisioning`,
35277
+ CHANNEL_PROVISIONING_SESSION: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}`,
35278
+ CHANNEL_PROVISIONING_CANCEL: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}/cancel`,
35265
35279
  // Invitations (org-scoped, admin)
35266
35280
  ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
35267
35281
  ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
@@ -35279,9 +35293,16 @@ var ENDPOINTS = {
35279
35293
  INVITE_LINK_JOIN: `${API_BASE}/invite-link/join`,
35280
35294
  // Wikis (org-scoped, served by wiki-service)
35281
35295
  WIKIS: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
35296
+ // Recycle bin — soft-deleted wikis (owner only). Must precede WIKI in the
35297
+ // backend router so `deleted` isn't captured as a {wikiId}; the SDK builder
35298
+ // is just a string.
35299
+ WIKIS_DELETED: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis/deleted`,
35300
+ // Detail URL — also reused for DELETE (soft-delete) and `${WIKI}/restore`.
35282
35301
  WIKI: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
35302
+ WIKI_RESTORE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restore`,
35283
35303
  WIKI_TREE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
35284
35304
  WIKI_BLOB: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
35305
+ WIKI_COPY: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/copy`,
35285
35306
  WIKI_NODE_SECTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/node-sections`,
35286
35307
  WIKI_SEARCH: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/search`,
35287
35308
  WIKI_PAGE_INDEX: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/page-index`,
@@ -35364,6 +35385,14 @@ var ENDPOINTS = {
35364
35385
  BILLING_AUTO_RELOAD: (orgId) => `${API_BASE}/orgs/${orgId}/billing/auto-reload`,
35365
35386
  BILLING_SETUP_INTENT: (orgId) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
35366
35387
  COMPUTE_PRICING: () => `${API_BASE}/billing/compute-pricing`,
35388
+ // Runtime capability table (public, no auth) — SSOT for the create/settings
35389
+ // interlock: per-runtime compute modes, native model family, cross-family
35390
+ // availability, and recommended model.
35391
+ RUNTIMES: () => `${API_BASE}/runtimes`,
35392
+ // Model catalog (public, no auth) — served from the server's DB-backed
35393
+ // snapshot. Optional include_model keeps an agent's pinned hidden legacy
35394
+ // model representable in settings pickers.
35395
+ MODELS: (includeModel) => includeModel ? `${API_BASE}/models?include_model=${encodeURIComponent(includeModel)}` : `${API_BASE}/models`,
35367
35396
  // ADMIN_GRANTS intentionally NOT exported here — it sits under the
35368
35397
  // unauthenticated `/internal/admin/*` surface and must not bleed into the
35369
35398
  // public SDK. Admin dashboard hits the URL directly from its own client.
@@ -35376,6 +35405,7 @@ var ENDPOINTS = {
35376
35405
  AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
35377
35406
  CLIP_INVOKE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
35378
35407
  CLIP_ONLINE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/online`,
35408
+ BROWSER_RUNTIME_STATUS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
35379
35409
  BROWSER_PROFILES: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
35380
35410
  BROWSER_PROFILE: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
35381
35411
  BROWSER_PROFILE_OPEN: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/open`,
@@ -35720,6 +35750,13 @@ var ParallClient = class _ParallClient {
35720
35750
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
35721
35751
  return res.data;
35722
35752
  }
35753
+ /** User IDs of soft-removed (former) org members — for marking their avatar /
35754
+ * name as "left" where dormant relations still surface them (DMs, task
35755
+ * assignees, message history). */
35756
+ async getFormerMemberIds(orgId) {
35757
+ const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_FORMER(orgId));
35758
+ return res.user_ids ?? [];
35759
+ }
35723
35760
  async getTeams(orgId) {
35724
35761
  const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
35725
35762
  return res.data;
@@ -36208,20 +36245,25 @@ var ParallClient = class _ParallClient {
36208
36245
  return res.data;
36209
36246
  }
36210
36247
  /** `POST /machines/me/browser-profiles/{profileId}/status` — report runtime status. */
36211
- async reportBrowserProfileStatus(profileId, status, errorMsg) {
36248
+ async reportBrowserProfileStatus(profileId, status, errorMsg, generation) {
36212
36249
  await this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
36213
36250
  status,
36214
- ...errorMsg ? { error_msg: errorMsg } : {}
36251
+ ...errorMsg ? { error_msg: errorMsg } : {},
36252
+ ...generation !== void 0 ? { generation } : {}
36215
36253
  });
36216
36254
  }
36217
36255
  /**
36218
36256
  * `POST /machines/me/health` — bump the Machine's `updated_at` to now and
36219
- * report daemon state. The daemon should call this on a fixed cadence (e.g.
36220
- * every 30s) so an external observer can detect a wedged supervisor. Both
36257
+ * report daemon state. The daemon calls this on startup and again whenever
36258
+ * periodic runtime re-detection produces a CHANGED result (there is no
36259
+ * fixed-cadence keepalive loop today — steady state posts nothing). All
36221
36260
  * fields are optional and only persisted when changed:
36222
36261
  * - `daemonVersion` — the running bundle/launcher version.
36223
36262
  * - `selfUpdateCapable` — whether the daemon can act on a machine.update
36224
36263
  * signal (true under a service manager, false for bare `npx` foreground).
36264
+ * - `detectedRuntimes` — runtime CLIs found on the host. Omitted = no
36265
+ * report this beat (server keeps the stored value); [] = detection ran
36266
+ * and found nothing (server clears to empty).
36225
36267
  */
36226
36268
  async postMachineHeartbeat(opts) {
36227
36269
  const body = {};
@@ -36229,6 +36271,8 @@ var ParallClient = class _ParallClient {
36229
36271
  body.daemon_version = opts.daemonVersion;
36230
36272
  if (opts?.selfUpdateCapable !== void 0)
36231
36273
  body.self_update_capable = opts.selfUpdateCapable;
36274
+ if (opts?.detectedRuntimes !== void 0)
36275
+ body.detected_runtimes = opts.detectedRuntimes;
36232
36276
  return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, Object.keys(body).length > 0 ? body : void 0);
36233
36277
  }
36234
36278
  async reportAgentWorkspaceState(agentId, state2) {
@@ -36547,6 +36591,59 @@ var ParallClient = class _ParallClient {
36547
36591
  async deleteExternalConnection(orgId, connectionId) {
36548
36592
  return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
36549
36593
  }
36594
+ // ---- External IM channel (org-scoped) ----
36595
+ async createChannelConnection(orgId, input) {
36596
+ return this.request("POST", ENDPOINTS.CHANNEL_CONNECTIONS(orgId), input);
36597
+ }
36598
+ async listChannelConnections(orgId) {
36599
+ return this.request("GET", ENDPOINTS.CHANNEL_CONNECTIONS(orgId));
36600
+ }
36601
+ async getChannelConnection(orgId, connectionId) {
36602
+ return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
36603
+ }
36604
+ async updateChannelConnection(orgId, connectionId, patch) {
36605
+ return this.request("PATCH", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId), patch);
36606
+ }
36607
+ async archiveChannelConnection(orgId, connectionId) {
36608
+ return this.request("DELETE", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
36609
+ }
36610
+ async deliverChannelCredentials(orgId, connectionId, credentials) {
36611
+ return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_CREDENTIALS(orgId, connectionId), credentials);
36612
+ }
36613
+ async regenerateChannelIngressToken(orgId, connectionId) {
36614
+ return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
36615
+ }
36616
+ /** Start a Feishu one-click provisioning session (device flow QR). */
36617
+ async initiateChannelProvisioning(orgId, input) {
36618
+ return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING(orgId), input);
36619
+ }
36620
+ /**
36621
+ * Lazy status poll — server-side this may forward one provider poll, so
36622
+ * call it at the session's `poll_interval_seconds` cadence, not faster.
36623
+ */
36624
+ async getChannelProvisioningSession(orgId, sessionId) {
36625
+ return this.request("GET", ENDPOINTS.CHANNEL_PROVISIONING_SESSION(orgId, sessionId));
36626
+ }
36627
+ async cancelChannelProvisioning(orgId, sessionId) {
36628
+ return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING_CANCEL(orgId, sessionId));
36629
+ }
36630
+ async listChannelConversations(orgId, connectionId) {
36631
+ return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
36632
+ }
36633
+ async getChannelConversation(orgId, conversationId) {
36634
+ return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION(orgId, conversationId));
36635
+ }
36636
+ async listChannelConversationMessages(orgId, conversationId, limit) {
36637
+ return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION_MESSAGES(orgId, conversationId), void 0, { limit });
36638
+ }
36639
+ async setChannelConversationSession(orgId, conversationId, agentSessionId) {
36640
+ return this.request("PATCH", ENDPOINTS.CHANNEL_CONVERSATION_SESSION(orgId, conversationId), {
36641
+ agent_session_id: agentSessionId
36642
+ });
36643
+ }
36644
+ async getChannelMessage(orgId, messageId) {
36645
+ return this.request("GET", ENDPOINTS.CHANNEL_MESSAGE(orgId, messageId));
36646
+ }
36550
36647
  async getExternalTriggerSchema(orgId, connectionId) {
36551
36648
  return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
36552
36649
  }
@@ -36588,6 +36685,23 @@ var ParallClient = class _ParallClient {
36588
36685
  async getWiki(orgId, wikiId) {
36589
36686
  return this.request("GET", ENDPOINTS.WIKI(orgId, wikiId));
36590
36687
  }
36688
+ /** Soft-delete an entire wiki (org owner only; the default wiki is
36689
+ * protected → 403 WIKI_PROTECTED). Returns the deleted wiki. Recoverable
36690
+ * via restoreWiki within the retention window. */
36691
+ async deleteWiki(orgId, wikiId) {
36692
+ return this.request("DELETE", ENDPOINTS.WIKI(orgId, wikiId));
36693
+ }
36694
+ /** Restore a soft-deleted wiki (org owner only). 409 WIKI_PURGE_STARTED once
36695
+ * purge has been claimed (no longer restorable), or 404 once the row is gone.
36696
+ * Returns the restored wiki. */
36697
+ async restoreWiki(orgId, wikiId) {
36698
+ return this.request("POST", ENDPOINTS.WIKI_RESTORE(orgId, wikiId));
36699
+ }
36700
+ /** Recycle bin — list soft-deleted wikis for the org (owner only). */
36701
+ async getDeletedWikis(orgId) {
36702
+ const res = await this.request("GET", ENDPOINTS.WIKIS_DELETED(orgId));
36703
+ return res.data;
36704
+ }
36591
36705
  async getWikiTree(orgId, wikiId, params) {
36592
36706
  return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
36593
36707
  }
@@ -36621,6 +36735,15 @@ var ParallClient = class _ParallClient {
36621
36735
  blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
36622
36736
  return blob;
36623
36737
  }
36738
+ /**
36739
+ * Copy one wiki file to another path as an independent snapshot (no sync).
36740
+ * Used to "share a private file out": copy a file from the caller's personal
36741
+ * namespace (`users/{userId}/…`) into the public wiki, leaving the original
36742
+ * untouched. 409 `FILE_EXISTS` if `dest_path` already exists.
36743
+ */
36744
+ async copyWikiFile(orgId, wikiId, req) {
36745
+ return this.request("POST", ENDPOINTS.WIKI_COPY(orgId, wikiId), req);
36746
+ }
36624
36747
  async getWikiNodeSections(orgId, wikiId, params) {
36625
36748
  return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
36626
36749
  }
@@ -36894,6 +37017,19 @@ var ParallClient = class _ParallClient {
36894
37017
  const resp = await this.request("GET", ENDPOINTS.COMPUTE_PRICING());
36895
37018
  return resp.data;
36896
37019
  }
37020
+ /** Runtime capability table (public) — SSOT for the create/settings interlock. */
37021
+ async getRuntimes() {
37022
+ const resp = await this.request("GET", ENDPOINTS.RUNTIMES());
37023
+ return resp.data;
37024
+ }
37025
+ /**
37026
+ * Platform model catalog (public) — DB-backed, replaces the compile-time
37027
+ * PLATFORM_MODELS constant. Pass includeModel to keep an agent's pinned
37028
+ * hidden legacy model representable (settings picker).
37029
+ */
37030
+ async getModels(includeModel) {
37031
+ return this.request("GET", ENDPOINTS.MODELS(includeModel));
37032
+ }
36897
37033
  // ---- Clips ----
36898
37034
  async listClips(orgId) {
36899
37035
  const resp = await this.request("GET", ENDPOINTS.CLIPS(orgId));
@@ -36938,6 +37074,11 @@ var ParallClient = class _ParallClient {
36938
37074
  const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
36939
37075
  return resp.data;
36940
37076
  }
37077
+ /** Deployment-wide hosted browser runtime availability — drives whether the
37078
+ * create UI offers the platform-hosted placement. Fail-closed server-side. */
37079
+ async getBrowserRuntimeStatus(orgId) {
37080
+ return this.request("GET", ENDPOINTS.BROWSER_RUNTIME_STATUS(orgId));
37081
+ }
36941
37082
  /** Org-wide browser-profile discovery list. Returns the sanitized
36942
37083
  * {@link BrowserProfileListItem} shape (not the full domain model), each row
36943
37084
  * carrying a per-viewer `can_open` control hint. */
@@ -37046,6 +37187,50 @@ import { createRequire } from "node:module";
37046
37187
  import * as path from "node:path";
37047
37188
  import { fileURLToPath } from "node:url";
37048
37189
 
37190
+ // ts/daemon/dist/clip-runtime/browser-daemon-env.js
37191
+ var BROWSER_DAEMON_STRIPPED_ENV = [
37192
+ "PRLL_API_KEY",
37193
+ // machine key (mck_) — BYOC + agent control plane
37194
+ "PRLL_PROVIDER_CONFIG",
37195
+ // JSON ProviderConfig blob — may carry BYO LLM api keys
37196
+ "PRLL_ASSIGNMENT_TOKEN",
37197
+ // hosted pod platform assignment token (pba_)
37198
+ "PRLL_POOL_TOKEN",
37199
+ // hosted warm-pool bootstrap token (pba_)
37200
+ "AWS_ACCESS_KEY_ID",
37201
+ // hosted S3 browser-state STS creds (browser-pod.ts)
37202
+ "AWS_SECRET_ACCESS_KEY",
37203
+ "AWS_SESSION_TOKEN",
37204
+ // AWS credential-RESOLUTION vars: the SDK derives creds from these too (IRSA
37205
+ // web-identity, ECS/EKS container creds, named profile, on-disk cred/config
37206
+ // files), so a child inheriting them could assume the pod's role even without
37207
+ // the static keys above. bb-browser uses none of them.
37208
+ "AWS_WEB_IDENTITY_TOKEN_FILE",
37209
+ "AWS_ROLE_ARN",
37210
+ "AWS_ROLE_SESSION_NAME",
37211
+ "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
37212
+ "AWS_CONTAINER_CREDENTIALS_FULL_URI",
37213
+ "AWS_CONTAINER_AUTHORIZATION_TOKEN",
37214
+ "AWS_PROFILE",
37215
+ "AWS_SHARED_CREDENTIALS_FILE",
37216
+ "AWS_CONFIG_FILE",
37217
+ "AWS_REGION",
37218
+ // hosted browser-state config — bb-browser does not use it
37219
+ "PRLL_BROWSER_STATE_REGION",
37220
+ // browser-pod AWS_REGION fallback — same config family
37221
+ "PRLL_BROWSER_STATE_BUCKET",
37222
+ "PRLL_STATE_GENERATION",
37223
+ "PRLL_BROWSER_STATE_ENDPOINT"
37224
+ ];
37225
+ function buildBrowserDaemonEnv(baseEnv, homeDir) {
37226
+ const env = { ...baseEnv, BB_BROWSER_HOME: homeDir };
37227
+ clearAllProviderCreds(env);
37228
+ for (const key of BROWSER_DAEMON_STRIPPED_ENV) {
37229
+ delete env[key];
37230
+ }
37231
+ return env;
37232
+ }
37233
+
37049
37234
  // ts/daemon/dist/clip-runtime/browser-viewer-streamer.js
37050
37235
  import { spawn } from "node:child_process";
37051
37236
 
@@ -37075,6 +37260,41 @@ async function findFreePort() {
37075
37260
  function sleep(ms) {
37076
37261
  return new Promise((resolve2) => setTimeout(resolve2, ms));
37077
37262
  }
37263
+ function waitForChildExit(child, log, graceMs = 5e3, hardCapMs = 15e3) {
37264
+ if (child.exitCode !== null || child.signalCode !== null)
37265
+ return Promise.resolve();
37266
+ return new Promise((resolve2) => {
37267
+ let killTimer;
37268
+ let capTimer;
37269
+ const finish = () => {
37270
+ child.removeListener("exit", finish);
37271
+ if (killTimer)
37272
+ clearTimeout(killTimer);
37273
+ if (capTimer)
37274
+ clearTimeout(capTimer);
37275
+ resolve2();
37276
+ };
37277
+ child.once("exit", finish);
37278
+ try {
37279
+ child.kill("SIGTERM");
37280
+ } catch {
37281
+ }
37282
+ killTimer = setTimeout(() => {
37283
+ if (child.exitCode === null && child.signalCode === null) {
37284
+ try {
37285
+ child.kill("SIGKILL");
37286
+ } catch {
37287
+ }
37288
+ }
37289
+ }, graceMs);
37290
+ capTimer = setTimeout(() => {
37291
+ log?.warn(`child did not exit within ${hardCapMs}ms after SIGKILL; proceeding`);
37292
+ finish();
37293
+ }, hardCapMs);
37294
+ killTimer.unref?.();
37295
+ capTimer.unref?.();
37296
+ });
37297
+ }
37078
37298
 
37079
37299
  // ts/daemon/dist/clip-runtime/browser-viewer-streamer.js
37080
37300
  var BB_VIEWER_HEALTH_TIMEOUT_MS = 1e4;
@@ -37653,6 +37873,7 @@ function filterTabListToAccount(result, account) {
37653
37873
 
37654
37874
  // ts/daemon/dist/clip-runtime/browser-profile-manager.js
37655
37875
  var BB_BROWSER_DAEMON_START_TIMEOUT_MS = 15e3;
37876
+ var BB_BROWSER_DAEMON_START_ATTEMPTS = 3;
37656
37877
  var BrowserCommandError = class extends Error {
37657
37878
  method;
37658
37879
  account;
@@ -37668,6 +37889,11 @@ var BrowserProfileManager = class {
37668
37889
  daemon = null;
37669
37890
  starting = null;
37670
37891
  restarting = null;
37892
+ // Set by stop() and never reset (stop() is terminal — the pool disposes the
37893
+ // manager and the hosted pod exits). Makes a startDaemon() that resolves after
37894
+ // stop() began un-adoptable, so a late child can't be reassigned to this.daemon
37895
+ // and escape teardown.
37896
+ stopping = false;
37671
37897
  ensuredAccounts = /* @__PURE__ */ new Set();
37672
37898
  ensuringAccounts = /* @__PURE__ */ new Map();
37673
37899
  reportedStatuses = /* @__PURE__ */ new Map();
@@ -37718,9 +37944,12 @@ var BrowserProfileManager = class {
37718
37944
  throw err;
37719
37945
  }
37720
37946
  }
37721
- async ensureRuntime(profileId) {
37947
+ async ensureRuntime(profileId, opts = {}) {
37722
37948
  if (!profileId)
37723
37949
  throw new Error("browser profile id is required");
37950
+ if (opts.forceStatusReport) {
37951
+ this.reportedStatuses.delete(profileId);
37952
+ }
37724
37953
  try {
37725
37954
  await this.withDaemonRecovery(() => this.ensureAccount(profileId), `ensure runtime for ${profileId}`);
37726
37955
  this.reportStatus(profileId, "running");
@@ -37769,22 +37998,36 @@ var BrowserProfileManager = class {
37769
37998
  }
37770
37999
  }
37771
38000
  async stop() {
38001
+ this.stopping = true;
37772
38002
  this.viewer.shutdown();
38003
+ const inflight = this.starting;
38004
+ const restarting = this.restarting;
37773
38005
  const daemon = this.daemon;
37774
38006
  this.daemon = null;
37775
38007
  this.starting = null;
37776
38008
  this.ensuredAccounts.clear();
37777
38009
  this.ensuringAccounts.clear();
37778
38010
  this.reportedStatuses.clear();
37779
- if (!daemon)
37780
- return;
37781
- try {
37782
- await this.post("/shutdown", daemon, void 0, 3e3);
37783
- } catch {
38011
+ if (restarting) {
38012
+ try {
38013
+ await restarting;
38014
+ } catch {
38015
+ }
37784
38016
  }
37785
- if (daemon.child.exitCode === null && daemon.child.signalCode === null) {
37786
- daemon.child.kill("SIGTERM");
38017
+ let started = null;
38018
+ if (inflight) {
38019
+ try {
38020
+ started = await inflight;
38021
+ } catch {
38022
+ }
37787
38023
  }
38024
+ await Promise.all([daemon, started].filter((d5) => d5 !== null).map(async (d5) => {
38025
+ try {
38026
+ await this.post("/shutdown", d5, void 0, 3e3);
38027
+ } catch {
38028
+ }
38029
+ await waitForChildExit(d5.child, this.opts.log);
38030
+ }));
37788
38031
  }
37789
38032
  /**
37790
38033
  * Viewer control command entrypoint — delegates to BrowserViewerStreamer
@@ -38010,17 +38253,28 @@ var BrowserProfileManager = class {
38010
38253
  async ensureDaemon() {
38011
38254
  if (this.restarting)
38012
38255
  await this.restarting;
38256
+ if (this.stopping)
38257
+ throw new Error("browser profile manager is stopping");
38013
38258
  if (this.daemon && this.daemon.child.exitCode === null && this.daemon.child.signalCode === null) {
38014
38259
  return this.daemon;
38015
38260
  }
38016
- if (this.starting)
38017
- return this.starting;
38261
+ if (this.starting) {
38262
+ const started = await this.starting;
38263
+ if (this.stopping)
38264
+ throw new Error("browser profile manager is stopping");
38265
+ return started;
38266
+ }
38018
38267
  this.starting = this.startDaemon();
38268
+ const startPromise = this.starting;
38019
38269
  try {
38020
- this.daemon = await this.starting;
38270
+ const started = await startPromise;
38271
+ if (this.stopping)
38272
+ throw new Error("browser profile manager is stopping");
38273
+ this.daemon = started;
38021
38274
  return this.daemon;
38022
38275
  } finally {
38023
- this.starting = null;
38276
+ if (this.starting === startPromise)
38277
+ this.starting = null;
38024
38278
  }
38025
38279
  }
38026
38280
  async withDaemonRecovery(operation2, label) {
@@ -38056,20 +38310,46 @@ var BrowserProfileManager = class {
38056
38310
  await this.post("/shutdown", daemon, void 0, 3e3);
38057
38311
  } catch {
38058
38312
  }
38059
- if (daemon.child.exitCode === null && daemon.child.signalCode === null) {
38060
- daemon.child.kill("SIGTERM");
38061
- }
38313
+ await waitForChildExit(daemon.child, this.opts.log);
38062
38314
  }
38063
38315
  async startDaemon() {
38316
+ let lastErr;
38317
+ for (let attempt = 1; attempt <= BB_BROWSER_DAEMON_START_ATTEMPTS; attempt++) {
38318
+ try {
38319
+ return await this.startDaemonAttempt();
38320
+ } catch (err) {
38321
+ lastErr = err;
38322
+ if (attempt < BB_BROWSER_DAEMON_START_ATTEMPTS) {
38323
+ this.opts.log.warn(`[bb-browser] daemon start attempt ${attempt} failed; retrying with fresh ports: ${String(err)}`);
38324
+ }
38325
+ }
38326
+ }
38327
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
38328
+ }
38329
+ async startDaemonAttempt() {
38064
38330
  const host = "127.0.0.1";
38065
38331
  const port = await findFreePort();
38332
+ let cdpPort = await findFreePort();
38333
+ for (let i5 = 0; cdpPort === port && i5 < 5; i5++) {
38334
+ cdpPort = await findFreePort();
38335
+ }
38336
+ if (cdpPort === port) {
38337
+ throw new Error("bb-browser: could not allocate distinct HTTP and CDP ports");
38338
+ }
38066
38339
  const token = randomToken();
38067
38340
  const daemonPath = resolveBbBrowserDaemonPath();
38068
- const child = spawn2(process.execPath, [daemonPath, "--host", host, "--port", String(port), "--token", token], {
38069
- env: {
38070
- ...process.env,
38071
- BB_BROWSER_HOME: this.opts.homeDir
38072
- },
38341
+ const child = spawn2(process.execPath, [
38342
+ daemonPath,
38343
+ "--host",
38344
+ host,
38345
+ "--port",
38346
+ String(port),
38347
+ "--token",
38348
+ token,
38349
+ "--cdp-port",
38350
+ String(cdpPort)
38351
+ ], {
38352
+ env: buildBrowserDaemonEnv(process.env, this.opts.homeDir),
38073
38353
  stdio: ["ignore", "ignore", "pipe"]
38074
38354
  });
38075
38355
  let childError = null;
@@ -38098,7 +38378,7 @@ var BrowserProfileManager = class {
38098
38378
  await sleep(200);
38099
38379
  }
38100
38380
  }
38101
- child.kill("SIGTERM");
38381
+ await waitForChildExit(child, this.opts.log);
38102
38382
  throw new Error("bb-browser-daemon did not start in time");
38103
38383
  }
38104
38384
  async post(pathName, daemon, body, timeoutMs = 1e4) {