@parall/daemon 1.35.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
  }
@@ -27219,6 +27242,17 @@ var ENDPOINTS = {
27219
27242
  SCHEDULE_CANCEL: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}/cancel`,
27220
27243
  SCHEDULE_RUNS: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}/runs`,
27221
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}`,
27222
27256
  // Invitations (org-scoped, admin)
27223
27257
  ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
27224
27258
  ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
@@ -27428,6 +27462,7 @@ var WS_EVENTS = {
27428
27462
  // ts/sdk/dist/client.js
27429
27463
  var ParallClient = class _ParallClient {
27430
27464
  baseUrl;
27465
+ wikiBaseUrl;
27431
27466
  token;
27432
27467
  onTokenExpired;
27433
27468
  getRefreshToken;
@@ -27479,12 +27514,22 @@ var ParallClient = class _ParallClient {
27479
27514
  }
27480
27515
  constructor(options = {}) {
27481
27516
  this.baseUrl = options.baseUrl ?? "";
27517
+ this.wikiBaseUrl = options.wikiBaseUrl ?? this.baseUrl;
27482
27518
  this.token = options.token ?? null;
27483
27519
  this.onTokenExpired = options.onTokenExpired;
27484
27520
  this.getRefreshToken = options.getRefreshToken;
27485
27521
  this.setTokens = options.setTokens;
27486
27522
  this.swimlaneName = options.swimlaneName;
27487
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(path8) {
27531
+ return path8.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
27532
+ }
27488
27533
  setToken(token) {
27489
27534
  this.token = token;
27490
27535
  }
@@ -27549,7 +27594,7 @@ var ParallClient = class _ParallClient {
27549
27594
  if (!retried) {
27550
27595
  await this.ensureFreshToken(path8);
27551
27596
  }
27552
- let url = `${this.baseUrl}${path8}`;
27597
+ let url = `${this.baseUrlFor(path8)}${path8}`;
27553
27598
  if (query) {
27554
27599
  const params = new URLSearchParams();
27555
27600
  for (const [key, value] of Object.entries(query)) {
@@ -27621,7 +27666,7 @@ var ParallClient = class _ParallClient {
27621
27666
  void _drop;
27622
27667
  let res;
27623
27668
  try {
27624
- res = await fetch(`${this.baseUrl}${path8}`, {
27669
+ res = await fetch(`${this.baseUrlFor(path8)}${path8}`, {
27625
27670
  method,
27626
27671
  headers,
27627
27672
  body,
@@ -27781,6 +27826,21 @@ var ParallClient = class _ParallClient {
27781
27826
  q.limit = String(params.limit);
27782
27827
  return this.request("GET", ENDPOINTS.ORG_MEMBER_TASKS(orgId, memberId), void 0, q);
27783
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
+ }
27784
27844
  // ---- Invitations ----
27785
27845
  async createInvitation(orgId, email, role) {
27786
27846
  return this.request("POST", ENDPOINTS.ORG_INVITATIONS(orgId), { email, role });
@@ -28370,7 +28430,7 @@ var ParallClient = class _ParallClient {
28370
28430
  * Returns null when the server responds with 304 (config unchanged).
28371
28431
  */
28372
28432
  async getPlatformConfig(currentVersion) {
28373
- const url = `${this.baseUrl}${ENDPOINTS.PLATFORM_CONFIG}`;
28433
+ const url = `${this.baseUrlFor(ENDPOINTS.PLATFORM_CONFIG)}${ENDPOINTS.PLATFORM_CONFIG}`;
28374
28434
  const extra = {};
28375
28435
  if (currentVersion !== void 0) {
28376
28436
  extra["If-None-Match"] = currentVersion;
@@ -28536,6 +28596,55 @@ var ParallClient = class _ParallClient {
28536
28596
  async getScheduleRun(orgId, runId) {
28537
28597
  return this.request("GET", ENDPOINTS.SCHEDULE_RUN(orgId, runId));
28538
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
+ }
28539
28648
  // ---- Wikis (org-scoped) ----
28540
28649
  async createWiki(orgId, data) {
28541
28650
  return this.request("POST", ENDPOINTS.WIKIS(orgId), data);
@@ -28550,8 +28659,35 @@ var ParallClient = class _ParallClient {
28550
28659
  async getWikiTree(orgId, wikiId, params) {
28551
28660
  return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
28552
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
+ }
28553
28686
  async getWikiBlob(orgId, wikiId, params) {
28554
- 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;
28555
28691
  }
28556
28692
  async getWikiNodeSections(orgId, wikiId, params) {
28557
28693
  return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
@@ -28641,7 +28777,10 @@ var ParallClient = class _ParallClient {
28641
28777
  * token — don't leak it.
28642
28778
  */
28643
28779
  async getWikiFilePreviewUrl(orgId, wikiId, params) {
28644
- 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;
28645
28784
  }
28646
28785
  // ---- Wiki Path Scopes (AFCS ACL) ----
28647
28786
  async getWikiPathScopes(orgId, wikiId) {
@@ -28857,6 +28996,9 @@ var ParallClient = class _ParallClient {
28857
28996
  const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
28858
28997
  return resp.data;
28859
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. */
28860
29002
  async listBrowserProfiles(orgId) {
28861
29003
  const resp = await this.request("GET", ENDPOINTS.BROWSER_PROFILES(orgId));
28862
29004
  return resp.data;
@@ -29510,6 +29652,9 @@ function resolveStepTarget(event) {
29510
29652
  if (event.type === "schedule" || event.targetId.startsWith("sch_")) {
29511
29653
  return { target_type: "schedule", target_id: event.targetId };
29512
29654
  }
29655
+ if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
29656
+ return { target_type: "external_trigger", target_id: event.targetId };
29657
+ }
29513
29658
  if (event.type === "wiki_comment") {
29514
29659
  return { target_type: "wiki", target_id: event.targetId || void 0 };
29515
29660
  }
@@ -29714,6 +29859,18 @@ var ParallAgentGateway = class {
29714
29859
  } catch (err) {
29715
29860
  this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
29716
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
+ }
29717
29874
  } else if (data.event_type === "approval_decided") {
29718
29875
  if (!data.source_id)
29719
29876
  return;
@@ -29792,8 +29949,13 @@ var ParallAgentGateway = class {
29792
29949
  target_type: target.target_type,
29793
29950
  target_id: target.target_id,
29794
29951
  content: {
29795
- 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",
29796
- 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 },
29797
29959
  sender_id: event.senderId,
29798
29960
  sender_name: event.senderName,
29799
29961
  summary: event.body.substring(0, 200),
@@ -30829,6 +30991,62 @@ var ParallAgentGateway = class {
30829
30991
  }
30830
30992
  return dispatched;
30831
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
+ }
30832
31050
  async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
30833
31051
  let approval = null;
30834
31052
  try {
@@ -30939,6 +31157,8 @@ var ParallAgentGateway = class {
30939
31157
  dispatched = await this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason);
30940
31158
  } else if (item.event_type === "schedule.fire" && item.source_id) {
30941
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);
30942
31162
  } else if (item.event_type === "approval_decided" && item.source_id) {
30943
31163
  dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
30944
31164
  } else if (item.event_type === "message" && item.source_id && item.chat_id) {
@@ -31398,11 +31618,17 @@ Every entity is addressable with a \`prll://\` URI. Common prefixes you'll see i
31398
31618
  | \`prll://prj_\` | Project | parall-tasks |
31399
31619
  | \`prll://sch_\` | Schedule (time trigger) | parall-schedules |
31400
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 |
31401
31625
  | \`prll://wik_\` | Wiki | parall-wiki |
31402
31626
  | \`prll://att_\` | Attachment | parall-platform (files) |
31403
31627
 
31404
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).
31405
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
+
31406
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.
31407
31633
  `;
31408
31634
 
@@ -31411,16 +31637,39 @@ var PARALL_TASKS_SKILL = `# Parall Tasks
31411
31637
 
31412
31638
  Manage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.
31413
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
+
31414
31661
  ## Task Commands
31415
31662
 
31416
31663
  \`\`\`bash
31417
- # List tasks (filterable by status)
31664
+ # List tasks (org-wide; filter by status, assignee, or parent)
31418
31665
  parall tasks list
31419
31666
  parall tasks list --status todo
31420
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
31421
31670
 
31422
- # Create a task
31423
- 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]
31424
31673
 
31425
31674
  # Update task status
31426
31675
  parall tasks update prll://tsk_xxx --status in_progress
@@ -31430,6 +31679,12 @@ parall tasks update prll://tsk_xxx --status done
31430
31679
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
31431
31680
  \`\`\`
31432
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
+
31433
31688
  ## Project Commands
31434
31689
 
31435
31690
  \`\`\`bash
@@ -31492,8 +31747,11 @@ Key facts the commands won't tell you:
31492
31747
  the absolute workspace path (\`synced \u2192 /path/to/<slug>\` / \`Mount: ...\`).
31493
31748
  Always address wiki files by that absolute path \u2014 your shell cwd is usually
31494
31749
  NOT inside the workspace.
31495
- - **Text files only.** Changesets reject binary content. Don't put images or
31496
- archives in the workspace; binary uploads go through the web UI.
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.
31497
31755
  - **\`cat\`, \`search\`, \`query\`, \`outline\`, and \`section\` read your local
31498
31756
  workspace copy when it exists** \u2014 including your own unproposed edits. Add
31499
31757
  \`--remote\` to \`cat\` to read the server version instead.
@@ -31574,6 +31832,50 @@ Re-propose REPLACES the changeset's previous contents with your current
31574
31832
  workspace diff \u2014 to withdraw a file from the proposal, revert it locally
31575
31833
  (restore the synced content) and re-propose; it drops out of the changeset.
31576
31834
 
31835
+ ## Binary files
31836
+
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).
31842
+
31843
+ \`\`\`bash
31844
+ # Maintainer: direct-commit a binary to the default branch (no review)
31845
+ parall wiki file upload ./diagram.png docs/assets/diagram.png
31846
+
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
31852
+
31853
+ # Remove a binary from the default branch (git history still has it)
31854
+ parall wiki file delete docs/assets/diagram.png
31855
+ \`\`\`
31856
+
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.
31860
+
31861
+ ### Reader: propose markdown that embeds an image
31862
+
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:
31865
+
31866
+ \`\`\`bash
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
31872
+ \`\`\`
31873
+
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
+
31577
31879
  ## Discovery & history
31578
31880
 
31579
31881
  \`\`\`bash
@@ -31687,6 +31989,96 @@ Do not treat schedule fires as "tasks assigned to you" \u2014 there's no status
31687
31989
  CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr (for example, \`Created: prll://sch_xxx\`).
31688
31990
  `;
31689
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
+
31690
32082
  // ts/agent-core/dist/skills/parall-clips.js
31691
32083
  var PARALL_CLIPS_SKILL = `# Parall Clips
31692
32084
 
@@ -31755,6 +32147,11 @@ var SKILLS = [
31755
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.",
31756
32148
  content: PARALL_SCHEDULES_SKILL
31757
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
+ },
31758
32155
  {
31759
32156
  name: "parall-clips",
31760
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.",