@odience-network/paperclip-plugin-telegram-enhanced 0.2.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/dist/acp-bridge.d.ts +35 -0
  4. package/dist/acp-bridge.js +891 -0
  5. package/dist/acp-bridge.js.map +1 -0
  6. package/dist/adapter.d.ts +35 -0
  7. package/dist/adapter.js +75 -0
  8. package/dist/adapter.js.map +1 -0
  9. package/dist/agent-labels.d.ts +12 -0
  10. package/dist/agent-labels.js +96 -0
  11. package/dist/agent-labels.js.map +1 -0
  12. package/dist/allowlist.d.ts +27 -0
  13. package/dist/allowlist.js +34 -0
  14. package/dist/allowlist.js.map +1 -0
  15. package/dist/approval-routing.d.ts +2 -0
  16. package/dist/approval-routing.js +7 -0
  17. package/dist/approval-routing.js.map +1 -0
  18. package/dist/command-registry.d.ts +3 -0
  19. package/dist/command-registry.js +268 -0
  20. package/dist/command-registry.js.map +1 -0
  21. package/dist/commands.d.ts +11 -0
  22. package/dist/commands.js +516 -0
  23. package/dist/commands.js.map +1 -0
  24. package/dist/constants.d.ts +76 -0
  25. package/dist/constants.js +71 -0
  26. package/dist/constants.js.map +1 -0
  27. package/dist/escalation.d.ts +42 -0
  28. package/dist/escalation.js +252 -0
  29. package/dist/escalation.js.map +1 -0
  30. package/dist/file-routing.d.ts +51 -0
  31. package/dist/file-routing.js +212 -0
  32. package/dist/file-routing.js.map +1 -0
  33. package/dist/formatters.d.ts +31 -0
  34. package/dist/formatters.js +336 -0
  35. package/dist/formatters.js.map +1 -0
  36. package/dist/index.d.ts +6 -0
  37. package/dist/index.js +4 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/interaction-delivery.d.ts +90 -0
  40. package/dist/interaction-delivery.js +142 -0
  41. package/dist/interaction-delivery.js.map +1 -0
  42. package/dist/manifest.d.ts +3 -0
  43. package/dist/manifest.js +111 -0
  44. package/dist/manifest.js.map +1 -0
  45. package/dist/media-pipeline.d.ts +47 -0
  46. package/dist/media-pipeline.js +162 -0
  47. package/dist/media-pipeline.js.map +1 -0
  48. package/dist/notification-filters.d.ts +23 -0
  49. package/dist/notification-filters.js +93 -0
  50. package/dist/notification-filters.js.map +1 -0
  51. package/dist/paperclip-api.d.ts +25 -0
  52. package/dist/paperclip-api.js +69 -0
  53. package/dist/paperclip-api.js.map +1 -0
  54. package/dist/polling-offset.d.ts +22 -0
  55. package/dist/polling-offset.js +68 -0
  56. package/dist/polling-offset.js.map +1 -0
  57. package/dist/secret-ref-validation.d.ts +7 -0
  58. package/dist/secret-ref-validation.js +49 -0
  59. package/dist/secret-ref-validation.js.map +1 -0
  60. package/dist/telegram-api.d.ts +40 -0
  61. package/dist/telegram-api.js +251 -0
  62. package/dist/telegram-api.js.map +1 -0
  63. package/dist/topic-projects.d.ts +2 -0
  64. package/dist/topic-projects.js +45 -0
  65. package/dist/topic-projects.js.map +1 -0
  66. package/dist/ui/index.d.ts +2 -0
  67. package/dist/ui/index.js +1446 -0
  68. package/dist/ui/index.js.map +1 -0
  69. package/dist/watch-registry.d.ts +9 -0
  70. package/dist/watch-registry.js +272 -0
  71. package/dist/watch-registry.js.map +1 -0
  72. package/dist/worker.d.ts +162 -0
  73. package/dist/worker.js +1520 -0
  74. package/dist/worker.js.map +1 -0
  75. package/package.json +59 -0
@@ -0,0 +1,71 @@
1
+ export const PLUGIN_ID = "paperclip-plugin-telegram-enhanced";
2
+ export const PLUGIN_VERSION = "0.2.0";
3
+ export const MAX_AGENTS_PER_THREAD = 5;
4
+ export const DEFAULT_CONFIG = {
5
+ telegramBotTokenRef: "",
6
+ defaultChatId: "",
7
+ approvalsChatId: "",
8
+ approvalsTopicId: "",
9
+ errorsChatId: "",
10
+ errorsTopicId: "",
11
+ digestChatId: "",
12
+ digestTopicId: "",
13
+ paperclipBaseUrl: "http://localhost:3100",
14
+ paperclipBoardApiTokenRef: "",
15
+ paperclipPublicUrl: "",
16
+ notifyOnIssueCreated: true,
17
+ notifyOnIssueDone: true,
18
+ notifyOnIssueAssigned: false,
19
+ onlyNotifyIfAssignedTo: "",
20
+ notifyOnApprovalCreated: true,
21
+ onlyNotifyBoardApprovals: false,
22
+ notifyOnAgentError: true,
23
+ notifyOnAgentRunStarted: false,
24
+ notifyOnAgentRunFinished: false,
25
+ enableCommands: true,
26
+ enableInbound: true,
27
+ allowedTelegramUserIds: [],
28
+ allowedTelegramChatIds: [],
29
+ // Project-key file routing (ant013 TEL-23): route outbound markdown documents
30
+ // to a Telegram chat/topic by Paperclip project key.
31
+ fileRoutes: [],
32
+ // TEL-23 (ant013): route run-lifecycle / "ops" chatter to dedicated ops chats
33
+ // per company, keeping the primary chat reserved for important signals.
34
+ opsRoutes: [],
35
+ digestMode: "off",
36
+ dailyDigestTime: "09:00",
37
+ bidailySecondTime: "17:00",
38
+ tridailyTimes: "07:00,13:00,19:00",
39
+ topicRouting: false,
40
+ maxAgentsPerThread: MAX_AGENTS_PER_THREAD,
41
+ escalationChatId: "",
42
+ escalationTimeoutMs: 900000,
43
+ escalationDefaultAction: "defer",
44
+ escalationHoldMessage: "Let me check on that - I'll get back to you shortly.",
45
+ // Phase 3: Media Pipeline
46
+ briefAgentId: "",
47
+ briefAgentChatIds: [],
48
+ transcriptionApiKeyRef: "",
49
+ // Phase 5: Proactive Suggestions
50
+ maxSuggestionsPerHourPerCompany: 10,
51
+ watchDeduplicationWindowMs: 86400000, // 24h
52
+ };
53
+ export const AGENT_ERROR_DEDUPLICATION_WINDOW_MS = 30 * 60 * 1000;
54
+ export const MAX_CONVERSATION_TURNS = 50;
55
+ export const DEFAULT_CONVERSATION_TURNS = 10;
56
+ export const METRIC_NAMES = {
57
+ sent: "telegram_notifications_sent",
58
+ failed: "telegram_notification_failures",
59
+ commandsHandled: "telegram_commands_handled",
60
+ inboundRouted: "telegram_inbound_routed",
61
+ escalationsCreated: "telegram_escalations_created",
62
+ escalationsResolved: "telegram_escalations_resolved",
63
+ escalationsTimedOut: "telegram_escalations_timed_out",
64
+ mediaProcessed: "telegram_media_processed",
65
+ commandsExecuted: "telegram_custom_commands_executed",
66
+ suggestionsEmitted: "telegram_suggestions_emitted",
67
+ };
68
+ // Cross-plugin ACP event names
69
+ export const ACP_SPAWN_EVENT = "acp-spawn";
70
+ export const ACP_OUTPUT_EVENT = "plugin.paperclip-plugin-acp.output";
71
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG,oCAAoC,CAAC;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AACtC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,mBAAmB,EAAE,EAAE;IACvB,aAAa,EAAE,EAAE;IACjB,eAAe,EAAE,EAAE;IACnB,gBAAgB,EAAE,EAAE;IACpB,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,EAAE;IACjB,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,EAAE;IACjB,gBAAgB,EAAE,uBAAuB;IACzC,yBAAyB,EAAE,EAAE;IAC7B,kBAAkB,EAAE,EAAE;IACtB,oBAAoB,EAAE,IAAI;IAC1B,iBAAiB,EAAE,IAAI;IACvB,qBAAqB,EAAE,KAAK;IAC5B,sBAAsB,EAAE,EAAE;IAC1B,uBAAuB,EAAE,IAAI;IAC7B,wBAAwB,EAAE,KAAK;IAC/B,kBAAkB,EAAE,IAAI;IACxB,uBAAuB,EAAE,KAAK;IAC9B,wBAAwB,EAAE,KAAK;IAC/B,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,IAAI;IACnB,sBAAsB,EAAE,EAAc;IACtC,sBAAsB,EAAE,EAAc;IACtC,8EAA8E;IAC9E,qDAAqD;IACrD,UAAU,EAAE,EAMV;IACF,8EAA8E;IAC9E,wEAAwE;IACxE,SAAS,EAAE,EAOT;IACF,UAAU,EAAE,KAAiD;IAC7D,eAAe,EAAE,OAAO;IACxB,iBAAiB,EAAE,OAAO;IAC1B,aAAa,EAAE,mBAAmB;IAClC,YAAY,EAAE,KAAK;IACnB,kBAAkB,EAAE,qBAAqB;IACzC,gBAAgB,EAAE,EAAE;IACpB,mBAAmB,EAAE,MAAM;IAC3B,uBAAuB,EAAE,OAAO;IAChC,qBAAqB,EAAE,sDAAsD;IAC7E,0BAA0B;IAC1B,YAAY,EAAE,EAAE;IAChB,iBAAiB,EAAE,EAAc;IACjC,sBAAsB,EAAE,EAAE;IAC1B,iCAAiC;IACjC,+BAA+B,EAAE,EAAE;IACnC,0BAA0B,EAAE,QAAQ,EAAE,MAAM;CACpC,CAAC;AAEX,MAAM,CAAC,MAAM,mCAAmC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElE,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,6BAA6B;IACnC,MAAM,EAAE,gCAAgC;IACxC,eAAe,EAAE,2BAA2B;IAC5C,aAAa,EAAE,yBAAyB;IACxC,kBAAkB,EAAE,8BAA8B;IAClD,mBAAmB,EAAE,+BAA+B;IACpD,mBAAmB,EAAE,gCAAgC;IACrD,cAAc,EAAE,0BAA0B;IAC1C,gBAAgB,EAAE,mCAAmC;IACrD,kBAAkB,EAAE,8BAA8B;CAC1C,CAAC;AAEX,+BAA+B;AAC/B,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAC3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,oCAAoC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { PluginContext } from "@paperclipai/plugin-sdk";
2
+ export type EscalationReason = "low_confidence" | "explicit_request" | "policy_violation" | "unknown_intent";
3
+ export type EscalationEvent = {
4
+ escalationId: string;
5
+ agentId: string;
6
+ agentName?: string;
7
+ companyId: string;
8
+ reason: EscalationReason;
9
+ context: {
10
+ conversationHistory: Array<{
11
+ role: string;
12
+ text: string;
13
+ }>;
14
+ agentReasoning: string;
15
+ suggestedActions: string[];
16
+ suggestedReply?: string;
17
+ confidenceScore?: number;
18
+ };
19
+ timeout: {
20
+ durationMs: number;
21
+ defaultAction: "defer" | "auto_reply" | "close";
22
+ };
23
+ originChatId?: string;
24
+ originThreadId?: string;
25
+ originMessageId?: string;
26
+ transport?: "native" | "acp";
27
+ sessionId?: string;
28
+ };
29
+ export type EscalationResponse = {
30
+ escalationId: string;
31
+ responderId: string;
32
+ responseText: string;
33
+ action: "reply_to_customer" | "override_suggested" | "dismiss";
34
+ };
35
+ export declare class EscalationManager {
36
+ create(ctx: PluginContext, token: string, event: EscalationEvent, escalationChatId: string): Promise<void>;
37
+ handleCallback(ctx: PluginContext, token: string, action: string, escalationId: string, actor: string, callbackQueryId: string, chatId: string | null, messageId: number | undefined): Promise<void>;
38
+ respond(ctx: PluginContext, token: string, escalationId: string, response: EscalationResponse): Promise<void>;
39
+ private resolve;
40
+ checkTimeouts(ctx: PluginContext, token: string): Promise<void>;
41
+ private removePending;
42
+ }
@@ -0,0 +1,252 @@
1
+ import { sendMessage, editMessage, escapeMarkdownV2, truncateAtWord } from "./telegram-api.js";
2
+ import { wakeAgentWithIssue } from "./acp-bridge.js";
3
+ import { displayNameFromFields, resolveAgentDisplayName } from "./agent-labels.js";
4
+ const REASON_LABELS = {
5
+ low_confidence: "Low Confidence",
6
+ explicit_request: "User Requested Human",
7
+ policy_violation: "Policy Violation",
8
+ unknown_intent: "Unknown Intent",
9
+ };
10
+ function esc(s) {
11
+ return escapeMarkdownV2(s);
12
+ }
13
+ export class EscalationManager {
14
+ async create(ctx, token, event, escalationChatId) {
15
+ const reasonLabel = REASON_LABELS[event.reason] ?? event.reason;
16
+ const confidence = event.context.confidenceScore != null
17
+ ? ` \\(${esc(String(Math.round(event.context.confidenceScore * 100)))}%\\)`
18
+ : "";
19
+ const agentLabel = displayNameFromFields(event.agentName)
20
+ ?? await resolveAgentDisplayName(ctx, event.companyId, event.agentId)
21
+ ?? event.agentId;
22
+ const lines = [
23
+ `${esc("\u26a0\ufe0f")} *Escalation*`,
24
+ `${esc("Reason:")} ${esc(reasonLabel)}${confidence}`,
25
+ "",
26
+ `*Agent:* ${esc(agentLabel)}`,
27
+ `*Reasoning:* ${esc(event.context.agentReasoning ? truncateAtWord(event.context.agentReasoning, 500) : "No details provided")}`,
28
+ ];
29
+ if (event.context.suggestedActions.length > 0) {
30
+ lines.push("");
31
+ lines.push("*Suggested actions:*");
32
+ for (const action of event.context.suggestedActions.slice(0, 5)) {
33
+ lines.push(` ${esc("-")} ${esc(action)}`);
34
+ }
35
+ }
36
+ if (event.context.suggestedReply) {
37
+ lines.push("");
38
+ lines.push("*Suggested reply:*");
39
+ lines.push(`${esc(">")} ${esc(truncateAtWord(event.context.suggestedReply, 300))}`);
40
+ }
41
+ lines.push("");
42
+ lines.push(`ID: \`${esc(event.escalationId)}\``);
43
+ const buttons = [];
44
+ if (event.context.suggestedReply) {
45
+ buttons.push([
46
+ { text: "Send Suggested Reply", callback_data: `esc_suggested_${event.escalationId}` },
47
+ ]);
48
+ }
49
+ buttons.push([
50
+ { text: "Reply", callback_data: `esc_reply_${event.escalationId}` },
51
+ { text: "Override", callback_data: `esc_override_${event.escalationId}` },
52
+ { text: "Dismiss", callback_data: `esc_dismiss_${event.escalationId}` },
53
+ ]);
54
+ const messageId = await sendMessage(ctx, token, escalationChatId, lines.join("\n"), {
55
+ parseMode: "MarkdownV2",
56
+ inlineKeyboard: buttons,
57
+ });
58
+ if (!messageId) {
59
+ ctx.logger.error("Failed to send escalation message", { escalationId: event.escalationId });
60
+ return;
61
+ }
62
+ const timeoutAt = new Date(Date.now() + event.timeout.durationMs).toISOString();
63
+ const stored = {
64
+ escalationId: event.escalationId,
65
+ agentId: event.agentId,
66
+ agentName: agentLabel,
67
+ companyId: event.companyId,
68
+ reason: event.reason,
69
+ agentReasoning: event.context.agentReasoning,
70
+ suggestedReply: event.context.suggestedReply,
71
+ suggestedActions: event.context.suggestedActions,
72
+ confidenceScore: event.context.confidenceScore,
73
+ originChatId: event.originChatId,
74
+ originThreadId: event.originThreadId,
75
+ originMessageId: event.originMessageId,
76
+ escalationChatId,
77
+ escalationMessageId: String(messageId),
78
+ status: "pending",
79
+ createdAt: new Date().toISOString(),
80
+ timeoutAt,
81
+ defaultAction: event.timeout.defaultAction,
82
+ transport: event.transport,
83
+ sessionId: event.sessionId,
84
+ };
85
+ await ctx.state.set({ scopeKind: "instance", stateKey: `escalation_${event.escalationId}` }, stored);
86
+ // Map the escalation message back so replies can be routed
87
+ await ctx.state.set({ scopeKind: "instance", stateKey: `msg_${escalationChatId}_${messageId}` }, {
88
+ entityId: event.escalationId,
89
+ entityType: "escalation",
90
+ companyId: event.companyId,
91
+ eventType: "escalation.created",
92
+ });
93
+ // Track pending escalation IDs for timeout checks
94
+ const pendingIds = await ctx.state.get({
95
+ scopeKind: "instance",
96
+ stateKey: "escalation_pending_ids",
97
+ }) ?? [];
98
+ pendingIds.push(event.escalationId);
99
+ await ctx.state.set({ scopeKind: "instance", stateKey: "escalation_pending_ids" }, pendingIds);
100
+ ctx.logger.info("Escalation created", {
101
+ escalationId: event.escalationId,
102
+ reason: event.reason,
103
+ timeoutAt,
104
+ });
105
+ }
106
+ async handleCallback(ctx, token, action, escalationId, actor, callbackQueryId, chatId, messageId) {
107
+ const stored = await ctx.state.get({
108
+ scopeKind: "instance",
109
+ stateKey: `escalation_${escalationId}`,
110
+ });
111
+ if (!stored || stored.status !== "pending") {
112
+ return;
113
+ }
114
+ switch (action) {
115
+ case "suggested": {
116
+ if (!stored.suggestedReply)
117
+ break;
118
+ await this.resolve(ctx, token, stored, {
119
+ escalationId,
120
+ responderId: `telegram:${actor}`,
121
+ responseText: stored.suggestedReply,
122
+ action: "reply_to_customer",
123
+ });
124
+ break;
125
+ }
126
+ case "reply": {
127
+ if (chatId && messageId) {
128
+ await editMessage(ctx, token, chatId, messageId, `${esc("\u26a0\ufe0f")} *Escalation* \\- *Awaiting your reply*\n\n${esc("Reply to this message with your response to the customer.")}`, { parseMode: "MarkdownV2" });
129
+ }
130
+ break;
131
+ }
132
+ case "dismiss": {
133
+ await this.resolve(ctx, token, stored, {
134
+ escalationId,
135
+ responderId: `telegram:${actor}`,
136
+ responseText: "",
137
+ action: "dismiss",
138
+ });
139
+ break;
140
+ }
141
+ case "override": {
142
+ if (chatId && messageId) {
143
+ await editMessage(ctx, token, chatId, messageId, `${esc("\u26a0\ufe0f")} *Escalation* \\- *Override mode*\n\n${esc("Reply to this message with your custom response.")}`, { parseMode: "MarkdownV2" });
144
+ }
145
+ break;
146
+ }
147
+ }
148
+ }
149
+ async respond(ctx, token, escalationId, response) {
150
+ const stored = await ctx.state.get({
151
+ scopeKind: "instance",
152
+ stateKey: `escalation_${escalationId}`,
153
+ });
154
+ if (!stored || stored.status !== "pending") {
155
+ ctx.logger.warn("Escalation respond called for non-pending escalation", { escalationId });
156
+ return;
157
+ }
158
+ await this.resolve(ctx, token, stored, response);
159
+ }
160
+ async resolve(ctx, token, stored, response) {
161
+ stored.status = "resolved";
162
+ await ctx.state.set({ scopeKind: "instance", stateKey: `escalation_${stored.escalationId}` }, stored);
163
+ await this.removePending(ctx, stored.escalationId);
164
+ const statusLabel = response.action === "dismiss" ? "Dismissed" : "Resolved";
165
+ await editMessage(ctx, token, stored.escalationChatId, Number(stored.escalationMessageId), `${esc("\u2705")} *Escalation ${statusLabel}* by ${esc(response.responderId)}\n\nID: \`${esc(stored.escalationId)}\``, { parseMode: "MarkdownV2" });
166
+ // Route reply back via the correct transport
167
+ if (response.action === "reply_to_customer" && response.responseText) {
168
+ if (stored.transport === "native" && stored.agentId) {
169
+ await wakeAgentWithIssue(ctx, stored.agentId, stored.companyId, `[Human escalation response] ${response.responseText}`, "escalation_reply");
170
+ }
171
+ else if (stored.transport === "acp" && stored.sessionId) {
172
+ // Route back via ACP event
173
+ ctx.events.emit("acp-spawn", stored.companyId, {
174
+ type: "message",
175
+ sessionId: stored.sessionId,
176
+ text: `[Human escalation response] ${response.responseText}`,
177
+ });
178
+ }
179
+ // Also send to the originating Telegram chat if available
180
+ if (stored.originChatId) {
181
+ await sendMessage(ctx, token, stored.originChatId, esc(response.responseText), {
182
+ parseMode: "MarkdownV2",
183
+ messageThreadId: stored.originThreadId ? Number(stored.originThreadId) : undefined,
184
+ replyToMessageId: stored.originMessageId ? Number(stored.originMessageId) : undefined,
185
+ });
186
+ }
187
+ }
188
+ // Emit resolution event - companyId is SECOND arg
189
+ ctx.events.emit("escalation.resolved", stored.companyId, {
190
+ escalationId: stored.escalationId,
191
+ agentId: stored.agentId,
192
+ responderId: response.responderId,
193
+ responseText: response.responseText,
194
+ action: response.action,
195
+ });
196
+ ctx.logger.info("Escalation resolved", {
197
+ escalationId: stored.escalationId,
198
+ action: response.action,
199
+ responderId: response.responderId,
200
+ });
201
+ }
202
+ async checkTimeouts(ctx, token) {
203
+ const pendingIds = await ctx.state.get({
204
+ scopeKind: "instance",
205
+ stateKey: "escalation_pending_ids",
206
+ }) ?? [];
207
+ if (pendingIds.length === 0)
208
+ return;
209
+ const now = Date.now();
210
+ for (const escalationId of pendingIds) {
211
+ const stored = await ctx.state.get({
212
+ scopeKind: "instance",
213
+ stateKey: `escalation_${escalationId}`,
214
+ });
215
+ if (!stored || stored.status !== "pending") {
216
+ await this.removePending(ctx, escalationId);
217
+ continue;
218
+ }
219
+ const timeoutAt = new Date(stored.timeoutAt).getTime();
220
+ if (now < timeoutAt)
221
+ continue;
222
+ ctx.logger.info("Escalation timed out", { escalationId, defaultAction: stored.defaultAction });
223
+ stored.status = "timed_out";
224
+ await ctx.state.set({ scopeKind: "instance", stateKey: `escalation_${escalationId}` }, stored);
225
+ await this.removePending(ctx, escalationId);
226
+ await editMessage(ctx, token, stored.escalationChatId, Number(stored.escalationMessageId), `${esc("\u23f0")} *Escalation Timed Out*\n\nDefault action: ${esc(stored.defaultAction)}\nID: \`${esc(escalationId)}\``, { parseMode: "MarkdownV2" });
227
+ // Emit timeout event - companyId is SECOND arg
228
+ ctx.events.emit("escalation.timed_out", stored.companyId, {
229
+ escalationId,
230
+ agentId: stored.agentId,
231
+ defaultAction: stored.defaultAction,
232
+ suggestedReply: stored.suggestedReply,
233
+ });
234
+ if (stored.defaultAction === "auto_reply" && stored.suggestedReply && stored.originChatId) {
235
+ await sendMessage(ctx, token, stored.originChatId, esc(stored.suggestedReply), {
236
+ parseMode: "MarkdownV2",
237
+ messageThreadId: stored.originThreadId ? Number(stored.originThreadId) : undefined,
238
+ replyToMessageId: stored.originMessageId ? Number(stored.originMessageId) : undefined,
239
+ });
240
+ }
241
+ }
242
+ }
243
+ async removePending(ctx, escalationId) {
244
+ const pendingIds = await ctx.state.get({
245
+ scopeKind: "instance",
246
+ stateKey: "escalation_pending_ids",
247
+ }) ?? [];
248
+ const updated = pendingIds.filter((id) => id !== escalationId);
249
+ await ctx.state.set({ scopeKind: "instance", stateKey: "escalation_pending_ids" }, updated);
250
+ }
251
+ }
252
+ //# sourceMappingURL=escalation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escalation.js","sourceRoot":"","sources":["../src/escalation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ export type TelegramFileRoute = {
2
+ name?: unknown;
3
+ enabled?: unknown;
4
+ projectKey?: unknown;
5
+ chatId?: unknown;
6
+ topicId?: unknown;
7
+ };
8
+ export type NormalizedTelegramFileRoute = {
9
+ name: string;
10
+ projectKey: string;
11
+ chatId: string;
12
+ topicId?: number;
13
+ };
14
+ export type FileRouteValidationIssue = {
15
+ index?: number;
16
+ field: "fileRoutes" | "name" | "projectKey" | "chatId" | "topicId";
17
+ message: string;
18
+ };
19
+ export type FileRouteValidationResult = {
20
+ routes: NormalizedTelegramFileRoute[];
21
+ issues: FileRouteValidationIssue[];
22
+ duplicateProjectKeys: string[];
23
+ };
24
+ export type TelegramFileDestination = {
25
+ ok: true;
26
+ chatId: string;
27
+ topicId?: number;
28
+ source: "explicit" | "file_route" | "legacy_fallback";
29
+ routeName?: string;
30
+ projectKey?: string;
31
+ issueIdentifier?: string;
32
+ } | {
33
+ ok: false;
34
+ code: "missing_destination" | "missing_route_context" | "unknown_project_route" | "ambiguous_route" | "invalid_route_config" | "conflicting_destination" | "unresolved_issue";
35
+ message: string;
36
+ projectKey?: string;
37
+ issueIdentifier?: string;
38
+ };
39
+ export type TelegramFileDestinationRequest = {
40
+ explicitChatId?: string | null;
41
+ explicitThreadId?: number;
42
+ issueId?: string | null;
43
+ issueIdentifier?: string | null;
44
+ projectKey?: string | null;
45
+ lookupIssueIdentifier?: (issueId: string) => Promise<string | null>;
46
+ };
47
+ export declare function normalizeProjectKey(value: unknown): string | null;
48
+ export declare function parseProjectKeyFromIssueIdentifier(value: unknown): string | null;
49
+ export declare function validateTelegramFileRoutes(value: unknown): FileRouteValidationResult;
50
+ export declare function getTelegramFileRouteSaveErrors(value: unknown): string[];
51
+ export declare function resolveTelegramFileDestination(fileRoutes: unknown, request: TelegramFileDestinationRequest): Promise<TelegramFileDestination>;
@@ -0,0 +1,212 @@
1
+ // Ported from ant013/paperclip-plugin-telegram (TEL-23, commit 45fea97):
2
+ // "route Telegram file sends by project key". Self-contained validation and
3
+ // destination-resolution helpers for outbound Telegram file routing.
4
+ const PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]*$/;
5
+ const ISSUE_IDENTIFIER_PATTERN = /^([A-Z][A-Z0-9]*)-\d+$/;
6
+ const CHAT_ID_PATTERN = /^-?\d+$/;
7
+ const TOPIC_ID_PATTERN = /^\d+$/;
8
+ function isRecord(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+ function cleanString(value) {
12
+ return typeof value === "string" ? value.trim() : "";
13
+ }
14
+ function routeEnabled(value) {
15
+ return value !== false;
16
+ }
17
+ export function normalizeProjectKey(value) {
18
+ const normalized = cleanString(value).toUpperCase();
19
+ return PROJECT_KEY_PATTERN.test(normalized) ? normalized : null;
20
+ }
21
+ export function parseProjectKeyFromIssueIdentifier(value) {
22
+ const issueIdentifier = cleanString(value).toUpperCase();
23
+ const match = ISSUE_IDENTIFIER_PATTERN.exec(issueIdentifier);
24
+ return match?.[1] ?? null;
25
+ }
26
+ export function validateTelegramFileRoutes(value) {
27
+ const routes = [];
28
+ const issues = [];
29
+ if (value === undefined || value === null) {
30
+ return { routes, issues, duplicateProjectKeys: [] };
31
+ }
32
+ if (!Array.isArray(value)) {
33
+ return {
34
+ routes,
35
+ issues: [{ field: "fileRoutes", message: "fileRoutes must be an array." }],
36
+ duplicateProjectKeys: [],
37
+ };
38
+ }
39
+ for (const [index, route] of value.entries()) {
40
+ if (!isRecord(route)) {
41
+ issues.push({ index, field: "fileRoutes", message: "Enabled file routes must be objects." });
42
+ continue;
43
+ }
44
+ if (!routeEnabled(route.enabled)) {
45
+ continue;
46
+ }
47
+ const name = cleanString(route.name);
48
+ const projectKey = cleanString(route.projectKey);
49
+ const chatId = cleanString(route.chatId);
50
+ const rawTopicId = cleanString(route.topicId);
51
+ let valid = true;
52
+ if (!name) {
53
+ issues.push({ index, field: "name", message: "Enabled file routes need a name." });
54
+ valid = false;
55
+ }
56
+ if (!PROJECT_KEY_PATTERN.test(projectKey)) {
57
+ issues.push({ index, field: "projectKey", message: "Project key must use uppercase letters and numbers." });
58
+ valid = false;
59
+ }
60
+ if (!CHAT_ID_PATTERN.test(chatId)) {
61
+ issues.push({ index, field: "chatId", message: "Enabled file routes need a numeric Telegram chat ID." });
62
+ valid = false;
63
+ }
64
+ if (rawTopicId && !TOPIC_ID_PATTERN.test(rawTopicId)) {
65
+ issues.push({ index, field: "topicId", message: "Topic ID must be numeric when provided." });
66
+ valid = false;
67
+ }
68
+ if (valid) {
69
+ routes.push({
70
+ name,
71
+ projectKey,
72
+ chatId,
73
+ topicId: rawTopicId ? Number(rawTopicId) : undefined,
74
+ });
75
+ }
76
+ }
77
+ const duplicateNames = findDuplicates(routes.map((route) => route.name));
78
+ for (const name of duplicateNames) {
79
+ issues.push({ field: "name", message: `Enabled file route names must be unique: ${name}.` });
80
+ }
81
+ return {
82
+ routes,
83
+ issues,
84
+ duplicateProjectKeys: findDuplicates(routes.map((route) => route.projectKey)),
85
+ };
86
+ }
87
+ export function getTelegramFileRouteSaveErrors(value) {
88
+ const validation = validateTelegramFileRoutes(value);
89
+ return [
90
+ ...validation.issues.map((issue) => issue.message),
91
+ ...validation.duplicateProjectKeys.map((projectKey) => `Enabled file routes must not duplicate project key ${projectKey}.`),
92
+ ];
93
+ }
94
+ export async function resolveTelegramFileDestination(fileRoutes, request) {
95
+ const hasExplicitChat = Boolean(request.explicitChatId);
96
+ const hasExplicitThread = request.explicitThreadId !== undefined;
97
+ const hasRouteInput = Boolean(request.projectKey || request.issueIdentifier || request.issueId);
98
+ if (!hasRouteInput) {
99
+ if (hasExplicitChat) {
100
+ return { ok: true, source: "explicit", chatId: request.explicitChatId };
101
+ }
102
+ return { ok: true, source: "legacy_fallback", chatId: "" };
103
+ }
104
+ if (hasExplicitChat || hasExplicitThread) {
105
+ return {
106
+ ok: false,
107
+ code: "conflicting_destination",
108
+ message: "Route-aware Telegram file sends cannot also set chatId or threadId.",
109
+ };
110
+ }
111
+ const routeContext = await resolveRouteContext(request);
112
+ if (!routeContext.ok)
113
+ return routeContext;
114
+ const validation = validateTelegramFileRoutes(fileRoutes);
115
+ if (validation.issues.length > 0) {
116
+ return {
117
+ ok: false,
118
+ code: "invalid_route_config",
119
+ message: "Telegram file route configuration has invalid enabled routes.",
120
+ projectKey: routeContext.projectKey,
121
+ issueIdentifier: routeContext.issueIdentifier,
122
+ };
123
+ }
124
+ const matches = validation.routes.filter((route) => route.projectKey === routeContext.projectKey);
125
+ if (matches.length === 0) {
126
+ return {
127
+ ok: false,
128
+ code: "unknown_project_route",
129
+ message: `No enabled Telegram file route matches project key ${routeContext.projectKey}.`,
130
+ projectKey: routeContext.projectKey,
131
+ issueIdentifier: routeContext.issueIdentifier,
132
+ };
133
+ }
134
+ if (matches.length > 1) {
135
+ return {
136
+ ok: false,
137
+ code: "ambiguous_route",
138
+ message: `Multiple enabled Telegram file routes match project key ${routeContext.projectKey}.`,
139
+ projectKey: routeContext.projectKey,
140
+ issueIdentifier: routeContext.issueIdentifier,
141
+ };
142
+ }
143
+ const route = matches[0];
144
+ return {
145
+ ok: true,
146
+ source: "file_route",
147
+ chatId: route.chatId,
148
+ topicId: route.topicId,
149
+ routeName: route.name,
150
+ projectKey: route.projectKey,
151
+ issueIdentifier: routeContext.issueIdentifier,
152
+ };
153
+ }
154
+ function findDuplicates(values) {
155
+ const seen = new Set();
156
+ const duplicates = new Set();
157
+ for (const value of values) {
158
+ if (seen.has(value)) {
159
+ duplicates.add(value);
160
+ }
161
+ else {
162
+ seen.add(value);
163
+ }
164
+ }
165
+ return [...duplicates].sort();
166
+ }
167
+ async function resolveRouteContext(request) {
168
+ const issueId = cleanString(request.issueId);
169
+ let resolvedIssueIdentifier;
170
+ if (issueId) {
171
+ const lookupResult = await request.lookupIssueIdentifier?.(issueId);
172
+ if (!lookupResult) {
173
+ return {
174
+ ok: false,
175
+ code: "unresolved_issue",
176
+ message: "Could not resolve the Paperclip issue for Telegram file routing.",
177
+ };
178
+ }
179
+ resolvedIssueIdentifier = lookupResult.toUpperCase();
180
+ }
181
+ const explicitProjectKey = normalizeProjectKey(request.projectKey);
182
+ if (explicitProjectKey) {
183
+ return {
184
+ ok: true,
185
+ projectKey: explicitProjectKey,
186
+ issueIdentifier: cleanString(request.issueIdentifier).toUpperCase() || resolvedIssueIdentifier,
187
+ };
188
+ }
189
+ const issueIdentifier = cleanString(request.issueIdentifier).toUpperCase() || resolvedIssueIdentifier;
190
+ const projectKeyFromIdentifier = parseProjectKeyFromIssueIdentifier(issueIdentifier);
191
+ if (projectKeyFromIdentifier) {
192
+ return {
193
+ ok: true,
194
+ projectKey: projectKeyFromIdentifier,
195
+ issueIdentifier,
196
+ };
197
+ }
198
+ if (resolvedIssueIdentifier) {
199
+ return {
200
+ ok: false,
201
+ code: "missing_route_context",
202
+ message: "Resolved issue identifier does not contain a routable project key.",
203
+ issueIdentifier: resolvedIssueIdentifier,
204
+ };
205
+ }
206
+ return {
207
+ ok: false,
208
+ code: "missing_route_context",
209
+ message: "Telegram file routing needs projectKey, issueIdentifier, or issueId.",
210
+ };
211
+ }
212
+ //# sourceMappingURL=file-routing.js.map