@parall/agent-core 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.
@@ -1 +1 @@
1
- {"version":3,"file":"event-format.d.ts","sourceRoot":"","sources":["../src/event-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA4FzD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEtE;AAyBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAK/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAkBnE"}
1
+ {"version":3,"file":"event-format.d.ts","sourceRoot":"","sources":["../src/event-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAgHzD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEtE;AA6BD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAK/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAkBnE"}
@@ -93,6 +93,28 @@ export function buildEventBody(event) {
93
93
  lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
94
94
  lines.push('', event.body);
95
95
  }
96
+ else if (event.type === 'external_trigger') {
97
+ lines.push(`[Event: external.trigger]`);
98
+ lines.push(`[Trigger: prll://${event.targetId}]`);
99
+ lines.push(`[Run: prll://${event.messageId}]`);
100
+ if (event.externalConnectionId) {
101
+ const label = event.externalConnectionDisplayName
102
+ ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})`
103
+ : `prll://${event.externalConnectionId}`;
104
+ lines.push(`[Connection: ${label}]`);
105
+ }
106
+ if (event.externalIngressEventId)
107
+ lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
108
+ if (event.attachedUri)
109
+ lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
110
+ if (event.externalConnectionSourceType) {
111
+ lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
112
+ }
113
+ if (event.externalIngressEventType) {
114
+ lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
115
+ }
116
+ lines.push('', event.body);
117
+ }
96
118
  else {
97
119
  lines.push(`[Event: task.assigned]`);
98
120
  const taskLabel = event.targetName
@@ -123,6 +145,9 @@ function buildSendMessageHint(event) {
123
145
  if (event.targetId.startsWith('sch_')) {
124
146
  return `\n<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
125
147
  }
148
+ if (event.type === 'external_trigger' || event.targetId.startsWith('xtr_')) {
149
+ return `\n<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>`;
150
+ }
126
151
  return '';
127
152
  }
128
153
  export function buildForkScopePrefix(event) {
@@ -104,6 +104,8 @@ export declare class ParallAgentGateway {
104
104
  */
105
105
  private fetchAndHandleScheduleFire;
106
106
  private handleScheduleFire;
107
+ private fetchAndHandleExternalTriggerRun;
108
+ private handleExternalTriggerRun;
107
109
  private fetchAndHandleApprovalDecided;
108
110
  private catchUpFromDispatch;
109
111
  private buildOfflineSummary;
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
@@ -50,6 +50,9 @@ function resolveStepTarget(event) {
50
50
  if (event.type === 'schedule' || event.targetId.startsWith('sch_')) {
51
51
  return { target_type: 'schedule', target_id: event.targetId };
52
52
  }
53
+ if (event.type === 'external_trigger' || event.targetId.startsWith('xtr_')) {
54
+ return { target_type: 'external_trigger', target_id: event.targetId };
55
+ }
53
56
  if (event.type === 'wiki_comment') {
54
57
  // target_id is the full wiki target_uri (scheme-stripped routing key). The
55
58
  // server stores target_type freely and only publishes step WS events /
@@ -286,6 +289,19 @@ export class ParallAgentGateway {
286
289
  this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
287
290
  }
288
291
  }
292
+ else if (data.event_type === 'external_trigger') {
293
+ if (!data.source_id)
294
+ return;
295
+ try {
296
+ const dispatched = await this.fetchAndHandleExternalTriggerRun(data.source_id);
297
+ if (dispatched) {
298
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => { });
299
+ }
300
+ }
301
+ catch (err) {
302
+ this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
303
+ }
304
+ }
289
305
  else if (data.event_type === 'approval_decided') {
290
306
  if (!data.source_id)
291
307
  return;
@@ -382,9 +398,11 @@ export class ParallAgentGateway {
382
398
  ? 'wiki_comment'
383
399
  : event.type === 'schedule'
384
400
  ? 'schedule_fire'
385
- : event.type === 'approval'
386
- ? 'approval_decided'
387
- : 'mention',
401
+ : event.type === 'external_trigger'
402
+ ? 'external_trigger'
403
+ : event.type === 'approval'
404
+ ? 'approval_decided'
405
+ : 'mention',
388
406
  trigger_ref: event.type === 'task'
389
407
  ? { task_id: event.targetId }
390
408
  : event.type === 'task_comment'
@@ -393,9 +411,16 @@ export class ParallAgentGateway {
393
411
  ? { comment_id: event.messageId, target_uri: event.replyTargetUri }
394
412
  : event.type === 'schedule'
395
413
  ? { schedule_id: event.targetId, run_id: event.messageId }
396
- : event.type === 'approval'
397
- ? { approval_id: event.messageId }
398
- : { message_id: event.messageId },
414
+ : event.type === 'external_trigger'
415
+ ? {
416
+ trigger_id: event.targetId,
417
+ run_id: event.messageId,
418
+ connection_id: event.externalConnectionId,
419
+ ingress_event_id: event.externalIngressEventId,
420
+ }
421
+ : event.type === 'approval'
422
+ ? { approval_id: event.messageId }
423
+ : { message_id: event.messageId },
399
424
  sender_id: event.senderId,
400
425
  sender_name: event.senderName,
401
426
  summary: event.body.substring(0, 200),
@@ -1569,6 +1594,66 @@ export class ParallAgentGateway {
1569
1594
  }
1570
1595
  return dispatched;
1571
1596
  }
1597
+ async fetchAndHandleExternalTriggerRun(runId) {
1598
+ let run = null;
1599
+ try {
1600
+ run = await this.opts.client.getExternalTriggerRun(this.opts.config.org_id, runId);
1601
+ }
1602
+ catch (err) {
1603
+ const status = err?.status;
1604
+ if (status === 404) {
1605
+ this.opts.log?.warn(`external trigger run ${runId} not accessible (404), acking stale dispatch`);
1606
+ return true;
1607
+ }
1608
+ this.opts.log?.warn(`external trigger run fetch failed for ${runId}, leaving pending: ${String(err)}`);
1609
+ return false;
1610
+ }
1611
+ if (!run)
1612
+ return true;
1613
+ return this.handleExternalTriggerRun(run);
1614
+ }
1615
+ async handleExternalTriggerRun(run) {
1616
+ if (this.shuttingDown)
1617
+ return false;
1618
+ const dedupeKey = `external_trigger_run:${run.id}`;
1619
+ if (this.dispatchedTasks.has(dedupeKey))
1620
+ return false;
1621
+ this.dispatchedTasks.add(dedupeKey);
1622
+ this.opts.log?.info(`external trigger fired: ${run.id} (trigger ${run.trigger_id})`);
1623
+ const attachedUri = typeof run.trigger_snapshot?.attached_to_uri === 'string'
1624
+ ? run.trigger_snapshot.attached_to_uri
1625
+ : undefined;
1626
+ const event = {
1627
+ type: 'external_trigger',
1628
+ targetId: run.trigger_id,
1629
+ targetName: run.trigger_name || undefined,
1630
+ targetType: 'external_trigger',
1631
+ senderId: 'system',
1632
+ senderName: 'external',
1633
+ messageId: run.id,
1634
+ body: run.agent_input_body ?? '',
1635
+ externalConnectionId: run.connection_id,
1636
+ externalConnectionSourceType: run.connection_source_type || undefined,
1637
+ externalConnectionDisplayName: run.connection_display_name || undefined,
1638
+ externalIngressEventId: run.ingress_event_id,
1639
+ externalIngressEventType: run.ingress_event_type || undefined,
1640
+ attachedUri,
1641
+ ackSourceType: 'external_trigger_run',
1642
+ ackSourceId: run.id,
1643
+ };
1644
+ let dispatched;
1645
+ try {
1646
+ dispatched = await this.handleInboundEvent(event);
1647
+ }
1648
+ catch (err) {
1649
+ this.dispatchedTasks.delete(dedupeKey);
1650
+ throw err;
1651
+ }
1652
+ if (!dispatched) {
1653
+ this.dispatchedTasks.delete(dedupeKey);
1654
+ }
1655
+ return dispatched;
1656
+ }
1572
1657
  async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
1573
1658
  let approval = null;
1574
1659
  try {
@@ -1692,6 +1777,9 @@ export class ParallAgentGateway {
1692
1777
  else if (item.event_type === 'schedule.fire' && item.source_id) {
1693
1778
  dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
1694
1779
  }
1780
+ else if (item.event_type === 'external_trigger' && item.source_id) {
1781
+ dispatched = await this.fetchAndHandleExternalTriggerRun(item.source_id);
1782
+ }
1695
1783
  else if (item.event_type === 'approval_decided' && item.source_id) {
1696
1784
  dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
1697
1785
  }
@@ -2,6 +2,7 @@ export { PARALL_PLATFORM_SKILL } from './parall-platform.js';
2
2
  export { PARALL_TASKS_SKILL } from './parall-tasks.js';
3
3
  export { PARALL_WIKI_SKILL } from './parall-wiki.js';
4
4
  export { PARALL_SCHEDULES_SKILL } from './parall-schedules.js';
5
+ export { PARALL_EXTERNAL_TRIGGERS_SKILL } from './parall-external-triggers.js';
5
6
  export { PARALL_CLIPS_SKILL } from './parall-clips.js';
6
7
  export type SkillMeta = {
7
8
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skills/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAQvD,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,eAAO,MAAM,MAAM,EAAE,SAAS,EA+B7B,CAAC;AAEF,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAKvD;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAIjE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skills/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AASvD,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,eAAO,MAAM,MAAM,EAAE,SAAS,EAqC7B,CAAC;AAEF,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAKvD;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAIjE"}
@@ -4,11 +4,13 @@ export { PARALL_PLATFORM_SKILL } from './parall-platform.js';
4
4
  export { PARALL_TASKS_SKILL } from './parall-tasks.js';
5
5
  export { PARALL_WIKI_SKILL } from './parall-wiki.js';
6
6
  export { PARALL_SCHEDULES_SKILL } from './parall-schedules.js';
7
+ export { PARALL_EXTERNAL_TRIGGERS_SKILL } from './parall-external-triggers.js';
7
8
  export { PARALL_CLIPS_SKILL } from './parall-clips.js';
8
9
  import { PARALL_PLATFORM_SKILL } from './parall-platform.js';
9
10
  import { PARALL_TASKS_SKILL } from './parall-tasks.js';
10
11
  import { PARALL_WIKI_SKILL } from './parall-wiki.js';
11
12
  import { PARALL_SCHEDULES_SKILL } from './parall-schedules.js';
13
+ import { PARALL_EXTERNAL_TRIGGERS_SKILL } from './parall-external-triggers.js';
12
14
  import { PARALL_CLIPS_SKILL } from './parall-clips.js';
13
15
  export const SKILLS = [
14
16
  {
@@ -31,6 +33,11 @@ export const SKILLS = [
31
33
  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.',
32
34
  content: PARALL_SCHEDULES_SKILL,
33
35
  },
36
+ {
37
+ name: 'parall-external-triggers',
38
+ 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.',
39
+ content: PARALL_EXTERNAL_TRIGGERS_SKILL,
40
+ },
34
41
  {
35
42
  name: 'parall-clips',
36
43
  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.',
@@ -0,0 +1,2 @@
1
+ export declare const PARALL_EXTERNAL_TRIGGERS_SKILL = "# Parall External Triggers\n\nAn **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.\n\nUse 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.\n\n## Prerequisite\n\nExternal 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.\n\n## Creating a trigger\n\n```bash\n# 1. Create a connection. The ingress token is shown once; prefer writing it\n# to a local file so it does not land in shell history or logs.\nparall external-triggers create-connection --name \"GitHub CI\" --token-file ./github-ci-webhook-token.txt\n\n# 2. Create a trigger that targets one or more agents.\nparall external-triggers create \\\n --connection prll://xcn_xxx \\\n --name \"Failed checks\" \\\n --target-ids prll://usr_agent_xxx \\\n --filter \"body.json.check_run.conclusion == 'failure'\" \\\n --template-file ./github-check-failed.md \\\n --attached-to-uri prll://tsk_xxx\n```\n\n`--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.\n\n`--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.\n\nExample template:\n\n```liquid\nGitHub check failed.\n\nEvent: {{ request.headers.x_github_event | default: \"unknown\" }}\nRepo: {{ body.json.repository.full_name }}\nPR: {{ body.json.pull_request.number }} {{ body.json.pull_request.title }}\nCheck: {{ body.json.check_run.name }}\nConclusion: {{ body.json.check_run.conclusion }}\n\nRun: {{ body.json.check_run.html_url }}\n```\n\nDo 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.\n\n## Inspecting and lifecycle\n\n```bash\nparall external-triggers connections\nparall external-triggers connection prll://xcn_xxx\nparall external-triggers schema prll://xcn_xxx\n\nparall external-triggers list\nparall external-triggers list --connection prll://xcn_xxx\nparall external-triggers get prll://xtr_xxx\nparall external-triggers update prll://xtr_xxx --filter \"event.type == 'check_run'\"\nparall external-triggers pause prll://xtr_xxx\nparall external-triggers resume prll://xtr_xxx\nparall external-triggers delete prll://xtr_xxx\n\nparall external-triggers runs prll://xtr_xxx\nparall external-triggers run prll://xrn_xxx\nparall external-triggers events --connection prll://xcn_xxx\nparall external-triggers event prll://xin_xxx\n```\n\n## Responding to external trigger dispatches\n\nWhen you receive `[Event: external.trigger]`, Parall has already matched a trigger and rendered its template. The prompt includes headers such as:\n\n- `[Trigger: prll://xtr_xxx]`\n- `[Run: prll://xrn_xxx]`\n- `[Connection: ... (prll://xcn_xxx)]`\n- `[Ingress: prll://xin_xxx]`\n- Optional `[Attached: prll://...]`\n\nThe 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:\n\n```bash\nparall external-triggers run prll://xrn_xxx\n```\n\nAct 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.\n\nCLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr, for example `Created: prll://xtr_xxx`.\n";
2
+ //# sourceMappingURL=parall-external-triggers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parall-external-triggers.d.ts","sourceRoot":"","sources":["../../src/skills/parall-external-triggers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,8BAA8B,g5IAuF1C,CAAC"}
@@ -0,0 +1,88 @@
1
+ export const PARALL_EXTERNAL_TRIGGERS_SKILL = `# Parall External Triggers
2
+
3
+ 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.
4
+
5
+ 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.
6
+
7
+ ## Prerequisite
8
+
9
+ 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.
10
+
11
+ ## Creating a trigger
12
+
13
+ \`\`\`bash
14
+ # 1. Create a connection. The ingress token is shown once; prefer writing it
15
+ # to a local file so it does not land in shell history or logs.
16
+ parall external-triggers create-connection --name "GitHub CI" --token-file ./github-ci-webhook-token.txt
17
+
18
+ # 2. Create a trigger that targets one or more agents.
19
+ parall external-triggers create \\
20
+ --connection prll://xcn_xxx \\
21
+ --name "Failed checks" \\
22
+ --target-ids prll://usr_agent_xxx \\
23
+ --filter "body.json.check_run.conclusion == 'failure'" \\
24
+ --template-file ./github-check-failed.md \\
25
+ --attached-to-uri prll://tsk_xxx
26
+ \`\`\`
27
+
28
+ \`--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.
29
+
30
+ \`--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.
31
+
32
+ Example template:
33
+
34
+ \`\`\`liquid
35
+ GitHub check failed.
36
+
37
+ Event: {{ request.headers.x_github_event | default: "unknown" }}
38
+ Repo: {{ body.json.repository.full_name }}
39
+ PR: {{ body.json.pull_request.number }} {{ body.json.pull_request.title }}
40
+ Check: {{ body.json.check_run.name }}
41
+ Conclusion: {{ body.json.check_run.conclusion }}
42
+
43
+ Run: {{ body.json.check_run.html_url }}
44
+ \`\`\`
45
+
46
+ 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.
47
+
48
+ ## Inspecting and lifecycle
49
+
50
+ \`\`\`bash
51
+ parall external-triggers connections
52
+ parall external-triggers connection prll://xcn_xxx
53
+ parall external-triggers schema prll://xcn_xxx
54
+
55
+ parall external-triggers list
56
+ parall external-triggers list --connection prll://xcn_xxx
57
+ parall external-triggers get prll://xtr_xxx
58
+ parall external-triggers update prll://xtr_xxx --filter "event.type == 'check_run'"
59
+ parall external-triggers pause prll://xtr_xxx
60
+ parall external-triggers resume prll://xtr_xxx
61
+ parall external-triggers delete prll://xtr_xxx
62
+
63
+ parall external-triggers runs prll://xtr_xxx
64
+ parall external-triggers run prll://xrn_xxx
65
+ parall external-triggers events --connection prll://xcn_xxx
66
+ parall external-triggers event prll://xin_xxx
67
+ \`\`\`
68
+
69
+ ## Responding to external trigger dispatches
70
+
71
+ When you receive \`[Event: external.trigger]\`, Parall has already matched a trigger and rendered its template. The prompt includes headers such as:
72
+
73
+ - \`[Trigger: prll://xtr_xxx]\`
74
+ - \`[Run: prll://xrn_xxx]\`
75
+ - \`[Connection: ... (prll://xcn_xxx)]\`
76
+ - \`[Ingress: prll://xin_xxx]\`
77
+ - Optional \`[Attached: prll://...]\`
78
+
79
+ 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:
80
+
81
+ \`\`\`bash
82
+ parall external-triggers run prll://xrn_xxx
83
+ \`\`\`
84
+
85
+ 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.
86
+
87
+ CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr, for example \`Created: prll://xtr_xxx\`.
88
+ `;
@@ -1,2 +1,2 @@
1
- export declare const PARALL_PLATFORM_SKILL = "# Parall Platform\n\nQuery organization data via the Parall CLI. Auth is pre-configured.\n\n## Identity\n\n```bash\nparall whoami\n```\n\n## Members & Agents\n\n```bash\nparall members list # All org members (humans + agents)\nparall agents list # Agents only\nparall users get prll://usr_xxx # Get user details by ID\n```\n\nCreate a hosted agent when the user asks for a Parall-managed runtime. Hosted\nprovisioning is asynchronous: creation means the agent identity, API key, and\nmachine record were accepted, not that the runtime is online yet. Use `--wait`\nto wait until the machine reaches `running`, and use `--wait-online` when the\ntask requires the child agent to be connected before you report completion.\nFor hosted agents, use `--discard-api-key`; the server injects the one-time key\ninto the hosted runtime, so the parent agent must not print or persist it.\n\nCreate a self-hosted agent only when the runtime will be connected outside\nParall-managed compute. In that case, write the one-time `api_key` to\n`--api-key-file` so it is not captured in tool-result logs. Treat `api_key` as a\nsecret: do not print, read aloud, post it in shared chats, or echo the file\ncontents. Include the `user.id` in normal responses, and pass the key file only\nthrough an explicit secure runtime handoff when connection is required. Never\nuse `--show-api-key` from an agent runtime. Agent callers cannot set provider\noverrides until the dedicated fine-grained permission flow lands.\n\n```bash\n# Hosted runtime (Parall-managed compute)\nparall agents create \\\n --name \"Research Agent\" \\\n --runtime-type codex \\\n --machine-type cloud \\\n --machine-label standard \\\n --discard-api-key \\\n --wait \\\n --wait-online\n\n# Self-hosted runtime\nparall agents create --name \"Research Agent\" --runtime-type codex --api-key-file /tmp/research-agent.api-key\n```\n\nInspect hosted provisioning directly when a create command returns before the\nruntime is online, or when you need logs for a failed machine. If `agents create`\nexits non-zero after creating a hosted agent, read the printed `user.id` and\n`machine.id`, then use these commands to decide whether to wait, inspect logs,\nor report the failed machine for retry.\n\n```bash\nparall machines status prll://mch_xxx\nparall machines logs prll://mch_xxx --lines 100\n```\n\n## Chats & Messages\n\n```bash\nparall chats list # List all chats\nparall messages list prll://cht_xxx # Read chat message history\n```\n\n## Sending Messages\n\nEach `[Event: message.new]` includes `[Chat: ... (prll://cht_xxx)]` \u2014 use that chat URI to reply.\n\n```bash\n# Reply to a chat (use the chat URI from the event)\nparall messages send prll://cht_xxx --text \"Your reply\"\n\n# Direct message by user URI or display name\nparall dm prll://usr_xxx --text \"Hello\"\nparall dm \"Alice\" --text \"Hello\"\n\n# Thread reply\nparall messages send prll://cht_xxx --text \"Reply\" --thread-root-id 01JWC...\n\n# FYI message (no response expected \u2014 the recipient sees `[Hint: no_reply]`)\nparall messages send prll://cht_xxx --text \"FYI: done\" --no-reply\n\n# Silence this turn entirely \u2014 no chat message produced. Use when you receive\n# `[Hint: no_reply]` or otherwise decide the turn needs no visible reply.\n# Run BEFORE any `messages send` / `dm`; those still deliver real messages.\nparall no-reply --reason \"ack only, nothing to add\"\n```\n\n## Files & Attachments\n\nAttachments appear in events as `[Attachment: prll://att_xxx | mime | size | name]`.\n\n```bash\n# Download an attachment\nparall files download att_xxx --output /tmp/file.png\n\n# Upload a file (returns attachment_id)\nparall files upload /tmp/report.pdf\n\n# Send a message with a file\nparall messages send prll://cht_xxx --file /tmp/output.png --text \"Done\"\n\n# Send an existing attachment to another chat\nparall messages send prll://cht_xxx --attachment att_xxx --text \"See attached\"\n\n# DM with a file\nparall dm \"Alice\" --file /tmp/report.pdf --text \"Report attached\"\n```\n\n`--file` and `--attachment` are mutually exclusive. `--text` can be combined with either.\n\n## Approvals\n\nWhen a CLI command returns a `PERMISSION_DENIED` error, the output includes the denied `action` and `resource_uri`. Whether that action can be approved is decided by the server (there is no fixed allowlist):\n- If it IS approvable, a `Request approval:` line with a `parall approvals request` command follows \u2014 fill in the placeholders it shows (`--chat`, `--title`, `--reason`) and run it.\n- If it is NOT approvable, the output says so \u2014 ask a human with permission instead of requesting approval.\n\nA different `INVALID_TARGET` error means you addressed the wrong kind of thing (e.g. a `usr_` id where a chat is expected). Follow the message (e.g. use `parall dm` to message a user) \u2014 do not request approval for it.\n\n```bash\n# Request approval (use action and resource_uri from the error)\nparall approvals request --action chat.archive --resource prll://cht_xxx --chat prll://cht_yyy --title \"Archive old channel\" --reason \"No activity in 6 months\"\n\n# Check a specific approval's status\nparall approvals get prll://apr_xxx\n\n# Wait for a decision (blocks until approved/rejected/timeout)\nparall approvals wait prll://apr_xxx --timeout 300\n\n# List all your pending approvals\nparall approvals list\n\n# List available approvable actions\nparall approvals actions\n\n# Cancel a pending request you made\nparall approvals cancel prll://apr_xxx\n```\n\nOnly request approval after receiving an actual `PERMISSION_DENIED` error \u2014 never preemptively. The `--chat` flag specifies where the approval card appears; use the chat where the conversation is happening.\n\n## Reference URIs\n\nEvery entity is addressable with a `prll://` URI. Common prefixes you'll see in events, messages, and schedule descriptions:\n\n| Prefix | Entity | Skill |\n|--------|--------|-------|\n| `prll://usr_` | User (human or agent) | parall-platform |\n| `prll://cht_` | Chat | parall-platform |\n| `prll://msg_` | Message | parall-platform |\n| `prll://tsk_` | Task | parall-tasks |\n| `prll://prj_` | Project | parall-tasks |\n| `prll://sch_` | Schedule (time trigger) | parall-schedules |\n| `prll://srn_` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |\n| `prll://wik_` | Wiki | parall-wiki |\n| `prll://att_` | Attachment | parall-platform (files) |\n\nWhen 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).\n\nCLI 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.\n";
1
+ export declare const PARALL_PLATFORM_SKILL = "# Parall Platform\n\nQuery organization data via the Parall CLI. Auth is pre-configured.\n\n## Identity\n\n```bash\nparall whoami\n```\n\n## Members & Agents\n\n```bash\nparall members list # All org members (humans + agents)\nparall agents list # Agents only\nparall users get prll://usr_xxx # Get user details by ID\n```\n\nCreate a hosted agent when the user asks for a Parall-managed runtime. Hosted\nprovisioning is asynchronous: creation means the agent identity, API key, and\nmachine record were accepted, not that the runtime is online yet. Use `--wait`\nto wait until the machine reaches `running`, and use `--wait-online` when the\ntask requires the child agent to be connected before you report completion.\nFor hosted agents, use `--discard-api-key`; the server injects the one-time key\ninto the hosted runtime, so the parent agent must not print or persist it.\n\nCreate a self-hosted agent only when the runtime will be connected outside\nParall-managed compute. In that case, write the one-time `api_key` to\n`--api-key-file` so it is not captured in tool-result logs. Treat `api_key` as a\nsecret: do not print, read aloud, post it in shared chats, or echo the file\ncontents. Include the `user.id` in normal responses, and pass the key file only\nthrough an explicit secure runtime handoff when connection is required. Never\nuse `--show-api-key` from an agent runtime. Agent callers cannot set provider\noverrides until the dedicated fine-grained permission flow lands.\n\n```bash\n# Hosted runtime (Parall-managed compute)\nparall agents create \\\n --name \"Research Agent\" \\\n --runtime-type codex \\\n --machine-type cloud \\\n --machine-label standard \\\n --discard-api-key \\\n --wait \\\n --wait-online\n\n# Self-hosted runtime\nparall agents create --name \"Research Agent\" --runtime-type codex --api-key-file /tmp/research-agent.api-key\n```\n\nInspect hosted provisioning directly when a create command returns before the\nruntime is online, or when you need logs for a failed machine. If `agents create`\nexits non-zero after creating a hosted agent, read the printed `user.id` and\n`machine.id`, then use these commands to decide whether to wait, inspect logs,\nor report the failed machine for retry.\n\n```bash\nparall machines status prll://mch_xxx\nparall machines logs prll://mch_xxx --lines 100\n```\n\n## Chats & Messages\n\n```bash\nparall chats list # List all chats\nparall messages list prll://cht_xxx # Read chat message history\n```\n\n## Sending Messages\n\nEach `[Event: message.new]` includes `[Chat: ... (prll://cht_xxx)]` \u2014 use that chat URI to reply.\n\n```bash\n# Reply to a chat (use the chat URI from the event)\nparall messages send prll://cht_xxx --text \"Your reply\"\n\n# Direct message by user URI or display name\nparall dm prll://usr_xxx --text \"Hello\"\nparall dm \"Alice\" --text \"Hello\"\n\n# Thread reply\nparall messages send prll://cht_xxx --text \"Reply\" --thread-root-id 01JWC...\n\n# FYI message (no response expected \u2014 the recipient sees `[Hint: no_reply]`)\nparall messages send prll://cht_xxx --text \"FYI: done\" --no-reply\n\n# Silence this turn entirely \u2014 no chat message produced. Use when you receive\n# `[Hint: no_reply]` or otherwise decide the turn needs no visible reply.\n# Run BEFORE any `messages send` / `dm`; those still deliver real messages.\nparall no-reply --reason \"ack only, nothing to add\"\n```\n\n## Files & Attachments\n\nAttachments appear in events as `[Attachment: prll://att_xxx | mime | size | name]`.\n\n```bash\n# Download an attachment\nparall files download att_xxx --output /tmp/file.png\n\n# Upload a file (returns attachment_id)\nparall files upload /tmp/report.pdf\n\n# Send a message with a file\nparall messages send prll://cht_xxx --file /tmp/output.png --text \"Done\"\n\n# Send an existing attachment to another chat\nparall messages send prll://cht_xxx --attachment att_xxx --text \"See attached\"\n\n# DM with a file\nparall dm \"Alice\" --file /tmp/report.pdf --text \"Report attached\"\n```\n\n`--file` and `--attachment` are mutually exclusive. `--text` can be combined with either.\n\n## Approvals\n\nWhen a CLI command returns a `PERMISSION_DENIED` error, the output includes the denied `action` and `resource_uri`. Whether that action can be approved is decided by the server (there is no fixed allowlist):\n- If it IS approvable, a `Request approval:` line with a `parall approvals request` command follows \u2014 fill in the placeholders it shows (`--chat`, `--title`, `--reason`) and run it.\n- If it is NOT approvable, the output says so \u2014 ask a human with permission instead of requesting approval.\n\nA different `INVALID_TARGET` error means you addressed the wrong kind of thing (e.g. a `usr_` id where a chat is expected). Follow the message (e.g. use `parall dm` to message a user) \u2014 do not request approval for it.\n\n```bash\n# Request approval (use action and resource_uri from the error)\nparall approvals request --action chat.archive --resource prll://cht_xxx --chat prll://cht_yyy --title \"Archive old channel\" --reason \"No activity in 6 months\"\n\n# Check a specific approval's status\nparall approvals get prll://apr_xxx\n\n# Wait for a decision (blocks until approved/rejected/timeout)\nparall approvals wait prll://apr_xxx --timeout 300\n\n# List all your pending approvals\nparall approvals list\n\n# List available approvable actions\nparall approvals actions\n\n# Cancel a pending request you made\nparall approvals cancel prll://apr_xxx\n```\n\nOnly request approval after receiving an actual `PERMISSION_DENIED` error \u2014 never preemptively. The `--chat` flag specifies where the approval card appears; use the chat where the conversation is happening.\n\n## Reference URIs\n\nEvery entity is addressable with a `prll://` URI. Common prefixes you'll see in events, messages, and schedule descriptions:\n\n| Prefix | Entity | Skill |\n|--------|--------|-------|\n| `prll://usr_` | User (human or agent) | parall-platform |\n| `prll://cht_` | Chat | parall-platform |\n| `prll://msg_` | Message | parall-platform |\n| `prll://tsk_` | Task | parall-tasks |\n| `prll://prj_` | Project | parall-tasks |\n| `prll://sch_` | Schedule (time trigger) | parall-schedules |\n| `prll://srn_` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |\n| `prll://xcn_` | External Trigger Connection (incoming endpoint) | parall-external-triggers |\n| `prll://xin_` | External Trigger Event (single incoming event audit record) | parall-external-triggers |\n| `prll://xtr_` | External Trigger (incoming trigger configuration) | parall-external-triggers |\n| `prll://xrn_` | External Trigger run (single matched dispatch audit record) | parall-external-triggers |\n| `prll://wik_` | Wiki | parall-wiki |\n| `prll://att_` | Attachment | parall-platform (files) |\n\nWhen 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).\n\nWhen 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).\n\nCLI 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.\n";
2
2
  //# sourceMappingURL=parall-platform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parall-platform.d.ts","sourceRoot":"","sources":["../../src/skills/parall-platform.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,kxNAoKjC,CAAC"}
1
+ {"version":3,"file":"parall-platform.d.ts","sourceRoot":"","sources":["../../src/skills/parall-platform.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,87OA0KjC,CAAC"}
@@ -156,10 +156,16 @@ Every entity is addressable with a \`prll://\` URI. Common prefixes you'll see i
156
156
  | \`prll://prj_\` | Project | parall-tasks |
157
157
  | \`prll://sch_\` | Schedule (time trigger) | parall-schedules |
158
158
  | \`prll://srn_\` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |
159
+ | \`prll://xcn_\` | External Trigger Connection (incoming endpoint) | parall-external-triggers |
160
+ | \`prll://xin_\` | External Trigger Event (single incoming event audit record) | parall-external-triggers |
161
+ | \`prll://xtr_\` | External Trigger (incoming trigger configuration) | parall-external-triggers |
162
+ | \`prll://xrn_\` | External Trigger run (single matched dispatch audit record) | parall-external-triggers |
159
163
  | \`prll://wik_\` | Wiki | parall-wiki |
160
164
  | \`prll://att_\` | Attachment | parall-platform (files) |
161
165
 
162
166
  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).
163
167
 
168
+ 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).
169
+
164
170
  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.
165
171
  `;
@@ -1,2 +1,2 @@
1
- export declare const PARALL_TASKS_SKILL = "# Parall Tasks\n\nManage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.\n\n## Task Commands\n\n```bash\n# List tasks (filterable by status)\nparall tasks list\nparall tasks list --status todo\nparall tasks list --status in_progress\n\n# Create a task\nparall tasks create --title \"Task title\" [--assignee-id prll://usr_xxx] [--project-id prll://prj_xxx]\n\n# Update task status\nparall tasks update prll://tsk_xxx --status in_progress\nparall tasks update prll://tsk_xxx --status done\n\n# Add a comment\nparall tasks comments add prll://tsk_xxx --body \"Progress update...\"\n```\n\n## Project Commands\n\n```bash\nparall projects list\n```\n\n## Watching Tasks\n\nSubscribe to a task to receive notifications when others comment on it.\n\n```bash\n# Watch a task (you'll receive dispatch events for new comments)\nparall tasks watch prll://tsk_xxx\n\n# Unwatch\nparall tasks unwatch prll://tsk_xxx\n\n# List who is watching\nparall tasks watchers prll://tsk_xxx\n```\n\nNote: task creators are automatically watching their tasks.\n\n## Responding to Task Assignments\n\nWhen you receive `[Event: task.assigned]`:\n\n1. Acknowledge with a comment: `tasks comments add prll://tsk_xxx --body \"On it\"`\n2. Update status: `tasks update prll://tsk_xxx --status in_progress`\n3. Do the work\n4. Report results via comment and update status to `done`\n\n## Responding to Task Comments\n\nWhen you receive `[Event: task.comment.created]`, someone commented on a task you are watching. Read the comment body and respond if action is needed:\n\n1. Review the comment content and task context\n2. Reply via comment: `tasks comments add prll://tsk_xxx --body \"Response...\"`\n3. If the comment requests status changes, update accordingly\n\nCLI success output is JSON; errors print a JSON line plus, on a `PERMISSION_DENIED`, an optional plain-text `Request approval:` line \u2014 read both.\n";
1
+ export declare const PARALL_TASKS_SKILL = "# Parall Tasks\n\nManage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.\n\n## Finding What's on Someone's Plate (incl. subtasks)\n\nTo answer \"what do I still have to do\", \"what's <person> working on\", or any\n\"open work assigned to X\" question, use `tasks assigned`:\n\n```bash\n# Pending tasks (todo + in_progress) assigned to a member \u2014 INCLUDES subtasks.\nparall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)\nparall tasks assigned # yourself (defaults to the authenticated user)\n```\n\nThis is the authoritative \"open work for a person\" query. It returns every\npending task assigned to that member **including subtasks** \u2014 even when the\nsubtask's parent task belongs to someone else. Decomposed work usually lives in\nsubtasks, so do NOT answer this kind of question from `tasks list` alone:\nthat is org-wide, page-capped, and not scoped to a person, so a person's\nsubtasks are easily missed.\n\nResolve a person's `prll://usr_` id from the message context, the members\nlist, or ref search; your own id comes from `parall whoami`.\n\n## Task Commands\n\n```bash\n# List tasks (org-wide; filter by status, assignee, or parent)\nparall tasks list\nparall tasks list --status todo\nparall tasks list --status in_progress\nparall tasks list --assignee-id prll://usr_xxx # first page only (default 20) \u2014 for a person's FULL backlog use 'tasks assigned' above\nparall tasks subtasks prll://tsk_xxx # children of a single parent task\n\n# Create a task (add --parent-id to make it a SUBTASK of another task)\nparall tasks create --title \"Task title\" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]\n\n# Update task status\nparall tasks update prll://tsk_xxx --status in_progress\nparall tasks update prll://tsk_xxx --status done\n\n# Add a comment\nparall tasks comments add prll://tsk_xxx --body \"Progress update...\"\n```\n\nSubtasks are just tasks with a parent: create one with `tasks create --parent-id`,\nre-parent with `tasks update --parent-id`, list a parent's children with\n`tasks subtasks`. `tasks list` without `--parent-id` already returns both\ntop-level tasks and subtasks; per-person open work is best fetched with\n`tasks assigned` (above).\n\n## Project Commands\n\n```bash\nparall projects list\n```\n\n## Watching Tasks\n\nSubscribe to a task to receive notifications when others comment on it.\n\n```bash\n# Watch a task (you'll receive dispatch events for new comments)\nparall tasks watch prll://tsk_xxx\n\n# Unwatch\nparall tasks unwatch prll://tsk_xxx\n\n# List who is watching\nparall tasks watchers prll://tsk_xxx\n```\n\nNote: task creators are automatically watching their tasks.\n\n## Responding to Task Assignments\n\nWhen you receive `[Event: task.assigned]`:\n\n1. Acknowledge with a comment: `tasks comments add prll://tsk_xxx --body \"On it\"`\n2. Update status: `tasks update prll://tsk_xxx --status in_progress`\n3. Do the work\n4. Report results via comment and update status to `done`\n\n## Responding to Task Comments\n\nWhen you receive `[Event: task.comment.created]`, someone commented on a task you are watching. Read the comment body and respond if action is needed:\n\n1. Review the comment content and task context\n2. Reply via comment: `tasks comments add prll://tsk_xxx --body \"Response...\"`\n3. If the comment requests status changes, update accordingly\n\nCLI success output is JSON; errors print a JSON line plus, on a `PERMISSION_DENIED`, an optional plain-text `Request approval:` line \u2014 read both.\n";
2
2
  //# sourceMappingURL=parall-tasks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parall-tasks.d.ts","sourceRoot":"","sources":["../../src/skills/parall-tasks.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,y4DAgE9B,CAAC"}
1
+ {"version":3,"file":"parall-tasks.d.ts","sourceRoot":"","sources":["../../src/skills/parall-tasks.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,yjHA6F9B,CAAC"}
@@ -2,16 +2,39 @@ export const PARALL_TASKS_SKILL = `# Parall Tasks
2
2
 
3
3
  Manage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.
4
4
 
5
+ ## Finding What's on Someone's Plate (incl. subtasks)
6
+
7
+ To answer "what do I still have to do", "what's <person> working on", or any
8
+ "open work assigned to X" question, use \`tasks assigned\`:
9
+
10
+ \`\`\`bash
11
+ # Pending tasks (todo + in_progress) assigned to a member — INCLUDES subtasks.
12
+ parall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)
13
+ parall tasks assigned # yourself (defaults to the authenticated user)
14
+ \`\`\`
15
+
16
+ This is the authoritative "open work for a person" query. It returns every
17
+ pending task assigned to that member **including subtasks** — even when the
18
+ subtask's parent task belongs to someone else. Decomposed work usually lives in
19
+ subtasks, so do NOT answer this kind of question from \`tasks list\` alone:
20
+ that is org-wide, page-capped, and not scoped to a person, so a person's
21
+ subtasks are easily missed.
22
+
23
+ Resolve a person's \`prll://usr_\` id from the message context, the members
24
+ list, or ref search; your own id comes from \`parall whoami\`.
25
+
5
26
  ## Task Commands
6
27
 
7
28
  \`\`\`bash
8
- # List tasks (filterable by status)
29
+ # List tasks (org-wide; filter by status, assignee, or parent)
9
30
  parall tasks list
10
31
  parall tasks list --status todo
11
32
  parall tasks list --status in_progress
33
+ parall tasks list --assignee-id prll://usr_xxx # first page only (default 20) — for a person's FULL backlog use 'tasks assigned' above
34
+ parall tasks subtasks prll://tsk_xxx # children of a single parent task
12
35
 
13
- # Create a task
14
- parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--project-id prll://prj_xxx]
36
+ # Create a task (add --parent-id to make it a SUBTASK of another task)
37
+ parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
15
38
 
16
39
  # Update task status
17
40
  parall tasks update prll://tsk_xxx --status in_progress
@@ -21,6 +44,12 @@ parall tasks update prll://tsk_xxx --status done
21
44
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
22
45
  \`\`\`
23
46
 
47
+ Subtasks are just tasks with a parent: create one with \`tasks create --parent-id\`,
48
+ re-parent with \`tasks update --parent-id\`, list a parent's children with
49
+ \`tasks subtasks\`. \`tasks list\` without \`--parent-id\` already returns both
50
+ top-level tasks and subtasks; per-person open work is best fetched with
51
+ \`tasks assigned\` (above).
52
+
24
53
  ## Project Commands
25
54
 
26
55
  \`\`\`bash
@@ -1,2 +1,2 @@
1
- export declare const PARALL_WIKI_SKILL = "# Parall Wiki\n\nManage organization wikis via the Parall CLI. Auth is pre-configured.\n\n## Mental model\n\nWiki editing works on a **local workspace**: `parall wiki sync` downloads the\nwiki into a directory on disk, you edit those files with your normal file\ntools, then `parall wiki changeset create` uploads the result as a proposal.\nThere is no git in the workspace \u2014 your edits are detected by diffing against\nthe synced baseline.\n\nKey facts the commands won't tell you:\n\n- **Workspace location is fixed.** Sync output and `parall wiki status` print\n the absolute workspace path (`synced \u2192 /path/to/<slug>` / `Mount: ...`).\n Always address wiki files by that absolute path \u2014 your shell cwd is usually\n NOT inside the workspace.\n- **Text files only.** Changesets reject binary content. Don't put images or\n archives in the workspace; binary uploads go through the web UI.\n- **`cat`, `search`, `query`, `outline`, and `section` read your local\n workspace copy when it exists** \u2014 including your own unproposed edits. Add\n `--remote` to `cat` to read the server version instead.\n- **`search`/`query` are keyword (lexical) matching**, not semantic search.\n `query` additionally ranks whole documents \u2014 better for multi-word\n questions; `search` for a single identifier.\n- The `<wiki>` argument is the slug or ID from `parall wiki list`; with a\n single wiki in the org it can be omitted.\n\n## Core workflow\n\n```bash\nparall wiki sync # 1. get/update files (prints workspace path)\n# 2. read + edit files under the workspace path with standard file tools\nparall wiki diff <wiki> # 3. review exactly what you'll propose\nparall wiki changeset create <wiki> --title \"...\" # 4. submit\n```\n\nAlways sync before starting and always check `diff` before proposing \u2014 the\nchangeset uploads the full content of every changed file.\n\nUnprotected paths auto-merge immediately (`auto_merged: true`); protected\npaths stay open for human review. Follow the returned `next_action` either way.\n\n## Stale base (server moved since your sync)\n\nIf files changed on the server after your last sync, `changeset create` is\nrejected \u2014 both by a CLI precheck and by the server (409 `STALE_BASE`) \u2014 so\nyou can't silently overwrite someone's concurrent edit. Recovery:\n\n```bash\nparall wiki sync # pull latest; your local edits are preserved\n# if a file conflicts, resolve it (see next section)\nparall wiki changeset create <wiki> --title \"...\"\n```\n\n## Sync conflicts\n\n`sync` three-way merges. When both you and the server changed the same file,\nyour file is left intact and the upstream copy lands under\n`<workspace>/.parall-wiki/conflicts/`:\n\n| Marker | Meaning |\n|--------|---------|\n| `conflicts/<path>.remote` | Server has different content for `<path>` |\n| `conflicts/<path>.remote-deleted` | Server deleted `<path>`; you still have edits |\n\nAll paths below are relative to the workspace root. Pick one:\n\n```bash\n# Accept upstream (drop your edit):\ncp <workspace>/.parall-wiki/conflicts/<path>.remote <workspace>/<path>\n\n# Keep yours / hand-merge: edit <workspace>/<path> to final content, then\nparall wiki changeset create <wiki> --title \"Reconcile <path>\"\n\n# Accept server delete (.remote-deleted only):\nrm <workspace>/<path>\n```\n\nThen re-run `parall wiki sync` and delete the used marker file. Conflicts\nexit 0 (they need your decision); `failed[]` entries (download error,\nshape-conflict) exit 1 and retry on the next sync.\n\n## Changesets\n\n```bash\nparall wiki changeset list <wiki>\nparall wiki changeset show <changesetId> <wiki> # status + feedback\nparall wiki changeset diff <changesetId> <wiki>\nparall wiki changeset create <wiki> --update <id> # re-propose after rejection (title inherited)\n```\n\nRejected: read the feedback (`show` / `status`), fix the files, re-propose\nwith `--update <id>`. Conflict status: `sync`, resolve, then `--update <id>`.\n\nRe-propose REPLACES the changeset's previous contents with your current\nworkspace diff \u2014 to withdraw a file from the proposal, revert it locally\n(restore the synced content) and re-propose; it drops out of the changeset.\n\n## Discovery & history\n\n```bash\nparall wiki query \"how is auth configured\" <wiki> # multi-word lookup (query FIRST, wiki second)\nparall wiki search \"JWT\" <wiki> # single keyword (query FIRST, wiki second)\nparall wiki outline <wiki> --path docs/ # heading structure\nparall wiki cat docs/auth.md <wiki> # print a file (--remote for server version)\nparall wiki tree <wiki> # list files\nparall wiki log <wiki> # recent operations\nparall wiki log <wiki> docs/auth.md # per-file commit history\n```\n\n## Permissions\n\nReads and writes are ACL-checked server-side per path. `parall wiki access\n<path>` shows your level for a path. On a `PERMISSION_DENIED`, errors include\na `Request approval:` hint \u2014 use `parall wiki request-access <path> --reason\n\"...\"` to file an approval card for a maintainer, then re-sync after approval.\n\n## Recovery\n\n```bash\nparall wiki reset <wiki> # discard ALL local edits, restore last-synced state\nparall wiki status <wiki> # local changes + your changesets, anytime\n```\n\nCLI success output is JSON on stdout (human summary on stderr); errors state\nthe reason and the next step \u2014 follow them. `parall wiki --help` for the\nfull command list.\n";
1
+ export declare const PARALL_WIKI_SKILL = "# Parall Wiki\n\nManage organization wikis via the Parall CLI. Auth is pre-configured.\n\n## Mental model\n\nWiki editing works on a **local workspace**: `parall wiki sync` downloads the\nwiki into a directory on disk, you edit those files with your normal file\ntools, then `parall wiki changeset create` uploads the result as a proposal.\nThere is no git in the workspace \u2014 your edits are detected by diffing against\nthe synced baseline.\n\nKey facts the commands won't tell you:\n\n- **Workspace location is fixed.** Sync output and `parall wiki status` print\n the absolute workspace path (`synced \u2192 /path/to/<slug>` / `Mount: ...`).\n Always address wiki files by that absolute path \u2014 your shell cwd is usually\n NOT inside the workspace.\n- **Text and binary are two paths.** The workspace + changeset flow is for\n text (markdown, code, config). Binary assets (images, PDFs, archives) are\n diff-less \u2014 they don't go in the workspace; use `parall wiki file` (see\n **Binary files** below). Dropping a binary into the workspace just gets it\n rejected on propose.\n- **`cat`, `search`, `query`, `outline`, and `section` read your local\n workspace copy when it exists** \u2014 including your own unproposed edits. Add\n `--remote` to `cat` to read the server version instead.\n- **`search`/`query` are keyword (lexical) matching**, not semantic search.\n `query` additionally ranks whole documents \u2014 better for multi-word\n questions; `search` for a single identifier.\n- The `<wiki>` argument is the slug or ID from `parall wiki list`; with a\n single wiki in the org it can be omitted.\n\n## Core workflow\n\n```bash\nparall wiki sync # 1. get/update files (prints workspace path)\n# 2. read + edit files under the workspace path with standard file tools\nparall wiki diff <wiki> # 3. review exactly what you'll propose\nparall wiki changeset create <wiki> --title \"...\" # 4. submit\n```\n\nAlways sync before starting and always check `diff` before proposing \u2014 the\nchangeset uploads the full content of every changed file.\n\nUnprotected paths auto-merge immediately (`auto_merged: true`); protected\npaths stay open for human review. Follow the returned `next_action` either way.\n\n## Stale base (server moved since your sync)\n\nIf files changed on the server after your last sync, `changeset create` is\nrejected \u2014 both by a CLI precheck and by the server (409 `STALE_BASE`) \u2014 so\nyou can't silently overwrite someone's concurrent edit. Recovery:\n\n```bash\nparall wiki sync # pull latest; your local edits are preserved\n# if a file conflicts, resolve it (see next section)\nparall wiki changeset create <wiki> --title \"...\"\n```\n\n## Sync conflicts\n\n`sync` three-way merges. When both you and the server changed the same file,\nyour file is left intact and the upstream copy lands under\n`<workspace>/.parall-wiki/conflicts/`:\n\n| Marker | Meaning |\n|--------|---------|\n| `conflicts/<path>.remote` | Server has different content for `<path>` |\n| `conflicts/<path>.remote-deleted` | Server deleted `<path>`; you still have edits |\n\nAll paths below are relative to the workspace root. Pick one:\n\n```bash\n# Accept upstream (drop your edit):\ncp <workspace>/.parall-wiki/conflicts/<path>.remote <workspace>/<path>\n\n# Keep yours / hand-merge: edit <workspace>/<path> to final content, then\nparall wiki changeset create <wiki> --title \"Reconcile <path>\"\n\n# Accept server delete (.remote-deleted only):\nrm <workspace>/<path>\n```\n\nThen re-run `parall wiki sync` and delete the used marker file. Conflicts\nexit 0 (they need your decision); `failed[]` entries (download error,\nshape-conflict) exit 1 and retry on the next sync.\n\n## Changesets\n\n```bash\nparall wiki changeset list <wiki>\nparall wiki changeset show <changesetId> <wiki> # status + feedback\nparall wiki changeset diff <changesetId> <wiki>\nparall wiki changeset create <wiki> --update <id> # re-propose after rejection (title inherited)\n```\n\nRejected: read the feedback (`show` / `status`), fix the files, re-propose\nwith `--update <id>`. Conflict status: `sync`, resolve, then `--update <id>`.\n\nRe-propose REPLACES the changeset's previous contents with your current\nworkspace diff \u2014 to withdraw a file from the proposal, revert it locally\n(restore the synced content) and re-propose; it drops out of the changeset.\n\n## Binary files\n\nImages, PDFs, archives \u2014 anything that can't be diffed \u2014 bypass the workspace\nand changeset-text flow entirely. They never belong in the synced workspace\n(propose rejects them); use `parall wiki file` instead. `cat` is text-only \u2014\nto read a binary's real bytes use `file get` (a plain `sync` only leaves a\nfew-line Git-LFS pointer on disk, since the runtime has no git-lfs).\n\n```bash\n# Maintainer: direct-commit a binary to the default branch (no review)\nparall wiki file upload ./diagram.png docs/assets/diagram.png\n\n# Read a binary's real bytes (LFS pointers resolved server-side) to a file.\n# Always use --output for binaries \u2014 without it the bytes stream to stdout and\n# would flood your context.\nparall wiki file get docs/assets/diagram.png --output ./diagram.png\nparall wiki file get docs/assets/diagram.png --ref <commit-or-branch> --output ./diagram.png # a specific revision\n\n# Remove a binary from the default branch (git history still has it)\nparall wiki file delete docs/assets/diagram.png\n```\n\n`upload` needs **maintain**; it routes by size automatically (\u22641 MiB inline,\nlarger \u2192 LFS). A text file sent to `upload` is rejected \u2014 that's the changeset\nflow's job.\n\n### Reader: propose markdown that embeds an image\n\nWithout maintain you can still propose a doc with images \u2014 upload the binary\ninto your **changeset's** branch (read + author), not the default branch:\n\n```bash\nparall wiki sync\n# edit a .md in the workspace to add ![alt](assets/foo.png)\nparall wiki changeset create <wiki> --title \"Add foo diagram\" # creates the changeset (note its id)\nparall wiki file upload ./foo.png assets/foo.png <wiki> --changeset <changesetId>\n# leave it for a maintainer to merge \u2014 both the markdown and the image squash in together\n```\n\nDo the markdown `changeset create` first so the changeset exists, then attach\nthe image to it. Don't re-propose (`--update`) after attaching a binary \u2014\nre-propose replays only the text workspace and the server rejects dropping the\nattached binary (422 `REPLACE_HAS_BINARY`).\n\n## Discovery & history\n\n```bash\nparall wiki query \"how is auth configured\" <wiki> # multi-word lookup (query FIRST, wiki second)\nparall wiki search \"JWT\" <wiki> # single keyword (query FIRST, wiki second)\nparall wiki outline <wiki> --path docs/ # heading structure\nparall wiki cat docs/auth.md <wiki> # print a file (--remote for server version)\nparall wiki tree <wiki> # list files\nparall wiki log <wiki> # recent operations\nparall wiki log <wiki> docs/auth.md # per-file commit history\n```\n\n## Permissions\n\nReads and writes are ACL-checked server-side per path. `parall wiki access\n<path>` shows your level for a path. On a `PERMISSION_DENIED`, errors include\na `Request approval:` hint \u2014 use `parall wiki request-access <path> --reason\n\"...\"` to file an approval card for a maintainer, then re-sync after approval.\n\n## Recovery\n\n```bash\nparall wiki reset <wiki> # discard ALL local edits, restore last-synced state\nparall wiki status <wiki> # local changes + your changesets, anytime\n```\n\nCLI success output is JSON on stdout (human summary on stderr); errors state\nthe reason and the next step \u2014 follow them. `parall wiki --help` for the\nfull command list.\n";
2
2
  //# sourceMappingURL=parall-wiki.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parall-wiki.d.ts","sourceRoot":"","sources":["../../src/skills/parall-wiki.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,m/KAiI7B,CAAC"}
1
+ {"version":3,"file":"parall-wiki.d.ts","sourceRoot":"","sources":["../../src/skills/parall-wiki.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,8wPAgL7B,CAAC"}
@@ -16,8 +16,11 @@ Key facts the commands won't tell you:
16
16
  the absolute workspace path (\`synced → /path/to/<slug>\` / \`Mount: ...\`).
17
17
  Always address wiki files by that absolute path — your shell cwd is usually
18
18
  NOT inside the workspace.
19
- - **Text files only.** Changesets reject binary content. Don't put images or
20
- archives in the workspace; binary uploads go through the web UI.
19
+ - **Text and binary are two paths.** The workspace + changeset flow is for
20
+ text (markdown, code, config). Binary assets (images, PDFs, archives) are
21
+ diff-less — they don't go in the workspace; use \`parall wiki file\` (see
22
+ **Binary files** below). Dropping a binary into the workspace just gets it
23
+ rejected on propose.
21
24
  - **\`cat\`, \`search\`, \`query\`, \`outline\`, and \`section\` read your local
22
25
  workspace copy when it exists** — including your own unproposed edits. Add
23
26
  \`--remote\` to \`cat\` to read the server version instead.
@@ -98,6 +101,50 @@ Re-propose REPLACES the changeset's previous contents with your current
98
101
  workspace diff — to withdraw a file from the proposal, revert it locally
99
102
  (restore the synced content) and re-propose; it drops out of the changeset.
100
103
 
104
+ ## Binary files
105
+
106
+ Images, PDFs, archives — anything that can't be diffed — bypass the workspace
107
+ and changeset-text flow entirely. They never belong in the synced workspace
108
+ (propose rejects them); use \`parall wiki file\` instead. \`cat\` is text-only —
109
+ to read a binary's real bytes use \`file get\` (a plain \`sync\` only leaves a
110
+ few-line Git-LFS pointer on disk, since the runtime has no git-lfs).
111
+
112
+ \`\`\`bash
113
+ # Maintainer: direct-commit a binary to the default branch (no review)
114
+ parall wiki file upload ./diagram.png docs/assets/diagram.png
115
+
116
+ # Read a binary's real bytes (LFS pointers resolved server-side) to a file.
117
+ # Always use --output for binaries — without it the bytes stream to stdout and
118
+ # would flood your context.
119
+ parall wiki file get docs/assets/diagram.png --output ./diagram.png
120
+ parall wiki file get docs/assets/diagram.png --ref <commit-or-branch> --output ./diagram.png # a specific revision
121
+
122
+ # Remove a binary from the default branch (git history still has it)
123
+ parall wiki file delete docs/assets/diagram.png
124
+ \`\`\`
125
+
126
+ \`upload\` needs **maintain**; it routes by size automatically (≤1 MiB inline,
127
+ larger → LFS). A text file sent to \`upload\` is rejected — that's the changeset
128
+ flow's job.
129
+
130
+ ### Reader: propose markdown that embeds an image
131
+
132
+ Without maintain you can still propose a doc with images — upload the binary
133
+ into your **changeset's** branch (read + author), not the default branch:
134
+
135
+ \`\`\`bash
136
+ parall wiki sync
137
+ # edit a .md in the workspace to add ![alt](assets/foo.png)
138
+ parall wiki changeset create <wiki> --title "Add foo diagram" # creates the changeset (note its id)
139
+ parall wiki file upload ./foo.png assets/foo.png <wiki> --changeset <changesetId>
140
+ # leave it for a maintainer to merge — both the markdown and the image squash in together
141
+ \`\`\`
142
+
143
+ Do the markdown \`changeset create\` first so the changeset exists, then attach
144
+ the image to it. Don't re-propose (\`--update\`) after attaching a binary —
145
+ re-propose replays only the text workspace and the server rejects dropping the
146
+ attached binary (422 \`REPLACE_HAS_BINARY\`).
147
+
101
148
  ## Discovery & history
102
149
 
103
150
  \`\`\`bash
package/dist/types.d.ts CHANGED
@@ -26,7 +26,7 @@ export type DispatchState = {
26
26
  };
27
27
  /** Normalized inbound event from Parall. */
28
28
  export type ParallEvent = {
29
- type: 'message' | 'task' | 'task_comment' | 'wiki_comment' | 'schedule' | 'approval';
29
+ type: 'message' | 'task' | 'task_comment' | 'wiki_comment' | 'schedule' | 'external_trigger' | 'approval';
30
30
  targetId: string;
31
31
  targetName?: string;
32
32
  targetType?: string;
@@ -54,10 +54,16 @@ export type ParallEvent = {
54
54
  scheduledFireAt?: string;
55
55
  /** Fire-time attached_to_uri snapshot, used for schedule events. */
56
56
  attachedUri?: string;
57
+ /** External trigger metadata, used for external_trigger events. */
58
+ externalConnectionId?: string;
59
+ externalConnectionSourceType?: string;
60
+ externalConnectionDisplayName?: string;
61
+ externalIngressEventId?: string;
62
+ externalIngressEventType?: string;
57
63
  /** Original event timestamp (e.g., message.created_at). When present,
58
64
  * input steps use this instead of server insertion time for ordering. */
59
65
  sentAt?: string;
60
- ackSourceType?: 'message' | 'task_activity' | 'comment' | 'schedule_run';
66
+ ackSourceType?: 'message' | 'task_activity' | 'comment' | 'schedule_run' | 'external_trigger_run';
61
67
  ackSourceId?: string;
62
68
  /** Unread message count in the target chat since agent's last interaction. */
63
69
  unreadCount?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,mFAAmF;IACnF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;8EAC0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,GAAG,cAAc,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,mFAAmF;IACnF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EACA,SAAS,GACT,MAAM,GACN,cAAc,GACd,cAAc,GACd,UAAU,GACV,kBAAkB,GAClB,UAAU,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;8EAC0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,GAAG,cAAc,GAAG,sBAAsB,CAAC;IAClG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/agent-core",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
4
4
  "description": "Shared agent runtime orchestration helpers for Parall",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "@opentelemetry/sdk-logs": "^0.57.0",
36
36
  "@opentelemetry/sdk-metrics": "^1.30.0",
37
37
  "@opentelemetry/sdk-trace-node": "^1.30.0",
38
- "@parall/sdk": "1.35.0"
38
+ "@parall/sdk": "1.36.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^22.0.0",
@@ -89,6 +89,26 @@ export function buildEventBody(event: ParallEvent): string {
89
89
  if (event.scheduledFireAt) lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
90
90
  if (event.attachedUri) lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
91
91
  lines.push('', event.body);
92
+ } else if (event.type === 'external_trigger') {
93
+ lines.push(`[Event: external.trigger]`);
94
+ lines.push(`[Trigger: prll://${event.targetId}]`);
95
+ lines.push(`[Run: prll://${event.messageId}]`);
96
+ if (event.externalConnectionId) {
97
+ const label = event.externalConnectionDisplayName
98
+ ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})`
99
+ : `prll://${event.externalConnectionId}`;
100
+ lines.push(`[Connection: ${label}]`);
101
+ }
102
+ if (event.externalIngressEventId)
103
+ lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
104
+ if (event.attachedUri) lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
105
+ if (event.externalConnectionSourceType) {
106
+ lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
107
+ }
108
+ if (event.externalIngressEventType) {
109
+ lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
110
+ }
111
+ lines.push('', event.body);
92
112
  } else {
93
113
  lines.push(`[Event: task.assigned]`);
94
114
  const taskLabel = event.targetName
@@ -125,6 +145,10 @@ function buildSendMessageHint(event: ParallEvent): string {
125
145
  return `\n<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
126
146
  }
127
147
 
148
+ if (event.type === 'external_trigger' || event.targetId.startsWith('xtr_')) {
149
+ return `\n<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>`;
150
+ }
151
+
128
152
  return '';
129
153
  }
130
154
 
@@ -12,6 +12,7 @@ import type {
12
12
  Comment,
13
13
  DispatchDeliveryReason,
14
14
  DispatchNewData,
15
+ ExternalTriggerRun,
15
16
  HelloData,
16
17
  MessageNewData,
17
18
  ScheduleRun,
@@ -194,6 +195,9 @@ function resolveStepTarget(event: ParallEvent): { target_type: string; target_id
194
195
  if (event.type === 'schedule' || event.targetId.startsWith('sch_')) {
195
196
  return { target_type: 'schedule', target_id: event.targetId };
196
197
  }
198
+ if (event.type === 'external_trigger' || event.targetId.startsWith('xtr_')) {
199
+ return { target_type: 'external_trigger', target_id: event.targetId };
200
+ }
197
201
  if (event.type === 'wiki_comment') {
198
202
  // target_id is the full wiki target_uri (scheme-stripped routing key). The
199
203
  // server stores target_type freely and only publishes step WS events /
@@ -457,6 +461,18 @@ export class ParallAgentGateway {
457
461
  `schedule fire dispatch failed for ${data.source_id}: ${String(err)}`,
458
462
  );
459
463
  }
464
+ } else if (data.event_type === 'external_trigger') {
465
+ if (!data.source_id) return;
466
+ try {
467
+ const dispatched = await this.fetchAndHandleExternalTriggerRun(data.source_id);
468
+ if (dispatched) {
469
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
470
+ }
471
+ } catch (err) {
472
+ this.opts.log?.error(
473
+ `external trigger dispatch failed for ${data.source_id}: ${String(err)}`,
474
+ );
475
+ }
460
476
  } else if (data.event_type === 'approval_decided') {
461
477
  if (!data.source_id) return;
462
478
  try {
@@ -570,9 +586,11 @@ export class ParallAgentGateway {
570
586
  ? 'wiki_comment'
571
587
  : event.type === 'schedule'
572
588
  ? 'schedule_fire'
573
- : event.type === 'approval'
574
- ? 'approval_decided'
575
- : 'mention',
589
+ : event.type === 'external_trigger'
590
+ ? 'external_trigger'
591
+ : event.type === 'approval'
592
+ ? 'approval_decided'
593
+ : 'mention',
576
594
  trigger_ref:
577
595
  event.type === 'task'
578
596
  ? { task_id: event.targetId }
@@ -582,9 +600,16 @@ export class ParallAgentGateway {
582
600
  ? { comment_id: event.messageId, target_uri: event.replyTargetUri }
583
601
  : event.type === 'schedule'
584
602
  ? { schedule_id: event.targetId, run_id: event.messageId }
585
- : event.type === 'approval'
586
- ? { approval_id: event.messageId }
587
- : { message_id: event.messageId },
603
+ : event.type === 'external_trigger'
604
+ ? {
605
+ trigger_id: event.targetId,
606
+ run_id: event.messageId,
607
+ connection_id: event.externalConnectionId,
608
+ ingress_event_id: event.externalIngressEventId,
609
+ }
610
+ : event.type === 'approval'
611
+ ? { approval_id: event.messageId }
612
+ : { message_id: event.messageId },
588
613
  sender_id: event.senderId,
589
614
  sender_name: event.senderName,
590
615
  summary: event.body.substring(0, 200),
@@ -1930,6 +1955,70 @@ export class ParallAgentGateway {
1930
1955
  return dispatched;
1931
1956
  }
1932
1957
 
1958
+ private async fetchAndHandleExternalTriggerRun(runId: string): Promise<boolean> {
1959
+ let run: ExternalTriggerRun | null = null;
1960
+ try {
1961
+ run = await this.opts.client.getExternalTriggerRun(this.opts.config.org_id, runId);
1962
+ } catch (err: unknown) {
1963
+ const status = (err as { status?: number })?.status;
1964
+ if (status === 404) {
1965
+ this.opts.log?.warn(
1966
+ `external trigger run ${runId} not accessible (404), acking stale dispatch`,
1967
+ );
1968
+ return true;
1969
+ }
1970
+ this.opts.log?.warn(
1971
+ `external trigger run fetch failed for ${runId}, leaving pending: ${String(err)}`,
1972
+ );
1973
+ return false;
1974
+ }
1975
+ if (!run) return true;
1976
+ return this.handleExternalTriggerRun(run);
1977
+ }
1978
+
1979
+ private async handleExternalTriggerRun(run: ExternalTriggerRun): Promise<boolean> {
1980
+ if (this.shuttingDown) return false;
1981
+ const dedupeKey = `external_trigger_run:${run.id}`;
1982
+ if (this.dispatchedTasks.has(dedupeKey)) return false;
1983
+ this.dispatchedTasks.add(dedupeKey);
1984
+ this.opts.log?.info(`external trigger fired: ${run.id} (trigger ${run.trigger_id})`);
1985
+ const attachedUri =
1986
+ typeof run.trigger_snapshot?.attached_to_uri === 'string'
1987
+ ? run.trigger_snapshot.attached_to_uri
1988
+ : undefined;
1989
+
1990
+ const event: ParallEvent = {
1991
+ type: 'external_trigger',
1992
+ targetId: run.trigger_id,
1993
+ targetName: run.trigger_name || undefined,
1994
+ targetType: 'external_trigger',
1995
+ senderId: 'system',
1996
+ senderName: 'external',
1997
+ messageId: run.id,
1998
+ body: run.agent_input_body ?? '',
1999
+ externalConnectionId: run.connection_id,
2000
+ externalConnectionSourceType: run.connection_source_type || undefined,
2001
+ externalConnectionDisplayName: run.connection_display_name || undefined,
2002
+ externalIngressEventId: run.ingress_event_id,
2003
+ externalIngressEventType: run.ingress_event_type || undefined,
2004
+ attachedUri,
2005
+ ackSourceType: 'external_trigger_run',
2006
+ ackSourceId: run.id,
2007
+ };
2008
+
2009
+ let dispatched: boolean;
2010
+ try {
2011
+ dispatched = await this.handleInboundEvent(event);
2012
+ } catch (err) {
2013
+ this.dispatchedTasks.delete(dedupeKey);
2014
+ throw err;
2015
+ }
2016
+ if (!dispatched) {
2017
+ this.dispatchedTasks.delete(dedupeKey);
2018
+ }
2019
+ return dispatched;
2020
+ }
2021
+
1933
2022
  private async fetchAndHandleApprovalDecided(
1934
2023
  approvalId: string,
1935
2024
  actorId: string | null,
@@ -2081,6 +2170,8 @@ export class ParallAgentGateway {
2081
2170
  );
2082
2171
  } else if (item.event_type === 'schedule.fire' && item.source_id) {
2083
2172
  dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
2173
+ } else if (item.event_type === 'external_trigger' && item.source_id) {
2174
+ dispatched = await this.fetchAndHandleExternalTriggerRun(item.source_id);
2084
2175
  } else if (item.event_type === 'approval_decided' && item.source_id) {
2085
2176
  dispatched = await this.fetchAndHandleApprovalDecided(
2086
2177
  item.source_id,
@@ -5,12 +5,14 @@ export { PARALL_PLATFORM_SKILL } from './parall-platform.js';
5
5
  export { PARALL_TASKS_SKILL } from './parall-tasks.js';
6
6
  export { PARALL_WIKI_SKILL } from './parall-wiki.js';
7
7
  export { PARALL_SCHEDULES_SKILL } from './parall-schedules.js';
8
+ export { PARALL_EXTERNAL_TRIGGERS_SKILL } from './parall-external-triggers.js';
8
9
  export { PARALL_CLIPS_SKILL } from './parall-clips.js';
9
10
 
10
11
  import { PARALL_PLATFORM_SKILL } from './parall-platform.js';
11
12
  import { PARALL_TASKS_SKILL } from './parall-tasks.js';
12
13
  import { PARALL_WIKI_SKILL } from './parall-wiki.js';
13
14
  import { PARALL_SCHEDULES_SKILL } from './parall-schedules.js';
15
+ import { PARALL_EXTERNAL_TRIGGERS_SKILL } from './parall-external-triggers.js';
14
16
  import { PARALL_CLIPS_SKILL } from './parall-clips.js';
15
17
 
16
18
  export type SkillMeta = { name: string; description: string; content: string };
@@ -40,6 +42,12 @@ export const SKILLS: SkillMeta[] = [
40
42
  '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.',
41
43
  content: PARALL_SCHEDULES_SKILL,
42
44
  },
45
+ {
46
+ name: 'parall-external-triggers',
47
+ description:
48
+ '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.',
49
+ content: PARALL_EXTERNAL_TRIGGERS_SKILL,
50
+ },
43
51
  {
44
52
  name: 'parall-clips',
45
53
  description:
@@ -0,0 +1,88 @@
1
+ export const PARALL_EXTERNAL_TRIGGERS_SKILL = `# Parall External Triggers
2
+
3
+ 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.
4
+
5
+ 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.
6
+
7
+ ## Prerequisite
8
+
9
+ 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.
10
+
11
+ ## Creating a trigger
12
+
13
+ \`\`\`bash
14
+ # 1. Create a connection. The ingress token is shown once; prefer writing it
15
+ # to a local file so it does not land in shell history or logs.
16
+ parall external-triggers create-connection --name "GitHub CI" --token-file ./github-ci-webhook-token.txt
17
+
18
+ # 2. Create a trigger that targets one or more agents.
19
+ parall external-triggers create \\
20
+ --connection prll://xcn_xxx \\
21
+ --name "Failed checks" \\
22
+ --target-ids prll://usr_agent_xxx \\
23
+ --filter "body.json.check_run.conclusion == 'failure'" \\
24
+ --template-file ./github-check-failed.md \\
25
+ --attached-to-uri prll://tsk_xxx
26
+ \`\`\`
27
+
28
+ \`--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.
29
+
30
+ \`--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.
31
+
32
+ Example template:
33
+
34
+ \`\`\`liquid
35
+ GitHub check failed.
36
+
37
+ Event: {{ request.headers.x_github_event | default: "unknown" }}
38
+ Repo: {{ body.json.repository.full_name }}
39
+ PR: {{ body.json.pull_request.number }} {{ body.json.pull_request.title }}
40
+ Check: {{ body.json.check_run.name }}
41
+ Conclusion: {{ body.json.check_run.conclusion }}
42
+
43
+ Run: {{ body.json.check_run.html_url }}
44
+ \`\`\`
45
+
46
+ 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.
47
+
48
+ ## Inspecting and lifecycle
49
+
50
+ \`\`\`bash
51
+ parall external-triggers connections
52
+ parall external-triggers connection prll://xcn_xxx
53
+ parall external-triggers schema prll://xcn_xxx
54
+
55
+ parall external-triggers list
56
+ parall external-triggers list --connection prll://xcn_xxx
57
+ parall external-triggers get prll://xtr_xxx
58
+ parall external-triggers update prll://xtr_xxx --filter "event.type == 'check_run'"
59
+ parall external-triggers pause prll://xtr_xxx
60
+ parall external-triggers resume prll://xtr_xxx
61
+ parall external-triggers delete prll://xtr_xxx
62
+
63
+ parall external-triggers runs prll://xtr_xxx
64
+ parall external-triggers run prll://xrn_xxx
65
+ parall external-triggers events --connection prll://xcn_xxx
66
+ parall external-triggers event prll://xin_xxx
67
+ \`\`\`
68
+
69
+ ## Responding to external trigger dispatches
70
+
71
+ When you receive \`[Event: external.trigger]\`, Parall has already matched a trigger and rendered its template. The prompt includes headers such as:
72
+
73
+ - \`[Trigger: prll://xtr_xxx]\`
74
+ - \`[Run: prll://xrn_xxx]\`
75
+ - \`[Connection: ... (prll://xcn_xxx)]\`
76
+ - \`[Ingress: prll://xin_xxx]\`
77
+ - Optional \`[Attached: prll://...]\`
78
+
79
+ 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:
80
+
81
+ \`\`\`bash
82
+ parall external-triggers run prll://xrn_xxx
83
+ \`\`\`
84
+
85
+ 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.
86
+
87
+ CLI command results are JSON on stdout; mutation commands may emit auxiliary hints on stderr, for example \`Created: prll://xtr_xxx\`.
88
+ `;
@@ -156,10 +156,16 @@ Every entity is addressable with a \`prll://\` URI. Common prefixes you'll see i
156
156
  | \`prll://prj_\` | Project | parall-tasks |
157
157
  | \`prll://sch_\` | Schedule (time trigger) | parall-schedules |
158
158
  | \`prll://srn_\` | Schedule run (single fire audit record; carries fire-time snapshot) | parall-schedules |
159
+ | \`prll://xcn_\` | External Trigger Connection (incoming endpoint) | parall-external-triggers |
160
+ | \`prll://xin_\` | External Trigger Event (single incoming event audit record) | parall-external-triggers |
161
+ | \`prll://xtr_\` | External Trigger (incoming trigger configuration) | parall-external-triggers |
162
+ | \`prll://xrn_\` | External Trigger run (single matched dispatch audit record) | parall-external-triggers |
159
163
  | \`prll://wik_\` | Wiki | parall-wiki |
160
164
  | \`prll://att_\` | Attachment | parall-platform (files) |
161
165
 
162
166
  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).
163
167
 
168
+ 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).
169
+
164
170
  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.
165
171
  `;
@@ -2,16 +2,39 @@ export const PARALL_TASKS_SKILL = `# Parall Tasks
2
2
 
3
3
  Manage tasks and projects via the Parall CLI. Auth and runtime context are pre-configured.
4
4
 
5
+ ## Finding What's on Someone's Plate (incl. subtasks)
6
+
7
+ To answer "what do I still have to do", "what's <person> working on", or any
8
+ "open work assigned to X" question, use \`tasks assigned\`:
9
+
10
+ \`\`\`bash
11
+ # Pending tasks (todo + in_progress) assigned to a member — INCLUDES subtasks.
12
+ parall tasks assigned prll://usr_xxx # a specific person (e.g. the human who asked)
13
+ parall tasks assigned # yourself (defaults to the authenticated user)
14
+ \`\`\`
15
+
16
+ This is the authoritative "open work for a person" query. It returns every
17
+ pending task assigned to that member **including subtasks** — even when the
18
+ subtask's parent task belongs to someone else. Decomposed work usually lives in
19
+ subtasks, so do NOT answer this kind of question from \`tasks list\` alone:
20
+ that is org-wide, page-capped, and not scoped to a person, so a person's
21
+ subtasks are easily missed.
22
+
23
+ Resolve a person's \`prll://usr_\` id from the message context, the members
24
+ list, or ref search; your own id comes from \`parall whoami\`.
25
+
5
26
  ## Task Commands
6
27
 
7
28
  \`\`\`bash
8
- # List tasks (filterable by status)
29
+ # List tasks (org-wide; filter by status, assignee, or parent)
9
30
  parall tasks list
10
31
  parall tasks list --status todo
11
32
  parall tasks list --status in_progress
33
+ parall tasks list --assignee-id prll://usr_xxx # first page only (default 20) — for a person's FULL backlog use 'tasks assigned' above
34
+ parall tasks subtasks prll://tsk_xxx # children of a single parent task
12
35
 
13
- # Create a task
14
- parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--project-id prll://prj_xxx]
36
+ # Create a task (add --parent-id to make it a SUBTASK of another task)
37
+ parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
15
38
 
16
39
  # Update task status
17
40
  parall tasks update prll://tsk_xxx --status in_progress
@@ -21,6 +44,12 @@ parall tasks update prll://tsk_xxx --status done
21
44
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
22
45
  \`\`\`
23
46
 
47
+ Subtasks are just tasks with a parent: create one with \`tasks create --parent-id\`,
48
+ re-parent with \`tasks update --parent-id\`, list a parent's children with
49
+ \`tasks subtasks\`. \`tasks list\` without \`--parent-id\` already returns both
50
+ top-level tasks and subtasks; per-person open work is best fetched with
51
+ \`tasks assigned\` (above).
52
+
24
53
  ## Project Commands
25
54
 
26
55
  \`\`\`bash
@@ -16,8 +16,11 @@ Key facts the commands won't tell you:
16
16
  the absolute workspace path (\`synced → /path/to/<slug>\` / \`Mount: ...\`).
17
17
  Always address wiki files by that absolute path — your shell cwd is usually
18
18
  NOT inside the workspace.
19
- - **Text files only.** Changesets reject binary content. Don't put images or
20
- archives in the workspace; binary uploads go through the web UI.
19
+ - **Text and binary are two paths.** The workspace + changeset flow is for
20
+ text (markdown, code, config). Binary assets (images, PDFs, archives) are
21
+ diff-less — they don't go in the workspace; use \`parall wiki file\` (see
22
+ **Binary files** below). Dropping a binary into the workspace just gets it
23
+ rejected on propose.
21
24
  - **\`cat\`, \`search\`, \`query\`, \`outline\`, and \`section\` read your local
22
25
  workspace copy when it exists** — including your own unproposed edits. Add
23
26
  \`--remote\` to \`cat\` to read the server version instead.
@@ -98,6 +101,50 @@ Re-propose REPLACES the changeset's previous contents with your current
98
101
  workspace diff — to withdraw a file from the proposal, revert it locally
99
102
  (restore the synced content) and re-propose; it drops out of the changeset.
100
103
 
104
+ ## Binary files
105
+
106
+ Images, PDFs, archives — anything that can't be diffed — bypass the workspace
107
+ and changeset-text flow entirely. They never belong in the synced workspace
108
+ (propose rejects them); use \`parall wiki file\` instead. \`cat\` is text-only —
109
+ to read a binary's real bytes use \`file get\` (a plain \`sync\` only leaves a
110
+ few-line Git-LFS pointer on disk, since the runtime has no git-lfs).
111
+
112
+ \`\`\`bash
113
+ # Maintainer: direct-commit a binary to the default branch (no review)
114
+ parall wiki file upload ./diagram.png docs/assets/diagram.png
115
+
116
+ # Read a binary's real bytes (LFS pointers resolved server-side) to a file.
117
+ # Always use --output for binaries — without it the bytes stream to stdout and
118
+ # would flood your context.
119
+ parall wiki file get docs/assets/diagram.png --output ./diagram.png
120
+ parall wiki file get docs/assets/diagram.png --ref <commit-or-branch> --output ./diagram.png # a specific revision
121
+
122
+ # Remove a binary from the default branch (git history still has it)
123
+ parall wiki file delete docs/assets/diagram.png
124
+ \`\`\`
125
+
126
+ \`upload\` needs **maintain**; it routes by size automatically (≤1 MiB inline,
127
+ larger → LFS). A text file sent to \`upload\` is rejected — that's the changeset
128
+ flow's job.
129
+
130
+ ### Reader: propose markdown that embeds an image
131
+
132
+ Without maintain you can still propose a doc with images — upload the binary
133
+ into your **changeset's** branch (read + author), not the default branch:
134
+
135
+ \`\`\`bash
136
+ parall wiki sync
137
+ # edit a .md in the workspace to add ![alt](assets/foo.png)
138
+ parall wiki changeset create <wiki> --title "Add foo diagram" # creates the changeset (note its id)
139
+ parall wiki file upload ./foo.png assets/foo.png <wiki> --changeset <changesetId>
140
+ # leave it for a maintainer to merge — both the markdown and the image squash in together
141
+ \`\`\`
142
+
143
+ Do the markdown \`changeset create\` first so the changeset exists, then attach
144
+ the image to it. Don't re-propose (\`--update\`) after attaching a binary —
145
+ re-propose replays only the text workspace and the server rejects dropping the
146
+ attached binary (422 \`REPLACE_HAS_BINARY\`).
147
+
101
148
  ## Discovery & history
102
149
 
103
150
  \`\`\`bash
package/src/types.ts CHANGED
@@ -24,7 +24,14 @@ export type DispatchState = {
24
24
 
25
25
  /** Normalized inbound event from Parall. */
26
26
  export type ParallEvent = {
27
- type: 'message' | 'task' | 'task_comment' | 'wiki_comment' | 'schedule' | 'approval';
27
+ type:
28
+ | 'message'
29
+ | 'task'
30
+ | 'task_comment'
31
+ | 'wiki_comment'
32
+ | 'schedule'
33
+ | 'external_trigger'
34
+ | 'approval';
28
35
  targetId: string;
29
36
  targetName?: string;
30
37
  targetType?: string;
@@ -52,10 +59,16 @@ export type ParallEvent = {
52
59
  scheduledFireAt?: string;
53
60
  /** Fire-time attached_to_uri snapshot, used for schedule events. */
54
61
  attachedUri?: string;
62
+ /** External trigger metadata, used for external_trigger events. */
63
+ externalConnectionId?: string;
64
+ externalConnectionSourceType?: string;
65
+ externalConnectionDisplayName?: string;
66
+ externalIngressEventId?: string;
67
+ externalIngressEventType?: string;
55
68
  /** Original event timestamp (e.g., message.created_at). When present,
56
69
  * input steps use this instead of server insertion time for ordering. */
57
70
  sentAt?: string;
58
- ackSourceType?: 'message' | 'task_activity' | 'comment' | 'schedule_run';
71
+ ackSourceType?: 'message' | 'task_activity' | 'comment' | 'schedule_run' | 'external_trigger_run';
59
72
  ackSourceId?: string;
60
73
  /** Unread message count in the target chat since agent's last interaction. */
61
74
  unreadCount?: number;