@parall/agent-core 1.24.0 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge-workspace.d.ts +1 -1
- package/dist/bridge-workspace.d.ts.map +1 -1
- package/dist/bridge-workspace.js +3 -2
- package/dist/gateway-base.d.ts +1 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +42 -22
- package/package.json +2 -2
- package/src/bridge-workspace.ts +3 -2
- package/src/gateway-base.ts +43 -20
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `@parall/agent-core` stays runtime-neutral and exports only text + pure
|
|
8
8
|
* helpers here.
|
|
9
9
|
*/
|
|
10
|
-
export declare const BRIDGE_WORKSPACE_INSTRUCTIONS = "# Agent workspace\n\nYou are an agent in Parall IM. You participate in chats, handle tasks, and interact exclusively through the Parall CLI.\n\n## Message Model\n\nIncoming events are rendered as structured `[Event: ...]` blocks.\nEach event includes `[Chat: ... (prll://cht_xxx)]` \u2014 use that chat ID (or full URI) when replying.\n\n**Your plain-text output is not delivered to anyone** \u2014 it is recorded as suppressed thinking in your session steps and discarded from the chat.\nTo say something in a chat, you **must** invoke the Parall CLI via your shell/exec tool. To stay silent, simply do not invoke it.\n\n## Parall CLI\n\nAll outbound interactions go through the `parall` CLI. Credentials are pre-injected as environment variables \u2014 no setup needed. If `parall` is not on PATH, use `npx --yes @parall/cli@latest` instead.\n\n- `parall messages send prll://cht_xxx --text \"...\"` \u2014 reply into the triggering chat\n- `parall dm prll://usr_xxx --text \"...\" [--no-reply]` \u2014 direct message another user\n- `parall tasks update prll://tsk_xxx --status in_progress` \u2014 task state\n- `parall no-reply [--reason \"...\"]` \u2014 explicitly declare this turn silent (audit signal; not required for silence, just clarifies intent)\n\nThe bridge injects Parall context via environment variables. The static credentials `PRLL_API_URL`, `PRLL_API_KEY`, and `PRLL_ORG_ID` are always set. Per-dispatch context \u2014 `PRLL_SESSION_ID`, `PRLL_CHAT_ID`, `PRLL_TRIGGER_MESSAGE_ID`, `PRLL_STEP_ID_FILE` \u2014 is set in subprocess-per-dispatch runtimes (Claude Code); in long-running runtimes (Codex) those may be absent and the CLI will fall back to whatever defaults you supply on the command line.\n\n## Attachments\n\nImage attachments are pre-downloaded under `.parall/attachments/<messageId>/`. Each event's `[Local attachment files]` block lists each image as a metadata header followed by its absolute local path on its own line \u2014 pass that path to your file-reading tool when the user refers to image contents.\n\nSupported image types: PNG, JPEG, WebP, GIF. Other attachment types (PDFs, archives, etc.) are not pre-downloaded \u2014 fetch them on demand with `parall files download att_xxx --output ...`.\n\n## Guardrails\n\n- A dispatch may coalesce multiple events. Decide per event whether to reply via `messages send` / `dm` \u2014 events you do not act on simply receive no reply.\n- If an event carries `[Hint: no_reply]`, do not send anything for that event. `no-reply` is optional and only useful as an explicit intent marker.\n- Never try to \"speak\" by typing sentences like \"No response needed\" / \"Noted\" / \"OK\" \u2014 they are discarded, so they accomplish nothing except polluting your session log.\n- Keep CLI replies concise and task-focused.\n\nSee `docs/engineering-design/agent-dm-loop-prevention.md` \u00A7 Layer 0 for why plain text is never auto-projected.\n\n## Approval Flow\n\nWhen you try an action (e.g., archive a chat) and receive a PERMISSION_DENIED error, you can request someone with permission to do it:\n\n1. The error includes a `PERMISSION_DENIED` code
|
|
10
|
+
export declare const BRIDGE_WORKSPACE_INSTRUCTIONS = "# Agent workspace\n\nYou are an agent in Parall IM. You participate in chats, handle tasks, and interact exclusively through the Parall CLI.\n\n## Message Model\n\nIncoming events are rendered as structured `[Event: ...]` blocks.\nEach event includes `[Chat: ... (prll://cht_xxx)]` \u2014 use that chat ID (or full URI) when replying.\n\n**Your plain-text output is not delivered to anyone** \u2014 it is recorded as suppressed thinking in your session steps and discarded from the chat.\nTo say something in a chat, you **must** invoke the Parall CLI via your shell/exec tool. To stay silent, simply do not invoke it.\n\n## Parall CLI\n\nAll outbound interactions go through the `parall` CLI. Credentials are pre-injected as environment variables \u2014 no setup needed. If `parall` is not on PATH, use `npx --yes @parall/cli@latest` instead.\n\n- `parall messages send prll://cht_xxx --text \"...\"` \u2014 reply into the triggering chat\n- `parall dm prll://usr_xxx --text \"...\" [--no-reply]` \u2014 direct message another user\n- `parall tasks update prll://tsk_xxx --status in_progress` \u2014 task state\n- `parall no-reply [--reason \"...\"]` \u2014 explicitly declare this turn silent (audit signal; not required for silence, just clarifies intent)\n\nThe bridge injects Parall context via environment variables. The static credentials `PRLL_API_URL`, `PRLL_API_KEY`, and `PRLL_ORG_ID` are always set. Per-dispatch context \u2014 `PRLL_SESSION_ID`, `PRLL_CHAT_ID`, `PRLL_TRIGGER_MESSAGE_ID`, `PRLL_STEP_ID_FILE` \u2014 is set in subprocess-per-dispatch runtimes (Claude Code); in long-running runtimes (Codex) those may be absent and the CLI will fall back to whatever defaults you supply on the command line.\n\n## Attachments\n\nImage attachments are pre-downloaded under `.parall/attachments/<messageId>/`. Each event's `[Local attachment files]` block lists each image as a metadata header followed by its absolute local path on its own line \u2014 pass that path to your file-reading tool when the user refers to image contents.\n\nSupported image types: PNG, JPEG, WebP, GIF. Other attachment types (PDFs, archives, etc.) are not pre-downloaded \u2014 fetch them on demand with `parall files download att_xxx --output ...`.\n\n## Guardrails\n\n- A dispatch may coalesce multiple events. Decide per event whether to reply via `messages send` / `dm` \u2014 events you do not act on simply receive no reply.\n- If an event carries `[Hint: no_reply]`, do not send anything for that event. `no-reply` is optional and only useful as an explicit intent marker.\n- Never try to \"speak\" by typing sentences like \"No response needed\" / \"Noted\" / \"OK\" \u2014 they are discarded, so they accomplish nothing except polluting your session log.\n- Keep CLI replies concise and task-focused.\n\nSee `docs/engineering-design/agent-dm-loop-prevention.md` \u00A7 Layer 0 for why plain text is never auto-projected.\n\n## Approval Flow\n\nWhen you try an action (e.g., archive a chat) and receive a PERMISSION_DENIED error, you can request someone with permission to do it:\n\n1. The error includes a `PERMISSION_DENIED` code. For commonly approvable actions (archive, delete, restore), a `Tip:` line with an approval-request template is printed \u2014 copy it and fill in the remaining placeholders (`--chat`, `--title`, `--reason`)\n2. Request approval: `parall approvals request --action chat.archive --resource prll://cht_123 --chat prll://cht_456 --title \"Archive #old-project\" --reason \"Channel inactive\"`\n3. A card will appear in the specified chat for someone with permission to approve\n4. Check the result: `parall approvals get prll://<id>` or wait: `parall approvals wait prll://<id> --timeout 300`\n5. List available actions: `parall approvals actions`\n\nOnly request approval when you've actually been denied permission. Don't request approval preemptively.\n";
|
|
11
11
|
/** Extracts the `command` string from a shell/bash tool call's input payload. */
|
|
12
12
|
export declare function extractShellCommand(input: unknown): string | undefined;
|
|
13
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-workspace.d.ts","sourceRoot":"","sources":["../src/bridge-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,6BAA6B,
|
|
1
|
+
{"version":3,"file":"bridge-workspace.d.ts","sourceRoot":"","sources":["../src/bridge-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,6BAA6B,gzHAiDzC,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAItE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAmBzE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKxE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI3E"}
|
package/dist/bridge-workspace.js
CHANGED
|
@@ -49,10 +49,11 @@ See \`docs/engineering-design/agent-dm-loop-prevention.md\` § Layer 0 for why p
|
|
|
49
49
|
|
|
50
50
|
When you try an action (e.g., archive a chat) and receive a PERMISSION_DENIED error, you can request someone with permission to do it:
|
|
51
51
|
|
|
52
|
-
1. The error includes a \`PERMISSION_DENIED\` code
|
|
52
|
+
1. The error includes a \`PERMISSION_DENIED\` code. For commonly approvable actions (archive, delete, restore), a \`Tip:\` line with an approval-request template is printed — copy it and fill in the remaining placeholders (\`--chat\`, \`--title\`, \`--reason\`)
|
|
53
53
|
2. Request approval: \`parall approvals request --action chat.archive --resource prll://cht_123 --chat prll://cht_456 --title "Archive #old-project" --reason "Channel inactive"\`
|
|
54
54
|
3. A card will appear in the specified chat for someone with permission to approve
|
|
55
|
-
4. Check the result
|
|
55
|
+
4. Check the result: \`parall approvals get prll://<id>\` or wait: \`parall approvals wait prll://<id> --timeout 300\`
|
|
56
|
+
5. List available actions: \`parall approvals actions\`
|
|
56
57
|
|
|
57
58
|
Only request approval when you've actually been denied permission. Don't request approval preemptively.
|
|
58
59
|
`;
|
package/dist/gateway-base.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export declare class ParallAgentGateway {
|
|
|
68
68
|
private buildMessageDispatchDecision;
|
|
69
69
|
private handleMessage;
|
|
70
70
|
private handleTaskAssignment;
|
|
71
|
+
private handleTaskDispatch;
|
|
71
72
|
private handleTaskComment;
|
|
72
73
|
/**
|
|
73
74
|
* Fetch the schedule_run snapshot, then feed it as an inbound event. 404 is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EACV,qBAAqB,EAYtB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAEV,eAAe,EAGf,aAAa,EAEd,MAAM,uBAAuB,CAAC;AAoD/B,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,QAAQ,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,eAAe,EAAE,eAAe,CAAC;IACjC,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAI3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wBAAwB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACtE,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,QAAQ,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjH,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACjD,CAAC;AAUF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKnF;AAqCD,qBAAa,kBAAkB;IAmCjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAlCjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqC;IACtE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsC;IACjE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAK5B;IAEF,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAS;IAMzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,cAAc,CAAyB;IAE/C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAQ;IAChD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAI9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAEjB,IAAI,EAAE,oBAAoB;IAKjD,GAAG,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
|
package/dist/gateway-base.js
CHANGED
|
@@ -166,6 +166,19 @@ export class ParallAgentGateway {
|
|
|
166
166
|
this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
else if (data.event_type === "task_update") {
|
|
170
|
+
if (!data.task_id)
|
|
171
|
+
return;
|
|
172
|
+
try {
|
|
173
|
+
const dispatched = await this.handleTaskDispatch(data.task_id, data.source_id ?? data.task_id, { allowCreator: true });
|
|
174
|
+
if (dispatched) {
|
|
175
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => { });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
169
182
|
else if (data.event_type === "schedule.fire") {
|
|
170
183
|
if (!data.source_id)
|
|
171
184
|
return;
|
|
@@ -828,6 +841,25 @@ export class ParallAgentGateway {
|
|
|
828
841
|
}
|
|
829
842
|
return dispatched;
|
|
830
843
|
}
|
|
844
|
+
async handleTaskDispatch(taskId, ackSourceId, opts = {}) {
|
|
845
|
+
let task = null;
|
|
846
|
+
try {
|
|
847
|
+
task = await this.opts.client.getTask(this.opts.config.org_id, taskId);
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
const status = err?.status;
|
|
851
|
+
if (status === 404)
|
|
852
|
+
return true;
|
|
853
|
+
throw err;
|
|
854
|
+
}
|
|
855
|
+
const isAssignee = task.assignee_id === this.opts.agentUserId;
|
|
856
|
+
const isCreatorUpdate = opts.allowCreator === true && task.creator_id === this.opts.agentUserId;
|
|
857
|
+
if (!isAssignee && !isCreatorUpdate) {
|
|
858
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: skipping stale task dispatch ${ackSourceId ?? taskId} — assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
859
|
+
return true;
|
|
860
|
+
}
|
|
861
|
+
return this.handleTaskAssignment(task, ackSourceId);
|
|
862
|
+
}
|
|
831
863
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
832
864
|
if (this.shuttingDown)
|
|
833
865
|
return false; // drain window — let server requeue via catch-up
|
|
@@ -996,33 +1028,21 @@ export class ParallAgentGateway {
|
|
|
996
1028
|
try {
|
|
997
1029
|
let dispatched = false;
|
|
998
1030
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
999
|
-
let task = null;
|
|
1000
|
-
let taskFetchFailed = false;
|
|
1001
1031
|
try {
|
|
1002
|
-
|
|
1032
|
+
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id);
|
|
1003
1033
|
}
|
|
1004
1034
|
catch (err) {
|
|
1005
|
-
|
|
1006
|
-
if (status === 404) {
|
|
1007
|
-
task = null;
|
|
1008
|
-
}
|
|
1009
|
-
else {
|
|
1010
|
-
taskFetchFailed = true;
|
|
1011
|
-
this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
if (taskFetchFailed)
|
|
1035
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1015
1036
|
continue;
|
|
1016
|
-
if (task) {
|
|
1017
|
-
if (task.assignee_id !== this.opts.agentUserId) {
|
|
1018
|
-
this.opts.log?.info(`parall[${this.opts.accountId}]: skipping stale task dispatch ${item.id} — reassigned to ${task.assignee_id}`);
|
|
1019
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => { });
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
dispatched = await this.handleTaskAssignment(task);
|
|
1023
1037
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1038
|
+
}
|
|
1039
|
+
else if (item.event_type === "task_update" && item.task_id) {
|
|
1040
|
+
try {
|
|
1041
|
+
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id, { allowCreator: true });
|
|
1042
|
+
}
|
|
1043
|
+
catch (err) {
|
|
1044
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1045
|
+
continue;
|
|
1026
1046
|
}
|
|
1027
1047
|
}
|
|
1028
1048
|
else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Shared agent runtime orchestration helpers for Parall",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"src"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@parall/sdk": "1.
|
|
29
|
+
"@parall/sdk": "1.25.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0",
|
package/src/bridge-workspace.ts
CHANGED
|
@@ -50,10 +50,11 @@ See \`docs/engineering-design/agent-dm-loop-prevention.md\` § Layer 0 for why p
|
|
|
50
50
|
|
|
51
51
|
When you try an action (e.g., archive a chat) and receive a PERMISSION_DENIED error, you can request someone with permission to do it:
|
|
52
52
|
|
|
53
|
-
1. The error includes a \`PERMISSION_DENIED\` code
|
|
53
|
+
1. The error includes a \`PERMISSION_DENIED\` code. For commonly approvable actions (archive, delete, restore), a \`Tip:\` line with an approval-request template is printed — copy it and fill in the remaining placeholders (\`--chat\`, \`--title\`, \`--reason\`)
|
|
54
54
|
2. Request approval: \`parall approvals request --action chat.archive --resource prll://cht_123 --chat prll://cht_456 --title "Archive #old-project" --reason "Channel inactive"\`
|
|
55
55
|
3. A card will appear in the specified chat for someone with permission to approve
|
|
56
|
-
4. Check the result
|
|
56
|
+
4. Check the result: \`parall approvals get prll://<id>\` or wait: \`parall approvals wait prll://<id> --timeout 300\`
|
|
57
|
+
5. List available actions: \`parall approvals actions\`
|
|
57
58
|
|
|
58
59
|
Only request approval when you've actually been denied permission. Don't request approval preemptively.
|
|
59
60
|
`;
|
package/src/gateway-base.ts
CHANGED
|
@@ -272,6 +272,16 @@ export class ParallAgentGateway {
|
|
|
272
272
|
} catch (err) {
|
|
273
273
|
this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
274
274
|
}
|
|
275
|
+
} else if (data.event_type === "task_update") {
|
|
276
|
+
if (!data.task_id) return;
|
|
277
|
+
try {
|
|
278
|
+
const dispatched = await this.handleTaskDispatch(data.task_id, data.source_id ?? data.task_id, { allowCreator: true });
|
|
279
|
+
if (dispatched) {
|
|
280
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
284
|
+
}
|
|
275
285
|
} else if (data.event_type === "schedule.fire") {
|
|
276
286
|
if (!data.source_id) return;
|
|
277
287
|
try {
|
|
@@ -959,6 +969,30 @@ export class ParallAgentGateway {
|
|
|
959
969
|
return dispatched;
|
|
960
970
|
}
|
|
961
971
|
|
|
972
|
+
private async handleTaskDispatch(
|
|
973
|
+
taskId: string,
|
|
974
|
+
ackSourceId?: string,
|
|
975
|
+
opts: { allowCreator?: boolean } = {},
|
|
976
|
+
): Promise<boolean> {
|
|
977
|
+
let task: Awaited<ReturnType<typeof this.opts.client.getTask>> | null = null;
|
|
978
|
+
try {
|
|
979
|
+
task = await this.opts.client.getTask(this.opts.config.org_id, taskId);
|
|
980
|
+
} catch (err: unknown) {
|
|
981
|
+
const status = (err as { status?: number })?.status;
|
|
982
|
+
if (status === 404) return true;
|
|
983
|
+
throw err;
|
|
984
|
+
}
|
|
985
|
+
const isAssignee = task.assignee_id === this.opts.agentUserId;
|
|
986
|
+
const isCreatorUpdate = opts.allowCreator === true && task.creator_id === this.opts.agentUserId;
|
|
987
|
+
if (!isAssignee && !isCreatorUpdate) {
|
|
988
|
+
this.opts.log?.info(
|
|
989
|
+
`parall[${this.opts.accountId}]: skipping stale task dispatch ${ackSourceId ?? taskId} — assigned to ${task.assignee_id}, creator ${task.creator_id}`,
|
|
990
|
+
);
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
return this.handleTaskAssignment(task, ackSourceId);
|
|
994
|
+
}
|
|
995
|
+
|
|
962
996
|
private async handleTaskComment(
|
|
963
997
|
commentId: string,
|
|
964
998
|
taskId: string,
|
|
@@ -1134,29 +1168,18 @@ export class ParallAgentGateway {
|
|
|
1134
1168
|
try {
|
|
1135
1169
|
let dispatched = false;
|
|
1136
1170
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
1137
|
-
let task: Awaited<ReturnType<typeof this.opts.client.getTask>> | null = null;
|
|
1138
|
-
let taskFetchFailed = false;
|
|
1139
1171
|
try {
|
|
1140
|
-
|
|
1172
|
+
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id);
|
|
1141
1173
|
} catch (err: unknown) {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
task = null;
|
|
1145
|
-
} else {
|
|
1146
|
-
taskFetchFailed = true;
|
|
1147
|
-
this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1148
|
-
}
|
|
1174
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1175
|
+
continue;
|
|
1149
1176
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
dispatched = await this.handleTaskAssignment(task);
|
|
1158
|
-
} else {
|
|
1159
|
-
dispatched = true;
|
|
1177
|
+
} else if (item.event_type === "task_update" && item.task_id) {
|
|
1178
|
+
try {
|
|
1179
|
+
dispatched = await this.handleTaskDispatch(item.task_id, item.source_id ?? item.task_id, { allowCreator: true });
|
|
1180
|
+
} catch (err: unknown) {
|
|
1181
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
1182
|
+
continue;
|
|
1160
1183
|
}
|
|
1161
1184
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
1162
1185
|
dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id, item.delivery_reason);
|