switchroom 0.5.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 (718) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +447 -0
  3. package/bin/autoaccept.exp +81 -0
  4. package/bin/boot-self-test.sh +149 -0
  5. package/bin/bridge-watchdog.sh +967 -0
  6. package/bin/handoff-briefing.sh +206 -0
  7. package/bin/run-hook.sh +228 -0
  8. package/bin/switchroom.ts +4 -0
  9. package/bin/timezone-hook.sh +67 -0
  10. package/bin/user-profile-refresh-hook.sh +38 -0
  11. package/bin/workspace-dynamic-hook.sh +142 -0
  12. package/bin/workspace-stable-hook.sh +57 -0
  13. package/dist/cli/autoaccept-poll.js +118 -0
  14. package/dist/cli/switchroom.js +48557 -0
  15. package/package.json +95 -0
  16. package/profiles/_base/settings.json.hbs +15 -0
  17. package/profiles/_base/start.sh.hbs +383 -0
  18. package/profiles/_shared/telegram-style.md.hbs +140 -0
  19. package/profiles/coding/CLAUDE.md.hbs +57 -0
  20. package/profiles/coding/skills/architecture/SKILL.md +70 -0
  21. package/profiles/coding/skills/code-review/SKILL.md +58 -0
  22. package/profiles/coding/workspace/SOUL.md.hbs +25 -0
  23. package/profiles/default/CLAUDE.md +238 -0
  24. package/profiles/default/CLAUDE.md.hbs +113 -0
  25. package/profiles/default/workspace/CLAUDE.md.hbs +126 -0
  26. package/profiles/default/workspace/HEARTBEAT.md.hbs +40 -0
  27. package/profiles/default/workspace/IDENTITY.md.hbs +32 -0
  28. package/profiles/default/workspace/MEMORY.md.hbs +29 -0
  29. package/profiles/default/workspace/SOUL.md.hbs +61 -0
  30. package/profiles/default/workspace/TOOLS.md.hbs +29 -0
  31. package/profiles/default/workspace/USER.md.hbs +52 -0
  32. package/profiles/default/workspace/memory/.gitkeep +0 -0
  33. package/profiles/executive-assistant/CLAUDE.md.hbs +51 -0
  34. package/profiles/executive-assistant/skills/daily-briefing/SKILL.md +55 -0
  35. package/profiles/executive-assistant/skills/meeting-prep/SKILL.md +58 -0
  36. package/profiles/executive-assistant/workspace/SOUL.md.hbs +25 -0
  37. package/profiles/health-coach/CLAUDE.md.hbs +45 -0
  38. package/profiles/health-coach/skills/check-in/SKILL.md +41 -0
  39. package/profiles/health-coach/skills/weekly-review/SKILL.md +53 -0
  40. package/profiles/health-coach/workspace/SOUL.md.hbs +25 -0
  41. package/skills/buildkite-agent-infrastructure/SKILL.md +302 -0
  42. package/skills/buildkite-agent-infrastructure/agents/openai.yaml +6 -0
  43. package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-large.png +0 -0
  44. package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-small.png +0 -0
  45. package/skills/buildkite-agent-infrastructure/references/audit-logging.md +87 -0
  46. package/skills/buildkite-agent-infrastructure/references/graphql-mutations.md +690 -0
  47. package/skills/buildkite-agent-infrastructure/references/instance-shapes.md +38 -0
  48. package/skills/buildkite-agent-infrastructure/references/pipeline-templates.md +73 -0
  49. package/skills/buildkite-agent-infrastructure/references/self-hosted-agents.md +137 -0
  50. package/skills/buildkite-agent-infrastructure/references/sso-saml.md +92 -0
  51. package/skills/buildkite-agent-runtime/SKILL.md +476 -0
  52. package/skills/buildkite-agent-runtime/agents/openai.yaml +6 -0
  53. package/skills/buildkite-agent-runtime/assets/buildkite-icon-large.png +0 -0
  54. package/skills/buildkite-agent-runtime/assets/buildkite-icon-small.png +0 -0
  55. package/skills/buildkite-agent-runtime/references/flag-reference.md +417 -0
  56. package/skills/buildkite-agent-runtime/references/patterns-and-recipes.md +555 -0
  57. package/skills/buildkite-api/SKILL.md +285 -0
  58. package/skills/buildkite-api/agents/openai.yaml +6 -0
  59. package/skills/buildkite-api/assets/buildkite-icon-large.png +0 -0
  60. package/skills/buildkite-api/assets/buildkite-icon-small.png +0 -0
  61. package/skills/buildkite-api/references/graphql-reference.md +195 -0
  62. package/skills/buildkite-api/references/patterns.md +44 -0
  63. package/skills/buildkite-api/references/webhooks.md +161 -0
  64. package/skills/buildkite-cli/SKILL.md +379 -0
  65. package/skills/buildkite-cli/agents/openai.yaml +6 -0
  66. package/skills/buildkite-cli/assets/buildkite-icon-large.png +0 -0
  67. package/skills/buildkite-cli/assets/buildkite-icon-small.png +0 -0
  68. package/skills/buildkite-cli/references/command-reference.md +181 -0
  69. package/skills/buildkite-migration/SKILL.md +182 -0
  70. package/skills/buildkite-pipelines/SKILL.md +464 -0
  71. package/skills/buildkite-pipelines/agents/openai.yaml +6 -0
  72. package/skills/buildkite-pipelines/assets/buildkite-icon-large.png +0 -0
  73. package/skills/buildkite-pipelines/assets/buildkite-icon-small.png +0 -0
  74. package/skills/buildkite-pipelines/examples/basic-pipeline.yml +24 -0
  75. package/skills/buildkite-pipelines/examples/optimized-pipeline.yml +100 -0
  76. package/skills/buildkite-pipelines/references/advanced-patterns.md +286 -0
  77. package/skills/buildkite-pipelines/references/retry-and-error-codes.md +131 -0
  78. package/skills/buildkite-pipelines/references/step-types-reference.md +225 -0
  79. package/skills/buildkite-secure-delivery/SKILL.md +168 -0
  80. package/skills/buildkite-secure-delivery/agents/openai.yaml +6 -0
  81. package/skills/buildkite-secure-delivery/assets/buildkite-icon-large.png +0 -0
  82. package/skills/buildkite-secure-delivery/assets/buildkite-icon-small.png +0 -0
  83. package/skills/buildkite-secure-delivery/references/oidc-cloud-providers.md +83 -0
  84. package/skills/buildkite-secure-delivery/references/package-publishing.md +100 -0
  85. package/skills/buildkite-test-engine/SKILL.md +239 -0
  86. package/skills/buildkite-test-engine/agents/openai.yaml +6 -0
  87. package/skills/buildkite-test-engine/assets/buildkite-icon-large.png +0 -0
  88. package/skills/buildkite-test-engine/assets/buildkite-icon-small.png +0 -0
  89. package/skills/buildkite-test-engine/examples/bktec-splitting.yml +16 -0
  90. package/skills/buildkite-test-engine/examples/collector-pipeline.yml +11 -0
  91. package/skills/buildkite-test-engine/references/collectors.md +198 -0
  92. package/skills/buildkite-test-engine/references/splitting-examples.md +93 -0
  93. package/skills/docx/LICENSE.txt +30 -0
  94. package/skills/docx/SKILL.md +590 -0
  95. package/skills/docx/VENDORED.md +32 -0
  96. package/skills/docx/scripts/__init__.py +1 -0
  97. package/skills/docx/scripts/accept_changes.py +135 -0
  98. package/skills/docx/scripts/comment.py +318 -0
  99. package/skills/docx/scripts/office/helpers/__init__.py +0 -0
  100. package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
  101. package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
  102. package/skills/docx/scripts/office/pack.py +159 -0
  103. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  104. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  105. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  106. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  107. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  108. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  109. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  110. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  111. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  112. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  113. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  114. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  115. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  116. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  117. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  118. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  119. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  120. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  121. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  122. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  123. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  124. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  125. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  126. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  127. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  128. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  129. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  130. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  131. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  132. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  133. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  134. package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
  135. package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  136. package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  137. package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  138. package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  139. package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  140. package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  141. package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  142. package/skills/docx/scripts/office/soffice.py +183 -0
  143. package/skills/docx/scripts/office/unpack.py +132 -0
  144. package/skills/docx/scripts/office/validate.py +111 -0
  145. package/skills/docx/scripts/office/validators/__init__.py +15 -0
  146. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
  147. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
  148. package/skills/docx/scripts/office/validators/base.py +847 -0
  149. package/skills/docx/scripts/office/validators/docx.py +446 -0
  150. package/skills/docx/scripts/office/validators/pptx.py +275 -0
  151. package/skills/docx/scripts/office/validators/redlining.py +247 -0
  152. package/skills/docx/scripts/templates/comments.xml +3 -0
  153. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  154. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  155. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  156. package/skills/docx/scripts/templates/people.xml +3 -0
  157. package/skills/file-bug/SKILL.md +129 -0
  158. package/skills/humanizer/LICENSE +21 -0
  159. package/skills/humanizer/SKILL.md +559 -0
  160. package/skills/humanizer/VENDORED.md +38 -0
  161. package/skills/humanizer-calibrate/SKILL.md +144 -0
  162. package/skills/mcp-builder/LICENSE.txt +202 -0
  163. package/skills/mcp-builder/SKILL.md +236 -0
  164. package/skills/mcp-builder/VENDORED.md +32 -0
  165. package/skills/mcp-builder/reference/evaluation.md +602 -0
  166. package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  167. package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  168. package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  169. package/skills/mcp-builder/scripts/connections.py +151 -0
  170. package/skills/mcp-builder/scripts/evaluation.py +373 -0
  171. package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  172. package/skills/mcp-builder/scripts/requirements.txt +2 -0
  173. package/skills/pdf/LICENSE.txt +30 -0
  174. package/skills/pdf/SKILL.md +314 -0
  175. package/skills/pdf/VENDORED.md +32 -0
  176. package/skills/pdf/forms.md +294 -0
  177. package/skills/pdf/reference.md +612 -0
  178. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  179. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  180. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  181. package/skills/pdf/scripts/create_validation_image.py +37 -0
  182. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  183. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  184. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  185. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  186. package/skills/pptx/LICENSE.txt +30 -0
  187. package/skills/pptx/SKILL.md +232 -0
  188. package/skills/pptx/VENDORED.md +32 -0
  189. package/skills/pptx/editing.md +205 -0
  190. package/skills/pptx/pptxgenjs.md +420 -0
  191. package/skills/pptx/scripts/__init__.py +0 -0
  192. package/skills/pptx/scripts/add_slide.py +195 -0
  193. package/skills/pptx/scripts/clean.py +286 -0
  194. package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
  195. package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
  196. package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
  197. package/skills/pptx/scripts/office/pack.py +159 -0
  198. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  199. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  200. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  201. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  202. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  203. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  204. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  205. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  206. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  207. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  208. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  209. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  210. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  211. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  212. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  213. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  214. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  215. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  216. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  217. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  218. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  219. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  220. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  221. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  222. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  223. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  224. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  225. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  226. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  227. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  228. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  229. package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
  230. package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  231. package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  232. package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  233. package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  234. package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  235. package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  236. package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  237. package/skills/pptx/scripts/office/soffice.py +183 -0
  238. package/skills/pptx/scripts/office/unpack.py +132 -0
  239. package/skills/pptx/scripts/office/validate.py +111 -0
  240. package/skills/pptx/scripts/office/validators/__init__.py +15 -0
  241. package/skills/pptx/scripts/office/validators/base.py +847 -0
  242. package/skills/pptx/scripts/office/validators/docx.py +446 -0
  243. package/skills/pptx/scripts/office/validators/pptx.py +275 -0
  244. package/skills/pptx/scripts/office/validators/redlining.py +247 -0
  245. package/skills/pptx/scripts/thumbnail.py +289 -0
  246. package/skills/skill-creator/LICENSE.txt +202 -0
  247. package/skills/skill-creator/SKILL.md +485 -0
  248. package/skills/skill-creator/VENDORED.md +32 -0
  249. package/skills/skill-creator/agents/analyzer.md +274 -0
  250. package/skills/skill-creator/agents/comparator.md +202 -0
  251. package/skills/skill-creator/agents/grader.md +223 -0
  252. package/skills/skill-creator/assets/eval_review.html +146 -0
  253. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  254. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  255. package/skills/skill-creator/references/schemas.md +430 -0
  256. package/skills/skill-creator/scripts/__init__.py +0 -0
  257. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  258. package/skills/skill-creator/scripts/generate_report.py +326 -0
  259. package/skills/skill-creator/scripts/improve_description.py +247 -0
  260. package/skills/skill-creator/scripts/package_skill.py +136 -0
  261. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  262. package/skills/skill-creator/scripts/run_eval.py +310 -0
  263. package/skills/skill-creator/scripts/run_loop.py +328 -0
  264. package/skills/skill-creator/scripts/utils.py +47 -0
  265. package/skills/switchroom-architecture/SKILL.md +60 -0
  266. package/skills/switchroom-architecture/cascade.md +112 -0
  267. package/skills/switchroom-architecture/sub-agents.md +87 -0
  268. package/skills/switchroom-architecture/telegram.md +94 -0
  269. package/skills/switchroom-cli/SKILL.md +274 -0
  270. package/skills/switchroom-health/SKILL.md +101 -0
  271. package/skills/switchroom-install/SKILL.md +116 -0
  272. package/skills/switchroom-manage/SKILL.md +90 -0
  273. package/skills/switchroom-status/SKILL.md +69 -0
  274. package/skills/switchroom-status/scripts/status.sh +69 -0
  275. package/skills/telegram-test-harness/SKILL.md +191 -0
  276. package/skills/token-helpers/SKILL.md +73 -0
  277. package/skills/token-helpers/scripts/google-cal-token.sh +62 -0
  278. package/skills/token-helpers/scripts/ms-graph-token.sh +70 -0
  279. package/skills/webapp-testing/LICENSE.txt +202 -0
  280. package/skills/webapp-testing/SKILL.md +96 -0
  281. package/skills/webapp-testing/VENDORED.md +32 -0
  282. package/skills/webapp-testing/examples/console_logging.py +35 -0
  283. package/skills/webapp-testing/examples/element_discovery.py +40 -0
  284. package/skills/webapp-testing/examples/static_html_automation.py +33 -0
  285. package/skills/webapp-testing/scripts/with_server.py +106 -0
  286. package/skills/xlsx/LICENSE.txt +30 -0
  287. package/skills/xlsx/SKILL.md +292 -0
  288. package/skills/xlsx/VENDORED.md +32 -0
  289. package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
  290. package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
  291. package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  292. package/skills/xlsx/scripts/office/pack.py +159 -0
  293. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  294. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  295. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  296. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  297. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  298. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  299. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  300. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  301. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  302. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  303. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  304. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  305. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  306. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  307. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  308. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  309. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  310. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  311. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  312. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  313. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  314. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  315. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  316. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  317. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  318. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  319. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  320. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  321. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  322. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  323. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  324. package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  325. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  326. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  327. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  328. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  329. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  330. package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  331. package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  332. package/skills/xlsx/scripts/office/soffice.py +183 -0
  333. package/skills/xlsx/scripts/office/unpack.py +132 -0
  334. package/skills/xlsx/scripts/office/validate.py +111 -0
  335. package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
  336. package/skills/xlsx/scripts/office/validators/base.py +847 -0
  337. package/skills/xlsx/scripts/office/validators/docx.py +446 -0
  338. package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
  339. package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
  340. package/skills/xlsx/scripts/recalc.py +184 -0
  341. package/telegram-plugin/.claude-plugin/plugin.json +20 -0
  342. package/telegram-plugin/.mcp.json +14 -0
  343. package/telegram-plugin/LICENSE +21 -0
  344. package/telegram-plugin/README.md +352 -0
  345. package/telegram-plugin/active-pins-sweep.ts +204 -0
  346. package/telegram-plugin/active-pins.ts +146 -0
  347. package/telegram-plugin/active-reactions-sweep.ts +79 -0
  348. package/telegram-plugin/active-reactions.ts +134 -0
  349. package/telegram-plugin/admin-commands/dispatch.test.ts +149 -0
  350. package/telegram-plugin/admin-commands/index.ts +106 -0
  351. package/telegram-plugin/answer-stream.ts +565 -0
  352. package/telegram-plugin/ask-user.ts +179 -0
  353. package/telegram-plugin/attachment-path.ts +80 -0
  354. package/telegram-plugin/auth-code-redact.ts +83 -0
  355. package/telegram-plugin/auth-dashboard.ts +1104 -0
  356. package/telegram-plugin/auth-slot-parser.ts +497 -0
  357. package/telegram-plugin/auto-fallback-dispatcher.ts +68 -0
  358. package/telegram-plugin/auto-fallback.ts +348 -0
  359. package/telegram-plugin/bridge/bridge.ts +687 -0
  360. package/telegram-plugin/bridge/ipc-client.ts +326 -0
  361. package/telegram-plugin/bun.lock +218 -0
  362. package/telegram-plugin/card-format.ts +62 -0
  363. package/telegram-plugin/channel-envelope-safety.test.ts +56 -0
  364. package/telegram-plugin/channel-envelope-safety.ts +56 -0
  365. package/telegram-plugin/chat-lock.ts +65 -0
  366. package/telegram-plugin/context-exhaustion.ts +38 -0
  367. package/telegram-plugin/credits-watch.ts +220 -0
  368. package/telegram-plugin/dist/bridge/bridge.js +24758 -0
  369. package/telegram-plugin/dist/foreman/foreman.js +30723 -0
  370. package/telegram-plugin/dist/gateway/gateway.js +46497 -0
  371. package/telegram-plugin/dist/server.js +24551 -0
  372. package/telegram-plugin/dm-command-gate.ts +56 -0
  373. package/telegram-plugin/docs/gateway-server-split.md +133 -0
  374. package/telegram-plugin/docs/multi-agent-card-design.md +847 -0
  375. package/telegram-plugin/docs/pinned-progress-card-reliability.md +144 -0
  376. package/telegram-plugin/docs/stream-json-daemon-mode.md +477 -0
  377. package/telegram-plugin/docs/waiting-ux-spec.md +233 -0
  378. package/telegram-plugin/draft-stream.ts +442 -0
  379. package/telegram-plugin/draft-transport.ts +72 -0
  380. package/telegram-plugin/first-paint.ts +246 -0
  381. package/telegram-plugin/fleet-state.ts +246 -0
  382. package/telegram-plugin/foreman/foreman-create-flow.ts +202 -0
  383. package/telegram-plugin/foreman/foreman-handlers.ts +493 -0
  384. package/telegram-plugin/foreman/foreman.ts +1130 -0
  385. package/telegram-plugin/foreman/setup-flow.ts +345 -0
  386. package/telegram-plugin/foreman/setup-state.ts +239 -0
  387. package/telegram-plugin/foreman/state.ts +203 -0
  388. package/telegram-plugin/format.ts +685 -0
  389. package/telegram-plugin/gateway/access-validator.test.ts +95 -0
  390. package/telegram-plugin/gateway/access-validator.ts +37 -0
  391. package/telegram-plugin/gateway/boot-card.ts +582 -0
  392. package/telegram-plugin/gateway/boot-probes.ts +863 -0
  393. package/telegram-plugin/gateway/boot-reason.ts +51 -0
  394. package/telegram-plugin/gateway/boot-sweep-filter.test.ts +54 -0
  395. package/telegram-plugin/gateway/boot-sweep-filter.ts +32 -0
  396. package/telegram-plugin/gateway/clean-shutdown-marker.ts +183 -0
  397. package/telegram-plugin/gateway/disconnect-flush.ts +109 -0
  398. package/telegram-plugin/gateway/gateway.ts +10202 -0
  399. package/telegram-plugin/gateway/inbound-coalesce.ts +147 -0
  400. package/telegram-plugin/gateway/inject-handler.test.ts +221 -0
  401. package/telegram-plugin/gateway/inject-handler.ts +190 -0
  402. package/telegram-plugin/gateway/ipc-protocol.ts +151 -0
  403. package/telegram-plugin/gateway/ipc-server.ts +494 -0
  404. package/telegram-plugin/gateway/pid-file.ts +103 -0
  405. package/telegram-plugin/gateway/poll-health.ts +156 -0
  406. package/telegram-plugin/gateway/preamble-suppressor.ts +154 -0
  407. package/telegram-plugin/gateway/quota-cache.ts +125 -0
  408. package/telegram-plugin/gateway/resolve-calling-subagent.ts +78 -0
  409. package/telegram-plugin/gateway/restart-watchdog.ts +200 -0
  410. package/telegram-plugin/gateway/session-marker.ts +83 -0
  411. package/telegram-plugin/gateway/shutdown-drain.ts +162 -0
  412. package/telegram-plugin/gateway/startup-mutex.ts +285 -0
  413. package/telegram-plugin/gateway/startup-network-retry.ts +142 -0
  414. package/telegram-plugin/gateway/turn-active-marker.ts +176 -0
  415. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +78 -0
  416. package/telegram-plugin/handoff-continuity.ts +200 -0
  417. package/telegram-plugin/history.ts +468 -0
  418. package/telegram-plugin/hooks/hooks.json +58 -0
  419. package/telegram-plugin/hooks/secret-guard-pretool.mjs +208 -0
  420. package/telegram-plugin/hooks/secret-scrub-stop.mjs +98 -0
  421. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +111 -0
  422. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +296 -0
  423. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +261 -0
  424. package/telegram-plugin/html-sanitize.ts +244 -0
  425. package/telegram-plugin/idle-footer.ts +65 -0
  426. package/telegram-plugin/inline-keyboard-callbacks.ts +166 -0
  427. package/telegram-plugin/interrupt-marker.ts +66 -0
  428. package/telegram-plugin/issues-card.ts +371 -0
  429. package/telegram-plugin/issues-watcher.ts +125 -0
  430. package/telegram-plugin/model-unavailable.ts +325 -0
  431. package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  432. package/telegram-plugin/operator-events-history.ts +94 -0
  433. package/telegram-plugin/operator-events.fixtures.json +161 -0
  434. package/telegram-plugin/operator-events.ts +421 -0
  435. package/telegram-plugin/package.json +55 -0
  436. package/telegram-plugin/permission-rule.ts +133 -0
  437. package/telegram-plugin/permission-title.ts +117 -0
  438. package/telegram-plugin/pin-event-log.ts +76 -0
  439. package/telegram-plugin/plugin-logger.ts +136 -0
  440. package/telegram-plugin/progress-card-driver.ts +2697 -0
  441. package/telegram-plugin/progress-card-pin-manager.ts +589 -0
  442. package/telegram-plugin/progress-card-pin-watchdog.ts +98 -0
  443. package/telegram-plugin/progress-card.ts +1409 -0
  444. package/telegram-plugin/pty-partial-handler.ts +247 -0
  445. package/telegram-plugin/pty-tail.ts +730 -0
  446. package/telegram-plugin/quota-check.ts +474 -0
  447. package/telegram-plugin/recent-outbound-dedup.ts +169 -0
  448. package/telegram-plugin/registry/api-registry.test.ts +201 -0
  449. package/telegram-plugin/registry/subagents-bugs.test.ts +454 -0
  450. package/telegram-plugin/registry/subagents-schema.ts +509 -0
  451. package/telegram-plugin/registry/subagents.test.ts +476 -0
  452. package/telegram-plugin/registry/turns-schema.test.ts +101 -0
  453. package/telegram-plugin/registry/turns-schema.ts +417 -0
  454. package/telegram-plugin/retry-api-call.ts +172 -0
  455. package/telegram-plugin/scripts/build.mjs +78 -0
  456. package/telegram-plugin/secret-detect/audit.ts +66 -0
  457. package/telegram-plugin/secret-detect/chunker.ts +37 -0
  458. package/telegram-plugin/secret-detect/entropy.ts +20 -0
  459. package/telegram-plugin/secret-detect/gitleaks-loader.ts +74 -0
  460. package/telegram-plugin/secret-detect/gitleaks.toml +27 -0
  461. package/telegram-plugin/secret-detect/index.ts +218 -0
  462. package/telegram-plugin/secret-detect/kv-scanner.ts +60 -0
  463. package/telegram-plugin/secret-detect/mask.ts +13 -0
  464. package/telegram-plugin/secret-detect/patterns.ts +115 -0
  465. package/telegram-plugin/secret-detect/pipeline.ts +144 -0
  466. package/telegram-plugin/secret-detect/rewrite.ts +26 -0
  467. package/telegram-plugin/secret-detect/secretlint-source.ts +95 -0
  468. package/telegram-plugin/secret-detect/slug.ts +44 -0
  469. package/telegram-plugin/secret-detect/staging.ts +85 -0
  470. package/telegram-plugin/secret-detect/suppressor.ts +34 -0
  471. package/telegram-plugin/secret-detect/url-redact.ts +60 -0
  472. package/telegram-plugin/secret-detect/vault-write.ts +56 -0
  473. package/telegram-plugin/server.js +41795 -0
  474. package/telegram-plugin/server.ts +171 -0
  475. package/telegram-plugin/session-tail.ts +884 -0
  476. package/telegram-plugin/shared/bot-runtime.ts +324 -0
  477. package/telegram-plugin/silent-reply.ts +58 -0
  478. package/telegram-plugin/slot-banner-driver.ts +147 -0
  479. package/telegram-plugin/slot-banner.ts +86 -0
  480. package/telegram-plugin/start.js +26 -0
  481. package/telegram-plugin/startup-reset.ts +45 -0
  482. package/telegram-plugin/status-reactions.ts +332 -0
  483. package/telegram-plugin/steering.ts +155 -0
  484. package/telegram-plugin/sticker-aliases.ts +249 -0
  485. package/telegram-plugin/stream-controller.ts +311 -0
  486. package/telegram-plugin/stream-reply-handler.ts +664 -0
  487. package/telegram-plugin/streaming-metrics.ts +134 -0
  488. package/telegram-plugin/streaming-report.ts +204 -0
  489. package/telegram-plugin/subagent-watcher.ts +880 -0
  490. package/telegram-plugin/telegram-button-constraints.ts +191 -0
  491. package/telegram-plugin/telegraph.ts +381 -0
  492. package/telegram-plugin/tests/HARNESS.md +340 -0
  493. package/telegram-plugin/tests/_progress-card-harness.ts +105 -0
  494. package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +211 -0
  495. package/telegram-plugin/tests/active-pins-sweep.test.ts +309 -0
  496. package/telegram-plugin/tests/active-pins.test.ts +187 -0
  497. package/telegram-plugin/tests/active-reactions-sweep.test.ts +116 -0
  498. package/telegram-plugin/tests/active-reactions.test.ts +198 -0
  499. package/telegram-plugin/tests/answer-stream-dedup.test.ts +352 -0
  500. package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +236 -0
  501. package/telegram-plugin/tests/answer-stream.test.ts +878 -0
  502. package/telegram-plugin/tests/ask-user.test.ts +203 -0
  503. package/telegram-plugin/tests/attachment-path.test.ts +199 -0
  504. package/telegram-plugin/tests/auth-account-identity-surface.test.ts +118 -0
  505. package/telegram-plugin/tests/auth-code-auto-capture.test.ts +144 -0
  506. package/telegram-plugin/tests/auth-code-redact.test.ts +248 -0
  507. package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +260 -0
  508. package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +140 -0
  509. package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +559 -0
  510. package/telegram-plugin/tests/auth-dashboard.test.ts +1045 -0
  511. package/telegram-plugin/tests/auth-login-url-button.test.ts +122 -0
  512. package/telegram-plugin/tests/auth-slot-commands.test.ts +640 -0
  513. package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +183 -0
  514. package/telegram-plugin/tests/auto-fallback.test.ts +381 -0
  515. package/telegram-plugin/tests/boot-card-account-quota.test.ts +137 -0
  516. package/telegram-plugin/tests/boot-card-dedupe.test.ts +154 -0
  517. package/telegram-plugin/tests/boot-card-probe-target.test.ts +194 -0
  518. package/telegram-plugin/tests/boot-card-reason.test.ts +103 -0
  519. package/telegram-plugin/tests/boot-card-render.test.ts +219 -0
  520. package/telegram-plugin/tests/boot-probes.test.ts +451 -0
  521. package/telegram-plugin/tests/bot-api.harness.ts +116 -0
  522. package/telegram-plugin/tests/bot-runtime.test.ts +190 -0
  523. package/telegram-plugin/tests/bridge-anonymous-refuse.test.ts +60 -0
  524. package/telegram-plugin/tests/context-exhaustion.test.ts +114 -0
  525. package/telegram-plugin/tests/credits-watch.test.ts +221 -0
  526. package/telegram-plugin/tests/dm-command-gate.test.ts +176 -0
  527. package/telegram-plugin/tests/draft-stream.test.ts +752 -0
  528. package/telegram-plugin/tests/draft-transport.test.ts +141 -0
  529. package/telegram-plugin/tests/e2e.test.ts +436 -0
  530. package/telegram-plugin/tests/fake-bot-api.test.ts +213 -0
  531. package/telegram-plugin/tests/fake-bot-api.ts +617 -0
  532. package/telegram-plugin/tests/false-restart-banner.test.ts +253 -0
  533. package/telegram-plugin/tests/first-paint.test.ts +257 -0
  534. package/telegram-plugin/tests/fixtures/pty-tail-tmux-fragment.bin +6 -0
  535. package/telegram-plugin/tests/fixtures/service-log-current-claude-code.bin +3624 -0
  536. package/telegram-plugin/tests/fleet-state-watcher.test.ts +101 -0
  537. package/telegram-plugin/tests/fleet-state.test.ts +185 -0
  538. package/telegram-plugin/tests/foreman-create-flow.test.ts +359 -0
  539. package/telegram-plugin/tests/foreman-handlers.test.ts +347 -0
  540. package/telegram-plugin/tests/foreman-state.test.ts +164 -0
  541. package/telegram-plugin/tests/foreman-write-ops.test.ts +214 -0
  542. package/telegram-plugin/tests/gateway-409-retry-banner.test.ts +173 -0
  543. package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +72 -0
  544. package/telegram-plugin/tests/gateway-bridge.test.ts +811 -0
  545. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +414 -0
  546. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +144 -0
  547. package/telegram-plugin/tests/gateway-message-validator.test.ts +133 -0
  548. package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +103 -0
  549. package/telegram-plugin/tests/gateway-secret-detect.test.ts +127 -0
  550. package/telegram-plugin/tests/gateway-startup-mutex.test.ts +284 -0
  551. package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +185 -0
  552. package/telegram-plugin/tests/gateway-startup-reset.test.ts +72 -0
  553. package/telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts +125 -0
  554. package/telegram-plugin/tests/handoff-continuity.test.ts +249 -0
  555. package/telegram-plugin/tests/harness-ordering-invariants.test.ts +243 -0
  556. package/telegram-plugin/tests/harness-parse-mode-validation.test.ts +114 -0
  557. package/telegram-plugin/tests/history.test.ts +364 -0
  558. package/telegram-plugin/tests/html-balanced.ts +63 -0
  559. package/telegram-plugin/tests/html-sanitize.test.ts +146 -0
  560. package/telegram-plugin/tests/idle-footer-wiring.test.ts +88 -0
  561. package/telegram-plugin/tests/idle-footer.test.ts +66 -0
  562. package/telegram-plugin/tests/inbound-coalesce.test.ts +127 -0
  563. package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +150 -0
  564. package/telegram-plugin/tests/interrupt-marker.test.ts +126 -0
  565. package/telegram-plugin/tests/ipc-protocol.test.ts +218 -0
  566. package/telegram-plugin/tests/ipc-server-anonymous-refuse.test.ts +82 -0
  567. package/telegram-plugin/tests/ipc-server-client.test.ts +323 -0
  568. package/telegram-plugin/tests/ipc-server-race.test.ts +183 -0
  569. package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +91 -0
  570. package/telegram-plugin/tests/ipc-server-validate-pty-partial.test.ts +64 -0
  571. package/telegram-plugin/tests/ipc-server-validate-update-placeholder.test.ts +77 -0
  572. package/telegram-plugin/tests/ipc-validator.test.ts +274 -0
  573. package/telegram-plugin/tests/issues-card.test.ts +495 -0
  574. package/telegram-plugin/tests/issues-watcher.test.ts +165 -0
  575. package/telegram-plugin/tests/model-unavailable.test.ts +303 -0
  576. package/telegram-plugin/tests/multi-turn-continuity.test.ts +159 -0
  577. package/telegram-plugin/tests/operator-events-history.test.ts +125 -0
  578. package/telegram-plugin/tests/operator-events-session-tail.test.ts +192 -0
  579. package/telegram-plugin/tests/operator-events.test.ts +331 -0
  580. package/telegram-plugin/tests/outbound-ordering.test.ts +293 -0
  581. package/telegram-plugin/tests/parse-mode-rotation.test.ts +164 -0
  582. package/telegram-plugin/tests/permission-rule.test.ts +121 -0
  583. package/telegram-plugin/tests/permission-title.test.ts +106 -0
  584. package/telegram-plugin/tests/pin-event-log.test.ts +124 -0
  585. package/telegram-plugin/tests/plugin-logger.test.ts +97 -0
  586. package/telegram-plugin/tests/poll-health.test.ts +86 -0
  587. package/telegram-plugin/tests/preamble-suppressor.test.ts +285 -0
  588. package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +73 -0
  589. package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +272 -0
  590. package/telegram-plugin/tests/progress-card-cross-turn.test.ts +258 -0
  591. package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +81 -0
  592. package/telegram-plugin/tests/progress-card-draft-flag.test.ts +80 -0
  593. package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +215 -0
  594. package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +123 -0
  595. package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +76 -0
  596. package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +62 -0
  597. package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +84 -0
  598. package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +139 -0
  599. package/telegram-plugin/tests/progress-card-pin-manager.test.ts +773 -0
  600. package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +66 -0
  601. package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +64 -0
  602. package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +190 -0
  603. package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +146 -0
  604. package/telegram-plugin/tests/progress-update.test.ts +236 -0
  605. package/telegram-plugin/tests/protocol-fixtures.test.ts +59 -0
  606. package/telegram-plugin/tests/protocol-fixtures.ts +198 -0
  607. package/telegram-plugin/tests/pty-partial-handler.test.ts +326 -0
  608. package/telegram-plugin/tests/pty-tail-real-fixture.test.ts +114 -0
  609. package/telegram-plugin/tests/pty-tail-tmux-fragment.test.ts +71 -0
  610. package/telegram-plugin/tests/pty-tail.test.ts +525 -0
  611. package/telegram-plugin/tests/quota-cache.test.ts +187 -0
  612. package/telegram-plugin/tests/quota-check.test.ts +622 -0
  613. package/telegram-plugin/tests/races.test.ts +842 -0
  614. package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +123 -0
  615. package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +82 -0
  616. package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +114 -0
  617. package/telegram-plugin/tests/real-gateway-harness.ts +699 -0
  618. package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +313 -0
  619. package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +299 -0
  620. package/telegram-plugin/tests/real-gateway-spec.test.ts +487 -0
  621. package/telegram-plugin/tests/real-gateway.smoke.test.ts +101 -0
  622. package/telegram-plugin/tests/recent-outbound-dedup.test.ts +192 -0
  623. package/telegram-plugin/tests/registry-turns.test.ts +432 -0
  624. package/telegram-plugin/tests/reply-terminal-reaction.test.ts +149 -0
  625. package/telegram-plugin/tests/resolve-calling-subagent.test.ts +269 -0
  626. package/telegram-plugin/tests/restart-watchdog.test.ts +224 -0
  627. package/telegram-plugin/tests/retry-api-call.test.ts +287 -0
  628. package/telegram-plugin/tests/secret-detect-audit.test.ts +58 -0
  629. package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +83 -0
  630. package/telegram-plugin/tests/secret-detect-gitleaks.test.ts +32 -0
  631. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +308 -0
  632. package/telegram-plugin/tests/secret-detect-pipeline.test.ts +123 -0
  633. package/telegram-plugin/tests/secret-detect-secretlint.test.ts +101 -0
  634. package/telegram-plugin/tests/secret-detect-staging.test.ts +45 -0
  635. package/telegram-plugin/tests/secret-detect-suppressor-no-silent-allow.test.ts +67 -0
  636. package/telegram-plugin/tests/secret-detect.test.ts +223 -0
  637. package/telegram-plugin/tests/secret-guard-pretool.test.ts +194 -0
  638. package/telegram-plugin/tests/send-typing-action-validation.test.ts +61 -0
  639. package/telegram-plugin/tests/session-tail-capped.test.ts +285 -0
  640. package/telegram-plugin/tests/session-tail.test.ts +524 -0
  641. package/telegram-plugin/tests/setup-flow.test.ts +510 -0
  642. package/telegram-plugin/tests/setup-state.test.ts +146 -0
  643. package/telegram-plugin/tests/silent-reply-guard.test.ts +122 -0
  644. package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +350 -0
  645. package/telegram-plugin/tests/slot-banner.test.ts +74 -0
  646. package/telegram-plugin/tests/snapshot-serializer.ts +79 -0
  647. package/telegram-plugin/tests/spawn-detached-cgroup-escape.test.ts +51 -0
  648. package/telegram-plugin/tests/status-accent.test.ts +186 -0
  649. package/telegram-plugin/tests/status-reactions-allowed-filter.test.ts +132 -0
  650. package/telegram-plugin/tests/status-reactions.test.ts +314 -0
  651. package/telegram-plugin/tests/steering.test.ts +282 -0
  652. package/telegram-plugin/tests/sticker-aliases.test.ts +232 -0
  653. package/telegram-plugin/tests/stream-controller-html-fallback.test.ts +127 -0
  654. package/telegram-plugin/tests/stream-controller.test.ts +262 -0
  655. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +208 -0
  656. package/telegram-plugin/tests/stream-reply-handler.test.ts +1292 -0
  657. package/telegram-plugin/tests/streaming-e2e.test.ts +389 -0
  658. package/telegram-plugin/tests/streaming-metrics.test.ts +201 -0
  659. package/telegram-plugin/tests/streaming-orchestration.test.ts +756 -0
  660. package/telegram-plugin/tests/subagent-registry-bugs.test.ts +725 -0
  661. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +213 -0
  662. package/telegram-plugin/tests/subagent-watcher-parent-marker.test.ts +274 -0
  663. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +243 -0
  664. package/telegram-plugin/tests/subagent-watcher.test.ts +877 -0
  665. package/telegram-plugin/tests/subagents-schema-init-order.test.ts +109 -0
  666. package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +116 -0
  667. package/telegram-plugin/tests/telegram-button-constraints.test.ts +194 -0
  668. package/telegram-plugin/tests/telegram-format.test.ts +1093 -0
  669. package/telegram-plugin/tests/telegraph.test.ts +246 -0
  670. package/telegram-plugin/tests/tool-labels.test.ts +383 -0
  671. package/telegram-plugin/tests/turn-active-marker.test.ts +195 -0
  672. package/telegram-plugin/tests/turn-end-regressions.test.ts +489 -0
  673. package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +218 -0
  674. package/telegram-plugin/tests/turn-flush-dedup-controller.test.ts +144 -0
  675. package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +78 -0
  676. package/telegram-plugin/tests/turn-flush-safety.test.ts +189 -0
  677. package/telegram-plugin/tests/turn-signal-tracker.test.ts +107 -0
  678. package/telegram-plugin/tests/turns-writer.test.ts +323 -0
  679. package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +131 -0
  680. package/telegram-plugin/tests/two-zone-bg-detection.test.ts +120 -0
  681. package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +114 -0
  682. package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +87 -0
  683. package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +211 -0
  684. package/telegram-plugin/tests/two-zone-card-cap.test.ts +62 -0
  685. package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +101 -0
  686. package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +68 -0
  687. package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +110 -0
  688. package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +128 -0
  689. package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +58 -0
  690. package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +133 -0
  691. package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +155 -0
  692. package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +117 -0
  693. package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +143 -0
  694. package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +149 -0
  695. package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +101 -0
  696. package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +114 -0
  697. package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +105 -0
  698. package/telegram-plugin/tests/typing-wrap.test.ts +141 -0
  699. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +147 -0
  700. package/telegram-plugin/tests/update-factory-edited-and-reactions.test.ts +108 -0
  701. package/telegram-plugin/tests/update-factory.ts +305 -0
  702. package/telegram-plugin/tests/vault-grant-wizard.test.ts +84 -0
  703. package/telegram-plugin/tests/vault-grants-revoke.test.ts +265 -0
  704. package/telegram-plugin/tests/vault-subcommands.test.ts +234 -0
  705. package/telegram-plugin/tests/voice-transcribe.test.ts +196 -0
  706. package/telegram-plugin/tests/waiting-ux-harness.ts +381 -0
  707. package/telegram-plugin/tests/waiting-ux.e2e.test.ts +233 -0
  708. package/telegram-plugin/tests/welcome-text.test.ts +407 -0
  709. package/telegram-plugin/tool-error-filter.ts +89 -0
  710. package/telegram-plugin/tool-labels.ts +330 -0
  711. package/telegram-plugin/tool-names.ts +53 -0
  712. package/telegram-plugin/turn-flush-prose-recovery.ts +40 -0
  713. package/telegram-plugin/turn-flush-safety.ts +109 -0
  714. package/telegram-plugin/turn-signal-tracker.ts +79 -0
  715. package/telegram-plugin/two-zone-card.ts +249 -0
  716. package/telegram-plugin/typing-wrap.ts +92 -0
  717. package/telegram-plugin/voice-transcribe.ts +166 -0
  718. package/telegram-plugin/welcome-text.ts +359 -0
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Orchestration tests for the streaming state machine that lives in
3
+ * server.ts around `activeDraftStreams: Map<streamKey, DraftStreamHandle>`.
4
+ *
5
+ * These tests model the pattern used by the real `stream_reply` MCP
6
+ * handler and `handlePtyPartial` — both of which look up the map, create
7
+ * a controller on miss, and delete the entry on done. We exercise the
8
+ * *state transitions* directly against real `createStreamController`
9
+ * instances backed by the mock bot harness, so that regressions in the
10
+ * reliability-critical flows (claim-then-new-turn, cross-chat isolation,
11
+ * thread keying, post-done re-entry) fail a test instead of silently
12
+ * ghosting in production.
13
+ *
14
+ * Scope: single-controller primitives are covered in
15
+ * `stream-controller.test.ts`. This file exists for multi-stream
16
+ * + restart + race-shape scenarios.
17
+ */
18
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
19
+ import { createStreamController } from '../stream-controller.js'
20
+ import type { DraftStreamHandle } from '../draft-stream.js'
21
+ import { createMockBot, installBotResetHook, microtaskFlush } from './bot-api.harness.js'
22
+
23
+ function streamKey(chatId: string, threadId?: number): string {
24
+ return `${chatId}:${threadId ?? '_'}`
25
+ }
26
+
27
+ /**
28
+ * Tiny facsimile of server.ts's stream_reply case. Takes a Map of
29
+ * active streams and wires the create-on-miss + delete-on-done flow.
30
+ * Pure — no side effects outside the map and bot calls.
31
+ */
32
+ function modelStreamReply(params: {
33
+ bot: ReturnType<typeof createMockBot>
34
+ map: Map<string, DraftStreamHandle>
35
+ chatId: string
36
+ threadId?: number
37
+ text: string
38
+ done?: boolean
39
+ throttleMs?: number
40
+ parseMode?: 'HTML' | 'MarkdownV2'
41
+ }): { stream: DraftStreamHandle; settled: Promise<void> } {
42
+ const key = streamKey(params.chatId, params.threadId)
43
+ let stream = params.map.get(key)
44
+ if (!stream) {
45
+ stream = createStreamController({
46
+ bot: params.bot,
47
+ chatId: params.chatId,
48
+ threadId: params.threadId,
49
+ parseMode: params.parseMode,
50
+ throttleMs: params.throttleMs ?? 600,
51
+ })
52
+ params.map.set(key, stream)
53
+ }
54
+ // Fire-and-forget update — caller advances fake timers + flushes
55
+ // microtasks to drive progress, matching draft-stream.test.ts style.
56
+ void stream.update(params.text)
57
+ const settled = params.done
58
+ ? stream.finalize().then(() => { params.map.delete(key) })
59
+ : Promise.resolve()
60
+ return { stream, settled }
61
+ }
62
+
63
+ describe('streaming orchestration — activeDraftStreams map', () => {
64
+ const bot = createMockBot()
65
+ installBotResetHook(bot)
66
+
67
+ beforeEach(() => vi.useFakeTimers())
68
+ afterEach(() => vi.useRealTimers())
69
+
70
+ it('first call creates a stream in the map; second call reuses it', async () => {
71
+ const map = new Map<string, DraftStreamHandle>()
72
+
73
+ const { stream: a } = modelStreamReply({ bot, map, chatId: '1', text: 'hi' })
74
+ await microtaskFlush()
75
+ vi.advanceTimersByTime(700)
76
+ await microtaskFlush()
77
+ const { stream: b } = modelStreamReply({ bot, map, chatId: '1', text: 'hi again' })
78
+ await microtaskFlush()
79
+
80
+ expect(a).toBe(b)
81
+ expect(map.size).toBe(1)
82
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
83
+ expect(bot.api.editMessageText).toHaveBeenCalledTimes(1)
84
+ })
85
+
86
+ it('done=true deletes the map entry; next call creates a fresh stream', async () => {
87
+ const map = new Map<string, DraftStreamHandle>()
88
+
89
+ const { stream: first, settled } = modelStreamReply({ bot, map, chatId: '1', text: 'a', done: true })
90
+ await microtaskFlush()
91
+ await settled
92
+ expect(map.has('1:_')).toBe(false)
93
+ expect(first.isFinal()).toBe(true)
94
+
95
+ const { stream: second } = modelStreamReply({ bot, map, chatId: '1', text: 'next turn' })
96
+ await microtaskFlush()
97
+
98
+ expect(first).not.toBe(second)
99
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2)
100
+ expect(second.getMessageId()).toBe(501)
101
+ })
102
+
103
+ it('different chats keep independent streams (no crosstalk)', async () => {
104
+ const map = new Map<string, DraftStreamHandle>()
105
+
106
+ modelStreamReply({ bot, map, chatId: '1', text: 'chat A' })
107
+ modelStreamReply({ bot, map, chatId: '2', text: 'chat B' })
108
+ await microtaskFlush()
109
+
110
+ expect(map.size).toBe(2)
111
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2)
112
+ const chatIds = bot.api.sendMessage.mock.calls.map(c => c[0]).sort()
113
+ expect(chatIds).toEqual(['1', '2'])
114
+ })
115
+
116
+ it('same chat, different threads → independent streams (forum topic safety)', async () => {
117
+ const map = new Map<string, DraftStreamHandle>()
118
+
119
+ modelStreamReply({ bot, map, chatId: '1', threadId: 10, text: 'topic A' })
120
+ modelStreamReply({ bot, map, chatId: '1', threadId: 20, text: 'topic B' })
121
+ await microtaskFlush()
122
+
123
+ expect(map.size).toBe(2)
124
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2)
125
+ const threads = bot.api.sendMessage.mock.calls.map(c => c[2]?.message_thread_id).sort()
126
+ expect(threads).toEqual([10, 20])
127
+ })
128
+
129
+ it('pty preview → stream_reply handoff: second call edits the PTY message in place', async () => {
130
+ const map = new Map<string, DraftStreamHandle>()
131
+
132
+ const ptyStream = createStreamController({ bot, chatId: '1', parseMode: 'HTML', throttleMs: 600 })
133
+ map.set('1:_', ptyStream)
134
+ void ptyStream.update('<i>drafting…</i>')
135
+ await microtaskFlush()
136
+
137
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
138
+ const previewId = ptyStream.getMessageId()
139
+ expect(previewId).toBe(500)
140
+
141
+ vi.advanceTimersByTime(600)
142
+ await microtaskFlush()
143
+ const { settled } = modelStreamReply({
144
+ bot, map, chatId: '1', parseMode: 'HTML', text: 'final answer', done: true,
145
+ })
146
+ await microtaskFlush()
147
+ await settled
148
+
149
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
150
+ expect(bot.api.editMessageText).toHaveBeenCalled()
151
+ expect(bot.api.editMessageText.mock.calls[0][1]).toBe(previewId)
152
+ expect(map.has('1:_')).toBe(false)
153
+ })
154
+
155
+ it('rapid stream_reply burst coalesces to one send + one edit with latest text', async () => {
156
+ // The first update fires send('one') immediately. While that's
157
+ // in-flight, updates 'two'/'three'/'four' overwrite pendingText
158
+ // synchronously. draft-stream's flushLoop drains pendingText to
159
+ // 'four' in a single follow-up edit after send resolves. Net:
160
+ // 4 update() calls → 1 send + 1 edit with the latest text.
161
+ const map = new Map<string, DraftStreamHandle>()
162
+
163
+ for (const t of ['one', 'two', 'three', 'four']) {
164
+ modelStreamReply({ bot, map, chatId: '1', text: t, throttleMs: 1000 })
165
+ }
166
+ await microtaskFlush()
167
+
168
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
169
+ expect(bot.api.sendMessage.mock.calls[0][1]).toBe('one')
170
+ expect(bot.api.editMessageText).toHaveBeenCalledTimes(1)
171
+ expect(bot.api.editMessageText.mock.calls[0][2]).toBe('four')
172
+
173
+ // No further work scheduled; advancing timers should be a no-op.
174
+ vi.advanceTimersByTime(5000)
175
+ await microtaskFlush()
176
+ expect(bot.api.editMessageText).toHaveBeenCalledTimes(1)
177
+ })
178
+
179
+ it('done=true after pending text still flushes the final snapshot', async () => {
180
+ const map = new Map<string, DraftStreamHandle>()
181
+
182
+ modelStreamReply({ bot, map, chatId: '1', text: 'drafting…', throttleMs: 1000 })
183
+ await microtaskFlush()
184
+ const { settled } = modelStreamReply({
185
+ bot, map, chatId: '1', text: 'final answer', done: true, throttleMs: 1000,
186
+ })
187
+ await microtaskFlush()
188
+ await settled
189
+
190
+ expect(bot.api.editMessageText).toHaveBeenCalledTimes(1)
191
+ expect(bot.api.editMessageText.mock.calls[0][2]).toBe('final answer')
192
+ expect(map.size).toBe(0)
193
+ })
194
+
195
+ it('failed first send leaves a retry-able stream: next update re-attempts send', async () => {
196
+ // First send fails; draft-stream swallows the error, leaves
197
+ // messageId=null. A subsequent update() triggers a fresh send on the
198
+ // same stream object — this is what makes a momentary network blip
199
+ // recoverable without the caller needing to evict-and-recreate.
200
+ bot.api.sendMessage
201
+ .mockImplementationOnce(async () => { throw new Error('network down') })
202
+ .mockImplementationOnce(async () => ({ message_id: 777 }))
203
+
204
+ const map = new Map<string, DraftStreamHandle>()
205
+ const { stream } = modelStreamReply({ bot, map, chatId: '1', text: 'first try' })
206
+ await microtaskFlush()
207
+
208
+ expect(stream.getMessageId()).toBeNull()
209
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
210
+
211
+ // Caller issues next snapshot — should retry send, not try to edit null.
212
+ vi.advanceTimersByTime(1000)
213
+ modelStreamReply({ bot, map, chatId: '1', text: 'second try' })
214
+ await microtaskFlush()
215
+
216
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2)
217
+ expect(bot.api.editMessageText).not.toHaveBeenCalled()
218
+ expect(stream.getMessageId()).toBe(777)
219
+ })
220
+ })
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // suppressPtyPreview claim/release — the "duplicate Telegram message" race
224
+ // ---------------------------------------------------------------------------
225
+ //
226
+ // The reply tool handler claims the stream by:
227
+ // 1. adding the streamKey to suppressPtyPreview BEFORE touching the stream
228
+ // 2. finalizing and deleting the active stream
229
+ // 3. sending the canonical reply
230
+ // 4. NOT releasing the suppress lock in the `finally` — turn_end does that
231
+ //
232
+ // This prevents the PTY tail from seeing an empty activeDraftStreams in step
233
+ // 2 and creating a fresh preview that would surface as a duplicate.
234
+
235
+ function modelPtyPartial(params: {
236
+ bot: ReturnType<typeof createMockBot>
237
+ map: Map<string, DraftStreamHandle>
238
+ suppress: Set<string>
239
+ chatId: string
240
+ threadId?: number
241
+ text: string
242
+ throttleMs?: number
243
+ }): DraftStreamHandle | null {
244
+ const key = streamKey(params.chatId, params.threadId)
245
+ if (params.suppress.has(key)) return null
246
+ let stream = params.map.get(key)
247
+ if (!stream) {
248
+ stream = createStreamController({
249
+ bot: params.bot,
250
+ chatId: params.chatId,
251
+ threadId: params.threadId,
252
+ parseMode: 'HTML',
253
+ throttleMs: params.throttleMs ?? 600,
254
+ })
255
+ params.map.set(key, stream)
256
+ }
257
+ void stream.update(params.text)
258
+ return stream
259
+ }
260
+
261
+ async function modelReplyClaim(params: {
262
+ bot: ReturnType<typeof createMockBot>
263
+ map: Map<string, DraftStreamHandle>
264
+ suppress: Set<string>
265
+ chatId: string
266
+ threadId?: number
267
+ }): Promise<{ previewId: number | null }> {
268
+ const key = streamKey(params.chatId, params.threadId)
269
+ params.suppress.add(key) // step 1: claim BEFORE touching the stream
270
+ const open = params.map.get(key)
271
+ let previewId: number | null = null
272
+ if (open && !open.isFinal()) {
273
+ await open.finalize()
274
+ previewId = open.getMessageId()
275
+ params.map.delete(key)
276
+ }
277
+ return { previewId }
278
+ }
279
+
280
+ function modelTurnEnd(params: {
281
+ suppress: Set<string>
282
+ chatId: string
283
+ threadId?: number
284
+ }): void {
285
+ params.suppress.delete(streamKey(params.chatId, params.threadId))
286
+ }
287
+
288
+ describe('suppressPtyPreview claim/release race', () => {
289
+ const bot = createMockBot()
290
+ installBotResetHook(bot)
291
+
292
+ beforeEach(() => vi.useFakeTimers())
293
+ afterEach(() => vi.useRealTimers())
294
+
295
+ it('PTY partials during a reply claim are dropped (no duplicate send)', async () => {
296
+ const map = new Map<string, DraftStreamHandle>()
297
+ const suppress = new Set<string>()
298
+
299
+ // Step 1: PTY creates a preview
300
+ modelPtyPartial({ bot, map, suppress, chatId: '1', text: 'drafting' })
301
+ await microtaskFlush()
302
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
303
+
304
+ // Step 2: reply claims — finalizes preview, deletes map entry
305
+ const { previewId } = await modelReplyClaim({ bot, map, suppress, chatId: '1' })
306
+ expect(previewId).toBe(500)
307
+ expect(map.has('1:_')).toBe(false)
308
+ expect(suppress.has('1:_')).toBe(true)
309
+
310
+ // Step 3: while suppressed, PTY fires another partial — should be dropped
311
+ const result = modelPtyPartial({ bot, map, suppress, chatId: '1', text: 'more content' })
312
+ await microtaskFlush()
313
+
314
+ expect(result).toBeNull()
315
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1) // no duplicate send
316
+ expect(map.has('1:_')).toBe(false) // no ghost stream created
317
+ })
318
+
319
+ it('claim is added BEFORE stream deletion (race-tight ordering)', async () => {
320
+ // If suppress.add happens AFTER map.delete, a PTY partial firing in
321
+ // between would create a new stream. We model the claim in the exact
322
+ // order the real code uses — any reordering here should fail this test.
323
+ const map = new Map<string, DraftStreamHandle>()
324
+ const suppress = new Set<string>()
325
+
326
+ modelPtyPartial({ bot, map, suppress, chatId: '1', text: 'preview' })
327
+ await microtaskFlush()
328
+
329
+ // Interleave: start claim, but simulate a partial arriving mid-claim.
330
+ // In the real code, suppress.add is synchronous and happens before any
331
+ // await. So by the time PTY could fire, suppress is already set.
332
+ suppress.add('1:_') // the claim's first action
333
+ const result = modelPtyPartial({ bot, map, suppress, chatId: '1', text: 'racy partial' })
334
+ await microtaskFlush()
335
+
336
+ expect(result).toBeNull()
337
+ // Finish the claim
338
+ const open = map.get('1:_')!
339
+ await open.finalize()
340
+ map.delete('1:_')
341
+
342
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1) // only the original
343
+ })
344
+
345
+ it('turn_end releases suppress; next turn PTY partial creates a fresh stream', async () => {
346
+ const map = new Map<string, DraftStreamHandle>()
347
+ const suppress = new Set<string>()
348
+
349
+ modelPtyPartial({ bot, map, suppress, chatId: '1', text: 'first preview' })
350
+ await microtaskFlush()
351
+ await modelReplyClaim({ bot, map, suppress, chatId: '1' })
352
+
353
+ // Reply lands (simulated by a direct send). Turn ends.
354
+ modelTurnEnd({ suppress, chatId: '1' })
355
+ expect(suppress.has('1:_')).toBe(false)
356
+
357
+ // Next turn — a new PTY partial can now create a stream again.
358
+ const result = modelPtyPartial({ bot, map, suppress, chatId: '1', text: 'next turn preview' })
359
+ await microtaskFlush()
360
+
361
+ expect(result).not.toBeNull()
362
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2) // original preview + new one
363
+ })
364
+
365
+ it('suppress is scoped by chat+thread — claim in chat A does not block chat B', async () => {
366
+ const map = new Map<string, DraftStreamHandle>()
367
+ const suppress = new Set<string>()
368
+
369
+ modelPtyPartial({ bot, map, suppress, chatId: 'A', text: 'A draft' })
370
+ await microtaskFlush()
371
+ await modelReplyClaim({ bot, map, suppress, chatId: 'A' })
372
+
373
+ // Chat B is untouched by A's claim — a PTY partial for B should work
374
+ const result = modelPtyPartial({ bot, map, suppress, chatId: 'B', text: 'B draft' })
375
+ await microtaskFlush()
376
+
377
+ expect(result).not.toBeNull()
378
+ expect(suppress.has('A:_')).toBe(true)
379
+ expect(suppress.has('B:_')).toBe(false)
380
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2)
381
+ })
382
+ })
383
+
384
+ // ---------------------------------------------------------------------------
385
+ // PTY activity-lane wiring — onActivity → stream_reply on lane "activity"
386
+ // ---------------------------------------------------------------------------
387
+ //
388
+ // These tests model the wiring added in server.ts: when pty-tail's
389
+ // `onActivity(text)` fires, the plugin pushes `text` through
390
+ // handleStreamReply with `lane: "activity"`, producing a distinct Telegram
391
+ // message (different message id) from the answer-lane stream.
392
+ //
393
+ // We don't import server.ts directly (it has top-level side effects: it
394
+ // connects the bot, starts tails, etc.). Instead we reproduce the wire
395
+ // shape by calling handleStreamReply with the same lane arg the real code
396
+ // uses — that exercises stream-reply-handler's streamKey + Map behavior
397
+ // which is where the two-lane separation actually lives.
398
+
399
+ import { handleStreamReply, type StreamReplyDeps, type StreamReplyState } from '../stream-reply-handler.js'
400
+
401
+ function makeActivityDeps(
402
+ bot: ReturnType<typeof createMockBot>,
403
+ overrides?: Partial<StreamReplyDeps>,
404
+ ): StreamReplyDeps {
405
+ return {
406
+ bot,
407
+ markdownToHtml: (t) => t,
408
+ escapeMarkdownV2: (t) => t,
409
+ repairEscapedWhitespace: (t) => t,
410
+ takeHandoffPrefix: () => '',
411
+ assertAllowedChat: () => {},
412
+ resolveThreadId: (_, explicit) => (explicit != null ? Number(explicit) : undefined),
413
+ disableLinkPreview: true,
414
+ defaultFormat: 'text',
415
+ logStreamingEvent: () => {},
416
+ endStatusReaction: () => {},
417
+ historyEnabled: false,
418
+ recordOutbound: () => {},
419
+ writeError: () => {},
420
+ throttleMs: 600,
421
+ ...overrides,
422
+ }
423
+ }
424
+
425
+ describe('PTY activity-lane wiring (onActivity → stream_reply lane="activity")', () => {
426
+ const bot = createMockBot()
427
+ installBotResetHook(bot)
428
+
429
+ beforeEach(() => vi.useFakeTimers())
430
+ afterEach(() => vi.useRealTimers())
431
+
432
+ it('onActivity callback fires handleStreamReply on lane "activity" with expected text', async () => {
433
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
434
+ const deps = makeActivityDeps(bot)
435
+
436
+ // Simulate what server.ts's handlePtyActivity does when pty-tail emits
437
+ // the first activity line.
438
+ const onActivity = (text: string) => {
439
+ void handleStreamReply(
440
+ { chat_id: '1', text, lane: 'activity', format: 'text' },
441
+ state,
442
+ deps,
443
+ )
444
+ }
445
+
446
+ onActivity('Running Bash: git status')
447
+ await microtaskFlush()
448
+
449
+ // Lane-specific stream key must be present; the default (answer) lane
450
+ // must NOT — activity is its own channel.
451
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(true)
452
+ expect(state.activeDraftStreams.has('1:_')).toBe(false)
453
+
454
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(1)
455
+ expect(bot.api.sendMessage.mock.calls[0][0]).toBe('1')
456
+ expect(bot.api.sendMessage.mock.calls[0][1]).toBe('Running Bash: git status')
457
+ })
458
+
459
+ it('two concurrent lanes (activity + answer) each get their own message id', async () => {
460
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
461
+ const deps = makeActivityDeps(bot)
462
+
463
+ // Activity lane fires first — model started running tools
464
+ const pActivity = handleStreamReply(
465
+ { chat_id: '1', text: 'Running Bash: ls', lane: 'activity', format: 'text' },
466
+ state,
467
+ deps,
468
+ )
469
+ await microtaskFlush()
470
+ const rActivity = await pActivity
471
+
472
+ // Answer lane — model starts emitting the reply
473
+ const pAnswer = handleStreamReply(
474
+ { chat_id: '1', text: 'Here is the answer', format: 'text' }, // default lane
475
+ state,
476
+ deps,
477
+ )
478
+ await microtaskFlush()
479
+ const rAnswer = await pAnswer
480
+
481
+ // Both lanes have distinct stream keys in the map
482
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(true)
483
+ expect(state.activeDraftStreams.has('1:_')).toBe(true)
484
+ expect(state.activeDraftStreams.size).toBe(2)
485
+
486
+ // Two separate Telegram sends — two message ids
487
+ expect(bot.api.sendMessage).toHaveBeenCalledTimes(2)
488
+ expect(rActivity.messageId).not.toBeNull()
489
+ expect(rAnswer.messageId).not.toBeNull()
490
+ expect(rActivity.messageId).not.toBe(rAnswer.messageId)
491
+ })
492
+
493
+ // ---- closeActivityLane / turn-boundary coverage --------------------
494
+ //
495
+ // These cases model the `closeActivityLane(chatId, threadId)` helper in
496
+ // server.ts that fires on `turn_end`. It must (a) finalize + remove the
497
+ // activity-lane stream from the map, (b) be a no-op if no activity
498
+ // stream exists, and (c) allow a fresh activity stream on the NEXT
499
+ // turn (new message id — not editing the now-closed previous one).
500
+
501
+ function modelCloseActivityLane(
502
+ state: StreamReplyState,
503
+ chatId: string,
504
+ threadId?: number,
505
+ ): Promise<void> {
506
+ const key = `${chatId}:${threadId ?? '_'}:activity`
507
+ const stream = state.activeDraftStreams.get(key)
508
+ if (stream == null) return Promise.resolve()
509
+ state.activeDraftStreams.delete(key)
510
+ return stream.finalize().catch(() => { /* swallow */ })
511
+ }
512
+
513
+ it('closeActivityLane removes the stream entry and is idempotent', async () => {
514
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
515
+ const deps = makeActivityDeps(bot)
516
+
517
+ await handleStreamReply(
518
+ { chat_id: '1', text: 'Running Bash: ls', lane: 'activity', format: 'text' },
519
+ state, deps,
520
+ )
521
+ await microtaskFlush()
522
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(true)
523
+
524
+ await modelCloseActivityLane(state, '1', undefined)
525
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(false)
526
+
527
+ // Calling again with no stream present must not throw.
528
+ await expect(modelCloseActivityLane(state, '1', undefined)).resolves.toBeUndefined()
529
+ await expect(modelCloseActivityLane(state, 'never-existed', undefined)).resolves.toBeUndefined()
530
+ })
531
+
532
+ it('next turn after closeActivityLane creates a fresh message (no edit of closed stream)', async () => {
533
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
534
+ const deps = makeActivityDeps(bot)
535
+
536
+ // Turn 1
537
+ await handleStreamReply(
538
+ { chat_id: '1', text: 'Running Bash: ls', lane: 'activity', format: 'text' },
539
+ state, deps,
540
+ )
541
+ await microtaskFlush()
542
+ const firstId = state.activeDraftStreams.get('1:_:activity')!.getMessageId()
543
+ const sendsBefore = bot.api.sendMessage.mock.calls.length
544
+
545
+ // Turn boundary
546
+ await modelCloseActivityLane(state, '1', undefined)
547
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(false)
548
+
549
+ // Turn 2 — new activity must send a fresh message, not edit the old one
550
+ await handleStreamReply(
551
+ { chat_id: '1', text: 'Reading file: foo.ts', lane: 'activity', format: 'text' },
552
+ state, deps,
553
+ )
554
+ await microtaskFlush()
555
+
556
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(true)
557
+ const secondId = state.activeDraftStreams.get('1:_:activity')!.getMessageId()
558
+ expect(secondId).not.toBe(firstId)
559
+ expect(bot.api.sendMessage.mock.calls.length).toBe(sendsBefore + 1)
560
+ })
561
+
562
+ it('closeActivityLane leaves the answer lane untouched (independent lifecycle)', async () => {
563
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
564
+ const deps = makeActivityDeps(bot)
565
+
566
+ await handleStreamReply(
567
+ { chat_id: '1', text: 'Running Bash: ls', lane: 'activity', format: 'text' },
568
+ state, deps,
569
+ )
570
+ await microtaskFlush()
571
+ await handleStreamReply(
572
+ { chat_id: '1', text: 'Drafting answer…', format: 'text' },
573
+ state, deps,
574
+ )
575
+ await microtaskFlush()
576
+
577
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(true)
578
+ expect(state.activeDraftStreams.has('1:_')).toBe(true)
579
+
580
+ await modelCloseActivityLane(state, '1', undefined)
581
+
582
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(false)
583
+ // Answer lane survives — turn_end finalizes answer via its own
584
+ // done=true path, not via closeActivityLane.
585
+ expect(state.activeDraftStreams.has('1:_')).toBe(true)
586
+ })
587
+
588
+ // handlePtyActivity's "drop when no chat id" guard lives in server.ts.
589
+ // We model the guard shape directly: when currentSessionChatId is null
590
+ // the call short-circuits BEFORE handleStreamReply, so nothing lands
591
+ // in the map and no bot traffic fires.
592
+ it('handlePtyActivity guard: drops events when currentSessionChatId is null', async () => {
593
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
594
+ const deps = makeActivityDeps(bot)
595
+
596
+ let currentSessionChatId: string | null = null
597
+ const handlePtyActivity = (text: string) => {
598
+ if (currentSessionChatId == null) return
599
+ void handleStreamReply(
600
+ { chat_id: currentSessionChatId, text, lane: 'activity', format: 'text' },
601
+ state, deps,
602
+ )
603
+ }
604
+
605
+ const sendsBefore = bot.api.sendMessage.mock.calls.length
606
+ handlePtyActivity('Running Bash: git status')
607
+ await microtaskFlush()
608
+
609
+ expect(state.activeDraftStreams.size).toBe(0)
610
+ expect(bot.api.sendMessage.mock.calls.length).toBe(sendsBefore)
611
+
612
+ // Once chat id is known, the next activity line DOES land.
613
+ currentSessionChatId = '1'
614
+ handlePtyActivity('Running Bash: git status')
615
+ await microtaskFlush()
616
+
617
+ expect(state.activeDraftStreams.has('1:_:activity')).toBe(true)
618
+ expect(bot.api.sendMessage.mock.calls.length).toBe(sendsBefore + 1)
619
+ })
620
+
621
+ it('activity lane across chat ids: closing lane for chat A does not touch chat B', async () => {
622
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
623
+ const deps = makeActivityDeps(bot)
624
+
625
+ await handleStreamReply(
626
+ { chat_id: 'A', text: 'A running', lane: 'activity', format: 'text' },
627
+ state, deps,
628
+ )
629
+ await microtaskFlush()
630
+ await handleStreamReply(
631
+ { chat_id: 'B', text: 'B running', lane: 'activity', format: 'text' },
632
+ state, deps,
633
+ )
634
+ await microtaskFlush()
635
+
636
+ expect(state.activeDraftStreams.has('A:_:activity')).toBe(true)
637
+ expect(state.activeDraftStreams.has('B:_:activity')).toBe(true)
638
+
639
+ await modelCloseActivityLane(state, 'A', undefined)
640
+
641
+ expect(state.activeDraftStreams.has('A:_:activity')).toBe(false)
642
+ expect(state.activeDraftStreams.has('B:_:activity')).toBe(true)
643
+ })
644
+
645
+ it('activity lane updates edit the activity message, not the answer message', async () => {
646
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
647
+ const deps = makeActivityDeps(bot)
648
+
649
+ // Seed both lanes
650
+ await handleStreamReply(
651
+ { chat_id: '1', text: 'Running Bash: ls', lane: 'activity', format: 'text' },
652
+ state, deps,
653
+ )
654
+ await microtaskFlush()
655
+ await handleStreamReply(
656
+ { chat_id: '1', text: 'Drafting answer…', format: 'text' },
657
+ state, deps,
658
+ )
659
+ await microtaskFlush()
660
+
661
+ const activityId = state.activeDraftStreams.get('1:_:activity')!.getMessageId()
662
+ const answerId = state.activeDraftStreams.get('1:_')!.getMessageId()
663
+ expect(activityId).not.toBe(answerId)
664
+
665
+ // Advance past throttle window so subsequent updates can flush
666
+ vi.advanceTimersByTime(700)
667
+ await microtaskFlush()
668
+
669
+ // Next activity update should edit the activity message
670
+ await handleStreamReply(
671
+ { chat_id: '1', text: 'Reading file: foo.ts', lane: 'activity', format: 'text' },
672
+ state, deps,
673
+ )
674
+ await microtaskFlush()
675
+
676
+ const editCalls = bot.api.editMessageText.mock.calls
677
+ expect(editCalls.length).toBeGreaterThanOrEqual(1)
678
+ // The most recent edit must have targeted the activity message id
679
+ const lastEdit = editCalls[editCalls.length - 1]
680
+ expect(lastEdit[1]).toBe(activityId)
681
+ expect(lastEdit[2]).toBe('Reading file: foo.ts')
682
+ })
683
+
684
+ // ---- closeProgressLane / turn-boundary coverage --------------------
685
+ //
686
+ // Bug 2 regression: on turn_end the server also closes the `progress`
687
+ // lane (the one the progress-card driver owns). Without this, the
688
+ // same Telegram messageId is re-edited across turns and the user
689
+ // sees a visible shrink-flicker when a new turn opens with 0 items
690
+ // on top of the previous turn's 10-item "Done" state. These tests
691
+ // model `closeProgressLane(chatId, threadId)` exactly like the
692
+ // existing closeActivityLane cases above.
693
+
694
+ function modelCloseProgressLane(
695
+ state: StreamReplyState,
696
+ chatId: string,
697
+ threadId?: number,
698
+ ): Promise<void> {
699
+ const key = `${chatId}:${threadId ?? '_'}:progress`
700
+ const stream = state.activeDraftStreams.get(key)
701
+ if (stream == null) return Promise.resolve()
702
+ state.activeDraftStreams.delete(key)
703
+ return stream.finalize().catch(() => { /* swallow */ })
704
+ }
705
+
706
+ it('closeProgressLane removes the stream entry and is idempotent', async () => {
707
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
708
+ const deps = makeActivityDeps(bot)
709
+
710
+ await handleStreamReply(
711
+ { chat_id: '1', text: '⚙️ Working…', lane: 'progress', format: 'html' },
712
+ state, deps,
713
+ )
714
+ await microtaskFlush()
715
+ expect(state.activeDraftStreams.has('1:_:progress')).toBe(true)
716
+
717
+ await modelCloseProgressLane(state, '1', undefined)
718
+ expect(state.activeDraftStreams.has('1:_:progress')).toBe(false)
719
+
720
+ // Idempotent.
721
+ await expect(modelCloseProgressLane(state, '1', undefined)).resolves.toBeUndefined()
722
+ await expect(modelCloseProgressLane(state, 'never', undefined)).resolves.toBeUndefined()
723
+ })
724
+
725
+ it('next turn after closeProgressLane posts a FRESH progress message (new messageId)', async () => {
726
+ const state: StreamReplyState = { activeDraftStreams: new Map<string, DraftStreamHandle>() }
727
+ const deps = makeActivityDeps(bot)
728
+
729
+ // Turn 1: progress card lives at some messageId.
730
+ await handleStreamReply(
731
+ { chat_id: '1', text: '⚙️ Working… (turn 1)', lane: 'progress', format: 'html' },
732
+ state, deps,
733
+ )
734
+ await microtaskFlush()
735
+ const firstProgressId = state.activeDraftStreams.get('1:_:progress')!.getMessageId()
736
+ const sendsBefore = bot.api.sendMessage.mock.calls.length
737
+
738
+ // turn_end closes the progress lane.
739
+ await modelCloseProgressLane(state, '1', undefined)
740
+ expect(state.activeDraftStreams.has('1:_:progress')).toBe(false)
741
+
742
+ // Turn 2: the fresh progress-card open must send a NEW message, not
743
+ // edit the previous one — this is what prevents the shrink-flicker
744
+ // described in the bug report.
745
+ await handleStreamReply(
746
+ { chat_id: '1', text: '⚙️ Working… (turn 2)', lane: 'progress', format: 'html' },
747
+ state, deps,
748
+ )
749
+ await microtaskFlush()
750
+
751
+ expect(state.activeDraftStreams.has('1:_:progress')).toBe(true)
752
+ const secondProgressId = state.activeDraftStreams.get('1:_:progress')!.getMessageId()
753
+ expect(secondProgressId).not.toBe(firstProgressId)
754
+ expect(bot.api.sendMessage.mock.calls.length).toBe(sendsBefore + 1)
755
+ })
756
+ })