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,285 @@
1
+ /**
2
+ * Startup mutex for the telegram gateway — atomic single-writer guarantee
3
+ * on the per-agent PID file.
4
+ *
5
+ * Why this exists (2026-04-23 incident):
6
+ * --------------------------------------
7
+ * Two pollers can race on Telegram's getUpdates long-poll. When the OLD
8
+ * gateway hasn't fully released its long-poll TCP connection before the
9
+ * NEW gateway boots, the new one gets `409: Conflict: terminated by other
10
+ * getUpdates request`. The gateway then enters an exponential-backoff
11
+ * retry loop. Earlier today we saw the OLD clerk-gateway looping on retry
12
+ * attempt 13 with 10–12s backoffs — i.e. two gateway processes alive at
13
+ * the same time, both polling.
14
+ *
15
+ * The PRs #45–#50 stack added a PID-file probe and a SIGTERM marker but
16
+ * did NOT add a real startup mutex. This module closes that gap.
17
+ *
18
+ * Algorithm (POSIX-portable, no fcntl):
19
+ * 1. Write our record to a uniquely-named tmp file (pid + nanoseconds).
20
+ * 2. fs.link(tmp, canonical) — atomic on every POSIX filesystem.
21
+ * - If link succeeds: we hold the lock. Unlink the tmp.
22
+ * - If link fails with EEXIST: another holder. Read the existing
23
+ * file, check whether the recorded PID is still alive
24
+ * (process.kill(pid, 0)). If alive → blocked. If dead → unlink
25
+ * the stale canonical and retry the link once.
26
+ *
27
+ * Releases happen on shutdown (SIGTERM/SIGINT/uncaught error) by
28
+ * unlinking the canonical path. We log every state transition; do NOT
29
+ * silently swallow filesystem errors.
30
+ */
31
+ import {
32
+ link as linkAsync,
33
+ unlink as unlinkAsync,
34
+ writeFile as writeFileAsync,
35
+ readFile as readFileAsync,
36
+ } from "node:fs/promises";
37
+
38
+ export interface MutexRecord {
39
+ pid: number;
40
+ startedAtMs: number;
41
+ }
42
+
43
+ export type AcquireOutcome =
44
+ | {
45
+ status: "acquired";
46
+ record: MutexRecord;
47
+ /** True if a stale prior record was cleaned up before acquiring. */
48
+ recoveredFrom?: MutexRecord;
49
+ }
50
+ | {
51
+ status: "blocked";
52
+ holder: MutexRecord;
53
+ holderAgeSec: number;
54
+ };
55
+
56
+ export interface AcquireOptions {
57
+ /** Canonical path of the lock file (e.g. .../telegram/gateway.pid.json). */
58
+ path: string;
59
+ /** Record we'll try to write. */
60
+ record: MutexRecord;
61
+ /**
62
+ * PID liveness probe. Defaults to process.kill(pid, 0) semantics.
63
+ * Injectable so tests can simulate dead/alive PIDs without forking.
64
+ */
65
+ isPidAlive?: (pid: number) => boolean;
66
+ /**
67
+ * Logger. Defaults to process.stderr.write. Lines are pre-formatted
68
+ * with the `telegram gateway:` prefix to match journalctl style.
69
+ */
70
+ log?: (line: string) => void;
71
+ /**
72
+ * Agent name to include in log lines for journalctl filtering.
73
+ */
74
+ agentName?: string;
75
+ }
76
+
77
+ const DEFAULT_LOG = (line: string): void => {
78
+ process.stderr.write(line.endsWith("\n") ? line : line + "\n");
79
+ };
80
+
81
+ const DEFAULT_IS_ALIVE = (pid: number): boolean => {
82
+ if (!Number.isFinite(pid) || pid <= 0) return false;
83
+ if (pid === process.pid) return true;
84
+ try {
85
+ process.kill(pid, 0);
86
+ return true;
87
+ } catch (err) {
88
+ const code = (err as NodeJS.ErrnoException).code;
89
+ // EPERM means the process exists but we can't signal it (different
90
+ // user). Treat as alive — we'd rather block than collide with an
91
+ // unkillable holder.
92
+ if (code === "EPERM") return true;
93
+ return false;
94
+ }
95
+ };
96
+
97
+ function fmtAgent(agentName: string | undefined): string {
98
+ return agentName ? ` agent=${agentName}` : "";
99
+ }
100
+
101
+ function tmpPath(canonical: string, pid: number): string {
102
+ // hrtime.bigint() guarantees uniqueness even under same-millisecond
103
+ // double-boot races (which is exactly what we're defending against).
104
+ return `${canonical}.tmp-${pid}-${process.hrtime.bigint().toString(36)}`;
105
+ }
106
+
107
+ async function tryReadRecord(path: string): Promise<MutexRecord | null> {
108
+ try {
109
+ const raw = await readFileAsync(path, "utf-8");
110
+ const parsed = JSON.parse(raw) as Partial<MutexRecord>;
111
+ if (
112
+ typeof parsed.pid === "number" &&
113
+ typeof parsed.startedAtMs === "number" &&
114
+ Number.isFinite(parsed.pid) &&
115
+ Number.isFinite(parsed.startedAtMs)
116
+ ) {
117
+ return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
118
+ }
119
+ return null;
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Atomically attempt to acquire the lock. Resolves with either
127
+ * `acquired` (we own the file now) or `blocked` (a live holder exists).
128
+ *
129
+ * Throws only on unrecoverable filesystem errors (ENOSPC, EROFS, EACCES
130
+ * on a directory that should be writable). The caller MUST treat thrown
131
+ * errors as fatal — the gateway should exit non-zero so systemd can
132
+ * apply its restart-burst backoff.
133
+ */
134
+ export async function acquireStartupLock(
135
+ opts: AcquireOptions,
136
+ ): Promise<AcquireOutcome> {
137
+ const log = opts.log ?? DEFAULT_LOG;
138
+ const isAlive = opts.isPidAlive ?? DEFAULT_IS_ALIVE;
139
+ const { path, record, agentName } = opts;
140
+ const agentTag = fmtAgent(agentName);
141
+
142
+ const tmp = tmpPath(path, record.pid);
143
+ const payload = JSON.stringify(record);
144
+
145
+ // Write the tmp file first. If this throws, the canonical isn't
146
+ // touched — caller can retry on a fresh boot.
147
+ await writeFileAsync(tmp, payload, { encoding: "utf-8", mode: 0o600 });
148
+
149
+ let recoveredFrom: MutexRecord | undefined;
150
+
151
+ // Try the atomic link. ONE retry after stale-recovery; we don't loop
152
+ // forever — if the second link also fails EEXIST we treat as blocked
153
+ // (something else won the race in the gap, and that something else is
154
+ // now the legitimate holder).
155
+ for (let attempt = 0; attempt < 2; attempt++) {
156
+ try {
157
+ await linkAsync(tmp, path);
158
+ // Won the race. Drop the tmp; canonical is the live lock now.
159
+ await unlinkAsync(tmp).catch(() => {
160
+ /* tmp cleanup is best-effort */
161
+ });
162
+ log(
163
+ `telegram gateway: boot.lock_acquired pid=${record.pid} started_at=${new Date(
164
+ record.startedAtMs,
165
+ ).toISOString()}${agentTag}`,
166
+ );
167
+ return recoveredFrom
168
+ ? { status: "acquired", record, recoveredFrom }
169
+ : { status: "acquired", record };
170
+ } catch (err) {
171
+ const code = (err as NodeJS.ErrnoException).code;
172
+ if (code !== "EEXIST") {
173
+ // Unrecoverable. Clean up tmp before propagating.
174
+ await unlinkAsync(tmp).catch(() => {});
175
+ throw err;
176
+ }
177
+
178
+ // EEXIST → inspect holder.
179
+ const holder = await tryReadRecord(path);
180
+ if (holder == null) {
181
+ // File exists but unreadable / corrupt. Treat as stale and
182
+ // unlink it. (Better than blocking forever on a garbage file.)
183
+ log(
184
+ `telegram gateway: boot.lock_corrupt_recovered path=${path}${agentTag}`,
185
+ );
186
+ await unlinkAsync(path).catch(() => {});
187
+ continue;
188
+ }
189
+
190
+ if (isAlive(holder.pid)) {
191
+ // Live holder. Drop tmp and report blocked.
192
+ await unlinkAsync(tmp).catch(() => {});
193
+ const ageSec = Math.max(
194
+ 0,
195
+ Math.round((Date.now() - holder.startedAtMs) / 1000),
196
+ );
197
+ log(
198
+ `telegram gateway: boot.lock_blocked holder_pid=${holder.pid} holder_started_at=${new Date(
199
+ holder.startedAtMs,
200
+ ).toISOString()} holder_age_sec=${ageSec}${agentTag}`,
201
+ );
202
+ return { status: "blocked", holder, holderAgeSec: ageSec };
203
+ }
204
+
205
+ // Stale holder (PID dead). Unlink and loop once to retry the
206
+ // link. Log before unlink so the recovery is auditable.
207
+ log(
208
+ `telegram gateway: boot.lock_stale_recovered prior_pid=${holder.pid} prior_started_at=${new Date(
209
+ holder.startedAtMs,
210
+ ).toISOString()}${agentTag}`,
211
+ );
212
+ try {
213
+ await unlinkAsync(path);
214
+ } catch (unlinkErr) {
215
+ const unlinkCode = (unlinkErr as NodeJS.ErrnoException).code;
216
+ if (unlinkCode !== "ENOENT") {
217
+ // Couldn't clean up — propagate. Tmp gets cleaned by finalizer.
218
+ await unlinkAsync(tmp).catch(() => {});
219
+ throw unlinkErr;
220
+ }
221
+ // ENOENT means someone else cleaned it; that's fine, retry link.
222
+ }
223
+
224
+ recoveredFrom = holder;
225
+ }
226
+ }
227
+
228
+ // Reached only if both link attempts failed EEXIST. The second EEXIST
229
+ // implies a live process won the race after we cleared the stale
230
+ // file. Re-read and report as blocked.
231
+ await unlinkAsync(tmp).catch(() => {});
232
+ const finalHolder = await tryReadRecord(path);
233
+ if (finalHolder != null && isAlive(finalHolder.pid)) {
234
+ const ageSec = Math.max(
235
+ 0,
236
+ Math.round((Date.now() - finalHolder.startedAtMs) / 1000),
237
+ );
238
+ log(
239
+ `telegram gateway: boot.lock_blocked holder_pid=${finalHolder.pid} holder_started_at=${new Date(
240
+ finalHolder.startedAtMs,
241
+ ).toISOString()} holder_age_sec=${ageSec}${agentTag}`,
242
+ );
243
+ return { status: "blocked", holder: finalHolder, holderAgeSec: ageSec };
244
+ }
245
+ // Edge: file vanished between attempts. Treat as blocked-with-unknown
246
+ // rather than spinning further; systemd backoff is the right answer.
247
+ const fallback: MutexRecord = finalHolder ?? { pid: 0, startedAtMs: 0 };
248
+ log(
249
+ `telegram gateway: boot.lock_blocked_unknown_holder${agentTag}`,
250
+ );
251
+ return { status: "blocked", holder: fallback, holderAgeSec: 0 };
252
+ }
253
+
254
+ /**
255
+ * Release the lock by unlinking the canonical file. Logs and swallows
256
+ * ENOENT (already gone — fine). Other errors are logged but NOT thrown:
257
+ * shutdown paths run in finally blocks where throwing would skip
258
+ * subsequent cleanup steps.
259
+ */
260
+ export async function releaseStartupLock(opts: {
261
+ path: string;
262
+ pid: number;
263
+ log?: (line: string) => void;
264
+ agentName?: string;
265
+ }): Promise<void> {
266
+ const log = opts.log ?? DEFAULT_LOG;
267
+ const agentTag = fmtAgent(opts.agentName);
268
+ try {
269
+ await unlinkAsync(opts.path);
270
+ log(`telegram gateway: shutdown.lock_released pid=${opts.pid}${agentTag}`);
271
+ } catch (err) {
272
+ const code = (err as NodeJS.ErrnoException).code;
273
+ if (code === "ENOENT") {
274
+ // Already gone — log at info, not error. Common after a crash
275
+ // where someone else cleaned up.
276
+ log(
277
+ `telegram gateway: shutdown.lock_release_noop pid=${opts.pid}${agentTag}`,
278
+ );
279
+ return;
280
+ }
281
+ log(
282
+ `telegram gateway: shutdown.lock_release_failed pid=${opts.pid} code=${code ?? "unknown"} err=${(err as Error).message}${agentTag}`,
283
+ );
284
+ }
285
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Bounded exponential-backoff retry for gateway startup network errors.
3
+ *
4
+ * On 2026-04-29 all five switchroom gateways silently broke at boot because
5
+ * `api.telegram.org` was unreachable for ~27 minutes after system boot (the
6
+ * network stack wasn't fully usable when `network-online.target` fired).
7
+ * Grammy threw `HttpError: Network request for 'deleteWebhook'/'getMe' failed!`
8
+ * and the gateway's catch block logged the error and **returned** — leaving the
9
+ * process alive but not polling. No crash, so systemd's `Restart=always` never
10
+ * fired. Telegram → agent delivery was dead until manual restarts.
11
+ *
12
+ * This module provides:
13
+ *
14
+ * `isBootNetworkError(err)` — recognises network-layer errors thrown by
15
+ * grammy's HttpError wrapper and by raw fetch/Node network failures.
16
+ *
17
+ * `STARTUP_RETRY_DELAYS_MS` — the chosen backoff schedule.
18
+ *
19
+ * `gatewayStartupRetry(fn, opts)` — drives the retry loop. Calls `fn()` up to
20
+ * `maxAttempts` times with delays from `delaysMs`. On success it resolves.
21
+ * On exhaustion it calls `opts.onExhausted()` (default: `process.exit(1)`)
22
+ * so systemd's `Restart=always` can restart the unit cleanly.
23
+ *
24
+ * The function is extracted from `gateway.ts`'s top-level IIFE so it can be
25
+ * unit-tested without spinning up the full bot runtime.
26
+ */
27
+
28
+ export interface StartupRetryOpts {
29
+ /**
30
+ * Delay schedule in milliseconds. Each attempt waits the corresponding
31
+ * element before the NEXT attempt. Length determines max extra attempts
32
+ * (total = delays.length + 1 initial attempt).
33
+ *
34
+ * Defaults to `STARTUP_RETRY_DELAYS_MS` (~2 min budget).
35
+ */
36
+ delaysMs?: number[]
37
+
38
+ /** Inject a sleep helper so tests can use fake timers. */
39
+ sleep?: (ms: number) => Promise<void>
40
+
41
+ /**
42
+ * Called when all attempts are exhausted. Should NOT return (exit/throw).
43
+ * Defaults to `process.exit(1)`.
44
+ */
45
+ onExhausted?: (lastError: unknown) => never
46
+
47
+ /** Log sink for retry progress messages. Defaults to process.stderr.write. */
48
+ log?: (line: string) => void
49
+ }
50
+
51
+ /**
52
+ * Default backoff schedule: 1 s, 2 s, 4 s, 8 s, 16 s, 32 s, 64 s.
53
+ * Total budget including 8 attempts: ~2 min 7 s. Chosen so a typical
54
+ * post-boot network settle (empirically <90 s) is covered with headroom.
55
+ */
56
+ export const STARTUP_RETRY_DELAYS_MS: number[] = [
57
+ 1_000,
58
+ 2_000,
59
+ 4_000,
60
+ 8_000,
61
+ 16_000,
62
+ 32_000,
63
+ 64_000,
64
+ ]
65
+
66
+ const DEFAULT_SLEEP = (ms: number): Promise<void> =>
67
+ new Promise((resolve) => setTimeout(resolve, ms))
68
+
69
+ /**
70
+ * Returns true if `err` is a transient network-level failure that the startup
71
+ * retry loop should absorb. Covers:
72
+ *
73
+ * - Grammy's `HttpError` (name === 'HttpError'), which wraps fetch/ECONN errors
74
+ * during `deleteWebhook` and `getMe`.
75
+ * - Raw Node/fetch errors: ECONNRESET, ETIMEDOUT, ENOTFOUND, ECONNREFUSED,
76
+ * fetch failed, etc.
77
+ */
78
+ export function isBootNetworkError(err: unknown): boolean {
79
+ if (!(err instanceof Error)) return false
80
+ // Grammy wraps network errors in HttpError (name is set in the constructor)
81
+ if (err.name === 'HttpError') return true
82
+ const msg = err.message
83
+ return (
84
+ msg.includes('ECONNRESET') ||
85
+ msg.includes('ETIMEDOUT') ||
86
+ msg.includes('ENOTFOUND') ||
87
+ msg.includes('ECONNREFUSED') ||
88
+ msg.includes('fetch failed') ||
89
+ msg.includes('Network request')
90
+ )
91
+ }
92
+
93
+ /**
94
+ * Attempt `fn()` and retry on `isBootNetworkError` failures using the
95
+ * provided delay schedule.
96
+ *
97
+ * - On success: returns whatever `fn()` resolved to.
98
+ * - On non-network error: re-throws immediately (not a transient boot issue).
99
+ * - On exhausted retries: calls `opts.onExhausted(lastError)` which must not
100
+ * return (it should exit or throw). The default is `process.exit(1)` so
101
+ * systemd's `Restart=always` picks up the dead unit.
102
+ */
103
+ export async function gatewayStartupRetry<T>(
104
+ fn: () => Promise<T>,
105
+ opts: StartupRetryOpts = {},
106
+ ): Promise<T> {
107
+ const delays = opts.delaysMs ?? STARTUP_RETRY_DELAYS_MS
108
+ const sleep = opts.sleep ?? DEFAULT_SLEEP
109
+ const onExhausted: (err: unknown) => never =
110
+ opts.onExhausted ??
111
+ ((err: unknown) => {
112
+ process.stderr.write(
113
+ `telegram gateway: startup failed after ${delays.length + 1} attempts — exiting so systemd can restart: ${err}\n`,
114
+ )
115
+ process.exit(1)
116
+ })
117
+ const log =
118
+ opts.log ??
119
+ ((line: string) => {
120
+ process.stderr.write(line.endsWith('\n') ? line : line + '\n')
121
+ })
122
+
123
+ const maxAttempts = delays.length + 1
124
+ let lastError: unknown
125
+
126
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
127
+ try {
128
+ return await fn()
129
+ } catch (err) {
130
+ if (!isBootNetworkError(err)) throw err
131
+ lastError = err
132
+ if (attempt >= maxAttempts) break
133
+ const delayMs = delays[attempt - 1]
134
+ log(
135
+ `telegram gateway: startup network error (attempt ${attempt}/${maxAttempts}), retrying in ${delayMs / 1000}s: ${err}`,
136
+ )
137
+ await sleep(delayMs)
138
+ }
139
+ }
140
+
141
+ return onExhausted(lastError)
142
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Turn-active liveness marker (#412).
3
+ *
4
+ * Writes `<STATE_DIR>/turn-active.json` on turn_start, touches its mtime
5
+ * on every tool_use, removes it on turn_complete. The watchdog
6
+ * (bin/bridge-watchdog.sh) reads the mtime: if the file exists AND its
7
+ * mtime is older than TURN_HANG_SECS (default 300s = 5min), the agent
8
+ * is wedged mid-turn and the watchdog restarts.
9
+ *
10
+ * Why this exists: PR #410 raised the journal-silence detector to 4000s
11
+ * to kill false positives on chat-cadence agents that legitimately
12
+ * idle for hours between turns. That left a gap — Stop-hook deadlocks
13
+ * (the original failure mode #116 tracked) are no longer caught under
14
+ * default thresholds.
15
+ *
16
+ * The distinguisher is "in-turn-and-silent" vs "between-turns-and-silent":
17
+ * the former is a wedge, the latter is healthy idle. This marker exists
18
+ * exactly during in-turn windows, so its staleness uniquely indicates
19
+ * the wedge.
20
+ *
21
+ * Pure file I/O. The actual hang-detection-and-restart loop lives in the
22
+ * bash watchdog, where it composes with the existing
23
+ * Restart=on-failure / journal-silence / bridge-disconnect detectors.
24
+ */
25
+
26
+ import {
27
+ closeSync,
28
+ existsSync,
29
+ mkdirSync,
30
+ openSync,
31
+ readFileSync,
32
+ statSync,
33
+ unlinkSync,
34
+ utimesSync,
35
+ writeFileSync,
36
+ } from "node:fs";
37
+ import { join } from "node:path";
38
+
39
+ export const TURN_ACTIVE_MARKER_FILE = "turn-active.json";
40
+
41
+ export interface TurnActiveMarker {
42
+ turnKey: string;
43
+ chatId: string;
44
+ threadId?: string | null;
45
+ startedAt: number;
46
+ }
47
+
48
+ /**
49
+ * Write the marker file at turn-start. Idempotent — if the file
50
+ * already exists from a stale prior turn (unlikely; turn_complete
51
+ * removes it), the new write wins.
52
+ */
53
+ export function writeTurnActiveMarker(stateDir: string, marker: TurnActiveMarker): void {
54
+ try {
55
+ mkdirSync(stateDir, { recursive: true });
56
+ writeFileSync(
57
+ join(stateDir, TURN_ACTIVE_MARKER_FILE),
58
+ JSON.stringify(marker, null, 2) + "\n",
59
+ { mode: 0o600 },
60
+ );
61
+ } catch {
62
+ // Best-effort: marker file is a watchdog optimisation, not a
63
+ // correctness requirement. Don't break the turn-start path on
64
+ // disk-full, ENOSPC, etc.
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Touch the marker file's mtime. Called on every tool_use event so an
70
+ * agent doing real work continually advances the mtime. The watchdog's
71
+ * threshold compares against this mtime.
72
+ */
73
+ export function touchTurnActiveMarker(stateDir: string): void {
74
+ const path = join(stateDir, TURN_ACTIVE_MARKER_FILE);
75
+ if (!existsSync(path)) return;
76
+ const now = new Date();
77
+ try {
78
+ utimesSync(path, now, now);
79
+ } catch {
80
+ // utimesSync can fail on some filesystems; fall back to a tiny
81
+ // open-close cycle to bump the mtime via writes from the kernel side.
82
+ try {
83
+ const fd = openSync(path, "r+");
84
+ closeSync(fd);
85
+ } catch {
86
+ /* swallow — best-effort */
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Remove the marker file at turn_complete. Absence of the file is the
93
+ * watchdog's signal that no turn is in flight (legitimate idle, no
94
+ * reason to suspect a hang).
95
+ */
96
+ export function removeTurnActiveMarker(stateDir: string): void {
97
+ try {
98
+ unlinkSync(join(stateDir, TURN_ACTIVE_MARKER_FILE));
99
+ } catch {
100
+ // ENOENT is fine (already removed); other errors don't justify
101
+ // breaking the turn-end path.
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Sweep a stale marker file. Defence-in-depth backstop for #550 — when
107
+ * the primary `turn_end` removal path is silently skipped (e.g. SDK
108
+ * killed before the JSONL turn_duration record is written, or the
109
+ * progress-card driver's `forceCompleteTurn` no-ops because the card
110
+ * was already torn down), the marker leaks across restarts and the
111
+ * watchdog reads it as a hung turn.
112
+ *
113
+ * Removes the marker if EITHER:
114
+ * - mtime is older than `idleSweepMs` AND the caller asserts that no
115
+ * turn is currently in flight (`turnInFlight=false`), OR
116
+ * - mtime is older than `hardTtlMs` unconditionally (the absolute
117
+ * ceiling — anything older than this can't be a real turn).
118
+ *
119
+ * Both conditions are best-effort and idempotent. Returns true if the
120
+ * marker was removed, false otherwise.
121
+ */
122
+ export function sweepStaleTurnActiveMarker(
123
+ stateDir: string,
124
+ opts: {
125
+ turnInFlight: boolean;
126
+ idleSweepMs: number;
127
+ hardTtlMs: number;
128
+ now?: number;
129
+ /**
130
+ * Optional diagnostic callback invoked when the marker is removed.
131
+ * Best-effort: keeps the function pure (no logger coupling) while
132
+ * letting the caller emit a structured journalctl line. Must not
133
+ * throw — exceptions from this callback are swallowed.
134
+ */
135
+ onRemove?: (info: {
136
+ ageMs: number;
137
+ reason: "idle-stale" | "hard-ttl";
138
+ payload: string | null;
139
+ }) => void;
140
+ },
141
+ ): boolean {
142
+ const path = join(stateDir, TURN_ACTIVE_MARKER_FILE);
143
+ if (!existsSync(path)) return false;
144
+ const now = opts.now ?? Date.now();
145
+ try {
146
+ const st = statSync(path);
147
+ const ageMs = now - st.mtimeMs;
148
+ const hardExpired = ageMs > opts.hardTtlMs;
149
+ const idleExpired = !opts.turnInFlight && ageMs > opts.idleSweepMs;
150
+ if (!hardExpired && !idleExpired) return false;
151
+ // Best-effort read so the diagnostic callback can include the
152
+ // payload (turnKey, chatId, startedAt) for forensic logging.
153
+ let payload: string | null = null;
154
+ try {
155
+ payload = readFileSync(path, "utf8");
156
+ } catch {
157
+ /* unreadable — still drop the marker */
158
+ }
159
+ unlinkSync(path);
160
+ if (opts.onRemove) {
161
+ try {
162
+ opts.onRemove({
163
+ ageMs,
164
+ reason: hardExpired ? "hard-ttl" : "idle-stale",
165
+ payload,
166
+ });
167
+ } catch {
168
+ /* swallow — diagnostics must never break the sweep */
169
+ }
170
+ }
171
+ return true;
172
+ } catch {
173
+ // ENOENT race or stat failure — nothing actionable.
174
+ return false;
175
+ }
176
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Discriminating policy for the gateway's `unhandledRejection` handler.
3
+ *
4
+ * Background: gateway.ts crashes the process on every unhandledRejection
5
+ * (it calls `shutdown()` from the handler). Some Telegram API errors
6
+ * surface here as benign 400s — "message is not modified", "message to
7
+ * edit not found" — and crashing the gateway over them creates restart
8
+ * loops (issue #99 + lawgpt's 11:36 crash family).
9
+ *
10
+ * Pure helper so it can be tested without spinning up the gateway.
11
+ */
12
+
13
+ import { GrammyError } from 'grammy'
14
+
15
+ export type RejectionAction = 'shutdown' | 'log_only'
16
+
17
+ export interface RejectionPolicyOptions {
18
+ /** Allow tests to inject error type detection without depending on grammy. */
19
+ isGrammyError?: (err: unknown) => boolean
20
+ }
21
+
22
+ /**
23
+ * Decide whether an unhandledRejection should crash the gateway.
24
+ *
25
+ * Returns:
26
+ * - `'log_only'` for benign Telegram 400s the bot already tolerates
27
+ * elsewhere (see retry-api-call.ts). Logging surfaces the leak; not
28
+ * crashing prevents restart loops.
29
+ * - `'shutdown'` for everything else. Genuine bugs still crash, which
30
+ * systemd will surface as a restart and we want that signal.
31
+ *
32
+ * The set of benign descriptions is intentionally narrow — only the
33
+ * specific 400s the wrapper already swallows. Any other 400 still
34
+ * triggers shutdown so we don't silently mask new bugs.
35
+ */
36
+ export function classifyRejection(
37
+ err: unknown,
38
+ opts: RejectionPolicyOptions = {},
39
+ ): RejectionAction {
40
+ const isGrammy =
41
+ opts.isGrammyError != null
42
+ ? opts.isGrammyError(err)
43
+ : err instanceof GrammyError
44
+
45
+ if (!isGrammy) return 'shutdown'
46
+
47
+ const e = err as { error_code?: number; description?: string }
48
+ if (e.error_code !== 400) return 'shutdown'
49
+
50
+ const desc = (e.description ?? '').toLowerCase()
51
+ if (
52
+ desc.includes('message is not modified') ||
53
+ desc.includes('message to edit not found') ||
54
+ desc.includes('message to delete not found') ||
55
+ // HTML parse errors (e.g. formatDuration sub-second output like "<1s"
56
+ // interpreted as a tag). These are transient render bugs — log the
57
+ // failure so we can fix the root cause, but don't crash the gateway
58
+ // into a restart loop (issue #101).
59
+ desc.includes("can't parse entities") ||
60
+ desc.includes('unsupported start tag') ||
61
+ // 'chat not found' fires when an allowlisted chat id (typically a
62
+ // group in access.json/groups{}) is no longer reachable — bot was
63
+ // removed, group was deleted, or the id was stale config from a
64
+ // prior bot pairing. Boot-time pin sweep + various other checks
65
+ // call getChat against every allowlisted chat; a single unreachable
66
+ // entry was crashing the gateway into restart loops on ziggy
67
+ // (2026-05-02 12:05) even though boot-probe-failed already logged
68
+ // the failure structurally. The wrapped sweep handles the visible
69
+ // case; this policy entry covers any leaked rejection from the
70
+ // same family so a single bad chat id can't restart-loop the
71
+ // process. The visible boot-probe-failed log is the primary
72
+ // diagnostic; this is the can't-restart-loop guarantee.
73
+ desc.includes('chat not found')
74
+ ) {
75
+ return 'log_only'
76
+ }
77
+ return 'shutdown'
78
+ }