@parall/daemon 1.34.0 → 1.36.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.
@@ -26647,6 +26647,25 @@ 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 === "external_trigger") {
26651
+ lines.push(`[Event: external.trigger]`);
26652
+ lines.push(`[Trigger: prll://${event.targetId}]`);
26653
+ lines.push(`[Run: prll://${event.messageId}]`);
26654
+ if (event.externalConnectionId) {
26655
+ const label = event.externalConnectionDisplayName ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})` : `prll://${event.externalConnectionId}`;
26656
+ lines.push(`[Connection: ${label}]`);
26657
+ }
26658
+ if (event.externalIngressEventId)
26659
+ lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
26660
+ if (event.attachedUri)
26661
+ lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
26662
+ if (event.externalConnectionSourceType) {
26663
+ lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
26664
+ }
26665
+ if (event.externalIngressEventType) {
26666
+ lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
26667
+ }
26668
+ lines.push("", event.body);
26650
26669
  } else {
26651
26670
  lines.push(`[Event: task.assigned]`);
26652
26671
  const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
@@ -26678,6 +26697,10 @@ function buildSendMessageHint(event) {
26678
26697
  if (event.targetId.startsWith("sch_")) {
26679
26698
  return `
26680
26699
  <system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
26700
+ }
26701
+ if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
26702
+ return `
26703
+ <system-reminder>This external trigger is incoming-only. Your plain text output is not sent back to the external provider. To communicate in Parall, use \`parall messages send\` / \`parall dm\`; provider-specific outbound actions require a separate capability.</system-reminder>`;
26681
26704
  }
26682
26705
  return "";
26683
26706
  }
@@ -27081,6 +27104,7 @@ var ENDPOINTS = {
27081
27104
  // Org-scoped
27082
27105
  ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
27083
27106
  ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
27107
+ TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
27084
27108
  ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
27085
27109
  ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
27086
27110
  ORG_MEMBER_CHATS: (orgId, memberId) => `${API_BASE}/orgs/${orgId}/members/${memberId}/chats`,
@@ -27125,6 +27149,7 @@ var ENDPOINTS = {
27125
27149
  AGENT: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}`,
27126
27150
  AGENT_API_KEYS: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys`,
27127
27151
  AGENT_API_KEY: (orgId, agentId, key) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys/${key}`,
27152
+ AGENT_API_KEY_REGENERATE: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys/regenerate`,
27128
27153
  AGENT_AVATAR: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/avatar`,
27129
27154
  AGENT_ACTIVITY: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/activity`,
27130
27155
  AGENT_MONITOR: (orgId, agentId) => `${API_BASE}/orgs/${orgId}/agents/${agentId}/monitor`,
@@ -27164,6 +27189,7 @@ var ENDPOINTS = {
27164
27189
  MACHINE_AGENT_WORKSPACE_SETUP: (orgId, machineId, agentId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/agents/${agentId}/workspace/setup`,
27165
27190
  MACHINE_LLM_SOURCE: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/llm-source`,
27166
27191
  MACHINE_PROVIDER_ENABLED: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/provider-enabled`,
27192
+ MACHINE_CAPABILITIES: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/capabilities`,
27167
27193
  MACHINE_RUNTIME_AUTH: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth`,
27168
27194
  MACHINE_KEYS: (orgId, machineId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys`,
27169
27195
  MACHINE_KEY: (orgId, machineId, keyId) => `${API_BASE}/orgs/${orgId}/machines/${machineId}/keys/${keyId}`,
@@ -27184,6 +27210,12 @@ var ENDPOINTS = {
27184
27210
  MACHINES_ME_AGENT_WORKSPACE_STATE: (agentId) => `${API_BASE}/machines/me/agents/${agentId}/workspace-state`,
27185
27211
  MACHINES_ME_WS_TICKET: `${API_BASE}/machines/me/ws/ticket`,
27186
27212
  MACHINES_ME_BROWSE_RESPONSE: (requestId) => `${API_BASE}/machines/me/browse-response/${requestId}`,
27213
+ // Hosted browser live-viewer control plane (api-server, NOT clip-service):
27214
+ // the web client drives WebRTC signaling + tab nav through VIEWER_COMMAND;
27215
+ // api-server brokers each command to the host daemon via the machine:{id}
27216
+ // request/reply bridge (mirrors filesystem browse), and the daemon replies on
27217
+ // VIEWER_RESPONSE. See docs/engineering-design/hosted-browser-provider-design.md.
27218
+ MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE: (requestId) => `${API_BASE}/machines/me/browser-profiles/viewer-response/${requestId}`,
27187
27219
  // Tasks (org-scoped)
27188
27220
  TASKS: (orgId) => `${API_BASE}/orgs/${orgId}/tasks`,
27189
27221
  TASK: (orgId, taskId) => `${API_BASE}/orgs/${orgId}/tasks/${taskId}`,
@@ -27210,6 +27242,17 @@ var ENDPOINTS = {
27210
27242
  SCHEDULE_CANCEL: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}/cancel`,
27211
27243
  SCHEDULE_RUNS: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}/runs`,
27212
27244
  SCHEDULE_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/schedule_runs/${runId}`,
27245
+ // External triggers (org-scoped incoming integration primitive)
27246
+ EXTERNAL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/external-connections`,
27247
+ EXTERNAL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}`,
27248
+ EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}/ingress-token/regenerate`,
27249
+ EXTERNAL_TRIGGER_SCHEMA: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}/trigger-schema`,
27250
+ EXTERNAL_INGRESS_EVENTS: (orgId) => `${API_BASE}/orgs/${orgId}/external-ingress-events`,
27251
+ EXTERNAL_INGRESS_EVENT: (orgId, eventId) => `${API_BASE}/orgs/${orgId}/external-ingress-events/${eventId}`,
27252
+ EXTERNAL_TRIGGERS: (orgId) => `${API_BASE}/orgs/${orgId}/external-triggers`,
27253
+ EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
27254
+ EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
27255
+ EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
27213
27256
  // Invitations (org-scoped, admin)
27214
27257
  ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
27215
27258
  ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
@@ -27249,6 +27292,9 @@ var ENDPOINTS = {
27249
27292
  // Wiki Path Scopes (AFCS ACL)
27250
27293
  WIKI_PATH_SCOPES: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/path-scopes`,
27251
27294
  WIKI_PATH_SCOPE: (orgId, wikiId, scopeId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/path-scopes/${scopeId}`,
27295
+ // Wiki Path Restrictions (AFCS narrowing ACL — private subtrees)
27296
+ WIKI_RESTRICTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restrictions`,
27297
+ WIKI_RESTRICTION: (orgId, wikiId, restrictionId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restrictions/${restrictionId}`,
27252
27298
  WIKI_ACCESS_STATUS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/access-status`,
27253
27299
  WIKI_ACCESS_REQUESTS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/access-requests`,
27254
27300
  // Wiki History (commits, file commits, blame)
@@ -27314,6 +27360,7 @@ var ENDPOINTS = {
27314
27360
  // Clips (org-scoped, served by clip-service)
27315
27361
  CLIPS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/clips`,
27316
27362
  CLIP: (orgId, clipId) => `${CLIP_BASE}/orgs/${orgId}/clips/${clipId}`,
27363
+ CLIPS_BULK_METADATA: (orgId) => `${CLIP_BASE}/orgs/${orgId}/clips/bulk-metadata`,
27317
27364
  CLIP_AGENTS: (orgId, clipId) => `${CLIP_BASE}/orgs/${orgId}/clips/${clipId}/agents`,
27318
27365
  AGENT_CLIPS: (orgId, agentId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips`,
27319
27366
  AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
@@ -27326,6 +27373,9 @@ var ENDPOINTS = {
27326
27373
  BROWSER_PROFILE_RESET: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/reset`,
27327
27374
  BROWSER_PROFILE_CONSENTS: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/consents`,
27328
27375
  BROWSER_PROFILE_CONSENT: (orgId, profileId, clipId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/consents/${clipId}`,
27376
+ // Live viewer command — on api-server (API_BASE), not clip-service: it rides
27377
+ // the machine control-plane request/reply bridge that lives in api-server.
27378
+ BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
27329
27379
  // Clip registry (global, served by clip-service → Pinix Hub proxy)
27330
27380
  CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`
27331
27381
  };
@@ -27402,6 +27452,7 @@ var WS_EVENTS = {
27402
27452
  MACHINE_CONFIG_UPDATED: "machine.config.updated",
27403
27453
  MACHINE_CLIP_SYNC: "machine.clip.sync",
27404
27454
  MACHINE_BROWSER_PROFILE_LIFECYCLE: "machine.browser_profile.lifecycle",
27455
+ MACHINE_BROWSER_PROFILE_VIEWER: "machine.browser_profile.viewer",
27405
27456
  AGENT_NEW_SESSION: "agent.new_session",
27406
27457
  CLIP_CREATED: "clip.created",
27407
27458
  CLIP_REMOVED: "clip.removed",
@@ -27411,6 +27462,7 @@ var WS_EVENTS = {
27411
27462
  // ts/sdk/dist/client.js
27412
27463
  var ParallClient = class _ParallClient {
27413
27464
  baseUrl;
27465
+ wikiBaseUrl;
27414
27466
  token;
27415
27467
  onTokenExpired;
27416
27468
  getRefreshToken;
@@ -27462,12 +27514,22 @@ var ParallClient = class _ParallClient {
27462
27514
  }
27463
27515
  constructor(options = {}) {
27464
27516
  this.baseUrl = options.baseUrl ?? "";
27517
+ this.wikiBaseUrl = options.wikiBaseUrl ?? this.baseUrl;
27465
27518
  this.token = options.token ?? null;
27466
27519
  this.onTokenExpired = options.onTokenExpired;
27467
27520
  this.getRefreshToken = options.getRefreshToken;
27468
27521
  this.setTokens = options.setTokens;
27469
27522
  this.swimlaneName = options.swimlaneName;
27470
27523
  }
27524
+ /**
27525
+ * Pick the origin for a request path: wiki-service base for `/wiki/v1`
27526
+ * endpoints, api base for everything else. The path itself (from ENDPOINTS)
27527
+ * is authoritative, so wiki vs api routing can't drift from how a caller
27528
+ * happens to invoke the client.
27529
+ */
27530
+ baseUrlFor(path9) {
27531
+ return path9.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
27532
+ }
27471
27533
  setToken(token) {
27472
27534
  this.token = token;
27473
27535
  }
@@ -27532,7 +27594,7 @@ var ParallClient = class _ParallClient {
27532
27594
  if (!retried) {
27533
27595
  await this.ensureFreshToken(path9);
27534
27596
  }
27535
- let url = `${this.baseUrl}${path9}`;
27597
+ let url = `${this.baseUrlFor(path9)}${path9}`;
27536
27598
  if (query) {
27537
27599
  const params = new URLSearchParams();
27538
27600
  for (const [key, value] of Object.entries(query)) {
@@ -27553,7 +27615,10 @@ var ParallClient = class _ParallClient {
27553
27615
  method,
27554
27616
  headers,
27555
27617
  body: body ? JSON.stringify(body) : void 0,
27556
- signal
27618
+ signal,
27619
+ // keepalive lets a request fired during page unload (e.g. the browser
27620
+ // viewer's stream.close on pagehide) outlive the document.
27621
+ keepalive: opts?.keepalive
27557
27622
  });
27558
27623
  } catch (err) {
27559
27624
  throw _ParallClient.normalizeFetchError(err);
@@ -27601,7 +27666,7 @@ var ParallClient = class _ParallClient {
27601
27666
  void _drop;
27602
27667
  let res;
27603
27668
  try {
27604
- res = await fetch(`${this.baseUrl}${path9}`, {
27669
+ res = await fetch(`${this.baseUrlFor(path9)}${path9}`, {
27605
27670
  method,
27606
27671
  headers,
27607
27672
  body,
@@ -27724,6 +27789,10 @@ var ParallClient = class _ParallClient {
27724
27789
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
27725
27790
  return res.data;
27726
27791
  }
27792
+ async getTeams(orgId) {
27793
+ const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
27794
+ return res.data;
27795
+ }
27727
27796
  async getOnlineMembers(orgId) {
27728
27797
  const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_ONLINE(orgId));
27729
27798
  return res.user_ids ?? [];
@@ -27757,6 +27826,21 @@ var ParallClient = class _ParallClient {
27757
27826
  q.limit = String(params.limit);
27758
27827
  return this.request("GET", ENDPOINTS.ORG_MEMBER_TASKS(orgId, memberId), void 0, q);
27759
27828
  }
27829
+ // Auto-paginated variant of getMemberTasks: fetches ALL pending tasks
27830
+ // (todo + in_progress) assigned to a member, including subtasks (the
27831
+ // endpoint does not filter parent_id). Powers the CLI `tasks assigned`
27832
+ // command so an agent answering "what's on X's plate" sees the full
27833
+ // backlog, not just the first page.
27834
+ async getMemberTasksAll(orgId, memberId) {
27835
+ const all = [];
27836
+ let cursor;
27837
+ do {
27838
+ const res = await this.getMemberTasks(orgId, memberId, { cursor, limit: 100 });
27839
+ all.push(...res.data);
27840
+ cursor = res.has_more ? res.next_cursor : void 0;
27841
+ } while (cursor);
27842
+ return all;
27843
+ }
27760
27844
  // ---- Invitations ----
27761
27845
  async createInvitation(orgId, email, role) {
27762
27846
  return this.request("POST", ENDPOINTS.ORG_INVITATIONS(orgId), { email, role });
@@ -27962,6 +28046,10 @@ var ParallClient = class _ParallClient {
27962
28046
  async createAgentApiKey(orgId, agentId) {
27963
28047
  return this.request("POST", ENDPOINTS.AGENT_API_KEYS(orgId, agentId));
27964
28048
  }
28049
+ /** Revokes all of the agent's active API keys and mints a replacement. */
28050
+ async regenerateAgentApiKey(orgId, agentId) {
28051
+ return this.request("POST", ENDPOINTS.AGENT_API_KEY_REGENERATE(orgId, agentId));
28052
+ }
27965
28053
  async revokeAgentApiKey(orgId, agentId, key) {
27966
28054
  return this.request("DELETE", ENDPOINTS.AGENT_API_KEY(orgId, agentId, key));
27967
28055
  }
@@ -28133,6 +28221,17 @@ var ParallClient = class _ParallClient {
28133
28221
  provider_enabled: providerEnabled
28134
28222
  });
28135
28223
  }
28224
+ /**
28225
+ * Replace the machine's capability set (admin). Primary use: healing a
28226
+ * machine that registered without `browser_provider` during the capability
28227
+ * migration's rolling-deploy window. Removing `agent_host` is rejected by the
28228
+ * server while agents are attached (409 AGENTS_STILL_ATTACHED).
28229
+ */
28230
+ async patchMachineCapabilities(orgId, machineId, capabilities) {
28231
+ return this.request("PATCH", ENDPOINTS.MACHINE_CAPABILITIES(orgId, machineId), {
28232
+ capabilities
28233
+ });
28234
+ }
28136
28235
  /** Get machine-level runtime auth state. */
28137
28236
  async getMachineRuntimeAuth(orgId, machineId) {
28138
28237
  return this.request("GET", ENDPOINTS.MACHINE_RUNTIME_AUTH(orgId, machineId));
@@ -28223,6 +28322,22 @@ var ParallClient = class _ParallClient {
28223
28322
  async postBrowseResponse(requestId, response) {
28224
28323
  return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
28225
28324
  }
28325
+ /**
28326
+ * `POST /machines/me/browser-profiles/viewer-response/{requestId}` — daemon
28327
+ * reply to a `machine.browser_profile.viewer` control command. Wakes the
28328
+ * api-server request/reply bridge (mirrors {@link postBrowseResponse}).
28329
+ */
28330
+ async postBrowserProfileViewerResponse(requestId, response) {
28331
+ return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE(requestId), response);
28332
+ }
28333
+ /**
28334
+ * `POST /orgs/{orgId}/browser-profiles/{profileId}/viewer/command` — drive the
28335
+ * hosted browser live viewer (WebRTC signaling + tab nav). Authz: profile
28336
+ * owner or org admin. api-server brokers the command to the host daemon.
28337
+ */
28338
+ async browserViewerCommand(orgId, profileId, req, opts) {
28339
+ return this.request("POST", ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId), req, void 0, false, opts);
28340
+ }
28226
28341
  async resizeMachine(orgId, machineId, spec) {
28227
28342
  return this.request("PATCH", ENDPOINTS.MACHINE_SPEC(orgId, machineId), spec);
28228
28343
  }
@@ -28315,7 +28430,7 @@ var ParallClient = class _ParallClient {
28315
28430
  * Returns null when the server responds with 304 (config unchanged).
28316
28431
  */
28317
28432
  async getPlatformConfig(currentVersion) {
28318
- const url = `${this.baseUrl}${ENDPOINTS.PLATFORM_CONFIG}`;
28433
+ const url = `${this.baseUrlFor(ENDPOINTS.PLATFORM_CONFIG)}${ENDPOINTS.PLATFORM_CONFIG}`;
28319
28434
  const extra = {};
28320
28435
  if (currentVersion !== void 0) {
28321
28436
  extra["If-None-Match"] = currentVersion;
@@ -28481,6 +28596,55 @@ var ParallClient = class _ParallClient {
28481
28596
  async getScheduleRun(orgId, runId) {
28482
28597
  return this.request("GET", ENDPOINTS.SCHEDULE_RUN(orgId, runId));
28483
28598
  }
28599
+ // ---- External triggers (org-scoped) ----
28600
+ async createExternalConnection(orgId, input) {
28601
+ return this.request("POST", ENDPOINTS.EXTERNAL_CONNECTIONS(orgId), input);
28602
+ }
28603
+ async listExternalConnections(orgId, filters) {
28604
+ return this.request("GET", ENDPOINTS.EXTERNAL_CONNECTIONS(orgId), void 0, filters);
28605
+ }
28606
+ async getExternalConnection(orgId, connectionId) {
28607
+ return this.request("GET", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
28608
+ }
28609
+ async updateExternalConnection(orgId, connectionId, patch) {
28610
+ return this.request("PATCH", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId), patch);
28611
+ }
28612
+ async regenerateExternalConnectionIngressToken(orgId, connectionId) {
28613
+ return this.request("POST", ENDPOINTS.EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
28614
+ }
28615
+ async deleteExternalConnection(orgId, connectionId) {
28616
+ return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
28617
+ }
28618
+ async getExternalTriggerSchema(orgId, connectionId) {
28619
+ return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
28620
+ }
28621
+ async listExternalIngressEvents(orgId, filters) {
28622
+ return this.request("GET", ENDPOINTS.EXTERNAL_INGRESS_EVENTS(orgId), void 0, filters);
28623
+ }
28624
+ async getExternalIngressEvent(orgId, eventId) {
28625
+ return this.request("GET", ENDPOINTS.EXTERNAL_INGRESS_EVENT(orgId, eventId));
28626
+ }
28627
+ async createExternalTrigger(orgId, input) {
28628
+ return this.request("POST", ENDPOINTS.EXTERNAL_TRIGGERS(orgId), input);
28629
+ }
28630
+ async listExternalTriggers(orgId, filters) {
28631
+ return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGERS(orgId), void 0, filters);
28632
+ }
28633
+ async getExternalTrigger(orgId, triggerId) {
28634
+ return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
28635
+ }
28636
+ async updateExternalTrigger(orgId, triggerId, patch) {
28637
+ return this.request("PATCH", ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId), patch);
28638
+ }
28639
+ async deleteExternalTrigger(orgId, triggerId) {
28640
+ return this.request("DELETE", ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
28641
+ }
28642
+ async listExternalTriggerRuns(orgId, filters) {
28643
+ return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_RUNS(orgId), void 0, filters);
28644
+ }
28645
+ async getExternalTriggerRun(orgId, runId) {
28646
+ return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_RUN(orgId, runId));
28647
+ }
28484
28648
  // ---- Wikis (org-scoped) ----
28485
28649
  async createWiki(orgId, data) {
28486
28650
  return this.request("POST", ENDPOINTS.WIKIS(orgId), data);
@@ -28495,8 +28659,35 @@ var ParallClient = class _ParallClient {
28495
28659
  async getWikiTree(orgId, wikiId, params) {
28496
28660
  return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
28497
28661
  }
28662
+ /**
28663
+ * Resolve a server-returned, host-relative media URL (a wiki `signed_url`
28664
+ * like `/wiki/v1/signed/files?token=...`) against this client's base origin,
28665
+ * so it can be dropped straight into a browser `<img>`/`<video>`/`<iframe>`
28666
+ * `src`.
28667
+ *
28668
+ * wiki-service returns these relative on purpose — it doesn't know its own
28669
+ * public origin. A relative `src` resolves against the *page* origin, which
28670
+ * only works when the page and wiki-service share an origin (local dev:
28671
+ * same-origin + Next.js `/wiki/*` proxy). In deployed envs the app
28672
+ * (app.parall.com) and wiki-service (api.parall.com) are different origins,
28673
+ * so `app.parall.com/wiki/v1/signed/files` hits the SPA's own `/wiki/[...]`
28674
+ * catch-all route — an `<iframe>` then recursively renders the whole app
28675
+ * instead of the file. Prefixing with the wiki base (the exact origin every
28676
+ * wiki API request already uses — `baseUrlFor` resolves `/wiki/v1` paths to
28677
+ * `wikiBaseUrl`) makes the URL absolute against the origin that actually
28678
+ * serves the bytes. An empty base (local dev, same-origin proxy) leaves it
28679
+ * relative, preserving the proxy path.
28680
+ */
28681
+ absoluteMediaUrl(url) {
28682
+ if (/^https?:\/\//i.test(url))
28683
+ return url;
28684
+ return `${this.baseUrlFor(url)}${url}`;
28685
+ }
28498
28686
  async getWikiBlob(orgId, wikiId, params) {
28499
- return this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params);
28687
+ const blob = await this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params);
28688
+ if (blob.signed_url)
28689
+ blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
28690
+ return blob;
28500
28691
  }
28501
28692
  async getWikiNodeSections(orgId, wikiId, params) {
28502
28693
  return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
@@ -28586,7 +28777,10 @@ var ParallClient = class _ParallClient {
28586
28777
  * token — don't leak it.
28587
28778
  */
28588
28779
  async getWikiFilePreviewUrl(orgId, wikiId, params) {
28589
- return this.request("POST", ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId), params);
28780
+ const res = await this.request("POST", ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId), params);
28781
+ if (res.url)
28782
+ res.url = this.absoluteMediaUrl(res.url);
28783
+ return res;
28590
28784
  }
28591
28785
  // ---- Wiki Path Scopes (AFCS ACL) ----
28592
28786
  async getWikiPathScopes(orgId, wikiId) {
@@ -28599,6 +28793,17 @@ var ParallClient = class _ParallClient {
28599
28793
  async deleteWikiPathScope(orgId, wikiId, scopeId) {
28600
28794
  await this.request("DELETE", ENDPOINTS.WIKI_PATH_SCOPE(orgId, wikiId, scopeId));
28601
28795
  }
28796
+ // ---- Wiki Path Restrictions (narrowing ACL — private subtrees) ----
28797
+ async getWikiRestrictions(orgId, wikiId) {
28798
+ const res = await this.request("GET", ENDPOINTS.WIKI_RESTRICTIONS(orgId, wikiId));
28799
+ return res.data;
28800
+ }
28801
+ async createWikiRestriction(orgId, wikiId, data) {
28802
+ return this.request("POST", ENDPOINTS.WIKI_RESTRICTIONS(orgId, wikiId), data);
28803
+ }
28804
+ async deleteWikiRestriction(orgId, wikiId, restrictionId) {
28805
+ await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
28806
+ }
28602
28807
  async getWikiAccessStatus(orgId, wikiId, path9) {
28603
28808
  return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path9 ? { path: path9 } : void 0);
28604
28809
  }
@@ -28761,6 +28966,10 @@ var ParallClient = class _ParallClient {
28761
28966
  async updateClip(orgId, clipId, req) {
28762
28967
  return this.request("PATCH", ENDPOINTS.CLIP(orgId, clipId), req);
28763
28968
  }
28969
+ /** Atomically set display_name and/or description on multiple clip instances (application metadata). */
28970
+ async bulkUpdateClipMetadata(orgId, req) {
28971
+ return this.request("POST", ENDPOINTS.CLIPS_BULK_METADATA(orgId), req);
28972
+ }
28764
28973
  async deleteClip(orgId, clipId) {
28765
28974
  await this.request("DELETE", ENDPOINTS.CLIP(orgId, clipId));
28766
28975
  }
@@ -28787,6 +28996,9 @@ var ParallClient = class _ParallClient {
28787
28996
  const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
28788
28997
  return resp.data;
28789
28998
  }
28999
+ /** Org-wide browser-profile discovery list. Returns the sanitized
29000
+ * {@link BrowserProfileListItem} shape (not the full domain model), each row
29001
+ * carrying a per-viewer `can_open` control hint. */
28790
29002
  async listBrowserProfiles(orgId) {
28791
29003
  const resp = await this.request("GET", ENDPOINTS.BROWSER_PROFILES(orgId));
28792
29004
  return resp.data;
@@ -29440,6 +29652,9 @@ function resolveStepTarget(event) {
29440
29652
  if (event.type === "schedule" || event.targetId.startsWith("sch_")) {
29441
29653
  return { target_type: "schedule", target_id: event.targetId };
29442
29654
  }
29655
+ if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
29656
+ return { target_type: "external_trigger", target_id: event.targetId };
29657
+ }
29443
29658
  if (event.type === "wiki_comment") {
29444
29659
  return { target_type: "wiki", target_id: event.targetId || void 0 };
29445
29660
  }
@@ -29644,6 +29859,18 @@ var ParallAgentGateway = class {
29644
29859
  } catch (err) {
29645
29860
  this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
29646
29861
  }
29862
+ } else if (data.event_type === "external_trigger") {
29863
+ if (!data.source_id)
29864
+ return;
29865
+ try {
29866
+ const dispatched = await this.fetchAndHandleExternalTriggerRun(data.source_id);
29867
+ if (dispatched) {
29868
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
29869
+ });
29870
+ }
29871
+ } catch (err) {
29872
+ this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
29873
+ }
29647
29874
  } else if (data.event_type === "approval_decided") {
29648
29875
  if (!data.source_id)
29649
29876
  return;
@@ -29722,8 +29949,13 @@ var ParallAgentGateway = class {
29722
29949
  target_type: target.target_type,
29723
29950
  target_id: target.target_id,
29724
29951
  content: {
29725
- 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 === "approval" ? "approval_decided" : "mention",
29726
- 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 === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
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",
29953
+ 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
+ trigger_id: event.targetId,
29955
+ run_id: event.messageId,
29956
+ connection_id: event.externalConnectionId,
29957
+ ingress_event_id: event.externalIngressEventId
29958
+ } : event.type === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
29727
29959
  sender_id: event.senderId,
29728
29960
  sender_name: event.senderName,
29729
29961
  summary: event.body.substring(0, 200),
@@ -30292,7 +30524,7 @@ var ParallAgentGateway = class {
30292
30524
  this.dispatchState.mainDispatching = true;
30293
30525
  void this.drainMainBuffer();
30294
30526
  }
30295
- }, 5e3);
30527
+ }, 5e3).unref();
30296
30528
  }
30297
30529
  }
30298
30530
  }
@@ -30759,6 +30991,62 @@ var ParallAgentGateway = class {
30759
30991
  }
30760
30992
  return dispatched;
30761
30993
  }
30994
+ async fetchAndHandleExternalTriggerRun(runId) {
30995
+ let run = null;
30996
+ try {
30997
+ run = await this.opts.client.getExternalTriggerRun(this.opts.config.org_id, runId);
30998
+ } catch (err) {
30999
+ const status = err?.status;
31000
+ if (status === 404) {
31001
+ this.opts.log?.warn(`external trigger run ${runId} not accessible (404), acking stale dispatch`);
31002
+ return true;
31003
+ }
31004
+ this.opts.log?.warn(`external trigger run fetch failed for ${runId}, leaving pending: ${String(err)}`);
31005
+ return false;
31006
+ }
31007
+ if (!run)
31008
+ return true;
31009
+ return this.handleExternalTriggerRun(run);
31010
+ }
31011
+ async handleExternalTriggerRun(run) {
31012
+ if (this.shuttingDown)
31013
+ return false;
31014
+ const dedupeKey = `external_trigger_run:${run.id}`;
31015
+ if (this.dispatchedTasks.has(dedupeKey))
31016
+ return false;
31017
+ this.dispatchedTasks.add(dedupeKey);
31018
+ this.opts.log?.info(`external trigger fired: ${run.id} (trigger ${run.trigger_id})`);
31019
+ const attachedUri = typeof run.trigger_snapshot?.attached_to_uri === "string" ? run.trigger_snapshot.attached_to_uri : void 0;
31020
+ const event = {
31021
+ type: "external_trigger",
31022
+ targetId: run.trigger_id,
31023
+ targetName: run.trigger_name || void 0,
31024
+ targetType: "external_trigger",
31025
+ senderId: "system",
31026
+ senderName: "external",
31027
+ messageId: run.id,
31028
+ body: run.agent_input_body ?? "",
31029
+ externalConnectionId: run.connection_id,
31030
+ externalConnectionSourceType: run.connection_source_type || void 0,
31031
+ externalConnectionDisplayName: run.connection_display_name || void 0,
31032
+ externalIngressEventId: run.ingress_event_id,
31033
+ externalIngressEventType: run.ingress_event_type || void 0,
31034
+ attachedUri,
31035
+ ackSourceType: "external_trigger_run",
31036
+ ackSourceId: run.id
31037
+ };
31038
+ let dispatched;
31039
+ try {
31040
+ dispatched = await this.handleInboundEvent(event);
31041
+ } catch (err) {
31042
+ this.dispatchedTasks.delete(dedupeKey);
31043
+ throw err;
31044
+ }
31045
+ if (!dispatched) {
31046
+ this.dispatchedTasks.delete(dedupeKey);
31047
+ }
31048
+ return dispatched;
31049
+ }
30762
31050
  async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
30763
31051
  let approval = null;
30764
31052
  try {
@@ -30869,6 +31157,8 @@ var ParallAgentGateway = class {
30869
31157
  dispatched = await this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason);
30870
31158
  } else if (item.event_type === "schedule.fire" && item.source_id) {
30871
31159
  dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
31160
+ } else if (item.event_type === "external_trigger" && item.source_id) {
31161
+ dispatched = await this.fetchAndHandleExternalTriggerRun(item.source_id);
30872
31162
  } else if (item.event_type === "approval_decided" && item.source_id) {
30873
31163
  dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
30874
31164
  } else if (item.event_type === "message" && item.source_id && item.chat_id) {
@@ -31328,11 +31618,17 @@ Every entity is addressable with a \`prll://\` URI. Common prefixes you'll see i
31328
31618
  | \`prll://prj_\` | Project | parall-tasks |
31329
31619
  | \`prll://sch_\` | Schedule (time trigger) | parall-schedules |
31330
31620
  | \`prll://srn_\` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |
31621
+ | \`prll://xcn_\` | External Trigger Connection (incoming endpoint) | parall-external-triggers |
31622
+ | \`prll://xin_\` | External Trigger Event (single incoming event audit record) | parall-external-triggers |
31623
+ | \`prll://xtr_\` | External Trigger (incoming trigger configuration) | parall-external-triggers |
31624
+ | \`prll://xrn_\` | External Trigger run (single matched dispatch audit record) | parall-external-triggers |
31331
31625
  | \`prll://wik_\` | Wiki | parall-wiki |
31332
31626
  | \`prll://att_\` | Attachment | parall-platform (files) |
31333
31627
 
31334
31628
  When a message or event references \`prll://sch_xxx\` or \`prll://srn_xxx\`, or when you receive \`[Event: schedule.fired]\`, switch to the **parall-schedules** skill for the CLI commands (create / list / pause / resume / cancel / runs).
31335
31629
 
31630
+ 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
+
31336
31632
  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.
31337
31633
  `;
31338
31634
 
@@ -31341,16 +31637,39 @@ var PARALL_TASKS_SKILL = `# Parall Tasks
31341
31637
 
31342
31638
  Manage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.
31343
31639
 
31640
+ ## Finding What's on Someone's Plate (incl. subtasks)
31641
+
31642
+ To answer "what do I still have to do", "what's <person> working on", or any
31643
+ "open work assigned to X" question, use \`tasks assigned\`:
31644
+
31645
+ \`\`\`bash
31646
+ # Pending tasks (todo + in_progress) assigned to a member \u2014 INCLUDES subtasks.
31647
+ parall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)
31648
+ parall tasks assigned # yourself (defaults to the authenticated user)
31649
+ \`\`\`
31650
+
31651
+ This is the authoritative "open work for a person" query. It returns every
31652
+ pending task assigned to that member **including subtasks** \u2014 even when the
31653
+ subtask's parent task belongs to someone else. Decomposed work usually lives in
31654
+ subtasks, so do NOT answer this kind of question from \`tasks list\` alone:
31655
+ that is org-wide, page-capped, and not scoped to a person, so a person's
31656
+ subtasks are easily missed.
31657
+
31658
+ Resolve a person's \`prll://usr_\` id from the message context, the members
31659
+ list, or ref search; your own id comes from \`parall whoami\`.
31660
+
31344
31661
  ## Task Commands
31345
31662
 
31346
31663
  \`\`\`bash
31347
- # List tasks (filterable by status)
31664
+ # List tasks (org-wide; filter by status, assignee, or parent)
31348
31665
  parall tasks list
31349
31666
  parall tasks list --status todo
31350
31667
  parall tasks list --status in_progress
31668
+ parall tasks list --assignee-id prll://usr_xxx # first page only (default 20) \u2014 for a person's FULL backlog use 'tasks assigned' above
31669
+ parall tasks subtasks prll://tsk_xxx # children of a single parent task
31351
31670
 
31352
- # Create a task
31353
- parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--project-id prll://prj_xxx]
31671
+ # Create a task (add --parent-id to make it a SUBTASK of another task)
31672
+ parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
31354
31673
 
31355
31674
  # Update task status
31356
31675
  parall tasks update prll://tsk_xxx --status in_progress
@@ -31360,6 +31679,12 @@ parall tasks update prll://tsk_xxx --status done
31360
31679
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
31361
31680
  \`\`\`
31362
31681
 
31682
+ Subtasks are just tasks with a parent: create one with \`tasks create --parent-id\`,
31683
+ re-parent with \`tasks update --parent-id\`, list a parent's children with
31684
+ \`tasks subtasks\`. \`tasks list\` without \`--parent-id\` already returns both
31685
+ top-level tasks and subtasks; per-person open work is best fetched with
31686
+ \`tasks assigned\` (above).
31687
+
31363
31688
  ## Project Commands
31364
31689
 
31365
31690
  \`\`\`bash
@@ -31408,132 +31733,178 @@ var PARALL_WIKI_SKILL = `# Parall Wiki
31408
31733
 
31409
31734
  Manage organization wikis via the Parall CLI. Auth is pre-configured.
31410
31735
 
31411
- ## Browsing (no local state needed)
31736
+ ## Mental model
31737
+
31738
+ Wiki editing works on a **local workspace**: \`parall wiki sync\` downloads the
31739
+ wiki into a directory on disk, you edit those files with your normal file
31740
+ tools, then \`parall wiki changeset create\` uploads the result as a proposal.
31741
+ There is no git in the workspace \u2014 your edits are detected by diffing against
31742
+ the synced baseline.
31743
+
31744
+ Key facts the commands won't tell you:
31745
+
31746
+ - **Workspace location is fixed.** Sync output and \`parall wiki status\` print
31747
+ the absolute workspace path (\`synced \u2192 /path/to/<slug>\` / \`Mount: ...\`).
31748
+ Always address wiki files by that absolute path \u2014 your shell cwd is usually
31749
+ NOT inside the workspace.
31750
+ - **Text and binary are two paths.** The workspace + changeset flow is for
31751
+ text (markdown, code, config). Binary assets (images, PDFs, archives) are
31752
+ diff-less \u2014 they don't go in the workspace; use \`parall wiki file\` (see
31753
+ **Binary files** below). Dropping a binary into the workspace just gets it
31754
+ rejected on propose.
31755
+ - **\`cat\`, \`search\`, \`query\`, \`outline\`, and \`section\` read your local
31756
+ workspace copy when it exists** \u2014 including your own unproposed edits. Add
31757
+ \`--remote\` to \`cat\` to read the server version instead.
31758
+ - **\`search\`/\`query\` are keyword (lexical) matching**, not semantic search.
31759
+ \`query\` additionally ranks whole documents \u2014 better for multi-word
31760
+ questions; \`search\` for a single identifier.
31761
+ - The \`<wiki>\` argument is the slug or ID from \`parall wiki list\`; with a
31762
+ single wiki in the org it can be omitted.
31763
+
31764
+ ## Core workflow
31412
31765
 
31413
31766
  \`\`\`bash
31414
- parall wiki list # List all wikis
31415
- parall wiki tree <slug> # List files and directories
31416
- parall wiki tree <slug> --path docs/ # List a subdirectory
31767
+ parall wiki sync # 1. get/update files (prints workspace path)
31768
+ # 2. read + edit files under the workspace path with standard file tools
31769
+ parall wiki diff <wiki> # 3. review exactly what you'll propose
31770
+ parall wiki changeset create <wiki> --title "..." # 4. submit
31417
31771
  \`\`\`
31418
31772
 
31419
- ## Editing
31773
+ Always sync before starting and always check \`diff\` before proposing \u2014 the
31774
+ changeset uploads the full content of every changed file.
31420
31775
 
31421
- Wiki editing works on a **local workspace** \u2014 a directory on disk where wiki
31422
- files are synced. You sync from the server, edit files locally, then propose
31423
- a changeset.
31776
+ Unprotected paths auto-merge immediately (\`auto_merged: true\`); protected
31777
+ paths stay open for human review. Follow the returned \`next_action\` either way.
31424
31778
 
31425
- ### Step 1: Sync
31779
+ ## Stale base (server moved since your sync)
31426
31780
 
31427
- \`\`\`bash
31428
- parall wiki sync <slug>
31429
- \`\`\`
31430
-
31431
- This downloads wiki files to a local directory. The output includes the
31432
- **absolute mount path** for each wiki (e.g. \`synced \u2192 /path/to/workspace/kb\`).
31433
-
31434
- ### Step 2: Find the mount path
31435
-
31436
- The sync output JSON contains \`synced[].path\` \u2014 the absolute path where files
31437
- live. You can also check it anytime with:
31781
+ If files changed on the server after your last sync, \`changeset create\` is
31782
+ rejected \u2014 both by a CLI precheck and by the server (409 \`STALE_BASE\`) \u2014 so
31783
+ you can't silently overwrite someone's concurrent edit. Recovery:
31438
31784
 
31439
31785
  \`\`\`bash
31440
- parall wiki status <slug>
31786
+ parall wiki sync # pull latest; your local edits are preserved
31787
+ # if a file conflicts, resolve it (see next section)
31788
+ parall wiki changeset create <wiki> --title "..."
31441
31789
  \`\`\`
31442
31790
 
31443
- The output includes \`Mount: /absolute/path/to/<slug>\`.
31444
-
31445
- Use this path with \`read\`, \`write\`, and \`edit\` tools. For example, if the mount
31446
- is \`/data/.openclaw/workspace/kb\`, then \`README.md\` is at
31447
- \`/data/.openclaw/workspace/kb/README.md\`.
31791
+ ## Sync conflicts
31448
31792
 
31449
- ### Step 3: Edit files
31793
+ \`sync\` three-way merges. When both you and the server changed the same file,
31794
+ your file is left intact and the upstream copy lands under
31795
+ \`<workspace>/.parall-wiki/conflicts/\`:
31450
31796
 
31451
- Use standard file tools (\`read\`, \`write\`, \`edit\`) on files under the mount path.
31797
+ | Marker | Meaning |
31798
+ |--------|---------|
31799
+ | \`conflicts/<path>.remote\` | Server has different content for \`<path>\` |
31800
+ | \`conflicts/<path>.remote-deleted\` | Server deleted \`<path>\`; you still have edits |
31452
31801
 
31453
- ### Step 4: Review changes
31802
+ All paths below are relative to the workspace root. Pick one:
31454
31803
 
31455
31804
  \`\`\`bash
31456
- parall wiki diff <slug> # Shows unified diff of all local changes
31457
- parall wiki status <slug> # Shows which files changed with +/- line counts
31805
+ # Accept upstream (drop your edit):
31806
+ cp <workspace>/.parall-wiki/conflicts/<path>.remote <workspace>/<path>
31807
+
31808
+ # Keep yours / hand-merge: edit <workspace>/<path> to final content, then
31809
+ parall wiki changeset create <wiki> --title "Reconcile <path>"
31810
+
31811
+ # Accept server delete (.remote-deleted only):
31812
+ rm <workspace>/<path>
31458
31813
  \`\`\`
31459
31814
 
31460
- Always review before proposing.
31815
+ Then re-run \`parall wiki sync\` and delete the used marker file. Conflicts
31816
+ exit 0 (they need your decision); \`failed[]\` entries (download error,
31817
+ shape-conflict) exit 1 and retry on the next sync.
31461
31818
 
31462
- ### Step 5: Propose changeset
31819
+ ## Changesets
31463
31820
 
31464
31821
  \`\`\`bash
31465
- parall wiki changeset create <slug> --title "Description of changes"
31822
+ parall wiki changeset list <wiki>
31823
+ parall wiki changeset show <changesetId> <wiki> # status + feedback
31824
+ parall wiki changeset diff <changesetId> <wiki>
31825
+ parall wiki changeset create <wiki> --update <id> # re-propose after rejection (title inherited)
31466
31826
  \`\`\`
31467
31827
 
31468
- This uploads your local changes. Unprotected paths auto-merge immediately; the
31469
- CLI prints \`auto_merged: true\` and refreshes the local manifest. Protected
31470
- paths remain as a changeset for review; follow the returned \`next_action\`.
31828
+ Rejected: read the feedback (\`show\` / \`status\`), fix the files, re-propose
31829
+ with \`--update <id>\`. Conflict status: \`sync\`, resolve, then \`--update <id>\`.
31471
31830
 
31472
- ## Changeset Management
31831
+ Re-propose REPLACES the changeset's previous contents with your current
31832
+ workspace diff \u2014 to withdraw a file from the proposal, revert it locally
31833
+ (restore the synced content) and re-propose; it drops out of the changeset.
31473
31834
 
31474
- \`\`\`bash
31475
- parall wiki changeset list <slug> # List all changesets
31476
- parall wiki changeset show <changesetId> <slug> # Show detail + feedback
31477
- parall wiki changeset diff <changesetId> <slug> # Show changeset diff
31478
- \`\`\`
31835
+ ## Binary files
31479
31836
 
31480
- If a changeset is rejected, fix the files locally and re-propose:
31837
+ Images, PDFs, archives \u2014 anything that can't be diffed \u2014 bypass the workspace
31838
+ and changeset-text flow entirely. They never belong in the synced workspace
31839
+ (propose rejects them); use \`parall wiki file\` instead. \`cat\` is text-only \u2014
31840
+ to read a binary's real bytes use \`file get\` (a plain \`sync\` only leaves a
31841
+ few-line Git-LFS pointer on disk, since the runtime has no git-lfs).
31481
31842
 
31482
31843
  \`\`\`bash
31483
- parall wiki changeset create <slug> --update <changesetId>
31484
- \`\`\`
31844
+ # Maintainer: direct-commit a binary to the default branch (no review)
31845
+ parall wiki file upload ./diagram.png docs/assets/diagram.png
31485
31846
 
31486
- The title is inherited from the original changeset \u2014 no need to repeat it.
31847
+ # Read a binary's real bytes (LFS pointers resolved server-side) to a file.
31848
+ # Always use --output for binaries \u2014 without it the bytes stream to stdout and
31849
+ # would flood your context.
31850
+ parall wiki file get docs/assets/diagram.png --output ./diagram.png
31851
+ parall wiki file get docs/assets/diagram.png --ref <commit-or-branch> --output ./diagram.png # a specific revision
31487
31852
 
31488
- ## Handling sync conflicts
31853
+ # Remove a binary from the default branch (git history still has it)
31854
+ parall wiki file delete docs/assets/diagram.png
31855
+ \`\`\`
31489
31856
 
31490
- \`wiki sync\` runs a three-way merge. When both you and the server changed the
31491
- same file, sync **does not overwrite your work**. It leaves your file intact
31492
- and drops the upstream version under \`.parall-wiki/conflicts/\`:
31857
+ \`upload\` needs **maintain**; it routes by size automatically (\u22641 MiB inline,
31858
+ larger \u2192 LFS). A text file sent to \`upload\` is rejected \u2014 that's the changeset
31859
+ flow's job.
31493
31860
 
31494
- | Marker | Meaning |
31495
- |--------|---------|
31496
- | \`.parall-wiki/conflicts/<path>.remote\` | Server has different content (concurrent edit, new file collision, or server changed a file you deleted) |
31497
- | \`.parall-wiki/conflicts/<path>.remote-deleted\` | Server deleted the file; you still have edits |
31861
+ ### Reader: propose markdown that embeds an image
31498
31862
 
31499
- stderr prints one line per conflict. Recovery:
31863
+ Without maintain you can still propose a doc with images \u2014 upload the binary
31864
+ into your **changeset's** branch (read + author), not the default branch:
31500
31865
 
31501
- **Accept upstream** (drop your edit):
31502
31866
  \`\`\`bash
31503
- cp .parall-wiki/conflicts/<path>.remote <path>
31504
31867
  parall wiki sync
31868
+ # edit a .md in the workspace to add ![alt](assets/foo.png)
31869
+ parall wiki changeset create <wiki> --title "Add foo diagram" # creates the changeset (note its id)
31870
+ parall wiki file upload ./foo.png assets/foo.png <wiki> --changeset <changesetId>
31871
+ # leave it for a maintainer to merge \u2014 both the markdown and the image squash in together
31505
31872
  \`\`\`
31506
31873
 
31507
- **Keep yours** (or hand-merge, then propose):
31508
- \`\`\`bash
31509
- # edit <path> to final content
31510
- parall wiki diff <slug>
31511
- parall wiki changeset create <slug> --title "Reconcile <path>"
31512
- parall wiki sync # fast-forwards after server merges
31513
- \`\`\`
31874
+ Do the markdown \`changeset create\` first so the changeset exists, then attach
31875
+ the image to it. Don't re-propose (\`--update\`) after attaching a binary \u2014
31876
+ re-propose replays only the text workspace and the server rejects dropping the
31877
+ attached binary (422 \`REPLACE_HAS_BINARY\`).
31878
+
31879
+ ## Discovery & history
31514
31880
 
31515
- **Accept server delete** (\`.remote-deleted\` only):
31516
31881
  \`\`\`bash
31517
- rm <path>
31518
- parall wiki sync
31882
+ parall wiki query "how is auth configured" <wiki> # multi-word lookup (query FIRST, wiki second)
31883
+ parall wiki search "JWT" <wiki> # single keyword (query FIRST, wiki second)
31884
+ parall wiki outline <wiki> --path docs/ # heading structure
31885
+ parall wiki cat docs/auth.md <wiki> # print a file (--remote for server version)
31886
+ parall wiki tree <wiki> # list files
31887
+ parall wiki log <wiki> # recent operations
31888
+ parall wiki log <wiki> docs/auth.md # per-file commit history
31519
31889
  \`\`\`
31520
31890
 
31521
- Sync failures (\`download\`, \`shape-conflict\`) set exit code 1 and retry on
31522
- next sync. Conflicts exit 0 \u2014 they need your decision, not a retry.
31891
+ ## Permissions
31523
31892
 
31524
- ## Discarding local changes
31893
+ Reads and writes are ACL-checked server-side per path. \`parall wiki access
31894
+ <path>\` shows your level for a path. On a \`PERMISSION_DENIED\`, errors include
31895
+ a \`Request approval:\` hint \u2014 use \`parall wiki request-access <path> --reason
31896
+ "..."\` to file an approval card for a maintainer, then re-sync after approval.
31525
31897
 
31526
- \`\`\`bash
31527
- parall wiki reset <slug> # Restore all files to last synced state
31528
- \`\`\`
31529
-
31530
- ## History
31898
+ ## Recovery
31531
31899
 
31532
31900
  \`\`\`bash
31533
- parall wiki log <slug> # Recent wiki operations
31901
+ parall wiki reset <wiki> # discard ALL local edits, restore last-synced state
31902
+ parall wiki status <wiki> # local changes + your changesets, anytime
31534
31903
  \`\`\`
31535
31904
 
31536
- CLI success output is JSON; errors may add a plain-text \`Request approval:\` line on a \`PERMISSION_DENIED\` \u2014 read both. Run \`parall wiki --help\` for full options.
31905
+ CLI success output is JSON on stdout (human summary on stderr); errors state
31906
+ the reason and the next step \u2014 follow them. \`parall wiki --help\` for the
31907
+ full command list.
31537
31908
  `;
31538
31909
 
31539
31910
  // ts/agent-core/dist/skills/parall-schedules.js
@@ -31618,6 +31989,96 @@ Do not treat schedule fires as "tasks assigned to you" \u2014 there's no status
31618
31989
  CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr (for example, \`Created: prll://sch_xxx\`).
31619
31990
  `;
31620
31991
 
31992
+ // ts/agent-core/dist/skills/parall-external-triggers.js
31993
+ var PARALL_EXTERNAL_TRIGGERS_SKILL = `# Parall External Triggers
31994
+
31995
+ An **External Trigger** is an incoming platform trigger. External systems send events to an External Trigger Connection, Parall matches active triggers with CEL, renders a Liquid template into an agent input body, and dispatches that input to the configured target agents.
31996
+
31997
+ Use External Triggers for incoming events such as GitHub callbacks, Slack/Feishu notifications, generic webhooks, or emails once the platform has a connection for them. The runtime behavior is incoming-only: receiving a trigger does not imply that you can call the external system back unless another explicit Parall capability is available.
31998
+
31999
+ ## Prerequisite
32000
+
32001
+ External Trigger CLI commands are gated by the org-level \`external-triggers\` feature flag. If a command reports that the feature is unavailable, ask a human org admin to enable the flag before trying again. Public ingress delivery may still be live even when the management CLI is hidden behind the rollout flag.
32002
+
32003
+ ## Creating a trigger
32004
+
32005
+ \`\`\`bash
32006
+ # 1. Create a connection. The ingress token is shown once; prefer writing it
32007
+ # to a local file so it does not land in shell history or logs.
32008
+ parall external-triggers create-connection --name "GitHub CI" --token-file ./github-ci-webhook-token.txt
32009
+
32010
+ # 2. Create a trigger that targets one or more agents.
32011
+ parall external-triggers create \\
32012
+ --connection prll://xcn_xxx \\
32013
+ --name "Failed checks" \\
32014
+ --target-ids prll://usr_agent_xxx \\
32015
+ --filter "body.json.check_run.conclusion == 'failure'" \\
32016
+ --template-file ./github-check-failed.md \\
32017
+ --attached-to-uri prll://tsk_xxx
32018
+ \`\`\`
32019
+
32020
+ \`--filter\` is CEL. Omit it to match all incoming events (\`true\`). Keep filters small and deterministic; do not model provider-specific branching in agent code when the CEL expression can select the relevant events first.
32021
+
32022
+ \`--template\` / \`--template-file\` is Liquid with the safe Parall profile. It can read event data, body data, safe request metadata, trigger fields, run fields, and connection fields. It cannot call HTTP, read databases, evaluate arbitrary code, access platform secrets, or read arbitrary request headers/query parameters.
32023
+
32024
+ Example template:
32025
+
32026
+ \`\`\`liquid
32027
+ GitHub check failed.
32028
+
32029
+ Event: {{ request.headers.x_github_event | default: "unknown" }}
32030
+ Repo: {{ body.json.repository.full_name }}
32031
+ PR: {{ body.json.pull_request.number }} {{ body.json.pull_request.title }}
32032
+ Check: {{ body.json.check_run.name }}
32033
+ Conclusion: {{ body.json.check_run.conclusion }}
32034
+
32035
+ Run: {{ body.json.check_run.html_url }}
32036
+ \`\`\`
32037
+
32038
+ Do not render access tokens, signing secrets, cookies, or private credentials into agent input. Request snapshots and bodies may contain third-party data; treat them as user-provided input.
32039
+
32040
+ ## Inspecting and lifecycle
32041
+
32042
+ \`\`\`bash
32043
+ parall external-triggers connections
32044
+ parall external-triggers connection prll://xcn_xxx
32045
+ parall external-triggers schema prll://xcn_xxx
32046
+
32047
+ parall external-triggers list
32048
+ parall external-triggers list --connection prll://xcn_xxx
32049
+ parall external-triggers get prll://xtr_xxx
32050
+ parall external-triggers update prll://xtr_xxx --filter "event.type == 'check_run'"
32051
+ parall external-triggers pause prll://xtr_xxx
32052
+ parall external-triggers resume prll://xtr_xxx
32053
+ parall external-triggers delete prll://xtr_xxx
32054
+
32055
+ parall external-triggers runs prll://xtr_xxx
32056
+ parall external-triggers run prll://xrn_xxx
32057
+ parall external-triggers events --connection prll://xcn_xxx
32058
+ parall external-triggers event prll://xin_xxx
32059
+ \`\`\`
32060
+
32061
+ ## Responding to external trigger dispatches
32062
+
32063
+ When you receive \`[Event: external.trigger]\`, Parall has already matched a trigger and rendered its template. The prompt includes headers such as:
32064
+
32065
+ - \`[Trigger: prll://xtr_xxx]\`
32066
+ - \`[Run: prll://xrn_xxx]\`
32067
+ - \`[Connection: ... (prll://xcn_xxx)]\`
32068
+ - \`[Ingress: prll://xin_xxx]\`
32069
+ - Optional \`[Attached: prll://...]\`
32070
+
32071
+ The rendered agent input body follows those headers. You usually do not need to fetch the run before acting. Fetch the run only for audit/debugging:
32072
+
32073
+ \`\`\`bash
32074
+ parall external-triggers run prll://xrn_xxx
32075
+ \`\`\`
32076
+
32077
+ Act on the rendered input the same way you would act on a user message: send a message, create or update tasks, edit wiki pages, or use available clips. If no visible response is needed, use \`parall no-reply --reason "handled external trigger"\` before sending any message.
32078
+
32079
+ CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr, for example \`Created: prll://xtr_xxx\`.
32080
+ `;
32081
+
31621
32082
  // ts/agent-core/dist/skills/parall-clips.js
31622
32083
  var PARALL_CLIPS_SKILL = `# Parall Clips
31623
32084
 
@@ -31686,6 +32147,11 @@ var SKILLS = [
31686
32147
  description: "Parall schedule operations: create / pause / resume / cancel recurring or one-shot time triggers; respond to schedule fire events. Use when: user asks to set up a recurring reminder, schedule a delayed prompt, run cron-like work, or when the agent receives an `[Event: schedule.fired]` dispatch.",
31687
32148
  content: PARALL_SCHEDULES_SKILL
31688
32149
  },
32150
+ {
32151
+ name: "parall-external-triggers",
32152
+ description: "Parall External Trigger operations: create incoming connections, configure CEL/Liquid triggers, inspect incoming events and runs, and respond to `[Event: external.trigger]` dispatches. Use when: user asks to connect an external system to an agent, set up incoming callbacks/notifications, or when the agent receives an external trigger event.",
32153
+ content: PARALL_EXTERNAL_TRIGGERS_SKILL
32154
+ },
31689
32155
  {
31690
32156
  name: "parall-clips",
31691
32157
  description: "Parall clip operations: list installed clips, invoke clip commands, inspect clip details. Use when: the task requires external capabilities (GitHub, web search, etc.), user asks about available tools/clips, or you need to call a clip command.",
@@ -32574,6 +33040,9 @@ function buildSpawnEnv(parentEnv, claudeHome, context2, opts) {
32574
33040
  PRLL_CONTEXT_FILE: context2.contextFilePath ?? "",
32575
33041
  PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? ""
32576
33042
  };
33043
+ if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
33044
+ result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
33045
+ }
32577
33046
  if (opts.effortLevel) {
32578
33047
  result.CLAUDE_CODE_EFFORT_LEVEL = opts.effortLevel;
32579
33048
  }
@@ -32880,7 +33349,11 @@ var ClaudeCodeAdapter = class {
32880
33349
  }
32881
33350
  spawnProcess(sessionKey, log2) {
32882
33351
  const args = this.buildArgs(sessionKey);
32883
- const env = buildSpawnEnv(process.env, this.opts.claudeHome, this.buildPlaceholderContext(sessionKey), { allowApiKey: this.opts.allowApiKey, effortLevel: this._effortLevel });
33352
+ const env = buildSpawnEnv(process.env, this.opts.claudeHome, this.buildPlaceholderContext(sessionKey), {
33353
+ allowApiKey: this.opts.allowApiKey,
33354
+ effortLevel: this._effortLevel,
33355
+ wikiMountRoot: this.opts.workspaceDir
33356
+ });
32884
33357
  log2?.info(`spawn long-lived ${this.opts.claudeBin} (session ${sessionKey}, model=${this._model || "default"}, effort=${this._effortLevel || "default"}, mode=${this.opts.permissionMode})`);
32885
33358
  const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.claudeBin) : this.opts.claudeBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
32886
33359
  cwd: this.opts.workspaceDir,