@parall/daemon 1.36.0 → 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 (59) hide show
  1. package/bundle/manifest.json +11 -11
  2. package/bundle/parall-browser-pod.js +384 -32
  3. package/bundle/parall-claude-agent.js +426 -51
  4. package/bundle/parall-codex-agent.js +425 -49
  5. package/bundle/parall-daemon.js +33336 -31520
  6. package/dist/browser-pod.d.ts +7 -0
  7. package/dist/browser-pod.d.ts.map +1 -1
  8. package/dist/browser-pod.js +73 -3
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +2 -2
  11. package/dist/clip-runtime/browser-daemon-env.d.ts +29 -0
  12. package/dist/clip-runtime/browser-daemon-env.d.ts.map +1 -0
  13. package/dist/clip-runtime/browser-daemon-env.js +70 -0
  14. package/dist/clip-runtime/browser-profile-manager.d.ts +34 -5
  15. package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
  16. package/dist/clip-runtime/browser-profile-manager.js +166 -24
  17. package/dist/clip-runtime/browser-profile-pool.d.ts +250 -0
  18. package/dist/clip-runtime/browser-profile-pool.d.ts.map +1 -0
  19. package/dist/clip-runtime/browser-profile-pool.js +581 -0
  20. package/dist/clip-runtime/index.d.ts +2 -1
  21. package/dist/clip-runtime/index.d.ts.map +1 -1
  22. package/dist/clip-runtime/index.js +2 -1
  23. package/dist/clip-runtime/process-manager.d.ts +5 -3
  24. package/dist/clip-runtime/process-manager.d.ts.map +1 -1
  25. package/dist/clip-runtime/subprocess.d.ts +14 -0
  26. package/dist/clip-runtime/subprocess.d.ts.map +1 -1
  27. package/dist/clip-runtime/subprocess.js +49 -0
  28. package/dist/config.d.ts +14 -15
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +8 -26
  31. package/dist/daemon-main.d.ts +8 -0
  32. package/dist/daemon-main.d.ts.map +1 -0
  33. package/dist/daemon-main.js +165 -0
  34. package/dist/daemon-paths.d.ts +14 -0
  35. package/dist/daemon-paths.d.ts.map +1 -0
  36. package/dist/daemon-paths.js +26 -0
  37. package/dist/daemon-update-mode.d.ts +8 -0
  38. package/dist/daemon-update-mode.d.ts.map +1 -0
  39. package/dist/daemon-update-mode.js +18 -0
  40. package/dist/index.js +41 -167
  41. package/dist/runtime-bin-resolver.d.ts +4 -0
  42. package/dist/runtime-bin-resolver.d.ts.map +1 -1
  43. package/dist/runtime-bin-resolver.js +40 -7
  44. package/dist/runtime-detector.d.ts +30 -0
  45. package/dist/runtime-detector.d.ts.map +1 -0
  46. package/dist/runtime-detector.js +100 -0
  47. package/dist/supervisor.d.ts +64 -2
  48. package/dist/supervisor.d.ts.map +1 -1
  49. package/dist/supervisor.js +629 -67
  50. package/dist/update-health-gate.d.ts +66 -0
  51. package/dist/update-health-gate.d.ts.map +1 -0
  52. package/dist/update-health-gate.js +93 -0
  53. package/dist/updater-manifest.d.ts +2 -1
  54. package/dist/updater-manifest.d.ts.map +1 -1
  55. package/dist/updater-manifest.js +38 -7
  56. package/dist/updater.d.ts +13 -2
  57. package/dist/updater.d.ts.map +1 -1
  58. package/dist/updater.js +126 -17
  59. package/package.json +6 -6
@@ -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`,
@@ -35343,6 +35364,7 @@ var ENDPOINTS = {
35343
35364
  // References (org-scoped)
35344
35365
  REFS_RESOLVE: (orgId) => `${API_BASE}/orgs/${orgId}/refs/resolve`,
35345
35366
  REFS_BACKLINKS: (orgId) => `${API_BASE}/orgs/${orgId}/refs/backlinks`,
35367
+ REFS_GRAPH: (orgId) => `${API_BASE}/orgs/${orgId}/refs/graph`,
35346
35368
  REFS_CHECK: (orgId) => `${API_BASE}/orgs/${orgId}/refs/check`,
35347
35369
  // Platform config (agent-scoped, not org-scoped)
35348
35370
  PLATFORM_CONFIG: `${API_BASE}/agents/platform-config`,
@@ -35363,6 +35385,14 @@ var ENDPOINTS = {
35363
35385
  BILLING_AUTO_RELOAD: (orgId) => `${API_BASE}/orgs/${orgId}/billing/auto-reload`,
35364
35386
  BILLING_SETUP_INTENT: (orgId) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
35365
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`,
35366
35396
  // ADMIN_GRANTS intentionally NOT exported here — it sits under the
35367
35397
  // unauthenticated `/internal/admin/*` surface and must not bleed into the
35368
35398
  // public SDK. Admin dashboard hits the URL directly from its own client.
@@ -35375,6 +35405,7 @@ var ENDPOINTS = {
35375
35405
  AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
35376
35406
  CLIP_INVOKE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
35377
35407
  CLIP_ONLINE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/online`,
35408
+ BROWSER_RUNTIME_STATUS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
35378
35409
  BROWSER_PROFILES: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
35379
35410
  BROWSER_PROFILE: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
35380
35411
  BROWSER_PROFILE_OPEN: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/open`,
@@ -35719,6 +35750,13 @@ var ParallClient = class _ParallClient {
35719
35750
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
35720
35751
  return res.data;
35721
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
+ }
35722
35760
  async getTeams(orgId) {
35723
35761
  const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
35724
35762
  return res.data;
@@ -36200,26 +36238,32 @@ var ParallClient = class _ParallClient {
36200
36238
  const res = await this.request("GET", ENDPOINTS.MACHINES_ME_CLIPS);
36201
36239
  return res.data;
36202
36240
  }
36203
- /** `GET /machines/me/browser-profiles` — browser profiles hosted by this machine. */
36241
+ /** `GET /machines/me/browser-profiles` — browser profiles hosted by this machine.
36242
+ * Returns the daemon DTO (carries proxy_password for bb-browser replay). */
36204
36243
  async listMachineBrowserProfiles() {
36205
36244
  const res = await this.request("GET", ENDPOINTS.MACHINES_ME_BROWSER_PROFILES);
36206
36245
  return res.data;
36207
36246
  }
36208
36247
  /** `POST /machines/me/browser-profiles/{profileId}/status` — report runtime status. */
36209
- async reportBrowserProfileStatus(profileId, status, errorMsg) {
36248
+ async reportBrowserProfileStatus(profileId, status, errorMsg, generation) {
36210
36249
  await this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
36211
36250
  status,
36212
- ...errorMsg ? { error_msg: errorMsg } : {}
36251
+ ...errorMsg ? { error_msg: errorMsg } : {},
36252
+ ...generation !== void 0 ? { generation } : {}
36213
36253
  });
36214
36254
  }
36215
36255
  /**
36216
36256
  * `POST /machines/me/health` — bump the Machine's `updated_at` to now and
36217
- * report daemon state. The daemon should call this on a fixed cadence (e.g.
36218
- * 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
36219
36260
  * fields are optional and only persisted when changed:
36220
36261
  * - `daemonVersion` — the running bundle/launcher version.
36221
36262
  * - `selfUpdateCapable` — whether the daemon can act on a machine.update
36222
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).
36223
36267
  */
36224
36268
  async postMachineHeartbeat(opts) {
36225
36269
  const body = {};
@@ -36227,6 +36271,8 @@ var ParallClient = class _ParallClient {
36227
36271
  body.daemon_version = opts.daemonVersion;
36228
36272
  if (opts?.selfUpdateCapable !== void 0)
36229
36273
  body.self_update_capable = opts.selfUpdateCapable;
36274
+ if (opts?.detectedRuntimes !== void 0)
36275
+ body.detected_runtimes = opts.detectedRuntimes;
36230
36276
  return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, Object.keys(body).length > 0 ? body : void 0);
36231
36277
  }
36232
36278
  async reportAgentWorkspaceState(agentId, state2) {
@@ -36545,6 +36591,59 @@ var ParallClient = class _ParallClient {
36545
36591
  async deleteExternalConnection(orgId, connectionId) {
36546
36592
  return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
36547
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
+ }
36548
36647
  async getExternalTriggerSchema(orgId, connectionId) {
36549
36648
  return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
36550
36649
  }
@@ -36586,6 +36685,23 @@ var ParallClient = class _ParallClient {
36586
36685
  async getWiki(orgId, wikiId) {
36587
36686
  return this.request("GET", ENDPOINTS.WIKI(orgId, wikiId));
36588
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
+ }
36589
36705
  async getWikiTree(orgId, wikiId, params) {
36590
36706
  return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
36591
36707
  }
@@ -36619,6 +36735,15 @@ var ParallClient = class _ParallClient {
36619
36735
  blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
36620
36736
  return blob;
36621
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
+ }
36622
36747
  async getWikiNodeSections(orgId, wikiId, params) {
36623
36748
  return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
36624
36749
  }
@@ -36787,6 +36912,16 @@ var ParallClient = class _ParallClient {
36787
36912
  async getBacklinks(orgId, params) {
36788
36913
  return this.request("GET", ENDPOINTS.REFS_BACKLINKS(orgId), void 0, params);
36789
36914
  }
36915
+ /**
36916
+ * Bounded multi-hop walk of the prll:// reference graph around `uri`. `uri`
36917
+ * must be an entity-level prll:// URI — a refined URI (path/query/fragment) is
36918
+ * rejected with 400 UNSUPPORTED_REFINED_URI. `depth` is clamped server-side to
36919
+ * [1, 4]; breadth (node/edge counts) is capped server-side and surfaced via
36920
+ * `truncated`. ACL is applied per hop (chat membership + wiki path scope).
36921
+ */
36922
+ async getRefsGraph(orgId, params) {
36923
+ return this.request("GET", ENDPOINTS.REFS_GRAPH(orgId), void 0, params);
36924
+ }
36790
36925
  async checkBrokenRefs(orgId) {
36791
36926
  return this.request("GET", ENDPOINTS.REFS_CHECK(orgId));
36792
36927
  }
@@ -36882,6 +37017,19 @@ var ParallClient = class _ParallClient {
36882
37017
  const resp = await this.request("GET", ENDPOINTS.COMPUTE_PRICING());
36883
37018
  return resp.data;
36884
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
+ }
36885
37033
  // ---- Clips ----
36886
37034
  async listClips(orgId) {
36887
37035
  const resp = await this.request("GET", ENDPOINTS.CLIPS(orgId));
@@ -36926,6 +37074,11 @@ var ParallClient = class _ParallClient {
36926
37074
  const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
36927
37075
  return resp.data;
36928
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
+ }
36929
37082
  /** Org-wide browser-profile discovery list. Returns the sanitized
36930
37083
  * {@link BrowserProfileListItem} shape (not the full domain model), each row
36931
37084
  * carrying a per-viewer `can_open` control hint. */
@@ -37034,6 +37187,50 @@ import { createRequire } from "node:module";
37034
37187
  import * as path from "node:path";
37035
37188
  import { fileURLToPath } from "node:url";
37036
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
+
37037
37234
  // ts/daemon/dist/clip-runtime/browser-viewer-streamer.js
37038
37235
  import { spawn } from "node:child_process";
37039
37236
 
@@ -37063,6 +37260,41 @@ async function findFreePort() {
37063
37260
  function sleep(ms) {
37064
37261
  return new Promise((resolve2) => setTimeout(resolve2, ms));
37065
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
+ }
37066
37298
 
37067
37299
  // ts/daemon/dist/clip-runtime/browser-viewer-streamer.js
37068
37300
  var BB_VIEWER_HEALTH_TIMEOUT_MS = 1e4;
@@ -37641,6 +37873,7 @@ function filterTabListToAccount(result, account) {
37641
37873
 
37642
37874
  // ts/daemon/dist/clip-runtime/browser-profile-manager.js
37643
37875
  var BB_BROWSER_DAEMON_START_TIMEOUT_MS = 15e3;
37876
+ var BB_BROWSER_DAEMON_START_ATTEMPTS = 3;
37644
37877
  var BrowserCommandError = class extends Error {
37645
37878
  method;
37646
37879
  account;
@@ -37656,6 +37889,11 @@ var BrowserProfileManager = class {
37656
37889
  daemon = null;
37657
37890
  starting = null;
37658
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;
37659
37897
  ensuredAccounts = /* @__PURE__ */ new Set();
37660
37898
  ensuringAccounts = /* @__PURE__ */ new Map();
37661
37899
  reportedStatuses = /* @__PURE__ */ new Map();
@@ -37706,9 +37944,12 @@ var BrowserProfileManager = class {
37706
37944
  throw err;
37707
37945
  }
37708
37946
  }
37709
- async ensureRuntime(profileId) {
37947
+ async ensureRuntime(profileId, opts = {}) {
37710
37948
  if (!profileId)
37711
37949
  throw new Error("browser profile id is required");
37950
+ if (opts.forceStatusReport) {
37951
+ this.reportedStatuses.delete(profileId);
37952
+ }
37712
37953
  try {
37713
37954
  await this.withDaemonRecovery(() => this.ensureAccount(profileId), `ensure runtime for ${profileId}`);
37714
37955
  this.reportStatus(profileId, "running");
@@ -37757,22 +37998,36 @@ var BrowserProfileManager = class {
37757
37998
  }
37758
37999
  }
37759
38000
  async stop() {
38001
+ this.stopping = true;
37760
38002
  this.viewer.shutdown();
38003
+ const inflight = this.starting;
38004
+ const restarting = this.restarting;
37761
38005
  const daemon = this.daemon;
37762
38006
  this.daemon = null;
37763
38007
  this.starting = null;
37764
38008
  this.ensuredAccounts.clear();
37765
38009
  this.ensuringAccounts.clear();
37766
38010
  this.reportedStatuses.clear();
37767
- if (!daemon)
37768
- return;
37769
- try {
37770
- await this.post("/shutdown", daemon, void 0, 3e3);
37771
- } catch {
38011
+ if (restarting) {
38012
+ try {
38013
+ await restarting;
38014
+ } catch {
38015
+ }
37772
38016
  }
37773
- if (daemon.child.exitCode === null && daemon.child.signalCode === null) {
37774
- daemon.child.kill("SIGTERM");
38017
+ let started = null;
38018
+ if (inflight) {
38019
+ try {
38020
+ started = await inflight;
38021
+ } catch {
38022
+ }
37775
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
+ }));
37776
38031
  }
37777
38032
  /**
37778
38033
  * Viewer control command entrypoint — delegates to BrowserViewerStreamer
@@ -37922,10 +38177,12 @@ var BrowserProfileManager = class {
37922
38177
  if (!isBrowserAccountInfoUnauthenticatedError(err)) {
37923
38178
  if (!isBrowserAccountNotFoundError(err))
37924
38179
  throw err;
38180
+ const proxy = this.opts.resolveProxy ? await this.opts.resolveProxy(account) : null;
37925
38181
  await this.sendCommand({
37926
38182
  method: "account_create",
37927
38183
  account,
37928
- ...accountUrl ? { accountUrl } : {}
38184
+ ...accountUrl ? { accountUrl } : {},
38185
+ ...proxyCreateFields(proxy)
37929
38186
  });
37930
38187
  accountCreated = true;
37931
38188
  }
@@ -37996,17 +38253,28 @@ var BrowserProfileManager = class {
37996
38253
  async ensureDaemon() {
37997
38254
  if (this.restarting)
37998
38255
  await this.restarting;
38256
+ if (this.stopping)
38257
+ throw new Error("browser profile manager is stopping");
37999
38258
  if (this.daemon && this.daemon.child.exitCode === null && this.daemon.child.signalCode === null) {
38000
38259
  return this.daemon;
38001
38260
  }
38002
- if (this.starting)
38003
- 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
+ }
38004
38267
  this.starting = this.startDaemon();
38268
+ const startPromise = this.starting;
38005
38269
  try {
38006
- 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;
38007
38274
  return this.daemon;
38008
38275
  } finally {
38009
- this.starting = null;
38276
+ if (this.starting === startPromise)
38277
+ this.starting = null;
38010
38278
  }
38011
38279
  }
38012
38280
  async withDaemonRecovery(operation2, label) {
@@ -38042,20 +38310,46 @@ var BrowserProfileManager = class {
38042
38310
  await this.post("/shutdown", daemon, void 0, 3e3);
38043
38311
  } catch {
38044
38312
  }
38045
- if (daemon.child.exitCode === null && daemon.child.signalCode === null) {
38046
- daemon.child.kill("SIGTERM");
38047
- }
38313
+ await waitForChildExit(daemon.child, this.opts.log);
38048
38314
  }
38049
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() {
38050
38330
  const host = "127.0.0.1";
38051
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
+ }
38052
38339
  const token = randomToken();
38053
38340
  const daemonPath = resolveBbBrowserDaemonPath();
38054
- const child = spawn2(process.execPath, [daemonPath, "--host", host, "--port", String(port), "--token", token], {
38055
- env: {
38056
- ...process.env,
38057
- BB_BROWSER_HOME: this.opts.homeDir
38058
- },
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),
38059
38353
  stdio: ["ignore", "ignore", "pipe"]
38060
38354
  });
38061
38355
  let childError = null;
@@ -38084,7 +38378,7 @@ var BrowserProfileManager = class {
38084
38378
  await sleep(200);
38085
38379
  }
38086
38380
  }
38087
- child.kill("SIGTERM");
38381
+ await waitForChildExit(child, this.opts.log);
38088
38382
  throw new Error("bb-browser-daemon did not start in time");
38089
38383
  }
38090
38384
  async post(pathName, daemon, body, timeoutMs = 1e4) {
@@ -38182,6 +38476,16 @@ function resolveBbBrowserDaemonPath() {
38182
38476
  function randomToken() {
38183
38477
  return randomBytes(16).toString("hex");
38184
38478
  }
38479
+ function proxyCreateFields(proxy) {
38480
+ if (!proxy?.server)
38481
+ return {};
38482
+ const fields = { proxyServer: proxy.server };
38483
+ if (proxy.username)
38484
+ fields.proxyUsername = proxy.username;
38485
+ if (proxy.password)
38486
+ fields.proxyPassword = proxy.password;
38487
+ return fields;
38488
+ }
38185
38489
 
38186
38490
  // ts/daemon/dist/clip-runtime/browser-state-store.js
38187
38491
  var import_client_s3 = __toESM(require_dist_cjs73(), 1);
@@ -40165,9 +40469,25 @@ function resolveBrowserPodConfig(env = process.env) {
40165
40469
  orgId,
40166
40470
  healthPort: parsePort(env.PRLL_HEALTH_PORT, DEFAULT_HEALTH_PORT),
40167
40471
  stateDir,
40168
- homeDir
40472
+ homeDir,
40473
+ proxy: parseProxyEnv(env)
40169
40474
  };
40170
40475
  }
40476
+ function parseProxyEnv(env) {
40477
+ const server = env.PRLL_BROWSER_PROXY_SERVER?.trim();
40478
+ const username = env.PRLL_BROWSER_PROXY_USERNAME || void 0;
40479
+ const password = env.PRLL_BROWSER_PROXY_PASSWORD || void 0;
40480
+ if (!server) {
40481
+ if (username !== void 0 || password !== void 0) {
40482
+ throw new Error("browser-pod: PRLL_BROWSER_PROXY_USERNAME/PASSWORD set without PRLL_BROWSER_PROXY_SERVER");
40483
+ }
40484
+ return null;
40485
+ }
40486
+ if (username === void 0 !== (password === void 0)) {
40487
+ throw new Error("browser-pod: PRLL_BROWSER_PROXY_USERNAME and PRLL_BROWSER_PROXY_PASSWORD must be set together");
40488
+ }
40489
+ return { server, username, password };
40490
+ }
40171
40491
  function resolvePoolPodConfig(env = process.env) {
40172
40492
  const poolToken = env.PRLL_POOL_TOKEN?.trim() || "";
40173
40493
  const podId = env.PRLL_POD_ID?.trim() || "";
@@ -40236,7 +40556,25 @@ function parseAssignRequest(body) {
40236
40556
  sessionToken
40237
40557
  };
40238
40558
  }
40239
- return { ok: true, value: { assignmentToken, profileId, orgId, leaseId, generation, state: state2 } };
40559
+ let proxy;
40560
+ if (b5.proxy !== void 0 && b5.proxy !== null) {
40561
+ if (typeof b5.proxy !== "object")
40562
+ return { ok: false, error: "proxy must be an object" };
40563
+ const p2 = b5.proxy;
40564
+ const server = str(p2.server);
40565
+ if (!server)
40566
+ return { ok: false, error: "proxy requires server" };
40567
+ const username = (typeof p2.username === "string" ? p2.username : "") || void 0;
40568
+ const password = (typeof p2.password === "string" ? p2.password : "") || void 0;
40569
+ if (username === void 0 !== (password === void 0)) {
40570
+ return { ok: false, error: "proxy username and password must be provided together" };
40571
+ }
40572
+ proxy = { server, username, password };
40573
+ }
40574
+ return {
40575
+ ok: true,
40576
+ value: { assignmentToken, profileId, orgId, leaseId, generation, state: state2, proxy }
40577
+ };
40240
40578
  }
40241
40579
  function applyAssignmentEnv(env, req) {
40242
40580
  env.PRLL_ASSIGNMENT_TOKEN = req.assignmentToken;
@@ -40254,6 +40592,16 @@ function applyAssignmentEnv(env, req) {
40254
40592
  if (req.state.endpoint)
40255
40593
  env.PRLL_BROWSER_STATE_ENDPOINT = req.state.endpoint;
40256
40594
  }
40595
+ delete env.PRLL_BROWSER_PROXY_SERVER;
40596
+ delete env.PRLL_BROWSER_PROXY_USERNAME;
40597
+ delete env.PRLL_BROWSER_PROXY_PASSWORD;
40598
+ if (req.proxy) {
40599
+ env.PRLL_BROWSER_PROXY_SERVER = req.proxy.server;
40600
+ if (req.proxy.username)
40601
+ env.PRLL_BROWSER_PROXY_USERNAME = req.proxy.username;
40602
+ if (req.proxy.password)
40603
+ env.PRLL_BROWSER_PROXY_PASSWORD = req.proxy.password;
40604
+ }
40257
40605
  }
40258
40606
  function poolTokenMatches(bearer, expected) {
40259
40607
  if (!bearer)
@@ -40507,7 +40855,11 @@ function makeCheckpointer(store) {
40507
40855
  };
40508
40856
  }
40509
40857
  async function preparePodRuntime(config, env, log) {
40510
- const browser = new BrowserProfileManager({ homeDir: config.homeDir, log });
40858
+ const browser = new BrowserProfileManager({
40859
+ homeDir: config.homeDir,
40860
+ log,
40861
+ resolveProxy: () => config.proxy
40862
+ });
40511
40863
  const clipManager = new ClipProcessManager({
40512
40864
  clipsDir: path5.join(config.stateDir, "clips"),
40513
40865
  dataDir: path5.join(config.stateDir, "clip-data"),