@iflow-mcp/jkheadley-instar 0.26.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 (1671) hide show
  1. package/.claude/hooks/free-text-guard.sh +117 -0
  2. package/.claude/settings.json +201 -0
  3. package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +234 -0
  4. package/.claude/skills/autonomous/hooks/hooks.json +15 -0
  5. package/.claude/skills/autonomous/scripts/setup-autonomous.sh +166 -0
  6. package/.claude/skills/autonomous/skill.md +254 -0
  7. package/.claude/skills/secret-setup/skill.md +210 -0
  8. package/.claude/skills/setup-wizard/skill.md +2132 -0
  9. package/LICENSE +21 -0
  10. package/README.md +214 -0
  11. package/dashboard/favicon.png +0 -0
  12. package/dashboard/index.html +5740 -0
  13. package/dashboard/logo.png +0 -0
  14. package/dist/cli.d.ts +21 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +1782 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/commands/backup.d.ts +16 -0
  19. package/dist/commands/backup.d.ts.map +1 -0
  20. package/dist/commands/backup.js +84 -0
  21. package/dist/commands/backup.js.map +1 -0
  22. package/dist/commands/discovery.d.ts +158 -0
  23. package/dist/commands/discovery.d.ts.map +1 -0
  24. package/dist/commands/discovery.js +532 -0
  25. package/dist/commands/discovery.js.map +1 -0
  26. package/dist/commands/git.d.ts +25 -0
  27. package/dist/commands/git.d.ts.map +1 -0
  28. package/dist/commands/git.js +152 -0
  29. package/dist/commands/git.js.map +1 -0
  30. package/dist/commands/init.d.ts +52 -0
  31. package/dist/commands/init.d.ts.map +1 -0
  32. package/dist/commands/init.js +4211 -0
  33. package/dist/commands/init.js.map +1 -0
  34. package/dist/commands/intent.d.ts +30 -0
  35. package/dist/commands/intent.d.ts.map +1 -0
  36. package/dist/commands/intent.js +349 -0
  37. package/dist/commands/intent.js.map +1 -0
  38. package/dist/commands/job.d.ts +42 -0
  39. package/dist/commands/job.d.ts.map +1 -0
  40. package/dist/commands/job.js +202 -0
  41. package/dist/commands/job.js.map +1 -0
  42. package/dist/commands/knowledge.d.ts +25 -0
  43. package/dist/commands/knowledge.d.ts.map +1 -0
  44. package/dist/commands/knowledge.js +127 -0
  45. package/dist/commands/knowledge.js.map +1 -0
  46. package/dist/commands/machine.d.ts +53 -0
  47. package/dist/commands/machine.d.ts.map +1 -0
  48. package/dist/commands/machine.js +680 -0
  49. package/dist/commands/machine.js.map +1 -0
  50. package/dist/commands/memory.d.ts +24 -0
  51. package/dist/commands/memory.d.ts.map +1 -0
  52. package/dist/commands/memory.js +163 -0
  53. package/dist/commands/memory.js.map +1 -0
  54. package/dist/commands/nuke.d.ts +22 -0
  55. package/dist/commands/nuke.d.ts.map +1 -0
  56. package/dist/commands/nuke.js +216 -0
  57. package/dist/commands/nuke.js.map +1 -0
  58. package/dist/commands/org.d.ts +16 -0
  59. package/dist/commands/org.d.ts.map +1 -0
  60. package/dist/commands/org.js +69 -0
  61. package/dist/commands/org.js.map +1 -0
  62. package/dist/commands/playbook.d.ts +91 -0
  63. package/dist/commands/playbook.d.ts.map +1 -0
  64. package/dist/commands/playbook.js +1016 -0
  65. package/dist/commands/playbook.js.map +1 -0
  66. package/dist/commands/reflect.d.ts +52 -0
  67. package/dist/commands/reflect.d.ts.map +1 -0
  68. package/dist/commands/reflect.js +316 -0
  69. package/dist/commands/reflect.js.map +1 -0
  70. package/dist/commands/relationship.d.ts +17 -0
  71. package/dist/commands/relationship.d.ts.map +1 -0
  72. package/dist/commands/relationship.js +156 -0
  73. package/dist/commands/relationship.js.map +1 -0
  74. package/dist/commands/relay.d.ts +26 -0
  75. package/dist/commands/relay.d.ts.map +1 -0
  76. package/dist/commands/relay.js +121 -0
  77. package/dist/commands/relay.js.map +1 -0
  78. package/dist/commands/review.d.ts +18 -0
  79. package/dist/commands/review.d.ts.map +1 -0
  80. package/dist/commands/review.js +193 -0
  81. package/dist/commands/review.js.map +1 -0
  82. package/dist/commands/semantic.d.ts +37 -0
  83. package/dist/commands/semantic.d.ts.map +1 -0
  84. package/dist/commands/semantic.js +198 -0
  85. package/dist/commands/semantic.js.map +1 -0
  86. package/dist/commands/server.d.ts +37 -0
  87. package/dist/commands/server.d.ts.map +1 -0
  88. package/dist/commands/server.js +4875 -0
  89. package/dist/commands/server.js.map +1 -0
  90. package/dist/commands/setup.d.ts +63 -0
  91. package/dist/commands/setup.d.ts.map +1 -0
  92. package/dist/commands/setup.js +1235 -0
  93. package/dist/commands/setup.js.map +1 -0
  94. package/dist/commands/slack-cli.d.ts +16 -0
  95. package/dist/commands/slack-cli.d.ts.map +1 -0
  96. package/dist/commands/slack-cli.js +259 -0
  97. package/dist/commands/slack-cli.js.map +1 -0
  98. package/dist/commands/status.d.ts +11 -0
  99. package/dist/commands/status.d.ts.map +1 -0
  100. package/dist/commands/status.js +120 -0
  101. package/dist/commands/status.js.map +1 -0
  102. package/dist/commands/user.d.ts +17 -0
  103. package/dist/commands/user.d.ts.map +1 -0
  104. package/dist/commands/user.js +53 -0
  105. package/dist/commands/user.js.map +1 -0
  106. package/dist/commands/whatsapp.d.ts +31 -0
  107. package/dist/commands/whatsapp.d.ts.map +1 -0
  108. package/dist/commands/whatsapp.js +408 -0
  109. package/dist/commands/whatsapp.js.map +1 -0
  110. package/dist/config/ConfigDefaults.d.ts +40 -0
  111. package/dist/config/ConfigDefaults.d.ts.map +1 -0
  112. package/dist/config/ConfigDefaults.js +175 -0
  113. package/dist/config/ConfigDefaults.js.map +1 -0
  114. package/dist/config/LiveConfig.d.ts +97 -0
  115. package/dist/config/LiveConfig.d.ts.map +1 -0
  116. package/dist/config/LiveConfig.js +220 -0
  117. package/dist/config/LiveConfig.js.map +1 -0
  118. package/dist/core/AccessControl.d.ts +91 -0
  119. package/dist/core/AccessControl.d.ts.map +1 -0
  120. package/dist/core/AccessControl.js +184 -0
  121. package/dist/core/AccessControl.js.map +1 -0
  122. package/dist/core/AdaptationValidator.d.ts +44 -0
  123. package/dist/core/AdaptationValidator.d.ts.map +1 -0
  124. package/dist/core/AdaptationValidator.js +132 -0
  125. package/dist/core/AdaptationValidator.js.map +1 -0
  126. package/dist/core/AdaptiveTrust.d.ts +188 -0
  127. package/dist/core/AdaptiveTrust.d.ts.map +1 -0
  128. package/dist/core/AdaptiveTrust.js +354 -0
  129. package/dist/core/AdaptiveTrust.js.map +1 -0
  130. package/dist/core/AgentBus.d.ts +168 -0
  131. package/dist/core/AgentBus.d.ts.map +1 -0
  132. package/dist/core/AgentBus.js +369 -0
  133. package/dist/core/AgentBus.js.map +1 -0
  134. package/dist/core/AgentConnector.d.ts +76 -0
  135. package/dist/core/AgentConnector.d.ts.map +1 -0
  136. package/dist/core/AgentConnector.js +324 -0
  137. package/dist/core/AgentConnector.js.map +1 -0
  138. package/dist/core/AgentRegistry.d.ts +107 -0
  139. package/dist/core/AgentRegistry.d.ts.map +1 -0
  140. package/dist/core/AgentRegistry.js +569 -0
  141. package/dist/core/AgentRegistry.js.map +1 -0
  142. package/dist/core/AnthropicIntelligenceProvider.d.ts +24 -0
  143. package/dist/core/AnthropicIntelligenceProvider.d.ts.map +1 -0
  144. package/dist/core/AnthropicIntelligenceProvider.js +63 -0
  145. package/dist/core/AnthropicIntelligenceProvider.js.map +1 -0
  146. package/dist/core/AuditTrail.d.ts +207 -0
  147. package/dist/core/AuditTrail.d.ts.map +1 -0
  148. package/dist/core/AuditTrail.js +271 -0
  149. package/dist/core/AuditTrail.js.map +1 -0
  150. package/dist/core/AutoApprover.d.ts +63 -0
  151. package/dist/core/AutoApprover.d.ts.map +1 -0
  152. package/dist/core/AutoApprover.js +151 -0
  153. package/dist/core/AutoApprover.js.map +1 -0
  154. package/dist/core/AutoDispatcher.d.ts +170 -0
  155. package/dist/core/AutoDispatcher.d.ts.map +1 -0
  156. package/dist/core/AutoDispatcher.js +647 -0
  157. package/dist/core/AutoDispatcher.js.map +1 -0
  158. package/dist/core/AutoUpdater.d.ts +193 -0
  159. package/dist/core/AutoUpdater.d.ts.map +1 -0
  160. package/dist/core/AutoUpdater.js +648 -0
  161. package/dist/core/AutoUpdater.js.map +1 -0
  162. package/dist/core/AutonomousEvolution.d.ts +168 -0
  163. package/dist/core/AutonomousEvolution.d.ts.map +1 -0
  164. package/dist/core/AutonomousEvolution.js +384 -0
  165. package/dist/core/AutonomousEvolution.js.map +1 -0
  166. package/dist/core/AutonomyProfileManager.d.ts +108 -0
  167. package/dist/core/AutonomyProfileManager.d.ts.map +1 -0
  168. package/dist/core/AutonomyProfileManager.js +323 -0
  169. package/dist/core/AutonomyProfileManager.js.map +1 -0
  170. package/dist/core/AutonomySkill.d.ts +98 -0
  171. package/dist/core/AutonomySkill.d.ts.map +1 -0
  172. package/dist/core/AutonomySkill.js +497 -0
  173. package/dist/core/AutonomySkill.js.map +1 -0
  174. package/dist/core/BackupManager.d.ts +66 -0
  175. package/dist/core/BackupManager.d.ts.map +1 -0
  176. package/dist/core/BackupManager.js +266 -0
  177. package/dist/core/BackupManager.js.map +1 -0
  178. package/dist/core/BitwardenProvider.d.ts +85 -0
  179. package/dist/core/BitwardenProvider.d.ts.map +1 -0
  180. package/dist/core/BitwardenProvider.js +437 -0
  181. package/dist/core/BitwardenProvider.js.map +1 -0
  182. package/dist/core/BlockerLearningLoop.d.ts +99 -0
  183. package/dist/core/BlockerLearningLoop.d.ts.map +1 -0
  184. package/dist/core/BlockerLearningLoop.js +205 -0
  185. package/dist/core/BlockerLearningLoop.js.map +1 -0
  186. package/dist/core/BranchManager.d.ts +165 -0
  187. package/dist/core/BranchManager.d.ts.map +1 -0
  188. package/dist/core/BranchManager.js +413 -0
  189. package/dist/core/BranchManager.js.map +1 -0
  190. package/dist/core/CaffeinateManager.d.ts +50 -0
  191. package/dist/core/CaffeinateManager.d.ts.map +1 -0
  192. package/dist/core/CaffeinateManager.js +189 -0
  193. package/dist/core/CaffeinateManager.js.map +1 -0
  194. package/dist/core/CallbackRegistry.d.ts +67 -0
  195. package/dist/core/CallbackRegistry.d.ts.map +1 -0
  196. package/dist/core/CallbackRegistry.js +145 -0
  197. package/dist/core/CallbackRegistry.js.map +1 -0
  198. package/dist/core/CanonicalState.d.ts +132 -0
  199. package/dist/core/CanonicalState.d.ts.map +1 -0
  200. package/dist/core/CanonicalState.js +297 -0
  201. package/dist/core/CanonicalState.js.map +1 -0
  202. package/dist/core/CapabilityMapper.d.ts +192 -0
  203. package/dist/core/CapabilityMapper.d.ts.map +1 -0
  204. package/dist/core/CapabilityMapper.js +958 -0
  205. package/dist/core/CapabilityMapper.js.map +1 -0
  206. package/dist/core/CapabilityRegistryGenerator.d.ts +72 -0
  207. package/dist/core/CapabilityRegistryGenerator.d.ts.map +1 -0
  208. package/dist/core/CapabilityRegistryGenerator.js +310 -0
  209. package/dist/core/CapabilityRegistryGenerator.js.map +1 -0
  210. package/dist/core/ClaudeCliIntelligenceProvider.d.ts +21 -0
  211. package/dist/core/ClaudeCliIntelligenceProvider.d.ts.map +1 -0
  212. package/dist/core/ClaudeCliIntelligenceProvider.js +60 -0
  213. package/dist/core/ClaudeCliIntelligenceProvider.js.map +1 -0
  214. package/dist/core/CoherenceGate.d.ts +198 -0
  215. package/dist/core/CoherenceGate.d.ts.map +1 -0
  216. package/dist/core/CoherenceGate.js +986 -0
  217. package/dist/core/CoherenceGate.js.map +1 -0
  218. package/dist/core/CoherenceReviewer.d.ts +104 -0
  219. package/dist/core/CoherenceReviewer.d.ts.map +1 -0
  220. package/dist/core/CoherenceReviewer.js +185 -0
  221. package/dist/core/CoherenceReviewer.js.map +1 -0
  222. package/dist/core/Config.d.ts +32 -0
  223. package/dist/core/Config.d.ts.map +1 -0
  224. package/dist/core/Config.js +332 -0
  225. package/dist/core/Config.js.map +1 -0
  226. package/dist/core/ConflictNegotiator.d.ts +167 -0
  227. package/dist/core/ConflictNegotiator.d.ts.map +1 -0
  228. package/dist/core/ConflictNegotiator.js +280 -0
  229. package/dist/core/ConflictNegotiator.js.map +1 -0
  230. package/dist/core/ContextHierarchy.d.ts +102 -0
  231. package/dist/core/ContextHierarchy.d.ts.map +1 -0
  232. package/dist/core/ContextHierarchy.js +594 -0
  233. package/dist/core/ContextHierarchy.js.map +1 -0
  234. package/dist/core/ContextSnapshotBuilder.d.ts +80 -0
  235. package/dist/core/ContextSnapshotBuilder.d.ts.map +1 -0
  236. package/dist/core/ContextSnapshotBuilder.js +342 -0
  237. package/dist/core/ContextSnapshotBuilder.js.map +1 -0
  238. package/dist/core/ContextualEvaluator.d.ts +103 -0
  239. package/dist/core/ContextualEvaluator.d.ts.map +1 -0
  240. package/dist/core/ContextualEvaluator.js +389 -0
  241. package/dist/core/ContextualEvaluator.js.map +1 -0
  242. package/dist/core/ConvergenceChecker.d.ts +24 -0
  243. package/dist/core/ConvergenceChecker.d.ts.map +1 -0
  244. package/dist/core/ConvergenceChecker.js +113 -0
  245. package/dist/core/ConvergenceChecker.js.map +1 -0
  246. package/dist/core/CoordinationProtocol.d.ts +198 -0
  247. package/dist/core/CoordinationProtocol.d.ts.map +1 -0
  248. package/dist/core/CoordinationProtocol.js +363 -0
  249. package/dist/core/CoordinationProtocol.js.map +1 -0
  250. package/dist/core/CustomReviewerLoader.d.ts +45 -0
  251. package/dist/core/CustomReviewerLoader.d.ts.map +1 -0
  252. package/dist/core/CustomReviewerLoader.js +153 -0
  253. package/dist/core/CustomReviewerLoader.js.map +1 -0
  254. package/dist/core/DecisionJournal.d.ts +56 -0
  255. package/dist/core/DecisionJournal.d.ts.map +1 -0
  256. package/dist/core/DecisionJournal.js +132 -0
  257. package/dist/core/DecisionJournal.js.map +1 -0
  258. package/dist/core/DeferredDispatchTracker.d.ts +91 -0
  259. package/dist/core/DeferredDispatchTracker.d.ts.map +1 -0
  260. package/dist/core/DeferredDispatchTracker.js +213 -0
  261. package/dist/core/DeferredDispatchTracker.js.map +1 -0
  262. package/dist/core/DiscoveryEvaluator.d.ts +131 -0
  263. package/dist/core/DiscoveryEvaluator.d.ts.map +1 -0
  264. package/dist/core/DiscoveryEvaluator.js +377 -0
  265. package/dist/core/DiscoveryEvaluator.js.map +1 -0
  266. package/dist/core/DispatchDecisionJournal.d.ts +83 -0
  267. package/dist/core/DispatchDecisionJournal.d.ts.map +1 -0
  268. package/dist/core/DispatchDecisionJournal.js +181 -0
  269. package/dist/core/DispatchDecisionJournal.js.map +1 -0
  270. package/dist/core/DispatchExecutor.d.ts +127 -0
  271. package/dist/core/DispatchExecutor.d.ts.map +1 -0
  272. package/dist/core/DispatchExecutor.js +355 -0
  273. package/dist/core/DispatchExecutor.js.map +1 -0
  274. package/dist/core/DispatchManager.d.ts +200 -0
  275. package/dist/core/DispatchManager.d.ts.map +1 -0
  276. package/dist/core/DispatchManager.js +524 -0
  277. package/dist/core/DispatchManager.js.map +1 -0
  278. package/dist/core/DispatchScopeEnforcer.d.ts +57 -0
  279. package/dist/core/DispatchScopeEnforcer.d.ts.map +1 -0
  280. package/dist/core/DispatchScopeEnforcer.js +173 -0
  281. package/dist/core/DispatchScopeEnforcer.js.map +1 -0
  282. package/dist/core/DispatchVerifier.d.ts +76 -0
  283. package/dist/core/DispatchVerifier.d.ts.map +1 -0
  284. package/dist/core/DispatchVerifier.js +128 -0
  285. package/dist/core/DispatchVerifier.js.map +1 -0
  286. package/dist/core/EvolutionManager.d.ts +223 -0
  287. package/dist/core/EvolutionManager.d.ts.map +1 -0
  288. package/dist/core/EvolutionManager.js +630 -0
  289. package/dist/core/EvolutionManager.js.map +1 -0
  290. package/dist/core/ExecutionJournal.d.ts +101 -0
  291. package/dist/core/ExecutionJournal.d.ts.map +1 -0
  292. package/dist/core/ExecutionJournal.js +301 -0
  293. package/dist/core/ExecutionJournal.js.map +1 -0
  294. package/dist/core/ExternalOperationGate.d.ts +204 -0
  295. package/dist/core/ExternalOperationGate.d.ts.map +1 -0
  296. package/dist/core/ExternalOperationGate.js +413 -0
  297. package/dist/core/ExternalOperationGate.js.map +1 -0
  298. package/dist/core/FeatureDefinitions.d.ts +14 -0
  299. package/dist/core/FeatureDefinitions.d.ts.map +1 -0
  300. package/dist/core/FeatureDefinitions.js +374 -0
  301. package/dist/core/FeatureDefinitions.js.map +1 -0
  302. package/dist/core/FeatureRegistry.d.ts +337 -0
  303. package/dist/core/FeatureRegistry.d.ts.map +1 -0
  304. package/dist/core/FeatureRegistry.js +863 -0
  305. package/dist/core/FeatureRegistry.js.map +1 -0
  306. package/dist/core/FeedbackManager.d.ts +69 -0
  307. package/dist/core/FeedbackManager.d.ts.map +1 -0
  308. package/dist/core/FeedbackManager.js +284 -0
  309. package/dist/core/FeedbackManager.js.map +1 -0
  310. package/dist/core/FileClassifier.d.ts +74 -0
  311. package/dist/core/FileClassifier.d.ts.map +1 -0
  312. package/dist/core/FileClassifier.js +377 -0
  313. package/dist/core/FileClassifier.js.map +1 -0
  314. package/dist/core/ForegroundRestartWatcher.d.ts +55 -0
  315. package/dist/core/ForegroundRestartWatcher.d.ts.map +1 -0
  316. package/dist/core/ForegroundRestartWatcher.js +116 -0
  317. package/dist/core/ForegroundRestartWatcher.js.map +1 -0
  318. package/dist/core/GitStateManager.d.ts +78 -0
  319. package/dist/core/GitStateManager.d.ts.map +1 -0
  320. package/dist/core/GitStateManager.js +366 -0
  321. package/dist/core/GitStateManager.js.map +1 -0
  322. package/dist/core/GitSync.d.ts +199 -0
  323. package/dist/core/GitSync.d.ts.map +1 -0
  324. package/dist/core/GitSync.js +955 -0
  325. package/dist/core/GitSync.js.map +1 -0
  326. package/dist/core/GlobalInstallCleanup.d.ts +23 -0
  327. package/dist/core/GlobalInstallCleanup.d.ts.map +1 -0
  328. package/dist/core/GlobalInstallCleanup.js +130 -0
  329. package/dist/core/GlobalInstallCleanup.js.map +1 -0
  330. package/dist/core/GlobalSecretStore.d.ts +112 -0
  331. package/dist/core/GlobalSecretStore.d.ts.map +1 -0
  332. package/dist/core/GlobalSecretStore.js +396 -0
  333. package/dist/core/GlobalSecretStore.js.map +1 -0
  334. package/dist/core/HandoffManager.d.ts +163 -0
  335. package/dist/core/HandoffManager.d.ts.map +1 -0
  336. package/dist/core/HandoffManager.js +370 -0
  337. package/dist/core/HandoffManager.js.map +1 -0
  338. package/dist/core/HeartbeatManager.d.ts +120 -0
  339. package/dist/core/HeartbeatManager.d.ts.map +1 -0
  340. package/dist/core/HeartbeatManager.js +240 -0
  341. package/dist/core/HeartbeatManager.js.map +1 -0
  342. package/dist/core/InputGuard.d.ts +98 -0
  343. package/dist/core/InputGuard.d.ts.map +1 -0
  344. package/dist/core/InputGuard.js +316 -0
  345. package/dist/core/InputGuard.js.map +1 -0
  346. package/dist/core/IntentDriftDetector.d.ts +100 -0
  347. package/dist/core/IntentDriftDetector.d.ts.map +1 -0
  348. package/dist/core/IntentDriftDetector.js +325 -0
  349. package/dist/core/IntentDriftDetector.js.map +1 -0
  350. package/dist/core/JobReflector.d.ts +81 -0
  351. package/dist/core/JobReflector.d.ts.map +1 -0
  352. package/dist/core/JobReflector.js +244 -0
  353. package/dist/core/JobReflector.js.map +1 -0
  354. package/dist/core/LLMConflictResolver.d.ts +132 -0
  355. package/dist/core/LLMConflictResolver.d.ts.map +1 -0
  356. package/dist/core/LLMConflictResolver.js +372 -0
  357. package/dist/core/LLMConflictResolver.js.map +1 -0
  358. package/dist/core/LedgerAuth.d.ts +99 -0
  359. package/dist/core/LedgerAuth.d.ts.map +1 -0
  360. package/dist/core/LedgerAuth.js +215 -0
  361. package/dist/core/LedgerAuth.js.map +1 -0
  362. package/dist/core/MachineIdentity.d.ts +196 -0
  363. package/dist/core/MachineIdentity.d.ts.map +1 -0
  364. package/dist/core/MachineIdentity.js +476 -0
  365. package/dist/core/MachineIdentity.js.map +1 -0
  366. package/dist/core/MessageSentinel.d.ts +142 -0
  367. package/dist/core/MessageSentinel.d.ts.map +1 -0
  368. package/dist/core/MessageSentinel.js +426 -0
  369. package/dist/core/MessageSentinel.js.map +1 -0
  370. package/dist/core/MigrationProvenance.d.ts +62 -0
  371. package/dist/core/MigrationProvenance.d.ts.map +1 -0
  372. package/dist/core/MigrationProvenance.js +183 -0
  373. package/dist/core/MigrationProvenance.js.map +1 -0
  374. package/dist/core/MultiMachineCoordinator.d.ts +106 -0
  375. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -0
  376. package/dist/core/MultiMachineCoordinator.js +311 -0
  377. package/dist/core/MultiMachineCoordinator.js.map +1 -0
  378. package/dist/core/NonceStore.d.ts +72 -0
  379. package/dist/core/NonceStore.d.ts.map +1 -0
  380. package/dist/core/NonceStore.js +163 -0
  381. package/dist/core/NonceStore.js.map +1 -0
  382. package/dist/core/OrgIntentManager.d.ts +58 -0
  383. package/dist/core/OrgIntentManager.d.ts.map +1 -0
  384. package/dist/core/OrgIntentManager.js +250 -0
  385. package/dist/core/OrgIntentManager.js.map +1 -0
  386. package/dist/core/OverlapGuard.d.ts +112 -0
  387. package/dist/core/OverlapGuard.d.ts.map +1 -0
  388. package/dist/core/OverlapGuard.js +241 -0
  389. package/dist/core/OverlapGuard.js.map +1 -0
  390. package/dist/core/PairingProtocol.d.ts +129 -0
  391. package/dist/core/PairingProtocol.d.ts.map +1 -0
  392. package/dist/core/PairingProtocol.js +265 -0
  393. package/dist/core/PairingProtocol.js.map +1 -0
  394. package/dist/core/PatternAnalyzer.d.ts +128 -0
  395. package/dist/core/PatternAnalyzer.d.ts.map +1 -0
  396. package/dist/core/PatternAnalyzer.js +388 -0
  397. package/dist/core/PatternAnalyzer.js.map +1 -0
  398. package/dist/core/PlatformActivityRegistry.d.ts +163 -0
  399. package/dist/core/PlatformActivityRegistry.d.ts.map +1 -0
  400. package/dist/core/PlatformActivityRegistry.js +307 -0
  401. package/dist/core/PlatformActivityRegistry.js.map +1 -0
  402. package/dist/core/PolicyEnforcementLayer.d.ts +63 -0
  403. package/dist/core/PolicyEnforcementLayer.d.ts.map +1 -0
  404. package/dist/core/PolicyEnforcementLayer.js +250 -0
  405. package/dist/core/PolicyEnforcementLayer.js.map +1 -0
  406. package/dist/core/PortRegistry.d.ts +9 -0
  407. package/dist/core/PortRegistry.d.ts.map +1 -0
  408. package/dist/core/PortRegistry.js +8 -0
  409. package/dist/core/PortRegistry.js.map +1 -0
  410. package/dist/core/PostUpdateMigrator.d.ts +170 -0
  411. package/dist/core/PostUpdateMigrator.d.ts.map +1 -0
  412. package/dist/core/PostUpdateMigrator.js +3674 -0
  413. package/dist/core/PostUpdateMigrator.js.map +1 -0
  414. package/dist/core/Prerequisites.d.ts +37 -0
  415. package/dist/core/Prerequisites.d.ts.map +1 -0
  416. package/dist/core/Prerequisites.js +272 -0
  417. package/dist/core/Prerequisites.js.map +1 -0
  418. package/dist/core/ProcessIntegrity.d.ts +90 -0
  419. package/dist/core/ProcessIntegrity.d.ts.map +1 -0
  420. package/dist/core/ProcessIntegrity.js +119 -0
  421. package/dist/core/ProcessIntegrity.js.map +1 -0
  422. package/dist/core/ProjectMapper.d.ts +97 -0
  423. package/dist/core/ProjectMapper.d.ts.map +1 -0
  424. package/dist/core/ProjectMapper.js +370 -0
  425. package/dist/core/ProjectMapper.js.map +1 -0
  426. package/dist/core/PromptGuard.d.ts +121 -0
  427. package/dist/core/PromptGuard.d.ts.map +1 -0
  428. package/dist/core/PromptGuard.js +259 -0
  429. package/dist/core/PromptGuard.js.map +1 -0
  430. package/dist/core/RecipientResolver.d.ts +54 -0
  431. package/dist/core/RecipientResolver.d.ts.map +1 -0
  432. package/dist/core/RecipientResolver.js +143 -0
  433. package/dist/core/RecipientResolver.js.map +1 -0
  434. package/dist/core/ReflectionConsolidator.d.ts +73 -0
  435. package/dist/core/ReflectionConsolidator.d.ts.map +1 -0
  436. package/dist/core/ReflectionConsolidator.js +202 -0
  437. package/dist/core/ReflectionConsolidator.js.map +1 -0
  438. package/dist/core/RelationshipManager.d.ts +146 -0
  439. package/dist/core/RelationshipManager.d.ts.map +1 -0
  440. package/dist/core/RelationshipManager.js +736 -0
  441. package/dist/core/RelationshipManager.js.map +1 -0
  442. package/dist/core/RelevanceFilter.d.ts +61 -0
  443. package/dist/core/RelevanceFilter.d.ts.map +1 -0
  444. package/dist/core/RelevanceFilter.js +160 -0
  445. package/dist/core/RelevanceFilter.js.map +1 -0
  446. package/dist/core/ResearchRateLimiter.d.ts +76 -0
  447. package/dist/core/ResearchRateLimiter.d.ts.map +1 -0
  448. package/dist/core/ResearchRateLimiter.js +168 -0
  449. package/dist/core/ResearchRateLimiter.js.map +1 -0
  450. package/dist/core/ResumeValidator.d.ts +58 -0
  451. package/dist/core/ResumeValidator.d.ts.map +1 -0
  452. package/dist/core/ResumeValidator.js +195 -0
  453. package/dist/core/ResumeValidator.js.map +1 -0
  454. package/dist/core/ScopeCoherenceTracker.d.ts +87 -0
  455. package/dist/core/ScopeCoherenceTracker.d.ts.map +1 -0
  456. package/dist/core/ScopeCoherenceTracker.js +226 -0
  457. package/dist/core/ScopeCoherenceTracker.js.map +1 -0
  458. package/dist/core/ScopeVerifier.d.ts +122 -0
  459. package/dist/core/ScopeVerifier.d.ts.map +1 -0
  460. package/dist/core/ScopeVerifier.js +350 -0
  461. package/dist/core/ScopeVerifier.js.map +1 -0
  462. package/dist/core/SecretManager.d.ts +120 -0
  463. package/dist/core/SecretManager.d.ts.map +1 -0
  464. package/dist/core/SecretManager.js +324 -0
  465. package/dist/core/SecretManager.js.map +1 -0
  466. package/dist/core/SecretMigrator.d.ts +39 -0
  467. package/dist/core/SecretMigrator.d.ts.map +1 -0
  468. package/dist/core/SecretMigrator.js +182 -0
  469. package/dist/core/SecretMigrator.js.map +1 -0
  470. package/dist/core/SecretRedactor.d.ts +121 -0
  471. package/dist/core/SecretRedactor.d.ts.map +1 -0
  472. package/dist/core/SecretRedactor.js +309 -0
  473. package/dist/core/SecretRedactor.js.map +1 -0
  474. package/dist/core/SecretStore.d.ts +104 -0
  475. package/dist/core/SecretStore.d.ts.map +1 -0
  476. package/dist/core/SecretStore.js +405 -0
  477. package/dist/core/SecretStore.js.map +1 -0
  478. package/dist/core/SecurityLog.d.ts +58 -0
  479. package/dist/core/SecurityLog.d.ts.map +1 -0
  480. package/dist/core/SecurityLog.js +123 -0
  481. package/dist/core/SecurityLog.js.map +1 -0
  482. package/dist/core/SendGateway.d.ts +77 -0
  483. package/dist/core/SendGateway.d.ts.map +1 -0
  484. package/dist/core/SendGateway.js +181 -0
  485. package/dist/core/SendGateway.js.map +1 -0
  486. package/dist/core/SessionManager.d.ts +304 -0
  487. package/dist/core/SessionManager.d.ts.map +1 -0
  488. package/dist/core/SessionManager.js +1402 -0
  489. package/dist/core/SessionManager.js.map +1 -0
  490. package/dist/core/SleepWakeDetector.d.ts +37 -0
  491. package/dist/core/SleepWakeDetector.d.ts.map +1 -0
  492. package/dist/core/SleepWakeDetector.js +59 -0
  493. package/dist/core/SleepWakeDetector.js.map +1 -0
  494. package/dist/core/SoulManager.d.ts +107 -0
  495. package/dist/core/SoulManager.d.ts.map +1 -0
  496. package/dist/core/SoulManager.js +574 -0
  497. package/dist/core/SoulManager.js.map +1 -0
  498. package/dist/core/StaleProcessGuard.d.ts +113 -0
  499. package/dist/core/StaleProcessGuard.d.ts.map +1 -0
  500. package/dist/core/StaleProcessGuard.js +134 -0
  501. package/dist/core/StaleProcessGuard.js.map +1 -0
  502. package/dist/core/StateManager.d.ts +56 -0
  503. package/dist/core/StateManager.d.ts.map +1 -0
  504. package/dist/core/StateManager.js +266 -0
  505. package/dist/core/StateManager.js.map +1 -0
  506. package/dist/core/StateWriteAuthority.d.ts +101 -0
  507. package/dist/core/StateWriteAuthority.d.ts.map +1 -0
  508. package/dist/core/StateWriteAuthority.js +169 -0
  509. package/dist/core/StateWriteAuthority.js.map +1 -0
  510. package/dist/core/SurfacingTemplates.d.ts +63 -0
  511. package/dist/core/SurfacingTemplates.d.ts.map +1 -0
  512. package/dist/core/SurfacingTemplates.js +138 -0
  513. package/dist/core/SurfacingTemplates.js.map +1 -0
  514. package/dist/core/SyncOrchestrator.d.ts +308 -0
  515. package/dist/core/SyncOrchestrator.d.ts.map +1 -0
  516. package/dist/core/SyncOrchestrator.js +925 -0
  517. package/dist/core/SyncOrchestrator.js.map +1 -0
  518. package/dist/core/TemporalCoherenceChecker.d.ts +140 -0
  519. package/dist/core/TemporalCoherenceChecker.d.ts.map +1 -0
  520. package/dist/core/TemporalCoherenceChecker.js +375 -0
  521. package/dist/core/TemporalCoherenceChecker.js.map +1 -0
  522. package/dist/core/TopicClassifier.d.ts +54 -0
  523. package/dist/core/TopicClassifier.d.ts.map +1 -0
  524. package/dist/core/TopicClassifier.js +144 -0
  525. package/dist/core/TopicClassifier.js.map +1 -0
  526. package/dist/core/TopicResumeMap.d.ts +76 -0
  527. package/dist/core/TopicResumeMap.d.ts.map +1 -0
  528. package/dist/core/TopicResumeMap.js +252 -0
  529. package/dist/core/TopicResumeMap.js.map +1 -0
  530. package/dist/core/TrustElevationTracker.d.ts +178 -0
  531. package/dist/core/TrustElevationTracker.d.ts.map +1 -0
  532. package/dist/core/TrustElevationTracker.js +343 -0
  533. package/dist/core/TrustElevationTracker.js.map +1 -0
  534. package/dist/core/TrustRecovery.d.ts +109 -0
  535. package/dist/core/TrustRecovery.d.ts.map +1 -0
  536. package/dist/core/TrustRecovery.js +190 -0
  537. package/dist/core/TrustRecovery.js.map +1 -0
  538. package/dist/core/UpdateChecker.d.ts +108 -0
  539. package/dist/core/UpdateChecker.d.ts.map +1 -0
  540. package/dist/core/UpdateChecker.js +593 -0
  541. package/dist/core/UpdateChecker.js.map +1 -0
  542. package/dist/core/UpdateGate.d.ts +102 -0
  543. package/dist/core/UpdateGate.d.ts.map +1 -0
  544. package/dist/core/UpdateGate.js +149 -0
  545. package/dist/core/UpdateGate.js.map +1 -0
  546. package/dist/core/UpgradeGuideProcessor.d.ts +105 -0
  547. package/dist/core/UpgradeGuideProcessor.d.ts.map +1 -0
  548. package/dist/core/UpgradeGuideProcessor.js +278 -0
  549. package/dist/core/UpgradeGuideProcessor.js.map +1 -0
  550. package/dist/core/UpgradeNotifyManager.d.ts +98 -0
  551. package/dist/core/UpgradeNotifyManager.d.ts.map +1 -0
  552. package/dist/core/UpgradeNotifyManager.js +205 -0
  553. package/dist/core/UpgradeNotifyManager.js.map +1 -0
  554. package/dist/core/WorkLedger.d.ts +144 -0
  555. package/dist/core/WorkLedger.d.ts.map +1 -0
  556. package/dist/core/WorkLedger.js +246 -0
  557. package/dist/core/WorkLedger.js.map +1 -0
  558. package/dist/core/models.d.ts +70 -0
  559. package/dist/core/models.d.ts.map +1 -0
  560. package/dist/core/models.js +110 -0
  561. package/dist/core/models.js.map +1 -0
  562. package/dist/core/reviewers/capability-accuracy.d.ts +13 -0
  563. package/dist/core/reviewers/capability-accuracy.d.ts.map +1 -0
  564. package/dist/core/reviewers/capability-accuracy.js +38 -0
  565. package/dist/core/reviewers/capability-accuracy.js.map +1 -0
  566. package/dist/core/reviewers/claim-provenance.d.ts +14 -0
  567. package/dist/core/reviewers/claim-provenance.d.ts.map +1 -0
  568. package/dist/core/reviewers/claim-provenance.js +50 -0
  569. package/dist/core/reviewers/claim-provenance.js.map +1 -0
  570. package/dist/core/reviewers/context-completeness.d.ts +13 -0
  571. package/dist/core/reviewers/context-completeness.d.ts.map +1 -0
  572. package/dist/core/reviewers/context-completeness.js +43 -0
  573. package/dist/core/reviewers/context-completeness.js.map +1 -0
  574. package/dist/core/reviewers/conversational-tone.d.ts +13 -0
  575. package/dist/core/reviewers/conversational-tone.d.ts.map +1 -0
  576. package/dist/core/reviewers/conversational-tone.js +55 -0
  577. package/dist/core/reviewers/conversational-tone.js.map +1 -0
  578. package/dist/core/reviewers/escalation-resolution.d.ts +56 -0
  579. package/dist/core/reviewers/escalation-resolution.d.ts.map +1 -0
  580. package/dist/core/reviewers/escalation-resolution.js +239 -0
  581. package/dist/core/reviewers/escalation-resolution.js.map +1 -0
  582. package/dist/core/reviewers/gate-reviewer.d.ts +26 -0
  583. package/dist/core/reviewers/gate-reviewer.d.ts.map +1 -0
  584. package/dist/core/reviewers/gate-reviewer.js +130 -0
  585. package/dist/core/reviewers/gate-reviewer.js.map +1 -0
  586. package/dist/core/reviewers/information-leakage.d.ts +21 -0
  587. package/dist/core/reviewers/information-leakage.d.ts.map +1 -0
  588. package/dist/core/reviewers/information-leakage.js +72 -0
  589. package/dist/core/reviewers/information-leakage.js.map +1 -0
  590. package/dist/core/reviewers/settling-detection.d.ts +13 -0
  591. package/dist/core/reviewers/settling-detection.d.ts.map +1 -0
  592. package/dist/core/reviewers/settling-detection.js +48 -0
  593. package/dist/core/reviewers/settling-detection.js.map +1 -0
  594. package/dist/core/reviewers/url-validity.d.ts +18 -0
  595. package/dist/core/reviewers/url-validity.d.ts.map +1 -0
  596. package/dist/core/reviewers/url-validity.js +60 -0
  597. package/dist/core/reviewers/url-validity.js.map +1 -0
  598. package/dist/core/reviewers/value-alignment.d.ts +14 -0
  599. package/dist/core/reviewers/value-alignment.d.ts.map +1 -0
  600. package/dist/core/reviewers/value-alignment.js +71 -0
  601. package/dist/core/reviewers/value-alignment.js.map +1 -0
  602. package/dist/core/types.d.ts +2159 -0
  603. package/dist/core/types.d.ts.map +1 -0
  604. package/dist/core/types.js +20 -0
  605. package/dist/core/types.js.map +1 -0
  606. package/dist/data/http-hook-templates.d.ts +53 -0
  607. package/dist/data/http-hook-templates.d.ts.map +1 -0
  608. package/dist/data/http-hook-templates.js +64 -0
  609. package/dist/data/http-hook-templates.js.map +1 -0
  610. package/dist/index.d.ts +263 -0
  611. package/dist/index.d.ts.map +1 -0
  612. package/dist/index.js +164 -0
  613. package/dist/index.js.map +1 -0
  614. package/dist/knowledge/CoverageAuditor.d.ts +58 -0
  615. package/dist/knowledge/CoverageAuditor.d.ts.map +1 -0
  616. package/dist/knowledge/CoverageAuditor.js +153 -0
  617. package/dist/knowledge/CoverageAuditor.js.map +1 -0
  618. package/dist/knowledge/IntegrityManager.d.ts +46 -0
  619. package/dist/knowledge/IntegrityManager.d.ts.map +1 -0
  620. package/dist/knowledge/IntegrityManager.js +101 -0
  621. package/dist/knowledge/IntegrityManager.js.map +1 -0
  622. package/dist/knowledge/KnowledgeManager.d.ts +85 -0
  623. package/dist/knowledge/KnowledgeManager.d.ts.map +1 -0
  624. package/dist/knowledge/KnowledgeManager.js +194 -0
  625. package/dist/knowledge/KnowledgeManager.js.map +1 -0
  626. package/dist/knowledge/ProbeRegistry.d.ts +54 -0
  627. package/dist/knowledge/ProbeRegistry.d.ts.map +1 -0
  628. package/dist/knowledge/ProbeRegistry.js +119 -0
  629. package/dist/knowledge/ProbeRegistry.js.map +1 -0
  630. package/dist/knowledge/SelfKnowledgeTree.d.ts +108 -0
  631. package/dist/knowledge/SelfKnowledgeTree.d.ts.map +1 -0
  632. package/dist/knowledge/SelfKnowledgeTree.js +560 -0
  633. package/dist/knowledge/SelfKnowledgeTree.js.map +1 -0
  634. package/dist/knowledge/TreeGenerator.d.ts +57 -0
  635. package/dist/knowledge/TreeGenerator.d.ts.map +1 -0
  636. package/dist/knowledge/TreeGenerator.js +527 -0
  637. package/dist/knowledge/TreeGenerator.js.map +1 -0
  638. package/dist/knowledge/TreeSynthesis.d.ts +24 -0
  639. package/dist/knowledge/TreeSynthesis.d.ts.map +1 -0
  640. package/dist/knowledge/TreeSynthesis.js +61 -0
  641. package/dist/knowledge/TreeSynthesis.js.map +1 -0
  642. package/dist/knowledge/TreeTraversal.d.ts +93 -0
  643. package/dist/knowledge/TreeTraversal.d.ts.map +1 -0
  644. package/dist/knowledge/TreeTraversal.js +359 -0
  645. package/dist/knowledge/TreeTraversal.js.map +1 -0
  646. package/dist/knowledge/TreeTriage.d.ts +80 -0
  647. package/dist/knowledge/TreeTriage.d.ts.map +1 -0
  648. package/dist/knowledge/TreeTriage.js +413 -0
  649. package/dist/knowledge/TreeTriage.js.map +1 -0
  650. package/dist/knowledge/types.d.ts +183 -0
  651. package/dist/knowledge/types.d.ts.map +1 -0
  652. package/dist/knowledge/types.js +28 -0
  653. package/dist/knowledge/types.js.map +1 -0
  654. package/dist/lifeline/MessageQueue.d.ts +42 -0
  655. package/dist/lifeline/MessageQueue.d.ts.map +1 -0
  656. package/dist/lifeline/MessageQueue.js +63 -0
  657. package/dist/lifeline/MessageQueue.js.map +1 -0
  658. package/dist/lifeline/ServerSupervisor.d.ts +203 -0
  659. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -0
  660. package/dist/lifeline/ServerSupervisor.js +1103 -0
  661. package/dist/lifeline/ServerSupervisor.js.map +1 -0
  662. package/dist/lifeline/SlackLifeline.d.ts +43 -0
  663. package/dist/lifeline/SlackLifeline.d.ts.map +1 -0
  664. package/dist/lifeline/SlackLifeline.js +227 -0
  665. package/dist/lifeline/SlackLifeline.js.map +1 -0
  666. package/dist/lifeline/TelegramLifeline.d.ts +180 -0
  667. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -0
  668. package/dist/lifeline/TelegramLifeline.js +1533 -0
  669. package/dist/lifeline/TelegramLifeline.js.map +1 -0
  670. package/dist/memory/ActivityPartitioner.d.ts +67 -0
  671. package/dist/memory/ActivityPartitioner.d.ts.map +1 -0
  672. package/dist/memory/ActivityPartitioner.js +193 -0
  673. package/dist/memory/ActivityPartitioner.js.map +1 -0
  674. package/dist/memory/Chunker.d.ts +39 -0
  675. package/dist/memory/Chunker.d.ts.map +1 -0
  676. package/dist/memory/Chunker.js +154 -0
  677. package/dist/memory/Chunker.js.map +1 -0
  678. package/dist/memory/EmbeddingProvider.d.ts +82 -0
  679. package/dist/memory/EmbeddingProvider.d.ts.map +1 -0
  680. package/dist/memory/EmbeddingProvider.js +168 -0
  681. package/dist/memory/EmbeddingProvider.js.map +1 -0
  682. package/dist/memory/EpisodicMemory.d.ts +141 -0
  683. package/dist/memory/EpisodicMemory.d.ts.map +1 -0
  684. package/dist/memory/EpisodicMemory.js +303 -0
  685. package/dist/memory/EpisodicMemory.js.map +1 -0
  686. package/dist/memory/MemoryExporter.d.ts +82 -0
  687. package/dist/memory/MemoryExporter.d.ts.map +1 -0
  688. package/dist/memory/MemoryExporter.js +233 -0
  689. package/dist/memory/MemoryExporter.js.map +1 -0
  690. package/dist/memory/MemoryIndex.d.ts +76 -0
  691. package/dist/memory/MemoryIndex.d.ts.map +1 -0
  692. package/dist/memory/MemoryIndex.js +413 -0
  693. package/dist/memory/MemoryIndex.js.map +1 -0
  694. package/dist/memory/MemoryMigrator.d.ts +103 -0
  695. package/dist/memory/MemoryMigrator.d.ts.map +1 -0
  696. package/dist/memory/MemoryMigrator.js +510 -0
  697. package/dist/memory/MemoryMigrator.js.map +1 -0
  698. package/dist/memory/SemanticMemory.d.ts +255 -0
  699. package/dist/memory/SemanticMemory.d.ts.map +1 -0
  700. package/dist/memory/SemanticMemory.js +1130 -0
  701. package/dist/memory/SemanticMemory.js.map +1 -0
  702. package/dist/memory/TopicMemory.d.ts +227 -0
  703. package/dist/memory/TopicMemory.d.ts.map +1 -0
  704. package/dist/memory/TopicMemory.js +731 -0
  705. package/dist/memory/TopicMemory.js.map +1 -0
  706. package/dist/memory/TopicSummarizer.d.ts +69 -0
  707. package/dist/memory/TopicSummarizer.d.ts.map +1 -0
  708. package/dist/memory/TopicSummarizer.js +163 -0
  709. package/dist/memory/TopicSummarizer.js.map +1 -0
  710. package/dist/memory/VectorSearch.d.ts +80 -0
  711. package/dist/memory/VectorSearch.d.ts.map +1 -0
  712. package/dist/memory/VectorSearch.js +148 -0
  713. package/dist/memory/VectorSearch.js.map +1 -0
  714. package/dist/memory/WorkingMemoryAssembler.d.ts +122 -0
  715. package/dist/memory/WorkingMemoryAssembler.d.ts.map +1 -0
  716. package/dist/memory/WorkingMemoryAssembler.js +406 -0
  717. package/dist/memory/WorkingMemoryAssembler.js.map +1 -0
  718. package/dist/messaging/AdapterRegistry.d.ts +27 -0
  719. package/dist/messaging/AdapterRegistry.d.ts.map +1 -0
  720. package/dist/messaging/AdapterRegistry.js +40 -0
  721. package/dist/messaging/AdapterRegistry.js.map +1 -0
  722. package/dist/messaging/AgentTokenManager.d.ts +86 -0
  723. package/dist/messaging/AgentTokenManager.d.ts.map +1 -0
  724. package/dist/messaging/AgentTokenManager.js +213 -0
  725. package/dist/messaging/AgentTokenManager.js.map +1 -0
  726. package/dist/messaging/DeliveryRetryManager.d.ts +47 -0
  727. package/dist/messaging/DeliveryRetryManager.d.ts.map +1 -0
  728. package/dist/messaging/DeliveryRetryManager.js +201 -0
  729. package/dist/messaging/DeliveryRetryManager.js.map +1 -0
  730. package/dist/messaging/DropPickup.d.ts +37 -0
  731. package/dist/messaging/DropPickup.d.ts.map +1 -0
  732. package/dist/messaging/DropPickup.js +120 -0
  733. package/dist/messaging/DropPickup.js.map +1 -0
  734. package/dist/messaging/GitSyncTransport.d.ts +120 -0
  735. package/dist/messaging/GitSyncTransport.d.ts.map +1 -0
  736. package/dist/messaging/GitSyncTransport.js +296 -0
  737. package/dist/messaging/GitSyncTransport.js.map +1 -0
  738. package/dist/messaging/MessageDelivery.d.ts +27 -0
  739. package/dist/messaging/MessageDelivery.d.ts.map +1 -0
  740. package/dist/messaging/MessageDelivery.js +90 -0
  741. package/dist/messaging/MessageDelivery.js.map +1 -0
  742. package/dist/messaging/MessageFormatter.d.ts +30 -0
  743. package/dist/messaging/MessageFormatter.d.ts.map +1 -0
  744. package/dist/messaging/MessageFormatter.js +97 -0
  745. package/dist/messaging/MessageFormatter.js.map +1 -0
  746. package/dist/messaging/MessageRouter.d.ts +157 -0
  747. package/dist/messaging/MessageRouter.d.ts.map +1 -0
  748. package/dist/messaging/MessageRouter.js +657 -0
  749. package/dist/messaging/MessageRouter.js.map +1 -0
  750. package/dist/messaging/MessageStore.d.ts +52 -0
  751. package/dist/messaging/MessageStore.d.ts.map +1 -0
  752. package/dist/messaging/MessageStore.js +363 -0
  753. package/dist/messaging/MessageStore.js.map +1 -0
  754. package/dist/messaging/NotificationBatcher.d.ts +95 -0
  755. package/dist/messaging/NotificationBatcher.d.ts.map +1 -0
  756. package/dist/messaging/NotificationBatcher.js +253 -0
  757. package/dist/messaging/NotificationBatcher.js.map +1 -0
  758. package/dist/messaging/SessionSummarySentinel.d.ts +104 -0
  759. package/dist/messaging/SessionSummarySentinel.d.ts.map +1 -0
  760. package/dist/messaging/SessionSummarySentinel.js +386 -0
  761. package/dist/messaging/SessionSummarySentinel.js.map +1 -0
  762. package/dist/messaging/SpawnRequestManager.d.ts +88 -0
  763. package/dist/messaging/SpawnRequestManager.d.ts.map +1 -0
  764. package/dist/messaging/SpawnRequestManager.js +159 -0
  765. package/dist/messaging/SpawnRequestManager.js.map +1 -0
  766. package/dist/messaging/TelegramAdapter.d.ts +636 -0
  767. package/dist/messaging/TelegramAdapter.d.ts.map +1 -0
  768. package/dist/messaging/TelegramAdapter.js +3434 -0
  769. package/dist/messaging/TelegramAdapter.js.map +1 -0
  770. package/dist/messaging/TopicContentValidator.d.ts +81 -0
  771. package/dist/messaging/TopicContentValidator.d.ts.map +1 -0
  772. package/dist/messaging/TopicContentValidator.js +113 -0
  773. package/dist/messaging/TopicContentValidator.js.map +1 -0
  774. package/dist/messaging/WhatsAppAdapter.d.ts +224 -0
  775. package/dist/messaging/WhatsAppAdapter.d.ts.map +1 -0
  776. package/dist/messaging/WhatsAppAdapter.js +736 -0
  777. package/dist/messaging/WhatsAppAdapter.js.map +1 -0
  778. package/dist/messaging/backends/BaileysBackend.d.ts +74 -0
  779. package/dist/messaging/backends/BaileysBackend.d.ts.map +1 -0
  780. package/dist/messaging/backends/BaileysBackend.js +481 -0
  781. package/dist/messaging/backends/BaileysBackend.js.map +1 -0
  782. package/dist/messaging/backends/BusinessApiBackend.d.ts +173 -0
  783. package/dist/messaging/backends/BusinessApiBackend.d.ts.map +1 -0
  784. package/dist/messaging/backends/BusinessApiBackend.js +269 -0
  785. package/dist/messaging/backends/BusinessApiBackend.js.map +1 -0
  786. package/dist/messaging/backends/WhatsAppWebhookRoutes.d.ts +26 -0
  787. package/dist/messaging/backends/WhatsAppWebhookRoutes.d.ts.map +1 -0
  788. package/dist/messaging/backends/WhatsAppWebhookRoutes.js +50 -0
  789. package/dist/messaging/backends/WhatsAppWebhookRoutes.js.map +1 -0
  790. package/dist/messaging/shared/AuthGate.d.ts +102 -0
  791. package/dist/messaging/shared/AuthGate.d.ts.map +1 -0
  792. package/dist/messaging/shared/AuthGate.js +159 -0
  793. package/dist/messaging/shared/AuthGate.js.map +1 -0
  794. package/dist/messaging/shared/CommandRouter.d.ts +84 -0
  795. package/dist/messaging/shared/CommandRouter.d.ts.map +1 -0
  796. package/dist/messaging/shared/CommandRouter.js +145 -0
  797. package/dist/messaging/shared/CommandRouter.js.map +1 -0
  798. package/dist/messaging/shared/CrossPlatformAlerts.d.ts +67 -0
  799. package/dist/messaging/shared/CrossPlatformAlerts.d.ts.map +1 -0
  800. package/dist/messaging/shared/CrossPlatformAlerts.js +134 -0
  801. package/dist/messaging/shared/CrossPlatformAlerts.js.map +1 -0
  802. package/dist/messaging/shared/EncryptedAuthStore.d.ts +51 -0
  803. package/dist/messaging/shared/EncryptedAuthStore.d.ts.map +1 -0
  804. package/dist/messaging/shared/EncryptedAuthStore.js +194 -0
  805. package/dist/messaging/shared/EncryptedAuthStore.js.map +1 -0
  806. package/dist/messaging/shared/FeatureFlags.d.ts +21 -0
  807. package/dist/messaging/shared/FeatureFlags.d.ts.map +1 -0
  808. package/dist/messaging/shared/FeatureFlags.js +21 -0
  809. package/dist/messaging/shared/FeatureFlags.js.map +1 -0
  810. package/dist/messaging/shared/MessageBridge.d.ts +70 -0
  811. package/dist/messaging/shared/MessageBridge.d.ts.map +1 -0
  812. package/dist/messaging/shared/MessageBridge.js +178 -0
  813. package/dist/messaging/shared/MessageBridge.js.map +1 -0
  814. package/dist/messaging/shared/MessageLogger.d.ts +94 -0
  815. package/dist/messaging/shared/MessageLogger.d.ts.map +1 -0
  816. package/dist/messaging/shared/MessageLogger.js +173 -0
  817. package/dist/messaging/shared/MessageLogger.js.map +1 -0
  818. package/dist/messaging/shared/MessagingEventBus.d.ts +149 -0
  819. package/dist/messaging/shared/MessagingEventBus.d.ts.map +1 -0
  820. package/dist/messaging/shared/MessagingEventBus.js +111 -0
  821. package/dist/messaging/shared/MessagingEventBus.js.map +1 -0
  822. package/dist/messaging/shared/PhoneUtils.d.ts +44 -0
  823. package/dist/messaging/shared/PhoneUtils.d.ts.map +1 -0
  824. package/dist/messaging/shared/PhoneUtils.js +110 -0
  825. package/dist/messaging/shared/PhoneUtils.js.map +1 -0
  826. package/dist/messaging/shared/PrivacyConsent.d.ts +61 -0
  827. package/dist/messaging/shared/PrivacyConsent.d.ts.map +1 -0
  828. package/dist/messaging/shared/PrivacyConsent.js +132 -0
  829. package/dist/messaging/shared/PrivacyConsent.js.map +1 -0
  830. package/dist/messaging/shared/SessionChannelRegistry.d.ts +48 -0
  831. package/dist/messaging/shared/SessionChannelRegistry.d.ts.map +1 -0
  832. package/dist/messaging/shared/SessionChannelRegistry.js +144 -0
  833. package/dist/messaging/shared/SessionChannelRegistry.js.map +1 -0
  834. package/dist/messaging/shared/SmartChunker.d.ts +16 -0
  835. package/dist/messaging/shared/SmartChunker.d.ts.map +1 -0
  836. package/dist/messaging/shared/SmartChunker.js +100 -0
  837. package/dist/messaging/shared/SmartChunker.js.map +1 -0
  838. package/dist/messaging/shared/StallDetector.d.ts +85 -0
  839. package/dist/messaging/shared/StallDetector.d.ts.map +1 -0
  840. package/dist/messaging/shared/StallDetector.js +225 -0
  841. package/dist/messaging/shared/StallDetector.js.map +1 -0
  842. package/dist/messaging/shared/index.d.ts +22 -0
  843. package/dist/messaging/shared/index.d.ts.map +1 -0
  844. package/dist/messaging/shared/index.js +13 -0
  845. package/dist/messaging/shared/index.js.map +1 -0
  846. package/dist/messaging/slack/ChannelManager.d.ts +36 -0
  847. package/dist/messaging/slack/ChannelManager.d.ts.map +1 -0
  848. package/dist/messaging/slack/ChannelManager.js +100 -0
  849. package/dist/messaging/slack/ChannelManager.js.map +1 -0
  850. package/dist/messaging/slack/FileHandler.d.ts +30 -0
  851. package/dist/messaging/slack/FileHandler.d.ts.map +1 -0
  852. package/dist/messaging/slack/FileHandler.js +87 -0
  853. package/dist/messaging/slack/FileHandler.js.map +1 -0
  854. package/dist/messaging/slack/RingBuffer.d.ts +22 -0
  855. package/dist/messaging/slack/RingBuffer.d.ts.map +1 -0
  856. package/dist/messaging/slack/RingBuffer.js +48 -0
  857. package/dist/messaging/slack/RingBuffer.js.map +1 -0
  858. package/dist/messaging/slack/SlackAdapter.d.ts +283 -0
  859. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -0
  860. package/dist/messaging/slack/SlackAdapter.js +1524 -0
  861. package/dist/messaging/slack/SlackAdapter.js.map +1 -0
  862. package/dist/messaging/slack/SlackApiClient.d.ts +51 -0
  863. package/dist/messaging/slack/SlackApiClient.d.ts.map +1 -0
  864. package/dist/messaging/slack/SlackApiClient.js +94 -0
  865. package/dist/messaging/slack/SlackApiClient.js.map +1 -0
  866. package/dist/messaging/slack/SocketModeClient.d.ts +44 -0
  867. package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -0
  868. package/dist/messaging/slack/SocketModeClient.js +209 -0
  869. package/dist/messaging/slack/SocketModeClient.js.map +1 -0
  870. package/dist/messaging/slack/index.d.ts +12 -0
  871. package/dist/messaging/slack/index.d.ts.map +1 -0
  872. package/dist/messaging/slack/index.js +15 -0
  873. package/dist/messaging/slack/index.js.map +1 -0
  874. package/dist/messaging/slack/sanitize.d.ts +39 -0
  875. package/dist/messaging/slack/sanitize.d.ts.map +1 -0
  876. package/dist/messaging/slack/sanitize.js +71 -0
  877. package/dist/messaging/slack/sanitize.js.map +1 -0
  878. package/dist/messaging/slack/types.d.ts +186 -0
  879. package/dist/messaging/slack/types.d.ts.map +1 -0
  880. package/dist/messaging/slack/types.js +54 -0
  881. package/dist/messaging/slack/types.js.map +1 -0
  882. package/dist/messaging/types.d.ts +400 -0
  883. package/dist/messaging/types.d.ts.map +1 -0
  884. package/dist/messaging/types.js +89 -0
  885. package/dist/messaging/types.js.map +1 -0
  886. package/dist/monitoring/AccountSwitcher.d.ts +61 -0
  887. package/dist/monitoring/AccountSwitcher.d.ts.map +1 -0
  888. package/dist/monitoring/AccountSwitcher.js +196 -0
  889. package/dist/monitoring/AccountSwitcher.js.map +1 -0
  890. package/dist/monitoring/CoherenceMonitor.d.ts +133 -0
  891. package/dist/monitoring/CoherenceMonitor.d.ts.map +1 -0
  892. package/dist/monitoring/CoherenceMonitor.js +550 -0
  893. package/dist/monitoring/CoherenceMonitor.js.map +1 -0
  894. package/dist/monitoring/CommitmentSentinel.d.ts +57 -0
  895. package/dist/monitoring/CommitmentSentinel.d.ts.map +1 -0
  896. package/dist/monitoring/CommitmentSentinel.js +251 -0
  897. package/dist/monitoring/CommitmentSentinel.js.map +1 -0
  898. package/dist/monitoring/CommitmentTracker.d.ts +186 -0
  899. package/dist/monitoring/CommitmentTracker.d.ts.map +1 -0
  900. package/dist/monitoring/CommitmentTracker.js +522 -0
  901. package/dist/monitoring/CommitmentTracker.js.map +1 -0
  902. package/dist/monitoring/CredentialProvider.d.ts +84 -0
  903. package/dist/monitoring/CredentialProvider.d.ts.map +1 -0
  904. package/dist/monitoring/CredentialProvider.js +196 -0
  905. package/dist/monitoring/CredentialProvider.js.map +1 -0
  906. package/dist/monitoring/DegradationReporter.d.ts +123 -0
  907. package/dist/monitoring/DegradationReporter.d.ts.map +1 -0
  908. package/dist/monitoring/DegradationReporter.js +241 -0
  909. package/dist/monitoring/DegradationReporter.js.map +1 -0
  910. package/dist/monitoring/FeedbackAnomalyDetector.d.ts +51 -0
  911. package/dist/monitoring/FeedbackAnomalyDetector.d.ts.map +1 -0
  912. package/dist/monitoring/FeedbackAnomalyDetector.js +120 -0
  913. package/dist/monitoring/FeedbackAnomalyDetector.js.map +1 -0
  914. package/dist/monitoring/HealthChecker.d.ts +45 -0
  915. package/dist/monitoring/HealthChecker.d.ts.map +1 -0
  916. package/dist/monitoring/HealthChecker.js +219 -0
  917. package/dist/monitoring/HealthChecker.js.map +1 -0
  918. package/dist/monitoring/HomeostasisMonitor.d.ts +102 -0
  919. package/dist/monitoring/HomeostasisMonitor.d.ts.map +1 -0
  920. package/dist/monitoring/HomeostasisMonitor.js +185 -0
  921. package/dist/monitoring/HomeostasisMonitor.js.map +1 -0
  922. package/dist/monitoring/HookEventReceiver.d.ts +132 -0
  923. package/dist/monitoring/HookEventReceiver.d.ts.map +1 -0
  924. package/dist/monitoring/HookEventReceiver.js +209 -0
  925. package/dist/monitoring/HookEventReceiver.js.map +1 -0
  926. package/dist/monitoring/InputClassifier.d.ts +68 -0
  927. package/dist/monitoring/InputClassifier.d.ts.map +1 -0
  928. package/dist/monitoring/InputClassifier.js +243 -0
  929. package/dist/monitoring/InputClassifier.js.map +1 -0
  930. package/dist/monitoring/InstructionsVerifier.d.ts +76 -0
  931. package/dist/monitoring/InstructionsVerifier.d.ts.map +1 -0
  932. package/dist/monitoring/InstructionsVerifier.js +116 -0
  933. package/dist/monitoring/InstructionsVerifier.js.map +1 -0
  934. package/dist/monitoring/MemoryPressureMonitor.d.ts +107 -0
  935. package/dist/monitoring/MemoryPressureMonitor.d.ts.map +1 -0
  936. package/dist/monitoring/MemoryPressureMonitor.js +329 -0
  937. package/dist/monitoring/MemoryPressureMonitor.js.map +1 -0
  938. package/dist/monitoring/OrphanProcessReaper.d.ts +125 -0
  939. package/dist/monitoring/OrphanProcessReaper.d.ts.map +1 -0
  940. package/dist/monitoring/OrphanProcessReaper.js +476 -0
  941. package/dist/monitoring/OrphanProcessReaper.js.map +1 -0
  942. package/dist/monitoring/PresenceProxy.d.ts +167 -0
  943. package/dist/monitoring/PresenceProxy.d.ts.map +1 -0
  944. package/dist/monitoring/PresenceProxy.js +972 -0
  945. package/dist/monitoring/PresenceProxy.js.map +1 -0
  946. package/dist/monitoring/PromptGate.d.ts +91 -0
  947. package/dist/monitoring/PromptGate.d.ts.map +1 -0
  948. package/dist/monitoring/PromptGate.js +411 -0
  949. package/dist/monitoring/PromptGate.js.map +1 -0
  950. package/dist/monitoring/QuotaCollector.d.ts +267 -0
  951. package/dist/monitoring/QuotaCollector.d.ts.map +1 -0
  952. package/dist/monitoring/QuotaCollector.js +790 -0
  953. package/dist/monitoring/QuotaCollector.js.map +1 -0
  954. package/dist/monitoring/QuotaExhaustionDetector.d.ts +21 -0
  955. package/dist/monitoring/QuotaExhaustionDetector.d.ts.map +1 -0
  956. package/dist/monitoring/QuotaExhaustionDetector.js +136 -0
  957. package/dist/monitoring/QuotaExhaustionDetector.js.map +1 -0
  958. package/dist/monitoring/QuotaManager.d.ts +191 -0
  959. package/dist/monitoring/QuotaManager.d.ts.map +1 -0
  960. package/dist/monitoring/QuotaManager.js +575 -0
  961. package/dist/monitoring/QuotaManager.js.map +1 -0
  962. package/dist/monitoring/QuotaNotifier.d.ts +38 -0
  963. package/dist/monitoring/QuotaNotifier.d.ts.map +1 -0
  964. package/dist/monitoring/QuotaNotifier.js +144 -0
  965. package/dist/monitoring/QuotaNotifier.js.map +1 -0
  966. package/dist/monitoring/QuotaTracker.d.ts +92 -0
  967. package/dist/monitoring/QuotaTracker.d.ts.map +1 -0
  968. package/dist/monitoring/QuotaTracker.js +239 -0
  969. package/dist/monitoring/QuotaTracker.js.map +1 -0
  970. package/dist/monitoring/ReflectionMetrics.d.ts +97 -0
  971. package/dist/monitoring/ReflectionMetrics.d.ts.map +1 -0
  972. package/dist/monitoring/ReflectionMetrics.js +170 -0
  973. package/dist/monitoring/ReflectionMetrics.js.map +1 -0
  974. package/dist/monitoring/SessionActivitySentinel.d.ts +95 -0
  975. package/dist/monitoring/SessionActivitySentinel.d.ts.map +1 -0
  976. package/dist/monitoring/SessionActivitySentinel.js +391 -0
  977. package/dist/monitoring/SessionActivitySentinel.js.map +1 -0
  978. package/dist/monitoring/SessionCredentialManager.d.ts +56 -0
  979. package/dist/monitoring/SessionCredentialManager.d.ts.map +1 -0
  980. package/dist/monitoring/SessionCredentialManager.js +91 -0
  981. package/dist/monitoring/SessionCredentialManager.js.map +1 -0
  982. package/dist/monitoring/SessionMigrator.d.ts +234 -0
  983. package/dist/monitoring/SessionMigrator.d.ts.map +1 -0
  984. package/dist/monitoring/SessionMigrator.js +604 -0
  985. package/dist/monitoring/SessionMigrator.js.map +1 -0
  986. package/dist/monitoring/SessionMonitor.d.ts +102 -0
  987. package/dist/monitoring/SessionMonitor.d.ts.map +1 -0
  988. package/dist/monitoring/SessionMonitor.js +238 -0
  989. package/dist/monitoring/SessionMonitor.js.map +1 -0
  990. package/dist/monitoring/SessionRecovery.d.ts +129 -0
  991. package/dist/monitoring/SessionRecovery.d.ts.map +1 -0
  992. package/dist/monitoring/SessionRecovery.js +496 -0
  993. package/dist/monitoring/SessionRecovery.js.map +1 -0
  994. package/dist/monitoring/SessionWatchdog.d.ts +138 -0
  995. package/dist/monitoring/SessionWatchdog.d.ts.map +1 -0
  996. package/dist/monitoring/SessionWatchdog.js +549 -0
  997. package/dist/monitoring/SessionWatchdog.js.map +1 -0
  998. package/dist/monitoring/StallTriageNurse.d.ts +96 -0
  999. package/dist/monitoring/StallTriageNurse.d.ts.map +1 -0
  1000. package/dist/monitoring/StallTriageNurse.js +711 -0
  1001. package/dist/monitoring/StallTriageNurse.js.map +1 -0
  1002. package/dist/monitoring/StallTriageNurse.types.d.ts +124 -0
  1003. package/dist/monitoring/StallTriageNurse.types.d.ts.map +1 -0
  1004. package/dist/monitoring/StallTriageNurse.types.js +5 -0
  1005. package/dist/monitoring/StallTriageNurse.types.js.map +1 -0
  1006. package/dist/monitoring/SubagentTracker.d.ts +86 -0
  1007. package/dist/monitoring/SubagentTracker.d.ts.map +1 -0
  1008. package/dist/monitoring/SubagentTracker.js +199 -0
  1009. package/dist/monitoring/SubagentTracker.js.map +1 -0
  1010. package/dist/monitoring/SystemReviewer.d.ts +243 -0
  1011. package/dist/monitoring/SystemReviewer.d.ts.map +1 -0
  1012. package/dist/monitoring/SystemReviewer.js +697 -0
  1013. package/dist/monitoring/SystemReviewer.js.map +1 -0
  1014. package/dist/monitoring/TelemetryAuth.d.ts +64 -0
  1015. package/dist/monitoring/TelemetryAuth.d.ts.map +1 -0
  1016. package/dist/monitoring/TelemetryAuth.js +141 -0
  1017. package/dist/monitoring/TelemetryAuth.js.map +1 -0
  1018. package/dist/monitoring/TelemetryCollector.d.ts +95 -0
  1019. package/dist/monitoring/TelemetryCollector.d.ts.map +1 -0
  1020. package/dist/monitoring/TelemetryCollector.js +326 -0
  1021. package/dist/monitoring/TelemetryCollector.js.map +1 -0
  1022. package/dist/monitoring/TelemetryHeartbeat.d.ts +160 -0
  1023. package/dist/monitoring/TelemetryHeartbeat.d.ts.map +1 -0
  1024. package/dist/monitoring/TelemetryHeartbeat.js +450 -0
  1025. package/dist/monitoring/TelemetryHeartbeat.js.map +1 -0
  1026. package/dist/monitoring/TriageOrchestrator.d.ts +217 -0
  1027. package/dist/monitoring/TriageOrchestrator.d.ts.map +1 -0
  1028. package/dist/monitoring/TriageOrchestrator.js +801 -0
  1029. package/dist/monitoring/TriageOrchestrator.js.map +1 -0
  1030. package/dist/monitoring/WorktreeMonitor.d.ts +124 -0
  1031. package/dist/monitoring/WorktreeMonitor.d.ts.map +1 -0
  1032. package/dist/monitoring/WorktreeMonitor.js +379 -0
  1033. package/dist/monitoring/WorktreeMonitor.js.map +1 -0
  1034. package/dist/monitoring/crash-detector.d.ts +50 -0
  1035. package/dist/monitoring/crash-detector.d.ts.map +1 -0
  1036. package/dist/monitoring/crash-detector.js +224 -0
  1037. package/dist/monitoring/crash-detector.js.map +1 -0
  1038. package/dist/monitoring/jsonl-truncator.d.ts +44 -0
  1039. package/dist/monitoring/jsonl-truncator.d.ts.map +1 -0
  1040. package/dist/monitoring/jsonl-truncator.js +224 -0
  1041. package/dist/monitoring/jsonl-truncator.js.map +1 -0
  1042. package/dist/monitoring/probes/LifelineProbe.d.ts +38 -0
  1043. package/dist/monitoring/probes/LifelineProbe.d.ts.map +1 -0
  1044. package/dist/monitoring/probes/LifelineProbe.js +285 -0
  1045. package/dist/monitoring/probes/LifelineProbe.js.map +1 -0
  1046. package/dist/monitoring/probes/MessagingProbe.d.ts +23 -0
  1047. package/dist/monitoring/probes/MessagingProbe.d.ts.map +1 -0
  1048. package/dist/monitoring/probes/MessagingProbe.js +214 -0
  1049. package/dist/monitoring/probes/MessagingProbe.js.map +1 -0
  1050. package/dist/monitoring/probes/PlatformProbe.d.ts +15 -0
  1051. package/dist/monitoring/probes/PlatformProbe.d.ts.map +1 -0
  1052. package/dist/monitoring/probes/PlatformProbe.js +222 -0
  1053. package/dist/monitoring/probes/PlatformProbe.js.map +1 -0
  1054. package/dist/monitoring/probes/SchedulerProbe.d.ts +33 -0
  1055. package/dist/monitoring/probes/SchedulerProbe.d.ts.map +1 -0
  1056. package/dist/monitoring/probes/SchedulerProbe.js +196 -0
  1057. package/dist/monitoring/probes/SchedulerProbe.js.map +1 -0
  1058. package/dist/monitoring/probes/SessionProbe.d.ts +28 -0
  1059. package/dist/monitoring/probes/SessionProbe.d.ts.map +1 -0
  1060. package/dist/monitoring/probes/SessionProbe.js +234 -0
  1061. package/dist/monitoring/probes/SessionProbe.js.map +1 -0
  1062. package/dist/monitoring/stall-detector.d.ts +34 -0
  1063. package/dist/monitoring/stall-detector.d.ts.map +1 -0
  1064. package/dist/monitoring/stall-detector.js +151 -0
  1065. package/dist/monitoring/stall-detector.js.map +1 -0
  1066. package/dist/paste/PasteManager.d.ts +154 -0
  1067. package/dist/paste/PasteManager.d.ts.map +1 -0
  1068. package/dist/paste/PasteManager.js +524 -0
  1069. package/dist/paste/PasteManager.js.map +1 -0
  1070. package/dist/paste/TruncationDetector.d.ts +51 -0
  1071. package/dist/paste/TruncationDetector.d.ts.map +1 -0
  1072. package/dist/paste/TruncationDetector.js +220 -0
  1073. package/dist/paste/TruncationDetector.js.map +1 -0
  1074. package/dist/privacy/OutputPrivacyRouter.d.ts +65 -0
  1075. package/dist/privacy/OutputPrivacyRouter.d.ts.map +1 -0
  1076. package/dist/privacy/OutputPrivacyRouter.js +156 -0
  1077. package/dist/privacy/OutputPrivacyRouter.js.map +1 -0
  1078. package/dist/publishing/PrivateViewer.d.ts +63 -0
  1079. package/dist/publishing/PrivateViewer.d.ts.map +1 -0
  1080. package/dist/publishing/PrivateViewer.js +398 -0
  1081. package/dist/publishing/PrivateViewer.js.map +1 -0
  1082. package/dist/publishing/TelegraphService.d.ts +137 -0
  1083. package/dist/publishing/TelegraphService.d.ts.map +1 -0
  1084. package/dist/publishing/TelegraphService.js +410 -0
  1085. package/dist/publishing/TelegraphService.js.map +1 -0
  1086. package/dist/scaffold/bootstrap.d.ts +21 -0
  1087. package/dist/scaffold/bootstrap.d.ts.map +1 -0
  1088. package/dist/scaffold/bootstrap.js +125 -0
  1089. package/dist/scaffold/bootstrap.js.map +1 -0
  1090. package/dist/scaffold/templates.d.ts +47 -0
  1091. package/dist/scaffold/templates.d.ts.map +1 -0
  1092. package/dist/scaffold/templates.js +1506 -0
  1093. package/dist/scaffold/templates.js.map +1 -0
  1094. package/dist/scheduler/IntegrationGate.d.ts +81 -0
  1095. package/dist/scheduler/IntegrationGate.d.ts.map +1 -0
  1096. package/dist/scheduler/IntegrationGate.js +242 -0
  1097. package/dist/scheduler/IntegrationGate.js.map +1 -0
  1098. package/dist/scheduler/JobClaimManager.d.ts +137 -0
  1099. package/dist/scheduler/JobClaimManager.d.ts.map +1 -0
  1100. package/dist/scheduler/JobClaimManager.js +283 -0
  1101. package/dist/scheduler/JobClaimManager.js.map +1 -0
  1102. package/dist/scheduler/JobLoader.d.ts +28 -0
  1103. package/dist/scheduler/JobLoader.d.ts.map +1 -0
  1104. package/dist/scheduler/JobLoader.js +269 -0
  1105. package/dist/scheduler/JobLoader.js.map +1 -0
  1106. package/dist/scheduler/JobRunHistory.d.ts +180 -0
  1107. package/dist/scheduler/JobRunHistory.d.ts.map +1 -0
  1108. package/dist/scheduler/JobRunHistory.js +349 -0
  1109. package/dist/scheduler/JobRunHistory.js.map +1 -0
  1110. package/dist/scheduler/JobScheduler.d.ts +244 -0
  1111. package/dist/scheduler/JobScheduler.d.ts.map +1 -0
  1112. package/dist/scheduler/JobScheduler.js +1085 -0
  1113. package/dist/scheduler/JobScheduler.js.map +1 -0
  1114. package/dist/scheduler/SkipLedger.d.ts +65 -0
  1115. package/dist/scheduler/SkipLedger.d.ts.map +1 -0
  1116. package/dist/scheduler/SkipLedger.js +179 -0
  1117. package/dist/scheduler/SkipLedger.js.map +1 -0
  1118. package/dist/security/LLMSanitizer.d.ts +65 -0
  1119. package/dist/security/LLMSanitizer.d.ts.map +1 -0
  1120. package/dist/security/LLMSanitizer.js +153 -0
  1121. package/dist/security/LLMSanitizer.js.map +1 -0
  1122. package/dist/security/ManifestIntegrity.d.ts +72 -0
  1123. package/dist/security/ManifestIntegrity.d.ts.map +1 -0
  1124. package/dist/security/ManifestIntegrity.js +159 -0
  1125. package/dist/security/ManifestIntegrity.js.map +1 -0
  1126. package/dist/server/AgentServer.d.ts +134 -0
  1127. package/dist/server/AgentServer.d.ts.map +1 -0
  1128. package/dist/server/AgentServer.js +307 -0
  1129. package/dist/server/AgentServer.js.map +1 -0
  1130. package/dist/server/SecretDrop.d.ts +133 -0
  1131. package/dist/server/SecretDrop.d.ts.map +1 -0
  1132. package/dist/server/SecretDrop.js +473 -0
  1133. package/dist/server/SecretDrop.js.map +1 -0
  1134. package/dist/server/WebSocketManager.d.ts +80 -0
  1135. package/dist/server/WebSocketManager.d.ts.map +1 -0
  1136. package/dist/server/WebSocketManager.js +367 -0
  1137. package/dist/server/WebSocketManager.js.map +1 -0
  1138. package/dist/server/fileRoutes.d.ts +19 -0
  1139. package/dist/server/fileRoutes.d.ts.map +1 -0
  1140. package/dist/server/fileRoutes.js +578 -0
  1141. package/dist/server/fileRoutes.js.map +1 -0
  1142. package/dist/server/machineAuth.d.ts +87 -0
  1143. package/dist/server/machineAuth.d.ts.map +1 -0
  1144. package/dist/server/machineAuth.js +188 -0
  1145. package/dist/server/machineAuth.js.map +1 -0
  1146. package/dist/server/machineRoutes.d.ts +49 -0
  1147. package/dist/server/machineRoutes.d.ts.map +1 -0
  1148. package/dist/server/machineRoutes.js +305 -0
  1149. package/dist/server/machineRoutes.js.map +1 -0
  1150. package/dist/server/middleware.d.ts +32 -0
  1151. package/dist/server/middleware.d.ts.map +1 -0
  1152. package/dist/server/middleware.js +202 -0
  1153. package/dist/server/middleware.js.map +1 -0
  1154. package/dist/server/routes.d.ts +132 -0
  1155. package/dist/server/routes.d.ts.map +1 -0
  1156. package/dist/server/routes.js +8124 -0
  1157. package/dist/server/routes.js.map +1 -0
  1158. package/dist/threadline/A2AGateway.d.ts +184 -0
  1159. package/dist/threadline/A2AGateway.d.ts.map +1 -0
  1160. package/dist/threadline/A2AGateway.js +438 -0
  1161. package/dist/threadline/A2AGateway.js.map +1 -0
  1162. package/dist/threadline/AgentCard.d.ts +116 -0
  1163. package/dist/threadline/AgentCard.d.ts.map +1 -0
  1164. package/dist/threadline/AgentCard.js +212 -0
  1165. package/dist/threadline/AgentCard.js.map +1 -0
  1166. package/dist/threadline/AgentDiscovery.d.ts +156 -0
  1167. package/dist/threadline/AgentDiscovery.d.ts.map +1 -0
  1168. package/dist/threadline/AgentDiscovery.js +390 -0
  1169. package/dist/threadline/AgentDiscovery.js.map +1 -0
  1170. package/dist/threadline/AgentTrustManager.d.ts +190 -0
  1171. package/dist/threadline/AgentTrustManager.d.ts.map +1 -0
  1172. package/dist/threadline/AgentTrustManager.js +526 -0
  1173. package/dist/threadline/AgentTrustManager.js.map +1 -0
  1174. package/dist/threadline/ApprovalQueue.d.ts +71 -0
  1175. package/dist/threadline/ApprovalQueue.d.ts.map +1 -0
  1176. package/dist/threadline/ApprovalQueue.js +154 -0
  1177. package/dist/threadline/ApprovalQueue.js.map +1 -0
  1178. package/dist/threadline/AutonomyGate.d.ts +130 -0
  1179. package/dist/threadline/AutonomyGate.d.ts.map +1 -0
  1180. package/dist/threadline/AutonomyGate.js +267 -0
  1181. package/dist/threadline/AutonomyGate.js.map +1 -0
  1182. package/dist/threadline/CircuitBreaker.d.ts +89 -0
  1183. package/dist/threadline/CircuitBreaker.d.ts.map +1 -0
  1184. package/dist/threadline/CircuitBreaker.js +238 -0
  1185. package/dist/threadline/CircuitBreaker.js.map +1 -0
  1186. package/dist/threadline/ComputeMeter.d.ts +114 -0
  1187. package/dist/threadline/ComputeMeter.d.ts.map +1 -0
  1188. package/dist/threadline/ComputeMeter.js +350 -0
  1189. package/dist/threadline/ComputeMeter.js.map +1 -0
  1190. package/dist/threadline/ContentClassifier.d.ts +83 -0
  1191. package/dist/threadline/ContentClassifier.d.ts.map +1 -0
  1192. package/dist/threadline/ContentClassifier.js +201 -0
  1193. package/dist/threadline/ContentClassifier.js.map +1 -0
  1194. package/dist/threadline/ContextThreadMap.d.ts +103 -0
  1195. package/dist/threadline/ContextThreadMap.d.ts.map +1 -0
  1196. package/dist/threadline/ContextThreadMap.js +279 -0
  1197. package/dist/threadline/ContextThreadMap.js.map +1 -0
  1198. package/dist/threadline/DNSVerifier.d.ts +48 -0
  1199. package/dist/threadline/DNSVerifier.d.ts.map +1 -0
  1200. package/dist/threadline/DNSVerifier.js +138 -0
  1201. package/dist/threadline/DNSVerifier.js.map +1 -0
  1202. package/dist/threadline/DigestCollector.d.ts +70 -0
  1203. package/dist/threadline/DigestCollector.d.ts.map +1 -0
  1204. package/dist/threadline/DigestCollector.js +146 -0
  1205. package/dist/threadline/DigestCollector.js.map +1 -0
  1206. package/dist/threadline/HandshakeManager.d.ts +130 -0
  1207. package/dist/threadline/HandshakeManager.d.ts.map +1 -0
  1208. package/dist/threadline/HandshakeManager.js +402 -0
  1209. package/dist/threadline/HandshakeManager.js.map +1 -0
  1210. package/dist/threadline/InboundMessageGate.d.ts +80 -0
  1211. package/dist/threadline/InboundMessageGate.d.ts.map +1 -0
  1212. package/dist/threadline/InboundMessageGate.js +241 -0
  1213. package/dist/threadline/InboundMessageGate.js.map +1 -0
  1214. package/dist/threadline/InvitationManager.d.ts +91 -0
  1215. package/dist/threadline/InvitationManager.d.ts.map +1 -0
  1216. package/dist/threadline/InvitationManager.js +228 -0
  1217. package/dist/threadline/InvitationManager.js.map +1 -0
  1218. package/dist/threadline/ListenerSessionManager.d.ts +147 -0
  1219. package/dist/threadline/ListenerSessionManager.d.ts.map +1 -0
  1220. package/dist/threadline/ListenerSessionManager.js +326 -0
  1221. package/dist/threadline/ListenerSessionManager.js.map +1 -0
  1222. package/dist/threadline/MCPAuth.d.ts +98 -0
  1223. package/dist/threadline/MCPAuth.d.ts.map +1 -0
  1224. package/dist/threadline/MCPAuth.js +228 -0
  1225. package/dist/threadline/MCPAuth.js.map +1 -0
  1226. package/dist/threadline/OpenClawBridge.d.ts +143 -0
  1227. package/dist/threadline/OpenClawBridge.d.ts.map +1 -0
  1228. package/dist/threadline/OpenClawBridge.js +336 -0
  1229. package/dist/threadline/OpenClawBridge.js.map +1 -0
  1230. package/dist/threadline/OpenClawSkillManifest.d.ts +47 -0
  1231. package/dist/threadline/OpenClawSkillManifest.d.ts.map +1 -0
  1232. package/dist/threadline/OpenClawSkillManifest.js +148 -0
  1233. package/dist/threadline/OpenClawSkillManifest.js.map +1 -0
  1234. package/dist/threadline/RateLimiter.d.ts +105 -0
  1235. package/dist/threadline/RateLimiter.d.ts.map +1 -0
  1236. package/dist/threadline/RateLimiter.js +236 -0
  1237. package/dist/threadline/RateLimiter.js.map +1 -0
  1238. package/dist/threadline/RelayGroundingPreamble.d.ts +48 -0
  1239. package/dist/threadline/RelayGroundingPreamble.d.ts.map +1 -0
  1240. package/dist/threadline/RelayGroundingPreamble.js +68 -0
  1241. package/dist/threadline/RelayGroundingPreamble.js.map +1 -0
  1242. package/dist/threadline/SessionLifecycle.d.ts +136 -0
  1243. package/dist/threadline/SessionLifecycle.d.ts.map +1 -0
  1244. package/dist/threadline/SessionLifecycle.js +317 -0
  1245. package/dist/threadline/SessionLifecycle.js.map +1 -0
  1246. package/dist/threadline/ThreadResumeMap.d.ts +128 -0
  1247. package/dist/threadline/ThreadResumeMap.d.ts.map +1 -0
  1248. package/dist/threadline/ThreadResumeMap.js +324 -0
  1249. package/dist/threadline/ThreadResumeMap.js.map +1 -0
  1250. package/dist/threadline/ThreadlineBootstrap.d.ts +68 -0
  1251. package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -0
  1252. package/dist/threadline/ThreadlineBootstrap.js +293 -0
  1253. package/dist/threadline/ThreadlineBootstrap.js.map +1 -0
  1254. package/dist/threadline/ThreadlineCrypto.d.ts +53 -0
  1255. package/dist/threadline/ThreadlineCrypto.d.ts.map +1 -0
  1256. package/dist/threadline/ThreadlineCrypto.js +123 -0
  1257. package/dist/threadline/ThreadlineCrypto.js.map +1 -0
  1258. package/dist/threadline/ThreadlineEndpoints.d.ts +35 -0
  1259. package/dist/threadline/ThreadlineEndpoints.d.ts.map +1 -0
  1260. package/dist/threadline/ThreadlineEndpoints.js +313 -0
  1261. package/dist/threadline/ThreadlineEndpoints.js.map +1 -0
  1262. package/dist/threadline/ThreadlineMCPServer.d.ts +165 -0
  1263. package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -0
  1264. package/dist/threadline/ThreadlineMCPServer.js +885 -0
  1265. package/dist/threadline/ThreadlineMCPServer.js.map +1 -0
  1266. package/dist/threadline/ThreadlineRouter.d.ts +105 -0
  1267. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -0
  1268. package/dist/threadline/ThreadlineRouter.js +323 -0
  1269. package/dist/threadline/ThreadlineRouter.js.map +1 -0
  1270. package/dist/threadline/TrustBootstrap.d.ts +92 -0
  1271. package/dist/threadline/TrustBootstrap.d.ts.map +1 -0
  1272. package/dist/threadline/TrustBootstrap.js +256 -0
  1273. package/dist/threadline/TrustBootstrap.js.map +1 -0
  1274. package/dist/threadline/adapters/AutoGenTool.d.ts +42 -0
  1275. package/dist/threadline/adapters/AutoGenTool.d.ts.map +1 -0
  1276. package/dist/threadline/adapters/AutoGenTool.js +145 -0
  1277. package/dist/threadline/adapters/AutoGenTool.js.map +1 -0
  1278. package/dist/threadline/adapters/CrewAITool.d.ts +31 -0
  1279. package/dist/threadline/adapters/CrewAITool.d.ts.map +1 -0
  1280. package/dist/threadline/adapters/CrewAITool.js +112 -0
  1281. package/dist/threadline/adapters/CrewAITool.js.map +1 -0
  1282. package/dist/threadline/adapters/LangGraphTool.d.ts +48 -0
  1283. package/dist/threadline/adapters/LangGraphTool.d.ts.map +1 -0
  1284. package/dist/threadline/adapters/LangGraphTool.js +153 -0
  1285. package/dist/threadline/adapters/LangGraphTool.js.map +1 -0
  1286. package/dist/threadline/adapters/RESTServer.d.ts +74 -0
  1287. package/dist/threadline/adapters/RESTServer.d.ts.map +1 -0
  1288. package/dist/threadline/adapters/RESTServer.js +291 -0
  1289. package/dist/threadline/adapters/RESTServer.js.map +1 -0
  1290. package/dist/threadline/adapters/index.d.ts +14 -0
  1291. package/dist/threadline/adapters/index.d.ts.map +1 -0
  1292. package/dist/threadline/adapters/index.js +10 -0
  1293. package/dist/threadline/adapters/index.js.map +1 -0
  1294. package/dist/threadline/client/IdentityManager.d.ts +40 -0
  1295. package/dist/threadline/client/IdentityManager.d.ts.map +1 -0
  1296. package/dist/threadline/client/IdentityManager.js +106 -0
  1297. package/dist/threadline/client/IdentityManager.js.map +1 -0
  1298. package/dist/threadline/client/MessageEncryptor.d.ts +63 -0
  1299. package/dist/threadline/client/MessageEncryptor.d.ts.map +1 -0
  1300. package/dist/threadline/client/MessageEncryptor.js +195 -0
  1301. package/dist/threadline/client/MessageEncryptor.js.map +1 -0
  1302. package/dist/threadline/client/RegistryRestClient.d.ts +46 -0
  1303. package/dist/threadline/client/RegistryRestClient.d.ts.map +1 -0
  1304. package/dist/threadline/client/RegistryRestClient.js +114 -0
  1305. package/dist/threadline/client/RegistryRestClient.js.map +1 -0
  1306. package/dist/threadline/client/RelayClient.d.ts +77 -0
  1307. package/dist/threadline/client/RelayClient.d.ts.map +1 -0
  1308. package/dist/threadline/client/RelayClient.js +249 -0
  1309. package/dist/threadline/client/RelayClient.js.map +1 -0
  1310. package/dist/threadline/client/ThreadlineClient.d.ts +117 -0
  1311. package/dist/threadline/client/ThreadlineClient.d.ts.map +1 -0
  1312. package/dist/threadline/client/ThreadlineClient.js +286 -0
  1313. package/dist/threadline/client/ThreadlineClient.js.map +1 -0
  1314. package/dist/threadline/client/index.d.ts +14 -0
  1315. package/dist/threadline/client/index.d.ts.map +1 -0
  1316. package/dist/threadline/client/index.js +9 -0
  1317. package/dist/threadline/client/index.js.map +1 -0
  1318. package/dist/threadline/index.d.ts +81 -0
  1319. package/dist/threadline/index.d.ts.map +1 -0
  1320. package/dist/threadline/index.js +57 -0
  1321. package/dist/threadline/index.js.map +1 -0
  1322. package/dist/threadline/mcp-stdio-entry.d.ts +24 -0
  1323. package/dist/threadline/mcp-stdio-entry.d.ts.map +1 -0
  1324. package/dist/threadline/mcp-stdio-entry.js +230 -0
  1325. package/dist/threadline/mcp-stdio-entry.js.map +1 -0
  1326. package/dist/threadline/relay/A2ABridge.d.ts +91 -0
  1327. package/dist/threadline/relay/A2ABridge.d.ts.map +1 -0
  1328. package/dist/threadline/relay/A2ABridge.js +457 -0
  1329. package/dist/threadline/relay/A2ABridge.js.map +1 -0
  1330. package/dist/threadline/relay/AbuseDetector.d.ts +131 -0
  1331. package/dist/threadline/relay/AbuseDetector.d.ts.map +1 -0
  1332. package/dist/threadline/relay/AbuseDetector.js +358 -0
  1333. package/dist/threadline/relay/AbuseDetector.js.map +1 -0
  1334. package/dist/threadline/relay/AdminServer.d.ts +55 -0
  1335. package/dist/threadline/relay/AdminServer.d.ts.map +1 -0
  1336. package/dist/threadline/relay/AdminServer.js +215 -0
  1337. package/dist/threadline/relay/AdminServer.js.map +1 -0
  1338. package/dist/threadline/relay/ConnectionManager.d.ts +86 -0
  1339. package/dist/threadline/relay/ConnectionManager.d.ts.map +1 -0
  1340. package/dist/threadline/relay/ConnectionManager.js +356 -0
  1341. package/dist/threadline/relay/ConnectionManager.js.map +1 -0
  1342. package/dist/threadline/relay/MessageRouter.d.ts +46 -0
  1343. package/dist/threadline/relay/MessageRouter.d.ts.map +1 -0
  1344. package/dist/threadline/relay/MessageRouter.js +138 -0
  1345. package/dist/threadline/relay/MessageRouter.js.map +1 -0
  1346. package/dist/threadline/relay/OfflineQueue.d.ts +87 -0
  1347. package/dist/threadline/relay/OfflineQueue.d.ts.map +1 -0
  1348. package/dist/threadline/relay/OfflineQueue.js +137 -0
  1349. package/dist/threadline/relay/OfflineQueue.js.map +1 -0
  1350. package/dist/threadline/relay/PresenceRegistry.d.ts +62 -0
  1351. package/dist/threadline/relay/PresenceRegistry.d.ts.map +1 -0
  1352. package/dist/threadline/relay/PresenceRegistry.js +148 -0
  1353. package/dist/threadline/relay/PresenceRegistry.js.map +1 -0
  1354. package/dist/threadline/relay/RegistryAuth.d.ts +45 -0
  1355. package/dist/threadline/relay/RegistryAuth.d.ts.map +1 -0
  1356. package/dist/threadline/relay/RegistryAuth.js +118 -0
  1357. package/dist/threadline/relay/RegistryAuth.js.map +1 -0
  1358. package/dist/threadline/relay/RegistryStore.d.ts +149 -0
  1359. package/dist/threadline/relay/RegistryStore.d.ts.map +1 -0
  1360. package/dist/threadline/relay/RegistryStore.js +542 -0
  1361. package/dist/threadline/relay/RegistryStore.js.map +1 -0
  1362. package/dist/threadline/relay/RelayMetrics.d.ts +62 -0
  1363. package/dist/threadline/relay/RelayMetrics.d.ts.map +1 -0
  1364. package/dist/threadline/relay/RelayMetrics.js +149 -0
  1365. package/dist/threadline/relay/RelayMetrics.js.map +1 -0
  1366. package/dist/threadline/relay/RelayRateLimiter.d.ts +58 -0
  1367. package/dist/threadline/relay/RelayRateLimiter.d.ts.map +1 -0
  1368. package/dist/threadline/relay/RelayRateLimiter.js +116 -0
  1369. package/dist/threadline/relay/RelayRateLimiter.js.map +1 -0
  1370. package/dist/threadline/relay/RelayServer.d.ts +94 -0
  1371. package/dist/threadline/relay/RelayServer.d.ts.map +1 -0
  1372. package/dist/threadline/relay/RelayServer.js +1049 -0
  1373. package/dist/threadline/relay/RelayServer.js.map +1 -0
  1374. package/dist/threadline/relay/index.d.ts +28 -0
  1375. package/dist/threadline/relay/index.d.ts.map +1 -0
  1376. package/dist/threadline/relay/index.js +17 -0
  1377. package/dist/threadline/relay/index.js.map +1 -0
  1378. package/dist/threadline/relay/types.d.ts +215 -0
  1379. package/dist/threadline/relay/types.d.ts.map +1 -0
  1380. package/dist/threadline/relay/types.js +21 -0
  1381. package/dist/threadline/relay/types.js.map +1 -0
  1382. package/dist/threadline/types.d.ts +39 -0
  1383. package/dist/threadline/types.d.ts.map +1 -0
  1384. package/dist/threadline/types.js +10 -0
  1385. package/dist/threadline/types.js.map +1 -0
  1386. package/dist/tunnel/TunnelManager.d.ts +113 -0
  1387. package/dist/tunnel/TunnelManager.d.ts.map +1 -0
  1388. package/dist/tunnel/TunnelManager.js +474 -0
  1389. package/dist/tunnel/TunnelManager.js.map +1 -0
  1390. package/dist/types/pipeline.d.ts +203 -0
  1391. package/dist/types/pipeline.d.ts.map +1 -0
  1392. package/dist/types/pipeline.js +152 -0
  1393. package/dist/types/pipeline.js.map +1 -0
  1394. package/dist/users/GdprCommands.d.ts +44 -0
  1395. package/dist/users/GdprCommands.d.ts.map +1 -0
  1396. package/dist/users/GdprCommands.js +153 -0
  1397. package/dist/users/GdprCommands.js.map +1 -0
  1398. package/dist/users/OnboardingGate.d.ts +107 -0
  1399. package/dist/users/OnboardingGate.d.ts.map +1 -0
  1400. package/dist/users/OnboardingGate.js +240 -0
  1401. package/dist/users/OnboardingGate.js.map +1 -0
  1402. package/dist/users/UserContextBuilder.d.ts +47 -0
  1403. package/dist/users/UserContextBuilder.d.ts.map +1 -0
  1404. package/dist/users/UserContextBuilder.js +174 -0
  1405. package/dist/users/UserContextBuilder.js.map +1 -0
  1406. package/dist/users/UserManager.d.ts +76 -0
  1407. package/dist/users/UserManager.d.ts.map +1 -0
  1408. package/dist/users/UserManager.js +213 -0
  1409. package/dist/users/UserManager.js.map +1 -0
  1410. package/dist/users/UserOnboarding.d.ts +145 -0
  1411. package/dist/users/UserOnboarding.d.ts.map +1 -0
  1412. package/dist/users/UserOnboarding.js +488 -0
  1413. package/dist/users/UserOnboarding.js.map +1 -0
  1414. package/dist/users/UserPropagator.d.ts +75 -0
  1415. package/dist/users/UserPropagator.d.ts.map +1 -0
  1416. package/dist/users/UserPropagator.js +145 -0
  1417. package/dist/users/UserPropagator.js.map +1 -0
  1418. package/dist/utils/jsonl-rotation.d.ts +27 -0
  1419. package/dist/utils/jsonl-rotation.d.ts.map +1 -0
  1420. package/dist/utils/jsonl-rotation.js +63 -0
  1421. package/dist/utils/jsonl-rotation.js.map +1 -0
  1422. package/dist/utils/privacy.d.ts +88 -0
  1423. package/dist/utils/privacy.d.ts.map +1 -0
  1424. package/dist/utils/privacy.js +182 -0
  1425. package/dist/utils/privacy.js.map +1 -0
  1426. package/dist/utils/sanitize.d.ts +81 -0
  1427. package/dist/utils/sanitize.d.ts.map +1 -0
  1428. package/dist/utils/sanitize.js +122 -0
  1429. package/dist/utils/sanitize.js.map +1 -0
  1430. package/package.json +1 -0
  1431. package/playbook-scripts/atomic_write.py +133 -0
  1432. package/playbook-scripts/bootstrap-manifest.json +92 -0
  1433. package/playbook-scripts/playbook-annotate-context.py +239 -0
  1434. package/playbook-scripts/playbook-assemble.py +385 -0
  1435. package/playbook-scripts/playbook-dashboard.py +242 -0
  1436. package/playbook-scripts/playbook-decay.py +377 -0
  1437. package/playbook-scripts/playbook-dedup-job.py +252 -0
  1438. package/playbook-scripts/playbook-dedup.py +341 -0
  1439. package/playbook-scripts/playbook-delta-validator.py +576 -0
  1440. package/playbook-scripts/playbook-dsar.py +291 -0
  1441. package/playbook-scripts/playbook-eval-log.py +425 -0
  1442. package/playbook-scripts/playbook-failsafe.py +513 -0
  1443. package/playbook-scripts/playbook-feedback-quarantine.py +335 -0
  1444. package/playbook-scripts/playbook-history.py +293 -0
  1445. package/playbook-scripts/playbook-hmac.py +224 -0
  1446. package/playbook-scripts/playbook-lifecycle.py +952 -0
  1447. package/playbook-scripts/playbook-manifest.py +458 -0
  1448. package/playbook-scripts/playbook-micro-eval.py +316 -0
  1449. package/playbook-scripts/playbook-migrate-lessons.py +396 -0
  1450. package/playbook-scripts/playbook-mount.py +393 -0
  1451. package/playbook-scripts/playbook-offline-adapt.py +323 -0
  1452. package/playbook-scripts/playbook-pii-screen.py +207 -0
  1453. package/playbook-scripts/playbook-reflector.py +266 -0
  1454. package/playbook-scripts/playbook-relevance.py +269 -0
  1455. package/playbook-scripts/playbook-retirement.py +365 -0
  1456. package/playbook-scripts/playbook-schema-validate.py +267 -0
  1457. package/playbook-scripts/playbook-scratchpad.py +346 -0
  1458. package/playbook-scripts/playbook-semantic-verify.py +280 -0
  1459. package/playbook-scripts/playbook-spawn-contract.py +341 -0
  1460. package/playbook-scripts/playbook-token-reestimate.py +248 -0
  1461. package/playbook-scripts/playbook-verify.py +357 -0
  1462. package/playbook-scripts/playbook_backend.py +249 -0
  1463. package/playbook-scripts/playbook_paths.py +232 -0
  1464. package/playbook-scripts/schemas/context-delta.schema.json +137 -0
  1465. package/playbook-scripts/schemas/context-manifest.schema.json +200 -0
  1466. package/playbook-scripts/schemas/playbook-config.schema.json +184 -0
  1467. package/scripts/analyze-release.js +752 -0
  1468. package/scripts/check-upgrade-guide.js +373 -0
  1469. package/scripts/collect-metrics.py +248 -0
  1470. package/scripts/demo-two-agents.mjs +187 -0
  1471. package/scripts/fix-better-sqlite3.cjs +100 -0
  1472. package/scripts/generate-builtin-manifest.cjs +440 -0
  1473. package/scripts/pre-push-gate.js +177 -0
  1474. package/scripts/relay-entrypoint.mjs +18 -0
  1475. package/scripts/seed-registry.mjs +258 -0
  1476. package/scripts/telemetry-worker/worker.js +776 -0
  1477. package/scripts/telemetry-worker/wrangler.toml +7 -0
  1478. package/scripts/test-bootstrap-relay.mjs +90 -0
  1479. package/scripts/test-multi-agent-relay.mjs +395 -0
  1480. package/scripts/test-relay-cloud.mjs +389 -0
  1481. package/scripts/test-relay-live.mjs +550 -0
  1482. package/src/data/builtin-manifest.json +1463 -0
  1483. package/src/data/http-hook-templates.ts +81 -0
  1484. package/src/templates/hooks/compaction-recovery.sh +371 -0
  1485. package/src/templates/hooks/dangerous-command-guard.sh +100 -0
  1486. package/src/templates/hooks/free-text-guard.sh +96 -0
  1487. package/src/templates/hooks/grounding-before-messaging.sh +52 -0
  1488. package/src/templates/hooks/session-start.sh +339 -0
  1489. package/src/templates/hooks/settings-template.json +142 -0
  1490. package/src/templates/hooks/slack-channel-context.sh +98 -0
  1491. package/src/templates/hooks/telegram-topic-context.sh +117 -0
  1492. package/src/templates/scripts/convergence-check.sh +99 -0
  1493. package/src/templates/scripts/git-sync-gate.sh +89 -0
  1494. package/src/templates/scripts/health-watchdog.sh +63 -0
  1495. package/src/templates/scripts/serendipity-capture.sh +345 -0
  1496. package/src/templates/scripts/slack-reply.sh +74 -0
  1497. package/src/templates/scripts/smart-fetch.py +215 -0
  1498. package/src/templates/scripts/telegram-reply.sh +67 -0
  1499. package/src/templates/scripts/whatsapp-reply.sh +68 -0
  1500. package/upgrades/0.10.0.md +254 -0
  1501. package/upgrades/0.10.1.md +47 -0
  1502. package/upgrades/0.10.2.md +26 -0
  1503. package/upgrades/0.10.3.md +23 -0
  1504. package/upgrades/0.10.4.md +26 -0
  1505. package/upgrades/0.10.5.md +19 -0
  1506. package/upgrades/0.10.6.md +35 -0
  1507. package/upgrades/0.10.7.md +48 -0
  1508. package/upgrades/0.10.8.md +53 -0
  1509. package/upgrades/0.10.9.md +21 -0
  1510. package/upgrades/0.11.0.md +146 -0
  1511. package/upgrades/0.12.0.md +31 -0
  1512. package/upgrades/0.12.1.md +21 -0
  1513. package/upgrades/0.12.10.md +26 -0
  1514. package/upgrades/0.12.11.md +23 -0
  1515. package/upgrades/0.12.12.md +23 -0
  1516. package/upgrades/0.12.13.md +19 -0
  1517. package/upgrades/0.12.14.md +21 -0
  1518. package/upgrades/0.12.15.md +26 -0
  1519. package/upgrades/0.12.16.md +33 -0
  1520. package/upgrades/0.12.17.md +38 -0
  1521. package/upgrades/0.12.18.md +27 -0
  1522. package/upgrades/0.12.19.md +31 -0
  1523. package/upgrades/0.12.2.md +27 -0
  1524. package/upgrades/0.12.20.md +24 -0
  1525. package/upgrades/0.12.21.md +28 -0
  1526. package/upgrades/0.12.22.md +23 -0
  1527. package/upgrades/0.12.23.md +44 -0
  1528. package/upgrades/0.12.24.md +24 -0
  1529. package/upgrades/0.12.25.md +55 -0
  1530. package/upgrades/0.12.26.md +31 -0
  1531. package/upgrades/0.12.27.md +19 -0
  1532. package/upgrades/0.12.28.md +19 -0
  1533. package/upgrades/0.12.29.md +42 -0
  1534. package/upgrades/0.12.3.md +22 -0
  1535. package/upgrades/0.12.31.md +24 -0
  1536. package/upgrades/0.12.32.md +34 -0
  1537. package/upgrades/0.12.33.md +62 -0
  1538. package/upgrades/0.12.34.md +59 -0
  1539. package/upgrades/0.12.4.md +19 -0
  1540. package/upgrades/0.12.5.md +31 -0
  1541. package/upgrades/0.12.6.md +34 -0
  1542. package/upgrades/0.12.7.md +24 -0
  1543. package/upgrades/0.12.8.md +28 -0
  1544. package/upgrades/0.12.9.md +30 -0
  1545. package/upgrades/0.13.0.md +26 -0
  1546. package/upgrades/0.14.0.md +75 -0
  1547. package/upgrades/0.14.1.md +41 -0
  1548. package/upgrades/0.15.0.md +59 -0
  1549. package/upgrades/0.16.0.md +61 -0
  1550. package/upgrades/0.17.0.md +88 -0
  1551. package/upgrades/0.17.10.md +23 -0
  1552. package/upgrades/0.17.11.md +25 -0
  1553. package/upgrades/0.17.12.md +43 -0
  1554. package/upgrades/0.17.13.md +24 -0
  1555. package/upgrades/0.17.14.md +26 -0
  1556. package/upgrades/0.17.2.md +42 -0
  1557. package/upgrades/0.17.3.md +37 -0
  1558. package/upgrades/0.17.4.md +27 -0
  1559. package/upgrades/0.17.5.md +32 -0
  1560. package/upgrades/0.17.6.md +32 -0
  1561. package/upgrades/0.17.7.md +39 -0
  1562. package/upgrades/0.17.8.md +34 -0
  1563. package/upgrades/0.17.9.md +25 -0
  1564. package/upgrades/0.18.1.md +34 -0
  1565. package/upgrades/0.18.2.md +29 -0
  1566. package/upgrades/0.18.3.md +26 -0
  1567. package/upgrades/0.18.4.md +28 -0
  1568. package/upgrades/0.18.5.md +25 -0
  1569. package/upgrades/0.18.6.md +25 -0
  1570. package/upgrades/0.18.7.md +30 -0
  1571. package/upgrades/0.19.0.md +136 -0
  1572. package/upgrades/0.19.1.md +27 -0
  1573. package/upgrades/0.19.2.md +27 -0
  1574. package/upgrades/0.19.3.md +32 -0
  1575. package/upgrades/0.19.4.md +19 -0
  1576. package/upgrades/0.19.6.md +17 -0
  1577. package/upgrades/0.19.7.md +33 -0
  1578. package/upgrades/0.20.0.md +54 -0
  1579. package/upgrades/0.21.1.md +55 -0
  1580. package/upgrades/0.21.2.md +48 -0
  1581. package/upgrades/0.21.3.md +29 -0
  1582. package/upgrades/0.21.4.md +33 -0
  1583. package/upgrades/0.22.0.md +114 -0
  1584. package/upgrades/0.23.0.md +81 -0
  1585. package/upgrades/0.23.1.md +28 -0
  1586. package/upgrades/0.23.10.md +19 -0
  1587. package/upgrades/0.23.11.md +21 -0
  1588. package/upgrades/0.23.12.md +30 -0
  1589. package/upgrades/0.23.13.md +25 -0
  1590. package/upgrades/0.23.14.md +23 -0
  1591. package/upgrades/0.23.15.md +30 -0
  1592. package/upgrades/0.23.16.md +21 -0
  1593. package/upgrades/0.23.17.md +23 -0
  1594. package/upgrades/0.23.18.md +41 -0
  1595. package/upgrades/0.23.2.md +32 -0
  1596. package/upgrades/0.23.4.md +21 -0
  1597. package/upgrades/0.23.6.md +19 -0
  1598. package/upgrades/0.23.7.md +33 -0
  1599. package/upgrades/0.23.8.md +38 -0
  1600. package/upgrades/0.23.9.md +35 -0
  1601. package/upgrades/0.24.1.md +32 -0
  1602. package/upgrades/0.24.10.md +23 -0
  1603. package/upgrades/0.24.12.md +17 -0
  1604. package/upgrades/0.24.13.md +16 -0
  1605. package/upgrades/0.24.14.md +26 -0
  1606. package/upgrades/0.24.15.md +49 -0
  1607. package/upgrades/0.24.16.md +48 -0
  1608. package/upgrades/0.24.17.md +40 -0
  1609. package/upgrades/0.24.18-beta.0.md +35 -0
  1610. package/upgrades/0.24.18.md +35 -0
  1611. package/upgrades/0.24.19.md +21 -0
  1612. package/upgrades/0.24.2.md +13 -0
  1613. package/upgrades/0.24.20.md +45 -0
  1614. package/upgrades/0.24.21.md +25 -0
  1615. package/upgrades/0.24.22.md +35 -0
  1616. package/upgrades/0.24.23.md +17 -0
  1617. package/upgrades/0.24.24.md +15 -0
  1618. package/upgrades/0.24.25.md +15 -0
  1619. package/upgrades/0.24.26.md +15 -0
  1620. package/upgrades/0.24.27.md +17 -0
  1621. package/upgrades/0.24.28.md +35 -0
  1622. package/upgrades/0.24.29.md +15 -0
  1623. package/upgrades/0.24.30.md +40 -0
  1624. package/upgrades/0.24.31.md +45 -0
  1625. package/upgrades/0.24.32.md +19 -0
  1626. package/upgrades/0.24.33.md +35 -0
  1627. package/upgrades/0.24.34.md +29 -0
  1628. package/upgrades/0.24.4.md +19 -0
  1629. package/upgrades/0.24.5.md +20 -0
  1630. package/upgrades/0.25.0.md +34 -0
  1631. package/upgrades/0.25.1.md +24 -0
  1632. package/upgrades/0.25.10.md +26 -0
  1633. package/upgrades/0.25.2.md +23 -0
  1634. package/upgrades/0.25.3.md +25 -0
  1635. package/upgrades/0.25.4.md +24 -0
  1636. package/upgrades/0.25.5.md +19 -0
  1637. package/upgrades/0.25.6.md +35 -0
  1638. package/upgrades/0.25.7.md +18 -0
  1639. package/upgrades/0.25.8.md +24 -0
  1640. package/upgrades/0.25.9.md +19 -0
  1641. package/upgrades/0.26.0.md +23 -0
  1642. package/upgrades/0.26.1.md +22 -0
  1643. package/upgrades/0.26.2.md +15 -0
  1644. package/upgrades/0.8.12.md +49 -0
  1645. package/upgrades/0.8.13.md +38 -0
  1646. package/upgrades/0.8.17.md +36 -0
  1647. package/upgrades/0.8.22.md +43 -0
  1648. package/upgrades/0.8.23.md +106 -0
  1649. package/upgrades/0.9.1.md +91 -0
  1650. package/upgrades/0.9.10.md +40 -0
  1651. package/upgrades/0.9.11.md +77 -0
  1652. package/upgrades/0.9.12.md +42 -0
  1653. package/upgrades/0.9.13.md +55 -0
  1654. package/upgrades/0.9.14.md +23 -0
  1655. package/upgrades/0.9.15.md +106 -0
  1656. package/upgrades/0.9.16.md +37 -0
  1657. package/upgrades/0.9.17.md +15 -0
  1658. package/upgrades/0.9.19.md +17 -0
  1659. package/upgrades/0.9.20.md +24 -0
  1660. package/upgrades/0.9.21.md +37 -0
  1661. package/upgrades/0.9.22.md +41 -0
  1662. package/upgrades/0.9.23.md +37 -0
  1663. package/upgrades/0.9.24.md +46 -0
  1664. package/upgrades/0.9.25.md +37 -0
  1665. package/upgrades/0.9.28.md +20 -0
  1666. package/upgrades/0.9.29.md +34 -0
  1667. package/upgrades/0.9.32.md +30 -0
  1668. package/upgrades/0.9.36.md +27 -0
  1669. package/upgrades/0.9.8.md +125 -0
  1670. package/upgrades/0.9.9.md +34 -0
  1671. package/upgrades/NEXT.md +35 -0
@@ -0,0 +1,3434 @@
1
+ /**
2
+ * Telegram Messaging Adapter — send/receive messages via Telegram Bot API.
3
+ *
4
+ * Uses long polling to receive messages. Supports forum topics
5
+ * (each user gets a topic thread). Includes topic-session registry,
6
+ * message logging, voice transcription, photo handling, stall detection,
7
+ * auth gating, and delivery confirmation.
8
+ *
9
+ * No external dependencies — uses native fetch for Telegram API calls.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { DegradationReporter } from '../monitoring/DegradationReporter.js';
14
+ import { NotificationBatcher } from './NotificationBatcher.js';
15
+ import { validateTopicContent, getTopicPurpose, classifyContent } from './TopicContentValidator.js';
16
+ import { SHARED_INFRA_FLAGS } from './shared/FeatureFlags.js';
17
+ import { MessageLogger } from './shared/MessageLogger.js';
18
+ import { StallDetector } from './shared/StallDetector.js';
19
+ import { CommandRouter } from './shared/CommandRouter.js';
20
+ import { AuthGate } from './shared/AuthGate.js';
21
+ import { MessagingEventBus } from './shared/MessagingEventBus.js';
22
+ import { CallbackRegistry, isAllowedButtonKey } from '../core/CallbackRegistry.js';
23
+ import { sanitizeForPrompt } from '../monitoring/SessionRecovery.js';
24
+ /**
25
+ * Telegram General topic convention:
26
+ * - Incoming: messages in General have message_thread_id=1 (or undefined in older API)
27
+ * - Internal: we use GENERAL_TOPIC_ID (1) as the sentinel
28
+ * - Outgoing: we OMIT message_thread_id for General (don't send 1, don't send 0)
29
+ *
30
+ * The isGeneralTopic() helper should be used instead of raw `topicId === 1` checks
31
+ * to keep the convention in one place.
32
+ */
33
+ const GENERAL_TOPIC_ID = 1;
34
+ function isGeneralTopic(topicId) {
35
+ return topicId <= GENERAL_TOPIC_ID;
36
+ }
37
+ const PRIORITY_EMOJI = {
38
+ URGENT: '\ud83d\udd34', // 🔴
39
+ HIGH: '\ud83d\udfe0', // 🟠
40
+ NORMAL: '\ud83d\udd35', // 🔵
41
+ LOW: '\u26aa', // ⚪
42
+ };
43
+ const PRIORITY_COLOR = {
44
+ URGENT: 16478047, // red
45
+ HIGH: 16749490, // orange
46
+ NORMAL: 7322096, // blue
47
+ LOW: 13338331, // purple
48
+ };
49
+ /**
50
+ * Standard topic styles for visual organization in Telegram forum.
51
+ * Colors are the 6 values Telegram's Bot API accepts for icon_color.
52
+ * Emojis prefix topic names for at-a-glance scanning.
53
+ */
54
+ export const TOPIC_STYLE = {
55
+ /** Green — core infrastructure (Lifeline) */
56
+ SYSTEM: { color: 9367192, emoji: '🛡️' },
57
+ /** Purple — automated recurring jobs */
58
+ JOB: { color: 13338331, emoji: '⚙️' },
59
+ /** Green — interactive user sessions */
60
+ SESSION: { color: 9367192, emoji: '💬' },
61
+ /** Blue — informational (Dashboard, Updates) */
62
+ INFO: { color: 7322096, emoji: '📢' },
63
+ /** Yellow — needs user attention */
64
+ ALERT: { color: 16766590, emoji: '🔔' },
65
+ };
66
+ /**
67
+ * Keyword → emoji mapping for smart topic emoji selection.
68
+ * First match wins, so more specific patterns come first.
69
+ * Falls back to 💬 for unmatched names.
70
+ */
71
+ const TOPIC_EMOJI_KEYWORDS = [
72
+ { keywords: ['debug', 'bug', 'fix', 'issue', 'error'], emoji: '🐛' },
73
+ { keywords: ['deploy', 'release', 'ship', 'launch'], emoji: '🚀' },
74
+ { keywords: ['test', 'testing', 'qa', 'cypress', 'jest'], emoji: '🧪' },
75
+ { keywords: ['review', 'pr', 'code review'], emoji: '👀' },
76
+ { keywords: ['research', 'explore', 'investigate'], emoji: '🔍' },
77
+ { keywords: ['design', 'ui', 'ux', 'frontend', 'css'], emoji: '🎨' },
78
+ { keywords: ['doc', 'docs', 'readme', 'write', 'draft'], emoji: '📝' },
79
+ { keywords: ['build', 'ci', 'pipeline', 'compile'], emoji: '🏗️' },
80
+ { keywords: ['security', 'auth', 'permission', 'access'], emoji: '🔒' },
81
+ { keywords: ['perf', 'performance', 'speed', 'optimize'], emoji: '⚡' },
82
+ { keywords: ['data', 'database', 'db', 'sql', 'prisma'], emoji: '🗄️' },
83
+ { keywords: ['api', 'endpoint', 'route', 'server'], emoji: '🔌' },
84
+ { keywords: ['monitor', 'metric', 'observ', 'dashboard'], emoji: '📊' },
85
+ { keywords: ['alert', 'incident', 'urgent', 'critical'], emoji: '🚨' },
86
+ { keywords: ['brainstorm', 'idea', 'think', 'plan'], emoji: '💡' },
87
+ { keywords: ['migrate', 'migration', 'upgrade'], emoji: '🔄' },
88
+ { keywords: ['config', 'setting', 'env'], emoji: '⚙️' },
89
+ { keywords: ['email', 'mail', 'newsletter', 'outreach'], emoji: '📧' },
90
+ { keywords: ['chat', 'talk', 'conversation', 'discuss'], emoji: '💬' },
91
+ { keywords: ['learn', 'study', 'tutorial', 'course'], emoji: '📚' },
92
+ { keywords: ['money', 'payment', 'billing', 'cost'], emoji: '💰' },
93
+ { keywords: ['clean', 'cleanup', 'refactor', 'tidy'], emoji: '🧹' },
94
+ ];
95
+ /**
96
+ * Select an appropriate emoji for a topic based on its name.
97
+ * Matches keywords case-insensitively. Falls back to 💬 for unmatched names.
98
+ */
99
+ export function selectTopicEmoji(topicName) {
100
+ const lower = topicName.toLowerCase();
101
+ for (const entry of TOPIC_EMOJI_KEYWORDS) {
102
+ if (entry.keywords.some(kw => lower.includes(kw))) {
103
+ return entry.emoji;
104
+ }
105
+ }
106
+ return TOPIC_STYLE.SESSION.emoji; // 💬 default
107
+ }
108
+ export class TelegramAdapter {
109
+ platform = 'telegram';
110
+ config;
111
+ handler = null;
112
+ polling = false;
113
+ /** True when this adapter is actively polling for messages (false in send-only mode). */
114
+ get isPolling() { return this.polling; }
115
+ pollTimeout = null;
116
+ lastUpdateId = 0;
117
+ startedAt = null;
118
+ consecutivePollErrors = 0;
119
+ // Forum detection — if the chat is not a forum, skip all topic operations
120
+ notAForum = false;
121
+ notAForumWarned = false;
122
+ // Topic-session registry (persisted to disk)
123
+ topicToSession = new Map();
124
+ sessionToTopic = new Map();
125
+ topicToName = new Map();
126
+ topicToPurpose = new Map();
127
+ registryPath;
128
+ messageLogPath;
129
+ offsetPath;
130
+ stateDir;
131
+ // Attention queue (persisted to disk)
132
+ attentionItemToTopic = new Map();
133
+ attentionTopicToItem = new Map();
134
+ attentionItems = new Map();
135
+ attentionFilePath;
136
+ // Stall detection
137
+ pendingMessages = new Map(); // key = topicId-timestamp
138
+ stallCheckInterval = null;
139
+ // Promise tracking (agent said "give me a minute" but hasn't followed up)
140
+ pendingPromises = new Map(); // key = topicId
141
+ // Topic message callback — fires on every incoming topic message
142
+ onTopicMessage = null;
143
+ // Session management callbacks (wired by server.ts)
144
+ onInterruptSession = null;
145
+ onRestartSession = null;
146
+ onListSessions = null;
147
+ onIsSessionAlive = null;
148
+ onIsSessionActive = null;
149
+ // Message log callback — fires on every message logged (inbound and outbound).
150
+ // Used by TopicMemory to dual-write to SQLite for search and summarization.
151
+ // Includes sender identity fields (Phase 1C/1D — User-Agent Topology Spec).
152
+ onMessageLogged = null;
153
+ // Sentinel interceptor — fires BEFORE the message handler for real-time interrupt detection.
154
+ // Returns the sentinel classification. If category is 'emergency-stop' or 'pause',
155
+ // the adapter will handle the session action and skip the normal handler.
156
+ onSentinelIntercept = null;
157
+ // Session kill/pause callbacks — used by sentinel to take immediate action
158
+ onSentinelKillSession = null;
159
+ onSentinelPauseSession = null;
160
+ // Attention queue callbacks
161
+ onAttentionStatusChange = null;
162
+ // Quota management callbacks
163
+ onSwitchAccountRequest = null;
164
+ onQuotaStatusRequest = null;
165
+ onLoginRequest = null;
166
+ onClassifySessionDeath = null;
167
+ /** LLM-powered stall triage — called instead of generic stall alert when set */
168
+ onStallDetected = null;
169
+ /** Get triage status for a topic — returns null if no active triage, or status summary */
170
+ onGetTriageStatus = null;
171
+ // Unknown user handling callbacks (Multi-User Setup Wizard Phase 4.5)
172
+ // Returns the registration policy and optional contact hint for the gated message
173
+ onGetRegistrationPolicy = null;
174
+ // Called when an admin-only join request is created (notify admin via lifeline/admin topic)
175
+ onNotifyAdminJoinRequest = null;
176
+ // Called to validate an invite code for invite-only policy
177
+ onValidateInviteCode = null;
178
+ // Called to start mini-onboarding for open policy
179
+ onStartMiniOnboarding = null;
180
+ // Rate limiting for unknown user responses (prevent spam)
181
+ unknownUserRateLimit = new Map(); // telegramUserId -> last response timestamp
182
+ static UNKNOWN_USER_COOLDOWN_MS = 60_000; // 1 minute between responses to same unknown user
183
+ // Notification batching
184
+ batcher = null;
185
+ // Intelligence provider — gates fallback stall/promise alerts behind LLM confirmation.
186
+ // Without this, fallback alerts fire purely from timers when StallTriageNurse is unavailable.
187
+ intelligence = null;
188
+ // Flush notifications callback — fires when user sends /flush
189
+ onFlushNotifications = null;
190
+ // Prompt Gate — relay prompts to Telegram and handle responses
191
+ callbackRegistry;
192
+ pendingPromptReply = new Map(); // topicId → pending
193
+ promptGateDisclosureSent = new Set(); // topicIds that have seen the disclosure
194
+ /** Callback to inject a response into a tmux session. Wired by server.ts. */
195
+ onPromptResponse = null;
196
+ /** Callback to inject text input into a tmux session. Wired by server.ts. */
197
+ onPromptTextResponse = null;
198
+ /** Callback when relay lease should extend idle timeout for a session */
199
+ onRelayLeaseStart = null;
200
+ /** Callback when relay lease is released (response received or timeout) */
201
+ onRelayLeaseEnd = null;
202
+ // Shared infrastructure modules (Phase 1 extraction)
203
+ sharedLogger = null;
204
+ sharedRegistry = null;
205
+ sharedStallDetector = null;
206
+ sharedCommandRouter = null;
207
+ sharedAuthGate = null;
208
+ eventBus = null;
209
+ /** Get the event bus for external subscribers (Phase 1e). Returns null if flag is off. */
210
+ getEventBus() {
211
+ return this.eventBus;
212
+ }
213
+ constructor(config, stateDir) {
214
+ if (config.chatId && !/^-?\d+$/.test(String(config.chatId))) {
215
+ throw new Error(`Invalid Telegram chatId "${config.chatId}". Chat IDs must be numeric (e.g., -1001234567890). ` +
216
+ `Update messaging.config.chatId in your instar.config.json with a valid numeric ID.`);
217
+ }
218
+ this.config = config;
219
+ this.stateDir = stateDir;
220
+ this.registryPath = path.join(stateDir, 'topic-session-registry.json');
221
+ this.messageLogPath = path.join(stateDir, 'telegram-messages.jsonl');
222
+ this.offsetPath = path.join(stateDir, 'telegram-poll-offset.json');
223
+ this.attentionFilePath = path.join(stateDir, 'state', 'attention-items.json');
224
+ this.loadRegistry();
225
+ this.loadOffset();
226
+ this.loadAttentionItems();
227
+ // Initialize Prompt Gate callback registry
228
+ const relayTimeoutMs = (config.promptGate?.relayTimeoutSeconds ?? 300) * 1000;
229
+ this.callbackRegistry = new CallbackRegistry({
230
+ maxEntries: 500,
231
+ maxAgeMs: relayTimeoutMs,
232
+ pruneIntervalMs: 60_000,
233
+ });
234
+ this.callbackRegistry.start();
235
+ // Initialize shared modules when feature flags are enabled
236
+ if (SHARED_INFRA_FLAGS.useSharedMessageLogger) {
237
+ this.sharedLogger = new MessageLogger({ logPath: this.messageLogPath });
238
+ }
239
+ if (SHARED_INFRA_FLAGS.useSharedStallDetector) {
240
+ this.sharedStallDetector = new StallDetector({
241
+ stallTimeoutMinutes: config.stallTimeoutMinutes,
242
+ promiseTimeoutMinutes: config.promiseTimeoutMinutes,
243
+ });
244
+ }
245
+ if (SHARED_INFRA_FLAGS.useSharedCommandRouter) {
246
+ this.sharedCommandRouter = new CommandRouter('telegram');
247
+ this.registerSharedCommands();
248
+ }
249
+ if (SHARED_INFRA_FLAGS.useSharedAuthGate) {
250
+ this.sharedAuthGate = new AuthGate({
251
+ authorizedUsers: (config.authorizedUserIds ?? []).map(id => id.toString()),
252
+ });
253
+ }
254
+ if (SHARED_INFRA_FLAGS.useEventEmitterPattern) {
255
+ this.eventBus = new MessagingEventBus('telegram');
256
+ }
257
+ }
258
+ /**
259
+ * Register all Telegram commands with the shared CommandRouter (Phase 1a).
260
+ * Each command delegates back to the existing handler logic.
261
+ */
262
+ registerSharedCommands() {
263
+ if (!this.sharedCommandRouter)
264
+ return;
265
+ // Attention topic interceptor
266
+ this.sharedCommandRouter.addInterceptor(async (ctx) => {
267
+ const topicId = parseInt(ctx.channelId, 10);
268
+ if (this.isAttentionTopic(topicId)) {
269
+ return this.handleAttentionCommand(topicId, ctx.rawText);
270
+ }
271
+ return false;
272
+ });
273
+ this.sharedCommandRouter.register('flush', async (ctx) => {
274
+ const topicId = parseInt(ctx.channelId, 10);
275
+ if (this.batcher && this.batcher.isEnabled()) {
276
+ const flushed = await this.batcher.flushAll();
277
+ if (flushed > 0) {
278
+ await this.sendToTopic(topicId, `Flushed ${flushed} batched notification${flushed === 1 ? '' : 's'}.`).catch(() => { });
279
+ }
280
+ else {
281
+ await this.sendToTopic(topicId, 'No batched notifications to flush.').catch(() => { });
282
+ }
283
+ }
284
+ else if (this.onFlushNotifications) {
285
+ this.onFlushNotifications(topicId).catch(err => {
286
+ console.error('[telegram] Flush notifications failed:', err);
287
+ this.sendToTopic(topicId, 'Failed to flush notifications.').catch(() => { });
288
+ });
289
+ }
290
+ else {
291
+ await this.sendToTopic(topicId, 'Notification batching is not enabled.').catch(() => { });
292
+ }
293
+ return true;
294
+ }, { description: 'Flush batched notifications' });
295
+ this.sharedCommandRouter.register('sessions', async (ctx) => {
296
+ const topicId = parseInt(ctx.channelId, 10);
297
+ const filterUnclaimed = ctx.args.includes('unclaimed');
298
+ if (!this.onListSessions) {
299
+ await this.sendToTopic(topicId, 'Session listing not available.').catch(() => { });
300
+ return true;
301
+ }
302
+ const sessions = this.onListSessions();
303
+ if (sessions.length === 0) {
304
+ await this.sendToTopic(topicId, 'No sessions running.').catch(() => { });
305
+ return true;
306
+ }
307
+ const lines = [];
308
+ for (const s of sessions) {
309
+ const linkedTopic = this.getTopicForSession(s.tmuxSession);
310
+ const claimed = linkedTopic !== null;
311
+ if (filterUnclaimed && claimed)
312
+ continue;
313
+ const status = s.alive ? '\u2705' : '\u274c';
314
+ const claimTag = claimed ? ` (topic ${linkedTopic})` : ' \u{1f7e1} unclaimed';
315
+ lines.push(`${status} ${s.name}${claimTag}`);
316
+ }
317
+ if (lines.length === 0) {
318
+ await this.sendToTopic(topicId, filterUnclaimed ? 'No unclaimed sessions.' : 'No sessions.').catch(() => { });
319
+ }
320
+ else {
321
+ await this.sendToTopic(topicId, lines.join('\n')).catch(() => { });
322
+ }
323
+ return true;
324
+ }, { description: 'List sessions', platforms: ['telegram'] });
325
+ this.sharedCommandRouter.register(['claim', 'link'], async (ctx) => {
326
+ const topicId = parseInt(ctx.channelId, 10);
327
+ const sessionName = ctx.args;
328
+ if (!sessionName) {
329
+ await this.sendToTopic(topicId, `Please include a session name — e.g. /${ctx.command} my-session`).catch(() => { });
330
+ return true;
331
+ }
332
+ const existingSession = this.getSessionForTopic(topicId);
333
+ if (existingSession) {
334
+ await this.sendToTopic(topicId, `This topic is already linked to "${existingSession}". Use /unlink first.`).catch(() => { });
335
+ return true;
336
+ }
337
+ this.registerTopicSession(topicId, sessionName);
338
+ const verb = ctx.command === 'claim' ? 'Claimed' : 'Linked';
339
+ await this.sendToTopic(topicId, `${verb} session "${sessionName}" ${ctx.command === 'claim' ? 'into' : 'to'} this topic.`).catch(() => { });
340
+ return true;
341
+ }, { description: 'Link a session to this topic', platforms: ['telegram'] });
342
+ this.sharedCommandRouter.register('unlink', async (ctx) => {
343
+ const topicId = parseInt(ctx.channelId, 10);
344
+ const sessionName = this.getSessionForTopic(topicId);
345
+ if (!sessionName) {
346
+ await this.sendToTopic(topicId, 'No session linked to this topic.').catch(() => { });
347
+ return true;
348
+ }
349
+ this.unregisterTopic(topicId);
350
+ await this.sendToTopic(topicId, `Unlinked session "${sessionName}" from this topic.`).catch(() => { });
351
+ return true;
352
+ }, { description: 'Unlink session from topic', platforms: ['telegram'] });
353
+ this.sharedCommandRouter.register('interrupt', async (ctx) => {
354
+ const topicId = parseInt(ctx.channelId, 10);
355
+ const sessionName = this.getSessionForTopic(topicId);
356
+ if (!sessionName) {
357
+ await this.sendToTopic(topicId, 'No session linked to this topic.').catch(() => { });
358
+ return true;
359
+ }
360
+ if (!this.onInterruptSession) {
361
+ await this.sendToTopic(topicId, 'Interrupt not available (no handler registered).').catch(() => { });
362
+ return true;
363
+ }
364
+ try {
365
+ const success = await this.onInterruptSession(sessionName);
366
+ this.clearStallForTopic(topicId);
367
+ if (success) {
368
+ await this.sendToTopic(topicId, `Nudged "${sessionName}" \u2014 it should resume shortly.`).catch(() => { });
369
+ }
370
+ else {
371
+ await this.sendToTopic(topicId, `Failed to interrupt "${sessionName}" \u2014 session may not exist.`).catch(() => { });
372
+ }
373
+ }
374
+ catch (err) {
375
+ console.error(`[telegram] Interrupt failed:`, err);
376
+ await this.sendToTopic(topicId, 'Couldn\'t interrupt the session. It may have already ended.').catch(() => { });
377
+ }
378
+ return true;
379
+ }, { description: 'Send Escape to unstick a stalled session' });
380
+ this.sharedCommandRouter.register('restart', async (ctx) => {
381
+ const topicId = parseInt(ctx.channelId, 10);
382
+ const sessionName = this.getSessionForTopic(topicId);
383
+ if (!sessionName) {
384
+ await this.sendToTopic(topicId, 'No session linked to this topic.').catch(() => { });
385
+ return true;
386
+ }
387
+ if (!this.onRestartSession) {
388
+ await this.sendToTopic(topicId, 'Restart not available (no handler registered).').catch(() => { });
389
+ return true;
390
+ }
391
+ this.clearStallForTopic(topicId);
392
+ await this.sendToTopic(topicId, `Restarting "${sessionName}"...`).catch(() => { });
393
+ try {
394
+ await this.onRestartSession(sessionName, topicId);
395
+ await this.sendToTopic(topicId, 'Session restarted.').catch(() => { });
396
+ }
397
+ catch (err) {
398
+ console.error(`[telegram] Restart failed:`, err);
399
+ await this.sendToTopic(topicId, 'Restart didn\'t work. The session may need to be recreated — try sending a new message.').catch(() => { });
400
+ }
401
+ return true;
402
+ }, { description: 'Kill and respawn session' });
403
+ this.sharedCommandRouter.register('status', async (ctx) => {
404
+ const topicId = parseInt(ctx.channelId, 10);
405
+ const s = this.getStatus();
406
+ const lines = [
407
+ `Telegram adapter: ${s.started ? '\u2705 running' : '\u274c stopped'}`,
408
+ `Uptime: ${s.uptime ? Math.round(s.uptime / 60000) + 'm' : 'n/a'}`,
409
+ `Topic mappings: ${s.topicMappings}`,
410
+ `Pending stall alerts: ${s.pendingStalls}`,
411
+ ];
412
+ await this.sendToTopic(topicId, lines.join('\n')).catch(() => { });
413
+ return true;
414
+ }, { description: 'Show adapter status' });
415
+ this.sharedCommandRouter.register(['switch-account', 'sa'], async (ctx) => {
416
+ const topicId = parseInt(ctx.channelId, 10);
417
+ if (!ctx.args)
418
+ return false;
419
+ if (this.onSwitchAccountRequest) {
420
+ this.onSwitchAccountRequest(ctx.args, topicId).catch(err => {
421
+ console.error('[telegram] Switch account failed:', err);
422
+ this.sendToTopic(topicId, 'Account switch didn\'t work. Try again or use /quota to check status.').catch(() => { });
423
+ });
424
+ }
425
+ else {
426
+ await this.sendToTopic(topicId, 'Account switching not available.').catch(() => { });
427
+ }
428
+ return true;
429
+ }, { description: 'Switch active Claude account' });
430
+ this.sharedCommandRouter.register(['quota', 'q'], async (ctx) => {
431
+ const topicId = parseInt(ctx.channelId, 10);
432
+ if (this.onQuotaStatusRequest) {
433
+ this.onQuotaStatusRequest(topicId).catch(err => {
434
+ console.error('[telegram] Quota status failed:', err);
435
+ this.sendToTopic(topicId, 'Couldn\'t check quota right now. Try again in a moment.').catch(() => { });
436
+ });
437
+ }
438
+ else {
439
+ await this.sendToTopic(topicId, 'Quota status not available.').catch(() => { });
440
+ }
441
+ return true;
442
+ }, { description: 'Show multi-account quota summary' });
443
+ this.sharedCommandRouter.register('login', async (ctx) => {
444
+ const topicId = parseInt(ctx.channelId, 10);
445
+ const email = ctx.args || null;
446
+ if (this.onLoginRequest) {
447
+ this.onLoginRequest(email, topicId).catch(err => {
448
+ console.error('[telegram] Login flow failed:', err);
449
+ this.sendToTopic(topicId, 'Login didn\'t complete. Try again, or the auth service may be temporarily unavailable.').catch(() => { });
450
+ });
451
+ }
452
+ else {
453
+ await this.sendToTopic(topicId, 'Login not available.').catch(() => { });
454
+ }
455
+ return true;
456
+ }, { description: 'Seamless OAuth login from Telegram' });
457
+ }
458
+ async start() {
459
+ if (this.polling)
460
+ return;
461
+ this.polling = true;
462
+ this.startedAt = new Date();
463
+ this.consecutivePollErrors = 0;
464
+ // Ensure Lifeline topic exists (auto-recreate if deleted)
465
+ await this.ensureLifelineTopic();
466
+ console.log(`[telegram] Starting long-polling...`);
467
+ this.poll();
468
+ // Start notification batcher if configured
469
+ if (this.batcher) {
470
+ this.batcher.start();
471
+ console.log('[telegram] Notification batcher started');
472
+ }
473
+ // Start stall detection if configured
474
+ if (this.sharedStallDetector) {
475
+ // Phase 1c: Wire shared stall detector callbacks and start
476
+ this.sharedStallDetector.setIsSessionAlive(this.onIsSessionAlive ? (name) => this.onIsSessionAlive(name) : null);
477
+ this.sharedStallDetector.setIsSessionActive(this.onIsSessionActive ? (name) => this.onIsSessionActive(name) : null);
478
+ this.sharedStallDetector.setOnStall(async (event, alive) => {
479
+ await this.handleSharedStallEvent(event, alive);
480
+ });
481
+ this.sharedStallDetector.start();
482
+ }
483
+ else {
484
+ const stallMinutes = this.config.stallTimeoutMinutes ?? 5;
485
+ if (stallMinutes > 0) {
486
+ this.stallCheckInterval = setInterval(() => this.checkForStalls(), 30_000);
487
+ }
488
+ }
489
+ }
490
+ async stop() {
491
+ this.polling = false;
492
+ if (this.pollTimeout) {
493
+ clearTimeout(this.pollTimeout);
494
+ this.pollTimeout = null;
495
+ }
496
+ if (this.sharedStallDetector) {
497
+ this.sharedStallDetector.stop();
498
+ }
499
+ if (this.stallCheckInterval) {
500
+ clearInterval(this.stallCheckInterval);
501
+ this.stallCheckInterval = null;
502
+ }
503
+ // Flush and stop the batcher on shutdown
504
+ if (this.batcher) {
505
+ try {
506
+ await this.batcher.flushAll();
507
+ }
508
+ catch (err) {
509
+ console.error('[telegram] Failed to flush batcher on stop:', err);
510
+ }
511
+ this.batcher.stop();
512
+ }
513
+ }
514
+ async send(message) {
515
+ const topicId = message.channel?.identifier;
516
+ const params = {
517
+ chat_id: this.config.chatId,
518
+ text: message.content,
519
+ parse_mode: 'Markdown',
520
+ };
521
+ if (topicId && !isGeneralTopic(parseInt(topicId, 10))) {
522
+ params.message_thread_id = parseInt(topicId, 10);
523
+ }
524
+ try {
525
+ const result = await this.apiCall('sendMessage', params);
526
+ return { messageId: result.message_id, topicId: topicId ? parseInt(topicId, 10) : undefined };
527
+ }
528
+ catch (err) {
529
+ // Only retry without parse_mode on 400 errors (likely Markdown parse failures)
530
+ const errMsg = err instanceof Error ? err.message : String(err);
531
+ if (errMsg.includes('(400)') && params.parse_mode) {
532
+ delete params.parse_mode;
533
+ const result = await this.apiCall('sendMessage', params);
534
+ return { messageId: result.message_id, topicId: topicId ? parseInt(topicId, 10) : undefined };
535
+ }
536
+ throw err;
537
+ }
538
+ }
539
+ /**
540
+ * Log an inbound user message that arrived via an external path (e.g. Lifeline
541
+ * forwarding through /internal/telegram-forward). This ensures the message
542
+ * appears in both JSONL and TopicMemory even when the normal polling handler
543
+ * didn't receive it.
544
+ */
545
+ logInboundMessage(entry) {
546
+ this.appendToLog({
547
+ messageId: entry.messageId,
548
+ topicId: entry.topicId,
549
+ text: entry.text,
550
+ fromUser: true,
551
+ timestamp: entry.timestamp,
552
+ sessionName: this.topicToSession.get(entry.topicId) ?? null,
553
+ senderName: entry.senderName,
554
+ senderUsername: entry.senderUsername,
555
+ telegramUserId: entry.telegramUserId,
556
+ });
557
+ }
558
+ /**
559
+ * Send a message to a specific forum topic.
560
+ * Returns the Telegram message ID for delivery confirmation.
561
+ */
562
+ async sendToTopic(topicId, text, options) {
563
+ const params = {
564
+ chat_id: this.config.chatId,
565
+ text,
566
+ };
567
+ if (!isGeneralTopic(topicId)) {
568
+ params.message_thread_id = topicId;
569
+ }
570
+ if (options?.silent) {
571
+ params.disable_notification = true;
572
+ }
573
+ let result;
574
+ try {
575
+ result = await this.apiCall('sendMessage', { ...params, parse_mode: 'Markdown' });
576
+ }
577
+ catch {
578
+ result = await this.apiCall('sendMessage', params);
579
+ }
580
+ // Log outbound messages too
581
+ this.appendToLog({
582
+ messageId: result.message_id,
583
+ topicId,
584
+ text,
585
+ fromUser: false,
586
+ timestamp: new Date().toISOString(),
587
+ sessionName: this.topicToSession.get(topicId) ?? null,
588
+ });
589
+ // Clear stall tracking for this topic (agent responded)
590
+ // Skip for proxy messages — PresenceProxy messages should NOT reset stall timers
591
+ if (!options?.skipStallClear) {
592
+ this.clearStallForTopic(topicId);
593
+ }
594
+ // Promise tracking — detect agent "working on it" messages that need follow-through
595
+ const sessionName = this.topicToSession.get(topicId);
596
+ if (sessionName) {
597
+ // Phase 1c: Delegate to shared StallDetector when flag is enabled
598
+ if (this.sharedStallDetector) {
599
+ this.sharedStallDetector.trackOutboundMessage(topicId.toString(), sessionName, text);
600
+ }
601
+ else {
602
+ if (this.isPromiseMessage(text)) {
603
+ this.pendingPromises.set(topicId, {
604
+ topicId,
605
+ sessionName,
606
+ promiseText: text.slice(0, 100),
607
+ promisedAt: Date.now(),
608
+ alerted: false,
609
+ });
610
+ }
611
+ else if (this.pendingPromises.has(topicId) && this.isFollowThroughMessage(text)) {
612
+ this.pendingPromises.delete(topicId);
613
+ }
614
+ }
615
+ }
616
+ return { messageId: result.message_id, topicId };
617
+ }
618
+ /**
619
+ * Send a notification through the batcher, falling back to direct send.
620
+ * Use this for internal system notifications that should be batched.
621
+ */
622
+ async notifyTopic(topicId, text, tier, category) {
623
+ if (this.batcher && this.batcher.isEnabled()) {
624
+ await this.batcher.enqueue({
625
+ tier,
626
+ category,
627
+ message: text,
628
+ timestamp: new Date(),
629
+ topicId,
630
+ });
631
+ }
632
+ else {
633
+ // No batcher or disabled — send directly
634
+ await this.sendToTopic(topicId, text);
635
+ }
636
+ }
637
+ /**
638
+ * Configure the notification batcher. Call before start() to enable batching.
639
+ * The batcher's send function is wired to sendToTopic automatically.
640
+ */
641
+ configureBatcher(config) {
642
+ this.batcher = new NotificationBatcher({
643
+ enabled: true,
644
+ ...config,
645
+ });
646
+ this.batcher.setSendFunction((topicId, text) => this.sendToTopic(topicId, text));
647
+ return this.batcher;
648
+ }
649
+ /**
650
+ * Get the notification batcher (if configured).
651
+ */
652
+ getBatcher() {
653
+ return this.batcher;
654
+ }
655
+ /**
656
+ * Create a forum topic in the supergroup.
657
+ */
658
+ async createForumTopic(name, iconColor) {
659
+ if (this.notAForum) {
660
+ throw new Error('Chat is not a forum — topic creation skipped');
661
+ }
662
+ const params = {
663
+ chat_id: this.config.chatId,
664
+ name,
665
+ };
666
+ if (iconColor !== undefined) {
667
+ params.icon_color = iconColor;
668
+ }
669
+ try {
670
+ const result = await this.apiCall('createForumTopic', params);
671
+ this.topicToName.set(result.message_thread_id, name);
672
+ this.saveRegistry();
673
+ console.log(`[telegram] Created forum topic: "${name}" (ID: ${result.message_thread_id})`);
674
+ return { topicId: result.message_thread_id, name: result.name };
675
+ }
676
+ catch (err) {
677
+ const errStr = String(err);
678
+ if (errStr.includes('not a forum') || errStr.includes('FORUM_REQUIRED')) {
679
+ this.notAForum = true;
680
+ if (!this.notAForumWarned) {
681
+ this.notAForumWarned = true;
682
+ console.warn('[telegram] ⚠️ Chat is not a forum-enabled supergroup. Forum topics (Lifeline, Dashboard, per-session) will not be created. Messaging will use the General Topic. To enable topics, convert your Telegram group to a supergroup with Topics enabled in group settings.');
683
+ }
684
+ }
685
+ throw err;
686
+ }
687
+ }
688
+ /**
689
+ * Edit a forum topic's name and/or icon color.
690
+ * Best-effort — silently ignores failures (topic may not exist).
691
+ */
692
+ async editForumTopic(topicId, name, iconColor) {
693
+ const params = {
694
+ chat_id: this.config.chatId,
695
+ message_thread_id: topicId,
696
+ };
697
+ if (name !== undefined)
698
+ params.name = name;
699
+ if (iconColor !== undefined)
700
+ params.icon_color = iconColor;
701
+ try {
702
+ await this.apiCall('editForumTopic', params);
703
+ if (name) {
704
+ this.topicToName.set(topicId, name);
705
+ this.saveRegistry();
706
+ }
707
+ console.log(`[telegram] Renamed topic ${topicId} → "${name}"`);
708
+ return true;
709
+ }
710
+ catch {
711
+ // @silent-fallback-ok — best-effort rename
712
+ return false;
713
+ }
714
+ }
715
+ /**
716
+ * Find an existing topic by name, or create a new one if none exists.
717
+ * Prevents duplicate topics when sessions respawn or the server restarts.
718
+ */
719
+ async findOrCreateForumTopic(name, iconColor) {
720
+ const normalizedName = name.toLowerCase().trim();
721
+ for (const [topicId, existingName] of this.topicToName) {
722
+ if (existingName.toLowerCase().trim() === normalizedName) {
723
+ console.log(`[telegram] Reusing existing topic ${topicId} for "${name}"`);
724
+ return { topicId, name: existingName, reused: true };
725
+ }
726
+ }
727
+ const result = await this.createForumTopic(name, iconColor);
728
+ return { ...result, reused: false };
729
+ }
730
+ /**
731
+ * Get the Lifeline topic ID (if configured).
732
+ */
733
+ getLifelineTopicId() {
734
+ return this.config.lifelineTopicId;
735
+ }
736
+ /**
737
+ * Ensure the Lifeline topic exists. If it was deleted, recreate it.
738
+ * Called on startup and can be called periodically.
739
+ */
740
+ async ensureLifelineTopic() {
741
+ if (this.notAForum)
742
+ return null;
743
+ const styledName = `${TOPIC_STYLE.SYSTEM.emoji} Lifeline`;
744
+ if (!this.config.lifelineTopicId) {
745
+ // No lifeline topic configured — create one
746
+ try {
747
+ const topic = await this.createForumTopic(styledName, TOPIC_STYLE.SYSTEM.color);
748
+ this.config.lifelineTopicId = topic.topicId;
749
+ this.persistLifelineTopicId(topic.topicId);
750
+ console.log(`[telegram] Created Lifeline topic: ${topic.topicId}`);
751
+ return topic.topicId;
752
+ }
753
+ catch (err) {
754
+ // @silent-fallback-ok — lifeline topic creation, logged
755
+ console.error(`[telegram] Failed to create Lifeline topic: ${err}`);
756
+ return null;
757
+ }
758
+ }
759
+ // Lifeline topic ID exists — verify it's still valid silently.
760
+ // Don't send a visible message — it spams the user on every server restart.
761
+ try {
762
+ await this.apiCall('sendChatAction', {
763
+ chat_id: this.config.chatId,
764
+ message_thread_id: this.config.lifelineTopicId,
765
+ action: 'typing',
766
+ });
767
+ // Best-effort rename to styled name if it doesn't match
768
+ const currentName = this.topicToName.get(this.config.lifelineTopicId);
769
+ if (currentName && !currentName.includes(TOPIC_STYLE.SYSTEM.emoji)) {
770
+ await this.editForumTopic(this.config.lifelineTopicId, styledName, TOPIC_STYLE.SYSTEM.color);
771
+ }
772
+ console.log(`[telegram] Lifeline topic verified: ${this.config.lifelineTopicId}`);
773
+ return this.config.lifelineTopicId;
774
+ }
775
+ catch (err) {
776
+ const errStr = String(err);
777
+ // Topic was deleted — "message thread not found" or "TOPIC_CLOSED" or similar
778
+ if (errStr.includes('thread not found') || errStr.includes('TOPIC_DELETED') ||
779
+ errStr.includes('TOPIC_CLOSED') || errStr.includes('not found')) {
780
+ console.log(`[telegram] Lifeline topic ${this.config.lifelineTopicId} was deleted — recreating`);
781
+ try {
782
+ const topic = await this.createForumTopic(styledName, TOPIC_STYLE.SYSTEM.color);
783
+ this.config.lifelineTopicId = topic.topicId;
784
+ this.persistLifelineTopicId(topic.topicId);
785
+ console.log(`[telegram] Recreated Lifeline topic: ${topic.topicId}`);
786
+ return topic.topicId;
787
+ }
788
+ catch (recreateErr) {
789
+ DegradationReporter.getInstance().report({
790
+ feature: 'Telegram.Lifeline',
791
+ primary: 'Verified lifeline topic for emergency agent communication',
792
+ fallback: 'No lifeline topic — agent unreachable in emergencies',
793
+ reason: `Lifeline topic deleted and recreation failed: ${recreateErr instanceof Error ? recreateErr.message : String(recreateErr)}`,
794
+ impact: 'Agent cannot receive emergency commands or stall recovery signals.',
795
+ });
796
+ return null;
797
+ }
798
+ }
799
+ // Some other error (network, etc.) — retry once before reporting degradation.
800
+ // Transient errors like "This operation was aborted" (15s fetch timeout) should
801
+ // not trigger degradation on the first attempt — a brief retry on startup resolves them.
802
+ let retryErr = err;
803
+ try {
804
+ await new Promise(resolve => setTimeout(resolve, 3000));
805
+ await this.apiCall('sendChatAction', {
806
+ chat_id: this.config.chatId,
807
+ message_thread_id: this.config.lifelineTopicId,
808
+ action: 'typing',
809
+ });
810
+ console.log(`[telegram] Lifeline topic verified (retry succeeded): ${this.config.lifelineTopicId}`);
811
+ return this.config.lifelineTopicId;
812
+ }
813
+ catch (e) {
814
+ // @silent-fallback-ok — error assigned to retryErr, reported to DegradationReporter below
815
+ retryErr = e;
816
+ }
817
+ DegradationReporter.getInstance().report({
818
+ feature: 'Telegram.Lifeline',
819
+ primary: 'Verified lifeline topic for emergency agent communication',
820
+ fallback: 'Using unverified (possibly stale) lifeline topic ID',
821
+ reason: `Lifeline topic check failed after retry: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
822
+ impact: 'Lifeline may be unreachable — messages to agent could fail silently.',
823
+ });
824
+ return this.config.lifelineTopicId;
825
+ }
826
+ }
827
+ /**
828
+ * Persist the Lifeline topic ID back to config.json so it survives restarts.
829
+ */
830
+ persistLifelineTopicId(topicId) {
831
+ try {
832
+ // Find config.json in state dir's parent (stateDir is .instar/state or .instar)
833
+ const candidates = [
834
+ path.join(this.stateDir, '..', 'config.json'),
835
+ path.join(this.stateDir, 'config.json'),
836
+ ];
837
+ for (const configPath of candidates) {
838
+ if (fs.existsSync(configPath)) {
839
+ const raw = fs.readFileSync(configPath, 'utf-8');
840
+ const config = JSON.parse(raw);
841
+ // Find the telegram messaging config and update it
842
+ if (Array.isArray(config.messaging)) {
843
+ const telegramEntry = config.messaging.find((m) => m.type === 'telegram');
844
+ if (telegramEntry?.config) {
845
+ telegramEntry.config.lifelineTopicId = topicId;
846
+ const tmpPath = `${configPath}.${process.pid}.tmp`;
847
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
848
+ fs.renameSync(tmpPath, configPath);
849
+ console.log(`[telegram] Saved lifelineTopicId=${topicId} to config`);
850
+ return;
851
+ }
852
+ }
853
+ }
854
+ }
855
+ }
856
+ catch (err) {
857
+ // @silent-fallback-ok — config persistence, in-memory ok
858
+ console.warn(`[telegram] Failed to persist lifelineTopicId: ${err}`);
859
+ }
860
+ }
861
+ // ── Dashboard Topic ──────────────────────────────────────────────────
862
+ /**
863
+ * Get the Dashboard topic ID (if configured).
864
+ */
865
+ getDashboardTopicId() {
866
+ return this.config.dashboardTopicId;
867
+ }
868
+ /**
869
+ * Whether the chat supports forum topics. False if we detected
870
+ * "the chat is not a forum" from the Telegram API.
871
+ */
872
+ get isForumChat() {
873
+ return !this.notAForum;
874
+ }
875
+ /**
876
+ * Ensure the Dashboard topic exists. Creates it on first run, verifies on restart.
877
+ * Same resilience pattern as the lifeline topic.
878
+ */
879
+ async ensureDashboardTopic() {
880
+ if (this.notAForum)
881
+ return null;
882
+ const styledName = `${TOPIC_STYLE.INFO.emoji} Dashboard`;
883
+ if (!this.config.dashboardTopicId) {
884
+ try {
885
+ const topic = await this.createForumTopic(styledName, TOPIC_STYLE.INFO.color);
886
+ this.config.dashboardTopicId = topic.topicId;
887
+ this.persistDashboardTopicId(topic.topicId);
888
+ console.log(`[telegram] Created Dashboard topic: ${topic.topicId}`);
889
+ // Send a one-time setup hint: mute this topic to avoid unread badges.
890
+ // The bot can't mute topics for users (client-side setting), so we guide them.
891
+ try {
892
+ await this.sendToTopic(topic.topicId, [
893
+ '💡 *Tip*: Mute this topic to avoid notification badges.',
894
+ '',
895
+ 'Long-press this topic → Mute → Forever.',
896
+ '',
897
+ '_The latest dashboard link will always be pinned here._',
898
+ ].join('\n'), { silent: true });
899
+ }
900
+ catch {
901
+ // @silent-fallback-ok — guidance message is nice-to-have
902
+ }
903
+ return topic.topicId;
904
+ }
905
+ catch (err) {
906
+ DegradationReporter.getInstance().report({
907
+ feature: 'TelegramAdapter.ensureDashboardTopic',
908
+ primary: 'Create Dashboard forum topic for status messages',
909
+ fallback: 'Dashboard topic unavailable, status messages have no destination',
910
+ reason: `Failed to create Dashboard topic: ${err instanceof Error ? err.message : String(err)}`,
911
+ impact: 'Dashboard status messages and pinned URLs will not be delivered',
912
+ });
913
+ return null;
914
+ }
915
+ }
916
+ // Dashboard topic ID exists — verify it's still valid
917
+ try {
918
+ await this.apiCall('sendChatAction', {
919
+ chat_id: this.config.chatId,
920
+ message_thread_id: this.config.dashboardTopicId,
921
+ action: 'typing',
922
+ });
923
+ // Best-effort rename to styled name
924
+ const currentName = this.topicToName.get(this.config.dashboardTopicId);
925
+ if (currentName && !currentName.includes(TOPIC_STYLE.INFO.emoji)) {
926
+ await this.editForumTopic(this.config.dashboardTopicId, styledName, TOPIC_STYLE.INFO.color);
927
+ }
928
+ return this.config.dashboardTopicId;
929
+ }
930
+ catch (err) {
931
+ // @silent-fallback-ok — self-healing: attempts topic recreation on deletion, returns existing ID for transient errors
932
+ const errStr = String(err);
933
+ if (errStr.includes('thread not found') || errStr.includes('TOPIC_DELETED') ||
934
+ errStr.includes('TOPIC_CLOSED') || errStr.includes('not found')) {
935
+ console.log(`[telegram] Dashboard topic ${this.config.dashboardTopicId} was deleted — recreating`);
936
+ try {
937
+ const topic = await this.createForumTopic(styledName, TOPIC_STYLE.INFO.color);
938
+ this.config.dashboardTopicId = topic.topicId;
939
+ this.persistDashboardTopicId(topic.topicId);
940
+ return topic.topicId;
941
+ }
942
+ catch (recreateErr) {
943
+ DegradationReporter.getInstance().report({
944
+ feature: 'TelegramAdapter.ensureDashboardTopic',
945
+ primary: 'Recreate deleted Dashboard forum topic',
946
+ fallback: 'No dashboard topic available, returning null',
947
+ reason: `Recreation failed: ${recreateErr instanceof Error ? recreateErr.message : String(recreateErr)}`,
948
+ impact: 'Dashboard status messages and pinned URLs will not be delivered until next restart',
949
+ });
950
+ return null;
951
+ }
952
+ }
953
+ return this.config.dashboardTopicId;
954
+ }
955
+ }
956
+ /**
957
+ * Broadcast the dashboard URL to the Dashboard topic.
958
+ *
959
+ * Edit-in-place pattern: instead of posting a new message each restart (which
960
+ * creates unread badges), we edit the existing pinned message. This means the
961
+ * Dashboard topic never shows as "unread" — it's a quiet reference the user
962
+ * checks when they need the link.
963
+ *
964
+ * Fallback: if the pinned message was deleted or doesn't exist yet, we send
965
+ * a new one, pin it, and save its ID for future edits.
966
+ */
967
+ async broadcastDashboardUrl(url, tunnelType) {
968
+ const topicId = this.config.dashboardTopicId;
969
+ if (!topicId)
970
+ return;
971
+ const pin = this.config.dashboardPin || '(check your config)';
972
+ const isNamed = tunnelType === 'named';
973
+ const message = this.formatDashboardMessage(url, pin, isNamed);
974
+ // Try to edit the existing pinned message (no new message = no unread badge)
975
+ const existingMessageId = this.loadDashboardMessageId();
976
+ if (existingMessageId) {
977
+ try {
978
+ await this.apiCall('editMessageText', {
979
+ chat_id: this.config.chatId,
980
+ message_id: existingMessageId,
981
+ text: message,
982
+ parse_mode: 'Markdown',
983
+ });
984
+ console.log(`[telegram] Edited dashboard message ${existingMessageId} in-place`);
985
+ return; // Success — no new message, no unread badge
986
+ }
987
+ catch (err) {
988
+ // Edit failed — message was deleted, or content unchanged. Fall through to send new.
989
+ const errStr = String(err);
990
+ if (errStr.includes('message is not modified')) {
991
+ console.log(`[telegram] Dashboard message unchanged — skipping`);
992
+ return;
993
+ }
994
+ console.log(`[telegram] Dashboard message ${existingMessageId} edit failed, sending new: ${errStr}`);
995
+ }
996
+ }
997
+ // Fallback: send a new message, pin it, and save for future edits
998
+ try {
999
+ const result = await this.sendToTopic(topicId, message, { silent: true });
1000
+ if (result.messageId) {
1001
+ // Unpin old pins, then pin the new message
1002
+ try {
1003
+ await this.apiCall('unpinAllForumTopicMessages', {
1004
+ chat_id: this.config.chatId,
1005
+ message_thread_id: topicId,
1006
+ });
1007
+ }
1008
+ catch {
1009
+ // @silent-fallback-ok — unpinning old messages is best-effort
1010
+ }
1011
+ try {
1012
+ await this.apiCall('pinChatMessage', {
1013
+ chat_id: this.config.chatId,
1014
+ message_id: result.messageId,
1015
+ disable_notification: true,
1016
+ });
1017
+ }
1018
+ catch {
1019
+ // @silent-fallback-ok — pinning is nice-to-have, send succeeded
1020
+ }
1021
+ // Save message ID for future edit-in-place
1022
+ this.saveDashboardMessageId(result.messageId);
1023
+ }
1024
+ }
1025
+ catch (err) {
1026
+ console.error(`[telegram] Failed to broadcast dashboard URL: ${err}`);
1027
+ }
1028
+ }
1029
+ formatDashboardMessage(url, pin, isNamed) {
1030
+ const dashboardUrl = url + '/dashboard';
1031
+ const quickLinks = [
1032
+ `Sessions: ${dashboardUrl}?tab=sessions`,
1033
+ `Files: ${dashboardUrl}?tab=files`,
1034
+ ];
1035
+ if (isNamed) {
1036
+ return [
1037
+ '*Dashboard*',
1038
+ '',
1039
+ `Your permanent dashboard link:`,
1040
+ dashboardUrl,
1041
+ '',
1042
+ `PIN: \`${pin}\``,
1043
+ '',
1044
+ `Quick links:`,
1045
+ ...quickLinks.map(l => ` ${l}`),
1046
+ '',
1047
+ `_This link is permanent — it won't change on restart._`,
1048
+ ].join('\n');
1049
+ }
1050
+ return [
1051
+ '*Dashboard*',
1052
+ '',
1053
+ `Your dashboard is live:`,
1054
+ dashboardUrl,
1055
+ '',
1056
+ `PIN: \`${pin}\``,
1057
+ '',
1058
+ `Quick links:`,
1059
+ ...quickLinks.map(l => ` ${l}`),
1060
+ '',
1061
+ `_This link changes when the server restarts._`,
1062
+ `_For a permanent link, ask me to set up a named tunnel._`,
1063
+ ].join('\n');
1064
+ }
1065
+ loadDashboardMessageId() {
1066
+ try {
1067
+ const statePath = path.join(this.stateDir, 'state', 'dashboard-message.json');
1068
+ if (fs.existsSync(statePath)) {
1069
+ const data = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
1070
+ return data.messageId ?? null;
1071
+ }
1072
+ }
1073
+ catch {
1074
+ // @silent-fallback-ok — missing state file means first run
1075
+ }
1076
+ return null;
1077
+ }
1078
+ saveDashboardMessageId(messageId) {
1079
+ try {
1080
+ const stateSubdir = path.join(this.stateDir, 'state');
1081
+ fs.mkdirSync(stateSubdir, { recursive: true });
1082
+ const statePath = path.join(stateSubdir, 'dashboard-message.json');
1083
+ const tmpPath = `${statePath}.${process.pid}.tmp`;
1084
+ fs.writeFileSync(tmpPath, JSON.stringify({ messageId, savedAt: new Date().toISOString() }));
1085
+ fs.renameSync(tmpPath, statePath);
1086
+ }
1087
+ catch (err) {
1088
+ console.warn(`[telegram] Failed to save dashboard message ID: ${err}`);
1089
+ }
1090
+ }
1091
+ /**
1092
+ * Persist the Dashboard topic ID back to config.json.
1093
+ */
1094
+ persistDashboardTopicId(topicId) {
1095
+ try {
1096
+ const candidates = [
1097
+ path.join(this.stateDir, '..', 'config.json'),
1098
+ path.join(this.stateDir, 'config.json'),
1099
+ ];
1100
+ for (const configPath of candidates) {
1101
+ if (fs.existsSync(configPath)) {
1102
+ const raw = fs.readFileSync(configPath, 'utf-8');
1103
+ const config = JSON.parse(raw);
1104
+ if (Array.isArray(config.messaging)) {
1105
+ const telegramEntry = config.messaging.find((m) => m.type === 'telegram');
1106
+ if (telegramEntry?.config) {
1107
+ telegramEntry.config.dashboardTopicId = topicId;
1108
+ const tmpPath = `${configPath}.${process.pid}.tmp`;
1109
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
1110
+ fs.renameSync(tmpPath, configPath);
1111
+ console.log(`[telegram] Saved dashboardTopicId=${topicId} to config`);
1112
+ return;
1113
+ }
1114
+ }
1115
+ }
1116
+ }
1117
+ }
1118
+ catch (err) {
1119
+ console.warn(`[telegram] Failed to persist dashboardTopicId: ${err}`);
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Close a forum topic.
1124
+ */
1125
+ async closeForumTopic(topicId) {
1126
+ try {
1127
+ await this.apiCall('closeForumTopic', {
1128
+ chat_id: this.config.chatId,
1129
+ message_thread_id: topicId,
1130
+ });
1131
+ return true;
1132
+ }
1133
+ catch {
1134
+ // @silent-fallback-ok — forum close boolean return
1135
+ return false;
1136
+ }
1137
+ }
1138
+ onMessage(handler) {
1139
+ this.handler = handler;
1140
+ }
1141
+ async resolveUser(channelIdentifier) {
1142
+ return null;
1143
+ }
1144
+ // ── Auth Gating ──────────────────────────────────────────
1145
+ /**
1146
+ * Check if a message is from an authorized user.
1147
+ * If no authorizedUserIds configured, all messages are accepted.
1148
+ */
1149
+ isAuthorized(userId) {
1150
+ // Phase 1d: Delegate to shared AuthGate when flag is enabled
1151
+ if (this.sharedAuthGate) {
1152
+ return this.sharedAuthGate.isAuthorized(userId.toString());
1153
+ }
1154
+ const authorized = this.config.authorizedUserIds;
1155
+ if (!authorized || authorized.length === 0)
1156
+ return true;
1157
+ return authorized.includes(userId);
1158
+ }
1159
+ /**
1160
+ * Handle a message from an unknown/unauthorized Telegram user.
1161
+ * Checks the registration policy and responds appropriately:
1162
+ * - admin-only: Gated message + notify admin
1163
+ * - invite-only: Ask for invite code
1164
+ * - open: Start mini-onboarding (rate limited)
1165
+ *
1166
+ * Rate-limited to prevent spam from the same unknown user.
1167
+ */
1168
+ async handleUnknownUser(telegramUserId, firstName, username, messageText) {
1169
+ // Rate limit: don't spam responses to the same unknown user
1170
+ const lastResponse = this.unknownUserRateLimit.get(telegramUserId);
1171
+ if (lastResponse && (Date.now() - lastResponse) < TelegramAdapter.UNKNOWN_USER_COOLDOWN_MS) {
1172
+ console.log(`[telegram] Rate-limited response to unknown user ${telegramUserId} (${username ?? firstName})`);
1173
+ return;
1174
+ }
1175
+ // Get registration policy from callback
1176
+ const policyInfo = this.onGetRegistrationPolicy?.();
1177
+ if (!policyInfo) {
1178
+ // No policy callback wired — fall back to silent ignore (legacy behavior)
1179
+ console.log(`[telegram] Ignoring message from unauthorized user ${telegramUserId} (${username ?? firstName}) — no registration policy configured`);
1180
+ return;
1181
+ }
1182
+ const { policy, contactHint, agentName } = policyInfo;
1183
+ const displayName = agentName || 'This agent';
1184
+ // Mark that we responded to this user
1185
+ this.unknownUserRateLimit.set(telegramUserId, Date.now());
1186
+ // Clean up old rate limit entries periodically (keep map from growing unbounded)
1187
+ if (this.unknownUserRateLimit.size > 100) {
1188
+ const cutoff = Date.now() - TelegramAdapter.UNKNOWN_USER_COOLDOWN_MS * 10;
1189
+ for (const [uid, ts] of this.unknownUserRateLimit) {
1190
+ if (ts < cutoff)
1191
+ this.unknownUserRateLimit.delete(uid);
1192
+ }
1193
+ }
1194
+ console.log(`[telegram] Unknown user ${telegramUserId} (${username ?? firstName}) — policy: ${policy}`);
1195
+ try {
1196
+ switch (policy) {
1197
+ case 'admin-only': {
1198
+ // Send gated message to the user
1199
+ let gatedMessage = `Hi ${firstName}! ${displayName} is not open for public registration. Access is managed by an administrator.`;
1200
+ if (contactHint) {
1201
+ gatedMessage += `\n\n${contactHint}`;
1202
+ }
1203
+ gatedMessage += `\n\nYour request has been noted and forwarded to the admin.`;
1204
+ // Reply in the group's General topic (since unknown users don't have their own topic)
1205
+ await this.sendToTopic(GENERAL_TOPIC_ID, gatedMessage).catch(() => { });
1206
+ // Notify admin via callback
1207
+ if (this.onNotifyAdminJoinRequest) {
1208
+ await this.onNotifyAdminJoinRequest({
1209
+ name: firstName,
1210
+ username,
1211
+ telegramUserId,
1212
+ }).catch(err => {
1213
+ console.error(`[telegram] Failed to notify admin of join request: ${err}`);
1214
+ });
1215
+ }
1216
+ break;
1217
+ }
1218
+ case 'invite-only': {
1219
+ // Check if the message contains an invite code
1220
+ const trimmedText = messageText?.trim();
1221
+ if (trimmedText && this.onValidateInviteCode) {
1222
+ const result = await this.onValidateInviteCode(trimmedText, telegramUserId);
1223
+ if (result.valid) {
1224
+ await this.sendToTopic(GENERAL_TOPIC_ID, `Welcome, ${firstName}! Your invite code has been accepted. Setting up your account...`).catch(() => { });
1225
+ // Trigger mini-onboarding after successful invite validation
1226
+ if (this.onStartMiniOnboarding) {
1227
+ await this.onStartMiniOnboarding(telegramUserId, firstName, username).catch(err => {
1228
+ console.error(`[telegram] Failed to start onboarding after invite: ${err}`);
1229
+ });
1230
+ }
1231
+ return;
1232
+ }
1233
+ else if (result.error) {
1234
+ await this.sendToTopic(GENERAL_TOPIC_ID, result.error).catch(() => { });
1235
+ return;
1236
+ }
1237
+ }
1238
+ // Default invite-only prompt
1239
+ let inviteMessage = `Hi ${firstName}! ${displayName} requires an invite code to join. Please reply with your invite code.`;
1240
+ if (contactHint) {
1241
+ inviteMessage += `\n\n${contactHint}`;
1242
+ }
1243
+ await this.sendToTopic(GENERAL_TOPIC_ID, inviteMessage).catch(() => { });
1244
+ break;
1245
+ }
1246
+ case 'open': {
1247
+ // Start mini-onboarding via callback
1248
+ if (this.onStartMiniOnboarding) {
1249
+ await this.sendToTopic(GENERAL_TOPIC_ID, `Hi ${firstName}! Welcome! Setting up your account...`).catch(() => { });
1250
+ await this.onStartMiniOnboarding(telegramUserId, firstName, username).catch(err => {
1251
+ // @silent-fallback-ok — supplementary notification
1252
+ console.error(`[telegram] Failed to start mini-onboarding: ${err}`);
1253
+ this.sendToTopic(GENERAL_TOPIC_ID, `Sorry ${firstName}, there was an issue setting up your account. Please try again later.`).catch(() => { });
1254
+ });
1255
+ }
1256
+ else {
1257
+ await this.sendToTopic(GENERAL_TOPIC_ID, `Hi ${firstName}! Registration is currently being set up. Please try again later.`).catch(() => { });
1258
+ }
1259
+ break;
1260
+ }
1261
+ default: {
1262
+ // Unknown policy — fall back to gated message
1263
+ console.warn(`[telegram] Unknown registration policy: ${policy}`);
1264
+ await this.sendToTopic(GENERAL_TOPIC_ID, `Hi ${firstName}! ${displayName} is not currently accepting new users.`).catch(() => { });
1265
+ }
1266
+ }
1267
+ }
1268
+ catch (err) {
1269
+ console.error(`[telegram] Error handling unknown user ${telegramUserId}: ${err}`);
1270
+ }
1271
+ }
1272
+ // ── Topic-Session Registry ─────────────────────────────────
1273
+ registerTopicSession(topicId, sessionName, topicName) {
1274
+ this.topicToSession.set(topicId, sessionName);
1275
+ this.sessionToTopic.set(sessionName, topicId);
1276
+ if (topicName) {
1277
+ this.topicToName.set(topicId, topicName);
1278
+ }
1279
+ this.saveRegistry();
1280
+ console.log(`[telegram] Registered topic ${topicId} <-> session "${sessionName}"${topicName ? ` (name: "${topicName}")` : ''}`);
1281
+ }
1282
+ unregisterTopic(topicId) {
1283
+ const sessionName = this.topicToSession.get(topicId);
1284
+ this.topicToSession.delete(topicId);
1285
+ if (sessionName)
1286
+ this.sessionToTopic.delete(sessionName);
1287
+ this.saveRegistry();
1288
+ }
1289
+ getSessionForTopic(topicId) {
1290
+ return this.topicToSession.get(topicId) ?? null;
1291
+ }
1292
+ /**
1293
+ * Get all active topic→session mappings.
1294
+ * Used by TopicResumeMap heartbeat to proactively persist UUIDs.
1295
+ */
1296
+ getAllTopicSessions() {
1297
+ return new Map(this.topicToSession);
1298
+ }
1299
+ getTopicForSession(sessionName) {
1300
+ return this.sessionToTopic.get(sessionName) ?? null;
1301
+ }
1302
+ getTopicName(topicId) {
1303
+ return this.topicToName.get(topicId) ?? null;
1304
+ }
1305
+ // ── Topic Purpose Management ─────────────────────────────────
1306
+ /**
1307
+ * Set the purpose for a topic (e.g., "billing", "technical").
1308
+ * Purpose is used for outbound content validation.
1309
+ */
1310
+ setTopicPurpose(topicId, purpose) {
1311
+ this.topicToPurpose.set(topicId, purpose.toLowerCase());
1312
+ this.saveRegistry();
1313
+ }
1314
+ /**
1315
+ * Get the purpose for a topic. Checks runtime map first, then config.
1316
+ * Returns null if no purpose is set (permissive — all content allowed).
1317
+ */
1318
+ getTopicPurpose(topicId) {
1319
+ // Runtime map takes precedence over config
1320
+ const runtimePurpose = this.topicToPurpose.get(topicId);
1321
+ if (runtimePurpose)
1322
+ return runtimePurpose;
1323
+ // Fall back to config
1324
+ const validationConfig = this.config.contentValidation;
1325
+ if (validationConfig) {
1326
+ return getTopicPurpose(topicId, validationConfig);
1327
+ }
1328
+ return null;
1329
+ }
1330
+ /**
1331
+ * Get all topic purposes (runtime + config merged).
1332
+ */
1333
+ getAllTopicPurposes() {
1334
+ const result = {};
1335
+ // Config purposes first
1336
+ const validationConfig = this.config.contentValidation;
1337
+ if (validationConfig) {
1338
+ for (const [id, purpose] of Object.entries(validationConfig.topicPurposes)) {
1339
+ result[Number(id)] = purpose.toLowerCase();
1340
+ }
1341
+ }
1342
+ // Runtime overrides
1343
+ for (const [topicId, purpose] of this.topicToPurpose) {
1344
+ result[topicId] = purpose;
1345
+ }
1346
+ return result;
1347
+ }
1348
+ /**
1349
+ * Validate outbound content against topic purpose.
1350
+ * Returns the validation result. Callers decide how to handle rejection.
1351
+ */
1352
+ validateOutboundContent(topicId, text, options) {
1353
+ const validationConfig = this.config.contentValidation;
1354
+ if (!validationConfig?.enabled) {
1355
+ return { allowed: true, reason: null, detectedCategory: null, topicPurpose: null, suggestion: null };
1356
+ }
1357
+ const purpose = this.getTopicPurpose(topicId);
1358
+ return validateTopicContent(text, purpose, validationConfig, options);
1359
+ }
1360
+ /**
1361
+ * Classify content using the configured categories.
1362
+ * Useful for debugging and API endpoints.
1363
+ */
1364
+ classifyContent(text) {
1365
+ const validationConfig = this.config.contentValidation;
1366
+ if (!validationConfig) {
1367
+ return { category: null, confidence: 'low', matchedKeywords: [] };
1368
+ }
1369
+ return classifyContent(text, validationConfig.categories);
1370
+ }
1371
+ /**
1372
+ * Get all topic-session mappings (for admin/debug UIs).
1373
+ */
1374
+ getAllTopicMappings() {
1375
+ const result = [];
1376
+ for (const [topicId, sessionName] of this.topicToSession) {
1377
+ result.push({
1378
+ topicId,
1379
+ sessionName,
1380
+ topicName: this.topicToName.get(topicId) ?? null,
1381
+ });
1382
+ }
1383
+ return result;
1384
+ }
1385
+ // ── Stall Detection ──────────────────────────────────────
1386
+ /**
1387
+ * Track that a message was injected into a session.
1388
+ * Used by stall detection to alert if no response comes back.
1389
+ */
1390
+ trackMessageInjection(topicId, sessionName, messageText) {
1391
+ // Phase 1c: Delegate to shared StallDetector when flag is enabled
1392
+ if (this.sharedStallDetector) {
1393
+ this.sharedStallDetector.trackMessageInjection(topicId.toString(), sessionName, messageText);
1394
+ return;
1395
+ }
1396
+ const key = `${topicId}-${Date.now()}`;
1397
+ this.pendingMessages.set(key, {
1398
+ topicId,
1399
+ sessionName,
1400
+ messageText: messageText.slice(0, 100),
1401
+ injectedAt: Date.now(),
1402
+ alerted: false,
1403
+ });
1404
+ }
1405
+ clearStallForTopic(topicId) {
1406
+ // Phase 1c: Delegate to shared StallDetector when flag is enabled
1407
+ if (this.sharedStallDetector) {
1408
+ this.sharedStallDetector.clearStallForChannel(topicId.toString());
1409
+ return;
1410
+ }
1411
+ for (const [key, pending] of this.pendingMessages) {
1412
+ if (pending.topicId === topicId) {
1413
+ this.pendingMessages.delete(key);
1414
+ }
1415
+ }
1416
+ }
1417
+ /**
1418
+ * Public interface for external callers (e.g., StallTriageNurse) to clear
1419
+ * stall tracking for a topic after successful recovery.
1420
+ */
1421
+ clearStallTracking(topicId) {
1422
+ this.clearStallForTopic(topicId);
1423
+ }
1424
+ /** Clear promise tracking for a topic (e.g., after successful recovery) */
1425
+ clearPromiseTracking(topicId) {
1426
+ if (this.sharedStallDetector) {
1427
+ this.sharedStallDetector.clearPromiseForChannel(topicId.toString());
1428
+ return;
1429
+ }
1430
+ this.pendingPromises.delete(topicId);
1431
+ }
1432
+ /** Detect "work-in-progress" messages that imply the agent will follow up */
1433
+ isPromiseMessage(text) {
1434
+ const promisePatterns = [
1435
+ /give me (?:a )?(?:couple|few|some) (?:more )?minutes/i,
1436
+ /give me (?:a )?(?:minute|moment|second|sec)/i,
1437
+ /working on (?:it|this|that)/i,
1438
+ /looking into (?:it|this|that)/i,
1439
+ /let me (?:check|look|investigate|dig|research)/i,
1440
+ /investigating/i,
1441
+ /still (?:on it|working|looking)/i,
1442
+ /one moment/i,
1443
+ /be right back/i,
1444
+ /hang on/i,
1445
+ /bear with me/i,
1446
+ /i'll (?:get back|follow up|check|look into)/i,
1447
+ /narrowing (?:it |this |that )?down/i,
1448
+ ];
1449
+ return promisePatterns.some(p => p.test(text));
1450
+ }
1451
+ /** Detect messages that indicate the agent delivered on its promise */
1452
+ isFollowThroughMessage(text) {
1453
+ // Messages that indicate the agent is delivering results (not just status updates)
1454
+ // Must be substantially longer than a typical status update
1455
+ if (text.length > 200)
1456
+ return true;
1457
+ // Explicit completion signals
1458
+ const completionPatterns = [
1459
+ /here(?:'s| is| are) (?:what|the)/i,
1460
+ /i found/i,
1461
+ /the (?:issue|problem|bug|fix|solution|answer|result)/i,
1462
+ /done|completed|finished|resolved/i,
1463
+ /summary|overview|analysis/i,
1464
+ ];
1465
+ return completionPatterns.some(p => p.test(text));
1466
+ }
1467
+ /**
1468
+ * LLM gate for fallback stall/promise alerts.
1469
+ *
1470
+ * Before sending a user-facing alert about a stall or expired promise,
1471
+ * check with the intelligence provider whether the alert is warranted.
1472
+ * This prevents false positives when the StallTriageNurse is unavailable.
1473
+ *
1474
+ * Returns true if the alert should be sent, false to suppress.
1475
+ * If no intelligence provider is available, returns true (fail-open for safety).
1476
+ */
1477
+ async confirmStallAlert(context) {
1478
+ if (!this.intelligence)
1479
+ return true; // No LLM available → fail-open
1480
+ const prompt = [
1481
+ 'You are evaluating whether to send an alert to a user about an AI agent session.',
1482
+ '',
1483
+ `Alert type: ${context.type}`,
1484
+ `Session: "${context.sessionName}" (${context.sessionAlive ? 'still running' : 'stopped'})`,
1485
+ `Time elapsed: ${context.minutesElapsed} minutes`,
1486
+ `Context: "${context.messageText}"`,
1487
+ '',
1488
+ 'Should we send a user-facing alert about this? Consider:',
1489
+ '- If the session stopped, the user needs to know',
1490
+ '- If the session is still running, it might just be working on a complex task',
1491
+ `- ${context.minutesElapsed} minutes is ${context.minutesElapsed > 15 ? 'a long time' : 'moderate'} for an AI task`,
1492
+ '',
1493
+ 'Respond with exactly one word: yes or no.',
1494
+ ].join('\n');
1495
+ try {
1496
+ const response = await this.intelligence.evaluate(prompt, {
1497
+ maxTokens: 5,
1498
+ temperature: 0,
1499
+ });
1500
+ const answer = response.trim().toLowerCase();
1501
+ if (answer === 'no') {
1502
+ console.log(`[telegram] LLM suppressed ${context.type} alert for "${context.sessionName}" (${context.minutesElapsed}m)`);
1503
+ return false;
1504
+ }
1505
+ return true;
1506
+ }
1507
+ catch (err) {
1508
+ // @silent-fallback-ok — LLM intelligence is optional; fail-open to alert user about stalls
1509
+ console.warn(`[telegram] LLM stall confirmation failed, allowing alert:`, err);
1510
+ return true; // Fail-open
1511
+ }
1512
+ }
1513
+ /** Get all active topic-session mappings (used by SessionMonitor) */
1514
+ getActiveTopicSessions() {
1515
+ return new Map(this.topicToSession);
1516
+ }
1517
+ /** Get recent message log entries for analysis */
1518
+ getMessageLog(limit = 100) {
1519
+ try {
1520
+ if (!fs.existsSync(this.messageLogPath))
1521
+ return [];
1522
+ const content = fs.readFileSync(this.messageLogPath, 'utf-8');
1523
+ const lines = content.trim().split('\n').filter(Boolean).slice(-limit);
1524
+ return lines.map(line => {
1525
+ try {
1526
+ const entry = JSON.parse(line);
1527
+ return {
1528
+ topicId: entry.topicId,
1529
+ text: entry.text || '',
1530
+ fromUser: entry.fromUser ?? true,
1531
+ timestamp: entry.timestamp || new Date().toISOString(),
1532
+ };
1533
+ }
1534
+ catch {
1535
+ // @silent-fallback-ok — JSONL parse, skip corrupted
1536
+ return null;
1537
+ }
1538
+ }).filter(Boolean);
1539
+ }
1540
+ catch {
1541
+ // @silent-fallback-ok — log read, empty array safe
1542
+ return [];
1543
+ }
1544
+ }
1545
+ async checkForStalls() {
1546
+ const stallMinutes = this.config.stallTimeoutMinutes ?? 5;
1547
+ const stallThresholdMs = stallMinutes * 60 * 1000;
1548
+ const now = Date.now();
1549
+ for (const [key, pending] of this.pendingMessages) {
1550
+ if (pending.alerted)
1551
+ continue;
1552
+ if (now - pending.injectedAt < stallThresholdMs)
1553
+ continue;
1554
+ // Check if session is still alive
1555
+ const alive = this.onIsSessionAlive
1556
+ ? this.onIsSessionAlive(pending.sessionName)
1557
+ : true; // assume alive if no checker
1558
+ // If alive, verify the session is truly stalled (not just responding through a different path)
1559
+ if (alive && this.onIsSessionActive) {
1560
+ try {
1561
+ const active = await this.onIsSessionActive(pending.sessionName);
1562
+ if (active) {
1563
+ // Session is producing output — false alarm, clear it
1564
+ console.log(`[telegram] Session "${pending.sessionName}" verified active, clearing stall`);
1565
+ this.pendingMessages.delete(key);
1566
+ continue;
1567
+ }
1568
+ }
1569
+ catch {
1570
+ // Verifier failed — fall through to alert
1571
+ }
1572
+ }
1573
+ pending.alerted = true;
1574
+ // Try LLM-powered triage first if available
1575
+ if (this.onStallDetected) {
1576
+ try {
1577
+ const triageResult = await this.onStallDetected(pending.topicId, pending.sessionName, pending.messageText, pending.injectedAt);
1578
+ if (triageResult.resolved) {
1579
+ this.pendingMessages.delete(key);
1580
+ continue; // Nurse handled it
1581
+ }
1582
+ // Nurse couldn't resolve — fall through to quota check / generic alert
1583
+ }
1584
+ catch (err) {
1585
+ console.warn(`[telegram] Triage nurse error:`, err);
1586
+ }
1587
+ }
1588
+ // Classify the stall — check if it's a quota death
1589
+ let isQuotaDeath = false;
1590
+ if (this.onClassifySessionDeath) {
1591
+ try {
1592
+ const classification = await this.onClassifySessionDeath(pending.sessionName);
1593
+ if (classification && classification.cause === 'quota_exhaustion') {
1594
+ isQuotaDeath = true;
1595
+ this.sendToTopic(pending.topicId, `\ud83d\udd34 Session hit quota limit \u2014 "${pending.sessionName}" can't respond.\n\n` +
1596
+ `${classification.detail}\n\n` +
1597
+ `Use /quota to check accounts, /switch-account to switch, or /login to authenticate a new account.`).catch(err => {
1598
+ console.error(`[telegram] Quota stall alert failed: ${err}`);
1599
+ });
1600
+ }
1601
+ }
1602
+ catch {
1603
+ // Classification failed — fall through to generic
1604
+ }
1605
+ }
1606
+ if (!isQuotaDeath) {
1607
+ const minutesAgo = Math.round((now - pending.injectedAt) / 60000);
1608
+ // LLM gate: confirm alert is warranted before sending user-facing message
1609
+ const shouldAlert = await this.confirmStallAlert({
1610
+ type: 'stall',
1611
+ sessionName: pending.sessionName,
1612
+ messageText: pending.messageText,
1613
+ minutesElapsed: minutesAgo,
1614
+ sessionAlive: alive,
1615
+ });
1616
+ if (shouldAlert) {
1617
+ const status = alive ? 'running but not responding' : 'no longer running';
1618
+ this.sendToTopic(pending.topicId, `\u26a0\ufe0f No response after ${minutesAgo} minutes. "${pending.sessionName}" is ${status}.\n\nYour message: "${pending.messageText}..."${alive ? '\n\nTry /interrupt to nudge it, or /restart to start fresh.' : '\n\nSend another message and a new session will start automatically.'}`).catch(err => {
1619
+ console.error(`[telegram] Stall alert failed: ${err}`);
1620
+ });
1621
+ }
1622
+ }
1623
+ }
1624
+ // Check for expired promises (agent said "give me a minute" but never followed up)
1625
+ const promiseMinutes = this.config.promiseTimeoutMinutes ?? 10;
1626
+ const promiseThresholdMs = promiseMinutes * 60 * 1000;
1627
+ if (promiseMinutes > 0) {
1628
+ for (const [topicId, promise] of this.pendingPromises) {
1629
+ if (promise.alerted)
1630
+ continue;
1631
+ if (now - promise.promisedAt < promiseThresholdMs)
1632
+ continue;
1633
+ promise.alerted = true;
1634
+ console.log(`[telegram] Promise expired for topic ${topicId}: "${promise.promiseText}" (${Math.round((now - promise.promisedAt) / 60000)} min ago)`);
1635
+ // Check if session is still alive
1636
+ const alive = this.onIsSessionAlive
1637
+ ? this.onIsSessionAlive(promise.sessionName)
1638
+ : true;
1639
+ // Delegate to triage nurse if available
1640
+ if (this.onStallDetected) {
1641
+ try {
1642
+ const triageResult = await this.onStallDetected(promise.topicId, promise.sessionName, `[promise expired] ${promise.promiseText}`, promise.promisedAt);
1643
+ if (triageResult.resolved) {
1644
+ this.pendingPromises.delete(topicId);
1645
+ continue;
1646
+ }
1647
+ }
1648
+ catch (err) {
1649
+ console.warn(`[telegram] Promise triage error:`, err);
1650
+ DegradationReporter.getInstance().report({
1651
+ feature: 'TelegramAdapter.onStallDetected',
1652
+ primary: 'LLM-based stall triage diagnosis',
1653
+ fallback: 'Stall goes undiagnosed',
1654
+ reason: `Why: ${err instanceof Error ? err.message : String(err)}`,
1655
+ impact: 'Stalled session persists without recovery attempt',
1656
+ });
1657
+ }
1658
+ }
1659
+ // Fallback: LLM-gated user-facing alert
1660
+ const promiseMinutesAgo = Math.round((now - promise.promisedAt) / 60000);
1661
+ const shouldAlertPromise = await this.confirmStallAlert({
1662
+ type: 'promise-expired',
1663
+ sessionName: promise.sessionName,
1664
+ messageText: promise.promiseText,
1665
+ minutesElapsed: promiseMinutesAgo,
1666
+ sessionAlive: alive,
1667
+ });
1668
+ if (shouldAlertPromise) {
1669
+ if (!alive) {
1670
+ await this.sendToTopic(topicId, `The session stopped unexpectedly after saying "${promise.promiseText}". Sending a new message will auto-spawn a fresh session.`).catch(() => { });
1671
+ }
1672
+ else {
1673
+ await this.sendToTopic(topicId, `It's been ${promiseMinutesAgo} minutes since the session said "${promise.promiseText}" — checking on it now...`).catch(() => { });
1674
+ }
1675
+ }
1676
+ }
1677
+ // Clean up old promise entries
1678
+ for (const [topicId, promise] of this.pendingPromises) {
1679
+ if (promise.alerted && now - promise.promisedAt > 60 * 60 * 1000) {
1680
+ this.pendingPromises.delete(topicId);
1681
+ }
1682
+ }
1683
+ }
1684
+ // Clean up old entries (older than 30 minutes, already alerted)
1685
+ for (const [key, pending] of this.pendingMessages) {
1686
+ if (pending.alerted && now - pending.injectedAt > 30 * 60 * 1000) {
1687
+ this.pendingMessages.delete(key);
1688
+ }
1689
+ }
1690
+ }
1691
+ /**
1692
+ * Handle stall events from the shared StallDetector (Phase 1c).
1693
+ * Bridges shared events back to Telegram-specific alert logic
1694
+ * (triage nurse, quota classification, LLM gate, user notifications).
1695
+ */
1696
+ async handleSharedStallEvent(event, alive) {
1697
+ const topicId = parseInt(event.channelId, 10);
1698
+ // Phase 1e: Emit to event bus
1699
+ if (this.eventBus) {
1700
+ if (event.type === 'stall') {
1701
+ this.eventBus.emit('stall:detected', {
1702
+ channelId: event.channelId,
1703
+ sessionName: event.sessionName,
1704
+ messageText: event.messageText,
1705
+ injectedAt: event.injectedAt,
1706
+ minutesElapsed: event.minutesElapsed,
1707
+ alive,
1708
+ }).catch(err => console.error(`[telegram] EventBus stall:detected error: ${err}`));
1709
+ }
1710
+ else {
1711
+ this.eventBus.emit('stall:promise-expired', {
1712
+ channelId: event.channelId,
1713
+ sessionName: event.sessionName,
1714
+ promiseText: event.messageText,
1715
+ promisedAt: event.injectedAt,
1716
+ minutesElapsed: event.minutesElapsed,
1717
+ alive,
1718
+ }).catch(err => console.error(`[telegram] EventBus stall:promise-expired error: ${err}`));
1719
+ }
1720
+ }
1721
+ if (event.type === 'stall') {
1722
+ // Try LLM-powered triage first
1723
+ if (this.onStallDetected) {
1724
+ try {
1725
+ const triageResult = await this.onStallDetected(topicId, event.sessionName, event.messageText, event.injectedAt);
1726
+ if (triageResult.resolved)
1727
+ return;
1728
+ }
1729
+ catch (err) {
1730
+ console.warn(`[telegram] Triage nurse error:`, err);
1731
+ }
1732
+ }
1733
+ // Classify — check if it's a quota death
1734
+ let isQuotaDeath = false;
1735
+ if (this.onClassifySessionDeath) {
1736
+ try {
1737
+ const classification = await this.onClassifySessionDeath(event.sessionName);
1738
+ if (classification && classification.cause === 'quota_exhaustion') {
1739
+ isQuotaDeath = true;
1740
+ this.sendToTopic(topicId, `\ud83d\udd34 Session hit quota limit \u2014 "${event.sessionName}" can't respond.\n\n` +
1741
+ `${classification.detail}\n\n` +
1742
+ `Use /quota to check accounts, /switch-account to switch, or /login to authenticate a new account.`).catch(err => console.error(`[telegram] Quota stall alert failed: ${err}`));
1743
+ }
1744
+ }
1745
+ catch { /* Classification failed — fall through */ }
1746
+ }
1747
+ if (!isQuotaDeath) {
1748
+ const shouldAlert = await this.confirmStallAlert({
1749
+ type: 'stall',
1750
+ sessionName: event.sessionName,
1751
+ messageText: event.messageText,
1752
+ minutesElapsed: event.minutesElapsed,
1753
+ sessionAlive: alive,
1754
+ });
1755
+ if (shouldAlert) {
1756
+ const status = alive ? 'running but not responding' : 'no longer running';
1757
+ this.sendToTopic(topicId, `\u26a0\ufe0f No response after ${event.minutesElapsed} minutes. Session "${event.sessionName}" is ${status}.\n\nMessage: "${event.messageText}..."${alive ? '\n\nTry /interrupt to unstick, or /restart to respawn.' : '\n\nSend another message to auto-respawn.'}`).catch(err => console.error(`[telegram] Stall alert failed: ${err}`));
1758
+ }
1759
+ }
1760
+ }
1761
+ else if (event.type === 'promise-expired') {
1762
+ // Try triage nurse first
1763
+ if (this.onStallDetected) {
1764
+ try {
1765
+ const triageResult = await this.onStallDetected(topicId, event.sessionName, `[promise expired] ${event.messageText}`, event.injectedAt);
1766
+ if (triageResult.resolved)
1767
+ return;
1768
+ }
1769
+ catch (err) {
1770
+ console.warn(`[telegram] Promise triage error:`, err);
1771
+ }
1772
+ }
1773
+ const shouldAlert = await this.confirmStallAlert({
1774
+ type: 'promise-expired',
1775
+ sessionName: event.sessionName,
1776
+ messageText: event.messageText,
1777
+ minutesElapsed: event.minutesElapsed,
1778
+ sessionAlive: alive,
1779
+ });
1780
+ if (shouldAlert) {
1781
+ if (!alive) {
1782
+ await this.sendToTopic(topicId, `The session stopped unexpectedly after saying "${event.messageText}". Sending a new message will auto-spawn a fresh session.`).catch(() => { });
1783
+ }
1784
+ else {
1785
+ await this.sendToTopic(topicId, `It's been ${event.minutesElapsed} minutes since the session said "${event.messageText}" — checking on it now...`).catch(() => { });
1786
+ }
1787
+ }
1788
+ }
1789
+ }
1790
+ // ── Health Status ────────────────────────────────────────
1791
+ getStatus() {
1792
+ const stallStatus = this.sharedStallDetector
1793
+ ? this.sharedStallDetector.getStatus()
1794
+ : { pendingStalls: this.pendingMessages.size, pendingPromises: this.pendingPromises.size };
1795
+ return {
1796
+ started: this.polling,
1797
+ uptime: this.startedAt ? Date.now() - this.startedAt.getTime() : null,
1798
+ pendingStalls: stallStatus.pendingStalls,
1799
+ pendingPromises: stallStatus.pendingPromises,
1800
+ topicMappings: this.topicToSession.size,
1801
+ };
1802
+ }
1803
+ // ── Voice Transcription ──────────────────────────────────
1804
+ /**
1805
+ * Download a file from Telegram by file_id.
1806
+ */
1807
+ async downloadFile(fileId, destPath) {
1808
+ const maxRetries = 3;
1809
+ let lastError;
1810
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1811
+ try {
1812
+ const fileInfo = await this.apiCall('getFile', { file_id: fileId });
1813
+ const fileUrl = `https://api.telegram.org/file/bot${this.config.token}/${fileInfo.file_path}`;
1814
+ const controller = new AbortController();
1815
+ const timer = setTimeout(() => controller.abort(), 60_000);
1816
+ try {
1817
+ const response = await fetch(fileUrl, { signal: controller.signal });
1818
+ if (!response.ok)
1819
+ throw new Error(`Download failed: ${response.status}`);
1820
+ const buffer = Buffer.from(await response.arrayBuffer());
1821
+ fs.writeFileSync(destPath, buffer);
1822
+ return; // Success
1823
+ }
1824
+ finally {
1825
+ clearTimeout(timer);
1826
+ }
1827
+ }
1828
+ catch (err) {
1829
+ lastError = err instanceof Error ? err : new Error(String(err));
1830
+ if (attempt < maxRetries) {
1831
+ const delay = attempt * 1000;
1832
+ console.warn(`[telegram] File download attempt ${attempt}/${maxRetries} failed, retrying in ${delay}ms...`);
1833
+ await new Promise(resolve => setTimeout(resolve, delay));
1834
+ }
1835
+ }
1836
+ }
1837
+ throw lastError;
1838
+ }
1839
+ /**
1840
+ * Resolve voice transcription provider from config or environment.
1841
+ * Checks explicit config, then env vars, then auto-detects.
1842
+ */
1843
+ resolveTranscriptionProvider() {
1844
+ const providers = {
1845
+ groq: {
1846
+ envKey: 'GROQ_API_KEY',
1847
+ baseUrl: 'https://api.groq.com/openai/v1',
1848
+ model: 'whisper-large-v3',
1849
+ },
1850
+ openai: {
1851
+ envKey: 'OPENAI_API_KEY',
1852
+ baseUrl: 'https://api.openai.com/v1',
1853
+ model: 'whisper-1',
1854
+ },
1855
+ };
1856
+ // Check explicit config
1857
+ const explicit = this.config.voiceProvider?.toLowerCase();
1858
+ if (explicit && providers[explicit]) {
1859
+ const p = providers[explicit];
1860
+ const apiKey = process.env[p.envKey];
1861
+ if (!apiKey) {
1862
+ console.warn(`[telegram] ${p.envKey} not set — required for ${explicit} voice transcription`);
1863
+ return null;
1864
+ }
1865
+ return { apiKey, baseUrl: p.baseUrl, model: p.model };
1866
+ }
1867
+ // Auto-detect: try Groq first (cheaper), then OpenAI
1868
+ for (const [name, p] of Object.entries(providers)) {
1869
+ const apiKey = process.env[p.envKey];
1870
+ if (apiKey) {
1871
+ console.log(`[telegram] Auto-detected voice transcription provider: ${name}`);
1872
+ return { apiKey, baseUrl: p.baseUrl, model: p.model };
1873
+ }
1874
+ }
1875
+ return null;
1876
+ }
1877
+ /**
1878
+ * Transcribe a voice message using the configured provider.
1879
+ */
1880
+ async transcribeVoice(filePath) {
1881
+ const provider = this.resolveTranscriptionProvider();
1882
+ if (!provider) {
1883
+ throw new Error('No voice transcription provider configured. Set GROQ_API_KEY or OPENAI_API_KEY.');
1884
+ }
1885
+ const formData = new FormData();
1886
+ const fileBuffer = fs.readFileSync(filePath);
1887
+ const blob = new Blob([fileBuffer], { type: 'audio/ogg' });
1888
+ formData.append('file', blob, path.basename(filePath));
1889
+ formData.append('model', provider.model);
1890
+ const controller = new AbortController();
1891
+ const timer = setTimeout(() => controller.abort(), 60_000);
1892
+ try {
1893
+ const response = await fetch(`${provider.baseUrl}/audio/transcriptions`, {
1894
+ method: 'POST',
1895
+ headers: { Authorization: `Bearer ${provider.apiKey}` },
1896
+ body: formData,
1897
+ signal: controller.signal,
1898
+ });
1899
+ if (!response.ok) {
1900
+ const errText = await response.text();
1901
+ throw new Error(`Transcription API error (${response.status}): ${errText}`);
1902
+ }
1903
+ const data = await response.json();
1904
+ return data.text;
1905
+ }
1906
+ finally {
1907
+ clearTimeout(timer);
1908
+ }
1909
+ }
1910
+ // ── Photo Handling ───────────────────────────────────────
1911
+ /**
1912
+ * Download a photo from Telegram and save it locally.
1913
+ * Returns the local file path.
1914
+ */
1915
+ async downloadPhoto(fileId, messageId) {
1916
+ const photoDir = path.join(this.stateDir, 'telegram-images');
1917
+ fs.mkdirSync(photoDir, { recursive: true });
1918
+ const filename = `photo-${Date.now()}-${messageId}.jpg`;
1919
+ const filepath = path.join(photoDir, filename);
1920
+ await this.downloadFile(fileId, filepath);
1921
+ return filepath;
1922
+ }
1923
+ // ── Document Handling ───────────────────────────────────
1924
+ /**
1925
+ * Download a document from Telegram and save it locally.
1926
+ * Preserves the original filename when available.
1927
+ * Returns the local file path.
1928
+ */
1929
+ async downloadDocument(fileId, messageId, originalName) {
1930
+ const docDir = path.join(this.stateDir, 'telegram-documents');
1931
+ fs.mkdirSync(docDir, { recursive: true });
1932
+ const ext = originalName ? path.extname(originalName) : '';
1933
+ const baseName = originalName
1934
+ ? originalName.replace(/[^a-zA-Z0-9._-]/g, '_')
1935
+ : `document-${messageId}${ext}`;
1936
+ const filename = `${Date.now()}-${baseName}`;
1937
+ const filepath = path.join(docDir, filename);
1938
+ await this.downloadFile(fileId, filepath);
1939
+ return filepath;
1940
+ }
1941
+ // ── Command Handling ─────────────────────────────────────
1942
+ /**
1943
+ * Process Telegram commands. Returns true if the message was a command.
1944
+ */
1945
+ async handleCommand(text, topicId, userId) {
1946
+ // Phase 1a: Delegate to shared CommandRouter when flag is enabled
1947
+ if (this.sharedCommandRouter) {
1948
+ return this.sharedCommandRouter.route(text, topicId.toString(), userId.toString(), { telegramUserId: userId, topicId });
1949
+ }
1950
+ const cmd = text.trim().toLowerCase();
1951
+ // Attention topic commands — intercept before general commands
1952
+ if (this.isAttentionTopic(topicId)) {
1953
+ const handled = await this.handleAttentionCommand(topicId, text);
1954
+ if (handled)
1955
+ return true;
1956
+ }
1957
+ // /flush — flush all batched notifications immediately
1958
+ if (cmd === '/flush') {
1959
+ if (this.batcher && this.batcher.isEnabled()) {
1960
+ const flushed = await this.batcher.flushAll();
1961
+ if (flushed > 0) {
1962
+ await this.sendToTopic(topicId, `Flushed ${flushed} batched notification${flushed === 1 ? '' : 's'}.`).catch(() => { });
1963
+ }
1964
+ else {
1965
+ await this.sendToTopic(topicId, 'No batched notifications to flush.').catch(() => { });
1966
+ }
1967
+ }
1968
+ else if (this.onFlushNotifications) {
1969
+ this.onFlushNotifications(topicId).catch(err => {
1970
+ console.error('[telegram] Flush notifications failed:', err);
1971
+ this.sendToTopic(topicId, 'Failed to flush notifications.').catch(() => { });
1972
+ });
1973
+ }
1974
+ else {
1975
+ await this.sendToTopic(topicId, 'Notification batching is not enabled.').catch(() => { });
1976
+ }
1977
+ return true;
1978
+ }
1979
+ // /sessions — list all sessions with claim status
1980
+ if (cmd === '/sessions' || cmd.startsWith('/sessions ')) {
1981
+ const filterUnclaimed = cmd.includes('unclaimed');
1982
+ if (!this.onListSessions) {
1983
+ await this.sendToTopic(topicId, 'Session listing not available.').catch(() => { });
1984
+ return true;
1985
+ }
1986
+ const sessions = this.onListSessions();
1987
+ if (sessions.length === 0) {
1988
+ await this.sendToTopic(topicId, 'No sessions running.').catch(() => { });
1989
+ return true;
1990
+ }
1991
+ const lines = [];
1992
+ for (const s of sessions) {
1993
+ const linkedTopic = this.getTopicForSession(s.tmuxSession);
1994
+ const claimed = linkedTopic !== null;
1995
+ if (filterUnclaimed && claimed)
1996
+ continue;
1997
+ const status = s.alive ? '\u2705' : '\u274c';
1998
+ const claimTag = claimed ? ` (topic ${linkedTopic})` : ' \u{1f7e1} unclaimed';
1999
+ lines.push(`${status} ${s.name}${claimTag}`);
2000
+ }
2001
+ if (lines.length === 0) {
2002
+ await this.sendToTopic(topicId, filterUnclaimed ? 'No unclaimed sessions.' : 'No sessions.').catch(() => { });
2003
+ }
2004
+ else {
2005
+ await this.sendToTopic(topicId, lines.join('\n')).catch(() => { });
2006
+ }
2007
+ return true;
2008
+ }
2009
+ // /claim <session> — claim a session into this topic
2010
+ if (cmd.startsWith('/claim ')) {
2011
+ const sessionName = text.trim().slice(7).trim();
2012
+ if (!sessionName) {
2013
+ await this.sendToTopic(topicId, 'Please include a session name — e.g. /claim my-session').catch(() => { });
2014
+ return true;
2015
+ }
2016
+ // Check if already claimed
2017
+ const existingSession = this.getSessionForTopic(topicId);
2018
+ if (existingSession) {
2019
+ await this.sendToTopic(topicId, `This topic is already linked to "${existingSession}". Use /unlink first.`).catch(() => { });
2020
+ return true;
2021
+ }
2022
+ this.registerTopicSession(topicId, sessionName);
2023
+ await this.sendToTopic(topicId, `Claimed session "${sessionName}" into this topic.`).catch(() => { });
2024
+ return true;
2025
+ }
2026
+ // /link <session> — alias for /claim
2027
+ if (cmd.startsWith('/link ')) {
2028
+ const sessionName = text.trim().slice(6).trim();
2029
+ if (!sessionName) {
2030
+ await this.sendToTopic(topicId, 'Please include a session name — e.g. /link my-session').catch(() => { });
2031
+ return true;
2032
+ }
2033
+ const existingSession = this.getSessionForTopic(topicId);
2034
+ if (existingSession) {
2035
+ await this.sendToTopic(topicId, `This topic is already linked to "${existingSession}". Use /unlink first.`).catch(() => { });
2036
+ return true;
2037
+ }
2038
+ this.registerTopicSession(topicId, sessionName);
2039
+ await this.sendToTopic(topicId, `Linked session "${sessionName}" to this topic.`).catch(() => { });
2040
+ return true;
2041
+ }
2042
+ // /unlink — unlink session from this topic
2043
+ if (cmd === '/unlink') {
2044
+ const sessionName = this.getSessionForTopic(topicId);
2045
+ if (!sessionName) {
2046
+ await this.sendToTopic(topicId, 'No session linked to this topic.').catch(() => { });
2047
+ return true;
2048
+ }
2049
+ this.unregisterTopic(topicId);
2050
+ await this.sendToTopic(topicId, `Unlinked session "${sessionName}" from this topic.`).catch(() => { });
2051
+ return true;
2052
+ }
2053
+ // /interrupt — send Escape to unstick a stalled session
2054
+ if (cmd === '/interrupt') {
2055
+ const sessionName = this.getSessionForTopic(topicId);
2056
+ if (!sessionName) {
2057
+ await this.sendToTopic(topicId, 'No session linked to this topic.').catch(() => { });
2058
+ return true;
2059
+ }
2060
+ if (!this.onInterruptSession) {
2061
+ await this.sendToTopic(topicId, 'Interrupt not available (no handler registered).').catch(() => { });
2062
+ return true;
2063
+ }
2064
+ try {
2065
+ const success = await this.onInterruptSession(sessionName);
2066
+ // Clear stall tracking — user is actively intervening
2067
+ this.clearStallForTopic(topicId);
2068
+ if (success) {
2069
+ await this.sendToTopic(topicId, `Nudged "${sessionName}" \u2014 it should resume shortly.`).catch(() => { });
2070
+ }
2071
+ else {
2072
+ await this.sendToTopic(topicId, `Failed to interrupt "${sessionName}" \u2014 session may not exist.`).catch(() => { });
2073
+ }
2074
+ }
2075
+ catch (err) {
2076
+ console.error(`[telegram] Interrupt failed:`, err);
2077
+ await this.sendToTopic(topicId, 'Couldn\'t interrupt the session. It may have already ended.').catch(() => { });
2078
+ }
2079
+ return true;
2080
+ }
2081
+ // /restart — kill and respawn the session for this topic
2082
+ if (cmd === '/restart') {
2083
+ const sessionName = this.getSessionForTopic(topicId);
2084
+ if (!sessionName) {
2085
+ await this.sendToTopic(topicId, 'No session linked to this topic.').catch(() => { });
2086
+ return true;
2087
+ }
2088
+ if (!this.onRestartSession) {
2089
+ await this.sendToTopic(topicId, 'Restart not available (no handler registered).').catch(() => { });
2090
+ return true;
2091
+ }
2092
+ // Clear stall tracking — user is actively intervening
2093
+ this.clearStallForTopic(topicId);
2094
+ await this.sendToTopic(topicId, `Restarting "${sessionName}"...`).catch(() => { });
2095
+ try {
2096
+ await this.onRestartSession(sessionName, topicId);
2097
+ await this.sendToTopic(topicId, 'Session restarted.').catch(() => { });
2098
+ }
2099
+ catch (err) {
2100
+ console.error(`[telegram] Restart failed:`, err);
2101
+ await this.sendToTopic(topicId, 'Restart didn\'t work. The session may need to be recreated — try sending a new message.').catch(() => { });
2102
+ }
2103
+ return true;
2104
+ }
2105
+ // /status — show Telegram adapter status
2106
+ if (cmd === '/status') {
2107
+ const s = this.getStatus();
2108
+ const lines = [
2109
+ `Telegram adapter: ${s.started ? '\u2705 running' : '\u274c stopped'}`,
2110
+ `Uptime: ${s.uptime ? Math.round(s.uptime / 60000) + 'm' : 'n/a'}`,
2111
+ `Topic mappings: ${s.topicMappings}`,
2112
+ `Pending stall alerts: ${s.pendingStalls}`,
2113
+ ];
2114
+ await this.sendToTopic(topicId, lines.join('\n')).catch(() => { });
2115
+ return true;
2116
+ }
2117
+ // /triage — show triage status for this topic
2118
+ if (cmd === '/triage') {
2119
+ if (!this.onGetTriageStatus) {
2120
+ await this.sendToTopic(topicId, 'Triage system not available.').catch(() => { });
2121
+ return true;
2122
+ }
2123
+ const status = this.onGetTriageStatus(topicId);
2124
+ if (!status || !status.active) {
2125
+ await this.sendToTopic(topicId, '🔍 No active triage for this topic. Session appears to be operating normally.').catch(() => { });
2126
+ }
2127
+ else {
2128
+ const lines = [
2129
+ `🔍 Active triage for this topic:`,
2130
+ `Classification: ${status.classification || 'pending'}`,
2131
+ `Checks: ${status.checkCount}`,
2132
+ status.lastCheck ? `Last check: ${status.lastCheck}` : '',
2133
+ ].filter(Boolean);
2134
+ await this.sendToTopic(topicId, lines.join('\n')).catch(() => { });
2135
+ }
2136
+ return true;
2137
+ }
2138
+ // /switch-account (or /sa) <target> — switch active Claude account
2139
+ const switchMatch = text.match(/^\/(?:switch[-_]?account|sa)\s+(.+)$/i);
2140
+ if (switchMatch) {
2141
+ const target = switchMatch[1].trim();
2142
+ if (this.onSwitchAccountRequest) {
2143
+ this.onSwitchAccountRequest(target, topicId).catch(err => {
2144
+ console.error('[telegram] Switch account failed:', err);
2145
+ this.sendToTopic(topicId, 'Account switch didn\'t work. Try again or use /quota to check status.').catch(() => { });
2146
+ });
2147
+ }
2148
+ else {
2149
+ await this.sendToTopic(topicId, 'Account switching not available.').catch(() => { });
2150
+ }
2151
+ return true;
2152
+ }
2153
+ // /quota (or /q) — show multi-account quota summary
2154
+ if (cmd === '/quota' || cmd === '/q') {
2155
+ if (this.onQuotaStatusRequest) {
2156
+ this.onQuotaStatusRequest(topicId).catch(err => {
2157
+ console.error('[telegram] Quota status failed:', err);
2158
+ this.sendToTopic(topicId, 'Couldn\'t check quota right now. Try again in a moment.').catch(() => { });
2159
+ });
2160
+ }
2161
+ else {
2162
+ await this.sendToTopic(topicId, 'Quota status not available.').catch(() => { });
2163
+ }
2164
+ return true;
2165
+ }
2166
+ // /login [email] — seamless OAuth login from Telegram
2167
+ const loginMatch = text.match(/^\/login(?:\s+(.+))?$/i);
2168
+ if (loginMatch) {
2169
+ const email = loginMatch[1]?.trim() || null;
2170
+ if (this.onLoginRequest) {
2171
+ this.onLoginRequest(email, topicId).catch(err => {
2172
+ // @silent-fallback-ok — login error, user notified
2173
+ console.error('[telegram] Login flow failed:', err);
2174
+ this.sendToTopic(topicId, 'Login didn\'t complete. Try again, or the auth service may be temporarily unavailable.').catch(() => { });
2175
+ });
2176
+ }
2177
+ else {
2178
+ await this.sendToTopic(topicId, 'Login not available.').catch(() => { });
2179
+ }
2180
+ return true;
2181
+ }
2182
+ // /new is handled in server.ts onTopicMessage — it needs sessionManager
2183
+ // access to spawn a session in the new topic. Don't intercept it here.
2184
+ return false;
2185
+ }
2186
+ // ── Message Log ────────────────────────────────────────────
2187
+ /**
2188
+ * Search the message log with flexible filters.
2189
+ * Supports text query, topicId filter, date range, and pagination.
2190
+ */
2191
+ searchLog(opts = {}) {
2192
+ if (!fs.existsSync(this.messageLogPath))
2193
+ return [];
2194
+ const limit = Math.min(opts.limit ?? 50, 500);
2195
+ const queryLower = opts.query?.toLowerCase();
2196
+ const sinceMs = opts.since?.getTime();
2197
+ const content = fs.readFileSync(this.messageLogPath, 'utf-8');
2198
+ const lines = content.split('\n').filter(Boolean);
2199
+ // Scan from end for efficiency (most queries want recent messages)
2200
+ const matches = [];
2201
+ for (let i = lines.length - 1; i >= 0 && matches.length < limit; i--) {
2202
+ try {
2203
+ const entry = JSON.parse(lines[i]);
2204
+ if (opts.topicId !== undefined && entry.topicId !== opts.topicId)
2205
+ continue;
2206
+ if (sinceMs && new Date(entry.timestamp).getTime() < sinceMs)
2207
+ continue;
2208
+ if (queryLower && !entry.text.toLowerCase().includes(queryLower))
2209
+ continue;
2210
+ matches.unshift(entry); // Maintain chronological order
2211
+ }
2212
+ catch { /* skip malformed */ }
2213
+ }
2214
+ return matches;
2215
+ }
2216
+ /**
2217
+ * Get message log statistics.
2218
+ */
2219
+ getLogStats() {
2220
+ if (!fs.existsSync(this.messageLogPath)) {
2221
+ return { totalMessages: 0, logSizeBytes: 0, logPath: this.messageLogPath };
2222
+ }
2223
+ const stat = fs.statSync(this.messageLogPath);
2224
+ const content = fs.readFileSync(this.messageLogPath, 'utf-8');
2225
+ const lineCount = content.split('\n').filter(Boolean).length;
2226
+ return { totalMessages: lineCount, logSizeBytes: stat.size, logPath: this.messageLogPath };
2227
+ }
2228
+ /**
2229
+ * Get recent messages for a topic (for thread history on respawn).
2230
+ */
2231
+ getTopicHistory(topicId, limit = 20) {
2232
+ if (!fs.existsSync(this.messageLogPath))
2233
+ return [];
2234
+ // Read the file to find matching entries.
2235
+ // Log rotation caps at 75,000 lines, so this is bounded.
2236
+ const content = fs.readFileSync(this.messageLogPath, 'utf-8');
2237
+ const lines = content.split('\n').filter(Boolean);
2238
+ // Scan from end to find matching entries (most recent first)
2239
+ const matching = [];
2240
+ for (let i = lines.length - 1; i >= 0 && matching.length < limit; i--) {
2241
+ try {
2242
+ const entry = JSON.parse(lines[i]);
2243
+ if (entry.topicId === topicId) {
2244
+ matching.unshift(entry); // Maintain chronological order
2245
+ }
2246
+ }
2247
+ catch { /* skip malformed */ }
2248
+ }
2249
+ return matching;
2250
+ }
2251
+ appendToLog(entry) {
2252
+ // Phase 1b: Delegate to shared MessageLogger when flag is enabled
2253
+ if (this.sharedLogger) {
2254
+ this.sharedLogger.append({
2255
+ messageId: entry.messageId,
2256
+ channelId: entry.topicId,
2257
+ text: entry.text,
2258
+ fromUser: entry.fromUser,
2259
+ timestamp: entry.timestamp,
2260
+ sessionName: entry.sessionName,
2261
+ senderName: entry.senderName,
2262
+ senderUsername: entry.senderUsername,
2263
+ platformUserId: entry.telegramUserId,
2264
+ platform: 'telegram',
2265
+ });
2266
+ // Also notify the Telegram-specific callback for backward compatibility
2267
+ if (this.onMessageLogged) {
2268
+ try {
2269
+ this.onMessageLogged(entry);
2270
+ }
2271
+ catch (err) {
2272
+ DegradationReporter.getInstance().report({
2273
+ feature: 'TopicMemory.dualWrite',
2274
+ primary: 'SQLite dual-write of messages for search and summaries',
2275
+ fallback: 'Message only in JSONL log (no search, no summary updates)',
2276
+ reason: `onMessageLogged callback failed: ${err instanceof Error ? err.message : String(err)}`,
2277
+ impact: 'Message may be missing from topic search and context summaries.',
2278
+ });
2279
+ }
2280
+ }
2281
+ // Phase 1e: Emit to event bus
2282
+ if (this.eventBus) {
2283
+ this.eventBus.emit('message:logged', {
2284
+ messageId: entry.messageId,
2285
+ channelId: entry.topicId?.toString() ?? '',
2286
+ text: entry.text,
2287
+ fromUser: entry.fromUser,
2288
+ timestamp: entry.timestamp,
2289
+ sessionName: entry.sessionName,
2290
+ senderName: entry.senderName,
2291
+ senderUsername: entry.senderUsername,
2292
+ platformUserId: entry.telegramUserId?.toString(),
2293
+ }).catch(err => console.error(`[telegram] EventBus message:logged error: ${err}`));
2294
+ }
2295
+ return;
2296
+ }
2297
+ // Legacy path (flag disabled)
2298
+ try {
2299
+ fs.appendFileSync(this.messageLogPath, JSON.stringify(entry) + '\n');
2300
+ this.maybeRotateLog();
2301
+ }
2302
+ catch (err) {
2303
+ console.error(`[telegram] Failed to append to message log: ${err}`);
2304
+ DegradationReporter.getInstance().report({
2305
+ feature: 'Telegram.messageLog',
2306
+ primary: 'JSONL message log for conversation history and recovery',
2307
+ fallback: 'Message lost from persistent log (only in memory)',
2308
+ reason: `Failed to write message log: ${err instanceof Error ? err.message : String(err)}`,
2309
+ impact: 'Conversation history gap — message may be missing from JSONL backup.',
2310
+ });
2311
+ }
2312
+ // Notify subscribers (TopicMemory for SQLite dual-write)
2313
+ if (this.onMessageLogged) {
2314
+ try {
2315
+ this.onMessageLogged(entry);
2316
+ }
2317
+ catch (err) {
2318
+ DegradationReporter.getInstance().report({
2319
+ feature: 'TopicMemory.dualWrite',
2320
+ primary: 'SQLite dual-write of messages for search and summaries',
2321
+ fallback: 'Message only in JSONL log (no search, no summary updates)',
2322
+ reason: `onMessageLogged callback failed: ${err instanceof Error ? err.message : String(err)}`,
2323
+ impact: 'Message may be missing from topic search and context summaries.',
2324
+ });
2325
+ }
2326
+ }
2327
+ // Phase 1e: Emit to event bus
2328
+ if (this.eventBus) {
2329
+ this.eventBus.emit('message:logged', {
2330
+ messageId: entry.messageId,
2331
+ channelId: entry.topicId?.toString() ?? '',
2332
+ text: entry.text,
2333
+ fromUser: entry.fromUser,
2334
+ timestamp: entry.timestamp,
2335
+ sessionName: entry.sessionName,
2336
+ senderName: entry.senderName,
2337
+ senderUsername: entry.senderUsername,
2338
+ platformUserId: entry.telegramUserId?.toString(),
2339
+ }).catch(err => console.error(`[telegram] EventBus message:logged error: ${err}`));
2340
+ }
2341
+ }
2342
+ /** Keep only the last 75,000 lines when log exceeds 100,000 lines.
2343
+ * High limits because message history is core agent memory.
2344
+ * At ~200 bytes/line average, 100k lines ~ 20MB — fine for a dedicated machine. */
2345
+ maybeRotateLog() {
2346
+ try {
2347
+ const stat = fs.statSync(this.messageLogPath);
2348
+ // Only check rotation when file exceeds ~20MB
2349
+ if (stat.size < 20 * 1024 * 1024)
2350
+ return;
2351
+ const content = fs.readFileSync(this.messageLogPath, 'utf-8');
2352
+ const lines = content.split('\n').filter(Boolean);
2353
+ if (lines.length > 100_000) {
2354
+ const kept = lines.slice(-75_000);
2355
+ const tmpPath = `${this.messageLogPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
2356
+ try {
2357
+ fs.writeFileSync(tmpPath, kept.join('\n') + '\n');
2358
+ fs.renameSync(tmpPath, this.messageLogPath);
2359
+ }
2360
+ catch (rotateErr) {
2361
+ try {
2362
+ fs.unlinkSync(tmpPath);
2363
+ }
2364
+ catch { /* ignore */ }
2365
+ throw rotateErr;
2366
+ }
2367
+ console.log(`[telegram] Rotated message log: ${lines.length} -> ${kept.length} lines`);
2368
+ }
2369
+ }
2370
+ catch {
2371
+ // @silent-fallback-ok — log rotation non-critical
2372
+ }
2373
+ }
2374
+ // ── Attention Queue ────────────────────────────────────────
2375
+ /**
2376
+ * Create an attention item and its Telegram topic.
2377
+ */
2378
+ async createAttentionItem(item) {
2379
+ // Check for existing
2380
+ if (this.attentionItems.has(item.id)) {
2381
+ return this.attentionItems.get(item.id);
2382
+ }
2383
+ const now = new Date().toISOString();
2384
+ const attention = {
2385
+ ...item,
2386
+ status: 'OPEN',
2387
+ createdAt: now,
2388
+ updatedAt: now,
2389
+ };
2390
+ // Create Telegram topic (uses the centralized method for forum detection)
2391
+ try {
2392
+ const emoji = PRIORITY_EMOJI[item.priority] || PRIORITY_EMOJI.NORMAL;
2393
+ const color = PRIORITY_COLOR[item.priority] || PRIORITY_COLOR.NORMAL;
2394
+ const topicTitle = `${emoji} ${item.title}`.slice(0, 128);
2395
+ const topic = await this.createForumTopic(topicTitle, color);
2396
+ const topicId = topic.topicId;
2397
+ attention.topicId = topicId;
2398
+ // Register mappings
2399
+ this.attentionItemToTopic.set(item.id, topicId);
2400
+ this.attentionTopicToItem.set(topicId, item.id);
2401
+ this.topicToName.set(topicId, item.title);
2402
+ // Registry already saved by createForumTopic
2403
+ // Post details as first message
2404
+ const detail = [
2405
+ `<b>${this.escapeHtml(item.category)}</b> | Priority: ${item.priority}`,
2406
+ ``,
2407
+ this.escapeHtml(item.summary),
2408
+ item.description ? `\n${this.escapeHtml(item.description.slice(0, 1000))}` : '',
2409
+ item.sourceContext ? `\n<i>Source: ${this.escapeHtml(item.sourceContext)}</i>` : '',
2410
+ ``,
2411
+ `Commands: /ack, /done, /wontdo, /reopen`,
2412
+ ].filter(Boolean).join('\n');
2413
+ // Send as HTML by calling API directly
2414
+ const sendParams = {
2415
+ chat_id: this.config.chatId,
2416
+ text: detail,
2417
+ parse_mode: 'HTML',
2418
+ };
2419
+ if (!isGeneralTopic(topicId))
2420
+ sendParams.message_thread_id = topicId;
2421
+ await this.apiCall('sendMessage', sendParams);
2422
+ }
2423
+ catch (err) {
2424
+ console.error(`[telegram] Failed to create attention topic for "${item.title}": ${err}`);
2425
+ DegradationReporter.getInstance().report({
2426
+ feature: 'TelegramAdapter.createAttentionItem',
2427
+ primary: 'Send attention/escalation notification',
2428
+ fallback: 'Attention item never delivered',
2429
+ reason: `Why: ${err instanceof Error ? err.message : String(err)}`,
2430
+ impact: 'User not notified of important escalation',
2431
+ });
2432
+ }
2433
+ this.attentionItems.set(item.id, attention);
2434
+ this.saveAttentionItems();
2435
+ return attention;
2436
+ }
2437
+ /**
2438
+ * Update attention item status. Called by /ack, /done, /wontdo, /reopen commands.
2439
+ */
2440
+ async updateAttentionStatus(itemId, status) {
2441
+ const item = this.attentionItems.get(itemId);
2442
+ if (!item)
2443
+ return false;
2444
+ item.status = status;
2445
+ item.updatedAt = new Date().toISOString();
2446
+ this.saveAttentionItems();
2447
+ const topicId = this.attentionItemToTopic.get(itemId);
2448
+ if (topicId) {
2449
+ const labels = {
2450
+ 'ACKNOWLEDGED': '\ud83d\udc40 Acknowledged',
2451
+ 'IN_PROGRESS': '\ud83d\udd28 In Progress',
2452
+ 'DONE': '\u2705 Done',
2453
+ 'WONT_DO': '\u23ed Won\'t Do',
2454
+ 'OPEN': '\ud83d\udccb Reopened',
2455
+ };
2456
+ await this.sendToTopic(topicId, `Status \u2192 ${labels[status] || status}`).catch(() => { });
2457
+ // Auto-close/reopen topic
2458
+ try {
2459
+ if (status === 'DONE' || status === 'WONT_DO') {
2460
+ await this.apiCall('closeForumTopic', { chat_id: this.config.chatId, message_thread_id: topicId });
2461
+ }
2462
+ else if (status === 'OPEN') {
2463
+ await this.apiCall('reopenForumTopic', { chat_id: this.config.chatId, message_thread_id: topicId });
2464
+ }
2465
+ }
2466
+ catch { /* topic operations may fail if already in desired state */ }
2467
+ }
2468
+ // Fire callback for external integrations
2469
+ if (this.onAttentionStatusChange) {
2470
+ await this.onAttentionStatusChange(itemId, status).catch(err => {
2471
+ console.error(`[telegram] Attention status callback failed: ${err}`);
2472
+ });
2473
+ }
2474
+ return true;
2475
+ }
2476
+ /**
2477
+ * Get all attention items, optionally filtered by status.
2478
+ */
2479
+ getAttentionItems(status) {
2480
+ const items = Array.from(this.attentionItems.values());
2481
+ if (status)
2482
+ return items.filter(i => i.status === status);
2483
+ return items;
2484
+ }
2485
+ /**
2486
+ * Get a specific attention item.
2487
+ */
2488
+ getAttentionItem(itemId) {
2489
+ return this.attentionItems.get(itemId);
2490
+ }
2491
+ /**
2492
+ * Check if a topic is an attention topic.
2493
+ */
2494
+ isAttentionTopic(topicId) {
2495
+ return this.attentionTopicToItem.has(topicId);
2496
+ }
2497
+ /**
2498
+ * Handle commands in attention topics (/ack, /done, /wontdo, /reopen).
2499
+ * Returns true if handled, false if not an attention command.
2500
+ */
2501
+ async handleAttentionCommand(topicId, text) {
2502
+ const itemId = this.attentionTopicToItem.get(topicId);
2503
+ if (!itemId)
2504
+ return false;
2505
+ const cmd = text.trim().toLowerCase();
2506
+ const statusMap = {
2507
+ '/ack': 'ACKNOWLEDGED',
2508
+ '/acknowledge': 'ACKNOWLEDGED',
2509
+ '/done': 'DONE',
2510
+ '/wontdo': 'WONT_DO',
2511
+ '/reopen': 'OPEN',
2512
+ };
2513
+ if (cmd in statusMap) {
2514
+ await this.updateAttentionStatus(itemId, statusMap[cmd]);
2515
+ return true;
2516
+ }
2517
+ return false;
2518
+ }
2519
+ loadAttentionItems() {
2520
+ try {
2521
+ if (!fs.existsSync(this.attentionFilePath))
2522
+ return;
2523
+ const data = JSON.parse(fs.readFileSync(this.attentionFilePath, 'utf-8'));
2524
+ if (data.items) {
2525
+ for (const item of data.items) {
2526
+ this.attentionItems.set(item.id, item);
2527
+ if (item.topicId) {
2528
+ this.attentionItemToTopic.set(item.id, item.topicId);
2529
+ this.attentionTopicToItem.set(item.topicId, item.id);
2530
+ }
2531
+ }
2532
+ console.log(`[telegram] Loaded ${this.attentionItems.size} attention items`);
2533
+ }
2534
+ }
2535
+ catch { /* file doesn't exist yet */ }
2536
+ }
2537
+ saveAttentionItems() {
2538
+ try {
2539
+ const dir = path.dirname(this.attentionFilePath);
2540
+ fs.mkdirSync(dir, { recursive: true });
2541
+ const data = { items: Array.from(this.attentionItems.values()) };
2542
+ const tmpPath = `${this.attentionFilePath}.${process.pid}.tmp`;
2543
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
2544
+ fs.renameSync(tmpPath, this.attentionFilePath);
2545
+ }
2546
+ catch (err) {
2547
+ console.error(`[telegram] Failed to save attention items: ${err}`);
2548
+ }
2549
+ }
2550
+ escapeHtml(text) {
2551
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2552
+ }
2553
+ // ── Registry Persistence ───────────────────────────────────
2554
+ loadRegistry() {
2555
+ try {
2556
+ const data = JSON.parse(fs.readFileSync(this.registryPath, 'utf-8'));
2557
+ if (data.topicToSession) {
2558
+ for (const [k, v] of Object.entries(data.topicToSession)) {
2559
+ this.topicToSession.set(Number(k), v);
2560
+ this.sessionToTopic.set(v, Number(k));
2561
+ }
2562
+ }
2563
+ if (data.topicToName) {
2564
+ for (const [k, v] of Object.entries(data.topicToName)) {
2565
+ this.topicToName.set(Number(k), v);
2566
+ }
2567
+ }
2568
+ if (data.topicToPurpose) {
2569
+ for (const [k, v] of Object.entries(data.topicToPurpose)) {
2570
+ this.topicToPurpose.set(Number(k), v);
2571
+ }
2572
+ }
2573
+ console.log(`[telegram] Loaded ${this.topicToSession.size} topic-session mappings from disk`);
2574
+ }
2575
+ catch {
2576
+ // File doesn't exist yet — start fresh
2577
+ }
2578
+ }
2579
+ saveRegistry() {
2580
+ try {
2581
+ const data = {
2582
+ topicToSession: Object.fromEntries(this.topicToSession),
2583
+ topicToName: Object.fromEntries(this.topicToName),
2584
+ topicToPurpose: Object.fromEntries(this.topicToPurpose),
2585
+ };
2586
+ // Atomic write: unique temp filename to prevent concurrent corruption
2587
+ const tmpPath = this.registryPath + `.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
2588
+ try {
2589
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
2590
+ fs.renameSync(tmpPath, this.registryPath);
2591
+ }
2592
+ catch (writeErr) {
2593
+ try {
2594
+ fs.unlinkSync(tmpPath);
2595
+ }
2596
+ catch { /* ignore */ }
2597
+ throw writeErr;
2598
+ }
2599
+ }
2600
+ catch (err) {
2601
+ console.error(`[telegram] Failed to save registry: ${err}`);
2602
+ }
2603
+ }
2604
+ // ── Polling Offset Persistence ────────────────────────────
2605
+ loadOffset() {
2606
+ try {
2607
+ const raw = fs.readFileSync(this.offsetPath, 'utf-8');
2608
+ const data = JSON.parse(raw);
2609
+ // Support both 'lastUpdateId' (canonical) and 'offset' (legacy/external)
2610
+ const candidate = data.lastUpdateId ?? data.offset;
2611
+ if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate > 0) {
2612
+ this.lastUpdateId = candidate;
2613
+ console.log(`[telegram] Restored poll offset: ${this.lastUpdateId}`);
2614
+ }
2615
+ else if (data.lastUpdateId !== undefined || data.offset !== undefined) {
2616
+ console.warn(`[telegram] Poll offset file has invalid value: ${raw.trim().substring(0, 100)}. Starting from 0.`);
2617
+ }
2618
+ }
2619
+ catch (err) {
2620
+ // Distinguish missing file from corrupted file
2621
+ if (err.code !== 'ENOENT') {
2622
+ console.warn(`[telegram] Poll offset file corrupted, starting from 0: ${err}`);
2623
+ }
2624
+ }
2625
+ }
2626
+ saveOffset() {
2627
+ try {
2628
+ const tmpPath = `${this.offsetPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
2629
+ try {
2630
+ fs.writeFileSync(tmpPath, JSON.stringify({ lastUpdateId: this.lastUpdateId }));
2631
+ fs.renameSync(tmpPath, this.offsetPath);
2632
+ }
2633
+ catch (writeErr) {
2634
+ try {
2635
+ fs.unlinkSync(tmpPath);
2636
+ }
2637
+ catch { /* ignore */ }
2638
+ throw writeErr;
2639
+ }
2640
+ }
2641
+ catch (err) {
2642
+ console.error(`[telegram] Failed to save poll offset: ${err}`);
2643
+ }
2644
+ }
2645
+ // ── Polling ────────────────────────────────────────────────
2646
+ async poll() {
2647
+ if (!this.polling)
2648
+ return;
2649
+ try {
2650
+ const updates = await this.getUpdates();
2651
+ this.consecutivePollErrors = 0; // Reset on success
2652
+ // Offset range sanity check: if received update_ids are significantly lower
2653
+ // than our stored offset, the offset is likely from a different bot token or
2654
+ // was corrupted during a migration. Auto-correct to prevent infinite replay.
2655
+ if (updates.length > 0 && this.lastUpdateId > 0) {
2656
+ const maxReceivedId = Math.max(...updates.map(u => u.update_id));
2657
+ const OFFSET_RANGE_THRESHOLD = 10_000_000; // 10M delta = cross-token corruption
2658
+ if (maxReceivedId < this.lastUpdateId - OFFSET_RANGE_THRESHOLD) {
2659
+ console.warn(`[telegram] Offset range mismatch: stored=${this.lastUpdateId}, ` +
2660
+ `received max=${maxReceivedId} (delta=${this.lastUpdateId - maxReceivedId}). ` +
2661
+ `Auto-correcting offset to prevent infinite replay loop.`);
2662
+ this.lastUpdateId = maxReceivedId;
2663
+ this.saveOffset();
2664
+ }
2665
+ }
2666
+ for (const update of updates) {
2667
+ await this.processUpdate(update);
2668
+ this.lastUpdateId = Math.max(this.lastUpdateId, update.update_id);
2669
+ // Save offset after each update so a crash mid-batch doesn't re-deliver
2670
+ // messages that were already processed (mirrors TelegramLifeline fix).
2671
+ this.saveOffset();
2672
+ }
2673
+ }
2674
+ catch (err) {
2675
+ this.consecutivePollErrors++;
2676
+ const errMsg = err instanceof Error ? err.message : String(err);
2677
+ // Check for fatal errors that require restart
2678
+ if (errMsg.includes('401') || errMsg.includes('Unauthorized')) {
2679
+ console.error(`[telegram] FATAL: Bot token is invalid. Stopping polling.`);
2680
+ this.polling = false;
2681
+ return;
2682
+ }
2683
+ // Exponential backoff on consecutive errors
2684
+ if (this.consecutivePollErrors > 1) {
2685
+ const backoffMs = Math.min(1000 * Math.pow(2, this.consecutivePollErrors - 1), 60_000);
2686
+ console.error(`[telegram] Poll error (attempt ${this.consecutivePollErrors}), backing off ${backoffMs}ms: ${errMsg}`);
2687
+ await new Promise(r => setTimeout(r, backoffMs));
2688
+ }
2689
+ else {
2690
+ console.error(`[telegram] Poll error: ${errMsg}`);
2691
+ }
2692
+ }
2693
+ // Schedule next poll
2694
+ const interval = this.config.pollIntervalMs ?? 2000;
2695
+ this.pollTimeout = setTimeout(() => this.poll(), interval);
2696
+ }
2697
+ /**
2698
+ * Process a single Telegram update (text, voice, photo, or callback query).
2699
+ */
2700
+ async processUpdate(update) {
2701
+ // Handle callback queries from inline keyboard buttons (Prompt Gate)
2702
+ if (update.callback_query) {
2703
+ await this.processCallbackQuery(update.callback_query);
2704
+ return;
2705
+ }
2706
+ const msg = update.message;
2707
+ if (!msg)
2708
+ return;
2709
+ // Auth gating — handle messages from unauthorized/unknown users
2710
+ if (!this.isAuthorized(msg.from.id)) {
2711
+ await this.handleUnknownUser(msg.from.id, msg.from.first_name, msg.from.username, msg.text);
2712
+ return;
2713
+ }
2714
+ const numericTopicId = msg.message_thread_id ?? GENERAL_TOPIC_ID;
2715
+ const topicId = numericTopicId.toString();
2716
+ // Auto-capture topic name from reply_to_message
2717
+ if (msg.reply_to_message?.forum_topic_created?.name) {
2718
+ const currentName = this.topicToName.get(numericTopicId);
2719
+ const realName = msg.reply_to_message.forum_topic_created.name;
2720
+ if (!currentName || /^topic-\d+$/.test(currentName)) {
2721
+ this.topicToName.set(numericTopicId, realName);
2722
+ this.saveRegistry();
2723
+ }
2724
+ }
2725
+ // Handle voice messages
2726
+ if (msg.voice) {
2727
+ await this.handleVoiceMessage(msg, numericTopicId);
2728
+ return;
2729
+ }
2730
+ // Handle photo messages
2731
+ if (msg.photo && msg.photo.length > 0) {
2732
+ await this.handlePhotoMessage(msg, numericTopicId);
2733
+ return;
2734
+ }
2735
+ // Handle document/file messages
2736
+ if (msg.document) {
2737
+ await this.handleDocumentMessage(msg, numericTopicId);
2738
+ return;
2739
+ }
2740
+ // Handle text messages
2741
+ if (!msg.text)
2742
+ return;
2743
+ const text = msg.text;
2744
+ // Check for commands first
2745
+ if (text.startsWith('/')) {
2746
+ const handled = await this.handleCommand(text, numericTopicId, msg.from.id);
2747
+ if (handled)
2748
+ return;
2749
+ }
2750
+ const message = {
2751
+ id: `tg-${msg.message_id}`,
2752
+ userId: msg.from.id.toString(),
2753
+ content: text,
2754
+ channel: { type: 'telegram', identifier: topicId },
2755
+ receivedAt: new Date(msg.date * 1000).toISOString(),
2756
+ metadata: {
2757
+ telegramUserId: msg.from.id,
2758
+ username: msg.from.username,
2759
+ firstName: msg.from.first_name,
2760
+ messageThreadId: numericTopicId,
2761
+ },
2762
+ };
2763
+ // Log the message (including sender identity for multi-user topics)
2764
+ this.appendToLog({
2765
+ messageId: msg.message_id,
2766
+ topicId: numericTopicId,
2767
+ text,
2768
+ fromUser: true,
2769
+ timestamp: new Date(msg.date * 1000).toISOString(),
2770
+ sessionName: this.topicToSession.get(numericTopicId) ?? null,
2771
+ senderName: msg.from.first_name,
2772
+ senderUsername: msg.from.username,
2773
+ telegramUserId: msg.from.id,
2774
+ });
2775
+ // Sentinel intercept — fires BEFORE routing to detect emergency stop/pause.
2776
+ // This runs in the server process, separate from the session, so it can
2777
+ // kill/pause the session even when the session is mid-tool-call.
2778
+ if (this.onSentinelIntercept) {
2779
+ try {
2780
+ const classification = await this.onSentinelIntercept(text, numericTopicId);
2781
+ if (classification && (classification.category === 'emergency-stop' || classification.category === 'pause')) {
2782
+ const sessionName = this.topicToSession.get(numericTopicId);
2783
+ if (classification.category === 'emergency-stop' && sessionName) {
2784
+ console.log(`[sentinel] Emergency stop for session "${sessionName}" in topic ${numericTopicId}`);
2785
+ if (this.onSentinelKillSession) {
2786
+ this.onSentinelKillSession(sessionName);
2787
+ }
2788
+ // Never include raw sentinel reasons in user-facing messages.
2789
+ // Log the full reason server-side, show only clean messages to users.
2790
+ if (classification.reason) {
2791
+ console.log(`[sentinel] Stop reason: ${classification.reason}`);
2792
+ }
2793
+ await this.sendToTopic(numericTopicId, `Session terminated.\n\nSend a new message to start a fresh session.`).catch(() => { });
2794
+ }
2795
+ else if (classification.category === 'pause' && sessionName) {
2796
+ console.log(`[sentinel] Pause for session "${sessionName}" in topic ${numericTopicId}`);
2797
+ if (this.onSentinelPauseSession) {
2798
+ this.onSentinelPauseSession(sessionName);
2799
+ }
2800
+ // Never include raw sentinel reasons in user-facing messages.
2801
+ if (classification.reason) {
2802
+ console.log(`[sentinel] Pause reason: ${classification.reason}`);
2803
+ }
2804
+ await this.sendToTopic(numericTopicId, `Session paused.\n\nSend a message to resume.`).catch(() => { });
2805
+ }
2806
+ else if (!sessionName) {
2807
+ // No active session — just acknowledge the stop/pause signal
2808
+ await this.sendToTopic(numericTopicId, `No active session to ${classification.category === 'emergency-stop' ? 'stop' : 'pause'}.`).catch(() => { });
2809
+ }
2810
+ return; // Don't route to session — sentinel handled it
2811
+ }
2812
+ }
2813
+ catch (err) {
2814
+ console.error(`[sentinel] Intercept error: ${err}`);
2815
+ // On sentinel error, fall through to normal routing (fail-open for message delivery)
2816
+ }
2817
+ }
2818
+ // Prompt Gate — intercept replies to relay messages (text-input prompts)
2819
+ if (this.pendingPromptReply.has(numericTopicId) && text) {
2820
+ const handled = this.handlePendingPromptReply(numericTopicId, msg);
2821
+ if (handled)
2822
+ return;
2823
+ }
2824
+ // Fire topic message callback (always fires — General topic falls back to ID 1)
2825
+ if (this.onTopicMessage) {
2826
+ try {
2827
+ this.onTopicMessage(message);
2828
+ }
2829
+ catch (err) {
2830
+ console.error(`[telegram] Topic message handler error: ${err}`);
2831
+ }
2832
+ }
2833
+ // Fire general handler
2834
+ if (this.handler) {
2835
+ try {
2836
+ await this.handler(message);
2837
+ }
2838
+ catch (err) {
2839
+ console.error(`[telegram] Handler error: ${err}`);
2840
+ }
2841
+ }
2842
+ }
2843
+ /**
2844
+ * Handle an incoming voice message: download, transcribe, route as text.
2845
+ */
2846
+ async handleVoiceMessage(msg, topicId) {
2847
+ const voice = msg.voice;
2848
+ // Download the voice file
2849
+ const voiceDir = path.join(this.stateDir, 'telegram-voice');
2850
+ fs.mkdirSync(voiceDir, { recursive: true });
2851
+ const filename = `voice-${Date.now()}-${msg.message_id}.ogg`;
2852
+ const filepath = path.join(voiceDir, filename);
2853
+ try {
2854
+ await this.downloadFile(voice.file_id, filepath);
2855
+ }
2856
+ catch (err) {
2857
+ console.error(`[telegram] Failed to download voice: ${err}`);
2858
+ await this.sendToTopic(topicId, `(Voice message received but download failed)`).catch(() => { });
2859
+ return;
2860
+ }
2861
+ // Transcribe
2862
+ try {
2863
+ const transcript = await this.transcribeVoice(filepath);
2864
+ console.log(`[telegram] Transcribed voice (${voice.duration}s): "${transcript.slice(0, 80)}"`);
2865
+ // Create a message with the transcription
2866
+ const message = {
2867
+ id: `tg-${msg.message_id}`,
2868
+ userId: msg.from.id.toString(),
2869
+ content: `[voice] ${transcript}`,
2870
+ channel: { type: 'telegram', identifier: topicId.toString() },
2871
+ receivedAt: new Date(msg.date * 1000).toISOString(),
2872
+ metadata: {
2873
+ telegramUserId: msg.from.id,
2874
+ username: msg.from.username,
2875
+ firstName: msg.from.first_name,
2876
+ messageThreadId: topicId,
2877
+ voiceFile: filepath,
2878
+ voiceDuration: voice.duration,
2879
+ },
2880
+ };
2881
+ // Log it (including sender identity for multi-user topics)
2882
+ this.appendToLog({
2883
+ messageId: msg.message_id,
2884
+ topicId,
2885
+ text: `[voice] ${transcript}`,
2886
+ fromUser: true,
2887
+ timestamp: new Date(msg.date * 1000).toISOString(),
2888
+ sessionName: this.topicToSession.get(topicId) ?? null,
2889
+ senderName: msg.from.first_name,
2890
+ senderUsername: msg.from.username,
2891
+ telegramUserId: msg.from.id,
2892
+ });
2893
+ // Fire callbacks
2894
+ if (this.onTopicMessage) {
2895
+ try {
2896
+ this.onTopicMessage(message);
2897
+ }
2898
+ catch (err) {
2899
+ console.error(`[telegram] Topic message handler error: ${err}`);
2900
+ }
2901
+ }
2902
+ if (this.handler) {
2903
+ try {
2904
+ await this.handler(message);
2905
+ }
2906
+ catch (err) {
2907
+ console.error(`[telegram] Handler error: ${err}`);
2908
+ }
2909
+ }
2910
+ }
2911
+ catch (err) {
2912
+ const errMsg = err instanceof Error ? err.message : String(err);
2913
+ const isNotConfigured = errMsg.includes('No voice transcription provider configured');
2914
+ const replyText = isNotConfigured
2915
+ ? '\ud83c\udfa4 Voice transcription is not configured. To enable it, set GROQ_API_KEY or OPENAI_API_KEY in your environment.'
2916
+ : `(Voice message received but transcription failed: ${errMsg})`;
2917
+ await this.sendToTopic(topicId, replyText).catch(() => { });
2918
+ }
2919
+ finally {
2920
+ // Clean up voice file after processing
2921
+ try {
2922
+ fs.unlinkSync(filepath);
2923
+ }
2924
+ catch { /* ignore */ }
2925
+ }
2926
+ }
2927
+ /**
2928
+ * Handle an incoming photo message: download, save, route with path.
2929
+ */
2930
+ async handlePhotoMessage(msg, topicId) {
2931
+ const photos = msg.photo;
2932
+ // Get highest resolution (last in array)
2933
+ const photo = photos[photos.length - 1];
2934
+ const caption = msg.caption || '';
2935
+ try {
2936
+ const filepath = await this.downloadPhoto(photo.file_id, msg.message_id);
2937
+ console.log(`[telegram] Downloaded photo: ${filepath}`);
2938
+ const content = caption
2939
+ ? `[image:${filepath}] ${caption}`
2940
+ : `[image:${filepath}]`;
2941
+ const message = {
2942
+ id: `tg-${msg.message_id}`,
2943
+ userId: msg.from.id.toString(),
2944
+ content,
2945
+ channel: { type: 'telegram', identifier: topicId.toString() },
2946
+ receivedAt: new Date(msg.date * 1000).toISOString(),
2947
+ metadata: {
2948
+ telegramUserId: msg.from.id,
2949
+ username: msg.from.username,
2950
+ firstName: msg.from.first_name,
2951
+ messageThreadId: topicId,
2952
+ photoPath: filepath,
2953
+ },
2954
+ };
2955
+ // Log it (including sender identity for multi-user topics)
2956
+ this.appendToLog({
2957
+ messageId: msg.message_id,
2958
+ topicId,
2959
+ text: content,
2960
+ fromUser: true,
2961
+ timestamp: new Date(msg.date * 1000).toISOString(),
2962
+ sessionName: this.topicToSession.get(topicId) ?? null,
2963
+ senderName: msg.from.first_name,
2964
+ senderUsername: msg.from.username,
2965
+ telegramUserId: msg.from.id,
2966
+ });
2967
+ // Fire callbacks
2968
+ if (this.onTopicMessage) {
2969
+ try {
2970
+ this.onTopicMessage(message);
2971
+ }
2972
+ catch (err) {
2973
+ console.error(`[telegram] Topic message handler error: ${err}`);
2974
+ }
2975
+ }
2976
+ if (this.handler) {
2977
+ try {
2978
+ await this.handler(message);
2979
+ }
2980
+ catch (err) {
2981
+ console.error(`[telegram] Handler error: ${err}`);
2982
+ }
2983
+ }
2984
+ }
2985
+ catch (err) {
2986
+ console.error(`[telegram] Failed to download photo: ${err}`);
2987
+ await this.sendToTopic(topicId, '(Photo received but I couldn\'t process it. Try sending it again.)').catch(() => { });
2988
+ }
2989
+ }
2990
+ /**
2991
+ * Handle an incoming document message: download, save, route with path.
2992
+ */
2993
+ async handleDocumentMessage(msg, topicId) {
2994
+ const doc = msg.document;
2995
+ const caption = msg.caption || '';
2996
+ try {
2997
+ const filepath = await this.downloadDocument(doc.file_id, msg.message_id, doc.file_name);
2998
+ console.log(`[telegram] Downloaded document: ${filepath}`);
2999
+ const content = caption
3000
+ ? `[document:${filepath}] ${caption}`
3001
+ : `[document:${filepath}]`;
3002
+ const message = {
3003
+ id: `tg-${msg.message_id}`,
3004
+ userId: msg.from.id.toString(),
3005
+ content,
3006
+ channel: { type: 'telegram', identifier: topicId.toString() },
3007
+ receivedAt: new Date(msg.date * 1000).toISOString(),
3008
+ metadata: {
3009
+ telegramUserId: msg.from.id,
3010
+ username: msg.from.username,
3011
+ firstName: msg.from.first_name,
3012
+ messageThreadId: topicId,
3013
+ documentPath: filepath,
3014
+ documentName: doc.file_name,
3015
+ documentMimeType: doc.mime_type,
3016
+ },
3017
+ };
3018
+ // Log it
3019
+ this.appendToLog({
3020
+ messageId: msg.message_id,
3021
+ topicId,
3022
+ text: content,
3023
+ fromUser: true,
3024
+ timestamp: new Date(msg.date * 1000).toISOString(),
3025
+ sessionName: this.topicToSession.get(topicId) ?? null,
3026
+ senderName: msg.from.first_name,
3027
+ senderUsername: msg.from.username,
3028
+ telegramUserId: msg.from.id,
3029
+ });
3030
+ // Fire callbacks
3031
+ if (this.onTopicMessage) {
3032
+ try {
3033
+ this.onTopicMessage(message);
3034
+ }
3035
+ catch (err) {
3036
+ console.error(`[telegram] Topic message handler error: ${err}`);
3037
+ }
3038
+ }
3039
+ if (this.handler) {
3040
+ try {
3041
+ await this.handler(message);
3042
+ }
3043
+ catch (err) {
3044
+ console.error(`[telegram] Handler error: ${err}`);
3045
+ }
3046
+ }
3047
+ }
3048
+ catch (err) {
3049
+ console.error(`[telegram] Failed to download document: ${err}`);
3050
+ await this.sendToTopic(topicId, '(Document received but I couldn\'t process it. Try sending it again.)').catch(() => { });
3051
+ }
3052
+ }
3053
+ // ── Prompt Gate: Telegram Relay ───────────────────────────────────
3054
+ /**
3055
+ * Relay a detected prompt to a Telegram topic with inline keyboard buttons.
3056
+ * For prompts with options: sends buttons. For questions: sends text asking for reply.
3057
+ * Returns the Telegram message ID of the relay message.
3058
+ */
3059
+ async relayPrompt(topicId, prompt) {
3060
+ // First-use disclosure (once per topic)
3061
+ if (!this.promptGateDisclosureSent.has(topicId)) {
3062
+ await this.sendToTopic(topicId, 'Prompt Gate is now active for this topic. Session prompts will appear here ' +
3063
+ 'for you to respond to. Note: prompt text is sent through Telegram\'s servers. ' +
3064
+ 'Avoid including credentials or sensitive data in your replies.').catch(() => { });
3065
+ this.promptGateDisclosureSent.add(topicId);
3066
+ }
3067
+ const text = this.formatPromptMessage(prompt);
3068
+ let result;
3069
+ if (prompt.options && prompt.options.length > 0) {
3070
+ // Add numbered options to the message body so full text is visible
3071
+ const optionLines = prompt.options.map((opt, i) => `${i + 1}. ${opt.label}`).join('\n');
3072
+ const fullText = `${text}\n\n${optionLines}`;
3073
+ // Build inline keyboard buttons with just the number/key (compact)
3074
+ const keyboard = prompt.options.map((opt, i) => {
3075
+ const token = this.callbackRegistry.register({
3076
+ sessionName: prompt.sessionName,
3077
+ promptId: prompt.id,
3078
+ key: opt.key,
3079
+ });
3080
+ return {
3081
+ text: String(i + 1),
3082
+ callback_data: JSON.stringify({ id: token }),
3083
+ };
3084
+ });
3085
+ // All buttons in a single row (they're just numbers now)
3086
+ result = await this.apiCall('sendMessage', {
3087
+ chat_id: this.config.chatId,
3088
+ message_thread_id: isGeneralTopic(topicId) ? undefined : topicId,
3089
+ text: fullText,
3090
+ reply_markup: { inline_keyboard: [keyboard] },
3091
+ parse_mode: 'Markdown',
3092
+ });
3093
+ }
3094
+ else {
3095
+ // No options — text reply expected (clarifying question)
3096
+ result = await this.apiCall('sendMessage', {
3097
+ chat_id: this.config.chatId,
3098
+ message_thread_id: isGeneralTopic(topicId) ? undefined : topicId,
3099
+ text,
3100
+ parse_mode: 'Markdown',
3101
+ });
3102
+ }
3103
+ // Track pending reply for text-input prompts
3104
+ this.pendingPromptReply.set(topicId, {
3105
+ prompt,
3106
+ relayMessageId: result.message_id,
3107
+ createdAt: Date.now(),
3108
+ });
3109
+ // Notify session manager to extend idle timeout (relay lease)
3110
+ if (this.onRelayLeaseStart) {
3111
+ this.onRelayLeaseStart(prompt.sessionName);
3112
+ }
3113
+ console.log(`[prompt-gate] Relayed ${prompt.type} prompt to topic ${topicId} (msg ${result.message_id})`);
3114
+ return result.message_id;
3115
+ }
3116
+ /**
3117
+ * Format a detected prompt into Telegram-friendly text.
3118
+ * Differentiates by prompt type and escapes Markdown special chars.
3119
+ */
3120
+ formatPromptMessage(prompt) {
3121
+ const escapedSummary = this.escapeMarkdown(prompt.summary);
3122
+ switch (prompt.type) {
3123
+ case 'permission':
3124
+ return `\u{23F3} *Your agent is waiting — approve or decline:*\n\n"${escapedSummary}"`;
3125
+ case 'plan':
3126
+ return `\u{23F3} *Agent plan ready — do you want to proceed?*\n\n"${escapedSummary}"`;
3127
+ case 'question':
3128
+ return `\u{23F3} *Your agent has a question:*\n\n"${escapedSummary}"\n\nReply to this message with your answer.`;
3129
+ case 'confirmation':
3130
+ return `\u{23F3} *Your agent needs confirmation:*\n\n"${escapedSummary}"`;
3131
+ case 'selection':
3132
+ return `\u{23F3} *Your agent needs you to choose:*\n\n"${escapedSummary}"`;
3133
+ default:
3134
+ return `\u{23F3} *Session needs your input:*\n\n"${escapedSummary}"`;
3135
+ }
3136
+ }
3137
+ /**
3138
+ * Escape Markdown special characters for Telegram.
3139
+ */
3140
+ escapeMarkdown(text) {
3141
+ return text
3142
+ .replace(/\\/g, '\\\\')
3143
+ .replace(/\*/g, '\\*')
3144
+ .replace(/_/g, '\\_')
3145
+ .replace(/`/g, '\\`')
3146
+ .replace(/\[/g, '\\[');
3147
+ }
3148
+ /**
3149
+ * Handle a forwarded callback query from the Lifeline process.
3150
+ * In send-only mode the server doesn't poll for callbacks, so the
3151
+ * Lifeline forwards them via /internal/telegram-callback.
3152
+ */
3153
+ async handleForwardedCallback(query) {
3154
+ await this.processCallbackQuery(query);
3155
+ }
3156
+ /**
3157
+ * Handle a Telegram callback query from an inline keyboard button press.
3158
+ */
3159
+ async processCallbackQuery(query) {
3160
+ if (!query.data) {
3161
+ await this.apiCall('answerCallbackQuery', {
3162
+ callback_query_id: query.id,
3163
+ text: 'Invalid button data',
3164
+ }).catch(() => { });
3165
+ return;
3166
+ }
3167
+ // Authorization check: verify sender is the configured owner
3168
+ const ownerId = this.config.promptGate?.ownerId;
3169
+ if (ownerId && query.from.id !== ownerId) {
3170
+ await this.apiCall('answerCallbackQuery', {
3171
+ callback_query_id: query.id,
3172
+ text: 'Only the session owner can respond to prompts',
3173
+ }).catch(() => { });
3174
+ // Do NOT resolve the token — preserve it for the real owner
3175
+ return;
3176
+ }
3177
+ // Parse callback data
3178
+ let tokenId;
3179
+ try {
3180
+ const data = JSON.parse(query.data);
3181
+ tokenId = data.id;
3182
+ if (!tokenId || typeof tokenId !== 'string')
3183
+ throw new Error('missing id');
3184
+ }
3185
+ catch {
3186
+ await this.apiCall('answerCallbackQuery', {
3187
+ callback_query_id: query.id,
3188
+ text: 'Invalid callback data',
3189
+ }).catch(() => { });
3190
+ return;
3191
+ }
3192
+ // Resolve the token
3193
+ const context = this.callbackRegistry.resolve(tokenId);
3194
+ if (!context) {
3195
+ // Stale button (server restarted, or entry pruned)
3196
+ await this.apiCall('answerCallbackQuery', {
3197
+ callback_query_id: query.id,
3198
+ text: 'Session expired \u2014 check the dashboard',
3199
+ }).catch(() => { });
3200
+ if (query.message) {
3201
+ await this.editMessageWithRetry(query.message.message_id, '\u274C Session expired before response received');
3202
+ }
3203
+ return;
3204
+ }
3205
+ // Validate the key against the allowlist
3206
+ if (!isAllowedButtonKey(context.key)) {
3207
+ console.warn(`[prompt-gate] Rejected non-allowlisted button key: ${context.key}`);
3208
+ await this.apiCall('answerCallbackQuery', {
3209
+ callback_query_id: query.id,
3210
+ text: 'Invalid response key',
3211
+ }).catch(() => { });
3212
+ return;
3213
+ }
3214
+ // Answer the callback (removes loading spinner on button)
3215
+ await this.apiCall('answerCallbackQuery', {
3216
+ callback_query_id: query.id,
3217
+ text: 'Sent to session',
3218
+ }).catch(() => { });
3219
+ // Update the message to show which option was chosen
3220
+ if (query.message) {
3221
+ await this.editMessageWithRetry(query.message.message_id, `\u2705 Responded: ${context.key}`);
3222
+ }
3223
+ // Clear pending reply for this topic (if any)
3224
+ const topicId = query.message?.message_thread_id;
3225
+ if (topicId) {
3226
+ this.pendingPromptReply.delete(topicId);
3227
+ }
3228
+ // Release relay lease
3229
+ if (this.onRelayLeaseEnd) {
3230
+ this.onRelayLeaseEnd(context.sessionName);
3231
+ }
3232
+ // Inject the response into the session
3233
+ if (this.onPromptResponse) {
3234
+ const sent = this.onPromptResponse(context.sessionName, context.key);
3235
+ if (!sent) {
3236
+ console.warn(`[prompt-gate] Failed to send key "${context.key}" to session "${context.sessionName}"`);
3237
+ }
3238
+ }
3239
+ else {
3240
+ console.warn(`[prompt-gate] No onPromptResponse handler — cannot inject response`);
3241
+ }
3242
+ console.log(`[prompt-gate] Callback resolved: session="${context.sessionName}" key="${context.key}"`);
3243
+ }
3244
+ /**
3245
+ * Handle a text reply to a Prompt Gate relay message (for text-input prompts).
3246
+ * Returns true if the message was intercepted, false to fall through to normal routing.
3247
+ */
3248
+ handlePendingPromptReply(topicId, msg) {
3249
+ const pending = this.pendingPromptReply.get(topicId);
3250
+ if (!pending)
3251
+ return false;
3252
+ // Check timeout (2x relay timeout)
3253
+ const relayTimeoutMs = (this.config.promptGate?.relayTimeoutSeconds ?? 300) * 2000;
3254
+ if (Date.now() - pending.createdAt > relayTimeoutMs) {
3255
+ this.pendingPromptReply.delete(topicId);
3256
+ // Release relay lease
3257
+ if (this.onRelayLeaseEnd) {
3258
+ this.onRelayLeaseEnd(pending.prompt.sessionName);
3259
+ }
3260
+ return false; // Expired — fall through to normal routing
3261
+ }
3262
+ // Reject forwarded messages — prevents forwarding attack
3263
+ if (msg.forward_origin || msg.forward_from || msg.forward_date) {
3264
+ return false; // Forwarded message — reject silently
3265
+ }
3266
+ // Verify sender is the authorized owner
3267
+ const ownerId = this.config.promptGate?.ownerId;
3268
+ if (ownerId && msg.from.id !== ownerId) {
3269
+ return false; // Not the owner — fall through to normal routing
3270
+ }
3271
+ // Verify this is a reply-to the relay message
3272
+ if (msg.reply_to_message?.message_id !== pending.relayMessageId) {
3273
+ return false; // Not replying to the prompt — fall through
3274
+ }
3275
+ // Intercept the reply
3276
+ this.pendingPromptReply.delete(topicId);
3277
+ // Release relay lease
3278
+ if (this.onRelayLeaseEnd) {
3279
+ this.onRelayLeaseEnd(pending.prompt.sessionName);
3280
+ }
3281
+ // Sanitize the input
3282
+ const sanitized = sanitizeForPrompt(msg.text ?? '', 512);
3283
+ if (!sanitized) {
3284
+ console.warn(`[prompt-gate] Empty text reply after sanitization, topic ${topicId}`);
3285
+ return true; // Still consume the message — don't route it as a new message
3286
+ }
3287
+ // Inject the text into the session
3288
+ if (this.onPromptTextResponse) {
3289
+ const sent = this.onPromptTextResponse(pending.prompt.sessionName, sanitized);
3290
+ if (!sent) {
3291
+ console.warn(`[prompt-gate] Failed to send text to session "${pending.prompt.sessionName}"`);
3292
+ }
3293
+ }
3294
+ else {
3295
+ console.warn(`[prompt-gate] No onPromptTextResponse handler — cannot inject text`);
3296
+ }
3297
+ // Update the relay message to show it was answered
3298
+ this.editMessageWithRetry(pending.relayMessageId, `\u2705 Answered: "${sanitized.slice(0, 100)}${sanitized.length > 100 ? '...' : ''}"`).catch(() => { });
3299
+ console.log(`[prompt-gate] Text reply intercepted: session="${pending.prompt.sessionName}" text="${sanitized.slice(0, 50)}"`);
3300
+ return true;
3301
+ }
3302
+ /**
3303
+ * Edit a Telegram message with retry on failure.
3304
+ * Uses exponential backoff (1s, 2s, 4s) for up to 3 attempts.
3305
+ */
3306
+ async editMessageWithRetry(messageId, text, retries = 3) {
3307
+ for (let attempt = 0; attempt < retries; attempt++) {
3308
+ try {
3309
+ await this.apiCall('editMessageText', {
3310
+ chat_id: this.config.chatId,
3311
+ message_id: messageId,
3312
+ text,
3313
+ });
3314
+ return;
3315
+ }
3316
+ catch (err) {
3317
+ if (attempt < retries - 1) {
3318
+ await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
3319
+ }
3320
+ else {
3321
+ console.warn(`[prompt-gate] Failed to edit message ${messageId} after ${retries} attempts: ${err}`);
3322
+ }
3323
+ }
3324
+ }
3325
+ }
3326
+ /**
3327
+ * Clean up Prompt Gate state for a session (call when session ends).
3328
+ */
3329
+ cleanupPromptGate(sessionName) {
3330
+ // Remove all callback registry entries for this session
3331
+ this.callbackRegistry.removeForSession(sessionName);
3332
+ // Clear any pending replies for topics bound to this session
3333
+ for (const [topicId, pending] of this.pendingPromptReply) {
3334
+ if (pending.prompt.sessionName === sessionName) {
3335
+ this.pendingPromptReply.delete(topicId);
3336
+ // Update the relay message
3337
+ this.editMessageWithRetry(pending.relayMessageId, '\u274C Session ended before response received').catch(() => { });
3338
+ }
3339
+ }
3340
+ }
3341
+ /**
3342
+ * Proactively prune expired relay prompts and send timeout messages.
3343
+ * Call periodically (e.g. every 60s) to handle cases where no new message
3344
+ * arrives to trigger the expiry check in handlePendingPromptReply.
3345
+ */
3346
+ async pruneExpiredRelays() {
3347
+ const relayTimeoutMs = (this.config.promptGate?.relayTimeoutSeconds ?? 300) * 1000;
3348
+ const reminderMs = relayTimeoutMs; // Send reminder at 1x timeout
3349
+ const expiryMs = relayTimeoutMs * 2; // Expire at 2x timeout
3350
+ const now = Date.now();
3351
+ for (const [topicId, pending] of this.pendingPromptReply) {
3352
+ const age = now - pending.createdAt;
3353
+ if (age > expiryMs) {
3354
+ // Expired — update message and clean up
3355
+ this.pendingPromptReply.delete(topicId);
3356
+ this.editMessageWithRetry(pending.relayMessageId, '\u23f0 Prompt expired — no response received. Check the dashboard to respond manually.').catch(() => { });
3357
+ // Release relay lease
3358
+ if (this.onRelayLeaseEnd) {
3359
+ this.onRelayLeaseEnd(pending.prompt.sessionName);
3360
+ }
3361
+ // Clean up callback registry entries for this prompt
3362
+ this.callbackRegistry.removeForSession(pending.prompt.sessionName);
3363
+ console.log(`[prompt-gate] Relay expired for topic ${topicId} (${Math.round(age / 1000)}s)`);
3364
+ }
3365
+ else if (age > reminderMs && !pending.reminderSent) {
3366
+ // Send reminder
3367
+ await this.sendToTopic(topicId, '\u23f3 Still waiting for your response on the prompt above.').catch(() => { });
3368
+ pending.reminderSent = true;
3369
+ }
3370
+ }
3371
+ }
3372
+ /**
3373
+ * Stop the callback registry (call on adapter shutdown).
3374
+ */
3375
+ stopPromptGate() {
3376
+ this.callbackRegistry.stop();
3377
+ }
3378
+ async getUpdates() {
3379
+ const result = await this.apiCall('getUpdates', {
3380
+ offset: this.lastUpdateId + 1,
3381
+ timeout: 30,
3382
+ allowed_updates: ['message', 'callback_query'],
3383
+ });
3384
+ return result ?? [];
3385
+ }
3386
+ async apiCall(method, params, retryCount = 0) {
3387
+ const url = `https://api.telegram.org/bot${this.config.token}/${method}`;
3388
+ const safeUrl = `https://api.telegram.org/bot[REDACTED]/${method}`;
3389
+ // Long polling uses 30s timeout in params — give extra headroom
3390
+ const timeoutMs = method === 'getUpdates' ? 60_000 : 15_000;
3391
+ const controller = new AbortController();
3392
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3393
+ let response;
3394
+ try {
3395
+ response = await fetch(url, {
3396
+ method: 'POST',
3397
+ headers: { 'Content-Type': 'application/json' },
3398
+ body: JSON.stringify(params),
3399
+ signal: controller.signal,
3400
+ });
3401
+ }
3402
+ finally {
3403
+ clearTimeout(timer);
3404
+ }
3405
+ if (!response.ok) {
3406
+ // Handle 429 Too Many Requests — respect Telegram's retry_after
3407
+ if (response.status === 429) {
3408
+ if (retryCount >= 3) {
3409
+ throw new Error(`Telegram API rate limited ${safeUrl} (429) after ${retryCount} retries`);
3410
+ }
3411
+ try {
3412
+ const errorData = await response.json();
3413
+ const retryAfter = errorData?.parameters?.retry_after ?? 5;
3414
+ console.warn(`[telegram] Rate limited on ${method}, waiting ${retryAfter}s (retry ${retryCount + 1}/3)...`);
3415
+ await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
3416
+ return this.apiCall(method, params, retryCount + 1);
3417
+ }
3418
+ catch (retryErr) {
3419
+ if (retryErr instanceof Error && retryErr.message.includes('after'))
3420
+ throw retryErr;
3421
+ throw new Error(`Telegram API rate limited ${safeUrl} (429)`);
3422
+ }
3423
+ }
3424
+ const text = await response.text();
3425
+ throw new Error(`Telegram API error ${safeUrl} (${response.status}): ${text}`);
3426
+ }
3427
+ const data = await response.json();
3428
+ if (!data.ok) {
3429
+ throw new Error(`Telegram API returned not ok: ${JSON.stringify(data)}`);
3430
+ }
3431
+ return data.result;
3432
+ }
3433
+ }
3434
+ //# sourceMappingURL=TelegramAdapter.js.map