@parall/agent-core 1.16.0 → 1.18.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/event-format.d.ts.map +1 -1
- package/dist/event-format.js +10 -0
- package/dist/gateway-base.d.ts +1 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +87 -2
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/event-format.ts +9 -0
- package/src/gateway-base.ts +91 -4
- package/src/types.ts +2 -2
|
@@ -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;AAM1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,
|
|
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;AAM1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAwCzD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CASnE"}
|
package/dist/event-format.js
CHANGED
|
@@ -25,6 +25,16 @@ export function buildEventBody(event) {
|
|
|
25
25
|
}
|
|
26
26
|
lines.push("", event.body);
|
|
27
27
|
}
|
|
28
|
+
else if (event.type === "task_comment") {
|
|
29
|
+
lines.push(`[Event: task.comment.created]`);
|
|
30
|
+
const taskLabel = event.targetName
|
|
31
|
+
? `${event.targetName} (prll://${event.targetId})`
|
|
32
|
+
: `prll://${event.targetId}`;
|
|
33
|
+
lines.push(`[Task: ${taskLabel}]`);
|
|
34
|
+
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
35
|
+
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
36
|
+
lines.push("", event.body);
|
|
37
|
+
}
|
|
28
38
|
else {
|
|
29
39
|
lines.push(`[Event: task.assigned]`);
|
|
30
40
|
const taskLabel = event.targetName
|
package/dist/gateway-base.d.ts
CHANGED
|
@@ -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,
|
|
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,EAUtB,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;IAC3B,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;AAkCF,qBAAa,kBAAkB;IAuBjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAtBjC,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;IAEzB,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAQ;IAChD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAEjB,IAAI,EAAE,oBAAoB;IAIjD,GAAG,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA2FlD,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,oBAAoB;YAmBd,eAAe;YAqBf,iBAAiB;IAsF/B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,eAAe;YAQT,gCAAgC;YAMhC,WAAW;YAyDX,gBAAgB;YAmEhB,eAAe;YAmDf,kBAAkB;YAoElB,kBAAkB;YAkBlB,4BAA4B;YAoD5B,aAAa;YA8Bb,oBAAoB;YAmCpB,iBAAiB;YAqEjB,mBAAmB;YAsGnB,WAAW;YA0DX,QAAQ;CAsBvB"}
|
package/dist/gateway-base.js
CHANGED
|
@@ -121,6 +121,21 @@ export class ParallAgentGateway {
|
|
|
121
121
|
this.opts.log?.error(`parall[${this.opts.accountId}]: task dispatch failed for ${data.id}: ${String(err)}`);
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
+
ws.on("dispatch.new", async (data) => {
|
|
125
|
+
if (data.event_type !== "task_comment")
|
|
126
|
+
return;
|
|
127
|
+
if (!data.source_id || !data.task_id)
|
|
128
|
+
return;
|
|
129
|
+
try {
|
|
130
|
+
const dispatched = await this.handleTaskComment(data.source_id, data.task_id, data.actor_id);
|
|
131
|
+
if (dispatched) {
|
|
132
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => { });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
124
139
|
this.opts.log?.info(`parall[${this.opts.accountId}]: connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
|
|
125
140
|
await ws.connect();
|
|
126
141
|
return new Promise((resolve) => {
|
|
@@ -198,8 +213,8 @@ export class ParallAgentGateway {
|
|
|
198
213
|
target_type: target.target_type,
|
|
199
214
|
target_id: target.target_id,
|
|
200
215
|
content: {
|
|
201
|
-
trigger_type: event.type === "task" ? "task_assign" : "mention",
|
|
202
|
-
trigger_ref: event.type === "task" ? { task_id: event.targetId } : { message_id: event.messageId },
|
|
216
|
+
trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : "mention",
|
|
217
|
+
trigger_ref: event.type === "task" ? { task_id: event.targetId } : event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } : { message_id: event.messageId },
|
|
203
218
|
sender_id: event.senderId,
|
|
204
219
|
sender_name: event.senderName,
|
|
205
220
|
summary: event.body.substring(0, 200),
|
|
@@ -673,6 +688,73 @@ export class ParallAgentGateway {
|
|
|
673
688
|
}
|
|
674
689
|
return dispatched;
|
|
675
690
|
}
|
|
691
|
+
async handleTaskComment(commentId, taskId, actorId) {
|
|
692
|
+
const dedupeKey = `comment:${commentId}`;
|
|
693
|
+
if (this.dispatchedTasks.has(dedupeKey))
|
|
694
|
+
return false;
|
|
695
|
+
this.dispatchedTasks.add(dedupeKey);
|
|
696
|
+
let comment = null;
|
|
697
|
+
try {
|
|
698
|
+
comment = await this.opts.client.getComment(this.opts.config.org_id, commentId);
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
const status = err?.status;
|
|
702
|
+
if (status === 404) {
|
|
703
|
+
// Comment deleted — ack stale dispatch so it doesn't pile up.
|
|
704
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: skipping deleted comment ${commentId}, acking stale dispatch`);
|
|
705
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
706
|
+
return true; // caller will ack
|
|
707
|
+
}
|
|
708
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
709
|
+
return false; // transient error — leave pending for retry
|
|
710
|
+
}
|
|
711
|
+
if (!comment) {
|
|
712
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
713
|
+
return true; // null response = gone, ack stale dispatch
|
|
714
|
+
}
|
|
715
|
+
let task = null;
|
|
716
|
+
try {
|
|
717
|
+
task = await this.opts.client.getTask(this.opts.config.org_id, taskId);
|
|
718
|
+
}
|
|
719
|
+
catch { /* task context is optional */ }
|
|
720
|
+
const taskLabel = task ? `${task.identifier ?? task.id} "${task.title}"` : taskId;
|
|
721
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: task comment on ${taskLabel} by ${actorId ?? "unknown"}`);
|
|
722
|
+
const parts = [];
|
|
723
|
+
if (task) {
|
|
724
|
+
parts.push(`Task: ${task.title} (prll://${task.id})`);
|
|
725
|
+
parts.push(`Status: ${task.status}`, `Priority: ${task.priority}`);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
parts.push(`Task: prll://${taskId}`);
|
|
729
|
+
}
|
|
730
|
+
parts.push(`Comment by: ${comment.author?.display_name ?? actorId ?? "unknown"} (prll://${comment.author_id})`);
|
|
731
|
+
parts.push("", comment.body);
|
|
732
|
+
const event = {
|
|
733
|
+
type: "task_comment",
|
|
734
|
+
targetId: taskId,
|
|
735
|
+
targetName: task?.identifier ?? undefined,
|
|
736
|
+
targetType: "task",
|
|
737
|
+
senderId: comment.author_id,
|
|
738
|
+
senderName: comment.author?.display_name ?? actorId ?? "unknown",
|
|
739
|
+
messageId: commentId,
|
|
740
|
+
body: parts.join("\n"),
|
|
741
|
+
ackSourceType: "comment",
|
|
742
|
+
ackSourceId: commentId,
|
|
743
|
+
};
|
|
744
|
+
let dispatched;
|
|
745
|
+
try {
|
|
746
|
+
dispatched = await this.handleInboundEvent(event);
|
|
747
|
+
}
|
|
748
|
+
catch (err) {
|
|
749
|
+
// Clear dedupe key so the event remains retryable on next catch-up.
|
|
750
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
751
|
+
throw err;
|
|
752
|
+
}
|
|
753
|
+
if (!dispatched) {
|
|
754
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
755
|
+
}
|
|
756
|
+
return dispatched;
|
|
757
|
+
}
|
|
676
758
|
async catchUpFromDispatch(coldStart = false) {
|
|
677
759
|
const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
|
|
678
760
|
let cursor;
|
|
@@ -722,6 +804,9 @@ export class ParallAgentGateway {
|
|
|
722
804
|
dispatched = true;
|
|
723
805
|
}
|
|
724
806
|
}
|
|
807
|
+
else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
808
|
+
dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id);
|
|
809
|
+
}
|
|
725
810
|
else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
726
811
|
if (!this.tryClaimMessage(item.source_id))
|
|
727
812
|
continue;
|
package/dist/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type DispatchState = {
|
|
|
18
18
|
};
|
|
19
19
|
/** Normalized inbound event from Parall. */
|
|
20
20
|
export type ParallEvent = {
|
|
21
|
-
type: "message" | "task";
|
|
21
|
+
type: "message" | "task" | "task_comment";
|
|
22
22
|
targetId: string;
|
|
23
23
|
targetName?: string;
|
|
24
24
|
targetType?: string;
|
|
@@ -34,7 +34,7 @@ export type ParallEvent = {
|
|
|
34
34
|
fileSize: number;
|
|
35
35
|
mimeType: string;
|
|
36
36
|
}>;
|
|
37
|
-
ackSourceType?: "message" | "task_activity";
|
|
37
|
+
ackSourceType?: "message" | "task_activity" | "comment";
|
|
38
38
|
ackSourceId?: string;
|
|
39
39
|
};
|
|
40
40
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,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;CAC3B,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,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,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,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;CAC3B,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,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,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Shared agent runtime orchestration helpers for Parall",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@parall/sdk": "1.
|
|
25
|
+
"@parall/sdk": "1.18.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.0.0",
|
package/src/event-format.ts
CHANGED
|
@@ -25,6 +25,15 @@ export function buildEventBody(event: ParallEvent): string {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
lines.push("", event.body);
|
|
28
|
+
} else if (event.type === "task_comment") {
|
|
29
|
+
lines.push(`[Event: task.comment.created]`);
|
|
30
|
+
const taskLabel = event.targetName
|
|
31
|
+
? `${event.targetName} (prll://${event.targetId})`
|
|
32
|
+
: `prll://${event.targetId}`;
|
|
33
|
+
lines.push(`[Task: ${taskLabel}]`);
|
|
34
|
+
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
35
|
+
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
36
|
+
lines.push("", event.body);
|
|
28
37
|
} else {
|
|
29
38
|
lines.push(`[Event: task.assigned]`);
|
|
30
39
|
const taskLabel = event.targetName
|
package/src/gateway-base.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
AgentConfigUpdateData,
|
|
8
8
|
Chat,
|
|
9
9
|
ChatUpdateData,
|
|
10
|
+
Comment,
|
|
11
|
+
DispatchNewData,
|
|
10
12
|
HelloData,
|
|
11
13
|
MessageNewData,
|
|
12
14
|
Task,
|
|
@@ -65,6 +67,7 @@ type DispatchableMessage = {
|
|
|
65
67
|
content: unknown;
|
|
66
68
|
thread_root_id?: string | null;
|
|
67
69
|
hints?: { no_reply?: boolean } | null;
|
|
70
|
+
attachments?: Array<{ id: string; file_name: string; file_size: number; mime_type: string }>;
|
|
68
71
|
};
|
|
69
72
|
|
|
70
73
|
type MessageDispatchDecision =
|
|
@@ -221,6 +224,19 @@ export class ParallAgentGateway {
|
|
|
221
224
|
}
|
|
222
225
|
});
|
|
223
226
|
|
|
227
|
+
ws.on("dispatch.new", async (data: DispatchNewData) => {
|
|
228
|
+
if (data.event_type !== "task_comment") return;
|
|
229
|
+
if (!data.source_id || !data.task_id) return;
|
|
230
|
+
try {
|
|
231
|
+
const dispatched = await this.handleTaskComment(data.source_id, data.task_id, data.actor_id);
|
|
232
|
+
if (dispatched) {
|
|
233
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
234
|
+
}
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
224
240
|
this.opts.log?.info(`parall[${this.opts.accountId}]: connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
|
|
225
241
|
await ws.connect();
|
|
226
242
|
|
|
@@ -298,8 +314,8 @@ export class ParallAgentGateway {
|
|
|
298
314
|
target_type: target.target_type,
|
|
299
315
|
target_id: target.target_id,
|
|
300
316
|
content: {
|
|
301
|
-
trigger_type: event.type === "task" ? "task_assign" : "mention",
|
|
302
|
-
trigger_ref: event.type === "task" ? { task_id: event.targetId } : { message_id: event.messageId },
|
|
317
|
+
trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : "mention",
|
|
318
|
+
trigger_ref: event.type === "task" ? { task_id: event.targetId } : event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } : { message_id: event.messageId },
|
|
303
319
|
sender_id: event.senderId,
|
|
304
320
|
sender_name: event.senderName,
|
|
305
321
|
summary: event.body.substring(0, 200),
|
|
@@ -693,7 +709,7 @@ export class ParallAgentGateway {
|
|
|
693
709
|
|
|
694
710
|
const content = message.content as TextContent;
|
|
695
711
|
const body = content.text?.trim() ?? "";
|
|
696
|
-
const hasAttachments =
|
|
712
|
+
const hasAttachments = message.attachments?.length;
|
|
697
713
|
|
|
698
714
|
if (!body && !hasAttachments) return { action: "skip" };
|
|
699
715
|
|
|
@@ -705,7 +721,7 @@ export class ParallAgentGateway {
|
|
|
705
721
|
if (!isMentioned) return { action: "skip" };
|
|
706
722
|
}
|
|
707
723
|
|
|
708
|
-
const attachments = (
|
|
724
|
+
const attachments = (message.attachments ?? []).map((a) => ({
|
|
709
725
|
id: a.id,
|
|
710
726
|
fileName: a.file_name,
|
|
711
727
|
fileSize: a.file_size,
|
|
@@ -797,6 +813,75 @@ export class ParallAgentGateway {
|
|
|
797
813
|
return dispatched;
|
|
798
814
|
}
|
|
799
815
|
|
|
816
|
+
private async handleTaskComment(commentId: string, taskId: string, actorId: string | null): Promise<boolean> {
|
|
817
|
+
const dedupeKey = `comment:${commentId}`;
|
|
818
|
+
if (this.dispatchedTasks.has(dedupeKey)) return false;
|
|
819
|
+
this.dispatchedTasks.add(dedupeKey);
|
|
820
|
+
|
|
821
|
+
let comment: Comment | null = null;
|
|
822
|
+
try {
|
|
823
|
+
comment = await this.opts.client.getComment(this.opts.config.org_id, commentId);
|
|
824
|
+
} catch (err: unknown) {
|
|
825
|
+
const status = (err as { status?: number })?.status;
|
|
826
|
+
if (status === 404) {
|
|
827
|
+
// Comment deleted — ack stale dispatch so it doesn't pile up.
|
|
828
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: skipping deleted comment ${commentId}, acking stale dispatch`);
|
|
829
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
830
|
+
return true; // caller will ack
|
|
831
|
+
}
|
|
832
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
833
|
+
return false; // transient error — leave pending for retry
|
|
834
|
+
}
|
|
835
|
+
if (!comment) {
|
|
836
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
837
|
+
return true; // null response = gone, ack stale dispatch
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
let task: Task | null = null;
|
|
841
|
+
try {
|
|
842
|
+
task = await this.opts.client.getTask(this.opts.config.org_id, taskId);
|
|
843
|
+
} catch { /* task context is optional */ }
|
|
844
|
+
|
|
845
|
+
const taskLabel = task ? `${task.identifier ?? task.id} "${task.title}"` : taskId;
|
|
846
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: task comment on ${taskLabel} by ${actorId ?? "unknown"}`);
|
|
847
|
+
|
|
848
|
+
const parts: string[] = [];
|
|
849
|
+
if (task) {
|
|
850
|
+
parts.push(`Task: ${task.title} (prll://${task.id})`);
|
|
851
|
+
parts.push(`Status: ${task.status}`, `Priority: ${task.priority}`);
|
|
852
|
+
} else {
|
|
853
|
+
parts.push(`Task: prll://${taskId}`);
|
|
854
|
+
}
|
|
855
|
+
parts.push(`Comment by: ${comment.author?.display_name ?? actorId ?? "unknown"} (prll://${comment.author_id})`);
|
|
856
|
+
parts.push("", comment.body);
|
|
857
|
+
|
|
858
|
+
const event: ParallEvent = {
|
|
859
|
+
type: "task_comment",
|
|
860
|
+
targetId: taskId,
|
|
861
|
+
targetName: task?.identifier ?? undefined,
|
|
862
|
+
targetType: "task",
|
|
863
|
+
senderId: comment.author_id,
|
|
864
|
+
senderName: comment.author?.display_name ?? actorId ?? "unknown",
|
|
865
|
+
messageId: commentId,
|
|
866
|
+
body: parts.join("\n"),
|
|
867
|
+
ackSourceType: "comment",
|
|
868
|
+
ackSourceId: commentId,
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
let dispatched: boolean;
|
|
872
|
+
try {
|
|
873
|
+
dispatched = await this.handleInboundEvent(event);
|
|
874
|
+
} catch (err) {
|
|
875
|
+
// Clear dedupe key so the event remains retryable on next catch-up.
|
|
876
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
877
|
+
throw err;
|
|
878
|
+
}
|
|
879
|
+
if (!dispatched) {
|
|
880
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
881
|
+
}
|
|
882
|
+
return dispatched;
|
|
883
|
+
}
|
|
884
|
+
|
|
800
885
|
private async catchUpFromDispatch(coldStart = false) {
|
|
801
886
|
const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
|
|
802
887
|
let cursor: string | undefined;
|
|
@@ -844,6 +929,8 @@ export class ParallAgentGateway {
|
|
|
844
929
|
} else {
|
|
845
930
|
dispatched = true;
|
|
846
931
|
}
|
|
932
|
+
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
933
|
+
dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id);
|
|
847
934
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
848
935
|
if (!this.tryClaimMessage(item.source_id)) continue;
|
|
849
936
|
let msg: Awaited<ReturnType<typeof this.opts.client.getMessage>> | null = null;
|
package/src/types.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type DispatchState = {
|
|
|
16
16
|
|
|
17
17
|
/** Normalized inbound event from Parall. */
|
|
18
18
|
export type ParallEvent = {
|
|
19
|
-
type: "message" | "task";
|
|
19
|
+
type: "message" | "task" | "task_comment";
|
|
20
20
|
targetId: string;
|
|
21
21
|
targetName?: string;
|
|
22
22
|
targetType?: string;
|
|
@@ -32,6 +32,6 @@ export type ParallEvent = {
|
|
|
32
32
|
fileSize: number;
|
|
33
33
|
mimeType: string;
|
|
34
34
|
}>;
|
|
35
|
-
ackSourceType?: "message" | "task_activity";
|
|
35
|
+
ackSourceType?: "message" | "task_activity" | "comment";
|
|
36
36
|
ackSourceId?: string;
|
|
37
37
|
};
|