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,685 @@
1
+ /**
2
+ * Telegram-flavored markdown→HTML rendering and chunking.
3
+ *
4
+ * Extracted from server.ts so tests can import these helpers without
5
+ * triggering the bot startup side effects (env loading, token check,
6
+ * grammy instantiation). server.ts re-exports the public API for
7
+ * backwards compatibility with any external callers.
8
+ *
9
+ * Three pieces:
10
+ * - markdownToHtml + isLikelyTelegramHtml: convert model output to
11
+ * Telegram-safe HTML, preserving any embedded whitelisted Telegram
12
+ * HTML tags so the model can mix markdown bold with raw <b>/<i>/<a>.
13
+ * - splitHtmlChunks: split a long HTML message into <=4096-char chunks
14
+ * that preserve open/close tag balance and don't bisect HTML entities.
15
+ * - escapeHtml: the three-char escape used everywhere.
16
+ */
17
+
18
+ /**
19
+ * Telegram-supported HTML tags. Anything outside this set is either
20
+ * unrecognized (Telegram strips it) or actively dangerous (the API
21
+ * rejects the message). Source: https://core.telegram.org/bots/api#html-style
22
+ */
23
+ export const TELEGRAM_HTML_TAGS = new Set([
24
+ 'b', 'strong',
25
+ 'i', 'em',
26
+ 'u', 'ins',
27
+ 's', 'strike', 'del',
28
+ 'span', // requires class="tg-spoiler"
29
+ 'tg-spoiler',
30
+ 'a',
31
+ 'tg-emoji',
32
+ 'code',
33
+ 'pre',
34
+ 'blockquote',
35
+ ])
36
+
37
+ /**
38
+ * Heuristic: does this look like already-rendered Telegram HTML rather
39
+ * than markdown waiting to be converted?
40
+ *
41
+ * Returns true when ALL the tags we find are recognized Telegram HTML
42
+ * tags AND there's at least one of them AND the text doesn't also have
43
+ * markdown-only syntax (** for bold, [text](url) for links). This is
44
+ * conservative: if the model wrote `<div>foo</div>` (not Telegram HTML),
45
+ * we treat it as markdown and escape it. If the model wrote `<b>foo</b>`,
46
+ * we trust it.
47
+ *
48
+ * Critical: we strip markdown code spans and fenced code blocks BEFORE
49
+ * scanning for tags, because the model frequently writes things like
50
+ * `\`<b>tag</b>\`` (an inline code example showing literal HTML). Without
51
+ * the strip, the heuristic would see `<b>` inside the code span and
52
+ * misclassify the whole text as raw HTML.
53
+ */
54
+ export function isLikelyTelegramHtml(text: string): boolean {
55
+ // Strip fenced code blocks first (greedy, cross-line)
56
+ let scanText = text.replace(/```[\s\S]*?```/g, '')
57
+ // Then strip inline code spans (single backticks, no newlines)
58
+ scanText = scanText.replace(/`[^`\n]+`/g, '')
59
+
60
+ // If the stripped text contains markdown-only syntax (**bold**,
61
+ // [text](url), or markdown headings), the caller is writing markdown
62
+ // even if they ALSO sprinkled some <b> tags in. Treat as markdown.
63
+ if (/\*\*[^\n*]+\*\*/.test(scanText)) return false
64
+ if (/\[[^\]]+\]\([^)]+\)/.test(scanText)) return false
65
+ if (/^#{1,6}\s+/m.test(scanText)) return false
66
+
67
+ // Now count remaining HTML tags
68
+ const tagMatches = scanText.matchAll(/<\/?([a-z][a-z0-9-]*)\b[^>]*>/gi)
69
+ let count = 0
70
+ for (const m of tagMatches) {
71
+ const tag = m[1].toLowerCase()
72
+ if (!TELEGRAM_HTML_TAGS.has(tag)) {
73
+ // Found an unsupported tag — caller didn't intend Telegram HTML
74
+ return false
75
+ }
76
+ count++
77
+ }
78
+ return count > 0
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Markdown table → Telegram HTML
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * Parse a contiguous block of lines as a markdown table.
87
+ *
88
+ * A valid markdown table requires:
89
+ * - A header row: | col | col | (leading/trailing pipes optional)
90
+ * - A separator row: | --- | --- | (cells are only dashes, colons, spaces)
91
+ * - At least one data row.
92
+ *
93
+ * The separator row is the discriminating signal — it prevents plain prose
94
+ * lines that happen to contain a pipe (e.g. `echo foo | bar`) from being
95
+ * mistaken for tables.
96
+ *
97
+ * Returns null when the block is not a valid table.
98
+ */
99
+ function parseMarkdownTable(lines: string[]): { headers: string[]; rows: string[][] } | null {
100
+ if (lines.length < 3) return null
101
+
102
+ // Separator line: cells contain only dashes, colons, and spaces.
103
+ const sepRe = /^\|?(?:[ \t]*:?-+:?[ \t]*\|)+[ \t]*:?-*:?[ \t]*\|?$/
104
+ // A pipe-delimited row: must contain at least one |
105
+ const rowRe = /\|/
106
+
107
+ // Find the separator line index (must be index 1 in this block)
108
+ if (!sepRe.test(lines[1].trim())) return null
109
+ // Double-check: the header row must also look like a table row
110
+ if (!rowRe.test(lines[0])) return null
111
+ // Must have at least one data row
112
+ if (lines.length < 3 || !rowRe.test(lines[2])) return null
113
+
114
+ const splitRow = (line: string): string[] =>
115
+ line
116
+ .replace(/^\|/, '')
117
+ .replace(/\|$/, '')
118
+ .split('|')
119
+ .map(c => c.trim())
120
+
121
+ const headers = splitRow(lines[0])
122
+ const rows: string[][] = []
123
+ for (let i = 2; i < lines.length; i++) {
124
+ if (!rowRe.test(lines[i])) break
125
+ rows.push(splitRow(lines[i]))
126
+ }
127
+
128
+ if (rows.length === 0) return null
129
+ return { headers, rows }
130
+ }
131
+
132
+ /**
133
+ * Render a parsed markdown table as Telegram-compatible HTML.
134
+ *
135
+ * Branch rules:
136
+ * - ≤3 columns AND ≤6 rows → bullet list:
137
+ * Each row is one bullet. First column in <b>; subsequent columns
138
+ * appended as " — value".
139
+ * - otherwise → <pre> block with padded columns.
140
+ */
141
+ function renderTable(headers: string[], rows: string[][]): string {
142
+ const colCount = headers.length
143
+ const rowCount = rows.length
144
+
145
+ if (colCount <= 3 && rowCount <= 6) {
146
+ // Bullet list rendering
147
+ const bullets = rows.map(row => {
148
+ // Normalise row length to match header count (guard empty cells)
149
+ const cells = headers.map((_, i) => (row[i] ?? '').trim())
150
+ const key = escapeHtml(cells[0] || '—')
151
+ const rest = cells
152
+ .slice(1)
153
+ .filter(v => v !== '')
154
+ .map(v => ` — ${escapeHtml(v)}`)
155
+ .join('')
156
+ return `• <b>${key}</b>${rest}`
157
+ })
158
+ // Prepend header names as a label line when there are 2+ columns
159
+ const headerLine =
160
+ colCount >= 2
161
+ ? `<b>${headers.map(h => escapeHtml(h)).join(' / ')}</b>\n`
162
+ : ''
163
+ return headerLine + bullets.join('\n')
164
+ }
165
+
166
+ // Pre-block with padded columns
167
+ // Compute column widths across headers + all rows
168
+ const allRows = [headers, ...rows]
169
+ const widths = headers.map((_, ci) =>
170
+ Math.max(...allRows.map(r => (r[ci] ?? '').length))
171
+ )
172
+ const pad = (s: string, w: number) => s + ' '.repeat(Math.max(0, w - s.length))
173
+
174
+ const formatRow = (r: string[]) =>
175
+ headers.map((_, ci) => pad(r[ci] ?? '', widths[ci])).join(' ')
176
+
177
+ const sepLine = widths.map(w => '-'.repeat(w)).join(' ')
178
+
179
+ const lines = [
180
+ formatRow(headers),
181
+ sepLine,
182
+ ...rows.map(r => formatRow(r)),
183
+ ]
184
+ return `<pre>${escapeHtml(lines.join('\n'))}</pre>`
185
+ }
186
+
187
+ /**
188
+ * Replace markdown table blocks in `text` with rendered HTML, storing the
189
+ * rendered output in `store` and emitting `placeholderPrefix<n>\x00` tokens
190
+ * so the rest of the pipeline does not re-process them.
191
+ *
192
+ * Tables are identified by their separator line (`| --- |`) which prevents
193
+ * plain prose containing a pipe (e.g. `echo foo | bar`) from being mistaken
194
+ * for a table. Fenced code blocks are extracted before this runs, so
195
+ * table-looking rows inside ``` blocks are already protected.
196
+ */
197
+ function extractMarkdownTables(
198
+ text: string,
199
+ store: string[],
200
+ placeholderPrefix: string,
201
+ ): string {
202
+ const inputLines = text.split('\n')
203
+ const outputLines: string[] = []
204
+ let i = 0
205
+
206
+ while (i < inputLines.length) {
207
+ const line = inputLines[i]
208
+ if (!line.includes('|')) {
209
+ outputLines.push(line)
210
+ i++
211
+ continue
212
+ }
213
+
214
+ // Collect a run of pipe-containing lines as a candidate block
215
+ let j = i
216
+ while (j < inputLines.length && inputLines[j].includes('|')) {
217
+ j++
218
+ }
219
+ const block = inputLines.slice(i, j)
220
+
221
+ const parsed = parseMarkdownTable(block)
222
+ if (parsed) {
223
+ const tableLineCount = 2 + parsed.rows.length
224
+ const remainder = block.slice(tableLineCount)
225
+ const idx = store.length
226
+ store.push(renderTable(parsed.headers, parsed.rows))
227
+ outputLines.push(`${placeholderPrefix}${idx}\x00`)
228
+ for (const r of remainder) outputLines.push(r)
229
+ i = j
230
+ } else {
231
+ for (const b of block) outputLines.push(b)
232
+ i = j
233
+ }
234
+ }
235
+
236
+ return outputLines.join('\n')
237
+ }
238
+
239
+ /**
240
+ * Convert markdown to Telegram-compatible HTML.
241
+ * Handles bold, italic, code, code blocks, strikethrough, links.
242
+ * Escapes HTML entities in plain text. Wraps file references in <code>.
243
+ * Preserves embedded whitelisted Telegram HTML tags so the model can
244
+ * mix markdown and raw HTML in the same message.
245
+ */
246
+ export function markdownToHtml(text: string): string {
247
+ // Smart pass-through: if the input is already valid Telegram HTML
248
+ // (every tag is in the supported list), trust the caller and return
249
+ // it unchanged.
250
+ if (isLikelyTelegramHtml(text)) {
251
+ return text
252
+ }
253
+
254
+ // First, extract code blocks and inline code to protect them from other transforms.
255
+ const codeBlocks: string[] = []
256
+ const BLOCK_PH = '\x00CODEBLOCK'
257
+ const INLINE_PH = '\x00CODEINLINE'
258
+
259
+ // Tables are extracted after code blocks so that table-looking rows inside
260
+ // fenced code blocks are already parked in codeBlocks placeholders and
261
+ // won't be touched. Rendered table HTML is stored alongside codeBlocks and
262
+ // uses the same placeholder so restoration happens in a single pass.
263
+ const TABLE_PH = '\x00TABLEBLOCK'
264
+
265
+ // Code blocks: ```lang\ncode\n```
266
+ let result = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, lang: string, code: string) => {
267
+ const escaped = escapeHtml(code.replace(/\n$/, ''))
268
+ const cls = lang ? ` class="language-${lang}"` : ''
269
+ const idx = codeBlocks.length
270
+ codeBlocks.push(`<pre><code${cls}>${escaped}</code></pre>`)
271
+ return `${BLOCK_PH}${idx}\x00`
272
+ })
273
+
274
+ // Extract markdown tables after fenced code blocks are parked. Rendered
275
+ // HTML is stored in codeBlocks (shared store); TABLE_PH is a distinct
276
+ // prefix so the two restore regexes below can target each independently.
277
+ result = extractMarkdownTables(result, codeBlocks, TABLE_PH)
278
+
279
+ // Convert markdown headings (# / ## / ### ...) to bold lines on their
280
+ // own. Telegram has no <h1> tag, and rendering ## as plain text leaves
281
+ // ugly hash marks in the message.
282
+ result = result.replace(/^(#{1,6})\s+(.+?)\s*$/gm, (_m, _hashes, title: string) => {
283
+ return `**${title}**`
284
+ })
285
+
286
+ // Inline code: `code`
287
+ const inlineCodes: string[] = []
288
+ result = result.replace(/`([^`\n]+)`/g, (_m, code: string) => {
289
+ const idx = inlineCodes.length
290
+ inlineCodes.push(`<code>${escapeHtml(code)}</code>`)
291
+ return `${INLINE_PH}${idx}\x00`
292
+ })
293
+
294
+ // Telegram HTML tag pass-through. Extract any opening/closing tag
295
+ // whose name is in the whitelist into placeholders. The TEXT BETWEEN
296
+ // tags still flows through escapeHtml and the markdown conversions
297
+ // below, so `<b>**bold**</b>` and `<b>plain</b>` both work. Tags are
298
+ // restored verbatim at the very end.
299
+ const htmlTags: string[] = []
300
+ const HTMLTAG_PH = '\x00HTMLTAG'
301
+ const tagNamePattern = Array.from(TELEGRAM_HTML_TAGS).join('|')
302
+ const htmlTagRe = new RegExp(`</?(?:${tagNamePattern})\\b[^>]*>`, 'gi')
303
+ result = result.replace(htmlTagRe, (match: string) => {
304
+ const idx = htmlTags.length
305
+ htmlTags.push(match)
306
+ return `${HTMLTAG_PH}${idx}\x00`
307
+ })
308
+
309
+ // Escape HTML entities in remaining plain text
310
+ result = escapeHtml(result)
311
+
312
+ // Bold: **text** (must come before italic)
313
+ result = result.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>')
314
+
315
+ // Italic: *text* (single asterisk, not preceded by another *)
316
+ result = result.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<i>$1</i>')
317
+
318
+ // Italic: _text_ (underscore form). Lookarounds guard snake_case,
319
+ // __double__, and word-internal underscores. Emoji codepoints are not
320
+ // \w, so emoji-leading/trailing italics like `_📥 queued_` work correctly.
321
+ result = result.replace(/(?<![\w_])_(?!_)([^_\n]+?)_(?![\w_])/g, '<i>$1</i>')
322
+
323
+ // Strikethrough: ~~text~~
324
+ result = result.replace(/~~(.+?)~~/g, '<s>$1</s>')
325
+
326
+ // Restore inline-code, code-block, and table-block placeholders ONLY
327
+ // AFTER bold/italic/strike have run. If the inline-code placeholder
328
+ // is restored before italic, an inline-code span containing asterisks
329
+ // (e.g. `\`size_t *p\``) gets matched by the italic regex on the
330
+ // restored `<code>...*p</code>` buffer and produces invalid HTML
331
+ // that Telegram rejects with 400 Bad Request — sending the caller
332
+ // into a `format: text` fallback for the rest of the chunk. Same
333
+ // fault class for code blocks containing `**` literals. See #415.
334
+ result = result.replace(new RegExp(`${escapeHtml(BLOCK_PH)}(\\d+)${escapeHtml('\x00')}`, 'g'), (_m, idx) => codeBlocks[Number(idx)])
335
+ result = result.replace(new RegExp(`${escapeHtml(TABLE_PH)}(\\d+)${escapeHtml('\x00')}`, 'g'), (_m, idx) => codeBlocks[Number(idx)])
336
+ result = result.replace(new RegExp(`${escapeHtml(INLINE_PH)}(\\d+)${escapeHtml('\x00')}`, 'g'), (_m, idx) => inlineCodes[Number(idx)])
337
+
338
+ // Links: [text](url). Two safety requirements here:
339
+ //
340
+ // 1. URL scheme allowlist. Unrestricted href accepts `javascript:` and
341
+ // `data:` URIs; Telegram historically renders tg:// links directly
342
+ // (opening another bot) which is a phishing primitive. Anything not
343
+ // in the allowlist falls back to `#`.
344
+ //
345
+ // 2. Escape the URL before interpolating into the attribute. The HTML
346
+ // tag extraction above parks whitelisted tags in \x00HTMLTAG<n>\x00
347
+ // placeholders that get restored AFTER this replace. Without escaping
348
+ // the href value, an adversarial `[text](x"></a><a href="evil">)` in
349
+ // model output produces two <a> tags after placeholder restoration —
350
+ // the second hijacks the visible link target. escapeAttr covers both
351
+ // the placeholder-restoration attack and plain `"` breakout.
352
+ const ALLOWED_LINK_SCHEMES = /^(?:https?|mailto|tel|tg):/i
353
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, linkText: string, url: string) => {
354
+ const safe = ALLOWED_LINK_SCHEMES.test(url.trim()) ? url.trim() : '#'
355
+ return `<a href="${escapeHtml(safe)}">${linkText}</a>`
356
+ })
357
+
358
+ // File references: wrap filename.ext patterns in <code> tags.
359
+ // Lookbehind excludes `>` so we don't double-wrap filenames that are
360
+ // already inside a restored inline-code placeholder like
361
+ // `<code>settings.json</code>`. Without this, the regex matched the
362
+ // filename character immediately after the `>` of the opening <code>
363
+ // tag and re-wrapped it, producing `<code><code>settings.json</code></code>`.
364
+ result = result.replace(/(?<![<\/\w>])(\b[\w][\w.-]*\.(?:ts|js|py|rs|go|json|yaml|yml|toml|md|txt|sh|bash|zsh|css|html|xml|sql|env|cfg|conf|ini|log|csv|tsx|jsx|vue|svelte|rb|java|kt|swift|c|cpp|h|hpp|zig|asm|wasm|lock|mod|sum)\b)(?![^<]*>)/g, '<code>$1</code>')
365
+
366
+ // Restore preserved Telegram HTML tags (must run last so the file-ref
367
+ // regex above doesn't accidentally match characters inside our placeholders).
368
+ result = result.replace(new RegExp(`${escapeHtml(HTMLTAG_PH)}(\\d+)${escapeHtml('\x00')}`, 'g'), (_m, idx) => htmlTags[Number(idx)])
369
+
370
+ return result
371
+ }
372
+
373
+ export function escapeHtml(text: string): string {
374
+ // Also escape `"` so callers that interpolate into HTML attribute values
375
+ // don't need a second helper. Safe for tag-content use too.
376
+ return text
377
+ .replace(/&/g, '&amp;')
378
+ .replace(/</g, '&lt;')
379
+ .replace(/>/g, '&gt;')
380
+ .replace(/"/g, '&quot;')
381
+ }
382
+
383
+ // ---------------------------------------------------------------------------
384
+ // Output sanitizer — enforces fleet-wide Telegram formatting invariants
385
+ // ---------------------------------------------------------------------------
386
+
387
+ /**
388
+ * Normalize outbound Telegram HTML text against well-known invariants.
389
+ *
390
+ * Runs AFTER markdownToHtml, just before the text is sent to the Bot API.
391
+ * Conservative by design: only rewrites things that are universally wrong;
392
+ * leaves semantic decisions (where to bold, link choice, list-vs-prose) to
393
+ * the agent.
394
+ *
395
+ * Rules applied (in order):
396
+ * 1. Strip markdown heading markers (`## Foo` → `<b>Foo</b>\n\n`).
397
+ * Headings that survived the markdown→HTML pass (e.g. when the input
398
+ * was already HTML and passed through isLikelyTelegramHtml) would render
399
+ * as ugly `## Foo` plain text. Convert to bold + blank line.
400
+ * 2. Flatten nested bullet indentation: `\n - sub` → `\n· sub`.
401
+ * 3. Collapse 3+ consecutive blank lines to exactly 2.
402
+ * 4. Strip trailing whitespace on each line.
403
+ * 5. Ensure `<` `>` `&` inside `<code>` and `<pre>` blocks are
404
+ * HTML-escaped (idempotent: won't double-escape existing `&amp;` etc.).
405
+ *
406
+ * The function is idempotent: sanitize(sanitize(x)) === sanitize(x).
407
+ * Content inside `<code>` / `<pre>` blocks is excluded from rules 1–4.
408
+ */
409
+ export function sanitizeForTelegram(text: string): string {
410
+ // ── Phase 1: extract <code> and <pre> blocks so rules 1-4 don't touch them.
411
+ //
412
+ // We capture the full tag with its content so we can round-trip correctly.
413
+ // Placeholders are non-printing control sequences that cannot appear in
414
+ // normal text.
415
+ const CODE_PH = '\x00SANCODE'
416
+ const PRE_PH = '\x00SANPRE'
417
+ const codeSegments: string[] = []
418
+ const preSegments: string[] = []
419
+
420
+ // Extract <pre>...</pre> blocks first (they may contain <code> inside).
421
+ let result = text.replace(/<pre>([\s\S]*?)<\/pre>/gi, (_m, inner: string) => {
422
+ const idx = preSegments.length
423
+ // Rule 5: escape unescaped < > & inside pre blocks.
424
+ preSegments.push(`<pre>${escapeUnescapedEntities(inner)}</pre>`)
425
+ return `${PRE_PH}${idx}\x00`
426
+ })
427
+
428
+ // Extract standalone <code>...</code> blocks (not nested inside <pre>).
429
+ result = result.replace(/<code([^>]*)>([\s\S]*?)<\/code>/gi, (_m, attrs: string, inner: string) => {
430
+ const idx = codeSegments.length
431
+ // Rule 5: escape unescaped < > & inside code spans.
432
+ codeSegments.push(`<code${attrs}>${escapeUnescapedEntities(inner)}</code>`)
433
+ return `${CODE_PH}${idx}\x00`
434
+ })
435
+
436
+ // ── Phase 2: apply text-level rules to the remaining (non-code) content.
437
+
438
+ // Rule 1: strip markdown heading markers that survived markdown→HTML pass.
439
+ // Matches lines starting with one or more `#` followed by a space.
440
+ // Preserves the heading text as bold + trailing blank line.
441
+ result = result.replace(/^(#{1,6}) +(.+?)\s*$/gm, (_m, _hashes, title: string) => {
442
+ return `<b>${title}</b>\n`
443
+ })
444
+
445
+ // Rule 2: flatten nested bullet indentation.
446
+ // Matches lines with a tab OR 2+ spaces at the start followed by - or *.
447
+ // A single tab is treated as sufficient indentation (standard 4-space equiv).
448
+ // Converts to a middle-dot bullet so the sub-detail survives as readable text.
449
+ result = result.replace(/^(?:\t+[ \t]*|[ \t]{2,})[*-] /gm, '· ')
450
+
451
+ // Rule 4: strip trailing whitespace on each line.
452
+ result = result.replace(/[ \t]+$/gm, '')
453
+
454
+ // Rule 3: collapse 3+ consecutive blank lines to exactly 2.
455
+ // A "blank line" is a line that contains only optional whitespace (already
456
+ // stripped above, but let's be safe).
457
+ result = result.replace(/(\n[ \t]*){3,}/g, '\n\n')
458
+
459
+ // ── Phase 3: restore placeholders.
460
+ result = result.replace(new RegExp(`${CODE_PH}(\\d+)\x00`, 'g'), (_m, idx) => codeSegments[Number(idx)])
461
+ result = result.replace(new RegExp(`${PRE_PH}(\\d+)\x00`, 'g'), (_m, idx) => preSegments[Number(idx)])
462
+
463
+ return result
464
+ }
465
+
466
+ /**
467
+ * Escape `<`, `>`, and `&` characters that are NOT already part of an HTML
468
+ * entity or tag. Used inside `<code>` and `<pre>` content to correct
469
+ * unescaped characters without double-escaping existing `&amp;`, `&lt;`, etc.
470
+ *
471
+ * Strategy: we walk the string and escape `&` only when it is not the start
472
+ * of a valid entity (`&name;` or `&#digits;` or `&#xhex;`). We always escape
473
+ * bare `<` and `>` because they cannot appear literally inside code content
474
+ * that is correct Telegram HTML.
475
+ */
476
+ function escapeUnescapedEntities(inner: string): string {
477
+ // Escape bare & first: replace & that is NOT followed by a valid entity
478
+ // pattern. A valid entity is: &[a-zA-Z][a-zA-Z0-9]*; or &#[0-9]+; or &#x[0-9a-fA-F]+;
479
+ let out = inner.replace(/&(?!(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);)/g, '&amp;')
480
+ // Escape bare < and > (they should never appear literally in code content)
481
+ out = out.replace(/</g, '&lt;')
482
+ out = out.replace(/>/g, '&gt;')
483
+ return out
484
+ }
485
+
486
+ /**
487
+ * Repair LLM-side JSON escape bungles.
488
+ *
489
+ * Some MCP clients (and some LLM tool-call generators) occasionally emit a
490
+ * tool-argument string whose whitespace has been double-escaped — real
491
+ * newlines become the two-character sequence `\n`, tabs become `\t`, etc.
492
+ * The message then ships to Telegram intact and the user sees literal
493
+ * `\n\n` in the chat instead of paragraph breaks.
494
+ *
495
+ * Heuristic: if the text contains ZERO real newlines AND has at least one
496
+ * literal `\n`, `\r`, or `\t` escape sequence, the caller almost certainly
497
+ * intended those as real whitespace and the client serializer ate them.
498
+ * Unescape them (also `\\` and `\"`). If the text has any real newline,
499
+ * trust the caller exactly as given and do nothing — legitimate content
500
+ * may contain a literal `\n` inside a shell snippet or regex.
501
+ *
502
+ * This is intentionally narrow: it only fires on the clear bug signature
503
+ * (multi-line-looking content collapsed to one physical line). False
504
+ * positives on a single-line message that legitimately contains `\n` are
505
+ * possible but rare — users writing single-line shell snippets typically
506
+ * wrap them in backticks, and this runs before markdown→HTML so the
507
+ * unescape has no effect on text inside fenced code blocks if it already
508
+ * has real newlines around them.
509
+ */
510
+ export function repairEscapedWhitespace(text: string): string {
511
+ if (text.includes('\n') || text.includes('\r')) return text
512
+ if (!/\\[nrt"\\]/.test(text)) return text
513
+ // Order matters: protect existing `\\` first so `\\n` stays as `\n`
514
+ // literal and doesn't become a newline.
515
+ const BACKSLASH_PH = '\x00BKSL\x00'
516
+ return text
517
+ .replace(/\\\\/g, BACKSLASH_PH)
518
+ .replace(/\\n/g, '\n')
519
+ .replace(/\\r/g, '\r')
520
+ .replace(/\\t/g, '\t')
521
+ .replace(/\\"/g, '"')
522
+ .replace(new RegExp(BACKSLASH_PH, 'g'), '\\')
523
+ }
524
+
525
+ // ---------------------------------------------------------------------------
526
+ // Smart HTML chunking — preserves open/close tag boundaries
527
+ // ---------------------------------------------------------------------------
528
+
529
+ /**
530
+ * Split HTML text into chunks that fit within maxLen, preserving tag integrity.
531
+ * At split boundaries, open tags are closed and reopened in the next chunk.
532
+ * Prefers splitting at \n\n, then \n, then spaces.
533
+ */
534
+ export function splitHtmlChunks(html: string, maxLen = 4000): string[] {
535
+ if (html.length <= maxLen) return [html]
536
+
537
+ const chunks: string[] = []
538
+ let rest = html
539
+
540
+ while (rest.length > 0) {
541
+ if (rest.length <= maxLen) {
542
+ chunks.push(rest)
543
+ break
544
+ }
545
+
546
+ // Find a good split point
547
+ let cut = maxLen
548
+ const paraIdx = rest.lastIndexOf('\n\n', maxLen)
549
+ const lineIdx = rest.lastIndexOf('\n', maxLen)
550
+ const spaceIdx = rest.lastIndexOf(' ', maxLen)
551
+
552
+ if (paraIdx > maxLen / 3) {
553
+ cut = paraIdx
554
+ } else if (lineIdx > maxLen / 3) {
555
+ cut = lineIdx
556
+ } else if (spaceIdx > 0) {
557
+ cut = spaceIdx
558
+ }
559
+
560
+ // Defense-in-depth: refuse to split inside an HTML entity (&amp;,
561
+ // &lt;, &#x1f4a9;). If the cut would land mid-entity, back up to
562
+ // before the `&`. Telegram rejects messages with broken entities.
563
+ cut = backOffEntity(rest, cut)
564
+ // Same idea for a bisected tag: if the cut lands inside `<...>` (or
565
+ // between `<` and its closing `>`), back up to before the `<`.
566
+ // Otherwise we'd emit a chunk ending in `<a` or `<a href="..` which
567
+ // Telegram rejects outright.
568
+ cut = backOffOpenTag(rest, cut)
569
+ // Pathological: the tag-back-off retreated to 0 because `rest`
570
+ // begins with a tag and the nearest space we picked landed inside
571
+ // that tag. Fall back to the hard maxLen cut — that position lives
572
+ // in content past the opening tag (since the tag itself is at the
573
+ // start) so it won't bisect anything, and we make forward progress.
574
+ if (cut <= 0) {
575
+ cut = Math.min(maxLen, rest.length)
576
+ cut = backOffOpenTag(rest, cut)
577
+ // If even the maxLen cut bisects a tag, emit the whole remainder
578
+ // as one chunk rather than spin forever. Telegram will reject
579
+ // a 4k+ message before it rejects a split one, but this only
580
+ // fires on genuinely malformed input.
581
+ if (cut <= 0) cut = rest.length
582
+ }
583
+
584
+ let segment = rest.slice(0, cut)
585
+ rest = rest.slice(cut).replace(/^\n+/, '')
586
+
587
+ // Track open tags in this segment — we keep the FULL opening tag
588
+ // string (including attributes) so we can reopen `<a href="...">`
589
+ // in the next chunk without dropping the href.
590
+ const openTags = getOpenTags(segment)
591
+
592
+ // Close any open tags at the end of this chunk (by tag name)
593
+ for (let i = openTags.length - 1; i >= 0; i--) {
594
+ segment += `</${openTags[i].name}>`
595
+ }
596
+ chunks.push(segment)
597
+
598
+ // Reopen tags at the start of the next chunk, preserving attrs
599
+ if (rest.length > 0 && openTags.length > 0) {
600
+ const reopenPrefix = openTags.map(t => t.openTag).join('')
601
+ rest = reopenPrefix + rest
602
+ }
603
+ }
604
+
605
+ return chunks
606
+ }
607
+
608
+ /**
609
+ * If `cut` lies inside an HTML entity (a `&...;` sequence), back it up to
610
+ * just before the `&` so the chunk boundary doesn't bisect the entity.
611
+ */
612
+ function backOffEntity(text: string, cut: number): number {
613
+ if (cut <= 0 || cut >= text.length) return cut
614
+ // Look backward up to 10 chars for an unterminated entity
615
+ const lookback = Math.max(0, cut - 10)
616
+ for (let i = cut - 1; i >= lookback; i--) {
617
+ const ch = text[i]
618
+ if (ch === ';') return cut // entity already closed before cut → safe
619
+ if (ch === '&') {
620
+ const closeIdx = text.indexOf(';', cut)
621
+ if (closeIdx !== -1 && closeIdx - i <= 10) {
622
+ // The entity spans the cut — back up to just before the `&`
623
+ return i
624
+ }
625
+ return cut
626
+ }
627
+ }
628
+ return cut
629
+ }
630
+
631
+ /**
632
+ * If `cut` lands inside an HTML tag (between `<` and the next `>`), back
633
+ * up to before the `<`. Telegram rejects messages that contain a stray
634
+ * `<` without a matching `>` (e.g. chunk ending `<a href="..`).
635
+ */
636
+ function backOffOpenTag(text: string, cut: number): number {
637
+ if (cut <= 0 || cut >= text.length) return cut
638
+ // Scan backward for the nearest `<` or `>` before the cut. If we hit
639
+ // `>` first the cut is outside any tag → safe. If we hit `<` first,
640
+ // check whether its closing `>` lies at or after the cut → bisected.
641
+ for (let i = cut - 1; i >= 0; i--) {
642
+ const ch = text[i]
643
+ if (ch === '>') return cut
644
+ if (ch === '<') {
645
+ const closeIdx = text.indexOf('>', i)
646
+ if (closeIdx >= cut) return i
647
+ return cut
648
+ }
649
+ }
650
+ return cut
651
+ }
652
+
653
+ /** A tag still open at the end of a fragment. */
654
+ interface OpenTag {
655
+ name: string // lowercase tag name, e.g. "a", "tg-spoiler"
656
+ openTag: string // full opening string with attrs, e.g. `<a href="...">`
657
+ }
658
+
659
+ /** Parse an HTML fragment and return the list of tags still open at the end. */
660
+ function getOpenTags(html: string): OpenTag[] {
661
+ const tagStack: OpenTag[] = []
662
+ // Allow hyphens in tag names so `tg-spoiler` and `tg-emoji` parse as a
663
+ // single tag rather than `tg` plus stray text.
664
+ const tagRe = /<(\/?)([a-z][a-z0-9-]*)\b[^>]*>/gi
665
+ let m: RegExpExecArray | null
666
+ while ((m = tagRe.exec(html)) !== null) {
667
+ const full = m[0]
668
+ const isClosing = m[1] === '/'
669
+ const tagName = m[2].toLowerCase()
670
+ if (isClosing) {
671
+ // Closing tag — pop the most recent matching entry off the stack
672
+ for (let i = tagStack.length - 1; i >= 0; i--) {
673
+ if (tagStack[i].name === tagName) {
674
+ tagStack.splice(i, 1)
675
+ break
676
+ }
677
+ }
678
+ } else if (!full.endsWith('/>')) {
679
+ // Opening tag (not self-closing) — remember the full open string
680
+ // so reopen in the next chunk preserves attributes.
681
+ tagStack.push({ name: tagName, openTag: full })
682
+ }
683
+ }
684
+ return tagStack
685
+ }