gsd-pi 2.18.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
  9. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  10. package/dist/resources/extensions/gsd/auto.ts +690 -39
  11. package/dist/resources/extensions/gsd/captures.ts +384 -0
  12. package/dist/resources/extensions/gsd/commands.ts +654 -36
  13. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  14. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  15. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  16. package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  17. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  18. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  19. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  20. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  22. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  23. package/dist/resources/extensions/gsd/export.ts +81 -2
  24. package/dist/resources/extensions/gsd/files.ts +39 -9
  25. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  26. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  27. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  28. package/dist/resources/extensions/gsd/history.ts +0 -1
  29. package/dist/resources/extensions/gsd/index.ts +277 -1
  30. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  31. package/dist/resources/extensions/gsd/metrics.ts +84 -0
  32. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  33. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  34. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  35. package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  36. package/dist/resources/extensions/gsd/preferences.ts +198 -150
  37. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  39. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  41. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  42. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  43. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  44. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  45. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  46. package/dist/resources/extensions/gsd/quick.ts +156 -0
  47. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  48. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  49. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  50. package/dist/resources/extensions/gsd/state.ts +30 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  52. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  53. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  54. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  57. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  58. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  59. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  60. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  62. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  63. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  64. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  65. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  66. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  67. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  68. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  69. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  70. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  71. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  72. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  73. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  74. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  75. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  76. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  77. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  78. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  79. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  80. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  81. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  82. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  83. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  84. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  85. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  86. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  87. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  88. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  89. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  90. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  91. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  92. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  93. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  94. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  95. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  96. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  97. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  98. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  99. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  100. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  101. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  102. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  103. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  104. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  105. package/dist/resources/extensions/gsd/types.ts +29 -0
  106. package/dist/resources/extensions/gsd/undo.ts +0 -1
  107. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  108. package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
  109. package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  110. package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
  111. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  112. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  113. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  114. package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  115. package/dist/resources/extensions/remote-questions/format.ts +166 -14
  116. package/dist/resources/extensions/remote-questions/manager.ts +14 -4
  117. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  118. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  119. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  120. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  121. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  122. package/dist/resources/extensions/voice/index.ts +4 -3
  123. package/package.json +1 -1
  124. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  131. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  137. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  139. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  140. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  142. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  146. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  148. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  150. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  153. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  156. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  161. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  162. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  163. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  164. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  165. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  166. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  167. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  168. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  169. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  170. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  171. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  172. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  173. package/src/resources/extensions/google-search/index.ts +164 -47
  174. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  175. package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
  176. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  177. package/src/resources/extensions/gsd/auto.ts +690 -39
  178. package/src/resources/extensions/gsd/captures.ts +384 -0
  179. package/src/resources/extensions/gsd/commands.ts +654 -36
  180. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  181. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  182. package/src/resources/extensions/gsd/context-store.ts +195 -0
  183. package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  184. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  185. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  186. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  187. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  188. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  189. package/src/resources/extensions/gsd/doctor.ts +283 -2
  190. package/src/resources/extensions/gsd/export.ts +81 -2
  191. package/src/resources/extensions/gsd/files.ts +39 -9
  192. package/src/resources/extensions/gsd/git-service.ts +6 -0
  193. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  194. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  195. package/src/resources/extensions/gsd/history.ts +0 -1
  196. package/src/resources/extensions/gsd/index.ts +277 -1
  197. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  198. package/src/resources/extensions/gsd/metrics.ts +84 -0
  199. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  200. package/src/resources/extensions/gsd/model-router.ts +256 -0
  201. package/src/resources/extensions/gsd/notifications.ts +0 -1
  202. package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  203. package/src/resources/extensions/gsd/preferences.ts +198 -150
  204. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  206. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  207. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  208. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  211. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  212. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  213. package/src/resources/extensions/gsd/quick.ts +156 -0
  214. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  215. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  216. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  217. package/src/resources/extensions/gsd/state.ts +30 -0
  218. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  219. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  220. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  221. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  222. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  224. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  226. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  227. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  228. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  229. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  230. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  231. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  232. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  233. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  235. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  236. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  237. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  238. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  239. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  240. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  241. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  242. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  243. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  245. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  246. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  247. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  248. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  249. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  250. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  251. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  253. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  254. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  255. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  256. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  257. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  258. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  259. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  260. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  261. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  262. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  263. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  264. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  265. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  266. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  267. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  268. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  269. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  270. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  271. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  272. package/src/resources/extensions/gsd/types.ts +29 -0
  273. package/src/resources/extensions/gsd/undo.ts +0 -1
  274. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  275. package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
  276. package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  277. package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
  278. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  279. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  280. package/src/resources/extensions/remote-questions/config.ts +4 -2
  281. package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  282. package/src/resources/extensions/remote-questions/format.ts +166 -14
  283. package/src/resources/extensions/remote-questions/manager.ts +14 -4
  284. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  285. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  286. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  287. package/src/resources/extensions/remote-questions/types.ts +2 -1
  288. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  289. package/src/resources/extensions/voice/index.ts +4 -3
@@ -3,10 +3,11 @@
3
3
  */
4
4
 
5
5
  import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, RemotePromptRef } from "./types.js";
6
- import { formatForSlack, parseSlackReply } from "./format.js";
6
+ import { formatForSlack, parseSlackReply, parseSlackReactionResponse, SLACK_NUMBER_REACTION_NAMES } from "./format.js";
7
7
 
8
8
  const SLACK_API = "https://slack.com/api";
9
9
  const PER_REQUEST_TIMEOUT_MS = 15_000;
10
+ const SLACK_ACK_REACTION = "white_check_mark";
10
11
 
11
12
  export class SlackAdapter implements ChannelAdapter {
12
13
  readonly name = "slack" as const;
@@ -36,6 +37,17 @@ export class SlackAdapter implements ChannelAdapter {
36
37
 
37
38
  const ts = String(res.ts);
38
39
  const channel = String(res.channel);
40
+ if (prompt.questions.length === 1) {
41
+ const reactionNames = SLACK_NUMBER_REACTION_NAMES.slice(0, prompt.questions[0].options.length);
42
+ for (const name of reactionNames) {
43
+ try {
44
+ await this.slackApi("reactions.add", { channel, timestamp: ts, name });
45
+ } catch {
46
+ // Best-effort only
47
+ }
48
+ }
49
+ }
50
+
39
51
  return {
40
52
  ref: {
41
53
  id: prompt.id,
@@ -51,6 +63,11 @@ export class SlackAdapter implements ChannelAdapter {
51
63
  async pollAnswer(prompt: RemotePrompt, ref: RemotePromptRef): Promise<RemoteAnswer | null> {
52
64
  if (!this.botUserId) await this.validate();
53
65
 
66
+ if (prompt.questions.length === 1) {
67
+ const reactionAnswer = await this.checkReactions(prompt, ref);
68
+ if (reactionAnswer) return reactionAnswer;
69
+ }
70
+
54
71
  const res = await this.slackApi("conversations.replies", {
55
72
  channel: ref.channelId,
56
73
  ts: ref.threadTs!,
@@ -66,9 +83,48 @@ export class SlackAdapter implements ChannelAdapter {
66
83
  return parseSlackReply(String(userReplies[0].text), prompt.questions);
67
84
  }
68
85
 
86
+ async acknowledgeAnswer(ref: RemotePromptRef): Promise<void> {
87
+ try {
88
+ await this.slackApi("reactions.add", {
89
+ channel: ref.channelId,
90
+ timestamp: ref.messageId,
91
+ name: SLACK_ACK_REACTION,
92
+ });
93
+ } catch {
94
+ // Best-effort only
95
+ }
96
+ }
97
+
98
+ private async checkReactions(prompt: RemotePrompt, ref: RemotePromptRef): Promise<RemoteAnswer | null> {
99
+ const res = await this.slackApi("reactions.get", {
100
+ channel: ref.channelId,
101
+ timestamp: ref.messageId,
102
+ full: "true",
103
+ });
104
+
105
+ if (!res.ok) return null;
106
+
107
+ const message = (res.message ?? {}) as {
108
+ reactions?: Array<{ name?: string; count?: number; users?: string[] }>;
109
+ };
110
+ const reactions = Array.isArray(message.reactions) ? message.reactions : [];
111
+ const picked = reactions
112
+ .filter((reaction) => reaction.name && SLACK_NUMBER_REACTION_NAMES.includes(reaction.name))
113
+ .filter((reaction) => {
114
+ const count = Number(reaction.count ?? 0);
115
+ const users = Array.isArray(reaction.users) ? reaction.users.map(String) : [];
116
+ const botIncluded = this.botUserId ? users.includes(this.botUserId) : false;
117
+ return count > (botIncluded ? 1 : 0);
118
+ })
119
+ .map((reaction) => String(reaction.name));
120
+
121
+ if (picked.length === 0) return null;
122
+ return parseSlackReactionResponse(picked, prompt.questions);
123
+ }
124
+
69
125
  private async slackApi(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>> {
70
126
  const url = `${SLACK_API}/${method}`;
71
- const isGet = method === "conversations.replies" || method === "auth.test";
127
+ const isGet = method === "conversations.replies" || method === "auth.test" || method === "reactions.get";
72
128
 
73
129
  let response: Response;
74
130
  if (isGet) {
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Remote Questions — Telegram adapter
3
+ */
4
+
5
+ import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, RemotePromptRef } from "./types.js";
6
+ import { formatForTelegram, parseTelegramResponse } from "./format.js";
7
+
8
+ const TELEGRAM_API = "https://api.telegram.org";
9
+ const PER_REQUEST_TIMEOUT_MS = 15_000;
10
+
11
+ export class TelegramAdapter implements ChannelAdapter {
12
+ readonly name = "telegram" as const;
13
+ private botUserId: number | null = null;
14
+ private lastUpdateId = 0;
15
+ private lastSentText = "";
16
+ private readonly token: string;
17
+ private readonly chatId: string;
18
+
19
+ constructor(token: string, chatId: string) {
20
+ this.token = token;
21
+ this.chatId = chatId;
22
+ }
23
+
24
+ async validate(): Promise<void> {
25
+ const res = await this.telegramApi("getMe");
26
+ if (!res.ok || !res.result?.id) throw new Error("Telegram auth failed: invalid bot token");
27
+ this.botUserId = res.result.id;
28
+ }
29
+
30
+ async sendPrompt(prompt: RemotePrompt): Promise<RemoteDispatchResult> {
31
+ const payload = formatForTelegram(prompt);
32
+ this.lastSentText = payload.text;
33
+
34
+ const params: Record<string, unknown> = {
35
+ chat_id: this.chatId,
36
+ text: payload.text,
37
+ parse_mode: payload.parse_mode,
38
+ };
39
+ if (payload.reply_markup) {
40
+ params.reply_markup = payload.reply_markup;
41
+ }
42
+
43
+ const res = await this.telegramApi("sendMessage", params);
44
+ if (!res.ok || !res.result?.message_id) {
45
+ throw new Error(`Telegram sendMessage failed: ${JSON.stringify(res)}`);
46
+ }
47
+
48
+ const messageId = String(res.result.message_id);
49
+ const messageUrl = this.buildMessageUrl(this.chatId, messageId);
50
+
51
+ return {
52
+ ref: {
53
+ id: prompt.id,
54
+ channel: "telegram",
55
+ messageId,
56
+ channelId: this.chatId,
57
+ threadUrl: messageUrl,
58
+ },
59
+ };
60
+ }
61
+
62
+ async pollAnswer(prompt: RemotePrompt, ref: RemotePromptRef): Promise<RemoteAnswer | null> {
63
+ if (!this.botUserId) await this.validate();
64
+
65
+ const res = await this.telegramApi("getUpdates", {
66
+ offset: this.lastUpdateId + 1,
67
+ timeout: 0,
68
+ allowed_updates: ["message", "callback_query"],
69
+ });
70
+
71
+ if (!res.ok || !Array.isArray(res.result)) return null;
72
+
73
+ for (const update of res.result) {
74
+ // Advance offset for all updates to prevent reprocessing
75
+ if (update.update_id > this.lastUpdateId) {
76
+ this.lastUpdateId = update.update_id;
77
+ }
78
+
79
+ // Handle callback_query (inline keyboard button press)
80
+ if (update.callback_query) {
81
+ const cq = update.callback_query;
82
+ const msg = cq.message;
83
+ if (
84
+ msg &&
85
+ String(msg.chat?.id) === ref.channelId &&
86
+ String(msg.message_id) === ref.messageId &&
87
+ cq.from?.id !== this.botUserId
88
+ ) {
89
+ // Dismiss the loading spinner on the button
90
+ try {
91
+ await this.telegramApi("answerCallbackQuery", { callback_query_id: cq.id });
92
+ } catch { /* best-effort */ }
93
+
94
+ return parseTelegramResponse(cq.data ?? null, null, prompt.questions, prompt.id);
95
+ }
96
+ }
97
+
98
+ // Handle text reply (reply_to_message)
99
+ if (update.message) {
100
+ const msg = update.message;
101
+ if (
102
+ String(msg.chat?.id) === ref.channelId &&
103
+ msg.reply_to_message &&
104
+ String(msg.reply_to_message.message_id) === ref.messageId &&
105
+ msg.from?.id !== this.botUserId &&
106
+ msg.text
107
+ ) {
108
+ return parseTelegramResponse(null, msg.text, prompt.questions, prompt.id);
109
+ }
110
+ }
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Acknowledge receipt by editing the original message to append a checkmark.
118
+ * Best-effort — failures are silently ignored.
119
+ */
120
+ async acknowledgeAnswer(ref: RemotePromptRef): Promise<void> {
121
+ try {
122
+ await this.telegramApi("editMessageText", {
123
+ chat_id: ref.channelId,
124
+ message_id: parseInt(ref.messageId, 10),
125
+ text: this.lastSentText + "\n\n✅ Answered",
126
+ parse_mode: "HTML",
127
+ });
128
+ } catch {
129
+ // Best-effort — don't let acknowledgement failures affect the flow
130
+ }
131
+ }
132
+
133
+ private buildMessageUrl(chatId: string, messageId: string): string | undefined {
134
+ // Supergroups have chat IDs starting with -100
135
+ if (chatId.startsWith("-100")) {
136
+ return `https://t.me/c/${chatId.slice(4)}/${messageId}`;
137
+ }
138
+ return undefined;
139
+ }
140
+
141
+ private async telegramApi(method: string, params?: Record<string, unknown>): Promise<any> {
142
+ const url = `${TELEGRAM_API}/bot${this.token}/${method}`;
143
+ const init: RequestInit = {
144
+ method: "POST",
145
+ headers: { "Content-Type": "application/json" },
146
+ signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS),
147
+ };
148
+
149
+ if (params) {
150
+ init.body = JSON.stringify(params);
151
+ }
152
+
153
+ const response = await fetch(url, init);
154
+ if (!response.ok) {
155
+ const text = await response.text().catch(() => "");
156
+ const safeText = text.length > 200 ? text.slice(0, 200) + "…" : text;
157
+ throw new Error(`Telegram API HTTP ${response.status}: ${safeText}`);
158
+ }
159
+ return response.json();
160
+ }
161
+ }
@@ -2,7 +2,7 @@
2
2
  * Remote Questions — shared types
3
3
  */
4
4
 
5
- export type RemoteChannel = "slack" | "discord";
5
+ export type RemoteChannel = "slack" | "discord" | "telegram";
6
6
 
7
7
  export interface RemoteQuestionOption {
8
8
  label: string;
@@ -72,4 +72,5 @@ export interface ChannelAdapter {
72
72
  validate(): Promise<void>;
73
73
  sendPrompt(prompt: RemotePrompt): Promise<RemoteDispatchResult>;
74
74
  pollAnswer(prompt: RemotePrompt, ref: RemotePromptRef): Promise<RemoteAnswer | null>;
75
+ acknowledgeAnswer?(ref: RemotePromptRef): Promise<void>;
75
76
  }
@@ -10,6 +10,7 @@
10
10
  * per-rule JS RegExp iteration when the native module is not loaded.
11
11
  */
12
12
  import picomatch from "picomatch";
13
+ import { debugTime, debugCount, debugPeak } from "../gsd/debug-logger.js";
13
14
 
14
15
  // ── Native TTSR engine (optional) ─────────────────────────────────────
15
16
  let nativeTtsr: {
@@ -98,6 +99,12 @@ const DEFAULT_SETTINGS: Required<TtsrSettings> = {
98
99
  /** Cap per-stream buffer at 512KB to prevent unbounded memory growth. */
99
100
  const MAX_BUFFER_BYTES = 512 * 1024;
100
101
 
102
+ /**
103
+ * Minimum interval (ms) between JS-fallback regex checks on the same buffer.
104
+ * Prevents CPU spinning when deltas arrive faster than regex evaluation (#468).
105
+ */
106
+ const JS_FALLBACK_CHECK_INTERVAL_MS = 50;
107
+
101
108
  const DEFAULT_SCOPE: TtsrScope = {
102
109
  allowText: true,
103
110
  allowThinking: false,
@@ -110,6 +117,8 @@ export class TtsrManager {
110
117
  readonly #rules = new Map<string, TtsrEntry>();
111
118
  readonly #injectionRecords = new Map<string, InjectionRecord>();
112
119
  readonly #buffers = new Map<string, string>();
120
+ /** Tracks last JS-fallback check time per buffer key to throttle CPU (#468). */
121
+ readonly #lastJsCheckAt = new Map<string, number>();
113
122
  #messageCount = 0;
114
123
  #nativeHandle: number | null = null;
115
124
  #nativeDirty = false;
@@ -333,6 +342,7 @@ export class TtsrManager {
333
342
  * remain in JS as they are lightweight and context-dependent.
334
343
  */
335
344
  checkDelta(delta: string, context: TtsrMatchContext): Rule[] {
345
+ const stopTimer = debugTime("ttsr-check");
336
346
  const bufferKey = this.#bufferKey(context);
337
347
  let nextBuffer = `${this.#buffers.get(bufferKey) ?? ""}${delta}`;
338
348
  // Cap buffer size — keep the tail so patterns still match recent output
@@ -340,6 +350,7 @@ export class TtsrManager {
340
350
  nextBuffer = nextBuffer.slice(-MAX_BUFFER_BYTES);
341
351
  }
342
352
  this.#buffers.set(bufferKey, nextBuffer);
353
+ debugPeak("ttsrPeakBuffer", nextBuffer.length);
343
354
 
344
355
  // Lazily compile native engine if rules changed.
345
356
  if (this.#nativeDirty) this.#compileNative();
@@ -357,10 +368,22 @@ export class TtsrManager {
357
368
  if (!this.#matchesGlobalPaths(entry, context)) continue;
358
369
  matches.push(entry.rule);
359
370
  }
371
+ debugCount("ttsrChecks");
372
+ stopTimer({ bufferSize: nextBuffer.length, native: true, rulesChecked: this.#rules.size, matched: matches.map(m => m.name) });
360
373
  return matches;
361
374
  }
362
375
 
363
376
  // ── JS fallback: per-rule regex iteration ─────────────────────────
377
+ // Throttle JS regex checks to prevent CPU spinning on fast token
378
+ // streams — regex on a growing buffer is O(rules × buffer_size) (#468).
379
+ const now = Date.now();
380
+ const lastCheck = this.#lastJsCheckAt.get(bufferKey) ?? 0;
381
+ if (now - lastCheck < JS_FALLBACK_CHECK_INTERVAL_MS) {
382
+ stopTimer({ bufferSize: nextBuffer.length, throttled: true });
383
+ return [];
384
+ }
385
+ this.#lastJsCheckAt.set(bufferKey, now);
386
+
364
387
  const matches: Rule[] = [];
365
388
  for (const [name, entry] of this.#rules) {
366
389
  if (!this.#canTrigger(name)) continue;
@@ -369,6 +392,8 @@ export class TtsrManager {
369
392
  if (!this.#matchesCondition(entry, nextBuffer)) continue;
370
393
  matches.push(entry.rule);
371
394
  }
395
+ debugCount("ttsrChecks");
396
+ stopTimer({ bufferSize: nextBuffer.length, native: false, rulesChecked: this.#rules.size, matched: matches.map(m => m.name) });
372
397
  return matches;
373
398
  }
374
399
 
@@ -406,6 +431,7 @@ export class TtsrManager {
406
431
  /** Reset stream buffers (called on new turn). */
407
432
  resetBuffer(): void {
408
433
  this.#buffers.clear();
434
+ this.#lastJsCheckAt.clear();
409
435
  }
410
436
 
411
437
  /** Check if any TTSR rules are registered. */
@@ -7,9 +7,10 @@ import * as fs from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
9
 
10
- const SWIFT_SRC = path.join(__dirname, "speech-recognizer.swift");
11
- const RECOGNIZER_BIN = path.join(__dirname, "speech-recognizer");
12
- const PYTHON_SCRIPT = path.join(__dirname, "speech-recognizer.py");
10
+ const __extensionDir = import.meta.dirname!;
11
+ const SWIFT_SRC = path.join(__extensionDir, "speech-recognizer.swift");
12
+ const RECOGNIZER_BIN = path.join(__extensionDir, "speech-recognizer");
13
+ const PYTHON_SCRIPT = path.join(__extensionDir, "speech-recognizer.py");
13
14
 
14
15
  const IS_DARWIN = process.platform === "darwin";
15
16
  const IS_LINUX = process.platform === "linux";