@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
@@ -26647,6 +26647,17 @@ function buildEventBody(event) {
26647
26647
  if (event.attachedUri)
26648
26648
  lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
26649
26649
  lines.push("", event.body);
26650
+ } else if (event.type === "channel_message") {
26651
+ lines.push(`[Event: channel.message]`);
26652
+ const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
26653
+ const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
26654
+ lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
26655
+ lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
26656
+ if (event.channelExternalMessageId) {
26657
+ lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
26658
+ }
26659
+ lines.push(`[Audience: this conversation lives on ${providerLabel}, OUTSIDE Parall. Readers cannot open prll:// links, Parall cards, or internal attachments \u2014 never include them in replies. Write plain conversational text.]`);
26660
+ lines.push("", event.body);
26650
26661
  } else if (event.type === "external_trigger") {
26651
26662
  lines.push(`[Event: external.trigger]`);
26652
26663
  lines.push(`[Trigger: prll://${event.targetId}]`);
@@ -26688,7 +26699,7 @@ function buildSendMessageHint(event) {
26688
26699
  }
26689
26700
  if (event.targetId.startsWith("cht_")) {
26690
26701
  return `
26691
- <system-reminder>To reply, run: \`parall messages send prll://${event.targetId} --text "..."\` \u2014 your plain text output is not delivered to the chat.</system-reminder>`;
26702
+ <system-reminder>To reply, run \`parall messages send prll://${event.targetId} --text-file - <<'EOF'\` \u2026 \`EOF\` \u2014 the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
26692
26703
  }
26693
26704
  if (event.targetId.startsWith("tsk_")) {
26694
26705
  return `
@@ -26697,6 +26708,14 @@ function buildSendMessageHint(event) {
26697
26708
  if (event.targetId.startsWith("sch_")) {
26698
26709
  return `
26699
26710
  <system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
26711
+ }
26712
+ if (event.type === "channel_message") {
26713
+ const clipLabel = event.channelProvider ? `the \`${event.channelProvider}\` clip's` : "your channel provider clip's";
26714
+ const apiLabel = event.channelProvider ?? "external platform";
26715
+ const target = event.channelExternalConversationId ? `{"chat_id": "${event.channelExternalConversationId}", "text": "..."}` : `{"chat_id": "<conversation id>", "text": "..."}`;
26716
+ const threadAlt = event.channelExternalMessageId ? ` To reply in-thread to this specific message, use {"message_id": "${event.channelExternalMessageId}", "text": "..."} instead.` : "";
26717
+ return `
26718
+ <system-reminder>To reply, invoke ${clipLabel} \`send_message\` command with ${target} \u2014 your plain text output is NOT delivered to the external conversation.${threadAlt} The same clip's \`call\` command reaches the wider ${apiLabel} API when needed.</system-reminder>`;
26700
26719
  }
26701
26720
  if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
26702
26721
  return `
@@ -26904,6 +26923,8 @@ Or upload first and reuse across chats:
26904
26923
  parall messages send prll://cht_aaa --attachment att_yyy --text "Report"
26905
26924
  parall messages send prll://cht_bbb --attachment att_yyy --text "FYI"
26906
26925
 
26926
+ The \`--text\` captions above are safe short literals. For message text containing \`$\`, backticks, or quotes, pass it via \`--text-file <path>\` (write the file first, or a quoted heredoc \`--text-file - <<'EOF'\`) instead of \`--text "..."\` \u2014 inside double quotes the shell turns \`$1,000\` into \`,000\` and executes \`$(...)\`.
26927
+
26907
26928
  ### When to reference
26908
26929
 
26909
26930
  - **Origin** \u2014 always link the message or task that triggered your work
@@ -26954,11 +26975,21 @@ To say something in a chat, you **must** invoke the Parall CLI via your shell/ex
26954
26975
 
26955
26976
  All outbound interactions go through the \`parall\` CLI. Credentials are pre-injected as environment variables \u2014 no setup needed. If \`parall\` is not on PATH, use \`npx --yes @parall/cli@latest\` instead.
26956
26977
 
26957
- - \`parall messages send prll://cht_xxx --text "..."\` \u2014 reply into the triggering chat
26958
- - \`parall dm prll://usr_xxx --text "..." [--no-reply]\` \u2014 direct message another user
26978
+ - \`parall messages send prll://cht_xxx --text-file -\` \u2014 reply into the triggering chat (pipe the body via a quoted heredoc; see Shell-safety below)
26979
+ - \`parall dm prll://usr_xxx --text-file - [--no-reply]\` \u2014 direct message another user
26959
26980
  - \`parall tasks update prll://tsk_xxx --status in_progress\` \u2014 task state
26960
26981
  - \`parall no-reply [--reason "..."]\` \u2014 explicitly declare this turn silent (audit signal; not required for silence, just clarifies intent)
26961
26982
 
26983
+ **Shell-safety \u2014 never wrap real message content in double quotes.** Your command runs in a shell, which expands \`$\`, backticks, and \`$(...)\` inside \`"..."\` before the CLI sees them: \`--text "That costs $1,000"\` sends \`That costs ,000\`, and \`--text "$(cmd)"\` executes \`cmd\`. Pass message bodies via \`--text-file <path>\` (write the file first \u2014 no shell touches it) or a quoted heredoc that disables expansion:
26984
+
26985
+ \`\`\`bash
26986
+ parall messages send prll://cht_xxx --text-file - <<'EOF'
26987
+ That costs $1,000, and $(whoami) stays literal. I'm on it.
26988
+ EOF
26989
+ \`\`\`
26990
+
26991
+ Keep \`--text "..."\` for short literals with no \`$\`, backtick, or apostrophe.
26992
+
26962
26993
  The bridge injects Parall context via environment variables. The static credentials \`PRLL_API_URL\`, \`PRLL_API_KEY\`, and \`PRLL_ORG_ID\` are always set. \`PRLL_CONTEXT_FILE\` points to a per-session JSON file that the gateway updates each dispatch with \`session_id\`, \`chat_id\`, \`trigger_message_id\`, \`no_reply\`, and \`step_id\` (updated per tool call). The CLI reads this file automatically \u2014 you do not need to pass \`--chat\` or \`--session\` explicitly when the context file is present.
26963
26994
 
26964
26995
  ## Attachments
@@ -27003,7 +27034,7 @@ Messages may arrive with a \`[Thread: prll://msg_xxx]\` line in the event block,
27003
27034
 
27004
27035
  ### CLI usage
27005
27036
  - The \`msg_xxx\` from the \`[Thread: prll://msg_xxx]\` line is the thread root ID \u2014 pass it as \`--thread-root-id\`
27006
- - Reply in a thread: \`parall messages send <chatId> --text "..." --thread-root-id <msgId>\`
27037
+ - Reply in a thread: \`parall messages send <chatId> --text-file /tmp/reply.md --thread-root-id <msgId>\`
27007
27038
  - Read thread replies: \`parall messages list <chatId> --thread-root-id <msgId>\`
27008
27039
 
27009
27040
  ### Thread etiquette
@@ -27104,6 +27135,7 @@ var ENDPOINTS = {
27104
27135
  // Org-scoped
27105
27136
  ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
27106
27137
  ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
27138
+ ORG_MEMBERS_FORMER: (orgId) => `${API_BASE}/orgs/${orgId}/members/former`,
27107
27139
  TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
27108
27140
  ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
27109
27141
  ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
@@ -27253,6 +27285,19 @@ var ENDPOINTS = {
27253
27285
  EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
27254
27286
  EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
27255
27287
  EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
27288
+ // External IM channel (org-scoped, platform-mediated Feishu/Slack)
27289
+ CHANNEL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/channel-connections`,
27290
+ CHANNEL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}`,
27291
+ CHANNEL_CONNECTION_CREDENTIALS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/credentials`,
27292
+ CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/ingress-token/regenerate`,
27293
+ CHANNEL_CONNECTION_CONVERSATIONS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/conversations`,
27294
+ CHANNEL_CONVERSATION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}`,
27295
+ CHANNEL_CONVERSATION_MESSAGES: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/messages`,
27296
+ CHANNEL_CONVERSATION_SESSION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/session`,
27297
+ CHANNEL_MESSAGE: (orgId, messageId) => `${API_BASE}/orgs/${orgId}/channel-messages/${messageId}`,
27298
+ CHANNEL_PROVISIONING: (orgId) => `${API_BASE}/orgs/${orgId}/channel-provisioning`,
27299
+ CHANNEL_PROVISIONING_SESSION: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}`,
27300
+ CHANNEL_PROVISIONING_CANCEL: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}/cancel`,
27256
27301
  // Invitations (org-scoped, admin)
27257
27302
  ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
27258
27303
  ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
@@ -27270,9 +27315,16 @@ var ENDPOINTS = {
27270
27315
  INVITE_LINK_JOIN: `${API_BASE}/invite-link/join`,
27271
27316
  // Wikis (org-scoped, served by wiki-service)
27272
27317
  WIKIS: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
27318
+ // Recycle bin — soft-deleted wikis (owner only). Must precede WIKI in the
27319
+ // backend router so `deleted` isn't captured as a {wikiId}; the SDK builder
27320
+ // is just a string.
27321
+ WIKIS_DELETED: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis/deleted`,
27322
+ // Detail URL — also reused for DELETE (soft-delete) and `${WIKI}/restore`.
27273
27323
  WIKI: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
27324
+ WIKI_RESTORE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restore`,
27274
27325
  WIKI_TREE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
27275
27326
  WIKI_BLOB: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
27327
+ WIKI_COPY: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/copy`,
27276
27328
  WIKI_NODE_SECTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/node-sections`,
27277
27329
  WIKI_SEARCH: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/search`,
27278
27330
  WIKI_PAGE_INDEX: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/page-index`,
@@ -27334,6 +27386,7 @@ var ENDPOINTS = {
27334
27386
  // References (org-scoped)
27335
27387
  REFS_RESOLVE: (orgId) => `${API_BASE}/orgs/${orgId}/refs/resolve`,
27336
27388
  REFS_BACKLINKS: (orgId) => `${API_BASE}/orgs/${orgId}/refs/backlinks`,
27389
+ REFS_GRAPH: (orgId) => `${API_BASE}/orgs/${orgId}/refs/graph`,
27337
27390
  REFS_CHECK: (orgId) => `${API_BASE}/orgs/${orgId}/refs/check`,
27338
27391
  // Platform config (agent-scoped, not org-scoped)
27339
27392
  PLATFORM_CONFIG: `${API_BASE}/agents/platform-config`,
@@ -27354,6 +27407,14 @@ var ENDPOINTS = {
27354
27407
  BILLING_AUTO_RELOAD: (orgId) => `${API_BASE}/orgs/${orgId}/billing/auto-reload`,
27355
27408
  BILLING_SETUP_INTENT: (orgId) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
27356
27409
  COMPUTE_PRICING: () => `${API_BASE}/billing/compute-pricing`,
27410
+ // Runtime capability table (public, no auth) — SSOT for the create/settings
27411
+ // interlock: per-runtime compute modes, native model family, cross-family
27412
+ // availability, and recommended model.
27413
+ RUNTIMES: () => `${API_BASE}/runtimes`,
27414
+ // Model catalog (public, no auth) — served from the server's DB-backed
27415
+ // snapshot. Optional include_model keeps an agent's pinned hidden legacy
27416
+ // model representable in settings pickers.
27417
+ MODELS: (includeModel) => includeModel ? `${API_BASE}/models?include_model=${encodeURIComponent(includeModel)}` : `${API_BASE}/models`,
27357
27418
  // ADMIN_GRANTS intentionally NOT exported here — it sits under the
27358
27419
  // unauthenticated `/internal/admin/*` surface and must not bleed into the
27359
27420
  // public SDK. Admin dashboard hits the URL directly from its own client.
@@ -27366,6 +27427,7 @@ var ENDPOINTS = {
27366
27427
  AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
27367
27428
  CLIP_INVOKE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
27368
27429
  CLIP_ONLINE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/online`,
27430
+ BROWSER_RUNTIME_STATUS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
27369
27431
  BROWSER_PROFILES: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
27370
27432
  BROWSER_PROFILE: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
27371
27433
  BROWSER_PROFILE_OPEN: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/open`,
@@ -27419,10 +27481,13 @@ var WS_EVENTS = {
27419
27481
  INVITATION_REVOKED: "invitation.revoked",
27420
27482
  ORG_JOIN_REQUEST_NEW: "org.join_request.new",
27421
27483
  ORG_INVITE_LINK_JOINED: "org.invite_link.joined",
27484
+ ORG_MEMBER_REMOVED: "org.member.removed",
27422
27485
  AGENT_CONFIG_UPDATE: "agent_config.update",
27423
27486
  PRESENCE_UPDATE: "presence.update",
27424
27487
  WIKI_CHANGESET_CREATED: "wiki.changeset.created",
27425
27488
  WIKI_CHANGESET_UPDATED: "wiki.changeset.updated",
27489
+ WIKI_DELETED: "wiki.deleted",
27490
+ WIKI_RESTORED: "wiki.restored",
27426
27491
  COMMENT_CREATED: "comment.created",
27427
27492
  COMMENT_UPDATED: "comment.updated",
27428
27493
  COMMENT_DELETED: "comment.deleted",
@@ -27450,6 +27515,7 @@ var WS_EVENTS = {
27450
27515
  MACHINE_FILESYSTEM_BROWSE: "machine.filesystem.browse",
27451
27516
  MACHINE_UPDATE: "machine.update",
27452
27517
  MACHINE_CONFIG_UPDATED: "machine.config.updated",
27518
+ MACHINE_AGENT_CONFIG_UPDATED: "machine.agent_config.updated",
27453
27519
  MACHINE_CLIP_SYNC: "machine.clip.sync",
27454
27520
  MACHINE_BROWSER_PROFILE_LIFECYCLE: "machine.browser_profile.lifecycle",
27455
27521
  MACHINE_BROWSER_PROFILE_VIEWER: "machine.browser_profile.viewer",
@@ -27789,6 +27855,13 @@ var ParallClient = class _ParallClient {
27789
27855
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
27790
27856
  return res.data;
27791
27857
  }
27858
+ /** User IDs of soft-removed (former) org members — for marking their avatar /
27859
+ * name as "left" where dormant relations still surface them (DMs, task
27860
+ * assignees, message history). */
27861
+ async getFormerMemberIds(orgId) {
27862
+ const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_FORMER(orgId));
27863
+ return res.user_ids ?? [];
27864
+ }
27792
27865
  async getTeams(orgId) {
27793
27866
  const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
27794
27867
  return res.data;
@@ -28270,26 +28343,32 @@ var ParallClient = class _ParallClient {
28270
28343
  const res = await this.request("GET", ENDPOINTS.MACHINES_ME_CLIPS);
28271
28344
  return res.data;
28272
28345
  }
28273
- /** `GET /machines/me/browser-profiles` — browser profiles hosted by this machine. */
28346
+ /** `GET /machines/me/browser-profiles` — browser profiles hosted by this machine.
28347
+ * Returns the daemon DTO (carries proxy_password for bb-browser replay). */
28274
28348
  async listMachineBrowserProfiles() {
28275
28349
  const res = await this.request("GET", ENDPOINTS.MACHINES_ME_BROWSER_PROFILES);
28276
28350
  return res.data;
28277
28351
  }
28278
28352
  /** `POST /machines/me/browser-profiles/{profileId}/status` — report runtime status. */
28279
- async reportBrowserProfileStatus(profileId, status, errorMsg) {
28353
+ async reportBrowserProfileStatus(profileId, status, errorMsg, generation) {
28280
28354
  await this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
28281
28355
  status,
28282
- ...errorMsg ? { error_msg: errorMsg } : {}
28356
+ ...errorMsg ? { error_msg: errorMsg } : {},
28357
+ ...generation !== void 0 ? { generation } : {}
28283
28358
  });
28284
28359
  }
28285
28360
  /**
28286
28361
  * `POST /machines/me/health` — bump the Machine's `updated_at` to now and
28287
- * report daemon state. The daemon should call this on a fixed cadence (e.g.
28288
- * every 30s) so an external observer can detect a wedged supervisor. Both
28362
+ * report daemon state. The daemon calls this on startup and again whenever
28363
+ * periodic runtime re-detection produces a CHANGED result (there is no
28364
+ * fixed-cadence keepalive loop today — steady state posts nothing). All
28289
28365
  * fields are optional and only persisted when changed:
28290
28366
  * - `daemonVersion` — the running bundle/launcher version.
28291
28367
  * - `selfUpdateCapable` — whether the daemon can act on a machine.update
28292
28368
  * signal (true under a service manager, false for bare `npx` foreground).
28369
+ * - `detectedRuntimes` — runtime CLIs found on the host. Omitted = no
28370
+ * report this beat (server keeps the stored value); [] = detection ran
28371
+ * and found nothing (server clears to empty).
28293
28372
  */
28294
28373
  async postMachineHeartbeat(opts) {
28295
28374
  const body = {};
@@ -28297,6 +28376,8 @@ var ParallClient = class _ParallClient {
28297
28376
  body.daemon_version = opts.daemonVersion;
28298
28377
  if (opts?.selfUpdateCapable !== void 0)
28299
28378
  body.self_update_capable = opts.selfUpdateCapable;
28379
+ if (opts?.detectedRuntimes !== void 0)
28380
+ body.detected_runtimes = opts.detectedRuntimes;
28300
28381
  return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, Object.keys(body).length > 0 ? body : void 0);
28301
28382
  }
28302
28383
  async reportAgentWorkspaceState(agentId, state) {
@@ -28615,6 +28696,59 @@ var ParallClient = class _ParallClient {
28615
28696
  async deleteExternalConnection(orgId, connectionId) {
28616
28697
  return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
28617
28698
  }
28699
+ // ---- External IM channel (org-scoped) ----
28700
+ async createChannelConnection(orgId, input) {
28701
+ return this.request("POST", ENDPOINTS.CHANNEL_CONNECTIONS(orgId), input);
28702
+ }
28703
+ async listChannelConnections(orgId) {
28704
+ return this.request("GET", ENDPOINTS.CHANNEL_CONNECTIONS(orgId));
28705
+ }
28706
+ async getChannelConnection(orgId, connectionId) {
28707
+ return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
28708
+ }
28709
+ async updateChannelConnection(orgId, connectionId, patch) {
28710
+ return this.request("PATCH", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId), patch);
28711
+ }
28712
+ async archiveChannelConnection(orgId, connectionId) {
28713
+ return this.request("DELETE", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
28714
+ }
28715
+ async deliverChannelCredentials(orgId, connectionId, credentials) {
28716
+ return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_CREDENTIALS(orgId, connectionId), credentials);
28717
+ }
28718
+ async regenerateChannelIngressToken(orgId, connectionId) {
28719
+ return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
28720
+ }
28721
+ /** Start a Feishu one-click provisioning session (device flow QR). */
28722
+ async initiateChannelProvisioning(orgId, input) {
28723
+ return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING(orgId), input);
28724
+ }
28725
+ /**
28726
+ * Lazy status poll — server-side this may forward one provider poll, so
28727
+ * call it at the session's `poll_interval_seconds` cadence, not faster.
28728
+ */
28729
+ async getChannelProvisioningSession(orgId, sessionId) {
28730
+ return this.request("GET", ENDPOINTS.CHANNEL_PROVISIONING_SESSION(orgId, sessionId));
28731
+ }
28732
+ async cancelChannelProvisioning(orgId, sessionId) {
28733
+ return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING_CANCEL(orgId, sessionId));
28734
+ }
28735
+ async listChannelConversations(orgId, connectionId) {
28736
+ return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
28737
+ }
28738
+ async getChannelConversation(orgId, conversationId) {
28739
+ return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION(orgId, conversationId));
28740
+ }
28741
+ async listChannelConversationMessages(orgId, conversationId, limit) {
28742
+ return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION_MESSAGES(orgId, conversationId), void 0, { limit });
28743
+ }
28744
+ async setChannelConversationSession(orgId, conversationId, agentSessionId) {
28745
+ return this.request("PATCH", ENDPOINTS.CHANNEL_CONVERSATION_SESSION(orgId, conversationId), {
28746
+ agent_session_id: agentSessionId
28747
+ });
28748
+ }
28749
+ async getChannelMessage(orgId, messageId) {
28750
+ return this.request("GET", ENDPOINTS.CHANNEL_MESSAGE(orgId, messageId));
28751
+ }
28618
28752
  async getExternalTriggerSchema(orgId, connectionId) {
28619
28753
  return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
28620
28754
  }
@@ -28656,6 +28790,23 @@ var ParallClient = class _ParallClient {
28656
28790
  async getWiki(orgId, wikiId) {
28657
28791
  return this.request("GET", ENDPOINTS.WIKI(orgId, wikiId));
28658
28792
  }
28793
+ /** Soft-delete an entire wiki (org owner only; the default wiki is
28794
+ * protected → 403 WIKI_PROTECTED). Returns the deleted wiki. Recoverable
28795
+ * via restoreWiki within the retention window. */
28796
+ async deleteWiki(orgId, wikiId) {
28797
+ return this.request("DELETE", ENDPOINTS.WIKI(orgId, wikiId));
28798
+ }
28799
+ /** Restore a soft-deleted wiki (org owner only). 409 WIKI_PURGE_STARTED once
28800
+ * purge has been claimed (no longer restorable), or 404 once the row is gone.
28801
+ * Returns the restored wiki. */
28802
+ async restoreWiki(orgId, wikiId) {
28803
+ return this.request("POST", ENDPOINTS.WIKI_RESTORE(orgId, wikiId));
28804
+ }
28805
+ /** Recycle bin — list soft-deleted wikis for the org (owner only). */
28806
+ async getDeletedWikis(orgId) {
28807
+ const res = await this.request("GET", ENDPOINTS.WIKIS_DELETED(orgId));
28808
+ return res.data;
28809
+ }
28659
28810
  async getWikiTree(orgId, wikiId, params) {
28660
28811
  return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
28661
28812
  }
@@ -28689,6 +28840,15 @@ var ParallClient = class _ParallClient {
28689
28840
  blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
28690
28841
  return blob;
28691
28842
  }
28843
+ /**
28844
+ * Copy one wiki file to another path as an independent snapshot (no sync).
28845
+ * Used to "share a private file out": copy a file from the caller's personal
28846
+ * namespace (`users/{userId}/…`) into the public wiki, leaving the original
28847
+ * untouched. 409 `FILE_EXISTS` if `dest_path` already exists.
28848
+ */
28849
+ async copyWikiFile(orgId, wikiId, req) {
28850
+ return this.request("POST", ENDPOINTS.WIKI_COPY(orgId, wikiId), req);
28851
+ }
28692
28852
  async getWikiNodeSections(orgId, wikiId, params) {
28693
28853
  return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
28694
28854
  }
@@ -28857,6 +29017,16 @@ var ParallClient = class _ParallClient {
28857
29017
  async getBacklinks(orgId, params) {
28858
29018
  return this.request("GET", ENDPOINTS.REFS_BACKLINKS(orgId), void 0, params);
28859
29019
  }
29020
+ /**
29021
+ * Bounded multi-hop walk of the prll:// reference graph around `uri`. `uri`
29022
+ * must be an entity-level prll:// URI — a refined URI (path/query/fragment) is
29023
+ * rejected with 400 UNSUPPORTED_REFINED_URI. `depth` is clamped server-side to
29024
+ * [1, 4]; breadth (node/edge counts) is capped server-side and surfaced via
29025
+ * `truncated`. ACL is applied per hop (chat membership + wiki path scope).
29026
+ */
29027
+ async getRefsGraph(orgId, params) {
29028
+ return this.request("GET", ENDPOINTS.REFS_GRAPH(orgId), void 0, params);
29029
+ }
28860
29030
  async checkBrokenRefs(orgId) {
28861
29031
  return this.request("GET", ENDPOINTS.REFS_CHECK(orgId));
28862
29032
  }
@@ -28952,6 +29122,19 @@ var ParallClient = class _ParallClient {
28952
29122
  const resp = await this.request("GET", ENDPOINTS.COMPUTE_PRICING());
28953
29123
  return resp.data;
28954
29124
  }
29125
+ /** Runtime capability table (public) — SSOT for the create/settings interlock. */
29126
+ async getRuntimes() {
29127
+ const resp = await this.request("GET", ENDPOINTS.RUNTIMES());
29128
+ return resp.data;
29129
+ }
29130
+ /**
29131
+ * Platform model catalog (public) — DB-backed, replaces the compile-time
29132
+ * PLATFORM_MODELS constant. Pass includeModel to keep an agent's pinned
29133
+ * hidden legacy model representable (settings picker).
29134
+ */
29135
+ async getModels(includeModel) {
29136
+ return this.request("GET", ENDPOINTS.MODELS(includeModel));
29137
+ }
28955
29138
  // ---- Clips ----
28956
29139
  async listClips(orgId) {
28957
29140
  const resp = await this.request("GET", ENDPOINTS.CLIPS(orgId));
@@ -28996,6 +29179,11 @@ var ParallClient = class _ParallClient {
28996
29179
  const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
28997
29180
  return resp.data;
28998
29181
  }
29182
+ /** Deployment-wide hosted browser runtime availability — drives whether the
29183
+ * create UI offers the platform-hosted placement. Fail-closed server-side. */
29184
+ async getBrowserRuntimeStatus(orgId) {
29185
+ return this.request("GET", ENDPOINTS.BROWSER_RUNTIME_STATUS(orgId));
29186
+ }
28999
29187
  /** Org-wide browser-profile discovery list. Returns the sanitized
29000
29188
  * {@link BrowserProfileListItem} shape (not the full domain model), each row
29001
29189
  * carrying a per-viewer `can_open` control hint. */
@@ -29655,6 +29843,9 @@ function resolveStepTarget(event) {
29655
29843
  if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
29656
29844
  return { target_type: "external_trigger", target_id: event.targetId };
29657
29845
  }
29846
+ if (event.type === "channel_message" || event.targetId.startsWith("chv_")) {
29847
+ return { target_type: "channel_conversation", target_id: event.targetId };
29848
+ }
29658
29849
  if (event.type === "wiki_comment") {
29659
29850
  return { target_type: "wiki", target_id: event.targetId || void 0 };
29660
29851
  }
@@ -29698,6 +29889,9 @@ var ParallAgentGateway = class {
29698
29889
  opts;
29699
29890
  chatInfoMap = /* @__PURE__ */ new Map();
29700
29891
  dispatchedTasks = /* @__PURE__ */ new Set();
29892
+ // connection id → provider alias, for channel_message prompt labeling
29893
+ // (stable mapping; avoids one connection fetch per inbound message).
29894
+ channelConnectionProviders = /* @__PURE__ */ new Map();
29701
29895
  dispatchedMessages = /* @__PURE__ */ new Set();
29702
29896
  forkStates = /* @__PURE__ */ new Map();
29703
29897
  dispatchState = {
@@ -29871,6 +30065,18 @@ var ParallAgentGateway = class {
29871
30065
  } catch (err) {
29872
30066
  this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
29873
30067
  }
30068
+ } else if (data.event_type === "channel_message") {
30069
+ if (!data.source_id)
30070
+ return;
30071
+ try {
30072
+ const dispatched = await this.fetchAndHandleChannelMessage(data.source_id);
30073
+ if (dispatched) {
30074
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30075
+ });
30076
+ }
30077
+ } catch (err) {
30078
+ this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
30079
+ }
29874
30080
  } else if (data.event_type === "approval_decided") {
29875
30081
  if (!data.source_id)
29876
30082
  return;
@@ -29949,12 +30155,17 @@ var ParallAgentGateway = class {
29949
30155
  target_type: target.target_type,
29950
30156
  target_id: target.target_id,
29951
30157
  content: {
29952
- trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : event.type === "wiki_comment" ? "wiki_comment" : event.type === "schedule" ? "schedule_fire" : event.type === "external_trigger" ? "external_trigger" : event.type === "approval" ? "approval_decided" : "mention",
30158
+ trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : event.type === "wiki_comment" ? "wiki_comment" : event.type === "schedule" ? "schedule_fire" : event.type === "external_trigger" ? "external_trigger" : event.type === "channel_message" ? "channel_message" : event.type === "approval" ? "approval_decided" : "mention",
29953
30159
  trigger_ref: event.type === "task" ? { task_id: event.targetId } : event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } : event.type === "wiki_comment" ? { comment_id: event.messageId, target_uri: event.replyTargetUri } : event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } : event.type === "external_trigger" ? {
29954
30160
  trigger_id: event.targetId,
29955
30161
  run_id: event.messageId,
29956
30162
  connection_id: event.externalConnectionId,
29957
30163
  ingress_event_id: event.externalIngressEventId
30164
+ } : event.type === "channel_message" ? {
30165
+ conversation_id: event.targetId,
30166
+ channel_message_id: event.messageId,
30167
+ provider: event.channelProvider,
30168
+ external_conversation_id: event.channelExternalConversationId
29958
30169
  } : event.type === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
29959
30170
  sender_id: event.senderId,
29960
30171
  sender_name: event.senderName,
@@ -30205,7 +30416,15 @@ var ParallAgentGateway = class {
30205
30416
  context: dispatchContext
30206
30417
  })) {
30207
30418
  if (runtimeEvent.type === "runtime_session") {
30419
+ const priorAgentSessionId = binding?.agentSessionId;
30208
30420
  binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
30421
+ if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
30422
+ try {
30423
+ await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
30424
+ } catch (err) {
30425
+ this.opts.log?.warn(`failed to record session mapping for channel conversation ${event.targetId}: ${String(err)}`);
30426
+ }
30427
+ }
30209
30428
  if (!inputStepsCreated) {
30210
30429
  if (earlierEvents.length > 0) {
30211
30430
  await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
@@ -31008,6 +31227,75 @@ var ParallAgentGateway = class {
31008
31227
  return true;
31009
31228
  return this.handleExternalTriggerRun(run);
31010
31229
  }
31230
+ // fetchAndHandleChannelMessage resolves a channel_message dispatch to its
31231
+ // durable ChannelMessage + conversation and hands it to the inbound
31232
+ // pipeline. targetId = the ChannelConversation id, so per-conversation
31233
+ // multi-turn continuity rides the same per-target session mechanics as
31234
+ // chats. Design: docs/engineering-design/external-im-channel-design.md.
31235
+ async fetchAndHandleChannelMessage(messageId) {
31236
+ if (this.shuttingDown)
31237
+ return false;
31238
+ const claimKey = `channel_message:${messageId}`;
31239
+ if (!this.tryClaimMessage(claimKey))
31240
+ return false;
31241
+ let msg = null;
31242
+ let conv = null;
31243
+ try {
31244
+ msg = await this.opts.client.getChannelMessage(this.opts.config.org_id, messageId);
31245
+ conv = await this.opts.client.getChannelConversation(this.opts.config.org_id, msg.conversation_id);
31246
+ } catch (err) {
31247
+ const status = err?.status;
31248
+ if (status === 404) {
31249
+ this.opts.log?.warn(`channel message ${messageId} not accessible (404), acking stale dispatch`);
31250
+ return true;
31251
+ }
31252
+ this.dispatchedMessages.delete(claimKey);
31253
+ this.opts.log?.warn(`channel message fetch failed for ${messageId}, leaving pending: ${String(err)}`);
31254
+ return false;
31255
+ }
31256
+ if (!msg || !conv) {
31257
+ return true;
31258
+ }
31259
+ this.opts.log?.info(`channel message: ${msg.id} (conversation ${conv.id})`);
31260
+ let provider = this.channelConnectionProviders.get(conv.connection_id);
31261
+ if (!provider) {
31262
+ try {
31263
+ const connection = await this.opts.client.getChannelConnection(this.opts.config.org_id, conv.connection_id);
31264
+ provider = connection.provider;
31265
+ this.channelConnectionProviders.set(conv.connection_id, provider);
31266
+ } catch {
31267
+ provider = void 0;
31268
+ }
31269
+ }
31270
+ const event = {
31271
+ type: "channel_message",
31272
+ targetId: conv.id,
31273
+ targetName: conv.external_user_name || conv.external_conversation_id,
31274
+ targetType: "channel_conversation",
31275
+ senderId: msg.external_user_id || "external",
31276
+ senderName: msg.external_user_name || msg.external_user_id || "external user",
31277
+ messageId: msg.id,
31278
+ body: msg.text,
31279
+ sentAt: msg.received_at,
31280
+ channelProvider: provider,
31281
+ channelConversationType: conv.conversation_type || void 0,
31282
+ channelExternalConversationId: conv.external_conversation_id,
31283
+ channelExternalMessageId: msg.external_message_id,
31284
+ ackSourceType: "channel_message",
31285
+ ackSourceId: msg.id
31286
+ };
31287
+ let dispatched;
31288
+ try {
31289
+ dispatched = await this.handleInboundEvent(event);
31290
+ } catch (err) {
31291
+ this.dispatchedMessages.delete(claimKey);
31292
+ throw err;
31293
+ }
31294
+ if (!dispatched) {
31295
+ this.dispatchedMessages.delete(claimKey);
31296
+ }
31297
+ return dispatched;
31298
+ }
31011
31299
  async handleExternalTriggerRun(run) {
31012
31300
  if (this.shuttingDown)
31013
31301
  return false;
@@ -31159,6 +31447,8 @@ var ParallAgentGateway = class {
31159
31447
  dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
31160
31448
  } else if (item.event_type === "external_trigger" && item.source_id) {
31161
31449
  dispatched = await this.fetchAndHandleExternalTriggerRun(item.source_id);
31450
+ } else if (item.event_type === "channel_message" && item.source_id) {
31451
+ dispatched = await this.fetchAndHandleChannelMessage(item.source_id);
31162
31452
  } else if (item.event_type === "approval_decided" && item.source_id) {
31163
31453
  dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
31164
31454
  } else if (item.event_type === "message" && item.source_id && item.chat_id) {
@@ -31356,6 +31646,9 @@ function resolveRuntimeModel(isOperatorOverride, platformModel, configModel) {
31356
31646
  const operatorOverride = isOperatorOverride ? platformModel : null;
31357
31647
  return operatorOverride ?? configModel ?? platformModel ?? void 0;
31358
31648
  }
31649
+ function deriveModelIsPin(defaults, profile) {
31650
+ return defaults.modelIsPin !== void 0 ? defaults.modelIsPin : isPlatformModelOverride(profile);
31651
+ }
31359
31652
  var CACHE_FILENAME = "parall-platform-config.json";
31360
31653
  var SUPPORTED_SCHEMA_VERSION = 1;
31361
31654
  function cachePath(stateDir) {
@@ -31373,6 +31666,7 @@ function saveCache(stateDir, response) {
31373
31666
  const cached = {
31374
31667
  version: response.version,
31375
31668
  config: response.config,
31669
+ modelIsPin: response.model_is_pin,
31376
31670
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
31377
31671
  };
31378
31672
  const filePath = cachePath(stateDir);
@@ -31383,23 +31677,27 @@ function saveCache(stateDir, response) {
31383
31677
  }
31384
31678
  function runtimeModelName(canonicalModel, runtimeType, config) {
31385
31679
  const runtime = runtimeType?.trim();
31386
- if (!runtime || runtime === "openclaw")
31680
+ if (!runtime || runtime === "openclaw" || runtime === "hermes")
31387
31681
  return canonicalModel;
31388
31682
  const models = config.models ?? {};
31389
31683
  const providers = models.providers ?? {};
31390
31684
  const parall = providers.parall ?? {};
31391
31685
  const catalog = Array.isArray(parall.models) ? parall.models : [];
31392
31686
  const match = catalog.find((model) => model.id === canonicalModel);
31393
- const runtimeNames = match?.runtime_names ?? {};
31687
+ if (!match)
31688
+ return null;
31689
+ const runtimeNames = match.runtime_names ?? {};
31394
31690
  const runtimeName = runtimeNames[runtime];
31395
- return typeof runtimeName === "string" && runtimeName ? runtimeName : null;
31691
+ if (typeof runtimeName === "string" && runtimeName)
31692
+ return runtimeName;
31693
+ return canonicalModel;
31396
31694
  }
31397
31695
  function extractDefaults(config, runtimeType) {
31398
31696
  const agents = config.agents ?? {};
31399
31697
  const defaults = agents.defaults ?? {};
31400
31698
  let model = null;
31401
31699
  if (typeof defaults.model === "string" && defaults.model) {
31402
- const canonicalModel = defaults.model.replace(/^parall\//, "");
31700
+ const canonicalModel = defaults.model.replace(/^parall-anthropic\//, "").replace(/^parall\//, "");
31403
31701
  model = runtimeModelName(canonicalModel, runtimeType, config);
31404
31702
  }
31405
31703
  let thinkingEffort = null;
@@ -31411,13 +31709,20 @@ function extractDefaults(config, runtimeType) {
31411
31709
  function createPlatformConfigManager(opts) {
31412
31710
  const { client, stateDir, runtimeType, log: log2 } = opts;
31413
31711
  let cachedVersion;
31414
- let currentDefaults = { model: null, thinkingEffort: null };
31712
+ let currentDefaults = {
31713
+ model: null,
31714
+ thinkingEffort: null,
31715
+ modelIsPin: void 0
31716
+ };
31415
31717
  let currentRawConfig = null;
31416
31718
  const cached = loadCache(stateDir);
31417
31719
  if (cached) {
31418
- cachedVersion = cached.version;
31720
+ cachedVersion = cached.modelIsPin === void 0 ? void 0 : cached.version;
31419
31721
  currentRawConfig = cached.config;
31420
- currentDefaults = extractDefaults(cached.config, runtimeType);
31722
+ currentDefaults = {
31723
+ ...extractDefaults(cached.config, runtimeType),
31724
+ modelIsPin: cached.modelIsPin
31725
+ };
31421
31726
  }
31422
31727
  return {
31423
31728
  async fetch() {
@@ -31443,7 +31748,10 @@ function createPlatformConfigManager(opts) {
31443
31748
  saveCache(stateDir, fresh);
31444
31749
  cachedVersion = fresh.version;
31445
31750
  currentRawConfig = fresh.config;
31446
- currentDefaults = extractDefaults(fresh.config, runtimeType);
31751
+ currentDefaults = {
31752
+ ...extractDefaults(fresh.config, runtimeType),
31753
+ modelIsPin: fresh.model_is_pin
31754
+ };
31447
31755
  return currentDefaults;
31448
31756
  },
31449
31757
  current() {
@@ -31526,22 +31834,69 @@ parall machines logs prll://mch_xxx --lines 100
31526
31834
  \`\`\`bash
31527
31835
  parall chats list # List all chats
31528
31836
  parall messages list prll://cht_xxx # Read chat message history
31837
+ parall messages list prll://cht_xxx --since 2026-01-01 # Only messages at/after a date (RFC3339 or YYYY-MM-DD)
31529
31838
  \`\`\`
31530
31839
 
31840
+ ## Org-Context Search
31841
+
31842
+ Before deciding or starting non-trivial work, search the org's real history \u2014
31843
+ past discussions, decisions, tasks, and wiki notes \u2014 so you don't re-litigate
31844
+ settled questions or repeat known mistakes. This searches live org data
31845
+ (semantic + keyword), not a local copy, and is permission-filtered to what you
31846
+ can see.
31847
+
31848
+ \`\`\`bash
31849
+ # Semantic + keyword search across messages, tasks, and wiki
31850
+ parall search "auth v5 upgrade"
31851
+
31852
+ # Restrict entity types (m=message, t=task, w=wiki). --channel narrows the
31853
+ # MESSAGE hits to one chat (tasks/wiki are unaffected by it).
31854
+ parall search "auth v5 upgrade" --types m,w --channel prll://cht_eng
31855
+
31856
+ # Time-box to recent activity (RFC3339 or YYYY-MM-DD). Narrows messages + tasks;
31857
+ # wiki is always matched by relevance (the index has no authored timestamp).
31858
+ parall search "auth v5 upgrade" --since 2026-01-01
31859
+
31860
+ # Narrow wiki hits to a frontmatter document type
31861
+ parall search "deploy steps" --types w --wiki-type Runbook
31862
+ \`\`\`
31863
+
31864
+ Even with zero curated notes, the raw message + task history is searchable \u2014 the
31865
+ original discussion and its approval/rejection IS the precedent.
31866
+
31531
31867
  ## Sending Messages
31532
31868
 
31533
31869
  Each \`[Event: message.new]\` includes \`[Chat: ... (prll://cht_xxx)]\` \u2014 use that chat URI to reply.
31534
31870
 
31871
+ > **How you pass the message body matters \u2014 your command runs through a shell.**
31872
+ > Inside double quotes the shell expands \`$\`, backticks, and \`$(...)\` *before*
31873
+ > the CLI sees them: \`--text "That costs $1,000"\` sends \`That costs ,000\`, and
31874
+ > \`--text "$(cmd)"\` runs \`cmd\`. Single quotes instead break on apostrophes
31875
+ > (\`I'm\`, \`don't\`). So do **not** wrap real message content in quotes \u2014 pass it
31876
+ > through \`--text-file\` (a written file, or a quoted heredoc \`<<'EOF'\` that
31877
+ > disables all expansion). Reserve \`--text "..."\` for short literals with no
31878
+ > \`$\`, backtick, or apostrophe.
31879
+
31535
31880
  \`\`\`bash
31536
- # Reply to a chat (use the chat URI from the event)
31537
- parall messages send prll://cht_xxx --text "Your reply"
31881
+ # One-off reply \u2192 quoted heredoc into stdin. The quoted delimiter <<'EOF'
31882
+ # disables ALL shell expansion, so $, backticks and apostrophes pass verbatim.
31883
+ parall messages send prll://cht_xxx --text-file - <<'PARALL_EOF'
31884
+ Sure \u2014 that's $1,000, and $(whoami) stays literal. I'm on it.
31885
+ PARALL_EOF
31886
+
31887
+ # Longer / multi-line reply \u2192 write it with your file tool (no shell touches
31888
+ # the body), then point --text-file at the file.
31889
+ parall messages send prll://cht_xxx --text-file /tmp/reply.md
31890
+
31891
+ # Short literal with no $, backtick, or apostrophe \u2192 --text is fine.
31892
+ parall messages send prll://cht_xxx --text "On it"
31538
31893
 
31539
- # Direct message by user URI or display name
31540
- parall dm prll://usr_xxx --text "Hello"
31894
+ # Direct message by user URI or display name (same --text-file / heredoc rules)
31895
+ parall dm prll://usr_xxx --text-file /tmp/reply.md
31541
31896
  parall dm "Alice" --text "Hello"
31542
31897
 
31543
31898
  # Thread reply
31544
- parall messages send prll://cht_xxx --text "Reply" --thread-root-id 01JWC...
31899
+ parall messages send prll://cht_xxx --text-file /tmp/reply.md --thread-root-id 01JWC...
31545
31900
 
31546
31901
  # FYI message (no response expected \u2014 the recipient sees \`[Hint: no_reply]\`)
31547
31902
  parall messages send prll://cht_xxx --text "FYI: done" --no-reply
@@ -31573,7 +31928,9 @@ parall messages send prll://cht_xxx --attachment att_xxx --text "See attached"
31573
31928
  parall dm "Alice" --file /tmp/report.pdf --text "Report attached"
31574
31929
  \`\`\`
31575
31930
 
31576
- \`--file\` and \`--attachment\` are mutually exclusive. \`--text\` can be combined with either.
31931
+ \`--file\` and \`--attachment\` are mutually exclusive. A caption (\`--text\` for
31932
+ short literals, or \`--text-file\` for anything with \`$\`, backticks, or quotes)
31933
+ can be combined with either.
31577
31934
 
31578
31935
  ## Approvals
31579
31936
 
@@ -31629,6 +31986,28 @@ When a message or event references \`prll://sch_xxx\` or \`prll://srn_xxx\`, or
31629
31986
 
31630
31987
  When a message or event references \`prll://xcn_xxx\`, \`prll://xin_xxx\`, \`prll://xtr_xxx\`, or \`prll://xrn_xxx\`, or when you receive \`[Event: external.trigger]\`, switch to the **parall-external-triggers** skill for the CLI commands (connections / triggers / events / runs).
31631
31988
 
31989
+ ## References (relationship graph)
31990
+
31991
+ \`prll://\` references between entities form a graph \u2014 a message cites a task, a
31992
+ task cites a wiki page, and so on. Walk it to answer "what is this decision /
31993
+ entity connected to". All results are permission-filtered to what you can see.
31994
+
31995
+ \`\`\`bash
31996
+ # Resolve URIs to entity metadata (titles, status, previews)
31997
+ parall refs resolve prll://tsk_xxx prll://wik_xxx
31998
+
31999
+ # Single hop \u2014 who references X
32000
+ parall refs backlinks prll://tsk_xxx
32001
+
32002
+ # Multi-hop \u2014 the connected sub-graph around X (entity-level URI only \u2014 no
32003
+ # path/anchor; depth 1\u20134, default 2)
32004
+ parall refs graph prll://tsk_xxx --depth 2
32005
+ \`\`\`
32006
+
32007
+ \`refs graph\` traverses both directions (inbound + outbound) and returns \`nodes\`
32008
+ and \`edges\` with each node's hop \`depth\`. \`truncated: true\` means a size cap clipped
32009
+ the result \u2014 narrow it with a smaller \`--depth\`.
32010
+
31632
32011
  CLI success output is JSON. Errors print a JSON line (\`{"error","status","code",...}\`) and, on a \`PERMISSION_DENIED\`, may add a plain-text \`Request approval:\` line \u2014 read both.
31633
32012
  `;
31634
32013
 
@@ -33730,14 +34109,13 @@ async function main() {
33730
34109
  log: agentLog
33731
34110
  });
33732
34111
  const platformDefaults = await configMgr.fetch();
33733
- let platformManaged = isPlatformManagedProfile(me.agent_profile);
33734
- let platformModelOverride = isPlatformModelOverride(me.agent_profile);
34112
+ const platformModelOverride = deriveModelIsPin(platformDefaults, me.agent_profile);
33735
34113
  const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
33736
- const resolvedEffort = platformManaged ? platformDefaults.thinkingEffort ?? (process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || void 0) : process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || void 0;
34114
+ const resolvedEffort = platformDefaults.thinkingEffort ?? (process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || void 0);
33737
34115
  if (platformDefaults.model)
33738
- agentLog.info(`platform config: model=${platformDefaults.model} \u2192 resolved=${resolvedModel ?? "cli-default"} (${platformModelOverride ? "platform-managed" : "floor/self"})`);
34116
+ agentLog.info(`platform config: model=${platformDefaults.model} \u2192 resolved=${resolvedModel ?? "cli-default"} (${platformModelOverride ? "pin/override" : "floor"})`);
33739
34117
  if (platformDefaults.thinkingEffort)
33740
- agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort} (${platformModelOverride ? "applied" : "ignored, model_management=self"})`);
34118
+ agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
33741
34119
  const adapter = new ClaudeCodeAdapter({
33742
34120
  claudeBin: config.claudeBin,
33743
34121
  claudeHome: config.claudeHome,
@@ -33762,6 +34140,23 @@ async function main() {
33762
34140
  if (resolvedEffort) {
33763
34141
  adapter.updateConfig({ effort: resolvedEffort });
33764
34142
  }
34143
+ const refreshPlatformConfig = async () => {
34144
+ const updated = await configMgr.fetch();
34145
+ let refreshedProfile;
34146
+ if (updated.modelIsPin === void 0) {
34147
+ try {
34148
+ refreshedProfile = (await getAgentMeWithLegacyFallback(client, config.orgId)).agent_profile;
34149
+ } catch (err) {
34150
+ agentLog.warn(`platform config refresh: legacy /agents/me fallback failed: ${String(err)}`);
34151
+ }
34152
+ }
34153
+ const isPin = deriveModelIsPin(updated, refreshedProfile);
34154
+ const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
34155
+ adapter.updateConfig({
34156
+ model: resolveRuntimeModel(isPin, updated.model, config.model) ?? null,
34157
+ effort: updated.thinkingEffort ?? localEffort
34158
+ });
34159
+ };
33765
34160
  const gateway = new ParallAgentGateway({
33766
34161
  accountId: agentUserId,
33767
34162
  client,
@@ -33788,28 +34183,8 @@ async function main() {
33788
34183
  dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
33789
34184
  contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
33790
34185
  stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
33791
- onConfigUpdate: async () => {
33792
- const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
33793
- platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
33794
- platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
33795
- const updated = await configMgr.fetch();
33796
- const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
33797
- adapter.updateConfig({
33798
- model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
33799
- effort: platformManaged ? updated.thinkingEffort ?? localEffort : localEffort
33800
- });
33801
- },
33802
- onSessionReady: async () => {
33803
- const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
33804
- platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
33805
- platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
33806
- const updated = await configMgr.fetch();
33807
- const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
33808
- adapter.updateConfig({
33809
- model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
33810
- effort: platformManaged ? updated.thinkingEffort ?? localEffort : localEffort
33811
- });
33812
- },
34186
+ onConfigUpdate: refreshPlatformConfig,
34187
+ onSessionReady: refreshPlatformConfig,
33813
34188
  // Long-lived `claude` subprocesses outlive a single dispatch, so the
33814
34189
  // gateway's shuttingDown flag alone does not tear them down. Piggyback on
33815
34190
  // onBeforeDisconnect to close stdin and SIGTERM any survivors after