@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,980 @@
1
+ /**
2
+ * cc-handler — OpenClaw plugin (TypeScript port of savvy-claude-code/cc-handler.js)
3
+ *
4
+ * Intercepts /cc <prompt> messages from Telegram via the before_dispatch hook,
5
+ * launches a Claude Code session through SessionManager, and lets the existing
6
+ * telegram-ux-bridge handle live card delivery.
7
+ *
8
+ * Supports per-session chat targeting and continuation:
9
+ * /cc <prompt> — start a new Claude Code session
10
+ * /cc+ <prompt> — continue the last session in this chat
11
+ * /cc continue <prompt> — alternative continuation syntax
12
+ * /cc status — check active session for this chat
13
+ * /cc stop — stop the active session for this chat
14
+ * /cc resume [id|slug] — resume by session ID (8-char hex) or slug
15
+ * /cc list — show resumable sessions for this chat
16
+ */
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import * as crypto from 'node:crypto';
20
+ import { request as httpsRequest } from 'node:https';
21
+ import { defaultRegisterGuard } from '../lib/register-guard.js';
22
+ import { VENDOR_FILES } from '../lib/vendor-paths.js';
23
+ // ── Constants ─────────────────────────────────────────────────────────────
24
+ const PLUGIN_TAG = '[cc-openclaw/command-router]';
25
+ const CC_PLUGIN_PATH = VENDOR_FILES.sessionManager;
26
+ const SESSIONS_DIR = '/home/a1xai/.openclaw/sessions';
27
+ const SESSIONS_INDEX_PATH = path.join(SESSIONS_DIR, 'index.json');
28
+ const DEFAULT_CWD = '/home/a1xai/.openclaw';
29
+ const IDLE_TIMEOUT_MS = 15 * 60 * 1000; // 15 min
30
+ const ACK_TIMEOUT_MS = 5_000;
31
+ // Max age for session recovery on warm restart.
32
+ const RECOVERY_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h
33
+ const OPENCLAW_CONFIG_PATH = '/home/a1xai/.openclaw/openclaw.json';
34
+ // ── Telegram Direct Send ──────────────────────────────────────────────────
35
+ let BOT_TOKEN = '';
36
+ function loadBotToken() {
37
+ if (BOT_TOKEN)
38
+ return;
39
+ try {
40
+ const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, 'utf8');
41
+ const cfg = JSON.parse(raw);
42
+ const tg = cfg.channels?.telegram;
43
+ if (tg?.accounts) {
44
+ const acctKey = tg.defaultAccount || 'default';
45
+ const acct = tg.accounts[acctKey] || {};
46
+ BOT_TOKEN = acct.botToken || '';
47
+ }
48
+ if (BOT_TOKEN)
49
+ logger.info(`${PLUGIN_TAG} Bot token loaded for direct replies`);
50
+ }
51
+ catch (err) {
52
+ logger.warn(`${PLUGIN_TAG} Failed to load bot token: ${err instanceof Error ? err.message : String(err)}`);
53
+ }
54
+ }
55
+ function telegramApiDirect(method, params) {
56
+ return new Promise((resolve, reject) => {
57
+ const body = JSON.stringify(params);
58
+ const options = {
59
+ hostname: 'api.telegram.org',
60
+ path: `/bot${BOT_TOKEN}/${method}`,
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ 'Content-Length': Buffer.byteLength(body),
65
+ },
66
+ };
67
+ const req = httpsRequest(options, (res) => {
68
+ let data = '';
69
+ res.on('data', (chunk) => (data += chunk));
70
+ res.on('end', () => {
71
+ try {
72
+ resolve(JSON.parse(data));
73
+ }
74
+ catch {
75
+ resolve({ ok: false });
76
+ }
77
+ });
78
+ });
79
+ req.on('error', reject);
80
+ req.setTimeout(10_000, () => req.destroy(new Error('Telegram API timeout')));
81
+ req.write(body);
82
+ req.end();
83
+ });
84
+ }
85
+ async function sendDirectReply(chatId, threadId, text) {
86
+ if (!BOT_TOKEN || !chatId)
87
+ return null;
88
+ try {
89
+ const params = {
90
+ chat_id: chatId,
91
+ text,
92
+ disable_web_page_preview: true,
93
+ };
94
+ if (threadId)
95
+ params.message_thread_id = Number(threadId);
96
+ const res = await telegramApiDirect('sendMessage', params);
97
+ if (!res.ok) {
98
+ logger.warn(`${PLUGIN_TAG} Direct reply failed: ${JSON.stringify(res.description || res)}`);
99
+ return null;
100
+ }
101
+ return res.result?.message_id ?? null;
102
+ }
103
+ catch (err) {
104
+ logger.warn(`${PLUGIN_TAG} Direct reply error: ${err instanceof Error ? err.message : String(err)}`);
105
+ return null;
106
+ }
107
+ }
108
+ // ── Slug Generator ────────────────────────────────────────────────────────
109
+ const FILLER_WORDS = new Set([
110
+ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
111
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
112
+ 'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for',
113
+ 'on', 'with', 'at', 'by', 'from', 'it', 'its', 'this', 'that', 'and',
114
+ 'or', 'but', 'not', 'so', 'if', 'then', 'than', 'just', 'also',
115
+ 'very', 'really', 'please', 'me', 'my', 'i',
116
+ ]);
117
+ function makeSlug(instruction) {
118
+ const tokens = instruction
119
+ .toLowerCase()
120
+ .replace(/[^a-z0-9\s-]/g, '')
121
+ .split(/\s+/)
122
+ .filter((t) => t.length > 0 && !FILLER_WORDS.has(t));
123
+ const suffix = Date.now().toString(36).slice(-4);
124
+ if (tokens.length === 0) {
125
+ return `task-${suffix}`;
126
+ }
127
+ let slug = tokens.slice(0, 4).join('-');
128
+ if (slug.length > 24) {
129
+ slug = slug.slice(0, 24).replace(/-$/, '');
130
+ }
131
+ return `${slug}-${suffix}`;
132
+ }
133
+ // ── Session ID Generator ──────────────────────────────────────────────────
134
+ function generateSessionId() {
135
+ return crypto.randomBytes(4).toString('hex');
136
+ }
137
+ // ── Session Index ─────────────────────────────────────────────────────────
138
+ let sessionIndex = {};
139
+ function loadSessionIndex() {
140
+ try {
141
+ if (fs.existsSync(SESSIONS_INDEX_PATH)) {
142
+ sessionIndex = JSON.parse(fs.readFileSync(SESSIONS_INDEX_PATH, 'utf8'));
143
+ }
144
+ }
145
+ catch {
146
+ sessionIndex = {};
147
+ }
148
+ }
149
+ function saveSessionIndex() {
150
+ try {
151
+ fs.mkdirSync(SESSIONS_DIR, { recursive: true });
152
+ const tmp = SESSIONS_INDEX_PATH + '.tmp';
153
+ fs.writeFileSync(tmp, JSON.stringify(sessionIndex, null, 2));
154
+ fs.renameSync(tmp, SESSIONS_INDEX_PATH);
155
+ }
156
+ catch { /* best effort */ }
157
+ }
158
+ // ── Session Store ─────────────────────────────────────────────────────────
159
+ function saveSession(meta) {
160
+ const dir = path.join(SESSIONS_DIR, meta.slug);
161
+ fs.mkdirSync(dir, { recursive: true });
162
+ const filePath = path.join(dir, 'session.json');
163
+ const tmp = filePath + '.tmp';
164
+ fs.writeFileSync(tmp, JSON.stringify(meta, null, 2));
165
+ fs.renameSync(tmp, filePath);
166
+ if (meta.id) {
167
+ sessionIndex[meta.id] = meta.slug;
168
+ saveSessionIndex();
169
+ }
170
+ }
171
+ function loadSession(slug) {
172
+ const filePath = path.join(SESSIONS_DIR, slug, 'session.json');
173
+ try {
174
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ }
180
+ function loadSessionById(id) {
181
+ const slug = sessionIndex[id];
182
+ if (slug)
183
+ return loadSession(slug);
184
+ const all = scanAllSessions();
185
+ const match = all.find((s) => s.id === id);
186
+ if (match) {
187
+ sessionIndex[id] = match.slug;
188
+ saveSessionIndex();
189
+ }
190
+ return match ?? null;
191
+ }
192
+ function scanAllSessions() {
193
+ const results = [];
194
+ try {
195
+ if (!fs.existsSync(SESSIONS_DIR))
196
+ return results;
197
+ const entries = fs.readdirSync(SESSIONS_DIR, { withFileTypes: true });
198
+ for (const entry of entries) {
199
+ if (!entry.isDirectory())
200
+ continue;
201
+ const meta = loadSession(entry.name);
202
+ if (meta && meta.slug && meta.chatId) {
203
+ results.push(meta);
204
+ }
205
+ }
206
+ }
207
+ catch {
208
+ // Best effort — if sessions dir is unreadable, return empty
209
+ }
210
+ return results;
211
+ }
212
+ // ── Plugin Entry ──────────────────────────────────────────────────────────
213
+ let logger = console;
214
+ let sessionManager = null;
215
+ const activeSessions = new Map();
216
+ function sessionMapKey(chatId, threadId) {
217
+ return threadId ? `${chatId}:${threadId}` : String(chatId);
218
+ }
219
+ // ── parseCcCommand — pure exported parser ─────────────────────────────────
220
+ /**
221
+ * Parse a raw input string into a CcCommand.
222
+ * Returns null if the input is not a /cc or /cc+ command.
223
+ *
224
+ * Recognised forms:
225
+ * /cc+ <prompt> → { subcommand: 'continue', prompt }
226
+ * /cc <prompt> → { subcommand: 'new', prompt }
227
+ * /cc continue <prompt> → { subcommand: 'continue', prompt }
228
+ * /cc stop → { subcommand: 'stop' }
229
+ * /cc resume [id|slug] → { subcommand: 'resume', target? }
230
+ * /cc status → { subcommand: 'status' }
231
+ * /cc list → { subcommand: 'list' }
232
+ */
233
+ export function parseCcCommand(input) {
234
+ if (!input)
235
+ return null;
236
+ const content = input.trim();
237
+ if (!content)
238
+ return null;
239
+ // /cc+ <prompt>
240
+ const ccPlusMatch = content.match(/^\/cc\+\s*([\s\S]+)/i);
241
+ if (ccPlusMatch) {
242
+ const prompt = ccPlusMatch[1].trim();
243
+ if (!prompt)
244
+ return null;
245
+ return { subcommand: 'continue', prompt };
246
+ }
247
+ // Must start with /cc
248
+ if (!/^\/cc(\s|$)/i.test(content))
249
+ return null;
250
+ const afterCc = content.slice(3).trim();
251
+ // /cc continue <prompt>
252
+ const continueMatch = afterCc.match(/^continue\s+([\s\S]+)/i);
253
+ if (continueMatch) {
254
+ const prompt = continueMatch[1].trim();
255
+ if (!prompt)
256
+ return null;
257
+ return { subcommand: 'continue', prompt };
258
+ }
259
+ // /cc stop
260
+ if (afterCc.toLowerCase() === 'stop') {
261
+ return { subcommand: 'stop' };
262
+ }
263
+ // /cc resume [id|slug]
264
+ const resumeMatch = afterCc.match(/^resume(?:\s+(.+))?$/i);
265
+ if (resumeMatch) {
266
+ const target = (resumeMatch[1] || '').trim() || undefined;
267
+ return { subcommand: 'resume', target };
268
+ }
269
+ // /cc status
270
+ if (afterCc.toLowerCase() === 'status') {
271
+ return { subcommand: 'status' };
272
+ }
273
+ // /cc list
274
+ if (afterCc.toLowerCase() === 'list') {
275
+ return { subcommand: 'list' };
276
+ }
277
+ // /cc <prompt> — new session (may be empty)
278
+ if (!afterCc)
279
+ return null;
280
+ return { subcommand: 'new', prompt: afterCc };
281
+ }
282
+ // ── Status Handler ────────────────────────────────────────────────────────
283
+ function buildPhase2FlagsLine() {
284
+ const allowBuiltins = process.env.CC_OPENCLAW_ALLOW_BUILTINS === '1';
285
+ const toolStream = process.env.CC_OPENCLAW_TOOL_STREAM === '1';
286
+ return `Flags: builtins=${allowBuiltins ? 'on' : 'off'} | toolstream=${toolStream ? 'on' : 'off'}`;
287
+ }
288
+ function handleStatus(chatId, threadId) {
289
+ const key = sessionMapKey(chatId, threadId);
290
+ const active = activeSessions.get(key);
291
+ const flagsLine = buildPhase2FlagsLine();
292
+ if (!active) {
293
+ const diskSessions = scanAllSessions()
294
+ .filter((s) => s.chatId === chatId &&
295
+ (s.threadId || undefined) === (threadId || undefined) &&
296
+ s.state !== 'failed')
297
+ .sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
298
+ if (diskSessions.length > 0) {
299
+ const last = diskSessions[0];
300
+ const id = last.id || '?';
301
+ return {
302
+ handled: true,
303
+ text: `No active session.\nLast: [${id}] ${last.slug} (${last.state}, ${last.turns || 0} turns)\nResume: /cc resume ${id}\n${flagsLine}`,
304
+ };
305
+ }
306
+ return { handled: true, text: `No active session in this chat.\n${flagsLine}` };
307
+ }
308
+ const elapsed = Math.round((Date.now() - new Date(active.meta.startedAt).getTime()) / 1000);
309
+ const turns = active.meta.turns || 1;
310
+ const id = active.meta.id || '?';
311
+ return {
312
+ handled: true,
313
+ text: `Active: [${id}] ${active.slug} (${turns} turn${turns > 1 ? 's' : ''}, ${elapsed}s)\n${flagsLine}`,
314
+ };
315
+ }
316
+ // ── List Handler ──────────────────────────────────────────────────────────
317
+ function handleList(chatId, threadId) {
318
+ const sessions = scanAllSessions()
319
+ .filter((s) => s.chatId === chatId &&
320
+ (s.threadId || undefined) === (threadId || undefined))
321
+ .sort((a, b) => {
322
+ const timeA = new Date(a.lastContinuedAt || a.completedAt || a.startedAt).getTime();
323
+ const timeB = new Date(b.lastContinuedAt || b.completedAt || b.startedAt).getTime();
324
+ return timeB - timeA;
325
+ })
326
+ .slice(0, 10);
327
+ if (sessions.length === 0) {
328
+ return { handled: true, text: 'No sessions found for this chat.' };
329
+ }
330
+ const lines = sessions.map((s) => {
331
+ const id = s.id || '--------';
332
+ const state = s.state || '?';
333
+ const turns = s.turns || 0;
334
+ const resumable = !!s.claudeSessionId;
335
+ const marker = resumable ? '✅' : '❌';
336
+ const prompt = (s.instruction || '').slice(0, 30);
337
+ return `${marker} ${id} ${s.slug} (${state}, ${turns}t) ${prompt}`;
338
+ });
339
+ return {
340
+ handled: true,
341
+ text: `Sessions (last 10):\n${lines.join('\n')}\n\nResume: /cc resume <id>`,
342
+ };
343
+ }
344
+ // ── Continuation Handler ──────────────────────────────────────────────────
345
+ function handleContinuation(prompt, chatId, threadId) {
346
+ if (!sessionManager) {
347
+ return {
348
+ handled: true,
349
+ text: 'Claude Code handler not ready — try again in a few seconds.',
350
+ };
351
+ }
352
+ const key = sessionMapKey(chatId, threadId);
353
+ const active = activeSessions.get(key);
354
+ if (!active) {
355
+ return {
356
+ handled: true,
357
+ text: 'No active session to continue. Start one with /cc <prompt> or /cc resume',
358
+ };
359
+ }
360
+ if (active.needsRehydration) {
361
+ if (!active.meta.claudeSessionId) {
362
+ activeSessions.delete(key);
363
+ return {
364
+ handled: true,
365
+ text: 'Recovered session has no Claude ID — start fresh with /cc <prompt>',
366
+ };
367
+ }
368
+ logger.info(`${PLUGIN_TAG} /cc+ rehydrating recovered session: slug=${active.slug} chat=${chatId} thread=${threadId || 'none'}`);
369
+ let resolveAckId;
370
+ const ackIdPromise = new Promise((r) => {
371
+ resolveAckId = r;
372
+ });
373
+ (async () => {
374
+ try {
375
+ await rehydrateSession(active.meta, chatId, threadId);
376
+ active.needsRehydration = false;
377
+ await continueTurn(active, prompt, chatId, threadId, ackIdPromise);
378
+ }
379
+ catch (err) {
380
+ logger.error(`${PLUGIN_TAG} Rehydrate+continue ${active.slug} failed: ${err instanceof Error ? err.message : String(err)}`);
381
+ scheduleIdle(key, active);
382
+ }
383
+ })();
384
+ return {
385
+ handled: true,
386
+ text: `⏳ [${active.meta.id || '?'}] Resuming & continuing ${active.slug}...`,
387
+ _resolveAckId: resolveAckId,
388
+ };
389
+ }
390
+ try {
391
+ sessionManager.getStatus(active.sessionName);
392
+ }
393
+ catch {
394
+ activeSessions.delete(key);
395
+ return {
396
+ handled: true,
397
+ text: 'Previous session expired. Use /cc resume or start fresh with /cc <prompt>',
398
+ };
399
+ }
400
+ if (active.idleTimer)
401
+ clearTimeout(active.idleTimer);
402
+ logger.info(`${PLUGIN_TAG} /cc+ continuation: slug=${active.slug} chat=${chatId} thread=${threadId || 'none'}`);
403
+ let resolveAckId;
404
+ const ackIdPromise = new Promise((r) => {
405
+ resolveAckId = r;
406
+ });
407
+ continueTurn(active, prompt, chatId, threadId, ackIdPromise).catch((err) => {
408
+ logger.error(`${PLUGIN_TAG} Continuation ${active.slug} failed: ${err instanceof Error ? err.message : String(err)}`);
409
+ scheduleIdle(key, active);
410
+ });
411
+ return {
412
+ handled: true,
413
+ text: `⏳ [${active.meta.id || '?'}] Continuing ${active.slug}...`,
414
+ _resolveAckId: resolveAckId,
415
+ };
416
+ }
417
+ // ── Stop Handler ──────────────────────────────────────────────────────────
418
+ function handleStop(chatId, threadId) {
419
+ if (!sessionManager) {
420
+ return { handled: true, text: 'Claude Code handler not ready.' };
421
+ }
422
+ const key = sessionMapKey(chatId, threadId);
423
+ const active = activeSessions.get(key);
424
+ if (!active) {
425
+ return { handled: true, text: 'No active session to stop.' };
426
+ }
427
+ const { sessionName, slug, meta } = active;
428
+ if (active.idleTimer)
429
+ clearTimeout(active.idleTimer);
430
+ activeSessions.delete(key);
431
+ (async () => {
432
+ try {
433
+ await sessionManager.stopSession(sessionName);
434
+ logger.info(`${PLUGIN_TAG} Session ${sessionName} stopped by /cc stop`);
435
+ }
436
+ catch (err) {
437
+ logger.warn(`${PLUGIN_TAG} stopSession(${sessionName}) error: ${err instanceof Error ? err.message : String(err)}`);
438
+ }
439
+ })();
440
+ meta.state = 'stopped';
441
+ meta.completedAt = new Date().toISOString();
442
+ try {
443
+ saveSession(meta);
444
+ }
445
+ catch { /* best effort */ }
446
+ const sessionId = meta.id || slug;
447
+ return {
448
+ handled: true,
449
+ text: `⏹ Stopped [${sessionId}] ${slug}.\nResume: /cc resume ${sessionId}`,
450
+ };
451
+ }
452
+ // ── Resume Handler ────────────────────────────────────────────────────────
453
+ function handleResume(chatId, threadId, targetSlug) {
454
+ if (!sessionManager) {
455
+ return { handled: true, text: 'Claude Code handler not ready.' };
456
+ }
457
+ const key = sessionMapKey(chatId, threadId);
458
+ const currentActive = activeSessions.get(key);
459
+ if (currentActive) {
460
+ return {
461
+ handled: true,
462
+ text: `Already have active session: ${currentActive.slug}\nUse /cc stop first, or /cc+ to continue it.`,
463
+ };
464
+ }
465
+ let meta = null;
466
+ if (targetSlug) {
467
+ const isSessionId = /^[0-9a-f]{8}$/i.test(targetSlug);
468
+ if (isSessionId) {
469
+ meta = loadSessionById(targetSlug.toLowerCase());
470
+ }
471
+ if (!meta) {
472
+ meta = loadSession(targetSlug);
473
+ }
474
+ if (!meta) {
475
+ return { handled: true, text: `Session "${targetSlug}" not found.` };
476
+ }
477
+ if (meta.chatId !== chatId) {
478
+ return {
479
+ handled: true,
480
+ text: `Session "${targetSlug}" belongs to a different chat.`,
481
+ };
482
+ }
483
+ }
484
+ else {
485
+ const candidates = scanAllSessions()
486
+ .filter((s) => s.chatId === chatId &&
487
+ (s.threadId || undefined) === (threadId || undefined) &&
488
+ !!s.claudeSessionId)
489
+ .sort((a, b) => {
490
+ const timeA = new Date(a.lastContinuedAt || a.completedAt || a.startedAt).getTime();
491
+ const timeB = new Date(b.lastContinuedAt || b.completedAt || b.startedAt).getTime();
492
+ return timeB - timeA;
493
+ });
494
+ if (candidates.length === 0) {
495
+ return {
496
+ handled: true,
497
+ text: 'No resumable session found. Start fresh with /cc <prompt>',
498
+ };
499
+ }
500
+ meta = candidates[0];
501
+ }
502
+ if (!meta.claudeSessionId) {
503
+ return {
504
+ handled: true,
505
+ text: `Session ${meta.slug} has no Claude session ID — cannot resume.\nStart fresh with /cc <prompt>`,
506
+ };
507
+ }
508
+ const { slug, id: resumeId } = meta;
509
+ logger.info(`${PLUGIN_TAG} /cc resume: id=${resumeId} slug=${slug} claudeId=${meta.claudeSessionId} chat=${chatId} thread=${threadId || 'none'}`);
510
+ rehydrateSession(meta, chatId, threadId).catch((err) => {
511
+ logger.error(`${PLUGIN_TAG} Resume ${slug} failed: ${err instanceof Error ? err.message : String(err)}`);
512
+ meta.state = 'failed';
513
+ meta.error = `Resume failed: ${err instanceof Error ? err.message : String(err)}`;
514
+ try {
515
+ saveSession(meta);
516
+ }
517
+ catch { /* best effort */ }
518
+ const active = activeSessions.get(key);
519
+ if (active?.slug === slug) {
520
+ if (active.idleTimer)
521
+ clearTimeout(active.idleTimer);
522
+ activeSessions.delete(key);
523
+ }
524
+ });
525
+ return { handled: true, text: `⏳ [${resumeId || '?'}] Resuming ${slug}...` };
526
+ }
527
+ // ── Session Launcher ──────────────────────────────────────────────────────
528
+ async function launchSession(sessionName, prompt, slug, meta, chatId, threadId, ackIdPromise) {
529
+ logger.info(`${PLUGIN_TAG} Starting session ${sessionName}...`);
530
+ const key = sessionMapKey(chatId, threadId);
531
+ const preAssignedId = crypto.randomUUID();
532
+ meta.claudeSessionId = preAssignedId;
533
+ meta.state = 'starting';
534
+ try {
535
+ saveSession(meta);
536
+ }
537
+ catch { /* best effort */ }
538
+ await sessionManager.startSession({
539
+ name: sessionName,
540
+ cwd: DEFAULT_CWD,
541
+ engine: 'claude',
542
+ permissionMode: 'bypassPermissions',
543
+ effort: 'high',
544
+ customSessionId: preAssignedId,
545
+ });
546
+ meta.state = 'running';
547
+ try {
548
+ saveSession(meta);
549
+ }
550
+ catch { /* best effort */ }
551
+ const prevActive = activeSessions.get(key);
552
+ if (prevActive && prevActive.sessionName !== sessionName) {
553
+ if (prevActive.idleTimer)
554
+ clearTimeout(prevActive.idleTimer);
555
+ try {
556
+ await sessionManager.stopSession(prevActive.sessionName);
557
+ }
558
+ catch { /* may already be gone */ }
559
+ }
560
+ const activeEntry = { sessionName, slug, meta, idleTimer: null };
561
+ activeSessions.set(key, activeEntry);
562
+ logger.info(`${PLUGIN_TAG} Session ${sessionName} started — sending prompt...`);
563
+ const opts = { telegramChatId: chatId };
564
+ if (threadId)
565
+ opts.telegramThreadId = threadId;
566
+ const ackMsgId = await Promise.race([
567
+ ackIdPromise,
568
+ new Promise((r) => setTimeout(() => r(null), ACK_TIMEOUT_MS)),
569
+ ]);
570
+ if (ackMsgId)
571
+ opts.telegramAckMessageId = ackMsgId;
572
+ const result = await sessionManager.sendMessage(sessionName, prompt, opts);
573
+ meta.state = 'idle';
574
+ meta.turns = 1;
575
+ meta.completedAt = new Date().toISOString();
576
+ meta.output = (result?.output || '').slice(0, 2000);
577
+ meta.claudeSessionId = result?.sessionId ?? meta.claudeSessionId;
578
+ try {
579
+ saveSession(meta);
580
+ }
581
+ catch { /* best effort */ }
582
+ logger.info(`${PLUGIN_TAG} Session ${sessionName} turn 1 complete — idle for ${IDLE_TIMEOUT_MS / 1000}s`);
583
+ scheduleIdle(key, activeEntry);
584
+ }
585
+ // ── Rehydrate Session ─────────────────────────────────────────────────────
586
+ async function rehydrateSession(meta, chatId, threadId) {
587
+ const { slug, sessionName, claudeSessionId } = meta;
588
+ const key = sessionMapKey(chatId, threadId);
589
+ logger.info(`${PLUGIN_TAG} Rehydrating ${sessionName} with claudeSessionId=${claudeSessionId}...`);
590
+ await sessionManager.startSession({
591
+ name: sessionName,
592
+ cwd: meta.cwd || DEFAULT_CWD,
593
+ engine: 'claude',
594
+ permissionMode: 'bypassPermissions',
595
+ effort: 'high',
596
+ resumeSessionId: claudeSessionId,
597
+ });
598
+ const prevActive = activeSessions.get(key);
599
+ if (prevActive && prevActive.sessionName !== sessionName) {
600
+ if (prevActive.idleTimer)
601
+ clearTimeout(prevActive.idleTimer);
602
+ try {
603
+ await sessionManager.stopSession(prevActive.sessionName);
604
+ }
605
+ catch { /* ignore */ }
606
+ }
607
+ meta.state = 'idle';
608
+ meta.threadId = threadId;
609
+ meta.lastContinuedAt = new Date().toISOString();
610
+ try {
611
+ saveSession(meta);
612
+ }
613
+ catch { /* best effort */ }
614
+ const activeEntry = { sessionName, slug, meta, idleTimer: null };
615
+ activeSessions.set(key, activeEntry);
616
+ logger.info(`${PLUGIN_TAG} Session ${sessionName} resumed — idle for ${IDLE_TIMEOUT_MS / 1000}s`);
617
+ scheduleIdle(key, activeEntry);
618
+ }
619
+ // ── Continuation Turn ─────────────────────────────────────────────────────
620
+ async function continueTurn(active, prompt, chatId, threadId, ackIdPromise) {
621
+ const { sessionName, meta } = active;
622
+ const key = sessionMapKey(chatId, threadId);
623
+ meta.state = 'running';
624
+ try {
625
+ saveSession(meta);
626
+ }
627
+ catch { /* best effort */ }
628
+ const opts = { telegramChatId: chatId };
629
+ if (threadId)
630
+ opts.telegramThreadId = threadId;
631
+ const ackMsgId = await Promise.race([
632
+ ackIdPromise,
633
+ new Promise((r) => setTimeout(() => r(null), ACK_TIMEOUT_MS)),
634
+ ]);
635
+ if (ackMsgId)
636
+ opts.telegramAckMessageId = ackMsgId;
637
+ const result = await sessionManager.sendMessage(sessionName, prompt, opts);
638
+ meta.state = 'idle';
639
+ meta.turns = (meta.turns || 1) + 1;
640
+ meta.lastContinuedAt = new Date().toISOString();
641
+ meta.output = (result?.output || '').slice(0, 2000);
642
+ meta.claudeSessionId = result?.sessionId ?? meta.claudeSessionId;
643
+ try {
644
+ saveSession(meta);
645
+ }
646
+ catch { /* best effort */ }
647
+ logger.info(`${PLUGIN_TAG} Session ${sessionName} turn ${meta.turns} complete`);
648
+ scheduleIdle(key, active);
649
+ }
650
+ // ── Idle Timer ────────────────────────────────────────────────────────────
651
+ function scheduleIdle(key, active) {
652
+ if (active.idleTimer)
653
+ clearTimeout(active.idleTimer);
654
+ active.idleTimer = setTimeout(() => {
655
+ logger.info(`${PLUGIN_TAG} Session ${active.sessionName} idle timeout — stopping`);
656
+ try {
657
+ sessionManager
658
+ ?.stopSession(active.sessionName)
659
+ .catch((e) => logger.warn(`${PLUGIN_TAG} idle stop error: ${e.message}`));
660
+ }
661
+ catch { /* may already be gone */ }
662
+ activeSessions.delete(key);
663
+ active.meta.state = 'idle';
664
+ active.meta.completedAt = new Date().toISOString();
665
+ try {
666
+ saveSession(active.meta);
667
+ }
668
+ catch { /* best effort */ }
669
+ }, IDLE_TIMEOUT_MS);
670
+ }
671
+ // ── Warm-Restart Recovery ─────────────────────────────────────────────────
672
+ function recoverSessions() {
673
+ const now = Date.now();
674
+ const allSessions = scanAllSessions();
675
+ let indexDirty = false;
676
+ for (const meta of allSessions) {
677
+ if (!meta.id) {
678
+ meta.id = generateSessionId();
679
+ sessionIndex[meta.id] = meta.slug;
680
+ indexDirty = true;
681
+ try {
682
+ saveSession(meta);
683
+ }
684
+ catch { /* best effort */ }
685
+ logger.info(`${PLUGIN_TAG} Backfilled session ID ${meta.id} for ${meta.slug}`);
686
+ }
687
+ else if (!sessionIndex[meta.id]) {
688
+ sessionIndex[meta.id] = meta.slug;
689
+ indexDirty = true;
690
+ }
691
+ }
692
+ if (indexDirty)
693
+ saveSessionIndex();
694
+ let recovered = 0;
695
+ for (const meta of allSessions) {
696
+ if (!['running', 'idle', 'timed_out'].includes(meta.state))
697
+ continue;
698
+ const lastActivity = new Date(meta.lastContinuedAt || meta.completedAt || meta.startedAt).getTime();
699
+ if (now - lastActivity > RECOVERY_MAX_AGE_MS)
700
+ continue;
701
+ if (!meta.claudeSessionId)
702
+ continue;
703
+ const recoverKey = sessionMapKey(meta.chatId, meta.threadId);
704
+ if (activeSessions.has(recoverKey))
705
+ continue;
706
+ if (meta.state === 'running') {
707
+ meta.state = 'interrupted';
708
+ try {
709
+ saveSession(meta);
710
+ }
711
+ catch { /* best effort */ }
712
+ }
713
+ const activeEntry = {
714
+ sessionName: meta.sessionName,
715
+ slug: meta.slug,
716
+ meta,
717
+ idleTimer: null,
718
+ needsRehydration: true,
719
+ };
720
+ activeSessions.set(recoverKey, activeEntry);
721
+ recovered++;
722
+ logger.info(`${PLUGIN_TAG} Recovered session ${meta.slug} for chat ${meta.chatId} thread=${meta.threadId || 'none'} (was ${meta.state === 'interrupted' ? 'running' : 'idle'})`);
723
+ }
724
+ if (recovered > 0) {
725
+ logger.info(`${PLUGIN_TAG} Warm-restart recovery: ${recovered} session(s) restored`);
726
+ }
727
+ else {
728
+ logger.info(`${PLUGIN_TAG} Warm-restart recovery: no recent sessions to restore`);
729
+ }
730
+ }
731
+ // ── Plugin Object & register() ────────────────────────────────────────────
732
+ /**
733
+ * Register the cc-handler plugin with the OpenClaw API.
734
+ * Idempotent: subsequent calls are no-ops (prevents listener stacking).
735
+ */
736
+ export function register(api) {
737
+ logger = api.logger || console;
738
+ defaultRegisterGuard.guard('command-router/cc-handler', api, () => {
739
+ // ── Read Claude Code plugin config from gateway ──────────────────────
740
+ const ccConfig = {
741
+ claudeBin: 'claude',
742
+ defaultPermissionMode: 'bypassPermissions',
743
+ defaultEffort: 'high',
744
+ maxConcurrentSessions: 5,
745
+ sessionTtlMinutes: 120,
746
+ };
747
+ try {
748
+ const pluginConfigs = api.config?.plugins?.configs ?? {};
749
+ const ccPluginConfig = pluginConfigs['openclaw-claude-code'] ?? {};
750
+ if (ccPluginConfig.claudeBin)
751
+ ccConfig.claudeBin = ccPluginConfig.claudeBin;
752
+ if (ccPluginConfig.defaultPermissionMode)
753
+ ccConfig.defaultPermissionMode = ccPluginConfig.defaultPermissionMode;
754
+ if (ccPluginConfig.defaultEffort)
755
+ ccConfig.defaultEffort = ccPluginConfig.defaultEffort;
756
+ if (ccPluginConfig.maxConcurrentSessions)
757
+ ccConfig.maxConcurrentSessions = ccPluginConfig.maxConcurrentSessions;
758
+ if (ccPluginConfig.sessionTtlMinutes)
759
+ ccConfig.sessionTtlMinutes = ccPluginConfig.sessionTtlMinutes;
760
+ }
761
+ catch {
762
+ logger.warn(`${PLUGIN_TAG} Could not read openclaw-claude-code config — using defaults`);
763
+ }
764
+ loadBotToken();
765
+ // ── Lifecycle service ────────────────────────────────────────────────
766
+ api.registerService({
767
+ id: 'telegram-cc-handler',
768
+ start: async () => {
769
+ logger.info(`${PLUGIN_TAG} Initialising SessionManager...`);
770
+ loadSessionIndex();
771
+ try {
772
+ // TODO(P2): refine import type when full plugin types land
773
+ const mod = await import(CC_PLUGIN_PATH);
774
+ const SessionManager = mod.SessionManager ??
775
+ mod.default?.SessionManager;
776
+ if (!SessionManager) {
777
+ logger.error(`${PLUGIN_TAG} SessionManager class not found — plugin inactive`);
778
+ return;
779
+ }
780
+ // ── Prevent orphan-cleanup race ───────────────────────────────────
781
+ // The vendored SessionManager runs `_cleanupOrphanedPids` in its
782
+ // constructor — reads the shared session-pids.json and kills any
783
+ // alive PID listed there. In our gateway, TWO SessionManagers are
784
+ // constructed: the eagerManager in cwd-patch.ts (for openai-compat)
785
+ // and this one (for /cc commands). The second construction's
786
+ // cleanup mistakes the first manager's prewarm session for an
787
+ // orphan and kills it, defeating the cold-start prewarm.
788
+ //
789
+ // Fix: make this second SessionManager's cleanup a no-op. The
790
+ // eagerManager (first to construct) already did the legitimate
791
+ // post-crash cleanup at boot. Any further cleanup would only kill
792
+ // peer-manager processes in the same gateway lifetime.
793
+ const _ProtoSM = SessionManager.prototype;
794
+ if (_ProtoSM._cleanupOrphanedPids && !_ProtoSM._cleanupOrphanedPids_orig) {
795
+ _ProtoSM._cleanupOrphanedPids_orig = _ProtoSM._cleanupOrphanedPids;
796
+ _ProtoSM._cleanupOrphanedPids = function () {
797
+ // no-op; eagerManager handles cleanup at boot
798
+ };
799
+ logger.info(`${PLUGIN_TAG} SessionManager._cleanupOrphanedPids no-oped (peer-manager protection)`);
800
+ }
801
+ sessionManager = new SessionManager(ccConfig);
802
+ logger.info(`${PLUGIN_TAG} SessionManager ready — /cc commands active`);
803
+ recoverSessions();
804
+ }
805
+ catch (err) {
806
+ logger.error(`${PLUGIN_TAG} Failed to init SessionManager: ${err instanceof Error ? err.message : String(err)}`);
807
+ }
808
+ },
809
+ stop: () => {
810
+ for (const [, active] of activeSessions) {
811
+ if (active.idleTimer)
812
+ clearTimeout(active.idleTimer);
813
+ }
814
+ activeSessions.clear();
815
+ if (sessionManager) {
816
+ sessionManager
817
+ .shutdown()
818
+ .catch((e) => logger.warn(`${PLUGIN_TAG} shutdown error: ${e.message}`));
819
+ sessionManager = null;
820
+ }
821
+ logger.info(`${PLUGIN_TAG} Stopped`);
822
+ },
823
+ });
824
+ // ── before_dispatch hook ─────────────────────────────────────────────
825
+ const ccInnerHandler = async (event, ctx) => {
826
+ if (event.channel !== 'telegram')
827
+ return undefined;
828
+ const content = (typeof event.content === 'string' ? event.content : '').trim();
829
+ const senderId = typeof event.senderId === 'string' ? event.senderId : '';
830
+ const rawConvId = (ctx.conversationId || '').replace(/^telegram:/i, '');
831
+ const topicMatch = rawConvId.match(/^(-?\d+):topic:(\d+)$/);
832
+ const chatId = topicMatch
833
+ ? topicMatch[1]
834
+ : /^-?\d+$/.test(rawConvId)
835
+ ? rawConvId
836
+ : senderId;
837
+ const threadId = topicMatch ? topicMatch[2] : undefined;
838
+ logger.info(`${PLUGIN_TAG} target: conv=${ctx.conversationId || 'empty'} chat=${chatId} thread=${threadId || 'none'} sender=${senderId} group=${event.isGroup}`);
839
+ // /cc+ <prompt>
840
+ const ccPlusMatch = content.match(/^\/cc\+\s*([\s\S]+)/i);
841
+ if (ccPlusMatch) {
842
+ const prompt = ccPlusMatch[1].trim();
843
+ if (!prompt)
844
+ return { handled: true, text: 'Usage: /cc+ <prompt>' };
845
+ return handleContinuation(prompt, chatId, threadId);
846
+ }
847
+ if (!content.match(/^\/cc(\s|$)/i))
848
+ return undefined;
849
+ const afterCc = content.slice(3).trim();
850
+ const continueMatch = afterCc.match(/^continue\s+([\s\S]+)/i);
851
+ if (continueMatch) {
852
+ const prompt = continueMatch[1].trim();
853
+ if (!prompt)
854
+ return { handled: true, text: 'Usage: /cc continue <prompt>' };
855
+ return handleContinuation(prompt, chatId, threadId);
856
+ }
857
+ if (afterCc.toLowerCase() === 'stop') {
858
+ return handleStop(chatId, threadId);
859
+ }
860
+ const resumeMatch = afterCc.match(/^resume(?:\s+(.+))?$/i);
861
+ if (resumeMatch) {
862
+ const targetSlug = (resumeMatch[1] || '').trim() || null;
863
+ return handleResume(chatId, threadId, targetSlug);
864
+ }
865
+ if (afterCc.toLowerCase() === 'status') {
866
+ return handleStatus(chatId, threadId);
867
+ }
868
+ if (afterCc.toLowerCase() === 'list') {
869
+ return handleList(chatId, threadId);
870
+ }
871
+ const prompt = afterCc;
872
+ if (!prompt) {
873
+ return {
874
+ handled: true,
875
+ text: [
876
+ 'Usage:',
877
+ ' /cc <prompt> — new session',
878
+ ' /cc+ <prompt> — continue',
879
+ ' /cc status — check active',
880
+ ' /cc stop — halt session',
881
+ ' /cc resume [id|slug] — resume previous',
882
+ ' /cc list — show resumable sessions',
883
+ ].join('\n'),
884
+ };
885
+ }
886
+ if (!sessionManager) {
887
+ return {
888
+ handled: true,
889
+ text: 'Claude Code handler not ready — try again in a few seconds.',
890
+ };
891
+ }
892
+ const slug = makeSlug(prompt);
893
+ const sessionId = generateSessionId();
894
+ const sessionName = `cc-${slug}`;
895
+ logger.info(`${PLUGIN_TAG} /cc claimed: id=${sessionId} slug=${slug} sender=${senderId} chat=${chatId}`);
896
+ const meta = {
897
+ id: sessionId,
898
+ slug,
899
+ sessionName,
900
+ chatId,
901
+ threadId,
902
+ senderId,
903
+ state: 'starting',
904
+ instruction: prompt,
905
+ turns: 0,
906
+ startedAt: new Date().toISOString(),
907
+ completedAt: null,
908
+ lastContinuedAt: null,
909
+ claudeSessionId: null,
910
+ output: null,
911
+ error: null,
912
+ };
913
+ try {
914
+ saveSession(meta);
915
+ }
916
+ catch (err) {
917
+ logger.error(`${PLUGIN_TAG} Failed to save session meta: ${err instanceof Error ? err.message : String(err)}`);
918
+ }
919
+ let resolveAckId;
920
+ const ackIdPromise = new Promise((r) => {
921
+ resolveAckId = r;
922
+ });
923
+ const launchKey = sessionMapKey(chatId, threadId);
924
+ launchSession(sessionName, prompt, slug, meta, chatId, threadId, ackIdPromise).catch((err) => {
925
+ logger.error(`${PLUGIN_TAG} Session ${sessionName} failed: ${err.message}`);
926
+ try {
927
+ const isTimeout = /timeout/i.test(err.message);
928
+ meta.state = isTimeout ? 'timed_out' : 'failed';
929
+ meta.error = err.message;
930
+ meta.completedAt = new Date().toISOString();
931
+ saveSession(meta);
932
+ }
933
+ catch { /* best effort */ }
934
+ const active = activeSessions.get(launchKey);
935
+ if (active?.sessionName === sessionName) {
936
+ if (active.idleTimer)
937
+ clearTimeout(active.idleTimer);
938
+ activeSessions.delete(launchKey);
939
+ }
940
+ });
941
+ return {
942
+ handled: true,
943
+ text: `⏳ [${sessionId}] ${slug} — starting Claude Code session...`,
944
+ _resolveAckId: resolveAckId,
945
+ };
946
+ };
947
+ api.on('before_dispatch', async (...args) => {
948
+ const event = args[0];
949
+ const ctx = args[1];
950
+ const result = await ccInnerHandler(event, ctx);
951
+ if (!result?.handled)
952
+ return result;
953
+ if (event && typeof event === 'object')
954
+ event.content = '';
955
+ if (result.text && BOT_TOKEN) {
956
+ const convId = (ctx.conversationId || '').replace(/^telegram:/i, '');
957
+ const tm = convId.match(/^(-?\d+):topic:(\d+)$/);
958
+ const ackChat = tm
959
+ ? tm[1]
960
+ : /^-?\d+$/.test(convId)
961
+ ? convId
962
+ : event.senderId || '';
963
+ const ackThread = tm ? tm[2] : undefined;
964
+ logger.info(`${PLUGIN_TAG} direct-send ack to chat=${ackChat} thread=${ackThread || 'none'}`);
965
+ const ackMessageId = await sendDirectReply(ackChat, ackThread, result.text);
966
+ if (result._resolveAckId) {
967
+ result._resolveAckId(ackMessageId ?? null);
968
+ }
969
+ }
970
+ else if (result._resolveAckId) {
971
+ result._resolveAckId(null);
972
+ }
973
+ return { handled: true, text: '' };
974
+ });
975
+ logger.info(`${PLUGIN_TAG} before_dispatch hook registered (/cc, /cc+, /cc stop, /cc resume, /cc status, /cc list)`);
976
+ }); // end defaultRegisterGuard.guard
977
+ }
978
+ // Default export compatible with the original plugin object shape
979
+ export default { id: 'telegram-cc-handler', name: 'Telegram /cc Handler', register };
980
+ //# sourceMappingURL=cc-handler.js.map