paperclip-plugin-telegram 0.2.1 → 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.
Files changed (61) hide show
  1. package/dist/acp-bridge.d.ts +34 -0
  2. package/dist/acp-bridge.js +805 -0
  3. package/dist/acp-bridge.js.map +1 -0
  4. package/dist/adapter.d.ts +35 -0
  5. package/dist/adapter.js +75 -0
  6. package/dist/adapter.js.map +1 -0
  7. package/dist/command-registry.d.ts +3 -0
  8. package/dist/command-registry.js +273 -0
  9. package/dist/command-registry.js.map +1 -0
  10. package/dist/commands.d.ts +10 -0
  11. package/dist/commands.js +213 -0
  12. package/dist/commands.js.map +1 -0
  13. package/dist/constants.d.ts +44 -0
  14. package/dist/constants.js +48 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/escalation.d.ts +41 -0
  17. package/dist/escalation.js +254 -0
  18. package/dist/escalation.js.map +1 -0
  19. package/dist/formatters.d.ts +13 -0
  20. package/dist/formatters.js +130 -0
  21. package/dist/formatters.js.map +1 -0
  22. package/dist/index.js +4 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/manifest.d.ts +3 -0
  25. package/dist/manifest.js +230 -0
  26. package/dist/manifest.js.map +1 -0
  27. package/dist/media-pipeline.d.ts +46 -0
  28. package/dist/media-pipeline.js +161 -0
  29. package/dist/media-pipeline.js.map +1 -0
  30. package/dist/telegram-api.d.ts +28 -0
  31. package/dist/telegram-api.js +147 -0
  32. package/dist/telegram-api.js.map +1 -0
  33. package/dist/watch-registry.d.ts +9 -0
  34. package/dist/watch-registry.js +272 -0
  35. package/dist/watch-registry.js.map +1 -0
  36. package/dist/worker.d.ts +1 -0
  37. package/dist/worker.js +548 -0
  38. package/dist/worker.js.map +1 -0
  39. package/package.json +7 -3
  40. package/src/acp-bridge.ts +0 -1273
  41. package/src/adapter.ts +0 -129
  42. package/src/command-registry.ts +0 -482
  43. package/src/commands.ts +0 -346
  44. package/src/constants.ts +0 -51
  45. package/src/escalation.ts +0 -421
  46. package/src/formatters.ts +0 -148
  47. package/src/manifest.ts +0 -246
  48. package/src/media-pipeline.ts +0 -234
  49. package/src/telegram-api.ts +0 -202
  50. package/src/watch-registry.ts +0 -369
  51. package/src/worker.ts +0 -783
  52. package/tests/acp-bridge.test.ts +0 -314
  53. package/tests/command-registry.test.ts +0 -283
  54. package/tests/commands.test.ts +0 -213
  55. package/tests/escalation.test.ts +0 -550
  56. package/tests/formatters.test.ts +0 -185
  57. package/tests/media-pipeline.test.ts +0 -324
  58. package/tests/telegram-api.test.ts +0 -108
  59. package/tests/watch-registry.test.ts +0 -404
  60. package/tsconfig.json +0 -16
  61. /package/{src/index.ts → dist/index.d.ts} +0 -0
package/src/commands.ts DELETED
@@ -1,346 +0,0 @@
1
- import type { PluginContext, Agent, Issue } from "@paperclipai/plugin-sdk";
2
- import { sendMessage, escapeMarkdownV2, sendChatAction } from "./telegram-api.js";
3
- import { METRIC_NAMES } from "./constants.js";
4
- import { handleAcpCommand } from "./acp-bridge.js";
5
-
6
- type BotCommand = {
7
- command: string;
8
- description: string;
9
- };
10
-
11
- export const BOT_COMMANDS: BotCommand[] = [
12
- { command: "status", description: "Company health: active agents, open issues" },
13
- { command: "issues", description: "List open issues (optionally by project)" },
14
- { command: "agents", description: "List agents with current status" },
15
- { command: "approve", description: "Approve a pending request by ID" },
16
- { command: "help", description: "Show available commands" },
17
- { command: "acp", description: "Manage agent sessions (spawn, status, cancel, close)" },
18
- { command: "commands", description: "Manage custom workflow commands (list, import, run, delete)" },
19
- ];
20
-
21
- export async function handleCommand(
22
- ctx: PluginContext,
23
- token: string,
24
- chatId: string,
25
- command: string,
26
- args: string,
27
- messageThreadId?: number,
28
- baseUrl?: string,
29
- ): Promise<void> {
30
- await ctx.metrics.write(METRIC_NAMES.commandsHandled, 1);
31
-
32
- switch (command) {
33
- case "status":
34
- await handleStatus(ctx, token, chatId, messageThreadId);
35
- break;
36
- case "issues":
37
- await handleIssues(ctx, token, chatId, args, messageThreadId);
38
- break;
39
- case "agents":
40
- await handleAgents(ctx, token, chatId, messageThreadId);
41
- break;
42
- case "approve":
43
- await handleApprove(ctx, token, chatId, args, messageThreadId, baseUrl);
44
- break;
45
- case "help":
46
- await handleHelp(ctx, token, chatId, messageThreadId);
47
- break;
48
- case "connect":
49
- await handleConnect(ctx, token, chatId, args, messageThreadId);
50
- break;
51
- case "connect-topic":
52
- await handleConnectTopic(ctx, token, chatId, args, messageThreadId);
53
- break;
54
- case "acp":
55
- await handleAcpCommand(ctx, token, chatId, args, messageThreadId);
56
- break;
57
- default:
58
- await sendMessage(ctx, token, chatId, `Unknown command: /${command}. Try /help`, {
59
- messageThreadId,
60
- });
61
- }
62
- }
63
-
64
- async function handleStatus(
65
- ctx: PluginContext,
66
- token: string,
67
- chatId: string,
68
- messageThreadId?: number,
69
- ): Promise<void> {
70
- await sendChatAction(ctx, token, chatId);
71
-
72
- try {
73
- const companyId = await resolveCompanyId(ctx, chatId);
74
- const agents = await ctx.agents.list({ companyId });
75
- const activeAgents = agents.filter((a: Agent) => a.status === "active");
76
- const issues = await ctx.issues.list({ companyId, limit: 10 });
77
- const doneIssues = issues.filter((i: Issue) => i.status === "done");
78
-
79
- const lines = [
80
- escapeMarkdownV2("📊") + " *Paperclip Status*",
81
- "",
82
- `${escapeMarkdownV2("🤖")} Active agents: *${activeAgents.length}*/${escapeMarkdownV2(String(agents.length))}`,
83
- `${escapeMarkdownV2("📋")} Recent issues: *${escapeMarkdownV2(String(issues.length))}* \\(${escapeMarkdownV2(String(doneIssues.length))} done\\)`,
84
- ];
85
-
86
- await sendMessage(ctx, token, chatId, lines.join("\n"), {
87
- parseMode: "MarkdownV2",
88
- messageThreadId,
89
- });
90
- } catch {
91
- await sendMessage(ctx, token, chatId, escapeMarkdownV2("📊") + " *Paperclip Status*\n\n" + escapeMarkdownV2("Could not fetch status. Make sure this chat is linked to a company with /connect."), {
92
- parseMode: "MarkdownV2",
93
- messageThreadId,
94
- });
95
- }
96
- }
97
-
98
- async function handleIssues(
99
- ctx: PluginContext,
100
- token: string,
101
- chatId: string,
102
- projectFilter: string,
103
- messageThreadId?: number,
104
- ): Promise<void> {
105
- await sendChatAction(ctx, token, chatId);
106
-
107
- try {
108
- const companyId = await resolveCompanyId(ctx, chatId);
109
- const issues = await ctx.issues.list({ companyId, limit: 10 });
110
- const filtered = projectFilter
111
- ? issues.filter((i: Issue) => {
112
- const projName = i.project?.name ?? "";
113
- return projName.toLowerCase().includes(projectFilter.toLowerCase());
114
- })
115
- : issues;
116
-
117
- if (filtered.length === 0) {
118
- const filter = projectFilter ? ` for project "${projectFilter}"` : "";
119
- await sendMessage(ctx, token, chatId, `No issues found${filter}.`, { messageThreadId });
120
- return;
121
- }
122
-
123
- const statusEmoji: Record<string, string> = { done: "✅", todo: "📋", in_progress: "🔄", backlog: "📥" };
124
- const lines = [escapeMarkdownV2("📋") + " *Open Issues*", ""];
125
- for (const issue of filtered) {
126
- const emoji = statusEmoji[issue.status] ?? "📋";
127
- const id = issue.identifier ?? issue.id;
128
- lines.push(`${escapeMarkdownV2(emoji)} ${escapeMarkdownV2(id)} \\- ${escapeMarkdownV2(issue.title)}`);
129
- }
130
-
131
- await sendMessage(ctx, token, chatId, lines.join("\n"), {
132
- parseMode: "MarkdownV2",
133
- messageThreadId,
134
- });
135
- } catch {
136
- const filter = projectFilter ? ` for project "${projectFilter}"` : "";
137
- await sendMessage(
138
- ctx,
139
- token,
140
- chatId,
141
- `Could not fetch issues${filter}. Make sure this chat is linked with /connect.`,
142
- { messageThreadId },
143
- );
144
- }
145
- }
146
-
147
- async function handleAgents(
148
- ctx: PluginContext,
149
- token: string,
150
- chatId: string,
151
- messageThreadId?: number,
152
- ): Promise<void> {
153
- await sendChatAction(ctx, token, chatId);
154
-
155
- try {
156
- const companyId = await resolveCompanyId(ctx, chatId);
157
- const agents = await ctx.agents.list({ companyId });
158
-
159
- if (agents.length === 0) {
160
- await sendMessage(ctx, token, chatId, "No agents found.", { messageThreadId });
161
- return;
162
- }
163
-
164
- const statusEmoji: Record<string, string> = { active: "🟢", error: "🔴", paused: "🟡", idle: "⚪", running: "🔵" };
165
- const lines = [escapeMarkdownV2("🤖") + " *Agents*", ""];
166
- for (const agent of agents) {
167
- const emoji = statusEmoji[agent.status] ?? "⚪";
168
- lines.push(`${escapeMarkdownV2(emoji)} *${escapeMarkdownV2(agent.name)}* \\- ${escapeMarkdownV2(agent.status)}`);
169
- }
170
-
171
- await sendMessage(ctx, token, chatId, lines.join("\n"), {
172
- parseMode: "MarkdownV2",
173
- messageThreadId,
174
- });
175
- } catch {
176
- await sendMessage(
177
- ctx,
178
- token,
179
- chatId,
180
- "Could not fetch agents. Make sure this chat is linked with /connect.",
181
- { messageThreadId },
182
- );
183
- }
184
- }
185
-
186
- async function handleApprove(
187
- ctx: PluginContext,
188
- token: string,
189
- chatId: string,
190
- approvalId: string,
191
- messageThreadId?: number,
192
- baseUrl: string = "http://localhost:3100",
193
- ): Promise<void> {
194
- if (!approvalId.trim()) {
195
- await sendMessage(ctx, token, chatId, "Usage: /approve <approval-id>", {
196
- messageThreadId,
197
- });
198
- return;
199
- }
200
-
201
- try {
202
- await ctx.http.fetch(
203
- `${baseUrl}/api/approvals/${approvalId.trim()}/approve`,
204
- {
205
- method: "POST",
206
- headers: { "Content-Type": "application/json" },
207
- body: JSON.stringify({ decidedByUserId: `telegram:${chatId}` }),
208
- },
209
- );
210
-
211
- await sendMessage(
212
- ctx,
213
- token,
214
- chatId,
215
- `${escapeMarkdownV2("✅")} *Approved*: \`${escapeMarkdownV2(approvalId.trim())}\``,
216
- { parseMode: "MarkdownV2", messageThreadId },
217
- );
218
- } catch (err) {
219
- await sendMessage(
220
- ctx,
221
- token,
222
- chatId,
223
- `Failed to approve ${approvalId}: ${err instanceof Error ? err.message : String(err)}`,
224
- { messageThreadId },
225
- );
226
- }
227
- }
228
-
229
- async function handleHelp(
230
- ctx: PluginContext,
231
- token: string,
232
- chatId: string,
233
- messageThreadId?: number,
234
- ): Promise<void> {
235
- const lines = [
236
- escapeMarkdownV2("📎") + " *Paperclip Bot Commands*",
237
- "",
238
- ...BOT_COMMANDS.map(
239
- (cmd) => `/${escapeMarkdownV2(cmd.command)} \\- ${escapeMarkdownV2(cmd.description)}`,
240
- ),
241
- "",
242
- `/${escapeMarkdownV2("connect")} \\- ${escapeMarkdownV2("Link this chat to a Paperclip company")}`,
243
- `/${escapeMarkdownV2("connect-topic")} \\- ${escapeMarkdownV2("Map a project to a forum topic")}`,
244
- ];
245
-
246
- await sendMessage(ctx, token, chatId, lines.join("\n"), {
247
- parseMode: "MarkdownV2",
248
- messageThreadId,
249
- });
250
- }
251
-
252
- async function handleConnect(
253
- ctx: PluginContext,
254
- token: string,
255
- chatId: string,
256
- companyName: string,
257
- messageThreadId?: number,
258
- ): Promise<void> {
259
- if (!companyName.trim()) {
260
- await sendMessage(ctx, token, chatId, "Usage: /connect <company-name>", {
261
- messageThreadId,
262
- });
263
- return;
264
- }
265
-
266
- await ctx.state.set(
267
- { scopeKind: "instance", stateKey: `chat_${chatId}` },
268
- { companyName: companyName.trim(), linkedAt: new Date().toISOString() },
269
- );
270
-
271
- await sendMessage(
272
- ctx,
273
- token,
274
- chatId,
275
- `${escapeMarkdownV2("🔗")} ${escapeMarkdownV2("Linked this chat to company:")} *${escapeMarkdownV2(companyName.trim())}*`,
276
- { parseMode: "MarkdownV2", messageThreadId },
277
- );
278
-
279
- ctx.logger.info("Chat linked to company", { chatId, companyName: companyName.trim() });
280
- }
281
-
282
- export async function handleConnectTopic(
283
- ctx: PluginContext,
284
- token: string,
285
- chatId: string,
286
- args: string,
287
- messageThreadId?: number,
288
- ): Promise<void> {
289
- const parts = args.trim().split(/\s+/);
290
- if (parts.length < 2) {
291
- await sendMessage(ctx, token, chatId, "Usage: /connect\\-topic <project\\-name> <topic\\-id>", {
292
- parseMode: "MarkdownV2",
293
- messageThreadId,
294
- });
295
- return;
296
- }
297
-
298
- const topicId = parts.pop()!;
299
- const projectName = parts.join(" ");
300
-
301
- const existing = (await ctx.state.get({
302
- scopeKind: "instance",
303
- stateKey: `topic-map-${chatId}`,
304
- })) as Record<string, string> | null;
305
-
306
- const topicMap = existing ?? {};
307
- topicMap[projectName] = topicId;
308
-
309
- await ctx.state.set(
310
- { scopeKind: "instance", stateKey: `topic-map-${chatId}` },
311
- topicMap,
312
- );
313
-
314
- await sendMessage(
315
- ctx,
316
- token,
317
- chatId,
318
- `${escapeMarkdownV2("🔗")} ${escapeMarkdownV2(`Mapped project "${projectName}" to topic ${topicId}`)}`,
319
- { parseMode: "MarkdownV2", messageThreadId },
320
- );
321
-
322
- ctx.logger.info("Topic mapped", { chatId, projectName, topicId });
323
- }
324
-
325
- export async function getTopicForProject(
326
- ctx: PluginContext,
327
- chatId: string,
328
- projectName?: string,
329
- ): Promise<number | undefined> {
330
- if (!projectName) return undefined;
331
- const topicMap = (await ctx.state.get({
332
- scopeKind: "instance",
333
- stateKey: `topic-map-${chatId}`,
334
- })) as Record<string, string> | null;
335
- if (!topicMap) return undefined;
336
- const topicId = topicMap[projectName];
337
- return topicId ? Number(topicId) : undefined;
338
- }
339
-
340
- async function resolveCompanyId(ctx: PluginContext, chatId: string): Promise<string> {
341
- const mapping = await ctx.state.get({
342
- scopeKind: "instance",
343
- stateKey: `chat_${chatId}`,
344
- }) as { companyName: string } | null;
345
- return mapping?.companyName ?? chatId;
346
- }
package/src/constants.ts DELETED
@@ -1,51 +0,0 @@
1
- export const PLUGIN_ID = "paperclip-plugin-telegram";
2
- export const PLUGIN_VERSION = "0.2.0";
3
-
4
- export const DEFAULT_CONFIG = {
5
- telegramBotTokenRef: "",
6
- defaultChatId: "",
7
- approvalsChatId: "",
8
- errorsChatId: "",
9
- paperclipBaseUrl: "http://localhost:3100",
10
- notifyOnIssueCreated: true,
11
- notifyOnIssueDone: true,
12
- notifyOnApprovalCreated: true,
13
- notifyOnAgentError: true,
14
- enableCommands: true,
15
- enableInbound: true,
16
- dailyDigestEnabled: false,
17
- dailyDigestTime: "09:00",
18
- topicRouting: false,
19
- escalationChatId: "",
20
- escalationTimeoutMs: 900000,
21
- escalationDefaultAction: "defer",
22
- escalationHoldMessage: "Let me check on that - I'll get back to you shortly.",
23
- // Phase 3: Media Pipeline
24
- briefAgentId: "",
25
- briefAgentChatIds: [] as string[],
26
- transcriptionApiKeyRef: "",
27
- // Phase 5: Proactive Suggestions
28
- maxSuggestionsPerHourPerCompany: 10,
29
- watchDeduplicationWindowMs: 86400000, // 24h
30
- } as const;
31
-
32
- export const MAX_AGENTS_PER_THREAD = 5;
33
- export const MAX_CONVERSATION_TURNS = 50;
34
- export const DEFAULT_CONVERSATION_TURNS = 10;
35
-
36
- export const METRIC_NAMES = {
37
- sent: "telegram_notifications_sent",
38
- failed: "telegram_notification_failures",
39
- commandsHandled: "telegram_commands_handled",
40
- inboundRouted: "telegram_inbound_routed",
41
- escalationsCreated: "telegram_escalations_created",
42
- escalationsResolved: "telegram_escalations_resolved",
43
- escalationsTimedOut: "telegram_escalations_timed_out",
44
- mediaProcessed: "telegram_media_processed",
45
- commandsExecuted: "telegram_custom_commands_executed",
46
- suggestionsEmitted: "telegram_suggestions_emitted",
47
- } as const;
48
-
49
- // Cross-plugin ACP event names
50
- export const ACP_SPAWN_EVENT = "acp-spawn";
51
- export const ACP_OUTPUT_EVENT = "plugin.paperclip-plugin-acp.output";