@parall/parall 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.
package/dist/index.bundle.mjs
CHANGED
|
@@ -26662,6 +26662,25 @@ function buildEventBody(event) {
|
|
|
26662
26662
|
if (event.attachedUri)
|
|
26663
26663
|
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
26664
26664
|
lines.push("", event.body);
|
|
26665
|
+
} else if (event.type === "external_trigger") {
|
|
26666
|
+
lines.push(`[Event: external.trigger]`);
|
|
26667
|
+
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
26668
|
+
lines.push(`[Run: prll://${event.messageId}]`);
|
|
26669
|
+
if (event.externalConnectionId) {
|
|
26670
|
+
const label = event.externalConnectionDisplayName ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})` : `prll://${event.externalConnectionId}`;
|
|
26671
|
+
lines.push(`[Connection: ${label}]`);
|
|
26672
|
+
}
|
|
26673
|
+
if (event.externalIngressEventId)
|
|
26674
|
+
lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
|
|
26675
|
+
if (event.attachedUri)
|
|
26676
|
+
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
26677
|
+
if (event.externalConnectionSourceType) {
|
|
26678
|
+
lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
|
|
26679
|
+
}
|
|
26680
|
+
if (event.externalIngressEventType) {
|
|
26681
|
+
lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
|
|
26682
|
+
}
|
|
26683
|
+
lines.push("", event.body);
|
|
26665
26684
|
} else {
|
|
26666
26685
|
lines.push(`[Event: task.assigned]`);
|
|
26667
26686
|
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
@@ -26693,6 +26712,10 @@ function buildSendMessageHint(event) {
|
|
|
26693
26712
|
if (event.targetId.startsWith("sch_")) {
|
|
26694
26713
|
return `
|
|
26695
26714
|
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
26715
|
+
}
|
|
26716
|
+
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
26717
|
+
return `
|
|
26718
|
+
<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>`;
|
|
26696
26719
|
}
|
|
26697
26720
|
return "";
|
|
26698
26721
|
}
|
|
@@ -27148,6 +27171,17 @@ var ENDPOINTS = {
|
|
|
27148
27171
|
SCHEDULE_CANCEL: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}/cancel`,
|
|
27149
27172
|
SCHEDULE_RUNS: (orgId, id) => `${API_BASE}/orgs/${orgId}/schedules/${id}/runs`,
|
|
27150
27173
|
SCHEDULE_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/schedule_runs/${runId}`,
|
|
27174
|
+
// External triggers (org-scoped incoming integration primitive)
|
|
27175
|
+
EXTERNAL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/external-connections`,
|
|
27176
|
+
EXTERNAL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}`,
|
|
27177
|
+
EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}/ingress-token/regenerate`,
|
|
27178
|
+
EXTERNAL_TRIGGER_SCHEMA: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}/trigger-schema`,
|
|
27179
|
+
EXTERNAL_INGRESS_EVENTS: (orgId) => `${API_BASE}/orgs/${orgId}/external-ingress-events`,
|
|
27180
|
+
EXTERNAL_INGRESS_EVENT: (orgId, eventId) => `${API_BASE}/orgs/${orgId}/external-ingress-events/${eventId}`,
|
|
27181
|
+
EXTERNAL_TRIGGERS: (orgId) => `${API_BASE}/orgs/${orgId}/external-triggers`,
|
|
27182
|
+
EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
|
|
27183
|
+
EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
|
|
27184
|
+
EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
|
|
27151
27185
|
// Invitations (org-scoped, admin)
|
|
27152
27186
|
ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
|
|
27153
27187
|
ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
|
|
@@ -27357,6 +27391,7 @@ var WS_EVENTS = {
|
|
|
27357
27391
|
// ../sdk/dist/client.js
|
|
27358
27392
|
var ParallClient = class _ParallClient {
|
|
27359
27393
|
baseUrl;
|
|
27394
|
+
wikiBaseUrl;
|
|
27360
27395
|
token;
|
|
27361
27396
|
onTokenExpired;
|
|
27362
27397
|
getRefreshToken;
|
|
@@ -27408,12 +27443,22 @@ var ParallClient = class _ParallClient {
|
|
|
27408
27443
|
}
|
|
27409
27444
|
constructor(options = {}) {
|
|
27410
27445
|
this.baseUrl = options.baseUrl ?? "";
|
|
27446
|
+
this.wikiBaseUrl = options.wikiBaseUrl ?? this.baseUrl;
|
|
27411
27447
|
this.token = options.token ?? null;
|
|
27412
27448
|
this.onTokenExpired = options.onTokenExpired;
|
|
27413
27449
|
this.getRefreshToken = options.getRefreshToken;
|
|
27414
27450
|
this.setTokens = options.setTokens;
|
|
27415
27451
|
this.swimlaneName = options.swimlaneName;
|
|
27416
27452
|
}
|
|
27453
|
+
/**
|
|
27454
|
+
* Pick the origin for a request path: wiki-service base for `/wiki/v1`
|
|
27455
|
+
* endpoints, api base for everything else. The path itself (from ENDPOINTS)
|
|
27456
|
+
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27457
|
+
* happens to invoke the client.
|
|
27458
|
+
*/
|
|
27459
|
+
baseUrlFor(path7) {
|
|
27460
|
+
return path7.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27461
|
+
}
|
|
27417
27462
|
setToken(token) {
|
|
27418
27463
|
this.token = token;
|
|
27419
27464
|
}
|
|
@@ -27478,7 +27523,7 @@ var ParallClient = class _ParallClient {
|
|
|
27478
27523
|
if (!retried) {
|
|
27479
27524
|
await this.ensureFreshToken(path7);
|
|
27480
27525
|
}
|
|
27481
|
-
let url = `${this.
|
|
27526
|
+
let url = `${this.baseUrlFor(path7)}${path7}`;
|
|
27482
27527
|
if (query) {
|
|
27483
27528
|
const params = new URLSearchParams();
|
|
27484
27529
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27550,7 +27595,7 @@ var ParallClient = class _ParallClient {
|
|
|
27550
27595
|
void _drop;
|
|
27551
27596
|
let res;
|
|
27552
27597
|
try {
|
|
27553
|
-
res = await fetch(`${this.
|
|
27598
|
+
res = await fetch(`${this.baseUrlFor(path7)}${path7}`, {
|
|
27554
27599
|
method,
|
|
27555
27600
|
headers,
|
|
27556
27601
|
body,
|
|
@@ -27710,6 +27755,21 @@ var ParallClient = class _ParallClient {
|
|
|
27710
27755
|
q.limit = String(params.limit);
|
|
27711
27756
|
return this.request("GET", ENDPOINTS.ORG_MEMBER_TASKS(orgId, memberId), void 0, q);
|
|
27712
27757
|
}
|
|
27758
|
+
// Auto-paginated variant of getMemberTasks: fetches ALL pending tasks
|
|
27759
|
+
// (todo + in_progress) assigned to a member, including subtasks (the
|
|
27760
|
+
// endpoint does not filter parent_id). Powers the CLI `tasks assigned`
|
|
27761
|
+
// command so an agent answering "what's on X's plate" sees the full
|
|
27762
|
+
// backlog, not just the first page.
|
|
27763
|
+
async getMemberTasksAll(orgId, memberId) {
|
|
27764
|
+
const all = [];
|
|
27765
|
+
let cursor;
|
|
27766
|
+
do {
|
|
27767
|
+
const res = await this.getMemberTasks(orgId, memberId, { cursor, limit: 100 });
|
|
27768
|
+
all.push(...res.data);
|
|
27769
|
+
cursor = res.has_more ? res.next_cursor : void 0;
|
|
27770
|
+
} while (cursor);
|
|
27771
|
+
return all;
|
|
27772
|
+
}
|
|
27713
27773
|
// ---- Invitations ----
|
|
27714
27774
|
async createInvitation(orgId, email, role) {
|
|
27715
27775
|
return this.request("POST", ENDPOINTS.ORG_INVITATIONS(orgId), { email, role });
|
|
@@ -28299,7 +28359,7 @@ var ParallClient = class _ParallClient {
|
|
|
28299
28359
|
* Returns null when the server responds with 304 (config unchanged).
|
|
28300
28360
|
*/
|
|
28301
28361
|
async getPlatformConfig(currentVersion) {
|
|
28302
|
-
const url = `${this.
|
|
28362
|
+
const url = `${this.baseUrlFor(ENDPOINTS.PLATFORM_CONFIG)}${ENDPOINTS.PLATFORM_CONFIG}`;
|
|
28303
28363
|
const extra = {};
|
|
28304
28364
|
if (currentVersion !== void 0) {
|
|
28305
28365
|
extra["If-None-Match"] = currentVersion;
|
|
@@ -28465,6 +28525,55 @@ var ParallClient = class _ParallClient {
|
|
|
28465
28525
|
async getScheduleRun(orgId, runId) {
|
|
28466
28526
|
return this.request("GET", ENDPOINTS.SCHEDULE_RUN(orgId, runId));
|
|
28467
28527
|
}
|
|
28528
|
+
// ---- External triggers (org-scoped) ----
|
|
28529
|
+
async createExternalConnection(orgId, input) {
|
|
28530
|
+
return this.request("POST", ENDPOINTS.EXTERNAL_CONNECTIONS(orgId), input);
|
|
28531
|
+
}
|
|
28532
|
+
async listExternalConnections(orgId, filters) {
|
|
28533
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_CONNECTIONS(orgId), void 0, filters);
|
|
28534
|
+
}
|
|
28535
|
+
async getExternalConnection(orgId, connectionId) {
|
|
28536
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
|
|
28537
|
+
}
|
|
28538
|
+
async updateExternalConnection(orgId, connectionId, patch) {
|
|
28539
|
+
return this.request("PATCH", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId), patch);
|
|
28540
|
+
}
|
|
28541
|
+
async regenerateExternalConnectionIngressToken(orgId, connectionId) {
|
|
28542
|
+
return this.request("POST", ENDPOINTS.EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
|
|
28543
|
+
}
|
|
28544
|
+
async deleteExternalConnection(orgId, connectionId) {
|
|
28545
|
+
return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
|
|
28546
|
+
}
|
|
28547
|
+
async getExternalTriggerSchema(orgId, connectionId) {
|
|
28548
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
|
|
28549
|
+
}
|
|
28550
|
+
async listExternalIngressEvents(orgId, filters) {
|
|
28551
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_INGRESS_EVENTS(orgId), void 0, filters);
|
|
28552
|
+
}
|
|
28553
|
+
async getExternalIngressEvent(orgId, eventId) {
|
|
28554
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_INGRESS_EVENT(orgId, eventId));
|
|
28555
|
+
}
|
|
28556
|
+
async createExternalTrigger(orgId, input) {
|
|
28557
|
+
return this.request("POST", ENDPOINTS.EXTERNAL_TRIGGERS(orgId), input);
|
|
28558
|
+
}
|
|
28559
|
+
async listExternalTriggers(orgId, filters) {
|
|
28560
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGERS(orgId), void 0, filters);
|
|
28561
|
+
}
|
|
28562
|
+
async getExternalTrigger(orgId, triggerId) {
|
|
28563
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
|
|
28564
|
+
}
|
|
28565
|
+
async updateExternalTrigger(orgId, triggerId, patch) {
|
|
28566
|
+
return this.request("PATCH", ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId), patch);
|
|
28567
|
+
}
|
|
28568
|
+
async deleteExternalTrigger(orgId, triggerId) {
|
|
28569
|
+
return this.request("DELETE", ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
|
|
28570
|
+
}
|
|
28571
|
+
async listExternalTriggerRuns(orgId, filters) {
|
|
28572
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_RUNS(orgId), void 0, filters);
|
|
28573
|
+
}
|
|
28574
|
+
async getExternalTriggerRun(orgId, runId) {
|
|
28575
|
+
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_RUN(orgId, runId));
|
|
28576
|
+
}
|
|
28468
28577
|
// ---- Wikis (org-scoped) ----
|
|
28469
28578
|
async createWiki(orgId, data) {
|
|
28470
28579
|
return this.request("POST", ENDPOINTS.WIKIS(orgId), data);
|
|
@@ -28479,8 +28588,35 @@ var ParallClient = class _ParallClient {
|
|
|
28479
28588
|
async getWikiTree(orgId, wikiId, params) {
|
|
28480
28589
|
return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
|
|
28481
28590
|
}
|
|
28591
|
+
/**
|
|
28592
|
+
* Resolve a server-returned, host-relative media URL (a wiki `signed_url`
|
|
28593
|
+
* like `/wiki/v1/signed/files?token=...`) against this client's base origin,
|
|
28594
|
+
* so it can be dropped straight into a browser `<img>`/`<video>`/`<iframe>`
|
|
28595
|
+
* `src`.
|
|
28596
|
+
*
|
|
28597
|
+
* wiki-service returns these relative on purpose — it doesn't know its own
|
|
28598
|
+
* public origin. A relative `src` resolves against the *page* origin, which
|
|
28599
|
+
* only works when the page and wiki-service share an origin (local dev:
|
|
28600
|
+
* same-origin + Next.js `/wiki/*` proxy). In deployed envs the app
|
|
28601
|
+
* (app.parall.com) and wiki-service (api.parall.com) are different origins,
|
|
28602
|
+
* so `app.parall.com/wiki/v1/signed/files` hits the SPA's own `/wiki/[...]`
|
|
28603
|
+
* catch-all route — an `<iframe>` then recursively renders the whole app
|
|
28604
|
+
* instead of the file. Prefixing with the wiki base (the exact origin every
|
|
28605
|
+
* wiki API request already uses — `baseUrlFor` resolves `/wiki/v1` paths to
|
|
28606
|
+
* `wikiBaseUrl`) makes the URL absolute against the origin that actually
|
|
28607
|
+
* serves the bytes. An empty base (local dev, same-origin proxy) leaves it
|
|
28608
|
+
* relative, preserving the proxy path.
|
|
28609
|
+
*/
|
|
28610
|
+
absoluteMediaUrl(url) {
|
|
28611
|
+
if (/^https?:\/\//i.test(url))
|
|
28612
|
+
return url;
|
|
28613
|
+
return `${this.baseUrlFor(url)}${url}`;
|
|
28614
|
+
}
|
|
28482
28615
|
async getWikiBlob(orgId, wikiId, params) {
|
|
28483
|
-
|
|
28616
|
+
const blob = await this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params);
|
|
28617
|
+
if (blob.signed_url)
|
|
28618
|
+
blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
|
|
28619
|
+
return blob;
|
|
28484
28620
|
}
|
|
28485
28621
|
async getWikiNodeSections(orgId, wikiId, params) {
|
|
28486
28622
|
return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
|
|
@@ -28570,7 +28706,10 @@ var ParallClient = class _ParallClient {
|
|
|
28570
28706
|
* token — don't leak it.
|
|
28571
28707
|
*/
|
|
28572
28708
|
async getWikiFilePreviewUrl(orgId, wikiId, params) {
|
|
28573
|
-
|
|
28709
|
+
const res = await this.request("POST", ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId), params);
|
|
28710
|
+
if (res.url)
|
|
28711
|
+
res.url = this.absoluteMediaUrl(res.url);
|
|
28712
|
+
return res;
|
|
28574
28713
|
}
|
|
28575
28714
|
// ---- Wiki Path Scopes (AFCS ACL) ----
|
|
28576
28715
|
async getWikiPathScopes(orgId, wikiId) {
|
|
@@ -28786,6 +28925,9 @@ var ParallClient = class _ParallClient {
|
|
|
28786
28925
|
const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
|
|
28787
28926
|
return resp.data;
|
|
28788
28927
|
}
|
|
28928
|
+
/** Org-wide browser-profile discovery list. Returns the sanitized
|
|
28929
|
+
* {@link BrowserProfileListItem} shape (not the full domain model), each row
|
|
28930
|
+
* carrying a per-viewer `can_open` control hint. */
|
|
28789
28931
|
async listBrowserProfiles(orgId) {
|
|
28790
28932
|
const resp = await this.request("GET", ENDPOINTS.BROWSER_PROFILES(orgId));
|
|
28791
28933
|
return resp.data;
|
|
@@ -29439,6 +29581,9 @@ function resolveStepTarget(event) {
|
|
|
29439
29581
|
if (event.type === "schedule" || event.targetId.startsWith("sch_")) {
|
|
29440
29582
|
return { target_type: "schedule", target_id: event.targetId };
|
|
29441
29583
|
}
|
|
29584
|
+
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
29585
|
+
return { target_type: "external_trigger", target_id: event.targetId };
|
|
29586
|
+
}
|
|
29442
29587
|
if (event.type === "wiki_comment") {
|
|
29443
29588
|
return { target_type: "wiki", target_id: event.targetId || void 0 };
|
|
29444
29589
|
}
|
|
@@ -29643,6 +29788,18 @@ var ParallAgentGateway = class {
|
|
|
29643
29788
|
} catch (err) {
|
|
29644
29789
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29645
29790
|
}
|
|
29791
|
+
} else if (data.event_type === "external_trigger") {
|
|
29792
|
+
if (!data.source_id)
|
|
29793
|
+
return;
|
|
29794
|
+
try {
|
|
29795
|
+
const dispatched = await this.fetchAndHandleExternalTriggerRun(data.source_id);
|
|
29796
|
+
if (dispatched) {
|
|
29797
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29798
|
+
});
|
|
29799
|
+
}
|
|
29800
|
+
} catch (err) {
|
|
29801
|
+
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29802
|
+
}
|
|
29646
29803
|
} else if (data.event_type === "approval_decided") {
|
|
29647
29804
|
if (!data.source_id)
|
|
29648
29805
|
return;
|
|
@@ -29721,8 +29878,13 @@ var ParallAgentGateway = class {
|
|
|
29721
29878
|
target_type: target.target_type,
|
|
29722
29879
|
target_id: target.target_id,
|
|
29723
29880
|
content: {
|
|
29724
|
-
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",
|
|
29725
|
-
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 === "
|
|
29881
|
+
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",
|
|
29882
|
+
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" ? {
|
|
29883
|
+
trigger_id: event.targetId,
|
|
29884
|
+
run_id: event.messageId,
|
|
29885
|
+
connection_id: event.externalConnectionId,
|
|
29886
|
+
ingress_event_id: event.externalIngressEventId
|
|
29887
|
+
} : event.type === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
|
|
29726
29888
|
sender_id: event.senderId,
|
|
29727
29889
|
sender_name: event.senderName,
|
|
29728
29890
|
summary: event.body.substring(0, 200),
|
|
@@ -30758,6 +30920,62 @@ var ParallAgentGateway = class {
|
|
|
30758
30920
|
}
|
|
30759
30921
|
return dispatched;
|
|
30760
30922
|
}
|
|
30923
|
+
async fetchAndHandleExternalTriggerRun(runId) {
|
|
30924
|
+
let run = null;
|
|
30925
|
+
try {
|
|
30926
|
+
run = await this.opts.client.getExternalTriggerRun(this.opts.config.org_id, runId);
|
|
30927
|
+
} catch (err) {
|
|
30928
|
+
const status = err?.status;
|
|
30929
|
+
if (status === 404) {
|
|
30930
|
+
this.opts.log?.warn(`external trigger run ${runId} not accessible (404), acking stale dispatch`);
|
|
30931
|
+
return true;
|
|
30932
|
+
}
|
|
30933
|
+
this.opts.log?.warn(`external trigger run fetch failed for ${runId}, leaving pending: ${String(err)}`);
|
|
30934
|
+
return false;
|
|
30935
|
+
}
|
|
30936
|
+
if (!run)
|
|
30937
|
+
return true;
|
|
30938
|
+
return this.handleExternalTriggerRun(run);
|
|
30939
|
+
}
|
|
30940
|
+
async handleExternalTriggerRun(run) {
|
|
30941
|
+
if (this.shuttingDown)
|
|
30942
|
+
return false;
|
|
30943
|
+
const dedupeKey = `external_trigger_run:${run.id}`;
|
|
30944
|
+
if (this.dispatchedTasks.has(dedupeKey))
|
|
30945
|
+
return false;
|
|
30946
|
+
this.dispatchedTasks.add(dedupeKey);
|
|
30947
|
+
this.opts.log?.info(`external trigger fired: ${run.id} (trigger ${run.trigger_id})`);
|
|
30948
|
+
const attachedUri = typeof run.trigger_snapshot?.attached_to_uri === "string" ? run.trigger_snapshot.attached_to_uri : void 0;
|
|
30949
|
+
const event = {
|
|
30950
|
+
type: "external_trigger",
|
|
30951
|
+
targetId: run.trigger_id,
|
|
30952
|
+
targetName: run.trigger_name || void 0,
|
|
30953
|
+
targetType: "external_trigger",
|
|
30954
|
+
senderId: "system",
|
|
30955
|
+
senderName: "external",
|
|
30956
|
+
messageId: run.id,
|
|
30957
|
+
body: run.agent_input_body ?? "",
|
|
30958
|
+
externalConnectionId: run.connection_id,
|
|
30959
|
+
externalConnectionSourceType: run.connection_source_type || void 0,
|
|
30960
|
+
externalConnectionDisplayName: run.connection_display_name || void 0,
|
|
30961
|
+
externalIngressEventId: run.ingress_event_id,
|
|
30962
|
+
externalIngressEventType: run.ingress_event_type || void 0,
|
|
30963
|
+
attachedUri,
|
|
30964
|
+
ackSourceType: "external_trigger_run",
|
|
30965
|
+
ackSourceId: run.id
|
|
30966
|
+
};
|
|
30967
|
+
let dispatched;
|
|
30968
|
+
try {
|
|
30969
|
+
dispatched = await this.handleInboundEvent(event);
|
|
30970
|
+
} catch (err) {
|
|
30971
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
30972
|
+
throw err;
|
|
30973
|
+
}
|
|
30974
|
+
if (!dispatched) {
|
|
30975
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
30976
|
+
}
|
|
30977
|
+
return dispatched;
|
|
30978
|
+
}
|
|
30761
30979
|
async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
|
|
30762
30980
|
let approval = null;
|
|
30763
30981
|
try {
|
|
@@ -30868,6 +31086,8 @@ var ParallAgentGateway = class {
|
|
|
30868
31086
|
dispatched = await this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason);
|
|
30869
31087
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
30870
31088
|
dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
|
|
31089
|
+
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31090
|
+
dispatched = await this.fetchAndHandleExternalTriggerRun(item.source_id);
|
|
30871
31091
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
30872
31092
|
dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
|
|
30873
31093
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/parall",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "OpenClaw channel plugin for Parall IM",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"openclaw.plugin.json"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@parall/
|
|
20
|
-
"@parall/
|
|
19
|
+
"@parall/sdk": "1.36.0",
|
|
20
|
+
"@parall/agent-core": "1.36.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.0.0",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: parall-external-triggers
|
|
3
|
+
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."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Parall External Triggers
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
## Prerequisite
|
|
13
|
+
|
|
14
|
+
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.
|
|
15
|
+
|
|
16
|
+
## Creating a trigger
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Create a connection. The ingress token is shown once; prefer writing it
|
|
20
|
+
# to a local file so it does not land in shell history or logs.
|
|
21
|
+
parall external-triggers create-connection --name "GitHub CI" --token-file ./github-ci-webhook-token.txt
|
|
22
|
+
|
|
23
|
+
# 2. Create a trigger that targets one or more agents.
|
|
24
|
+
parall external-triggers create \
|
|
25
|
+
--connection prll://xcn_xxx \
|
|
26
|
+
--name "Failed checks" \
|
|
27
|
+
--target-ids prll://usr_agent_xxx \
|
|
28
|
+
--filter "body.json.check_run.conclusion == 'failure'" \
|
|
29
|
+
--template-file ./github-check-failed.md \
|
|
30
|
+
--attached-to-uri prll://tsk_xxx
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`--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.
|
|
34
|
+
|
|
35
|
+
`--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.
|
|
36
|
+
|
|
37
|
+
Example template:
|
|
38
|
+
|
|
39
|
+
```liquid
|
|
40
|
+
GitHub check failed.
|
|
41
|
+
|
|
42
|
+
Event: {{ request.headers.x_github_event | default: "unknown" }}
|
|
43
|
+
Repo: {{ body.json.repository.full_name }}
|
|
44
|
+
PR: {{ body.json.pull_request.number }} {{ body.json.pull_request.title }}
|
|
45
|
+
Check: {{ body.json.check_run.name }}
|
|
46
|
+
Conclusion: {{ body.json.check_run.conclusion }}
|
|
47
|
+
|
|
48
|
+
Run: {{ body.json.check_run.html_url }}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
## Inspecting and lifecycle
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
parall external-triggers connections
|
|
57
|
+
parall external-triggers connection prll://xcn_xxx
|
|
58
|
+
parall external-triggers schema prll://xcn_xxx
|
|
59
|
+
|
|
60
|
+
parall external-triggers list
|
|
61
|
+
parall external-triggers list --connection prll://xcn_xxx
|
|
62
|
+
parall external-triggers get prll://xtr_xxx
|
|
63
|
+
parall external-triggers update prll://xtr_xxx --filter "event.type == 'check_run'"
|
|
64
|
+
parall external-triggers pause prll://xtr_xxx
|
|
65
|
+
parall external-triggers resume prll://xtr_xxx
|
|
66
|
+
parall external-triggers delete prll://xtr_xxx
|
|
67
|
+
|
|
68
|
+
parall external-triggers runs prll://xtr_xxx
|
|
69
|
+
parall external-triggers run prll://xrn_xxx
|
|
70
|
+
parall external-triggers events --connection prll://xcn_xxx
|
|
71
|
+
parall external-triggers event prll://xin_xxx
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Responding to external trigger dispatches
|
|
75
|
+
|
|
76
|
+
When you receive `[Event: external.trigger]`, Parall has already matched a trigger and rendered its template. The prompt includes headers such as:
|
|
77
|
+
|
|
78
|
+
- `[Trigger: prll://xtr_xxx]`
|
|
79
|
+
- `[Run: prll://xrn_xxx]`
|
|
80
|
+
- `[Connection: ... (prll://xcn_xxx)]`
|
|
81
|
+
- `[Ingress: prll://xin_xxx]`
|
|
82
|
+
- Optional `[Attached: prll://...]`
|
|
83
|
+
|
|
84
|
+
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:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
parall external-triggers run prll://xrn_xxx
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr, for example `Created: prll://xtr_xxx`.
|
|
@@ -161,9 +161,15 @@ Every entity is addressable with a `prll://` URI. Common prefixes you'll see in
|
|
|
161
161
|
| `prll://prj_` | Project | parall-tasks |
|
|
162
162
|
| `prll://sch_` | Schedule (time trigger) | parall-schedules |
|
|
163
163
|
| `prll://srn_` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |
|
|
164
|
+
| `prll://xcn_` | External Trigger Connection (incoming endpoint) | parall-external-triggers |
|
|
165
|
+
| `prll://xin_` | External Trigger Event (single incoming event audit record) | parall-external-triggers |
|
|
166
|
+
| `prll://xtr_` | External Trigger (incoming trigger configuration) | parall-external-triggers |
|
|
167
|
+
| `prll://xrn_` | External Trigger run (single matched dispatch audit record) | parall-external-triggers |
|
|
164
168
|
| `prll://wik_` | Wiki | parall-wiki |
|
|
165
169
|
| `prll://att_` | Attachment | parall-platform (files) |
|
|
166
170
|
|
|
167
171
|
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).
|
|
168
172
|
|
|
173
|
+
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).
|
|
174
|
+
|
|
169
175
|
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 — read both.
|
|
@@ -7,16 +7,39 @@ description: "Parall task operations: create, update, comment on, and query task
|
|
|
7
7
|
|
|
8
8
|
Manage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.
|
|
9
9
|
|
|
10
|
+
## Finding What's on Someone's Plate (incl. subtasks)
|
|
11
|
+
|
|
12
|
+
To answer "what do I still have to do", "what's <person> working on", or any
|
|
13
|
+
"open work assigned to X" question, use `tasks assigned`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Pending tasks (todo + in_progress) assigned to a member — INCLUDES subtasks.
|
|
17
|
+
parall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)
|
|
18
|
+
parall tasks assigned # yourself (defaults to the authenticated user)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This is the authoritative "open work for a person" query. It returns every
|
|
22
|
+
pending task assigned to that member **including subtasks** — even when the
|
|
23
|
+
subtask's parent task belongs to someone else. Decomposed work usually lives in
|
|
24
|
+
subtasks, so do NOT answer this kind of question from `tasks list` alone:
|
|
25
|
+
that is org-wide, page-capped, and not scoped to a person, so a person's
|
|
26
|
+
subtasks are easily missed.
|
|
27
|
+
|
|
28
|
+
Resolve a person's `prll://usr_` id from the message context, the members
|
|
29
|
+
list, or ref search; your own id comes from `parall whoami`.
|
|
30
|
+
|
|
10
31
|
## Task Commands
|
|
11
32
|
|
|
12
33
|
```bash
|
|
13
|
-
# List tasks (
|
|
34
|
+
# List tasks (org-wide; filter by status, assignee, or parent)
|
|
14
35
|
parall tasks list
|
|
15
36
|
parall tasks list --status todo
|
|
16
37
|
parall tasks list --status in_progress
|
|
38
|
+
parall tasks list --assignee-id prll://usr_xxx # first page only (default 20) — for a person's FULL backlog use 'tasks assigned' above
|
|
39
|
+
parall tasks subtasks prll://tsk_xxx # children of a single parent task
|
|
17
40
|
|
|
18
|
-
# Create a task
|
|
19
|
-
parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--project-id prll://prj_xxx]
|
|
41
|
+
# Create a task (add --parent-id to make it a SUBTASK of another task)
|
|
42
|
+
parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
|
|
20
43
|
|
|
21
44
|
# Update task status
|
|
22
45
|
parall tasks update prll://tsk_xxx --status in_progress
|
|
@@ -26,6 +49,12 @@ parall tasks update prll://tsk_xxx --status done
|
|
|
26
49
|
parall tasks comments add prll://tsk_xxx --body "Progress update..."
|
|
27
50
|
```
|
|
28
51
|
|
|
52
|
+
Subtasks are just tasks with a parent: create one with `tasks create --parent-id`,
|
|
53
|
+
re-parent with `tasks update --parent-id`, list a parent's children with
|
|
54
|
+
`tasks subtasks`. `tasks list` without `--parent-id` already returns both
|
|
55
|
+
top-level tasks and subtasks; per-person open work is best fetched with
|
|
56
|
+
`tasks assigned` (above).
|
|
57
|
+
|
|
29
58
|
## Project Commands
|
|
30
59
|
|
|
31
60
|
```bash
|
|
@@ -21,8 +21,11 @@ Key facts the commands won't tell you:
|
|
|
21
21
|
the absolute workspace path (`synced → /path/to/<slug>` / `Mount: ...`).
|
|
22
22
|
Always address wiki files by that absolute path — your shell cwd is usually
|
|
23
23
|
NOT inside the workspace.
|
|
24
|
-
- **Text
|
|
25
|
-
|
|
24
|
+
- **Text and binary are two paths.** The workspace + changeset flow is for
|
|
25
|
+
text (markdown, code, config). Binary assets (images, PDFs, archives) are
|
|
26
|
+
diff-less — they don't go in the workspace; use `parall wiki file` (see
|
|
27
|
+
**Binary files** below). Dropping a binary into the workspace just gets it
|
|
28
|
+
rejected on propose.
|
|
26
29
|
- **`cat`, `search`, `query`, `outline`, and `section` read your local
|
|
27
30
|
workspace copy when it exists** — including your own unproposed edits. Add
|
|
28
31
|
`--remote` to `cat` to read the server version instead.
|
|
@@ -103,6 +106,50 @@ Re-propose REPLACES the changeset's previous contents with your current
|
|
|
103
106
|
workspace diff — to withdraw a file from the proposal, revert it locally
|
|
104
107
|
(restore the synced content) and re-propose; it drops out of the changeset.
|
|
105
108
|
|
|
109
|
+
## Binary files
|
|
110
|
+
|
|
111
|
+
Images, PDFs, archives — anything that can't be diffed — bypass the workspace
|
|
112
|
+
and changeset-text flow entirely. They never belong in the synced workspace
|
|
113
|
+
(propose rejects them); use `parall wiki file` instead. `cat` is text-only —
|
|
114
|
+
to read a binary's real bytes use `file get` (a plain `sync` only leaves a
|
|
115
|
+
few-line Git-LFS pointer on disk, since the runtime has no git-lfs).
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Maintainer: direct-commit a binary to the default branch (no review)
|
|
119
|
+
parall wiki file upload ./diagram.png docs/assets/diagram.png
|
|
120
|
+
|
|
121
|
+
# Read a binary's real bytes (LFS pointers resolved server-side) to a file.
|
|
122
|
+
# Always use --output for binaries — without it the bytes stream to stdout and
|
|
123
|
+
# would flood your context.
|
|
124
|
+
parall wiki file get docs/assets/diagram.png --output ./diagram.png
|
|
125
|
+
parall wiki file get docs/assets/diagram.png --ref <commit-or-branch> --output ./diagram.png # a specific revision
|
|
126
|
+
|
|
127
|
+
# Remove a binary from the default branch (git history still has it)
|
|
128
|
+
parall wiki file delete docs/assets/diagram.png
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`upload` needs **maintain**; it routes by size automatically (≤1 MiB inline,
|
|
132
|
+
larger → LFS). A text file sent to `upload` is rejected — that's the changeset
|
|
133
|
+
flow's job.
|
|
134
|
+
|
|
135
|
+
### Reader: propose markdown that embeds an image
|
|
136
|
+
|
|
137
|
+
Without maintain you can still propose a doc with images — upload the binary
|
|
138
|
+
into your **changeset's** branch (read + author), not the default branch:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
parall wiki sync
|
|
142
|
+
# edit a .md in the workspace to add 
|
|
143
|
+
parall wiki changeset create <wiki> --title "Add foo diagram" # creates the changeset (note its id)
|
|
144
|
+
parall wiki file upload ./foo.png assets/foo.png <wiki> --changeset <changesetId>
|
|
145
|
+
# leave it for a maintainer to merge — both the markdown and the image squash in together
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Do the markdown `changeset create` first so the changeset exists, then attach
|
|
149
|
+
the image to it. Don't re-propose (`--update`) after attaching a binary —
|
|
150
|
+
re-propose replays only the text workspace and the server rejects dropping the
|
|
151
|
+
attached binary (422 `REPLACE_HAS_BINARY`).
|
|
152
|
+
|
|
106
153
|
## Discovery & history
|
|
107
154
|
|
|
108
155
|
```bash
|