@parall/agent-core 1.17.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 +88 -2
- 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,
|
|
@@ -222,6 +224,19 @@ export class ParallAgentGateway {
|
|
|
222
224
|
}
|
|
223
225
|
});
|
|
224
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
|
+
|
|
225
240
|
this.opts.log?.info(`parall[${this.opts.accountId}]: connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
|
|
226
241
|
await ws.connect();
|
|
227
242
|
|
|
@@ -299,8 +314,8 @@ export class ParallAgentGateway {
|
|
|
299
314
|
target_type: target.target_type,
|
|
300
315
|
target_id: target.target_id,
|
|
301
316
|
content: {
|
|
302
|
-
trigger_type: event.type === "task" ? "task_assign" : "mention",
|
|
303
|
-
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 },
|
|
304
319
|
sender_id: event.senderId,
|
|
305
320
|
sender_name: event.senderName,
|
|
306
321
|
summary: event.body.substring(0, 200),
|
|
@@ -798,6 +813,75 @@ export class ParallAgentGateway {
|
|
|
798
813
|
return dispatched;
|
|
799
814
|
}
|
|
800
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
|
+
|
|
801
885
|
private async catchUpFromDispatch(coldStart = false) {
|
|
802
886
|
const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
|
|
803
887
|
let cursor: string | undefined;
|
|
@@ -845,6 +929,8 @@ export class ParallAgentGateway {
|
|
|
845
929
|
} else {
|
|
846
930
|
dispatched = true;
|
|
847
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);
|
|
848
934
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
849
935
|
if (!this.tryClaimMessage(item.source_id)) continue;
|
|
850
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
|
};
|