@pixelbyte-software/pixcode 1.51.8 → 1.52.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 (307) hide show
  1. package/CODE_OF_CONDUCT.md +41 -41
  2. package/CONTRIBUTING.md +155 -155
  3. package/LICENSE +718 -718
  4. package/README.de.md +169 -169
  5. package/README.ja.md +167 -167
  6. package/README.ko.md +167 -167
  7. package/README.md +419 -419
  8. package/README.ru.md +169 -169
  9. package/README.tr.md +298 -298
  10. package/README.zh-CN.md +167 -167
  11. package/SECURITY.md +46 -46
  12. package/dist/api-automation.html +110 -110
  13. package/dist/api-docs.html +548 -548
  14. package/dist/assets/{index--kNEhygZ.js → index-HFIf2iQq.js} +153 -153
  15. package/dist/clear-cache.html +85 -85
  16. package/dist/convert-icons.md +52 -52
  17. package/dist/docs.html +308 -308
  18. package/dist/favicon.svg +8 -8
  19. package/dist/features.html +133 -133
  20. package/dist/generate-icons.js +48 -48
  21. package/dist/humans.txt +15 -15
  22. package/dist/icons/codex-white.svg +3 -3
  23. package/dist/icons/codex.svg +3 -3
  24. package/dist/icons/cursor-white.svg +11 -11
  25. package/dist/icons/icon-128x128.svg +9 -9
  26. package/dist/icons/icon-144x144.svg +9 -9
  27. package/dist/icons/icon-152x152.svg +9 -9
  28. package/dist/icons/icon-192x192.svg +9 -9
  29. package/dist/icons/icon-384x384.svg +9 -9
  30. package/dist/icons/icon-512x512.svg +9 -9
  31. package/dist/icons/icon-72x72.svg +9 -9
  32. package/dist/icons/icon-96x96.svg +9 -9
  33. package/dist/icons/icon-template.svg +9 -9
  34. package/dist/icons/qwen-logo.svg +14 -14
  35. package/dist/index.html +58 -58
  36. package/dist/landing.html +268 -268
  37. package/dist/llms-full.txt +119 -119
  38. package/dist/llms.txt +53 -53
  39. package/dist/logo.svg +12 -12
  40. package/dist/manifest.json +60 -60
  41. package/dist/openapi.yaml +1696 -1696
  42. package/dist/orchestration.html +125 -125
  43. package/dist/robots.txt +4 -4
  44. package/dist/site.css +692 -692
  45. package/dist/sitemap.xml +51 -51
  46. package/dist/sw.js +132 -132
  47. package/dist-server/server/cli.js +96 -96
  48. package/dist-server/server/daemon/manager.js +33 -33
  49. package/dist-server/server/daemon-manager.js +64 -64
  50. package/dist-server/server/index.js +91 -65
  51. package/dist-server/server/index.js.map +1 -1
  52. package/dist-server/server/routes/commands.js +25 -25
  53. package/dist-server/server/routes/git.js +17 -17
  54. package/dist-server/server/routes/live-view.js +46 -46
  55. package/dist-server/server/services/hermes-gateway.js +5 -5
  56. package/dist-server/server/services/public-api-manifest.js +51 -51
  57. package/package.json +222 -222
  58. package/scripts/fix-node-pty.js +67 -67
  59. package/scripts/github/create-v1.38-issues.mjs +351 -351
  60. package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
  61. package/scripts/hermes/configure-pixcode-mcp.mjs +165 -165
  62. package/scripts/hermes/pixcode-mcp-server.mjs +1009 -1009
  63. package/scripts/smoke/changes-panel-layout.mjs +48 -48
  64. package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
  65. package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
  66. package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
  67. package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
  68. package/scripts/smoke/chat-session-state.mjs +19 -19
  69. package/scripts/smoke/code-editor-theme.mjs +55 -55
  70. package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
  71. package/scripts/smoke/command-center-agent-writes.mjs +79 -79
  72. package/scripts/smoke/command-center-non-git.mjs +46 -46
  73. package/scripts/smoke/context-packet.mjs +43 -43
  74. package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
  75. package/scripts/smoke/daemon-entrypoint.mjs +20 -20
  76. package/scripts/smoke/default-landing-routing.mjs +33 -33
  77. package/scripts/smoke/desktop-native-notifications.mjs +30 -30
  78. package/scripts/smoke/desktop-tray-icon.mjs +33 -33
  79. package/scripts/smoke/discord-release-workflow.mjs +24 -24
  80. package/scripts/smoke/git-install-update.mjs +255 -255
  81. package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
  82. package/scripts/smoke/hermes-api-install.mjs +56 -56
  83. package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
  84. package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -426
  85. package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
  86. package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
  87. package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
  88. package/scripts/smoke/hermes-rest-gateway.mjs +79 -79
  89. package/scripts/smoke/hermes-rest-live.mjs +42 -42
  90. package/scripts/smoke/hermes-roundtrip.mjs +167 -167
  91. package/scripts/smoke/hermes-settings-commands.mjs +349 -349
  92. package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
  93. package/scripts/smoke/live-view-diagnostics.mjs +53 -53
  94. package/scripts/smoke/live-view-environment.mjs +92 -92
  95. package/scripts/smoke/live-view-integration.mjs +450 -450
  96. package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
  97. package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
  98. package/scripts/smoke/model-registry.mjs +36 -36
  99. package/scripts/smoke/multi-project-ui.mjs +45 -45
  100. package/scripts/smoke/multi-worker-slots.mjs +42 -42
  101. package/scripts/smoke/notification-center.mjs +87 -87
  102. package/scripts/smoke/notification-inapp-preference.mjs +23 -23
  103. package/scripts/smoke/notification-taxonomy.mjs +58 -58
  104. package/scripts/smoke/orchestration-api.mjs +172 -172
  105. package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
  106. package/scripts/smoke/orchestration-live-run.mjs +176 -176
  107. package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
  108. package/scripts/smoke/orchestration-model-sync.mjs +30 -30
  109. package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
  110. package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
  111. package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
  112. package/scripts/smoke/permission-policy.mjs +50 -50
  113. package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
  114. package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
  115. package/scripts/smoke/provider-rest-api.mjs +124 -124
  116. package/scripts/smoke/provider-selection-status.mjs +52 -52
  117. package/scripts/smoke/run-state-refresh.mjs +52 -52
  118. package/scripts/smoke/runtime-manager.mjs +99 -99
  119. package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
  120. package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
  121. package/scripts/smoke/static-root-routing.mjs +21 -21
  122. package/scripts/smoke/strict-handoff-compact.mjs +60 -60
  123. package/scripts/smoke/taskmaster-config.mjs +24 -24
  124. package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
  125. package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
  126. package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
  127. package/scripts/smoke/telegram-control.mjs +242 -242
  128. package/scripts/smoke/tunnel-persistence.mjs +56 -56
  129. package/scripts/smoke/update-issue-progress.mjs +69 -69
  130. package/scripts/smoke/update-ux.mjs +55 -55
  131. package/scripts/smoke/v138-completion.mjs +132 -132
  132. package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
  133. package/scripts/smoke/v138-diagnostics.mjs +63 -63
  134. package/scripts/smoke/v138-issue-planner.mjs +33 -33
  135. package/scripts/smoke/v143-remote-control.mjs +76 -76
  136. package/scripts/smoke/v144-production-loop.mjs +47 -47
  137. package/scripts/smoke/v145-platformization.mjs +46 -46
  138. package/scripts/smoke/v146-control-room-ui.mjs +150 -150
  139. package/scripts/smoke/version-modal-autoshow.mjs +29 -29
  140. package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
  141. package/scripts/smoke/vscode-workbench-polish.mjs +461 -461
  142. package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
  143. package/scripts/smoke/workflow-templates.mjs +43 -43
  144. package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
  145. package/scripts/update-git-install.mjs +283 -283
  146. package/server/claude-sdk.js +920 -920
  147. package/server/cli.js +1035 -1035
  148. package/server/constants/config.js +4 -4
  149. package/server/cursor-cli.js +344 -344
  150. package/server/daemon/manager.js +563 -563
  151. package/server/daemon-manager.js +964 -964
  152. package/server/database/db.js +895 -895
  153. package/server/database/json-store.js +197 -197
  154. package/server/gemini-cli.js +550 -550
  155. package/server/gemini-response-handler.js +79 -79
  156. package/server/index.js +111 -81
  157. package/server/load-env.js +35 -35
  158. package/server/middleware/auth.js +155 -155
  159. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
  160. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
  161. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
  162. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
  163. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
  164. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
  165. package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +100 -100
  166. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
  167. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
  168. package/server/modules/orchestration/a2a/agent-card.ts +55 -55
  169. package/server/modules/orchestration/a2a/routes.ts +590 -590
  170. package/server/modules/orchestration/a2a/task-store.ts +178 -178
  171. package/server/modules/orchestration/a2a/types.ts +126 -126
  172. package/server/modules/orchestration/a2a/validator.ts +113 -113
  173. package/server/modules/orchestration/hermes/hermes.routes.ts +642 -642
  174. package/server/modules/orchestration/index.ts +101 -101
  175. package/server/modules/orchestration/preview/port-watcher.ts +112 -112
  176. package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
  177. package/server/modules/orchestration/preview/types.ts +19 -19
  178. package/server/modules/orchestration/security/permission-policy.ts +401 -401
  179. package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
  180. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
  181. package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
  182. package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
  183. package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
  184. package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
  185. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
  186. package/server/modules/orchestration/workflows/context-packet.ts +186 -186
  187. package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
  188. package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
  189. package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
  190. package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
  191. package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
  192. package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
  193. package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
  194. package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
  195. package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
  196. package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
  197. package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
  198. package/server/modules/orchestration/workspace/path-safety.ts +55 -55
  199. package/server/modules/orchestration/workspace/types.ts +52 -52
  200. package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
  201. package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
  202. package/server/modules/providers/index.ts +2 -2
  203. package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
  204. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  205. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
  206. package/server/modules/providers/list/claude/claude.provider.ts +15 -15
  207. package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
  208. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  209. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
  210. package/server/modules/providers/list/codex/codex.provider.ts +15 -15
  211. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
  212. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  213. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
  214. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
  215. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
  216. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
  217. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
  218. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
  219. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
  220. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  221. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
  222. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  223. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
  224. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  225. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
  226. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  227. package/server/modules/providers/provider.registry.ts +40 -40
  228. package/server/modules/providers/provider.routes.ts +891 -891
  229. package/server/modules/providers/services/mcp.service.ts +86 -86
  230. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  231. package/server/modules/providers/services/sessions.service.ts +45 -45
  232. package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
  233. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  234. package/server/modules/providers/shared/provider-configs.ts +142 -142
  235. package/server/modules/providers/tests/mcp.test.ts +293 -293
  236. package/server/openai-codex.js +462 -462
  237. package/server/opencode-cli.js +491 -491
  238. package/server/opencode-response-handler.js +111 -111
  239. package/server/projects.js +3008 -3008
  240. package/server/qwen-code-cli.js +410 -410
  241. package/server/qwen-response-handler.js +73 -73
  242. package/server/routes/agent.js +1435 -1435
  243. package/server/routes/auth.js +142 -142
  244. package/server/routes/codex.js +20 -20
  245. package/server/routes/commands.js +538 -538
  246. package/server/routes/cursor.js +61 -61
  247. package/server/routes/diagnostics.js +41 -41
  248. package/server/routes/gemini.js +25 -25
  249. package/server/routes/git.js +1641 -1641
  250. package/server/routes/live-view.js +387 -387
  251. package/server/routes/mcp-utils.js +13 -13
  252. package/server/routes/messages.js +50 -50
  253. package/server/routes/network.js +117 -117
  254. package/server/routes/platformization.js +182 -182
  255. package/server/routes/plugins.js +320 -320
  256. package/server/routes/production-agent-loop.js +90 -90
  257. package/server/routes/projects.js +911 -911
  258. package/server/routes/public-api.js +34 -34
  259. package/server/routes/qwen.js +27 -27
  260. package/server/routes/remote.js +50 -50
  261. package/server/routes/settings.js +321 -321
  262. package/server/routes/telegram.js +140 -140
  263. package/server/routes/user.js +125 -125
  264. package/server/routes/webhooks.js +63 -63
  265. package/server/services/control-room.js +102 -102
  266. package/server/services/diagnostics.js +165 -165
  267. package/server/services/external-access.js +375 -375
  268. package/server/services/hermes-gateway.js +1562 -1562
  269. package/server/services/hermes-install-jobs.js +729 -729
  270. package/server/services/install-jobs.js +715 -715
  271. package/server/services/live-view.js +956 -956
  272. package/server/services/managed-runtimes.js +493 -493
  273. package/server/services/model-registry.js +144 -144
  274. package/server/services/notification-orchestrator.js +365 -365
  275. package/server/services/notification-taxonomy.js +204 -204
  276. package/server/services/platformization.js +773 -773
  277. package/server/services/production-agent-loop.js +248 -248
  278. package/server/services/provider-cli-versions.js +149 -149
  279. package/server/services/provider-credentials.js +189 -189
  280. package/server/services/provider-models.js +396 -396
  281. package/server/services/public-api-manifest.js +190 -190
  282. package/server/services/remote-connection.js +127 -127
  283. package/server/services/runtime-manager.js +323 -323
  284. package/server/services/startup-update.js +234 -234
  285. package/server/services/telegram/bot.js +331 -331
  286. package/server/services/telegram/control-center.js +979 -979
  287. package/server/services/telegram/telegram-http-client.js +151 -151
  288. package/server/services/telegram/translations.js +340 -340
  289. package/server/services/vapid-keys.js +36 -36
  290. package/server/services/webhooks.js +216 -216
  291. package/server/sessionManager.js +225 -225
  292. package/server/shared/interfaces.ts +54 -54
  293. package/server/shared/types.ts +172 -172
  294. package/server/shared/utils.ts +193 -193
  295. package/server/tsconfig.json +36 -36
  296. package/server/utils/colors.js +21 -21
  297. package/server/utils/commandParser.js +305 -305
  298. package/server/utils/frontmatter.js +18 -18
  299. package/server/utils/gitConfig.js +34 -34
  300. package/server/utils/plugin-loader.js +457 -457
  301. package/server/utils/plugin-process-manager.js +185 -185
  302. package/server/utils/port-access.js +209 -209
  303. package/server/utils/runtime-paths.js +37 -37
  304. package/server/utils/url-detection.js +71 -71
  305. package/server/vite-daemon.js +79 -79
  306. package/shared/modelConstants.js +161 -161
  307. package/shared/networkHosts.js +22 -22
@@ -1,331 +1,331 @@
1
- import { EventEmitter } from 'node:events';
2
-
3
- import { telegramConfigDb, telegramLinksDb } from '../../database/db.js';
4
-
5
- import {
6
- getTelegramControlCommand,
7
- handleTelegramControlCallback,
8
- handleTelegramControlMessage,
9
- isTelegramControlCommand,
10
- showMainMenu,
11
- } from './control-center.js';
12
- import { t } from './translations.js';
13
- // Swapped in v1.32: previously `node-telegram-bot-api` which carried the
14
- // deprecated `request`/`har-validator`/`uuid@3` chain. TelegramHttpBot is
15
- // a ~100-line fetch-based replacement with the same public surface
16
- // (getMe / sendMessage / on / stopPolling). See telegram-http-client.js.
17
- import { TelegramHttpBot } from './telegram-http-client.js';
18
-
19
- // Pairing code TTL. Ten minutes gives a user enough time to switch apps and
20
- // type the code without leaving a long-lived code sitting around if they
21
- // abandon the flow.
22
- const PAIRING_TTL_MS = 10 * 60 * 1000;
23
-
24
- // Singleton bot state. We intentionally host one bot per Pixcode instance —
25
- // running multiple bots from the same token would cause Telegram to trip
26
- // polling conflicts, and per-user bots are overkill.
27
- let bot = null;
28
- let botInfo = null; // { id, username, first_name }
29
- let lastError = null;
30
-
31
- export const setTelegramBotForTesting = (nextBot) => {
32
- bot = nextBot;
33
- };
34
-
35
- // Subscribers (notification-orchestrator, future session bridge) use this to
36
- // react to events without importing the bot module directly.
37
- export const telegramEvents = new EventEmitter();
38
-
39
- const now = () => Date.now();
40
-
41
- const generate6DigitCode = () => {
42
- // Zero-padded random 6-digit code. Using rand instead of crypto is fine
43
- // here: the code is short-lived (10min), single-use, and verified against
44
- // a per-user row — brute-force requires both the code AND a live pairing.
45
- const n = Math.floor(Math.random() * 1_000_000);
46
- return n.toString().padStart(6, '0');
47
- };
48
-
49
- export const createPairingCode = (userId, language = 'en') => {
50
- const code = generate6DigitCode();
51
- const expiresAtIso = new Date(now() + PAIRING_TTL_MS).toISOString();
52
- telegramLinksDb.setPairingCode(userId, code, expiresAtIso, language);
53
- return { code, expiresAt: expiresAtIso };
54
- };
55
-
56
- export const getBotState = () => ({
57
- running: Boolean(bot),
58
- username: botInfo?.username || null,
59
- error: lastError,
60
- });
61
-
62
- export const getPublicConfig = () => {
63
- const config = telegramConfigDb.get();
64
- return {
65
- configured: Boolean(config?.bot_token),
66
- username: config?.bot_username || null,
67
- };
68
- };
69
-
70
- const parseMaybeCode = (text) => {
71
- const trimmed = text.trim();
72
- // Only treat the message as a pairing attempt when it's *exactly* 6 digits,
73
- // otherwise a paired user typing "123456" as part of a prompt would get
74
- // rejected with "invalid code" instead of being forwarded.
75
- return /^\d{6}$/.test(trimmed) ? trimmed : null;
76
- };
77
-
78
- const safeSend = async (chatId, text, extra = {}) => {
79
- if (!bot) return;
80
- try {
81
- await bot.sendMessage(chatId, text, { parse_mode: 'Markdown', ...extra });
82
- } catch (err) {
83
- // Markdown parse errors from user input are common; retry plaintext.
84
- try {
85
- await bot.sendMessage(chatId, text, extra);
86
- } catch (fallbackErr) {
87
- console.warn('[telegram] sendMessage failed:', fallbackErr?.message || fallbackErr);
88
- }
89
- }
90
- };
91
-
92
- const handlePairing = async (msg, code) => {
93
- const link = telegramLinksDb.findByPairingCode(code);
94
- const language = link?.language || 'en';
95
-
96
- if (!link) {
97
- await safeSend(msg.chat.id, t(language, 'pairing.notFound'));
98
- return;
99
- }
100
-
101
- const expiresAt = link.pairing_code_expires_at ? Date.parse(link.pairing_code_expires_at) : 0;
102
- if (!expiresAt || expiresAt < now()) {
103
- await safeSend(msg.chat.id, t(language, 'pairing.expired'));
104
- return;
105
- }
106
-
107
- const telegramUsername = msg.from?.username || null;
108
- telegramLinksDb.verify(link.user_id, String(msg.chat.id), telegramUsername);
109
- telegramEvents.emit('paired', { userId: link.user_id, chatId: String(msg.chat.id), username: telegramUsername });
110
-
111
- await safeSend(msg.chat.id, t(language, 'pairing.success'));
112
- await safeSend(msg.chat.id, t(language, 'control.onboarding'));
113
- await showMainMenu({ bot, chatId: msg.chat.id, link: telegramLinksDb.getByUserId(link.user_id) });
114
- };
115
-
116
- const handleBridgeMessage = async (msg, existing) => {
117
- const language = existing.language || 'en';
118
-
119
- const handled = await handleTelegramControlMessage({ bot, msg, link: existing, safeSend });
120
- if (handled) return;
121
-
122
- if (!existing.bridge_enabled) {
123
- await safeSend(msg.chat.id, t(language, 'bridge.disabled'));
124
- return;
125
- }
126
-
127
- telegramEvents.emit('prompt', {
128
- userId: existing.user_id,
129
- chatId: String(msg.chat.id),
130
- text: msg.text || '',
131
- language,
132
- messageId: msg.message_id,
133
- });
134
-
135
- await safeSend(msg.chat.id, t(language, 'bridge.queued'));
136
- };
137
-
138
- const handleCallbackQuery = async (query) => {
139
- const chatId = query?.message?.chat?.id;
140
- if (!chatId) return;
141
- const existing = telegramLinksDb.getByChatId(String(chatId));
142
- if (!existing) {
143
- await bot?.answerCallbackQuery(query.id, { text: 'Pair Pixcode first.' }).catch(() => {});
144
- return;
145
- }
146
- await handleTelegramControlCallback({ bot, query, link: existing, safeSend });
147
- };
148
-
149
- const handleMessage = async (msg) => {
150
- if (!msg?.chat?.id || !msg?.text) return;
151
-
152
- const existing = telegramLinksDb.getByChatId(String(msg.chat.id));
153
- if (existing) {
154
- const language = existing.language || 'en';
155
- const command = getTelegramControlCommand(msg.text);
156
- if (command === '/start') {
157
- await safeSend(msg.chat.id, t(language, 'control.onboarding'));
158
- await showMainMenu({ bot, chatId: msg.chat.id, link: existing });
159
- return;
160
- }
161
- if (command === '/help') {
162
- await safeSend(msg.chat.id, t(language, 'control.help'));
163
- return;
164
- }
165
- if (command === '/menu' || command === 'menu') {
166
- await showMainMenu({ bot, chatId: msg.chat.id, link: existing });
167
- return;
168
- }
169
-
170
- if (isTelegramControlCommand(msg.text)) {
171
- await handleTelegramControlMessage({ bot, msg, link: existing });
172
- return;
173
- }
174
-
175
- // Paired user path: a 6-digit-only message is treated as noise (we keep
176
- // the already-paired binding); anything else is bridge traffic.
177
- const maybeCode = parseMaybeCode(msg.text);
178
- if (maybeCode) {
179
- await safeSend(msg.chat.id, t(existing.language || 'en', 'welcome.alreadyPaired'));
180
- return;
181
- }
182
- await handleBridgeMessage(msg, existing);
183
- return;
184
- }
185
-
186
- // Unpaired user path: only thing we accept is a 6-digit code. Anything else
187
- // is gently redirected to "please send your code" in a best-effort language
188
- // (we don't know their app language yet, so fall back to English).
189
- const maybeCode = parseMaybeCode(msg.text);
190
- if (maybeCode) {
191
- await handlePairing(msg, maybeCode);
192
- return;
193
- }
194
-
195
- if (/^\/start\b/i.test(msg.text)) {
196
- await safeSend(msg.chat.id, t('en', 'welcome.needsCode'));
197
- return;
198
- }
199
-
200
- // Anything else: keep nudging for the code (per user's "hep lütfen kodu
201
- // giriniz desin" requirement).
202
- await safeSend(msg.chat.id, t('en', 'pairing.stillNeeded'));
203
- };
204
-
205
- export const handleIncomingTelegramMessage = handleMessage;
206
-
207
- const wirePollingErrors = () => {
208
- if (!bot) return;
209
- bot.on('polling_error', (err) => {
210
- // 401 = bad token. 409 = another polling instance exists. Both mean we
211
- // should stop and surface the error rather than thrash.
212
- const code = err?.response?.statusCode || err?.code;
213
- lastError = { code: code || 'polling_error', message: err?.message || String(err) };
214
- if (code === 401 || code === 409) {
215
- console.error('[telegram] fatal polling error, stopping:', lastError);
216
- stopBot().catch(() => {});
217
- } else {
218
- console.warn('[telegram] polling error:', lastError);
219
- }
220
- });
221
- };
222
-
223
- export const startBot = async ({ token, persist = true } = {}) => {
224
- if (!token) {
225
- const config = telegramConfigDb.get();
226
- token = config?.bot_token;
227
- }
228
- if (!token) {
229
- throw new Error('No Telegram bot token available');
230
- }
231
-
232
- // Stop any previously-running instance before creating a new one — otherwise
233
- // node-telegram-bot-api will keep two pollers alive and Telegram will throw
234
- // 409 conflicts on every long-poll.
235
- if (bot) await stopBot();
236
-
237
- const instance = new TelegramHttpBot(token, { polling: true });
238
- // Validate the token first — if getMe fails we never want to persist a
239
- // broken token or leave the poller running.
240
- let me;
241
- try {
242
- me = await instance.getMe();
243
- } catch (err) {
244
- try { await instance.stopPolling(); } catch { /* ignore */ }
245
- const reason = err?.response?.body?.description || err?.message || String(err);
246
- lastError = { code: 'auth', message: reason };
247
- const error = new Error(`Invalid bot token: ${reason}`);
248
- error.code = 'INVALID_TOKEN';
249
- throw error;
250
- }
251
-
252
- bot = instance;
253
- botInfo = { id: me.id, username: me.username, first_name: me.first_name };
254
- lastError = null;
255
-
256
- if (persist) telegramConfigDb.set(token, me.username);
257
-
258
- bot.on('message', (msg) => {
259
- handleMessage(msg).catch((err) => {
260
- console.error('[telegram] handleMessage crashed:', err);
261
- });
262
- });
263
- bot.on('callback_query', (query) => {
264
- handleCallbackQuery(query).catch((err) => {
265
- console.error('[telegram] handleCallbackQuery crashed:', err);
266
- });
267
- });
268
- wirePollingErrors();
269
-
270
- console.log(`[telegram] bot started as @${me.username}`);
271
- telegramEvents.emit('started', { username: me.username });
272
- return botInfo;
273
- };
274
-
275
- export const stopBot = async () => {
276
- if (!bot) return;
277
- try {
278
- await bot.stopPolling({ cancel: true });
279
- } catch (err) {
280
- console.warn('[telegram] stopPolling failed:', err?.message || err);
281
- }
282
- bot = null;
283
- botInfo = null;
284
- telegramEvents.emit('stopped');
285
- };
286
-
287
- export const removeBotConfig = async () => {
288
- await stopBot();
289
- telegramConfigDb.clear();
290
- };
291
-
292
- export const sendToUser = async (userId, text) => {
293
- const link = telegramLinksDb.getByUserId(userId);
294
- if (!link?.chat_id || !bot) return false;
295
- try {
296
- await bot.sendMessage(link.chat_id, text);
297
- return true;
298
- } catch (err) {
299
- console.warn('[telegram] sendToUser failed:', err?.message || err);
300
- return false;
301
- }
302
- };
303
-
304
- // Convenience helper for notification-orchestrator: pick a translation key
305
- // based on notification kind, fill vars, and dispatch if the user has
306
- // notifications enabled.
307
- export const notifyUser = async ({ userId, kind, title, error }) => {
308
- const link = telegramLinksDb.getByUserId(userId);
309
- if (!link?.chat_id || !link.notifications_enabled) return false;
310
- const lang = link.language || 'en';
311
- const key =
312
- kind === 'error'
313
- ? 'notification.taskFailed'
314
- : kind === 'action_required'
315
- ? 'notification.actionRequired'
316
- : 'notification.taskDone';
317
- const text = t(lang, key, { title: title || 'Session', error: error || '' });
318
- return sendToUser(userId, text);
319
- };
320
-
321
- // Boot the bot automatically if a token was previously persisted. This runs
322
- // once during server startup so a restart doesn't silently un-pair everyone.
323
- export const restoreBotFromConfig = async () => {
324
- const config = telegramConfigDb.get();
325
- if (!config?.bot_token) return;
326
- try {
327
- await startBot({ token: config.bot_token, persist: false });
328
- } catch (err) {
329
- console.warn('[telegram] Failed to restore bot:', err?.message || err);
330
- }
331
- };
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ import { telegramConfigDb, telegramLinksDb } from '../../database/db.js';
4
+
5
+ import {
6
+ getTelegramControlCommand,
7
+ handleTelegramControlCallback,
8
+ handleTelegramControlMessage,
9
+ isTelegramControlCommand,
10
+ showMainMenu,
11
+ } from './control-center.js';
12
+ import { t } from './translations.js';
13
+ // Swapped in v1.32: previously `node-telegram-bot-api` which carried the
14
+ // deprecated `request`/`har-validator`/`uuid@3` chain. TelegramHttpBot is
15
+ // a ~100-line fetch-based replacement with the same public surface
16
+ // (getMe / sendMessage / on / stopPolling). See telegram-http-client.js.
17
+ import { TelegramHttpBot } from './telegram-http-client.js';
18
+
19
+ // Pairing code TTL. Ten minutes gives a user enough time to switch apps and
20
+ // type the code without leaving a long-lived code sitting around if they
21
+ // abandon the flow.
22
+ const PAIRING_TTL_MS = 10 * 60 * 1000;
23
+
24
+ // Singleton bot state. We intentionally host one bot per Pixcode instance —
25
+ // running multiple bots from the same token would cause Telegram to trip
26
+ // polling conflicts, and per-user bots are overkill.
27
+ let bot = null;
28
+ let botInfo = null; // { id, username, first_name }
29
+ let lastError = null;
30
+
31
+ export const setTelegramBotForTesting = (nextBot) => {
32
+ bot = nextBot;
33
+ };
34
+
35
+ // Subscribers (notification-orchestrator, future session bridge) use this to
36
+ // react to events without importing the bot module directly.
37
+ export const telegramEvents = new EventEmitter();
38
+
39
+ const now = () => Date.now();
40
+
41
+ const generate6DigitCode = () => {
42
+ // Zero-padded random 6-digit code. Using rand instead of crypto is fine
43
+ // here: the code is short-lived (10min), single-use, and verified against
44
+ // a per-user row — brute-force requires both the code AND a live pairing.
45
+ const n = Math.floor(Math.random() * 1_000_000);
46
+ return n.toString().padStart(6, '0');
47
+ };
48
+
49
+ export const createPairingCode = (userId, language = 'en') => {
50
+ const code = generate6DigitCode();
51
+ const expiresAtIso = new Date(now() + PAIRING_TTL_MS).toISOString();
52
+ telegramLinksDb.setPairingCode(userId, code, expiresAtIso, language);
53
+ return { code, expiresAt: expiresAtIso };
54
+ };
55
+
56
+ export const getBotState = () => ({
57
+ running: Boolean(bot),
58
+ username: botInfo?.username || null,
59
+ error: lastError,
60
+ });
61
+
62
+ export const getPublicConfig = () => {
63
+ const config = telegramConfigDb.get();
64
+ return {
65
+ configured: Boolean(config?.bot_token),
66
+ username: config?.bot_username || null,
67
+ };
68
+ };
69
+
70
+ const parseMaybeCode = (text) => {
71
+ const trimmed = text.trim();
72
+ // Only treat the message as a pairing attempt when it's *exactly* 6 digits,
73
+ // otherwise a paired user typing "123456" as part of a prompt would get
74
+ // rejected with "invalid code" instead of being forwarded.
75
+ return /^\d{6}$/.test(trimmed) ? trimmed : null;
76
+ };
77
+
78
+ const safeSend = async (chatId, text, extra = {}) => {
79
+ if (!bot) return;
80
+ try {
81
+ await bot.sendMessage(chatId, text, { parse_mode: 'Markdown', ...extra });
82
+ } catch (err) {
83
+ // Markdown parse errors from user input are common; retry plaintext.
84
+ try {
85
+ await bot.sendMessage(chatId, text, extra);
86
+ } catch (fallbackErr) {
87
+ console.warn('[telegram] sendMessage failed:', fallbackErr?.message || fallbackErr);
88
+ }
89
+ }
90
+ };
91
+
92
+ const handlePairing = async (msg, code) => {
93
+ const link = telegramLinksDb.findByPairingCode(code);
94
+ const language = link?.language || 'en';
95
+
96
+ if (!link) {
97
+ await safeSend(msg.chat.id, t(language, 'pairing.notFound'));
98
+ return;
99
+ }
100
+
101
+ const expiresAt = link.pairing_code_expires_at ? Date.parse(link.pairing_code_expires_at) : 0;
102
+ if (!expiresAt || expiresAt < now()) {
103
+ await safeSend(msg.chat.id, t(language, 'pairing.expired'));
104
+ return;
105
+ }
106
+
107
+ const telegramUsername = msg.from?.username || null;
108
+ telegramLinksDb.verify(link.user_id, String(msg.chat.id), telegramUsername);
109
+ telegramEvents.emit('paired', { userId: link.user_id, chatId: String(msg.chat.id), username: telegramUsername });
110
+
111
+ await safeSend(msg.chat.id, t(language, 'pairing.success'));
112
+ await safeSend(msg.chat.id, t(language, 'control.onboarding'));
113
+ await showMainMenu({ bot, chatId: msg.chat.id, link: telegramLinksDb.getByUserId(link.user_id) });
114
+ };
115
+
116
+ const handleBridgeMessage = async (msg, existing) => {
117
+ const language = existing.language || 'en';
118
+
119
+ const handled = await handleTelegramControlMessage({ bot, msg, link: existing, safeSend });
120
+ if (handled) return;
121
+
122
+ if (!existing.bridge_enabled) {
123
+ await safeSend(msg.chat.id, t(language, 'bridge.disabled'));
124
+ return;
125
+ }
126
+
127
+ telegramEvents.emit('prompt', {
128
+ userId: existing.user_id,
129
+ chatId: String(msg.chat.id),
130
+ text: msg.text || '',
131
+ language,
132
+ messageId: msg.message_id,
133
+ });
134
+
135
+ await safeSend(msg.chat.id, t(language, 'bridge.queued'));
136
+ };
137
+
138
+ const handleCallbackQuery = async (query) => {
139
+ const chatId = query?.message?.chat?.id;
140
+ if (!chatId) return;
141
+ const existing = telegramLinksDb.getByChatId(String(chatId));
142
+ if (!existing) {
143
+ await bot?.answerCallbackQuery(query.id, { text: 'Pair Pixcode first.' }).catch(() => {});
144
+ return;
145
+ }
146
+ await handleTelegramControlCallback({ bot, query, link: existing, safeSend });
147
+ };
148
+
149
+ const handleMessage = async (msg) => {
150
+ if (!msg?.chat?.id || !msg?.text) return;
151
+
152
+ const existing = telegramLinksDb.getByChatId(String(msg.chat.id));
153
+ if (existing) {
154
+ const language = existing.language || 'en';
155
+ const command = getTelegramControlCommand(msg.text);
156
+ if (command === '/start') {
157
+ await safeSend(msg.chat.id, t(language, 'control.onboarding'));
158
+ await showMainMenu({ bot, chatId: msg.chat.id, link: existing });
159
+ return;
160
+ }
161
+ if (command === '/help') {
162
+ await safeSend(msg.chat.id, t(language, 'control.help'));
163
+ return;
164
+ }
165
+ if (command === '/menu' || command === 'menu') {
166
+ await showMainMenu({ bot, chatId: msg.chat.id, link: existing });
167
+ return;
168
+ }
169
+
170
+ if (isTelegramControlCommand(msg.text)) {
171
+ await handleTelegramControlMessage({ bot, msg, link: existing });
172
+ return;
173
+ }
174
+
175
+ // Paired user path: a 6-digit-only message is treated as noise (we keep
176
+ // the already-paired binding); anything else is bridge traffic.
177
+ const maybeCode = parseMaybeCode(msg.text);
178
+ if (maybeCode) {
179
+ await safeSend(msg.chat.id, t(existing.language || 'en', 'welcome.alreadyPaired'));
180
+ return;
181
+ }
182
+ await handleBridgeMessage(msg, existing);
183
+ return;
184
+ }
185
+
186
+ // Unpaired user path: only thing we accept is a 6-digit code. Anything else
187
+ // is gently redirected to "please send your code" in a best-effort language
188
+ // (we don't know their app language yet, so fall back to English).
189
+ const maybeCode = parseMaybeCode(msg.text);
190
+ if (maybeCode) {
191
+ await handlePairing(msg, maybeCode);
192
+ return;
193
+ }
194
+
195
+ if (/^\/start\b/i.test(msg.text)) {
196
+ await safeSend(msg.chat.id, t('en', 'welcome.needsCode'));
197
+ return;
198
+ }
199
+
200
+ // Anything else: keep nudging for the code (per user's "hep lütfen kodu
201
+ // giriniz desin" requirement).
202
+ await safeSend(msg.chat.id, t('en', 'pairing.stillNeeded'));
203
+ };
204
+
205
+ export const handleIncomingTelegramMessage = handleMessage;
206
+
207
+ const wirePollingErrors = () => {
208
+ if (!bot) return;
209
+ bot.on('polling_error', (err) => {
210
+ // 401 = bad token. 409 = another polling instance exists. Both mean we
211
+ // should stop and surface the error rather than thrash.
212
+ const code = err?.response?.statusCode || err?.code;
213
+ lastError = { code: code || 'polling_error', message: err?.message || String(err) };
214
+ if (code === 401 || code === 409) {
215
+ console.error('[telegram] fatal polling error, stopping:', lastError);
216
+ stopBot().catch(() => {});
217
+ } else {
218
+ console.warn('[telegram] polling error:', lastError);
219
+ }
220
+ });
221
+ };
222
+
223
+ export const startBot = async ({ token, persist = true } = {}) => {
224
+ if (!token) {
225
+ const config = telegramConfigDb.get();
226
+ token = config?.bot_token;
227
+ }
228
+ if (!token) {
229
+ throw new Error('No Telegram bot token available');
230
+ }
231
+
232
+ // Stop any previously-running instance before creating a new one — otherwise
233
+ // node-telegram-bot-api will keep two pollers alive and Telegram will throw
234
+ // 409 conflicts on every long-poll.
235
+ if (bot) await stopBot();
236
+
237
+ const instance = new TelegramHttpBot(token, { polling: true });
238
+ // Validate the token first — if getMe fails we never want to persist a
239
+ // broken token or leave the poller running.
240
+ let me;
241
+ try {
242
+ me = await instance.getMe();
243
+ } catch (err) {
244
+ try { await instance.stopPolling(); } catch { /* ignore */ }
245
+ const reason = err?.response?.body?.description || err?.message || String(err);
246
+ lastError = { code: 'auth', message: reason };
247
+ const error = new Error(`Invalid bot token: ${reason}`);
248
+ error.code = 'INVALID_TOKEN';
249
+ throw error;
250
+ }
251
+
252
+ bot = instance;
253
+ botInfo = { id: me.id, username: me.username, first_name: me.first_name };
254
+ lastError = null;
255
+
256
+ if (persist) telegramConfigDb.set(token, me.username);
257
+
258
+ bot.on('message', (msg) => {
259
+ handleMessage(msg).catch((err) => {
260
+ console.error('[telegram] handleMessage crashed:', err);
261
+ });
262
+ });
263
+ bot.on('callback_query', (query) => {
264
+ handleCallbackQuery(query).catch((err) => {
265
+ console.error('[telegram] handleCallbackQuery crashed:', err);
266
+ });
267
+ });
268
+ wirePollingErrors();
269
+
270
+ console.log(`[telegram] bot started as @${me.username}`);
271
+ telegramEvents.emit('started', { username: me.username });
272
+ return botInfo;
273
+ };
274
+
275
+ export const stopBot = async () => {
276
+ if (!bot) return;
277
+ try {
278
+ await bot.stopPolling({ cancel: true });
279
+ } catch (err) {
280
+ console.warn('[telegram] stopPolling failed:', err?.message || err);
281
+ }
282
+ bot = null;
283
+ botInfo = null;
284
+ telegramEvents.emit('stopped');
285
+ };
286
+
287
+ export const removeBotConfig = async () => {
288
+ await stopBot();
289
+ telegramConfigDb.clear();
290
+ };
291
+
292
+ export const sendToUser = async (userId, text) => {
293
+ const link = telegramLinksDb.getByUserId(userId);
294
+ if (!link?.chat_id || !bot) return false;
295
+ try {
296
+ await bot.sendMessage(link.chat_id, text);
297
+ return true;
298
+ } catch (err) {
299
+ console.warn('[telegram] sendToUser failed:', err?.message || err);
300
+ return false;
301
+ }
302
+ };
303
+
304
+ // Convenience helper for notification-orchestrator: pick a translation key
305
+ // based on notification kind, fill vars, and dispatch if the user has
306
+ // notifications enabled.
307
+ export const notifyUser = async ({ userId, kind, title, error }) => {
308
+ const link = telegramLinksDb.getByUserId(userId);
309
+ if (!link?.chat_id || !link.notifications_enabled) return false;
310
+ const lang = link.language || 'en';
311
+ const key =
312
+ kind === 'error'
313
+ ? 'notification.taskFailed'
314
+ : kind === 'action_required'
315
+ ? 'notification.actionRequired'
316
+ : 'notification.taskDone';
317
+ const text = t(lang, key, { title: title || 'Session', error: error || '' });
318
+ return sendToUser(userId, text);
319
+ };
320
+
321
+ // Boot the bot automatically if a token was previously persisted. This runs
322
+ // once during server startup so a restart doesn't silently un-pair everyone.
323
+ export const restoreBotFromConfig = async () => {
324
+ const config = telegramConfigDb.get();
325
+ if (!config?.bot_token) return;
326
+ try {
327
+ await startBot({ token: config.bot_token, persist: false });
328
+ } catch (err) {
329
+ console.warn('[telegram] Failed to restore bot:', err?.message || err);
330
+ }
331
+ };