pi-harness-runtime 1.1.146 → 1.1.148

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 (179) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +113 -0
  3. package/index.ts +136 -6
  4. package/package.json +4 -3
  5. package/packages/a2a-adapter/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/codex-watcher/package.json +1 -1
  20. package/packages/config-capture/package.json +1 -1
  21. package/packages/context-compiler/package.json +1 -1
  22. package/packages/context-discovery/package.json +1 -1
  23. package/packages/context-manager/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/evaluation-engine/package.json +1 -1
  30. package/packages/evaluation-runner/package.json +1 -1
  31. package/packages/event-bus/package.json +1 -1
  32. package/packages/event-store/package.json +1 -1
  33. package/packages/experience-replay/package.json +1 -1
  34. package/packages/feedback-collector/package.json +1 -1
  35. package/packages/file-copy-helper/dist/src/extension.js +186 -0
  36. package/packages/file-copy-helper/package.json +1 -1
  37. package/packages/framework-detector/package.json +1 -1
  38. package/packages/framework-plugin-sdk/package.json +1 -1
  39. package/packages/frappe-plugin/package.json +1 -1
  40. package/packages/generic-web-plugin/package.json +1 -1
  41. package/packages/health-monitor/package.json +1 -1
  42. package/packages/intent-analyzer/package.json +1 -1
  43. package/packages/jev-judge/package.json +1 -1
  44. package/packages/knowledge-graph/package.json +1 -1
  45. package/packages/knowledge-retrieval/package.json +1 -1
  46. package/packages/laravel-plugin/package.json +1 -1
  47. package/packages/learning-engine/package.json +1 -1
  48. package/packages/mcp-adapter/package.json +1 -1
  49. package/packages/memory-engine/package.json +1 -1
  50. package/packages/milestone-manager/package.json +1 -1
  51. package/packages/model-registry/package.json +1 -1
  52. package/packages/nextjs-plugin/package.json +1 -1
  53. package/packages/notification/dist/adapters/telegram-adapter.d.ts +38 -1
  54. package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
  55. package/packages/notification/dist/adapters/telegram-adapter.js +159 -5
  56. package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
  57. package/packages/notification/dist/index.d.ts +2 -0
  58. package/packages/notification/dist/index.d.ts.map +1 -1
  59. package/packages/notification/dist/index.js +2 -0
  60. package/packages/notification/dist/index.js.map +1 -1
  61. package/packages/notification/dist/notification-center.d.ts +40 -1
  62. package/packages/notification/dist/notification-center.d.ts.map +1 -1
  63. package/packages/notification/dist/notification-center.js +103 -1
  64. package/packages/notification/dist/notification-center.js.map +1 -1
  65. package/packages/notification/dist/notification-policy.d.ts +66 -0
  66. package/packages/notification/dist/notification-policy.d.ts.map +1 -0
  67. package/packages/notification/dist/notification-policy.js +153 -0
  68. package/packages/notification/dist/notification-policy.js.map +1 -0
  69. package/packages/notification/dist/src/adapters/email-adapter.d.ts +17 -0
  70. package/packages/notification/dist/src/adapters/email-adapter.d.ts.map +1 -0
  71. package/packages/notification/dist/src/adapters/email-adapter.js +89 -0
  72. package/packages/notification/dist/src/adapters/email-adapter.js.map +1 -0
  73. package/packages/notification/dist/src/adapters/line-adapter.d.ts +17 -0
  74. package/packages/notification/dist/src/adapters/line-adapter.d.ts.map +1 -0
  75. package/packages/notification/dist/src/adapters/line-adapter.js +99 -0
  76. package/packages/notification/dist/src/adapters/line-adapter.js.map +1 -0
  77. package/packages/notification/dist/src/adapters/ntfy-adapter.d.ts +17 -0
  78. package/packages/notification/dist/src/adapters/ntfy-adapter.d.ts.map +1 -0
  79. package/packages/notification/dist/src/adapters/ntfy-adapter.js +84 -0
  80. package/packages/notification/dist/src/adapters/ntfy-adapter.js.map +1 -0
  81. package/packages/notification/dist/src/adapters/telegram-adapter.d.ts +56 -0
  82. package/packages/notification/dist/src/adapters/telegram-adapter.d.ts.map +1 -0
  83. package/packages/notification/dist/src/adapters/telegram-adapter.js +251 -0
  84. package/packages/notification/dist/src/adapters/telegram-adapter.js.map +1 -0
  85. package/packages/notification/dist/src/adapters/webhook-adapter.d.ts +15 -0
  86. package/packages/notification/dist/src/adapters/webhook-adapter.d.ts.map +1 -0
  87. package/packages/notification/dist/src/adapters/webhook-adapter.js +64 -0
  88. package/packages/notification/dist/src/adapters/webhook-adapter.js.map +1 -0
  89. package/packages/notification/dist/src/base-adapter.d.ts +36 -0
  90. package/packages/notification/dist/src/base-adapter.d.ts.map +1 -0
  91. package/packages/notification/dist/src/base-adapter.js +39 -0
  92. package/packages/notification/dist/src/base-adapter.js.map +1 -0
  93. package/packages/notification/dist/src/index.d.ts +12 -0
  94. package/packages/notification/dist/src/index.d.ts.map +1 -0
  95. package/packages/notification/dist/src/index.js +12 -0
  96. package/packages/notification/dist/src/index.js.map +1 -0
  97. package/packages/notification/dist/src/notification-center.d.ts +89 -0
  98. package/packages/notification/dist/src/notification-center.d.ts.map +1 -0
  99. package/packages/notification/dist/src/notification-center.js +356 -0
  100. package/packages/notification/dist/src/notification-center.js.map +1 -0
  101. package/packages/notification/dist/src/notification-policy.d.ts +66 -0
  102. package/packages/notification/dist/src/notification-policy.d.ts.map +1 -0
  103. package/packages/notification/dist/src/notification-policy.js +153 -0
  104. package/packages/notification/dist/src/notification-policy.js.map +1 -0
  105. package/packages/notification/dist/src/telegram-webhook-handler.d.ts +86 -0
  106. package/packages/notification/dist/src/telegram-webhook-handler.d.ts.map +1 -0
  107. package/packages/notification/dist/src/telegram-webhook-handler.js +149 -0
  108. package/packages/notification/dist/src/telegram-webhook-handler.js.map +1 -0
  109. package/packages/notification/dist/src/types.d.ts +106 -0
  110. package/packages/notification/dist/src/types.d.ts.map +1 -0
  111. package/packages/notification/dist/src/types.js +7 -0
  112. package/packages/notification/dist/src/types.js.map +1 -0
  113. package/packages/notification/dist/telegram-webhook-handler.d.ts +86 -0
  114. package/packages/notification/dist/telegram-webhook-handler.d.ts.map +1 -0
  115. package/packages/notification/dist/telegram-webhook-handler.js +149 -0
  116. package/packages/notification/dist/telegram-webhook-handler.js.map +1 -0
  117. package/packages/notification/dist/types.d.ts +37 -0
  118. package/packages/notification/dist/types.d.ts.map +1 -1
  119. package/packages/notification/package.json +1 -1
  120. package/packages/notification/src/adapters/telegram-adapter.ts +187 -5
  121. package/packages/notification/src/index.ts +2 -0
  122. package/packages/notification/src/notification-center.ts +139 -0
  123. package/packages/notification/src/notification-policy.ts +203 -0
  124. package/packages/notification/src/telegram-webhook-handler.ts +211 -0
  125. package/packages/notification/src/types.ts +42 -0
  126. package/packages/observability/package.json +1 -1
  127. package/packages/okf-indexer/package.json +1 -1
  128. package/packages/paperclip-integration/dist/index.d.ts.map +1 -1
  129. package/packages/paperclip-integration/dist/index.js +30 -18
  130. package/packages/paperclip-integration/dist/index.js.map +1 -1
  131. package/packages/paperclip-integration/package.json +1 -1
  132. package/packages/paperclip-integration/src/index.ts +35 -20
  133. package/packages/performance-optimizer/package.json +1 -1
  134. package/packages/privilege-broker/package.json +1 -1
  135. package/packages/project-analyzer/package.json +1 -1
  136. package/packages/project-bootstrap/package.json +1 -1
  137. package/packages/projection-engine/package.json +1 -1
  138. package/packages/prompt-compiler/package.json +1 -1
  139. package/packages/prompt-versioning/package.json +1 -1
  140. package/packages/provider-adapter-sdk/package.json +1 -1
  141. package/packages/provider-router/package.json +1 -1
  142. package/packages/provider-selector/package.json +1 -1
  143. package/packages/providers/package.json +1 -1
  144. package/packages/qdrant-skills/package.json +1 -1
  145. package/packages/quota-manager/package.json +1 -1
  146. package/packages/rate-limiter/package.json +1 -1
  147. package/packages/react-vite-plugin/package.json +1 -1
  148. package/packages/release-manager/package.json +1 -1
  149. package/packages/requirement-compiler/package.json +1 -1
  150. package/packages/runtime/package.json +1 -1
  151. package/packages/scheduler/package.json +1 -1
  152. package/packages/scheduler-adapter/package.json +1 -1
  153. package/packages/session/package.json +1 -1
  154. package/packages/session-api/package.json +1 -1
  155. package/packages/session-export/package.json +1 -1
  156. package/packages/shared-context/package.json +1 -1
  157. package/packages/skill-mcp-client/package.json +1 -1
  158. package/packages/skill-registry/package.json +1 -1
  159. package/packages/skills/package.json +1 -1
  160. package/packages/sprint-planner/package.json +1 -1
  161. package/packages/subscription-engine/package.json +1 -1
  162. package/packages/task-compiler/package.json +1 -1
  163. package/packages/tencentdb-memory/package.json +1 -1
  164. package/packages/tencentdb-sync/package.json +1 -1
  165. package/packages/test-data-generator/package.json +1 -1
  166. package/packages/test-generator/package.json +1 -1
  167. package/packages/todo-bd-sync/package.json +1 -1
  168. package/packages/token-estimation/package.json +1 -1
  169. package/packages/token-optimizer/package.json +1 -1
  170. package/packages/trajectory/package.json +1 -1
  171. package/packages/tui/package.json +1 -1
  172. package/packages/types/package.json +1 -1
  173. package/packages/workflow-events/package.json +1 -1
  174. package/packages/workspace-scanner/package.json +1 -1
  175. package/packages/worktree/package.json +1 -1
  176. package/packages/write-review/package.json +1 -1
  177. package/scripts/telegram-callback-processor.ts +180 -0
  178. package/scripts/telegram-webhook-server.ts +387 -0
  179. package/scripts/test-telegram-approval.ts +64 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Telegram Webhook Handler — RFC-0022
3
+ *
4
+ * Handles incoming webhook updates from Telegram (callback queries, messages).
5
+ * This enables 2-way communication with inline keyboard buttons.
6
+ */
7
+ /**
8
+ * Predefined action prefixes for callback data
9
+ */
10
+ export const CallbackActions = {
11
+ /** Approve/resume action */
12
+ APPROVE: "approve",
13
+ /** Reject/cancel action */
14
+ REJECT: "reject",
15
+ /** View details action */
16
+ VIEW: "view",
17
+ /** Custom action prefix */
18
+ CUSTOM: "custom",
19
+ };
20
+ /**
21
+ * Parse callback data string into structured object
22
+ * Format: "action_targetId" or "action_targetId_key1=val1,key2=val2"
23
+ */
24
+ export function parseCallbackData(data) {
25
+ const parts = data.split("_");
26
+ const action = parts[0] || "";
27
+ const targetId = parts[1] || "";
28
+ let payload;
29
+ // Check for additional payload
30
+ const payloadPart = parts.slice(2).join("_");
31
+ if (payloadPart && payloadPart.includes("=")) {
32
+ payload = {};
33
+ for (const pair of payloadPart.split(",")) {
34
+ const [key, value] = pair.split("=");
35
+ if (key && value) {
36
+ payload[key] = value;
37
+ }
38
+ }
39
+ }
40
+ return { action, targetId, payload };
41
+ }
42
+ /**
43
+ * Build callback data string from components
44
+ */
45
+ export function buildCallbackData(action, targetId, payload) {
46
+ let data = `${action}_${targetId}`;
47
+ if (payload && Object.keys(payload).length > 0) {
48
+ const pairs = Object.entries(payload)
49
+ .map(([k, v]) => `${k}=${v}`)
50
+ .join(",");
51
+ data += `_${pairs}`;
52
+ }
53
+ return data;
54
+ }
55
+ /**
56
+ * Create a standard Yes/No callback handler
57
+ */
58
+ export function createYesNoHandler(options) {
59
+ return async (data, query) => {
60
+ const parsed = parseCallbackData(data);
61
+ const { action, targetId } = parsed;
62
+ console.log(`[TelegramWebhook] Callback: action=${action}, target=${targetId}, from=${query.from.username || query.from.first_name}`);
63
+ switch (action) {
64
+ case CallbackActions.APPROVE:
65
+ if (options.onApprove) {
66
+ await options.onApprove(targetId, query);
67
+ }
68
+ else if (options.jobManager) {
69
+ await options.jobManager.resume(targetId);
70
+ console.log(`[TelegramWebhook] Job resumed: ${targetId}`);
71
+ }
72
+ break;
73
+ case CallbackActions.REJECT:
74
+ if (options.onReject) {
75
+ await options.onReject(targetId, query);
76
+ }
77
+ else if (options.jobManager) {
78
+ await options.jobManager.cancel(targetId);
79
+ console.log(`[TelegramWebhook] Job cancelled: ${targetId}`);
80
+ }
81
+ break;
82
+ case CallbackActions.VIEW:
83
+ if (options.onCustom) {
84
+ await options.onCustom(action, targetId, query);
85
+ }
86
+ break;
87
+ default:
88
+ if (options.onCustom) {
89
+ await options.onCustom(action, targetId, query);
90
+ }
91
+ else {
92
+ console.warn(`[TelegramWebhook] Unknown action: ${action}`);
93
+ }
94
+ }
95
+ };
96
+ }
97
+ /**
98
+ * Telegram webhook route handler for Express/Fastify/etc.
99
+ */
100
+ export function createTelegramWebhookHandler(adapter, secretToken) {
101
+ return async (req, res) => {
102
+ // Verify secret token
103
+ if (secretToken) {
104
+ const receivedToken = req.headers["x-telegram-bot-api-secret-token"];
105
+ if (receivedToken !== secretToken) {
106
+ console.error("[TelegramWebhook] Unauthorized webhook request");
107
+ res.sendStatus(403);
108
+ return;
109
+ }
110
+ }
111
+ try {
112
+ await adapter.handleWebhookUpdate(req.body);
113
+ res.sendStatus(200);
114
+ }
115
+ catch (error) {
116
+ console.error("[TelegramWebhook] Error processing update:", error);
117
+ res.sendStatus(500);
118
+ }
119
+ };
120
+ }
121
+ /**
122
+ * Notification payload helper with inline keyboard support
123
+ */
124
+ export function createInteractivePayload(event, jobId, title, message, buttons, details) {
125
+ return {
126
+ event,
127
+ jobId,
128
+ timestamp: new Date().toISOString(),
129
+ title,
130
+ message,
131
+ details: {
132
+ ...details,
133
+ _callbackButtons: buttons.map((b) => ({
134
+ text: b.text,
135
+ callbackData: buildCallbackData(b.action, b.targetId),
136
+ })),
137
+ },
138
+ };
139
+ }
140
+ /**
141
+ * Update TelegramConfig to include action buttons from payload details
142
+ */
143
+ export function extractButtonsFromPayload(details) {
144
+ if (!details)
145
+ return undefined;
146
+ const cb = details._callbackButtons;
147
+ return cb?.length ? cb : undefined;
148
+ }
149
+ //# sourceMappingURL=telegram-webhook-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-webhook-handler.js","sourceRoot":"","sources":["../src/telegram-webhook-handler.ts"],"names":[],"mappings":"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"}
@@ -29,7 +29,44 @@ export interface TelegramConfig {
29
29
  botToken: string;
30
30
  chatId: string;
31
31
  parseMode?: "MarkdownV2" | "HTML" | "Markdown";
32
+ enableInlineKeyboard?: boolean;
33
+ actionButtons?: InlineKeyboardButton[];
32
34
  }
35
+ /**
36
+ * Inline keyboard button for Telegram interactive messages
37
+ */
38
+ export interface InlineKeyboardButton {
39
+ /** Button text displayed to user */
40
+ text: string;
41
+ /** Callback data sent when button is clicked */
42
+ callbackData: string;
43
+ /** Optional URL to open when button is clicked (mutually exclusive with callbackData) */
44
+ url?: string;
45
+ }
46
+ /**
47
+ * Callback query received from Telegram inline keyboard
48
+ */
49
+ export interface TelegramCallbackQuery {
50
+ id: string;
51
+ from: {
52
+ id: number;
53
+ is_bot: boolean;
54
+ first_name: string;
55
+ username?: string;
56
+ };
57
+ chat_instance: string;
58
+ data: string;
59
+ message?: {
60
+ chat: {
61
+ id: number;
62
+ };
63
+ message_id: number;
64
+ };
65
+ }
66
+ /**
67
+ * Callback handler function type
68
+ */
69
+ export type TelegramCallbackHandler = (callbackData: string, query: TelegramCallbackQuery) => Promise<void> | void;
33
70
  export interface NtfyConfig {
34
71
  server: string;
35
72
  topic: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,iBAAiB,GAC1B,YAAY,GACZ,eAAe,GACf,YAAY,GACZ,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,sBAAsB,GACtB,WAAW,GACX,mBAAmB,GACnB,gBAAgB,GAChB,cAAc,GACd,qBAAqB,GACrB,qBAAqB,GACrB,mBAAmB,GACnB,OAAO,CAAC;AAEX,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IAEtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,yBAAyB;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC;CAC/E;AAED,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzF,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;CAC/C;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/notification",
3
- "version": "1.1.146",
3
+ "version": "1.1.148",
4
4
  "description": "pi-harness-runtime — notification",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -7,8 +7,11 @@
7
7
  /// <reference types="node" />
8
8
 
9
9
  import type {
10
+ InlineKeyboardButton,
10
11
  NotificationPayload,
11
12
  NotificationResult,
13
+ TelegramCallbackHandler,
14
+ TelegramCallbackQuery,
12
15
  TelegramConfig,
13
16
  } from "../types.js";
14
17
  import { BaseChannelAdapter } from "../base-adapter.js";
@@ -17,11 +20,27 @@ export class TelegramAdapter extends BaseChannelAdapter {
17
20
  readonly id = "telegram";
18
21
  readonly type = "telegram";
19
22
  private _botUsername: string | undefined;
23
+ private _callbackHandler: TelegramCallbackHandler | undefined;
24
+ private _webhookSecret: string | undefined;
20
25
 
21
26
  constructor(config: TelegramConfig) {
22
27
  super({ id: "telegram", type: "telegram", enabled: true, config });
23
28
  }
24
29
 
30
+ /**
31
+ * Register a callback handler for inline keyboard button clicks
32
+ */
33
+ setCallbackHandler(handler: TelegramCallbackHandler): void {
34
+ this._callbackHandler = handler;
35
+ }
36
+
37
+ /**
38
+ * Set webhook secret for verification
39
+ */
40
+ setWebhookSecret(secret: string): void {
41
+ this._webhookSecret = secret;
42
+ }
43
+
25
44
  get botUsername(): string | undefined {
26
45
  return this._botUsername;
27
46
  }
@@ -43,21 +62,32 @@ return this._botUsername;
43
62
  }
44
63
  }
45
64
 
65
+ /**
66
+ * Send a message with optional inline keyboard buttons
67
+ */
46
68
  async send(payload: NotificationPayload): Promise<NotificationResult> {
47
69
  try {
48
70
  const cfg = this.config.config as TelegramConfig;
49
71
  const message = this.formatMessage(payload);
50
72
 
73
+ // Build request body
74
+ const body: Record<string, unknown> = {
75
+ chat_id: cfg.chatId,
76
+ text: message,
77
+ parse_mode: cfg.parseMode ?? "MarkdownV2",
78
+ };
79
+
80
+ // Add inline keyboard if enabled and buttons are configured
81
+ if (cfg.enableInlineKeyboard && (cfg.actionButtons?.length ?? 0) > 0) {
82
+ body.reply_markup = this.buildInlineKeyboard(cfg.actionButtons!);
83
+ }
84
+
51
85
  const response = await fetch(
52
86
  `https://api.telegram.org/bot${cfg.botToken}/sendMessage`,
53
87
  {
54
88
  method: "POST",
55
89
  headers: { "Content-Type": "application/json" },
56
- body: JSON.stringify({
57
- chat_id: cfg.chatId,
58
- text: message,
59
- parse_mode: cfg.parseMode ?? "MarkdownV2",
60
- }),
90
+ body: JSON.stringify(body),
61
91
  },
62
92
  );
63
93
 
@@ -80,6 +110,158 @@ return this._botUsername;
80
110
  }
81
111
  }
82
112
 
113
+ /**
114
+ * Build inline keyboard markup from button configuration
115
+ */
116
+ private buildInlineKeyboard(
117
+ buttons: InlineKeyboardButton[],
118
+ ): { inline_keyboard: Array<Array<{ text: string; callback_data?: string; url?: string }>> } {
119
+ // Group buttons into rows of up to 3 buttons each (Telegram limit)
120
+ const ROW_SIZE = 3;
121
+ const rows: Array<Array<{ text: string; callback_data?: string; url?: string }>> = [];
122
+
123
+ for (let i = 0; i < buttons.length; i += ROW_SIZE) {
124
+ const row = buttons.slice(i, i + ROW_SIZE).map((btn) => ({
125
+ text: btn.text,
126
+ ...(btn.url ? { url: btn.url } : { callback_data: btn.callbackData }),
127
+ }));
128
+ rows.push(row);
129
+ }
130
+
131
+ return { inline_keyboard: rows };
132
+ }
133
+
134
+ /**
135
+ * Process incoming webhook update from Telegram
136
+ */
137
+ async handleWebhookUpdate(update: Record<string, unknown>): Promise<void> {
138
+ // Verify secret token if set
139
+ if (this._webhookSecret) {
140
+ const secretToken = (update as { secret_token?: string }).secret_token;
141
+ if (secretToken !== this._webhookSecret) {
142
+ console.error("[TelegramAdapter] Invalid webhook secret");
143
+ return;
144
+ }
145
+ }
146
+
147
+ // Handle callback query (button click)
148
+ const callbackQuery = update.callback_query as Record<string, unknown> | undefined;
149
+ if (callbackQuery && this._callbackHandler) {
150
+ const query = this.parseCallbackQuery(callbackQuery);
151
+ if (query) {
152
+ await this._callbackHandler(query.data, query);
153
+ await this.answerCallbackQuery(query.id);
154
+ }
155
+ }
156
+
157
+ // Handle regular messages (future: user can type commands)
158
+ const message = update.message as Record<string, unknown> | undefined;
159
+ if (message) {
160
+ console.log("[TelegramAdapter] Received message:", message.text);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Parse callback query from webhook update
166
+ */
167
+ private parseCallbackQuery(
168
+ raw: Record<string, unknown>,
169
+ ): TelegramCallbackQuery | null {
170
+ try {
171
+ const from = raw.from as Record<string, unknown>;
172
+ const message = raw.message as Record<string, unknown> | undefined;
173
+ const chat = message?.chat as Record<string, unknown> | undefined;
174
+
175
+ return {
176
+ id: String(raw.id),
177
+ from: {
178
+ id: Number(from?.id),
179
+ is_bot: Boolean(from?.is_bot),
180
+ first_name: String(from?.first_name ?? ""),
181
+ username: from?.username ? String(from.username) : undefined,
182
+ },
183
+ chat_instance: String(raw.chat_instance ?? ""),
184
+ data: String(raw.data ?? ""),
185
+ message: message
186
+ ? {
187
+ chat: { id: Number(chat?.id) },
188
+ message_id: Number(message?.message_id),
189
+ }
190
+ : undefined,
191
+ };
192
+ } catch {
193
+ console.error("[TelegramAdapter] Failed to parse callback query");
194
+ return null;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Answer a callback query to remove loading state
200
+ */
201
+ private async answerCallbackQuery(callbackQueryId: string): Promise<void> {
202
+ try {
203
+ const cfg = this.config.config as TelegramConfig;
204
+ await fetch(
205
+ `https://api.telegram.org/bot${cfg.botToken}/answerCallbackQuery`,
206
+ {
207
+ method: "POST",
208
+ headers: { "Content-Type": "application/json" },
209
+ body: JSON.stringify({ callback_query_id: callbackQueryId }),
210
+ },
211
+ );
212
+ } catch (error) {
213
+ console.error("[TelegramAdapter] Failed to answer callback query:", error);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Setup webhook for receiving updates
219
+ */
220
+ async setupWebhook(webhookUrl: string): Promise<boolean> {
221
+ try {
222
+ const cfg = this.config.config as TelegramConfig;
223
+ const response = await fetch(
224
+ `https://api.telegram.org/bot${cfg.botToken}/setWebhook`,
225
+ {
226
+ method: "POST",
227
+ headers: { "Content-Type": "application/json" },
228
+ body: JSON.stringify({
229
+ url: webhookUrl,
230
+ secret_token: this._webhookSecret,
231
+ }),
232
+ },
233
+ );
234
+
235
+ if (!response.ok) {
236
+ console.error("[TelegramAdapter] Webhook setup failed:", await response.text());
237
+ return false;
238
+ }
239
+
240
+ console.log(`[TelegramAdapter] Webhook set to: ${webhookUrl}`);
241
+ return true;
242
+ } catch (error) {
243
+ console.error("[TelegramAdapter] Webhook setup error:", error);
244
+ return false;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Get current webhook info
250
+ */
251
+ async getWebhookInfo(): Promise<Record<string, unknown> | null> {
252
+ try {
253
+ const cfg = this.config.config as TelegramConfig;
254
+ const response = await fetch(
255
+ `https://api.telegram.org/bot${cfg.botToken}/getWebhookInfo`,
256
+ );
257
+
258
+ if (!response.ok) return null;
259
+ return (await response.json()) as Record<string, unknown>;
260
+ } catch {
261
+ return null;
262
+ }
263
+ }
264
+
83
265
  private formatMessage(payload: NotificationPayload): string {
84
266
  const emoji = this.getEmoji(payload.event);
85
267
  const title = `${emoji} ${payload.title}`;
@@ -7,4 +7,6 @@
7
7
  export * from "./notification-center.js";
8
8
  export * from "./base-adapter.js";
9
9
  export * from "./types.js";
10
+ export * from "./telegram-webhook-handler.js";
10
11
  export { LineAdapter } from "./adapters/line-adapter.js";
12
+ export { TelegramAdapter } from "./adapters/telegram-adapter.js";
@@ -10,15 +10,19 @@
10
10
  */
11
11
 
12
12
  import type {
13
+ InlineKeyboardButton,
13
14
  NotificationEvent,
14
15
  NotificationPayload,
15
16
  NotificationConfig,
16
17
  NotificationChannelConfig,
17
18
  NotificationResult,
18
19
  NotificationContext,
20
+ TelegramCallbackHandler,
21
+ TelegramCallbackQuery,
19
22
  } from "./types.js";
20
23
  import type { ChannelAdapter } from "./base-adapter.js";
21
24
  import { TelegramAdapter } from "./adapters/telegram-adapter.js";
25
+ import { buildCallbackData, parseCallbackData, CallbackActions } from "./telegram-webhook-handler.js";
22
26
  import { LineAdapter } from "./adapters/line-adapter.js";
23
27
  import { NtfyAdapter } from "./adapters/ntfy-adapter.js";
24
28
  import { EmailAdapter } from "./adapters/email-adapter.js";
@@ -158,6 +162,132 @@ export class NotificationCenter {
158
162
  return undefined;
159
163
  }
160
164
 
165
+ /**
166
+ * Get Telegram adapter for advanced operations (webhook setup, etc.)
167
+ */
168
+ getTelegramAdapter(): TelegramAdapter | undefined {
169
+ const adapter = this.adapters.get("telegram");
170
+ if (adapter instanceof TelegramAdapter) {
171
+ return adapter;
172
+ }
173
+ return undefined;
174
+ }
175
+
176
+ /**
177
+ * Set callback handler for Telegram inline keyboard button clicks
178
+ */
179
+ setTelegramCallbackHandler(handler: TelegramCallbackHandler): void {
180
+ const adapter = this.adapters.get("telegram");
181
+ if (adapter instanceof TelegramAdapter) {
182
+ adapter.setCallbackHandler(handler);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Set webhook secret for Telegram updates verification
188
+ */
189
+ setTelegramWebhookSecret(secret: string): void {
190
+ const adapter = this.adapters.get("telegram");
191
+ if (adapter instanceof TelegramAdapter) {
192
+ adapter.setWebhookSecret(secret);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Setup Telegram webhook for receiving updates
198
+ */
199
+ async setupTelegramWebhook(webhookUrl: string): Promise<boolean> {
200
+ const adapter = this.adapters.get("telegram");
201
+ if (adapter instanceof TelegramAdapter) {
202
+ return await adapter.setupWebhook(webhookUrl);
203
+ }
204
+ return false;
205
+ }
206
+
207
+ /**
208
+ * Send notification with interactive Yes/No buttons
209
+ */
210
+ async notifyWithApproval(
211
+ event: NotificationEvent,
212
+ context: NotificationContext,
213
+ options?: {
214
+ /** Custom button labels */
215
+ approveLabel?: string;
216
+ rejectLabel?: string;
217
+ /** Additional details for the message */
218
+ additionalDetails?: Record<string, unknown>;
219
+ },
220
+ ): Promise<NotificationResult[]> {
221
+ const approveLabel = options?.approveLabel ?? "Yes";
222
+ const rejectLabel = options?.rejectLabel ?? "No";
223
+
224
+ // Build buttons
225
+ const buttons: InlineKeyboardButton[] = [
226
+ { text: `\u2705 ${approveLabel}`, callbackData: buildCallbackData(CallbackActions.APPROVE, context.jobId) },
227
+ { text: `\u274C ${rejectLabel}`, callbackData: buildCallbackData(CallbackActions.REJECT, context.jobId) },
228
+ ];
229
+
230
+ // Send to each channel with buttons (Telegram only)
231
+ const results: NotificationResult[] = [];
232
+
233
+ for (const [id, adapter] of this.adapters.entries()) {
234
+ if (adapter instanceof TelegramAdapter) {
235
+ try {
236
+ const payload = this.buildPayload(event, context, {
237
+ enableInlineKeyboard: true,
238
+ actionButtons: buttons,
239
+ ...(options?.additionalDetails && { additionalDetails: options.additionalDetails }),
240
+ });
241
+ const result = await adapter.send(payload);
242
+ results.push(result);
243
+ } catch (error) {
244
+ results.push({ success: false, channel: id, error: String(error) });
245
+ }
246
+ }
247
+ }
248
+
249
+ return results;
250
+ }
251
+
252
+ /**
253
+ * Send interactive notification with custom buttons
254
+ */
255
+ async notifyWithButtons(
256
+ event: NotificationEvent,
257
+ context: NotificationContext,
258
+ buttons: Array<{ text: string; action: string; targetId: string; url?: string }>,
259
+ options?: {
260
+ /** Additional details for the message */
261
+ additionalDetails?: Record<string, unknown>;
262
+ },
263
+ ): Promise<NotificationResult[]> {
264
+ const results: NotificationResult[] = [];
265
+
266
+ for (const [id, adapter] of this.adapters.entries()) {
267
+ if (adapter instanceof TelegramAdapter) {
268
+ try {
269
+ const formattedButtons: InlineKeyboardButton[] = buttons.map((b) => ({
270
+ text: b.text,
271
+ callbackData: buildCallbackData(b.action, b.targetId),
272
+ url: b.url,
273
+ }));
274
+
275
+ const payload = this.buildPayload(event, context, {
276
+ enableInlineKeyboard: true,
277
+ actionButtons: formattedButtons,
278
+ ...(options?.additionalDetails && { additionalDetails: options.additionalDetails }),
279
+ });
280
+ const result = await adapter.send(payload);
281
+ results.push(result);
282
+ } catch (error) {
283
+ results.push({ success: false, channel: id, error: String(error) });
284
+ }
285
+ }
286
+ }
287
+
288
+ return results;
289
+ }
290
+
161
291
  // --- Private Methods ------------------------------------------------
162
292
 
163
293
  private createAdapter(
@@ -192,6 +322,11 @@ export class NotificationCenter {
192
322
  private buildPayload(
193
323
  event: NotificationEvent,
194
324
  context: NotificationContext,
325
+ options?: {
326
+ enableInlineKeyboard?: boolean;
327
+ actionButtons?: InlineKeyboardButton[];
328
+ additionalDetails?: Record<string, unknown>;
329
+ },
195
330
  ): NotificationPayload {
196
331
  const { title, message } = this.getEventContent(event, context);
197
332
 
@@ -207,6 +342,10 @@ export class NotificationCenter {
207
342
  ...(context.taskId && { taskId: context.taskId }),
208
343
  ...(context.taskTitle && { taskTitle: context.taskTitle }),
209
344
  ...(context.error && { error: context.error }),
345
+ // Pass inline keyboard options to Telegram adapter
346
+ ...(options?.enableInlineKeyboard && { _enableInlineKeyboard: true }),
347
+ ...(options?.actionButtons && { _actionButtons: options.actionButtons }),
348
+ ...(options?.additionalDetails && options.additionalDetails),
210
349
  },
211
350
  };
212
351
  }