paperclip-plugin-telegram 0.2.0 â 0.2.2
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/README.md +68 -4
- package/dist/acp-bridge.d.ts +34 -0
- package/dist/acp-bridge.js +805 -0
- package/dist/acp-bridge.js.map +1 -0
- package/dist/adapter.d.ts +35 -0
- package/dist/adapter.js +75 -0
- package/dist/adapter.js.map +1 -0
- package/dist/command-registry.d.ts +3 -0
- package/dist/command-registry.js +273 -0
- package/dist/command-registry.js.map +1 -0
- package/dist/commands.d.ts +10 -0
- package/dist/commands.js +213 -0
- package/dist/commands.js.map +1 -0
- package/dist/constants.d.ts +44 -0
- package/dist/constants.js +48 -0
- package/dist/constants.js.map +1 -0
- package/dist/escalation.d.ts +41 -0
- package/dist/escalation.js +254 -0
- package/dist/escalation.js.map +1 -0
- package/dist/formatters.d.ts +13 -0
- package/dist/formatters.js +130 -0
- package/dist/formatters.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.js +230 -0
- package/dist/manifest.js.map +1 -0
- package/dist/media-pipeline.d.ts +46 -0
- package/dist/media-pipeline.js +161 -0
- package/dist/media-pipeline.js.map +1 -0
- package/dist/telegram-api.d.ts +28 -0
- package/dist/telegram-api.js +147 -0
- package/dist/telegram-api.js.map +1 -0
- package/dist/watch-registry.d.ts +9 -0
- package/dist/watch-registry.js +272 -0
- package/dist/watch-registry.js.map +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +548 -0
- package/dist/worker.js.map +1 -0
- package/package.json +7 -3
- package/src/acp-bridge.ts +0 -1273
- package/src/adapter.ts +0 -129
- package/src/command-registry.ts +0 -482
- package/src/commands.ts +0 -346
- package/src/constants.ts +0 -51
- package/src/escalation.ts +0 -421
- package/src/formatters.ts +0 -148
- package/src/manifest.ts +0 -246
- package/src/media-pipeline.ts +0 -234
- package/src/telegram-api.ts +0 -202
- package/src/watch-registry.ts +0 -369
- package/src/worker.ts +0 -783
- package/tests/acp-bridge.test.ts +0 -314
- package/tests/command-registry.test.ts +0 -283
- package/tests/commands.test.ts +0 -213
- package/tests/escalation.test.ts +0 -550
- package/tests/formatters.test.ts +0 -185
- package/tests/media-pipeline.test.ts +0 -324
- package/tests/telegram-api.test.ts +0 -108
- package/tests/watch-registry.test.ts +0 -404
- package/tsconfig.json +0 -16
- /package/{src/index.ts â dist/index.d.ts} +0 -0
package/src/escalation.ts
DELETED
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
import type { PluginContext } from "@paperclipai/plugin-sdk";
|
|
2
|
-
import { sendMessage, editMessage, escapeMarkdownV2, truncateAtWord } from "./telegram-api.js";
|
|
3
|
-
|
|
4
|
-
export type EscalationReason =
|
|
5
|
-
| "low_confidence"
|
|
6
|
-
| "explicit_request"
|
|
7
|
-
| "policy_violation"
|
|
8
|
-
| "unknown_intent";
|
|
9
|
-
|
|
10
|
-
export type EscalationEvent = {
|
|
11
|
-
escalationId: string;
|
|
12
|
-
agentId: string;
|
|
13
|
-
companyId: string;
|
|
14
|
-
reason: EscalationReason;
|
|
15
|
-
context: {
|
|
16
|
-
conversationHistory: Array<{ role: string; text: string }>;
|
|
17
|
-
agentReasoning: string;
|
|
18
|
-
suggestedActions: string[];
|
|
19
|
-
suggestedReply?: string;
|
|
20
|
-
confidenceScore?: number;
|
|
21
|
-
};
|
|
22
|
-
timeout: {
|
|
23
|
-
durationMs: number;
|
|
24
|
-
defaultAction: "defer" | "auto_reply" | "close";
|
|
25
|
-
};
|
|
26
|
-
originChatId?: string;
|
|
27
|
-
originThreadId?: string;
|
|
28
|
-
originMessageId?: string;
|
|
29
|
-
// Transport info for routing replies back
|
|
30
|
-
transport?: "native" | "acp";
|
|
31
|
-
sessionId?: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type EscalationResponse = {
|
|
35
|
-
escalationId: string;
|
|
36
|
-
responderId: string;
|
|
37
|
-
responseText: string;
|
|
38
|
-
action: "reply_to_customer" | "override_suggested" | "dismiss";
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
type StoredEscalation = {
|
|
42
|
-
escalationId: string;
|
|
43
|
-
agentId: string;
|
|
44
|
-
companyId: string;
|
|
45
|
-
reason: EscalationReason;
|
|
46
|
-
agentReasoning: string;
|
|
47
|
-
suggestedReply?: string;
|
|
48
|
-
suggestedActions: string[];
|
|
49
|
-
confidenceScore?: number;
|
|
50
|
-
originChatId?: string;
|
|
51
|
-
originThreadId?: string;
|
|
52
|
-
originMessageId?: string;
|
|
53
|
-
escalationChatId: string;
|
|
54
|
-
escalationMessageId: string;
|
|
55
|
-
status: "pending" | "resolved" | "timed_out";
|
|
56
|
-
createdAt: string;
|
|
57
|
-
timeoutAt: string;
|
|
58
|
-
defaultAction: "defer" | "auto_reply" | "close";
|
|
59
|
-
transport?: "native" | "acp";
|
|
60
|
-
sessionId?: string;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const REASON_LABELS: Record<EscalationReason, string> = {
|
|
64
|
-
low_confidence: "Low Confidence",
|
|
65
|
-
explicit_request: "User Requested Human",
|
|
66
|
-
policy_violation: "Policy Violation",
|
|
67
|
-
unknown_intent: "Unknown Intent",
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
function esc(s: string): string {
|
|
71
|
-
return escapeMarkdownV2(s);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export class EscalationManager {
|
|
75
|
-
async create(
|
|
76
|
-
ctx: PluginContext,
|
|
77
|
-
token: string,
|
|
78
|
-
event: EscalationEvent,
|
|
79
|
-
escalationChatId: string,
|
|
80
|
-
): Promise<void> {
|
|
81
|
-
const reasonLabel = REASON_LABELS[event.reason] ?? event.reason;
|
|
82
|
-
const confidence = event.context.confidenceScore != null
|
|
83
|
-
? ` \\(${esc(String(Math.round(event.context.confidenceScore * 100)))}%\\)`
|
|
84
|
-
: "";
|
|
85
|
-
|
|
86
|
-
const lines: string[] = [
|
|
87
|
-
`${esc("\u26a0\ufe0f")} *Escalation* \\- ${esc(reasonLabel)}${confidence}`,
|
|
88
|
-
"",
|
|
89
|
-
`*Agent:* ${esc(event.agentId)}`,
|
|
90
|
-
`*Reason:* ${esc(event.context.agentReasoning ? truncateAtWord(event.context.agentReasoning, 500) : "No details provided")}`,
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
if (event.context.suggestedActions.length > 0) {
|
|
94
|
-
lines.push("");
|
|
95
|
-
lines.push("*Suggested actions:*");
|
|
96
|
-
for (const action of event.context.suggestedActions.slice(0, 5)) {
|
|
97
|
-
lines.push(` ${esc("-")} ${esc(action)}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (event.context.suggestedReply) {
|
|
102
|
-
lines.push("");
|
|
103
|
-
lines.push("*Suggested reply:*");
|
|
104
|
-
lines.push(`${esc(">")} ${esc(truncateAtWord(event.context.suggestedReply, 300))}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
lines.push("");
|
|
108
|
-
lines.push(`ID: \`${esc(event.escalationId)}\``);
|
|
109
|
-
|
|
110
|
-
const buttons = [];
|
|
111
|
-
if (event.context.suggestedReply) {
|
|
112
|
-
buttons.push([
|
|
113
|
-
{ text: "Send Suggested Reply", callback_data: `esc_suggested_${event.escalationId}` },
|
|
114
|
-
]);
|
|
115
|
-
}
|
|
116
|
-
buttons.push([
|
|
117
|
-
{ text: "Reply", callback_data: `esc_reply_${event.escalationId}` },
|
|
118
|
-
{ text: "Override", callback_data: `esc_override_${event.escalationId}` },
|
|
119
|
-
{ text: "Dismiss", callback_data: `esc_dismiss_${event.escalationId}` },
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
const messageId = await sendMessage(ctx, token, escalationChatId, lines.join("\n"), {
|
|
123
|
-
parseMode: "MarkdownV2",
|
|
124
|
-
inlineKeyboard: buttons,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (!messageId) {
|
|
128
|
-
ctx.logger.error("Failed to send escalation message", { escalationId: event.escalationId });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const timeoutAt = new Date(Date.now() + event.timeout.durationMs).toISOString();
|
|
133
|
-
|
|
134
|
-
const stored: StoredEscalation = {
|
|
135
|
-
escalationId: event.escalationId,
|
|
136
|
-
agentId: event.agentId,
|
|
137
|
-
companyId: event.companyId,
|
|
138
|
-
reason: event.reason,
|
|
139
|
-
agentReasoning: event.context.agentReasoning,
|
|
140
|
-
suggestedReply: event.context.suggestedReply,
|
|
141
|
-
suggestedActions: event.context.suggestedActions,
|
|
142
|
-
confidenceScore: event.context.confidenceScore,
|
|
143
|
-
originChatId: event.originChatId,
|
|
144
|
-
originThreadId: event.originThreadId,
|
|
145
|
-
originMessageId: event.originMessageId,
|
|
146
|
-
escalationChatId,
|
|
147
|
-
escalationMessageId: String(messageId),
|
|
148
|
-
status: "pending",
|
|
149
|
-
createdAt: new Date().toISOString(),
|
|
150
|
-
timeoutAt,
|
|
151
|
-
defaultAction: event.timeout.defaultAction,
|
|
152
|
-
transport: event.transport,
|
|
153
|
-
sessionId: event.sessionId,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
await ctx.state.set(
|
|
157
|
-
{ scopeKind: "instance", stateKey: `escalation_${event.escalationId}` },
|
|
158
|
-
stored,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
// Map the escalation message back so replies can be routed
|
|
162
|
-
await ctx.state.set(
|
|
163
|
-
{ scopeKind: "instance", stateKey: `msg_${escalationChatId}_${messageId}` },
|
|
164
|
-
{
|
|
165
|
-
entityId: event.escalationId,
|
|
166
|
-
entityType: "escalation",
|
|
167
|
-
companyId: event.companyId,
|
|
168
|
-
eventType: "escalation.created",
|
|
169
|
-
},
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// Track pending escalation IDs for timeout checks
|
|
173
|
-
const pendingIds = (await ctx.state.get({
|
|
174
|
-
scopeKind: "instance",
|
|
175
|
-
stateKey: "escalation_pending_ids",
|
|
176
|
-
}) as string[] | null) ?? [];
|
|
177
|
-
pendingIds.push(event.escalationId);
|
|
178
|
-
await ctx.state.set(
|
|
179
|
-
{ scopeKind: "instance", stateKey: "escalation_pending_ids" },
|
|
180
|
-
pendingIds,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
ctx.logger.info("Escalation created", {
|
|
184
|
-
escalationId: event.escalationId,
|
|
185
|
-
reason: event.reason,
|
|
186
|
-
timeoutAt,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async handleCallback(
|
|
191
|
-
ctx: PluginContext,
|
|
192
|
-
token: string,
|
|
193
|
-
action: string,
|
|
194
|
-
escalationId: string,
|
|
195
|
-
actor: string,
|
|
196
|
-
callbackQueryId: string,
|
|
197
|
-
chatId: string | null,
|
|
198
|
-
messageId: number | undefined,
|
|
199
|
-
): Promise<void> {
|
|
200
|
-
const stored = await ctx.state.get({
|
|
201
|
-
scopeKind: "instance",
|
|
202
|
-
stateKey: `escalation_${escalationId}`,
|
|
203
|
-
}) as StoredEscalation | null;
|
|
204
|
-
|
|
205
|
-
if (!stored || stored.status !== "pending") {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
switch (action) {
|
|
210
|
-
case "suggested": {
|
|
211
|
-
if (!stored.suggestedReply) break;
|
|
212
|
-
await this.resolve(ctx, token, stored, {
|
|
213
|
-
escalationId,
|
|
214
|
-
responderId: `telegram:${actor}`,
|
|
215
|
-
responseText: stored.suggestedReply,
|
|
216
|
-
action: "reply_to_customer",
|
|
217
|
-
});
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
case "reply": {
|
|
221
|
-
if (chatId && messageId) {
|
|
222
|
-
await editMessage(
|
|
223
|
-
ctx,
|
|
224
|
-
token,
|
|
225
|
-
chatId,
|
|
226
|
-
messageId,
|
|
227
|
-
`${esc("\u26a0\ufe0f")} *Escalation* \\- *Awaiting your reply*\n\n${esc("Reply to this message with your response to the customer.")}`,
|
|
228
|
-
{ parseMode: "MarkdownV2" },
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
case "dismiss": {
|
|
234
|
-
await this.resolve(ctx, token, stored, {
|
|
235
|
-
escalationId,
|
|
236
|
-
responderId: `telegram:${actor}`,
|
|
237
|
-
responseText: "",
|
|
238
|
-
action: "dismiss",
|
|
239
|
-
});
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
case "override": {
|
|
243
|
-
if (chatId && messageId) {
|
|
244
|
-
await editMessage(
|
|
245
|
-
ctx,
|
|
246
|
-
token,
|
|
247
|
-
chatId,
|
|
248
|
-
messageId,
|
|
249
|
-
`${esc("\u26a0\ufe0f")} *Escalation* \\- *Override mode*\n\n${esc("Reply to this message with your custom response.")}`,
|
|
250
|
-
{ parseMode: "MarkdownV2" },
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async respond(
|
|
259
|
-
ctx: PluginContext,
|
|
260
|
-
token: string,
|
|
261
|
-
escalationId: string,
|
|
262
|
-
response: EscalationResponse,
|
|
263
|
-
): Promise<void> {
|
|
264
|
-
const stored = await ctx.state.get({
|
|
265
|
-
scopeKind: "instance",
|
|
266
|
-
stateKey: `escalation_${escalationId}`,
|
|
267
|
-
}) as StoredEscalation | null;
|
|
268
|
-
|
|
269
|
-
if (!stored || stored.status !== "pending") {
|
|
270
|
-
ctx.logger.warn("Escalation respond called for non-pending escalation", { escalationId });
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
await this.resolve(ctx, token, stored, response);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private async resolve(
|
|
278
|
-
ctx: PluginContext,
|
|
279
|
-
token: string,
|
|
280
|
-
stored: StoredEscalation,
|
|
281
|
-
response: EscalationResponse,
|
|
282
|
-
): Promise<void> {
|
|
283
|
-
stored.status = "resolved";
|
|
284
|
-
await ctx.state.set(
|
|
285
|
-
{ scopeKind: "instance", stateKey: `escalation_${stored.escalationId}` },
|
|
286
|
-
stored,
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
await this.removePending(ctx, stored.escalationId);
|
|
290
|
-
|
|
291
|
-
const statusLabel = response.action === "dismiss" ? "Dismissed" : "Resolved";
|
|
292
|
-
await editMessage(
|
|
293
|
-
ctx,
|
|
294
|
-
token,
|
|
295
|
-
stored.escalationChatId,
|
|
296
|
-
Number(stored.escalationMessageId),
|
|
297
|
-
`${esc("\u2705")} *Escalation ${statusLabel}* by ${esc(response.responderId)}\n\nID: \`${esc(stored.escalationId)}\``,
|
|
298
|
-
{ parseMode: "MarkdownV2" },
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
// Route reply back via the correct transport
|
|
302
|
-
if (response.action === "reply_to_customer" && response.responseText) {
|
|
303
|
-
if (stored.transport === "native" && stored.sessionId) {
|
|
304
|
-
// Route back through native agent session
|
|
305
|
-
try {
|
|
306
|
-
await ctx.agents.sessions.sendMessage(stored.sessionId, stored.companyId, {
|
|
307
|
-
prompt: `[Human escalation response] ${response.responseText}`,
|
|
308
|
-
reason: "escalation_reply",
|
|
309
|
-
});
|
|
310
|
-
} catch (err) {
|
|
311
|
-
ctx.logger.error("Failed to route escalation reply to native session", { error: String(err) });
|
|
312
|
-
}
|
|
313
|
-
} else if (stored.transport === "acp" && stored.sessionId) {
|
|
314
|
-
// Route back via ACP event
|
|
315
|
-
ctx.events.emit("acp-spawn", stored.companyId, {
|
|
316
|
-
type: "message",
|
|
317
|
-
sessionId: stored.sessionId,
|
|
318
|
-
text: `[Human escalation response] ${response.responseText}`,
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Also send to the originating Telegram chat if available
|
|
323
|
-
if (stored.originChatId) {
|
|
324
|
-
await sendMessage(ctx, token, stored.originChatId, esc(response.responseText), {
|
|
325
|
-
parseMode: "MarkdownV2",
|
|
326
|
-
messageThreadId: stored.originThreadId ? Number(stored.originThreadId) : undefined,
|
|
327
|
-
replyToMessageId: stored.originMessageId ? Number(stored.originMessageId) : undefined,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Emit resolution event - companyId is SECOND arg
|
|
333
|
-
ctx.events.emit("escalation.resolved", stored.companyId, {
|
|
334
|
-
escalationId: stored.escalationId,
|
|
335
|
-
agentId: stored.agentId,
|
|
336
|
-
responderId: response.responderId,
|
|
337
|
-
responseText: response.responseText,
|
|
338
|
-
action: response.action,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
ctx.logger.info("Escalation resolved", {
|
|
342
|
-
escalationId: stored.escalationId,
|
|
343
|
-
action: response.action,
|
|
344
|
-
responderId: response.responderId,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async checkTimeouts(ctx: PluginContext, token: string): Promise<void> {
|
|
349
|
-
const pendingIds = (await ctx.state.get({
|
|
350
|
-
scopeKind: "instance",
|
|
351
|
-
stateKey: "escalation_pending_ids",
|
|
352
|
-
}) as string[] | null) ?? [];
|
|
353
|
-
|
|
354
|
-
if (pendingIds.length === 0) return;
|
|
355
|
-
|
|
356
|
-
const now = Date.now();
|
|
357
|
-
|
|
358
|
-
for (const escalationId of pendingIds) {
|
|
359
|
-
const stored = await ctx.state.get({
|
|
360
|
-
scopeKind: "instance",
|
|
361
|
-
stateKey: `escalation_${escalationId}`,
|
|
362
|
-
}) as StoredEscalation | null;
|
|
363
|
-
|
|
364
|
-
if (!stored || stored.status !== "pending") {
|
|
365
|
-
await this.removePending(ctx, escalationId);
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const timeoutAt = new Date(stored.timeoutAt).getTime();
|
|
370
|
-
if (now < timeoutAt) continue;
|
|
371
|
-
|
|
372
|
-
ctx.logger.info("Escalation timed out", { escalationId, defaultAction: stored.defaultAction });
|
|
373
|
-
|
|
374
|
-
stored.status = "timed_out";
|
|
375
|
-
await ctx.state.set(
|
|
376
|
-
{ scopeKind: "instance", stateKey: `escalation_${escalationId}` },
|
|
377
|
-
stored,
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
await this.removePending(ctx, escalationId);
|
|
381
|
-
|
|
382
|
-
await editMessage(
|
|
383
|
-
ctx,
|
|
384
|
-
token,
|
|
385
|
-
stored.escalationChatId,
|
|
386
|
-
Number(stored.escalationMessageId),
|
|
387
|
-
`${esc("\u23f0")} *Escalation Timed Out*\n\nDefault action: ${esc(stored.defaultAction)}\nID: \`${esc(escalationId)}\``,
|
|
388
|
-
{ parseMode: "MarkdownV2" },
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
// Emit timeout event - companyId is SECOND arg
|
|
392
|
-
ctx.events.emit("escalation.timed_out", stored.companyId, {
|
|
393
|
-
escalationId,
|
|
394
|
-
agentId: stored.agentId,
|
|
395
|
-
defaultAction: stored.defaultAction,
|
|
396
|
-
suggestedReply: stored.suggestedReply,
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (stored.defaultAction === "auto_reply" && stored.suggestedReply && stored.originChatId) {
|
|
400
|
-
await sendMessage(ctx, token, stored.originChatId, esc(stored.suggestedReply), {
|
|
401
|
-
parseMode: "MarkdownV2",
|
|
402
|
-
messageThreadId: stored.originThreadId ? Number(stored.originThreadId) : undefined,
|
|
403
|
-
replyToMessageId: stored.originMessageId ? Number(stored.originMessageId) : undefined,
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
private async removePending(ctx: PluginContext, escalationId: string): Promise<void> {
|
|
410
|
-
const pendingIds = (await ctx.state.get({
|
|
411
|
-
scopeKind: "instance",
|
|
412
|
-
stateKey: "escalation_pending_ids",
|
|
413
|
-
}) as string[] | null) ?? [];
|
|
414
|
-
|
|
415
|
-
const updated = pendingIds.filter((id) => id !== escalationId);
|
|
416
|
-
await ctx.state.set(
|
|
417
|
-
{ scopeKind: "instance", stateKey: "escalation_pending_ids" },
|
|
418
|
-
updated,
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
}
|
package/src/formatters.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import type { PluginEvent } from "@paperclipai/plugin-sdk";
|
|
2
|
-
import { escapeMarkdownV2, truncateAtWord } from "./telegram-api.js";
|
|
3
|
-
import type { SendMessageOptions } from "./telegram-api.js";
|
|
4
|
-
|
|
5
|
-
type Payload = Record<string, unknown>;
|
|
6
|
-
|
|
7
|
-
type FormattedMessage = {
|
|
8
|
-
text: string;
|
|
9
|
-
options: SendMessageOptions;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function esc(s: string): string {
|
|
13
|
-
return escapeMarkdownV2(s);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function bold(s: string): string {
|
|
17
|
-
return `*${esc(s)}*`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function code(s: string): string {
|
|
21
|
-
return `\`${esc(s)}\``;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function formatIssueCreated(event: PluginEvent): FormattedMessage {
|
|
25
|
-
const p = event.payload as Payload;
|
|
26
|
-
const identifier = String(p.identifier ?? event.entityId);
|
|
27
|
-
const title = String(p.title ?? "Untitled");
|
|
28
|
-
const status = p.status ? String(p.status) : null;
|
|
29
|
-
const priority = p.priority ? String(p.priority) : null;
|
|
30
|
-
const assigneeName = p.assigneeName ? String(p.assigneeName) : null;
|
|
31
|
-
const projectName = p.projectName ? String(p.projectName) : null;
|
|
32
|
-
|
|
33
|
-
const lines: string[] = [
|
|
34
|
-
`${esc("đ")} ${bold("Issue Created")}: ${bold(identifier)}`,
|
|
35
|
-
bold(title),
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const meta: string[] = [];
|
|
39
|
-
if (status) meta.push(`Status: ${code(status)}`);
|
|
40
|
-
if (priority) meta.push(`Priority: ${code(priority)}`);
|
|
41
|
-
if (assigneeName) meta.push(`Assignee: ${esc(assigneeName)}`);
|
|
42
|
-
if (projectName) meta.push(`Project: ${esc(projectName)}`);
|
|
43
|
-
if (meta.length > 0) lines.push(meta.join(" \\| "));
|
|
44
|
-
|
|
45
|
-
if (p.description) {
|
|
46
|
-
const desc = truncateAtWord(String(p.description), 200);
|
|
47
|
-
lines.push(`\n${esc(">")} ${esc(desc)}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
text: lines.join("\n"),
|
|
52
|
-
options: { parseMode: "MarkdownV2" },
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function formatIssueDone(event: PluginEvent): FormattedMessage {
|
|
57
|
-
const p = event.payload as Payload;
|
|
58
|
-
const identifier = String(p.identifier ?? event.entityId);
|
|
59
|
-
const title = String(p.title ?? "");
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
text: [
|
|
63
|
-
`${esc("â
")} ${bold("Issue Completed")}: ${bold(identifier)}`,
|
|
64
|
-
`${bold(title)} ${esc("is now done.")}`,
|
|
65
|
-
].join("\n"),
|
|
66
|
-
options: { parseMode: "MarkdownV2" },
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function formatApprovalCreated(event: PluginEvent): FormattedMessage {
|
|
71
|
-
const p = event.payload as Payload;
|
|
72
|
-
const approvalType = String(p.type ?? "unknown");
|
|
73
|
-
const approvalId = String(p.approvalId ?? event.entityId);
|
|
74
|
-
const title = String(p.title ?? "Approval Requested");
|
|
75
|
-
const description = p.description ? String(p.description) : null;
|
|
76
|
-
const agentName = p.agentName ? String(p.agentName) : null;
|
|
77
|
-
|
|
78
|
-
const lines: string[] = [
|
|
79
|
-
`${esc("đ")} ${bold("Approval Requested")}`,
|
|
80
|
-
bold(title),
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
if (agentName) lines.push(`Agent: ${esc(agentName)} \\| Type: ${code(approvalType)}`);
|
|
84
|
-
if (description) lines.push(`\n${esc(truncateAtWord(description, 300))}`);
|
|
85
|
-
|
|
86
|
-
// Add linked issues if present
|
|
87
|
-
const linkedIssues = Array.isArray(p.linkedIssues) ? p.linkedIssues as Array<Payload> : [];
|
|
88
|
-
if (linkedIssues.length > 0) {
|
|
89
|
-
lines.push(`\n${bold(`Linked Issues (${String(linkedIssues.length)})`)}`);
|
|
90
|
-
for (const issue of linkedIssues.slice(0, 5)) {
|
|
91
|
-
const issueParts = [`${bold(String(issue.identifier ?? "?"))} ${esc(String(issue.title ?? ""))}`];
|
|
92
|
-
const issueMeta: string[] = [];
|
|
93
|
-
if (issue.status) issueMeta.push(String(issue.status));
|
|
94
|
-
if (issue.priority) issueMeta.push(String(issue.priority));
|
|
95
|
-
if (issue.assignee) issueMeta.push(`-> ${String(issue.assignee)}`);
|
|
96
|
-
if (issueMeta.length > 0) issueParts.push(`\\(${esc(issueMeta.join(" | "))}\\)`);
|
|
97
|
-
lines.push(issueParts.join(" "));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
text: lines.join("\n"),
|
|
103
|
-
options: {
|
|
104
|
-
parseMode: "MarkdownV2",
|
|
105
|
-
inlineKeyboard: [
|
|
106
|
-
[
|
|
107
|
-
{ text: "Approve", callback_data: `approve_${approvalId}` },
|
|
108
|
-
{ text: "Reject", callback_data: `reject_${approvalId}` },
|
|
109
|
-
],
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function formatAgentError(event: PluginEvent): FormattedMessage {
|
|
116
|
-
const p = event.payload as Payload;
|
|
117
|
-
const agentName = String(p.agentName ?? p.name ?? event.entityId);
|
|
118
|
-
const errorMessage = String(p.error ?? p.message ?? "Unknown error");
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
text: [
|
|
122
|
-
`${esc("â")} ${bold("Agent Error")}`,
|
|
123
|
-
`${bold(agentName)} ${esc("encountered an error")}`,
|
|
124
|
-
`\n${code(truncateAtWord(errorMessage, 500))}`,
|
|
125
|
-
].join("\n"),
|
|
126
|
-
options: { parseMode: "MarkdownV2" },
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function formatAgentRunStarted(event: PluginEvent): FormattedMessage {
|
|
131
|
-
const p = event.payload as Payload;
|
|
132
|
-
const agentName = String(p.agentName ?? event.entityId);
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
text: `${esc("âļī¸")} ${bold(agentName)} ${esc("started a new run")}`,
|
|
136
|
-
options: { parseMode: "MarkdownV2", disableNotification: true },
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function formatAgentRunFinished(event: PluginEvent): FormattedMessage {
|
|
141
|
-
const p = event.payload as Payload;
|
|
142
|
-
const agentName = String(p.agentName ?? event.entityId);
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
text: `${esc("âšī¸")} ${bold(agentName)} ${esc("completed successfully")}`,
|
|
146
|
-
options: { parseMode: "MarkdownV2", disableNotification: true },
|
|
147
|
-
};
|
|
148
|
-
}
|