@a1hvdy/cc-openclaw 0.3.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 (491) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +207 -0
  3. package/configs/.gitkeep +0 -0
  4. package/configs/council-reviewer-prompt.md +82 -0
  5. package/configs/council-system-prompt.md +141 -0
  6. package/dist/scripts/bench/ab-harness.d.ts +58 -0
  7. package/dist/scripts/bench/ab-harness.d.ts.map +1 -0
  8. package/dist/scripts/bench/ab-harness.js +78 -0
  9. package/dist/scripts/bench/ab-harness.js.map +1 -0
  10. package/dist/src/channels/adapter.d.ts +103 -0
  11. package/dist/src/channels/adapter.d.ts.map +1 -0
  12. package/dist/src/channels/adapter.js +38 -0
  13. package/dist/src/channels/adapter.js.map +1 -0
  14. package/dist/src/channels/telegram/completion-summary.d.ts +22 -0
  15. package/dist/src/channels/telegram/completion-summary.d.ts.map +1 -0
  16. package/dist/src/channels/telegram/completion-summary.js +186 -0
  17. package/dist/src/channels/telegram/completion-summary.js.map +1 -0
  18. package/dist/src/channels/telegram/error-renderer.d.ts +30 -0
  19. package/dist/src/channels/telegram/error-renderer.d.ts.map +1 -0
  20. package/dist/src/channels/telegram/error-renderer.js +133 -0
  21. package/dist/src/channels/telegram/error-renderer.js.map +1 -0
  22. package/dist/src/channels/telegram/event-reducer.d.ts +34 -0
  23. package/dist/src/channels/telegram/event-reducer.d.ts.map +1 -0
  24. package/dist/src/channels/telegram/event-reducer.js +579 -0
  25. package/dist/src/channels/telegram/event-reducer.js.map +1 -0
  26. package/dist/src/channels/telegram/index.d.ts +14 -0
  27. package/dist/src/channels/telegram/index.d.ts.map +1 -0
  28. package/dist/src/channels/telegram/index.js +14 -0
  29. package/dist/src/channels/telegram/index.js.map +1 -0
  30. package/dist/src/channels/telegram/injector.d.ts +54 -0
  31. package/dist/src/channels/telegram/injector.d.ts.map +1 -0
  32. package/dist/src/channels/telegram/injector.js +200 -0
  33. package/dist/src/channels/telegram/injector.js.map +1 -0
  34. package/dist/src/channels/telegram/live-card.d.ts +230 -0
  35. package/dist/src/channels/telegram/live-card.d.ts.map +1 -0
  36. package/dist/src/channels/telegram/live-card.js +916 -0
  37. package/dist/src/channels/telegram/live-card.js.map +1 -0
  38. package/dist/src/channels/telegram/state-machine.d.ts +23 -0
  39. package/dist/src/channels/telegram/state-machine.d.ts.map +1 -0
  40. package/dist/src/channels/telegram/state-machine.js +72 -0
  41. package/dist/src/channels/telegram/state-machine.js.map +1 -0
  42. package/dist/src/channels/telegram/tool-tracker.d.ts +147 -0
  43. package/dist/src/channels/telegram/tool-tracker.d.ts.map +1 -0
  44. package/dist/src/channels/telegram/tool-tracker.js +520 -0
  45. package/dist/src/channels/telegram/tool-tracker.js.map +1 -0
  46. package/dist/src/circuit-breaker.d.ts +22 -0
  47. package/dist/src/circuit-breaker.d.ts.map +1 -0
  48. package/dist/src/circuit-breaker.js +47 -0
  49. package/dist/src/circuit-breaker.js.map +1 -0
  50. package/dist/src/command-router/cc-handler.d.ts +67 -0
  51. package/dist/src/command-router/cc-handler.d.ts.map +1 -0
  52. package/dist/src/command-router/cc-handler.js +980 -0
  53. package/dist/src/command-router/cc-handler.js.map +1 -0
  54. package/dist/src/command-router/index.d.ts +3 -0
  55. package/dist/src/command-router/index.d.ts.map +1 -0
  56. package/dist/src/command-router/index.js +2 -0
  57. package/dist/src/command-router/index.js.map +1 -0
  58. package/dist/src/constants.d.ts +132 -0
  59. package/dist/src/constants.d.ts.map +1 -0
  60. package/dist/src/constants.js +140 -0
  61. package/dist/src/constants.js.map +1 -0
  62. package/dist/src/council/consensus.d.ts +21 -0
  63. package/dist/src/council/consensus.d.ts.map +1 -0
  64. package/dist/src/council/consensus.js +52 -0
  65. package/dist/src/council/consensus.js.map +1 -0
  66. package/dist/src/council/council.d.ts +68 -0
  67. package/dist/src/council/council.d.ts.map +1 -0
  68. package/dist/src/council/council.js +914 -0
  69. package/dist/src/council/council.js.map +1 -0
  70. package/dist/src/council/index.d.ts +3 -0
  71. package/dist/src/council/index.d.ts.map +1 -0
  72. package/dist/src/council/index.js +3 -0
  73. package/dist/src/council/index.js.map +1 -0
  74. package/dist/src/engines/base-oneshot-session.d.ts +88 -0
  75. package/dist/src/engines/base-oneshot-session.d.ts.map +1 -0
  76. package/dist/src/engines/base-oneshot-session.js +228 -0
  77. package/dist/src/engines/base-oneshot-session.js.map +1 -0
  78. package/dist/src/engines/index.d.ts +7 -0
  79. package/dist/src/engines/index.d.ts.map +1 -0
  80. package/dist/src/engines/index.js +7 -0
  81. package/dist/src/engines/index.js.map +1 -0
  82. package/dist/src/engines/persistent-codex-session.d.ts +17 -0
  83. package/dist/src/engines/persistent-codex-session.d.ts.map +1 -0
  84. package/dist/src/engines/persistent-codex-session.js +106 -0
  85. package/dist/src/engines/persistent-codex-session.js.map +1 -0
  86. package/dist/src/engines/persistent-cursor-session.d.ts +22 -0
  87. package/dist/src/engines/persistent-cursor-session.d.ts.map +1 -0
  88. package/dist/src/engines/persistent-cursor-session.js +242 -0
  89. package/dist/src/engines/persistent-cursor-session.js.map +1 -0
  90. package/dist/src/engines/persistent-custom-session.d.ts +79 -0
  91. package/dist/src/engines/persistent-custom-session.d.ts.map +1 -0
  92. package/dist/src/engines/persistent-custom-session.js +939 -0
  93. package/dist/src/engines/persistent-custom-session.js.map +1 -0
  94. package/dist/src/engines/persistent-gemini-session.d.ts +22 -0
  95. package/dist/src/engines/persistent-gemini-session.d.ts.map +1 -0
  96. package/dist/src/engines/persistent-gemini-session.js +217 -0
  97. package/dist/src/engines/persistent-gemini-session.js.map +1 -0
  98. package/dist/src/engines/persistent-session.d.ts +77 -0
  99. package/dist/src/engines/persistent-session.d.ts.map +1 -0
  100. package/dist/src/engines/persistent-session.js +730 -0
  101. package/dist/src/engines/persistent-session.js.map +1 -0
  102. package/dist/src/health/handler.d.ts +40 -0
  103. package/dist/src/health/handler.d.ts.map +1 -0
  104. package/dist/src/health/handler.js +70 -0
  105. package/dist/src/health/handler.js.map +1 -0
  106. package/dist/src/health/index.d.ts +2 -0
  107. package/dist/src/health/index.d.ts.map +1 -0
  108. package/dist/src/health/index.js +2 -0
  109. package/dist/src/health/index.js.map +1 -0
  110. package/dist/src/index.d.ts +49 -0
  111. package/dist/src/index.d.ts.map +1 -0
  112. package/dist/src/index.js +84 -0
  113. package/dist/src/index.js.map +1 -0
  114. package/dist/src/lib/auto-recovery.d.ts +45 -0
  115. package/dist/src/lib/auto-recovery.d.ts.map +1 -0
  116. package/dist/src/lib/auto-recovery.js +217 -0
  117. package/dist/src/lib/auto-recovery.js.map +1 -0
  118. package/dist/src/lib/cache-parity.d.ts +39 -0
  119. package/dist/src/lib/cache-parity.d.ts.map +1 -0
  120. package/dist/src/lib/cache-parity.js +92 -0
  121. package/dist/src/lib/cache-parity.js.map +1 -0
  122. package/dist/src/lib/circuit-breaker.d.ts +22 -0
  123. package/dist/src/lib/circuit-breaker.d.ts.map +1 -0
  124. package/dist/src/lib/circuit-breaker.js +47 -0
  125. package/dist/src/lib/circuit-breaker.js.map +1 -0
  126. package/dist/src/lib/config.d.ts +74 -0
  127. package/dist/src/lib/config.d.ts.map +1 -0
  128. package/dist/src/lib/config.js +244 -0
  129. package/dist/src/lib/config.js.map +1 -0
  130. package/dist/src/lib/drift-detector.d.ts +47 -0
  131. package/dist/src/lib/drift-detector.d.ts.map +1 -0
  132. package/dist/src/lib/drift-detector.js +192 -0
  133. package/dist/src/lib/drift-detector.js.map +1 -0
  134. package/dist/src/lib/error-formatter.d.ts +78 -0
  135. package/dist/src/lib/error-formatter.d.ts.map +1 -0
  136. package/dist/src/lib/error-formatter.js +149 -0
  137. package/dist/src/lib/error-formatter.js.map +1 -0
  138. package/dist/src/lib/heartbeat-workaround.d.ts +45 -0
  139. package/dist/src/lib/heartbeat-workaround.d.ts.map +1 -0
  140. package/dist/src/lib/heartbeat-workaround.js +61 -0
  141. package/dist/src/lib/heartbeat-workaround.js.map +1 -0
  142. package/dist/src/lib/index.d.ts +8 -0
  143. package/dist/src/lib/index.d.ts.map +1 -0
  144. package/dist/src/lib/index.js +8 -0
  145. package/dist/src/lib/index.js.map +1 -0
  146. package/dist/src/lib/register-guard.d.ts +49 -0
  147. package/dist/src/lib/register-guard.d.ts.map +1 -0
  148. package/dist/src/lib/register-guard.js +73 -0
  149. package/dist/src/lib/register-guard.js.map +1 -0
  150. package/dist/src/lib/route-flag.d.ts +50 -0
  151. package/dist/src/lib/route-flag.d.ts.map +1 -0
  152. package/dist/src/lib/route-flag.js +52 -0
  153. package/dist/src/lib/route-flag.js.map +1 -0
  154. package/dist/src/lib/sysprompt-strip.d.ts +54 -0
  155. package/dist/src/lib/sysprompt-strip.d.ts.map +1 -0
  156. package/dist/src/lib/sysprompt-strip.js +75 -0
  157. package/dist/src/lib/sysprompt-strip.js.map +1 -0
  158. package/dist/src/lib/telemetry.d.ts +39 -0
  159. package/dist/src/lib/telemetry.d.ts.map +1 -0
  160. package/dist/src/lib/telemetry.js +73 -0
  161. package/dist/src/lib/telemetry.js.map +1 -0
  162. package/dist/src/lib/test-mode.d.ts +27 -0
  163. package/dist/src/lib/test-mode.d.ts.map +1 -0
  164. package/dist/src/lib/test-mode.js +38 -0
  165. package/dist/src/lib/test-mode.js.map +1 -0
  166. package/dist/src/lib/vendor-paths.d.ts +15 -0
  167. package/dist/src/lib/vendor-paths.d.ts.map +1 -0
  168. package/dist/src/lib/vendor-paths.js +32 -0
  169. package/dist/src/lib/vendor-paths.js.map +1 -0
  170. package/dist/src/logger.d.ts +17 -0
  171. package/dist/src/logger.d.ts.map +1 -0
  172. package/dist/src/logger.js +46 -0
  173. package/dist/src/logger.js.map +1 -0
  174. package/dist/src/mcp/bridge.d.ts +22 -0
  175. package/dist/src/mcp/bridge.d.ts.map +1 -0
  176. package/dist/src/mcp/bridge.js +78 -0
  177. package/dist/src/mcp/bridge.js.map +1 -0
  178. package/dist/src/mcp/index.d.ts +3 -0
  179. package/dist/src/mcp/index.d.ts.map +1 -0
  180. package/dist/src/mcp/index.js +2 -0
  181. package/dist/src/mcp/index.js.map +1 -0
  182. package/dist/src/models.d.ts +70 -0
  183. package/dist/src/models.d.ts.map +1 -0
  184. package/dist/src/models.js +289 -0
  185. package/dist/src/models.js.map +1 -0
  186. package/dist/src/openai-compat/cli-stream-parser.d.ts +135 -0
  187. package/dist/src/openai-compat/cli-stream-parser.d.ts.map +1 -0
  188. package/dist/src/openai-compat/cli-stream-parser.js +195 -0
  189. package/dist/src/openai-compat/cli-stream-parser.js.map +1 -0
  190. package/dist/src/openai-compat/index.d.ts +2 -0
  191. package/dist/src/openai-compat/index.d.ts.map +1 -0
  192. package/dist/src/openai-compat/index.js +2 -0
  193. package/dist/src/openai-compat/index.js.map +1 -0
  194. package/dist/src/openai-compat/openai-compat.d.ts +281 -0
  195. package/dist/src/openai-compat/openai-compat.d.ts.map +1 -0
  196. package/dist/src/openai-compat/openai-compat.js +939 -0
  197. package/dist/src/openai-compat/openai-compat.js.map +1 -0
  198. package/dist/src/openai-compat/skill-resolver.d.ts +36 -0
  199. package/dist/src/openai-compat/skill-resolver.d.ts.map +1 -0
  200. package/dist/src/openai-compat/skill-resolver.js +134 -0
  201. package/dist/src/openai-compat/skill-resolver.js.map +1 -0
  202. package/dist/src/openai-compat/sse-translator.d.ts +32 -0
  203. package/dist/src/openai-compat/sse-translator.d.ts.map +1 -0
  204. package/dist/src/openai-compat/sse-translator.js +155 -0
  205. package/dist/src/openai-compat/sse-translator.js.map +1 -0
  206. package/dist/src/proxy/anthropic-adapter.d.ts +137 -0
  207. package/dist/src/proxy/anthropic-adapter.d.ts.map +1 -0
  208. package/dist/src/proxy/anthropic-adapter.js +392 -0
  209. package/dist/src/proxy/anthropic-adapter.js.map +1 -0
  210. package/dist/src/proxy/handler.d.ts +40 -0
  211. package/dist/src/proxy/handler.d.ts.map +1 -0
  212. package/dist/src/proxy/handler.js +378 -0
  213. package/dist/src/proxy/handler.js.map +1 -0
  214. package/dist/src/proxy/index.d.ts +5 -0
  215. package/dist/src/proxy/index.d.ts.map +1 -0
  216. package/dist/src/proxy/index.js +5 -0
  217. package/dist/src/proxy/index.js.map +1 -0
  218. package/dist/src/proxy/schema-cleaner.d.ts +12 -0
  219. package/dist/src/proxy/schema-cleaner.d.ts.map +1 -0
  220. package/dist/src/proxy/schema-cleaner.js +34 -0
  221. package/dist/src/proxy/schema-cleaner.js.map +1 -0
  222. package/dist/src/proxy/thought-cache.d.ts +20 -0
  223. package/dist/src/proxy/thought-cache.d.ts.map +1 -0
  224. package/dist/src/proxy/thought-cache.js +53 -0
  225. package/dist/src/proxy/thought-cache.js.map +1 -0
  226. package/dist/src/session/embedded-server.d.ts +26 -0
  227. package/dist/src/session/embedded-server.d.ts.map +1 -0
  228. package/dist/src/session/embedded-server.js +367 -0
  229. package/dist/src/session/embedded-server.js.map +1 -0
  230. package/dist/src/session/inbox-manager.d.ts +39 -0
  231. package/dist/src/session/inbox-manager.d.ts.map +1 -0
  232. package/dist/src/session/inbox-manager.js +111 -0
  233. package/dist/src/session/inbox-manager.js.map +1 -0
  234. package/dist/src/session/index.d.ts +4 -0
  235. package/dist/src/session/index.d.ts.map +1 -0
  236. package/dist/src/session/index.js +4 -0
  237. package/dist/src/session/index.js.map +1 -0
  238. package/dist/src/session/session-manager.d.ts +212 -0
  239. package/dist/src/session/session-manager.d.ts.map +1 -0
  240. package/dist/src/session/session-manager.js +1351 -0
  241. package/dist/src/session/session-manager.js.map +1 -0
  242. package/dist/src/session-bootstrap/cwd-patch.d.ts +51 -0
  243. package/dist/src/session-bootstrap/cwd-patch.d.ts.map +1 -0
  244. package/dist/src/session-bootstrap/cwd-patch.js +955 -0
  245. package/dist/src/session-bootstrap/cwd-patch.js.map +1 -0
  246. package/dist/src/session-bootstrap/index.d.ts +4 -0
  247. package/dist/src/session-bootstrap/index.d.ts.map +1 -0
  248. package/dist/src/session-bootstrap/index.js +4 -0
  249. package/dist/src/session-bootstrap/index.js.map +1 -0
  250. package/dist/src/session-bootstrap/sysprompt-strip.d.ts +26 -0
  251. package/dist/src/session-bootstrap/sysprompt-strip.d.ts.map +1 -0
  252. package/dist/src/session-bootstrap/sysprompt-strip.js +57 -0
  253. package/dist/src/session-bootstrap/sysprompt-strip.js.map +1 -0
  254. package/dist/src/session-bootstrap/think-conflict-resolver.d.ts +33 -0
  255. package/dist/src/session-bootstrap/think-conflict-resolver.d.ts.map +1 -0
  256. package/dist/src/session-bootstrap/think-conflict-resolver.js +234 -0
  257. package/dist/src/session-bootstrap/think-conflict-resolver.js.map +1 -0
  258. package/dist/src/types.d.ts +489 -0
  259. package/dist/src/types.d.ts.map +1 -0
  260. package/dist/src/types.js +8 -0
  261. package/dist/src/types.js.map +1 -0
  262. package/dist/src/validation.d.ts +32 -0
  263. package/dist/src/validation.d.ts.map +1 -0
  264. package/dist/src/validation.js +104 -0
  265. package/dist/src/validation.js.map +1 -0
  266. package/dist/tests/_helpers/subprocess-mock.d.ts +35 -0
  267. package/dist/tests/_helpers/subprocess-mock.d.ts.map +1 -0
  268. package/dist/tests/_helpers/subprocess-mock.js +136 -0
  269. package/dist/tests/_helpers/subprocess-mock.js.map +1 -0
  270. package/dist/tests/auto-recovery.test.d.ts +2 -0
  271. package/dist/tests/auto-recovery.test.d.ts.map +1 -0
  272. package/dist/tests/auto-recovery.test.js +189 -0
  273. package/dist/tests/auto-recovery.test.js.map +1 -0
  274. package/dist/tests/bench-harness.test.d.ts +2 -0
  275. package/dist/tests/bench-harness.test.d.ts.map +1 -0
  276. package/dist/tests/bench-harness.test.js +21 -0
  277. package/dist/tests/bench-harness.test.js.map +1 -0
  278. package/dist/tests/cache-parity.test.d.ts +2 -0
  279. package/dist/tests/cache-parity.test.d.ts.map +1 -0
  280. package/dist/tests/cache-parity.test.js +401 -0
  281. package/dist/tests/cache-parity.test.js.map +1 -0
  282. package/dist/tests/command-router.test.d.ts +2 -0
  283. package/dist/tests/command-router.test.d.ts.map +1 -0
  284. package/dist/tests/command-router.test.js +60 -0
  285. package/dist/tests/command-router.test.js.map +1 -0
  286. package/dist/tests/council.test.d.ts +2 -0
  287. package/dist/tests/council.test.d.ts.map +1 -0
  288. package/dist/tests/council.test.js +20 -0
  289. package/dist/tests/council.test.js.map +1 -0
  290. package/dist/tests/drift-detector.test.d.ts +2 -0
  291. package/dist/tests/drift-detector.test.d.ts.map +1 -0
  292. package/dist/tests/drift-detector.test.js +268 -0
  293. package/dist/tests/drift-detector.test.js.map +1 -0
  294. package/dist/tests/eager-bootstrap-gating.test.d.ts +9 -0
  295. package/dist/tests/eager-bootstrap-gating.test.d.ts.map +1 -0
  296. package/dist/tests/eager-bootstrap-gating.test.js +97 -0
  297. package/dist/tests/eager-bootstrap-gating.test.js.map +1 -0
  298. package/dist/tests/engines.test.d.ts +2 -0
  299. package/dist/tests/engines.test.d.ts.map +1 -0
  300. package/dist/tests/engines.test.js +8 -0
  301. package/dist/tests/engines.test.js.map +1 -0
  302. package/dist/tests/error-formatter.test.d.ts +2 -0
  303. package/dist/tests/error-formatter.test.d.ts.map +1 -0
  304. package/dist/tests/error-formatter.test.js +220 -0
  305. package/dist/tests/error-formatter.test.js.map +1 -0
  306. package/dist/tests/health.test.d.ts +2 -0
  307. package/dist/tests/health.test.d.ts.map +1 -0
  308. package/dist/tests/health.test.js +110 -0
  309. package/dist/tests/health.test.js.map +1 -0
  310. package/dist/tests/heartbeat-workaround.test.d.ts +2 -0
  311. package/dist/tests/heartbeat-workaround.test.d.ts.map +1 -0
  312. package/dist/tests/heartbeat-workaround.test.js +90 -0
  313. package/dist/tests/heartbeat-workaround.test.js.map +1 -0
  314. package/dist/tests/index.test.d.ts +2 -0
  315. package/dist/tests/index.test.d.ts.map +1 -0
  316. package/dist/tests/index.test.js +7 -0
  317. package/dist/tests/index.test.js.map +1 -0
  318. package/dist/tests/lib-sysprompt-strip.test.d.ts +2 -0
  319. package/dist/tests/lib-sysprompt-strip.test.d.ts.map +1 -0
  320. package/dist/tests/lib-sysprompt-strip.test.js +145 -0
  321. package/dist/tests/lib-sysprompt-strip.test.js.map +1 -0
  322. package/dist/tests/listener-activation.test.d.ts +2 -0
  323. package/dist/tests/listener-activation.test.d.ts.map +1 -0
  324. package/dist/tests/listener-activation.test.js +87 -0
  325. package/dist/tests/listener-activation.test.js.map +1 -0
  326. package/dist/tests/mcp-bridge.test.d.ts +2 -0
  327. package/dist/tests/mcp-bridge.test.d.ts.map +1 -0
  328. package/dist/tests/mcp-bridge.test.js +137 -0
  329. package/dist/tests/mcp-bridge.test.js.map +1 -0
  330. package/dist/tests/openai-compat.test.d.ts +2 -0
  331. package/dist/tests/openai-compat.test.d.ts.map +1 -0
  332. package/dist/tests/openai-compat.test.js +8 -0
  333. package/dist/tests/openai-compat.test.js.map +1 -0
  334. package/dist/tests/proxy-heartbeat-integration.test.d.ts +15 -0
  335. package/dist/tests/proxy-heartbeat-integration.test.d.ts.map +1 -0
  336. package/dist/tests/proxy-heartbeat-integration.test.js +122 -0
  337. package/dist/tests/proxy-heartbeat-integration.test.js.map +1 -0
  338. package/dist/tests/proxy.test.d.ts +2 -0
  339. package/dist/tests/proxy.test.d.ts.map +1 -0
  340. package/dist/tests/proxy.test.js +8 -0
  341. package/dist/tests/proxy.test.js.map +1 -0
  342. package/dist/tests/register-guard-stacking.test.d.ts +2 -0
  343. package/dist/tests/register-guard-stacking.test.d.ts.map +1 -0
  344. package/dist/tests/register-guard-stacking.test.js +61 -0
  345. package/dist/tests/register-guard-stacking.test.js.map +1 -0
  346. package/dist/tests/register-guard.test.d.ts +2 -0
  347. package/dist/tests/register-guard.test.d.ts.map +1 -0
  348. package/dist/tests/register-guard.test.js +129 -0
  349. package/dist/tests/register-guard.test.js.map +1 -0
  350. package/dist/tests/route-flag-rollback.test.d.ts +2 -0
  351. package/dist/tests/route-flag-rollback.test.d.ts.map +1 -0
  352. package/dist/tests/route-flag-rollback.test.js +70 -0
  353. package/dist/tests/route-flag-rollback.test.js.map +1 -0
  354. package/dist/tests/route-flag.test.d.ts +2 -0
  355. package/dist/tests/route-flag.test.d.ts.map +1 -0
  356. package/dist/tests/route-flag.test.js +101 -0
  357. package/dist/tests/route-flag.test.js.map +1 -0
  358. package/dist/tests/session-bootstrap.test.d.ts +2 -0
  359. package/dist/tests/session-bootstrap.test.d.ts.map +1 -0
  360. package/dist/tests/session-bootstrap.test.js +183 -0
  361. package/dist/tests/session-bootstrap.test.js.map +1 -0
  362. package/dist/tests/session.test.d.ts +2 -0
  363. package/dist/tests/session.test.d.ts.map +1 -0
  364. package/dist/tests/session.test.js +17 -0
  365. package/dist/tests/session.test.js.map +1 -0
  366. package/dist/tests/state-machine.test.d.ts +2 -0
  367. package/dist/tests/state-machine.test.d.ts.map +1 -0
  368. package/dist/tests/state-machine.test.js +133 -0
  369. package/dist/tests/state-machine.test.js.map +1 -0
  370. package/dist/tests/streaming/cli-stream-parser.test.d.ts +2 -0
  371. package/dist/tests/streaming/cli-stream-parser.test.d.ts.map +1 -0
  372. package/dist/tests/streaming/cli-stream-parser.test.js +233 -0
  373. package/dist/tests/streaming/cli-stream-parser.test.js.map +1 -0
  374. package/dist/tests/streaming/feature-flag.test.d.ts +14 -0
  375. package/dist/tests/streaming/feature-flag.test.d.ts.map +1 -0
  376. package/dist/tests/streaming/feature-flag.test.js +163 -0
  377. package/dist/tests/streaming/feature-flag.test.js.map +1 -0
  378. package/dist/tests/streaming/no-tools-prompt.test.d.ts +17 -0
  379. package/dist/tests/streaming/no-tools-prompt.test.d.ts.map +1 -0
  380. package/dist/tests/streaming/no-tools-prompt.test.js +229 -0
  381. package/dist/tests/streaming/no-tools-prompt.test.js.map +1 -0
  382. package/dist/tests/streaming/skill-plus-tools.test.d.ts +14 -0
  383. package/dist/tests/streaming/skill-plus-tools.test.d.ts.map +1 -0
  384. package/dist/tests/streaming/skill-plus-tools.test.js +234 -0
  385. package/dist/tests/streaming/skill-plus-tools.test.js.map +1 -0
  386. package/dist/tests/streaming/sse-translator.test.d.ts +2 -0
  387. package/dist/tests/streaming/sse-translator.test.d.ts.map +1 -0
  388. package/dist/tests/streaming/sse-translator.test.js +227 -0
  389. package/dist/tests/streaming/sse-translator.test.js.map +1 -0
  390. package/dist/tests/streaming/tool-result-roundtrip.test.d.ts +11 -0
  391. package/dist/tests/streaming/tool-result-roundtrip.test.d.ts.map +1 -0
  392. package/dist/tests/streaming/tool-result-roundtrip.test.js +215 -0
  393. package/dist/tests/streaming/tool-result-roundtrip.test.js.map +1 -0
  394. package/dist/tests/streaming/tool-use-translation.test.d.ts +10 -0
  395. package/dist/tests/streaming/tool-use-translation.test.d.ts.map +1 -0
  396. package/dist/tests/streaming/tool-use-translation.test.js +251 -0
  397. package/dist/tests/streaming/tool-use-translation.test.js.map +1 -0
  398. package/dist/tests/telegram-bridge.test.d.ts +2 -0
  399. package/dist/tests/telegram-bridge.test.d.ts.map +1 -0
  400. package/dist/tests/telegram-bridge.test.js +17 -0
  401. package/dist/tests/telegram-bridge.test.js.map +1 -0
  402. package/dist/tests/telegram-injector.test.d.ts +2 -0
  403. package/dist/tests/telegram-injector.test.d.ts.map +1 -0
  404. package/dist/tests/telegram-injector.test.js +74 -0
  405. package/dist/tests/telegram-injector.test.js.map +1 -0
  406. package/dist/tests/telemetry.test.d.ts +2 -0
  407. package/dist/tests/telemetry.test.d.ts.map +1 -0
  408. package/dist/tests/telemetry.test.js +405 -0
  409. package/dist/tests/telemetry.test.js.map +1 -0
  410. package/dist/tests/test-mode.test.d.ts +2 -0
  411. package/dist/tests/test-mode.test.d.ts.map +1 -0
  412. package/dist/tests/test-mode.test.js +39 -0
  413. package/dist/tests/test-mode.test.js.map +1 -0
  414. package/mcp-config.template.json +13 -0
  415. package/mcp-tools.json +1 -0
  416. package/openclaw-mcp-bridge.cjs +152 -0
  417. package/openclaw.plugin.json +30 -0
  418. package/package.json +45 -0
  419. package/skills/.gitkeep +0 -0
  420. package/stubs/commands-status-deps.runtime.js +10 -0
  421. package/stubs/status.runtime.js +149 -0
  422. package/vendor/base-oneshot-session.d.ts +87 -0
  423. package/vendor/base-oneshot-session.js +227 -0
  424. package/vendor/base-oneshot-session.js.map +1 -0
  425. package/vendor/circuit-breaker.d.ts +21 -0
  426. package/vendor/circuit-breaker.js +47 -0
  427. package/vendor/circuit-breaker.js.map +1 -0
  428. package/vendor/consensus.d.ts +20 -0
  429. package/vendor/consensus.js +52 -0
  430. package/vendor/consensus.js.map +1 -0
  431. package/vendor/constants.d.ts +130 -0
  432. package/vendor/constants.js +139 -0
  433. package/vendor/constants.js.map +1 -0
  434. package/vendor/council.d.ts +67 -0
  435. package/vendor/council.js +913 -0
  436. package/vendor/council.js.map +1 -0
  437. package/vendor/embedded-server.d.ts +25 -0
  438. package/vendor/embedded-server.js +360 -0
  439. package/vendor/embedded-server.js.map +1 -0
  440. package/vendor/inbox-manager.d.ts +38 -0
  441. package/vendor/inbox-manager.js +111 -0
  442. package/vendor/inbox-manager.js.map +1 -0
  443. package/vendor/index.d.ts +63 -0
  444. package/vendor/index.js +705 -0
  445. package/vendor/index.js.map +1 -0
  446. package/vendor/logger.d.ts +16 -0
  447. package/vendor/logger.js +44 -0
  448. package/vendor/logger.js.map +1 -0
  449. package/vendor/models.d.ts +69 -0
  450. package/vendor/models.js +289 -0
  451. package/vendor/models.js.map +1 -0
  452. package/vendor/openai-compat.d.ts +197 -0
  453. package/vendor/openai-compat.js +721 -0
  454. package/vendor/openai-compat.js.map +1 -0
  455. package/vendor/persistent-codex-session.d.ts +16 -0
  456. package/vendor/persistent-codex-session.js +105 -0
  457. package/vendor/persistent-codex-session.js.map +1 -0
  458. package/vendor/persistent-cursor-session.d.ts +21 -0
  459. package/vendor/persistent-cursor-session.js +241 -0
  460. package/vendor/persistent-cursor-session.js.map +1 -0
  461. package/vendor/persistent-custom-session.d.ts +78 -0
  462. package/vendor/persistent-custom-session.js +937 -0
  463. package/vendor/persistent-custom-session.js.map +1 -0
  464. package/vendor/persistent-gemini-session.d.ts +21 -0
  465. package/vendor/persistent-gemini-session.js +216 -0
  466. package/vendor/persistent-gemini-session.js.map +1 -0
  467. package/vendor/persistent-session.d.ts +74 -0
  468. package/vendor/persistent-session.js +684 -0
  469. package/vendor/persistent-session.js.map +1 -0
  470. package/vendor/proxy/anthropic-adapter.d.ts +136 -0
  471. package/vendor/proxy/anthropic-adapter.js +392 -0
  472. package/vendor/proxy/anthropic-adapter.js.map +1 -0
  473. package/vendor/proxy/handler.d.ts +39 -0
  474. package/vendor/proxy/handler.js +323 -0
  475. package/vendor/proxy/handler.js.map +1 -0
  476. package/vendor/proxy/schema-cleaner.d.ts +11 -0
  477. package/vendor/proxy/schema-cleaner.js +34 -0
  478. package/vendor/proxy/schema-cleaner.js.map +1 -0
  479. package/vendor/proxy/thought-cache.d.ts +19 -0
  480. package/vendor/proxy/thought-cache.js +53 -0
  481. package/vendor/proxy/thought-cache.js.map +1 -0
  482. package/vendor/session-manager.d.ts +211 -0
  483. package/vendor/session-manager.js +1345 -0
  484. package/vendor/session-manager.js.map +1 -0
  485. package/vendor/skill-resolver.js +107 -0
  486. package/vendor/types.d.ts +466 -0
  487. package/vendor/types.js +8 -0
  488. package/vendor/types.js.map +1 -0
  489. package/vendor/validation.d.ts +31 -0
  490. package/vendor/validation.js +104 -0
  491. package/vendor/validation.js.map +1 -0
@@ -0,0 +1,916 @@
1
+ /**
2
+ * telegram live-card — TypeScript port of savvy-claude-code/ux-bridge.js
3
+ *
4
+ * Handles:
5
+ * - Telegram Bot API HTTP calls (sendTg, editTg, telegramApi)
6
+ * - TelegramMessageQueue — serialises per-session card edits
7
+ * - bridgeEvent — maps raw stream events to TranscriptEntry shape
8
+ * - applyPatch — patches SessionManager.prototype.sendMessage to drive
9
+ * TelegramUXPipeline for every Telegram-originating CC session
10
+ *
11
+ * Source lines: 1–930 of ux-bridge.js
12
+ */
13
+ import { request as httpsRequest } from 'node:https';
14
+ import { readFileSync, unlinkSync, readdirSync, mkdirSync, appendFileSync } from 'node:fs';
15
+ import { readFile as readFileAsync } from 'node:fs/promises';
16
+ import { createRequire } from 'node:module';
17
+ import { createHash } from 'node:crypto';
18
+ import { defaultRegisterGuard } from '../../lib/register-guard.js';
19
+ import { isTestMode } from '../../lib/test-mode.js';
20
+ import { getQuotaPausePct } from '../../lib/config.js';
21
+ import { VENDOR_FILES } from '../../lib/vendor-paths.js';
22
+ const __require = createRequire(import.meta.url);
23
+ // ── Constants ─────────────────────────────────────────────────────────────
24
+ const PLUGIN_TAG = '[cc-openclaw/telegram/live-card]';
25
+ const CC_PLUGIN_PATH = VENDOR_FILES.sessionManager;
26
+ export const UX_MODULE_PATH = '/home/a1xai/.openclaw/workspace/lib/telegram-ux';
27
+ export const VERBOSITY_STORE = '/home/a1xai/.openclaw/workspace/lib/telegram-ux/verbosity-prefs.json';
28
+ const OPENCLAW_CONFIG_PATH = '/home/a1xai/.openclaw/openclaw.json';
29
+ const STATUS_TEE_DIR = '/tmp/openclaw-ux-status';
30
+ const STATUS_POLL_MS = 30000;
31
+ const GPT_POLL_MS = 30000;
32
+ const QUOTA_POLL_MS = 30000;
33
+ // ── Shared mutable state (module-level singletons) ───────────────────────
34
+ export let BOT_TOKEN = '';
35
+ // eslint-disable-next-line prefer-const
36
+ export let minimaxTracker = null;
37
+ export let buttonHandler = null;
38
+ export let nsCommands = null;
39
+ // Active session registry for callback routing: sessionId → { pipeline, queue, name }
40
+ export const activeSessions = new Map();
41
+ // Finished-session grace cache — keeps a session discoverable for button
42
+ // callbacks (Continue / Transcript / Verbose / Model) that arrive AFTER
43
+ // the turn ends. Expires entries after FINISHED_SESSION_TTL_MS.
44
+ export const recentlyFinishedSessions = new Map();
45
+ export const FINISHED_SESSION_TTL_MS = 5 * 60 * 1000;
46
+ // Tier 6: Delegation chain store — tracks Savvy card IDs for CC session linking/cleanup
47
+ export const delegatedCardStore = new Map();
48
+ // F3 — Module-level mirror of the most recent quota fetch.
49
+ export let latestQuotaState = null;
50
+ // F3 — Auto-pause threshold.
51
+ const QUOTA_PAUSE_PCT = getQuotaPausePct();
52
+ // Per-chat cooldown on pause notifications
53
+ const _pauseNotifiedAt = new Map(); // chatId → timestamp
54
+ /**
55
+ * Defensive in-flight dedup for patchedSendMessage. The doubled-greeting bug
56
+ * surfaces as two separate sendMessage invocations for the same model output
57
+ * (each constructs its own TelegramMessageQueue, defeating queue-level
58
+ * gating). Until the upstream double-call root cause is identified, we
59
+ * short-circuit duplicate (chatId, payload-hash) pairs that arrive
60
+ * within DEDUP_WINDOW_MS of each other. Logs a single WARN per duplicate to
61
+ * collect evidence for the next-round investigation.
62
+ *
63
+ * Important: keyed on chatId (NOT sessionName) because the upstream double
64
+ * call uses two different session names for the same Telegram-chat output.
65
+ * The chatId is the stable invariant that identifies "where this message
66
+ * is going" regardless of which session generated it.
67
+ */
68
+ const _sendMessageDedup = new Map(); // chatId → entry
69
+ const DEDUP_WINDOW_MS = 3000;
70
+ function _hashPayload(message) {
71
+ let payload;
72
+ try {
73
+ payload = typeof message === 'string' ? message : JSON.stringify(message);
74
+ }
75
+ catch {
76
+ payload = String(message);
77
+ }
78
+ // 12-char sha1 prefix is collision-safe within a 2s window for our payload sizes
79
+ return createHash('sha1').update(payload.slice(0, 4096)).digest('hex').slice(0, 12);
80
+ }
81
+ /**
82
+ * Shared cross-handler dedup. Any handler dispatching to Telegram should
83
+ * (a) mark via `markDispatchedToTelegram(chatId, payload)` and (b) check via
84
+ * `wasDispatchedToTelegram(chatId, payload)` to short-circuit duplicates.
85
+ *
86
+ * Used to bridge the gap between cc-openclaw's patched SessionManager.send
87
+ * path and the gateway's native ACP message_sending path — both can fire
88
+ * for the same agent reply, producing the doubled-greeting on /new.
89
+ */
90
+ export function markDispatchedToTelegram(chatId, payload) {
91
+ if (!chatId)
92
+ return;
93
+ const hash = _hashPayload(payload);
94
+ _sendMessageDedup.set(chatId, { hash, expiresAt: Date.now() + DEDUP_WINDOW_MS });
95
+ }
96
+ export function wasDispatchedToTelegram(chatId, payload) {
97
+ if (!chatId)
98
+ return false;
99
+ const existing = _sendMessageDedup.get(chatId);
100
+ if (!existing)
101
+ return false;
102
+ if (existing.expiresAt <= Date.now()) {
103
+ _sendMessageDedup.delete(chatId);
104
+ return false;
105
+ }
106
+ return existing.hash === _hashPayload(payload);
107
+ }
108
+ const PAUSE_NOTIFY_COOLDOWN_MS = 60 * 1000;
109
+ // ── Telegram Bot API ──────────────────────────────────────────────────────
110
+ export function telegramApi(method, params) {
111
+ return new Promise((resolve, reject) => {
112
+ const body = JSON.stringify(params);
113
+ const options = {
114
+ hostname: 'api.telegram.org',
115
+ path: `/bot${BOT_TOKEN}/${method}`,
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/json',
119
+ 'Content-Length': Buffer.byteLength(body),
120
+ },
121
+ };
122
+ const req = httpsRequest(options, (res) => {
123
+ let data = '';
124
+ res.on('data', (chunk) => (data += chunk));
125
+ res.on('end', () => {
126
+ try {
127
+ resolve(JSON.parse(data));
128
+ }
129
+ catch {
130
+ resolve({ ok: false, description: 'JSON parse error' });
131
+ }
132
+ });
133
+ });
134
+ req.on('error', (err) => reject(err));
135
+ req.setTimeout(10_000, () => {
136
+ req.destroy(new Error('Telegram API timeout'));
137
+ });
138
+ req.write(body);
139
+ req.end();
140
+ });
141
+ }
142
+ /** Send a Telegram message. Tries MarkdownV2 first, falls back to plain text. */
143
+ export async function sendTg(chatId, text, threadId, replyMarkup, replyToMessageId) {
144
+ try {
145
+ const base = { chat_id: chatId, disable_web_page_preview: true };
146
+ if (threadId)
147
+ base.message_thread_id = Number(threadId);
148
+ if (replyMarkup)
149
+ base.reply_markup = replyMarkup;
150
+ if (replyToMessageId)
151
+ base.reply_to_message_id = Number(replyToMessageId);
152
+ const res = await telegramApi('sendMessage', {
153
+ ...base,
154
+ text,
155
+ parse_mode: 'MarkdownV2',
156
+ });
157
+ if (res.ok)
158
+ return res;
159
+ const plain = text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '');
160
+ return telegramApi('sendMessage', {
161
+ ...base,
162
+ text: plain || 'Session update',
163
+ });
164
+ }
165
+ catch {
166
+ return { ok: false };
167
+ }
168
+ }
169
+ /** Edit a Telegram message. Tries MarkdownV2 first, falls back to plain text. */
170
+ export async function editTg(chatId, messageId, text, replyMarkup) {
171
+ // TODO(P2): refine logger reference — currently uses module-level logger
172
+ try {
173
+ const params = {
174
+ chat_id: chatId,
175
+ message_id: messageId,
176
+ text,
177
+ parse_mode: 'MarkdownV2',
178
+ disable_web_page_preview: true,
179
+ };
180
+ if (replyMarkup)
181
+ params.reply_markup = replyMarkup;
182
+ const res = await telegramApi('editMessageText', params);
183
+ if (!res.ok && (res.error_code === 429 || res.description?.includes('Too Many Requests'))) {
184
+ const retryAfterSec = res.parameters?.retry_after || 5;
185
+ const waitMs = Math.min(retryAfterSec * 1000, 30000);
186
+ _logger.warn(`${PLUGIN_TAG} editTg 429 — waiting ${waitMs}ms`);
187
+ await new Promise(r => setTimeout(r, waitMs));
188
+ const retry = await telegramApi('editMessageText', params);
189
+ if (retry.ok)
190
+ return retry;
191
+ }
192
+ if (res.ok)
193
+ return res;
194
+ const plain = text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '');
195
+ const fallback = {
196
+ chat_id: chatId,
197
+ message_id: messageId,
198
+ text: plain || 'Session update',
199
+ disable_web_page_preview: true,
200
+ };
201
+ if (replyMarkup)
202
+ fallback.reply_markup = replyMarkup;
203
+ return telegramApi('editMessageText', fallback);
204
+ }
205
+ catch {
206
+ return { ok: false };
207
+ }
208
+ }
209
+ // ── Per-session Telegram message queue ────────────────────────────────────
210
+ export class TelegramMessageQueue {
211
+ chatId;
212
+ threadId;
213
+ cardMessageId;
214
+ _replyToMessageId;
215
+ /**
216
+ * Sticky flag — set true the first time `sendMessage()` is called for this
217
+ * queue instance. Lets the fast-turn fallback in `applyPatch()` detect that
218
+ * a new message was already dispatched by the pipeline's `onNewMessage`
219
+ * callback and avoid double-firing. Bug context: 2026-04-29 doubled-greeting
220
+ * on `/new` was caused by both the pipeline AND the fallback emitting the
221
+ * same model output as separate Telegram messages.
222
+ */
223
+ messageSent;
224
+ _cardChain;
225
+ _msgChain;
226
+ constructor(chatId, threadId, existingCardMessageId, replyToMessageId) {
227
+ this.chatId = chatId;
228
+ this.threadId = threadId;
229
+ this._replyToMessageId = replyToMessageId || null;
230
+ this.cardMessageId = existingCardMessageId || null;
231
+ this.messageSent = false;
232
+ this._cardChain = Promise.resolve();
233
+ this._msgChain = Promise.resolve();
234
+ }
235
+ /** Send or edit the session card. */
236
+ updateCard(text, replyMarkup) {
237
+ this._cardChain = this._cardChain.then(async () => {
238
+ if (this.cardMessageId) {
239
+ await editTg(this.chatId, this.cardMessageId, text, replyMarkup);
240
+ }
241
+ else {
242
+ const res = await sendTg(this.chatId, text, this.threadId, replyMarkup, this._replyToMessageId);
243
+ if (res.ok && res.result?.message_id) {
244
+ this.cardMessageId = res.result.message_id;
245
+ this._replyToMessageId = null;
246
+ }
247
+ }
248
+ }).catch(e => _logger.error(`${PLUGIN_TAG} card update error: ${e.message}`));
249
+ }
250
+ /** Send a standalone message (error alert, completion summary). */
251
+ sendMessage(text) {
252
+ this.messageSent = true;
253
+ this._msgChain = this._msgChain.then(async () => {
254
+ await sendTg(this.chatId, text, this.threadId);
255
+ }).catch(e => _logger.error(`${PLUGIN_TAG} message send error: ${e.message}`));
256
+ }
257
+ }
258
+ export function bridgeEvent(event) {
259
+ const ts = new Date().toISOString();
260
+ if (event.type === 'tool_use') {
261
+ const parentToolUseId = event.parent_tool_use_id ||
262
+ event.tool?.parent_tool_use_id ||
263
+ event.parentToolUseId ||
264
+ '';
265
+ return {
266
+ kind: 'tool_call',
267
+ ts,
268
+ name: event.tool?.name || 'Unknown',
269
+ input: event.tool?.input || {},
270
+ toolUseId: event.id || event.tool_use_id || '',
271
+ parentToolUseId,
272
+ };
273
+ }
274
+ if (event.type === 'tool_result') {
275
+ return {
276
+ kind: 'tool_result',
277
+ ts,
278
+ toolUseId: event.tool_use_id || event.id || '',
279
+ toolName: event.tool_name || event.tool?.name || undefined,
280
+ content: event.content || event.output || '',
281
+ isError: Boolean(event.is_error || event.error),
282
+ parentToolUseId: event.parent_tool_use_id || event.parentToolUseId || '',
283
+ };
284
+ }
285
+ if (event.type === 'text') {
286
+ return {
287
+ kind: 'assistant',
288
+ ts,
289
+ text: event.result || '',
290
+ delta: true,
291
+ };
292
+ }
293
+ return null;
294
+ }
295
+ // ── Module-level logger (set at register() time) ──────────────────────────
296
+ export let _logger = console;
297
+ // ── UX module + patch state ───────────────────────────────────────────────
298
+ let uxModule = null;
299
+ let patchApplied = false;
300
+ const PATCH_MARKER = Symbol.for('telegram-ux-bridge:patched');
301
+ export function loadUxModule() {
302
+ if (!uxModule) {
303
+ uxModule = __require(UX_MODULE_PATH);
304
+ }
305
+ if (!buttonHandler) {
306
+ try {
307
+ const { ButtonHandler, NS_COMMANDS } = __require(`${UX_MODULE_PATH}/button-handler`);
308
+ buttonHandler = new ButtonHandler();
309
+ nsCommands = NS_COMMANDS || null;
310
+ }
311
+ catch { /* button-handler not available — buttons disabled */ }
312
+ }
313
+ return uxModule;
314
+ }
315
+ function _inputFingerprint(input) {
316
+ if (!input || typeof input !== 'object')
317
+ return { size: 0, sig: '' };
318
+ const inp = input;
319
+ const size = (Object.keys(inp).length +
320
+ (inp.command?.length || 0) +
321
+ (inp.new_string?.length || 0) +
322
+ (inp.old_string?.length || 0) +
323
+ (inp.content?.length || 0) +
324
+ (inp.pattern?.length || 0) +
325
+ (inp.query?.length || 0) +
326
+ (inp.prompt?.length || 0));
327
+ const fp = (s) => s ? `${s.slice(0, 16)}\x02${s.slice(-16)}` : '';
328
+ const sig = fp(inp.command) + '\x01' +
329
+ fp(inp.old_string) + '\x01' +
330
+ fp(inp.new_string) + '\x01' +
331
+ fp(inp.content) + '\x01' +
332
+ fp(inp.pattern) + '\x01' +
333
+ fp(inp.query) + '\x01' +
334
+ fp(inp.prompt);
335
+ return { size, sig };
336
+ }
337
+ // ── applyPatch ────────────────────────────────────────────────────────────
338
+ export async function applyPatch() {
339
+ if (patchApplied)
340
+ return;
341
+ patchApplied = true;
342
+ let SessionManager;
343
+ try {
344
+ const mod = await import(CC_PLUGIN_PATH);
345
+ const Cls = (mod.SessionManager || mod.default?.SessionManager);
346
+ SessionManager = Cls;
347
+ }
348
+ catch (err) {
349
+ _logger.error(`${PLUGIN_TAG} Cannot import SessionManager: ${err.message}`);
350
+ return;
351
+ }
352
+ if (!SessionManager) {
353
+ _logger.error(`${PLUGIN_TAG} SessionManager class not found in module`);
354
+ return;
355
+ }
356
+ try {
357
+ loadUxModule();
358
+ }
359
+ catch (err) {
360
+ _logger.error(`${PLUGIN_TAG} Cannot load telegram-ux module: ${err.message}`);
361
+ return;
362
+ }
363
+ const original = SessionManager.prototype.sendMessage;
364
+ if (!original) {
365
+ _logger.error(`${PLUGIN_TAG} sendMessage not found on SessionManager.prototype`);
366
+ return;
367
+ }
368
+ if (original[PATCH_MARKER]) {
369
+ _logger.warn(`${PLUGIN_TAG} sendMessage already patched — skipping to prevent message duplication`);
370
+ return;
371
+ }
372
+ // TODO(P2): `this` context is the SessionManager instance at runtime — complex shape
373
+ SessionManager.prototype.sendMessage = async function patchedSendMessage(name, message, options = {}) {
374
+ const __PERF_KEY = Symbol.for('claude-local:perf-timing');
375
+ const __perfSendStart = performance.now();
376
+ let __perfFirstEventAbsMs = null;
377
+ const __perfStore = globalThis[__PERF_KEY]
378
+ = globalThis[__PERF_KEY] || new Map();
379
+ const __perfBareKey = name.startsWith('openai-') ? name.slice(7) : name;
380
+ {
381
+ const prev = __perfStore.get(name) || __perfStore.get(__perfBareKey) || {};
382
+ __perfStore.set(name, { ...prev, sendMessageStartAbsMs: __perfSendStart });
383
+ }
384
+ let chatId = options.telegramChatId || '';
385
+ let threadId = options.telegramThreadId || undefined;
386
+ if (!chatId) {
387
+ const UX_META = Symbol.for('telegram-ux-bridge:metadata');
388
+ const shared = globalThis[UX_META]?.get(name);
389
+ if (shared) {
390
+ chatId = shared.chatId || '';
391
+ threadId = shared.threadId || undefined;
392
+ globalThis[UX_META].delete(name);
393
+ if (chatId) {
394
+ _logger.info(`${PLUGIN_TAG} Resolved telegram metadata from global store for session "${name}": chat=${chatId} thread=${threadId || 'none'}`);
395
+ }
396
+ }
397
+ }
398
+ if (!BOT_TOKEN || !chatId) {
399
+ return original.call(this, name, message, options);
400
+ }
401
+ // ── In-flight dedup ─────────────────────────────────────────────────────
402
+ // Doubled-greeting defensive guard: if patchedSendMessage is invoked twice
403
+ // within DEDUP_WINDOW_MS for the same (chatId, payload), short-circuit the
404
+ // duplicate so it doesn't reach Telegram. Keyed on chatId because the
405
+ // upstream double-call uses two different session names for the same
406
+ // chat-bound output — sessionName is unstable, chatId is invariant.
407
+ // Logs the first duplicate at WARN to surface the upstream double-call.
408
+ {
409
+ const _payloadHash = _hashPayload(message);
410
+ const _existing = _sendMessageDedup.get(chatId);
411
+ const _now = Date.now();
412
+ if (_existing && _existing.hash === _payloadHash && _existing.expiresAt > _now) {
413
+ _logger.warn(`${PLUGIN_TAG} Duplicate patchedSendMessage detected within ${DEDUP_WINDOW_MS}ms for chat=${chatId} session=${name} payloadHash=${_payloadHash} — short-circuiting (upstream double-call evidence)`);
414
+ return; // no queue, no telegramApi call
415
+ }
416
+ _sendMessageDedup.set(chatId, { hash: _payloadHash, expiresAt: _now + DEDUP_WINDOW_MS });
417
+ // Cheap GC: prune entries whose window has elapsed (keeps map small).
418
+ if (_sendMessageDedup.size > 64) {
419
+ for (const [k, v] of _sendMessageDedup) {
420
+ if (v.expiresAt <= _now)
421
+ _sendMessageDedup.delete(k);
422
+ }
423
+ }
424
+ }
425
+ // Mark this (chatId, message) so the state-machine's message_sending
426
+ // hook can cancel the native ACP dispatcher when it also fires for
427
+ // the same payload — that's the doubled-greeting cross-path bug.
428
+ markDispatchedToTelegram(chatId, message);
429
+ // F3 — Quota auto-pause
430
+ if (QUOTA_PAUSE_PCT > 0 && QUOTA_PAUSE_PCT <= 100) {
431
+ const pct7d = latestQuotaState?.sevenDay?.usedPercent;
432
+ if (typeof pct7d === 'number' && pct7d >= QUOTA_PAUSE_PCT) {
433
+ const lastNotif = _pauseNotifiedAt.get(chatId) || 0;
434
+ if (Date.now() - lastNotif > PAUSE_NOTIFY_COOLDOWN_MS) {
435
+ _pauseNotifiedAt.set(chatId, Date.now());
436
+ const note = `⏸ Weekly plan quota at ${pct7d}% — dispatch paused (threshold ${QUOTA_PAUSE_PCT}%). Set OPENCLAW_QUOTA_PAUSE_PCT=100 to disable.`;
437
+ telegramApi('sendMessage', {
438
+ chat_id: chatId,
439
+ text: note,
440
+ ...(threadId ? { message_thread_id: Number(threadId) } : {}),
441
+ }).catch(() => { });
442
+ }
443
+ _logger.warn(`${PLUGIN_TAG} Quota pause: 7d=${pct7d}% threshold=${QUOTA_PAUSE_PCT}% name=${name}`);
444
+ const err = new Error(`quota-paused: 7d ${pct7d}% ≥ ${QUOTA_PAUSE_PCT}%`);
445
+ err.code = 'QUOTA_PAUSED';
446
+ throw err;
447
+ }
448
+ }
449
+ const { TelegramUXPipeline } = loadUxModule();
450
+ const ackMessageId = options.telegramAckMessageId || null;
451
+ const queue = new TelegramMessageQueue(chatId, threadId, ackMessageId);
452
+ const delegatedInfo = delegatedCardStore.get(name);
453
+ if (delegatedInfo && !ackMessageId) {
454
+ queue._replyToMessageId = delegatedInfo.cardMessageId;
455
+ }
456
+ let model = 'claude';
457
+ let permissionMode = null;
458
+ try {
459
+ const managed = this._getSession?.(name);
460
+ model = (managed?.config?.model || managed?.session?.options?.model || 'claude');
461
+ permissionMode = managed?.config?.permissionMode || null;
462
+ }
463
+ catch { /* ignore */ }
464
+ const sessionType = name.startsWith('cc-') ? 'cc'
465
+ : name.startsWith('openai-') ? 'savvy'
466
+ : 'delegated';
467
+ let sessionRef = null;
468
+ try {
469
+ sessionRef = this._getSession?.(name)?.session || null;
470
+ }
471
+ catch { /* session may not exist yet */ }
472
+ const pipeline = new TelegramUXPipeline({
473
+ sessionName: name,
474
+ model,
475
+ chatKey: `telegram:${chatId}`,
476
+ verbosityStorePath: VERBOSITY_STORE,
477
+ editIntervalMs: 5000,
478
+ onCardUpdate: (text, state) => {
479
+ const markup = buttonHandler ? buttonHandler.buildButtons(state, name) : undefined;
480
+ queue.updateCard(text, markup);
481
+ },
482
+ onNewMessage: (text) => queue.sendMessage(text),
483
+ livenessCheck: () => {
484
+ const pid = sessionRef?.pid || this._getSession?.(name)?.session?.pid;
485
+ if (!pid)
486
+ return true;
487
+ try {
488
+ process.kill(pid, 0);
489
+ return true;
490
+ }
491
+ catch {
492
+ return false;
493
+ }
494
+ },
495
+ });
496
+ pipeline.start();
497
+ // Stall auto-kill
498
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
499
+ const sessionMgr = this;
500
+ let consecutiveStalls = 0;
501
+ const MAX_STALLS_BEFORE_KILL = 3;
502
+ const origHandleStall = pipeline._handleStall.bind(pipeline);
503
+ const origHandleRecovery = pipeline._handleStallRecovery.bind(pipeline);
504
+ pipeline._handleStall = (idleMs) => {
505
+ consecutiveStalls++;
506
+ origHandleStall(idleMs);
507
+ const alive = pipeline._livenessCheck ? pipeline._livenessCheck() : true;
508
+ if (!alive || consecutiveStalls >= MAX_STALLS_BEFORE_KILL) {
509
+ _logger.warn(`${PLUGIN_TAG} Auto-killing stalled session "${name}" (alive=${alive}, stalls=${consecutiveStalls})`);
510
+ queue.sendMessage(`⚠️ Session auto-stopped: ${!alive ? 'process exited' : `${consecutiveStalls} consecutive stalls with no activity`}`);
511
+ try {
512
+ sessionMgr.stopSession(name);
513
+ }
514
+ catch (e) {
515
+ _logger.warn(`${PLUGIN_TAG} Auto-kill stopSession error: ${e.message}`);
516
+ }
517
+ }
518
+ };
519
+ pipeline._handleStallRecovery = () => {
520
+ consecutiveStalls = 0;
521
+ origHandleRecovery();
522
+ };
523
+ activeSessions.set(name, { pipeline, queue, name });
524
+ if (permissionMode)
525
+ pipeline.setPermissionMode(permissionMode);
526
+ pipeline.setSessionType(sessionType);
527
+ // Status file polling
528
+ let statusPollTimer = null;
529
+ let resolvedStatusPid = null;
530
+ const _pollStatus = async () => {
531
+ try {
532
+ if (!sessionRef) {
533
+ sessionRef = this._getSession?.(name)?.session || null;
534
+ }
535
+ const stats = sessionRef?.getStats?.() || sessionRef?.stats;
536
+ if (stats) {
537
+ if (typeof stats.tokensIn === 'number') {
538
+ pipeline.setTokenUsage(stats.tokensIn, stats.tokensOut || 0);
539
+ }
540
+ if (typeof stats.contextPercent === 'number' && stats.contextPercent > 0) {
541
+ pipeline.setContextPercent(Math.round(stats.contextPercent));
542
+ }
543
+ if (typeof stats.cachedTokens === 'number' && stats.cachedTokens > 0) {
544
+ const totalIn = (stats.tokensIn || 0) + (stats.cachedTokens || 0);
545
+ const pct = totalIn > 0 ? (stats.cachedTokens / totalIn) * 100 : null;
546
+ pipeline.setCachedTokens(stats.cachedTokens, pct);
547
+ }
548
+ }
549
+ const sessionModel = sessionRef?.options?.model;
550
+ if (sessionModel)
551
+ pipeline.setModel(sessionModel);
552
+ if (minimaxTracker) {
553
+ const mmUsage = minimaxTracker.getUsage();
554
+ if (mmUsage.used > 0) {
555
+ pipeline.setMiniMaxUsage(mmUsage);
556
+ }
557
+ }
558
+ }
559
+ catch { /* session not ready yet */ }
560
+ try {
561
+ if (!resolvedStatusPid) {
562
+ resolvedStatusPid = sessionRef?.pid || this._getSession?.(name)?.session?.pid || null;
563
+ }
564
+ if (!resolvedStatusPid) {
565
+ statusPollTimer = setTimeout(_pollStatus, STATUS_POLL_MS);
566
+ return;
567
+ }
568
+ const statusFile = `${STATUS_TEE_DIR}/${resolvedStatusPid}.json`;
569
+ const raw = await readFileAsync(statusFile, 'utf8');
570
+ const data = JSON.parse(raw);
571
+ const ctxPct = data?.context_window?.used_percentage;
572
+ if (typeof ctxPct === 'number')
573
+ pipeline.setContextPercent(Math.round(ctxPct));
574
+ const rateLimits = data?.rate_limits;
575
+ const quotaPct = rateLimits?.five_hour?.used_percentage;
576
+ const quotaReset = rateLimits?.five_hour?.resets_at;
577
+ if (typeof quotaPct === 'number')
578
+ pipeline.setQuotaPercent(Math.round(quotaPct), quotaReset || null);
579
+ const quotaPct7d = rateLimits?.seven_day?.used_percentage;
580
+ const quotaReset7d = rateLimits?.seven_day?.resets_at;
581
+ if (typeof quotaPct7d === 'number')
582
+ pipeline.setQuota7d(Math.round(quotaPct7d), quotaReset7d || null);
583
+ const modelData = data?.model;
584
+ const modelName = modelData?.display_name || modelData?.model_id;
585
+ if (modelName)
586
+ pipeline.setModel(modelName);
587
+ const ctxSize = data?.context_window?.context_window_size;
588
+ if (typeof ctxSize === 'number' && ctxSize > 0)
589
+ pipeline.setContextWindowSize(ctxSize);
590
+ const usage = data?.context_window?.current_usage;
591
+ const inTok = usage?.input_tokens;
592
+ const outTok = usage?.output_tokens;
593
+ if (typeof inTok === 'number' && typeof outTok === 'number')
594
+ pipeline.setTokenUsage(inTok, outTok);
595
+ }
596
+ catch { /* status file doesn't exist or parse error — normal for pipe mode */ }
597
+ statusPollTimer = setTimeout(_pollStatus, STATUS_POLL_MS);
598
+ };
599
+ statusPollTimer = setTimeout(_pollStatus, STATUS_POLL_MS);
600
+ // GPT/Codex usage polling
601
+ let gptPollTimer = null;
602
+ try {
603
+ const _ux = loadUxModule();
604
+ const { getCodexUsage } = (_ux.getCodexUsage
605
+ ? _ux
606
+ : __require(`${UX_MODULE_PATH}/codex-usage`));
607
+ const pollGpt = async () => {
608
+ try {
609
+ const usage = await getCodexUsage();
610
+ if (usage)
611
+ pipeline.setGptUsage(usage);
612
+ }
613
+ catch { /* ignore */ }
614
+ };
615
+ const initialUsage = await getCodexUsage().catch(() => null);
616
+ if (initialUsage)
617
+ pipeline.setGptUsage(initialUsage);
618
+ const lastUsedMs = initialUsage?.lastUsed || 0;
619
+ if (Date.now() - lastUsedMs < 24 * 60 * 60 * 1000) {
620
+ gptPollTimer = setInterval(pollGpt, GPT_POLL_MS);
621
+ }
622
+ }
623
+ catch { /* codex-usage module not available — skip */ }
624
+ // Anthropic OAuth quota polling
625
+ let quotaPollTimer = null;
626
+ try {
627
+ const { QuotaFetcher } = __require(`${UX_MODULE_PATH}/quota-fetcher`);
628
+ const quotaFetcher = new QuotaFetcher();
629
+ quotaFetcher.startPolling(QUOTA_POLL_MS, (quota) => {
630
+ latestQuotaState = quota;
631
+ if (quota.fiveHour && quota.fiveHour.usedPercent != null) {
632
+ pipeline.setQuotaPercent(quota.fiveHour.usedPercent, quota.fiveHour.resetsAt);
633
+ }
634
+ if (quota.sevenDay && quota.sevenDay.usedPercent != null) {
635
+ pipeline.setQuota7d(quota.sevenDay.usedPercent, quota.sevenDay.resetsAt);
636
+ }
637
+ });
638
+ quotaPollTimer = quotaFetcher;
639
+ }
640
+ catch { /* quota-fetcher module not available — skip */ }
641
+ let capturedResult = null;
642
+ const resultListener = (event) => { capturedResult = event; };
643
+ try {
644
+ sessionRef = this._getSession?.(name)?.session || null;
645
+ if (sessionRef?.on) {
646
+ sessionRef.on('result', resultListener);
647
+ }
648
+ }
649
+ catch { /* ignore */ }
650
+ const originalOnEvent = options.onEvent;
651
+ const toolUseInputSize = new Map();
652
+ options.onEvent = (event) => {
653
+ if (event.type === 'tool_use') {
654
+ const id = event.id || event.tool_use_id || '';
655
+ if (id) {
656
+ const curr = _inputFingerprint(event.tool?.input);
657
+ const prev = toolUseInputSize.get(id);
658
+ if (prev && curr.size <= prev.size && curr.sig === prev.sig) {
659
+ if (originalOnEvent) {
660
+ try {
661
+ originalOnEvent(event);
662
+ }
663
+ catch { /* ignore */ }
664
+ }
665
+ return;
666
+ }
667
+ toolUseInputSize.set(id, curr);
668
+ }
669
+ }
670
+ if (__perfFirstEventAbsMs == null) {
671
+ __perfFirstEventAbsMs = performance.now();
672
+ }
673
+ const entry = bridgeEvent(event);
674
+ if (entry) {
675
+ pipeline.processTranscriptEntry(entry);
676
+ }
677
+ if (originalOnEvent) {
678
+ try {
679
+ originalOnEvent(event);
680
+ }
681
+ catch { /* ignore */ }
682
+ }
683
+ };
684
+ try {
685
+ const result = await original.call(this, name, message, options);
686
+ try {
687
+ const stats = sessionRef?.stats || {};
688
+ const resultEntry = {
689
+ kind: 'result',
690
+ ts: new Date().toISOString(),
691
+ // Fallback chain matches savvy's ux-bridge.js: result.output → capturedResult.result → ''
692
+ text: result?.output ||
693
+ capturedResult?.result ||
694
+ '',
695
+ inputTokens: capturedResult?.usage?.input_tokens || stats.tokensIn || 0,
696
+ outputTokens: capturedResult?.usage?.output_tokens || stats.tokensOut || 0,
697
+ cachedTokens: capturedResult?.usage?.cache_read_input_tokens || stats.cachedTokens || 0,
698
+ costUsd: stats.costUsd || 0,
699
+ };
700
+ pipeline.finish(resultEntry);
701
+ // Fast-turn fallback: when the pipeline's gate suppresses card posting
702
+ // (no tools, <20s thinking, no files/errors), pipeline.finish() returns
703
+ // silently and the ack message is never edited. For plain Q&A turns
704
+ // (/cc how r u), this would silently drop the reply. If the card was
705
+ // never posted AND no message was sent through the queue AND we have
706
+ // an ack to edit AND a text result, deliver the result directly via
707
+ // queue.sendMessage() — which edits the ack in-place since
708
+ // cardMessageId is set. Closes a real bug surfaced in production
709
+ // 2026-04-27.
710
+ //
711
+ // Bug fix 2026-04-29: also gate on `queue.messageSent` to avoid
712
+ // double-firing when the pipeline's `onNewMessage` callback already
713
+ // delivered the result. Without this gate, fast turns like the
714
+ // /new greeting were dispatched twice (pipeline+fallback) — observed
715
+ // as two `[telegram] sendMessage ok` calls within ~350ms for one
716
+ // model output.
717
+ const _cardNeverPosted = pipeline._lastCardEditAt === 0;
718
+ const _resultText = resultEntry.text || '';
719
+ if (_cardNeverPosted && !queue.messageSent && ackMessageId && _resultText) {
720
+ // queue.sendMessage is fire-and-forget (returns void); errors are swallowed by the queue's own _msgChain.
721
+ queue.sendMessage(_resultText);
722
+ _logger.info(`${PLUGIN_TAG} fast-turn fallback: delivered result via queue.sendMessage (card gate suppressed) session=${name}`);
723
+ }
724
+ try {
725
+ const state = pipeline._cardState?.getState?.() || {};
726
+ const metricsDir = '/home/a1xai/.openclaw/workspace/memory';
727
+ mkdirSync(metricsDir, { recursive: true });
728
+ const __perfEnd = performance.now();
729
+ const __perfEntry = __perfStore.get(name) || __perfStore.get(__perfBareKey) || {};
730
+ const perfFields = {
731
+ perfRoutePatchMs: __perfEntry.routePatchMs ?? null,
732
+ perfRouteToSendMs: __perfEntry.routePatchEndAbsMs != null
733
+ ? Math.round(__perfSendStart - __perfEntry.routePatchEndAbsMs)
734
+ : null,
735
+ perfTtftMs: __perfFirstEventAbsMs != null
736
+ ? Math.round(__perfFirstEventAbsMs - __perfSendStart)
737
+ : null,
738
+ perfSendMessageMs: Math.round(__perfEnd - __perfSendStart),
739
+ perfTurnTotalMs: __perfEntry.routePatchEndAbsMs != null
740
+ ? Math.round(__perfEnd - (__perfEntry.routePatchEndAbsMs - (__perfEntry.routePatchMs || 0)))
741
+ : null,
742
+ };
743
+ if (perfFields.perfRoutePatchMs != null) {
744
+ perfFields.perfRoutePatchMs = Math.round(perfFields.perfRoutePatchMs);
745
+ }
746
+ __perfStore.delete(name);
747
+ __perfStore.delete(__perfBareKey);
748
+ const metricsLine = JSON.stringify({
749
+ ts: new Date().toISOString(),
750
+ sessionName: name,
751
+ chatId: chatId || null,
752
+ durationMs: state.elapsedMs || 0,
753
+ toolCount: state.toolCount || 0,
754
+ toolsByCategory: state.toolSummary || {},
755
+ errorCount: state.errorCount || 0,
756
+ stallCount: state.stallCount || 0,
757
+ thinkingMs: state.thinkingMs || 0,
758
+ filesModifiedCount: (state.filesModified || []).length,
759
+ subagentCount: (state.subagents || []).length,
760
+ inputTokens: resultEntry.inputTokens,
761
+ outputTokens: resultEntry.outputTokens,
762
+ cachedTokens: resultEntry.cachedTokens,
763
+ status: state.status || 'done',
764
+ ...perfFields,
765
+ }) + '\n';
766
+ setImmediate(() => {
767
+ try {
768
+ appendFileSync(`${metricsDir}/session-metrics.jsonl`, metricsLine);
769
+ }
770
+ catch { /* metrics advisory — non-fatal */ }
771
+ });
772
+ }
773
+ catch { /* metrics are advisory — never block on failure */ }
774
+ }
775
+ catch {
776
+ pipeline.finish(null);
777
+ }
778
+ return result;
779
+ }
780
+ catch (err) {
781
+ pipeline.abort();
782
+ throw err;
783
+ }
784
+ finally {
785
+ if (statusPollTimer)
786
+ clearTimeout(statusPollTimer);
787
+ if (gptPollTimer)
788
+ clearInterval(gptPollTimer);
789
+ if (quotaPollTimer)
790
+ quotaPollTimer.stopPolling();
791
+ if (resolvedStatusPid) {
792
+ try {
793
+ unlinkSync(`${STATUS_TEE_DIR}/${resolvedStatusPid}.json`);
794
+ }
795
+ catch { /* ignore */ }
796
+ }
797
+ if (sessionRef?.removeListener) {
798
+ sessionRef.removeListener('result', resultListener);
799
+ }
800
+ activeSessions.delete(name);
801
+ recentlyFinishedSessions.set(name, { pipeline, queue, name, finishedAt: Date.now() });
802
+ setTimeout(() => {
803
+ const e = recentlyFinishedSessions.get(name);
804
+ if (e && Date.now() - e.finishedAt >= FINISHED_SESSION_TTL_MS) {
805
+ recentlyFinishedSessions.delete(name);
806
+ }
807
+ }, FINISHED_SESSION_TTL_MS + 100);
808
+ const delegatedRef = delegatedCardStore.get(name);
809
+ if (delegatedRef) {
810
+ delegatedCardStore.delete(name);
811
+ const collapsedText = '➡️ *Savvy* — delegated \\(complete\\)';
812
+ editTg(delegatedRef.chatId, delegatedRef.cardMessageId, collapsedText, { inline_keyboard: [] })
813
+ .catch(e => _logger.warn(`${PLUGIN_TAG} delegated card collapse failed: ${e.message}`));
814
+ setTimeout(() => {
815
+ telegramApi('deleteMessage', {
816
+ chat_id: delegatedRef.chatId,
817
+ message_id: delegatedRef.cardMessageId,
818
+ }).catch(() => { });
819
+ }, 5 * 60 * 1000);
820
+ }
821
+ }
822
+ };
823
+ SessionManager.prototype.sendMessage[PATCH_MARKER] = true;
824
+ _logger.info(`${PLUGIN_TAG} SessionManager.sendMessage patched — UX pipeline active`);
825
+ }
826
+ // ── Stale status file cleanup ─────────────────────────────────────────────
827
+ export function cleanStaleStatusFiles() {
828
+ try {
829
+ const files = readdirSync(STATUS_TEE_DIR);
830
+ for (const file of files) {
831
+ if (!file.endsWith('.json'))
832
+ continue;
833
+ const pid = parseInt(file.replace('.json', ''), 10);
834
+ if (isNaN(pid))
835
+ continue;
836
+ try {
837
+ process.kill(pid, 0);
838
+ }
839
+ catch {
840
+ try {
841
+ unlinkSync(`${STATUS_TEE_DIR}/${file}`);
842
+ }
843
+ catch { /* ignore */ }
844
+ }
845
+ }
846
+ }
847
+ catch { /* directory doesn't exist — nothing to clean */ }
848
+ }
849
+ // ── register() ────────────────────────────────────────────────────────────
850
+ /**
851
+ * Register the live-card plugin hooks.
852
+ * Idempotent — subsequent calls are no-ops.
853
+ */
854
+ export function register(api) {
855
+ _logger = api.logger || console;
856
+ defaultRegisterGuard.guard('channels/telegram/live-card', api, () => {
857
+ // Read bot token from api.config first, then filesystem fallback
858
+ try {
859
+ const cfg = api.config || {};
860
+ const tgChannels = cfg.channels?.telegram;
861
+ if (tgChannels?.accounts) {
862
+ const acctKey = tgChannels.defaultAccount || 'default';
863
+ const acct = tgChannels.accounts[acctKey] || {};
864
+ BOT_TOKEN = acct.botToken || '';
865
+ }
866
+ }
867
+ catch { /* fall through to filesystem */ }
868
+ if (!BOT_TOKEN) {
869
+ try {
870
+ const raw = readFileSync(OPENCLAW_CONFIG_PATH, 'utf8');
871
+ const ocConfig = JSON.parse(raw);
872
+ const tgChannels = ocConfig.channels?.telegram;
873
+ if (tgChannels?.accounts) {
874
+ const acctKey = tgChannels.defaultAccount || 'default';
875
+ const acct = tgChannels.accounts[acctKey] || {};
876
+ BOT_TOKEN = acct.botToken || '';
877
+ }
878
+ if (BOT_TOKEN) {
879
+ _logger.info(`${PLUGIN_TAG} Telegram credentials loaded from ${OPENCLAW_CONFIG_PATH}`);
880
+ }
881
+ }
882
+ catch (err) {
883
+ _logger.warn(`${PLUGIN_TAG} Filesystem config fallback failed: ${err.message}`);
884
+ }
885
+ }
886
+ if (!BOT_TOKEN) {
887
+ _logger.warn(`${PLUGIN_TAG} No Telegram bot token found — plugin will be inactive`);
888
+ }
889
+ if (api.registerService) {
890
+ api.registerService({
891
+ id: 'telegram-ux-bridge',
892
+ start: async () => {
893
+ _logger.info(`${PLUGIN_TAG} Initialising via service lifecycle...`);
894
+ await applyPatch();
895
+ },
896
+ stop: () => {
897
+ _logger.info(`${PLUGIN_TAG} Stopped`);
898
+ },
899
+ });
900
+ }
901
+ if (BOT_TOKEN) {
902
+ if (isTestMode()) {
903
+ _logger.info(`${PLUGIN_TAG} TEST_MODE=1 — skipping eager SessionManager.sendMessage patch and stale status file cleanup`);
904
+ }
905
+ else {
906
+ _logger.info(`${PLUGIN_TAG} Eagerly applying SessionManager patch...`);
907
+ cleanStaleStatusFiles();
908
+ applyPatch().catch((err) => {
909
+ _logger.error(`${PLUGIN_TAG} Eager patch failed: ${err.message}`);
910
+ });
911
+ }
912
+ }
913
+ _logger.info(`${PLUGIN_TAG} live-card hooks registered`);
914
+ }); // end defaultRegisterGuard.guard
915
+ }
916
+ //# sourceMappingURL=live-card.js.map