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,414 @@
1
+ /**
2
+ * Regression tests for the clean-shutdown marker that suppresses the
3
+ * "⚡ Recovered from unexpected restart" banner on planned shutdowns.
4
+ *
5
+ * Background — 2026-04-23 UX gap:
6
+ * The boot path posts a recovery banner whenever it doesn't find a
7
+ * /restart user-marker. That assumption was fine for crashes but
8
+ * wrong for deliberate restarts (`systemctl --user restart …`,
9
+ * `switchroom agent restart`, Coolify redeploys, anything that sends
10
+ * SIGTERM cleanly). PR #52's drain coordinator made these planned
11
+ * shutdowns common enough that every redeploy looked unexpected.
12
+ *
13
+ * Fix: SIGTERM/SIGINT writes a separate sentinel file BEFORE draining;
14
+ * boot reads it and suppresses the banner if fresh.
15
+ *
16
+ * These tests pin the contract:
17
+ * - read/write/clear roundtrip works
18
+ * - malformed JSON returns null instead of crashing boot
19
+ * - the boot decision (shouldSuppressRecoveryBanner) is correct for
20
+ * present-fresh / present-stale / absent / clock-skewed inputs
21
+ *
22
+ * Integration with the gateway boot path is structurally tested via
23
+ * the same pattern as gateway-startup-mutex.test.ts: extract the pure
24
+ * helper, unit-test it, trust the wiring via live verification.
25
+ *
26
+ * Run with:
27
+ * bun test telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts
28
+ */
29
+ import { describe, it, expect, afterEach } from "bun:test";
30
+ import { mkdtempSync, writeFileSync, existsSync, rmSync, readFileSync } from "fs";
31
+ import { join } from "path";
32
+ import { tmpdir } from "os";
33
+ import {
34
+ writeCleanShutdownMarker,
35
+ readCleanShutdownMarker,
36
+ clearCleanShutdownMarker,
37
+ shouldSuppressRecoveryBanner,
38
+ resolveShutdownMarker,
39
+ DEFAULT_MAX_AGE_MS,
40
+ EXTERNAL_RESTART_FALLBACK_REASON,
41
+ REASON_PRESERVE_MAX_AGE_MS,
42
+ type CleanShutdownMarker,
43
+ } from "../gateway/clean-shutdown-marker.js";
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Helpers
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function tmpMarkerPath(): { dir: string; path: string } {
50
+ const dir = mkdtempSync(join(tmpdir(), "gw-clean-shutdown-test-"));
51
+ return { dir, path: join(dir, "clean-shutdown.json") };
52
+ }
53
+
54
+ const cleanups: string[] = [];
55
+ function track(dir: string): void { cleanups.push(dir); }
56
+
57
+ afterEach(() => {
58
+ while (cleanups.length > 0) {
59
+ const dir = cleanups.pop();
60
+ if (dir) {
61
+ try { rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
62
+ }
63
+ }
64
+ });
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // File I/O round-trip
68
+ // ---------------------------------------------------------------------------
69
+
70
+ describe("clean-shutdown-marker file I/O", () => {
71
+ it("writes a marker with ts + signal that read returns verbatim", () => {
72
+ const { dir, path } = tmpMarkerPath();
73
+ track(dir);
74
+
75
+ const marker: CleanShutdownMarker = { ts: 1_700_000_000_000, signal: "SIGTERM" };
76
+ writeCleanShutdownMarker(path, marker);
77
+
78
+ expect(existsSync(path)).toBe(true);
79
+ const got = readCleanShutdownMarker(path);
80
+ expect(got).not.toBeNull();
81
+ expect(got?.ts).toBe(marker.ts);
82
+ expect(got?.signal).toBe("SIGTERM");
83
+ // Default schema: no reason field.
84
+ expect(got?.reason).toBeUndefined();
85
+ });
86
+
87
+ it("preserves the optional reason field", () => {
88
+ const { dir, path } = tmpMarkerPath();
89
+ track(dir);
90
+
91
+ writeCleanShutdownMarker(path, { ts: Date.now(), signal: "SIGINT", reason: "deploy" });
92
+ const got = readCleanShutdownMarker(path);
93
+ expect(got?.reason).toBe("deploy");
94
+ });
95
+
96
+ it("clearCleanShutdownMarker deletes the file", () => {
97
+ const { dir, path } = tmpMarkerPath();
98
+ track(dir);
99
+
100
+ writeCleanShutdownMarker(path, { ts: Date.now(), signal: "SIGTERM" });
101
+ expect(existsSync(path)).toBe(true);
102
+
103
+ clearCleanShutdownMarker(path);
104
+ expect(existsSync(path)).toBe(false);
105
+ });
106
+
107
+ it("clearCleanShutdownMarker is a no-op when the file is already absent", () => {
108
+ const { dir, path } = tmpMarkerPath();
109
+ track(dir);
110
+
111
+ // File never existed.
112
+ expect(existsSync(path)).toBe(false);
113
+ expect(() => clearCleanShutdownMarker(path)).not.toThrow();
114
+ });
115
+
116
+ it("readCleanShutdownMarker returns null for a missing file (no crash)", () => {
117
+ const { dir, path } = tmpMarkerPath();
118
+ track(dir);
119
+
120
+ expect(readCleanShutdownMarker(path)).toBeNull();
121
+ });
122
+
123
+ it("readCleanShutdownMarker returns null for malformed JSON (no crash on boot)", () => {
124
+ const { dir, path } = tmpMarkerPath();
125
+ track(dir);
126
+
127
+ writeFileSync(path, "{ this is not json", "utf-8");
128
+ expect(readCleanShutdownMarker(path)).toBeNull();
129
+ });
130
+
131
+ it("readCleanShutdownMarker returns null when ts is missing or non-numeric", () => {
132
+ const { dir, path } = tmpMarkerPath();
133
+ track(dir);
134
+
135
+ writeFileSync(path, JSON.stringify({ signal: "SIGTERM" }), "utf-8");
136
+ expect(readCleanShutdownMarker(path)).toBeNull();
137
+
138
+ writeFileSync(path, JSON.stringify({ ts: "nope", signal: "SIGTERM" }), "utf-8");
139
+ expect(readCleanShutdownMarker(path)).toBeNull();
140
+ });
141
+
142
+ it("readCleanShutdownMarker returns null when signal is missing or empty", () => {
143
+ const { dir, path } = tmpMarkerPath();
144
+ track(dir);
145
+
146
+ writeFileSync(path, JSON.stringify({ ts: Date.now() }), "utf-8");
147
+ expect(readCleanShutdownMarker(path)).toBeNull();
148
+
149
+ writeFileSync(path, JSON.stringify({ ts: Date.now(), signal: "" }), "utf-8");
150
+ expect(readCleanShutdownMarker(path)).toBeNull();
151
+ });
152
+
153
+ it("write is atomic: tmp file does not survive a successful write", () => {
154
+ const { dir, path } = tmpMarkerPath();
155
+ track(dir);
156
+
157
+ writeCleanShutdownMarker(path, { ts: Date.now(), signal: "SIGTERM" });
158
+ // After a successful write, only the final path should exist — no
159
+ // straggling .tmp-*. (Glob-ish check via reading the dir.)
160
+ const fs = require("node:fs") as typeof import("node:fs");
161
+ const entries = fs.readdirSync(dir);
162
+ const tmps = entries.filter((e) => e.includes(".tmp-"));
163
+ expect(tmps.length).toBe(0);
164
+ expect(entries).toContain("clean-shutdown.json");
165
+ });
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Boot decision: shouldSuppressRecoveryBanner
170
+ // ---------------------------------------------------------------------------
171
+
172
+ describe("shouldSuppressRecoveryBanner", () => {
173
+ it("returns false when no marker is present (true crash or first boot)", () => {
174
+ expect(shouldSuppressRecoveryBanner(null, Date.now())).toBe(false);
175
+ });
176
+
177
+ it("returns true for a fresh marker (age < default 60s)", () => {
178
+ const now = 1_700_000_000_000;
179
+ const marker: CleanShutdownMarker = { ts: now - 5_000, signal: "SIGTERM" };
180
+ expect(shouldSuppressRecoveryBanner(marker, now)).toBe(true);
181
+ });
182
+
183
+ it("returns true at the very edge of the window (age = 0)", () => {
184
+ const now = 1_700_000_000_000;
185
+ const marker: CleanShutdownMarker = { ts: now, signal: "SIGTERM" };
186
+ expect(shouldSuppressRecoveryBanner(marker, now)).toBe(true);
187
+ });
188
+
189
+ it("returns false when marker age equals maxAgeMs (boundary)", () => {
190
+ const now = 1_700_000_000_000;
191
+ const marker: CleanShutdownMarker = { ts: now - DEFAULT_MAX_AGE_MS, signal: "SIGTERM" };
192
+ expect(shouldSuppressRecoveryBanner(marker, now)).toBe(false);
193
+ });
194
+
195
+ it("returns false for a stale marker (age > default 60s)", () => {
196
+ const now = 1_700_000_000_000;
197
+ const marker: CleanShutdownMarker = { ts: now - 90_000, signal: "SIGTERM" };
198
+ expect(shouldSuppressRecoveryBanner(marker, now)).toBe(false);
199
+ });
200
+
201
+ it("respects a custom maxAgeMs", () => {
202
+ const now = 1_700_000_000_000;
203
+ const marker: CleanShutdownMarker = { ts: now - 30_000, signal: "SIGTERM" };
204
+ // Fresh under default (60s) but stale under a 10s window.
205
+ expect(shouldSuppressRecoveryBanner(marker, now, 60_000)).toBe(true);
206
+ expect(shouldSuppressRecoveryBanner(marker, now, 10_000)).toBe(false);
207
+ });
208
+
209
+ it("treats clock skew (negative age) as stale to avoid false suppression", () => {
210
+ // If the marker says the future, something is wrong with one of the
211
+ // clocks — don't suppress the banner. Operator probably wants to
212
+ // know about it.
213
+ const now = 1_700_000_000_000;
214
+ const marker: CleanShutdownMarker = { ts: now + 60_000, signal: "SIGTERM" };
215
+ expect(shouldSuppressRecoveryBanner(marker, now)).toBe(false);
216
+ });
217
+
218
+ it("works for both SIGTERM and SIGINT signals (signal value is opaque)", () => {
219
+ const now = 1_700_000_000_000;
220
+ expect(shouldSuppressRecoveryBanner({ ts: now, signal: "SIGTERM" }, now)).toBe(true);
221
+ expect(shouldSuppressRecoveryBanner({ ts: now, signal: "SIGINT" }, now)).toBe(true);
222
+ expect(shouldSuppressRecoveryBanner({ ts: now, signal: "SIGUSR1" }, now)).toBe(true);
223
+ });
224
+ });
225
+
226
+ describe("resolveShutdownMarker (SIGTERM-handler sequencing)", () => {
227
+ // These tests pin the fix for the 2026-04-24 bug where bare
228
+ // `systemctl restart switchroom-<name>-gateway` produced a reasonless
229
+ // marker AND the shutdown handler clobbered any reason stamped by a
230
+ // preceding CLI/watchdog/user-slash initiator. The resolver preserves
231
+ // fresh reasons and falls back to "systemctl: external restart" so
232
+ // the next greeting card always has a non-empty Restarted row for
233
+ // planned shutdowns.
234
+
235
+ it("preserves a fresh prior reason (cooperative race with a CLI/user stamp)", () => {
236
+ const now = 1_700_000_000_000;
237
+ const prior: CleanShutdownMarker = {
238
+ ts: now - 500, // 500ms old — CLI just stamped
239
+ signal: "SIGTERM",
240
+ reason: "cli: deploying abc1234",
241
+ };
242
+ const next = resolveShutdownMarker(prior, "SIGTERM", now);
243
+ expect(next.reason).toBe("cli: deploying abc1234");
244
+ expect(next.signal).toBe("SIGTERM");
245
+ expect(next.ts).toBe(now);
246
+ });
247
+
248
+ it("preserves user-slash attribution when the gateway stamps before spawning the CLI", () => {
249
+ // /restart, /reconcile, /new, /reset paths in gateway.ts call
250
+ // stampUserRestartReason() which writes the marker, then spawn the
251
+ // detached CLI which stamps its own. By the time SIGTERM arrives,
252
+ // the most recent marker should already carry "user: /restart from
253
+ // chat" (preserved by preserveExisting in writeRestartReasonMarker).
254
+ // The SIGTERM handler must then preserve it a second time.
255
+ const now = 1_700_000_000_000;
256
+ const prior: CleanShutdownMarker = {
257
+ ts: now - 2_000,
258
+ signal: "SIGTERM",
259
+ reason: "user: /restart from chat",
260
+ };
261
+ const next = resolveShutdownMarker(prior, "SIGTERM", now);
262
+ expect(next.reason).toBe("user: /restart from chat");
263
+ });
264
+
265
+ it("falls back to 'systemctl: external restart' when no prior marker exists", () => {
266
+ // Bare `systemctl restart switchroom-<name>-gateway` from an admin
267
+ // terminal — no CLI, no watchdog, no slash command stamped anything.
268
+ // Before this fix the marker was `{ts, signal}` with no reason and
269
+ // the greeting silently omitted the Restarted row. This is the
270
+ // 04:57 2026-04-24 bug Ken hit when applying an EnvironmentFile
271
+ // change.
272
+ const now = 1_700_000_000_000;
273
+ const next = resolveShutdownMarker(null, "SIGTERM", now);
274
+ expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
275
+ expect(next.signal).toBe("SIGTERM");
276
+ expect(next.ts).toBe(now);
277
+ });
278
+
279
+ it("falls back when a prior marker is missing its reason field", () => {
280
+ // A reasonless prior marker means an earlier SIGTERM-only path
281
+ // wrote it (pre-fix gateway) or an initiator stamped ts+signal
282
+ // without a reason. Either way, we have no attribution to preserve
283
+ // — use the fallback so the greeting is never silent.
284
+ const now = 1_700_000_000_000;
285
+ const prior: CleanShutdownMarker = { ts: now - 1_000, signal: "SIGTERM" };
286
+ const next = resolveShutdownMarker(prior, "SIGTERM", now);
287
+ expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
288
+ });
289
+
290
+ it("falls back when the prior reason is stale (>30s)", () => {
291
+ // A stale prior reason almost certainly belongs to an earlier
292
+ // shutdown cycle, not the current one. Using it would mis-attribute
293
+ // today's restart to yesterday's initiator. The 30s window matches
294
+ // writeRestartReasonMarker's preserveExisting guard in
295
+ // src/agents/lifecycle.ts.
296
+ const now = 1_700_000_000_000;
297
+ const prior: CleanShutdownMarker = {
298
+ ts: now - 60_000,
299
+ signal: "SIGTERM",
300
+ reason: "cli: deploying 0badc0de",
301
+ };
302
+ const next = resolveShutdownMarker(prior, "SIGTERM", now);
303
+ expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
304
+ });
305
+
306
+ it("falls back when the prior reason is in the future (clock skew)", () => {
307
+ // A negative age indicates clock skew; don't trust it.
308
+ const now = 1_700_000_000_000;
309
+ const prior: CleanShutdownMarker = {
310
+ ts: now + 5_000,
311
+ signal: "SIGTERM",
312
+ reason: "cli: restart",
313
+ };
314
+ const next = resolveShutdownMarker(prior, "SIGTERM", now);
315
+ expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
316
+ });
317
+
318
+ it("respects a custom maxPreserveAgeMs", () => {
319
+ const now = 1_700_000_000_000;
320
+ const prior: CleanShutdownMarker = {
321
+ ts: now - 10_000,
322
+ signal: "SIGTERM",
323
+ reason: "watchdog: bridge disconnected for 145s",
324
+ };
325
+ // Fresh under the default 30s guard, stale under a 5s guard.
326
+ expect(resolveShutdownMarker(prior, "SIGTERM", now).reason).toBe(
327
+ "watchdog: bridge disconnected for 145s",
328
+ );
329
+ expect(resolveShutdownMarker(prior, "SIGTERM", now, 5_000).reason).toBe(
330
+ EXTERNAL_RESTART_FALLBACK_REASON,
331
+ );
332
+ });
333
+
334
+ it("copies the incoming signal onto the resolved marker (SIGTERM vs SIGINT)", () => {
335
+ const now = 1_700_000_000_000;
336
+ expect(resolveShutdownMarker(null, "SIGTERM", now).signal).toBe("SIGTERM");
337
+ expect(resolveShutdownMarker(null, "SIGINT", now).signal).toBe("SIGINT");
338
+ });
339
+
340
+ it("REASON_PRESERVE_MAX_AGE_MS matches the lifecycle.ts cooperative window", () => {
341
+ // Load-bearing constant — if the two windows drift out of sync, the
342
+ // CLI-stamps-then-SIGTERM race gets harder to reason about. Pin it.
343
+ expect(REASON_PRESERVE_MAX_AGE_MS).toBe(30_000);
344
+ });
345
+ });
346
+
347
+ describe("gateway.ts shutdown-handler wiring (source-level)", () => {
348
+ // Source-grep pins so a future refactor can't silently drop the
349
+ // reason-preserving + fallback-writing behaviour the 2026-04-24 fix
350
+ // added to the SIGTERM handler.
351
+ const gatewaySource = readFileSync(
352
+ join(import.meta.dir, "..", "gateway", "gateway.ts"),
353
+ "utf8",
354
+ );
355
+
356
+ it("reads any prior marker before writing the new one", () => {
357
+ // The handler MUST readCleanShutdownMarker first — writing without
358
+ // the readback clobbers reasons stamped by CLI/watchdog/user-slash
359
+ // initiators that ran microseconds before SIGTERM.
360
+ expect(gatewaySource).toMatch(
361
+ /const prior = readCleanShutdownMarker\(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH\)/,
362
+ );
363
+ });
364
+
365
+ it("routes the marker computation through resolveShutdownMarker", () => {
366
+ expect(gatewaySource).toMatch(
367
+ /const next = resolveShutdownMarker\(prior, signal, Date\.now\(\)\)/,
368
+ );
369
+ expect(gatewaySource).toMatch(
370
+ /writeCleanShutdownMarker\(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH, next\)/,
371
+ );
372
+ });
373
+
374
+ it("logs the resolved reason + preserved flag so journals are diagnosable", () => {
375
+ // The journal line is what operators grep when "why did the greeting
376
+ // say X?" questions come up. Keep it present.
377
+ expect(gatewaySource).toContain("shutdown.clean_marker_written");
378
+ expect(gatewaySource).toContain("reason=${JSON.stringify(next.reason ?? '')}");
379
+ });
380
+ });
381
+
382
+ describe("gateway.ts crash-path guard (source-level)", () => {
383
+ // Regression for the reviewer-found bug in PR #55: shutdown() is called
384
+ // for OS signals AND for uncaughtException/unhandledRejection (PR #53 nit
385
+ // fix). The marker write must be GATED on signal value — otherwise a
386
+ // crash writes the marker and silently suppresses its own recovery
387
+ // banner at the next boot, defeating the entire feature.
388
+ //
389
+ // Pinned via source-level grep because the gateway.ts shutdown() function
390
+ // can't be unit-tested in isolation without mocking the entire bot/IPC
391
+ // surface. If anyone removes the guard, this test fails.
392
+ const gatewaySource = readFileSync(
393
+ join(import.meta.dir, "..", "gateway", "gateway.ts"),
394
+ "utf8",
395
+ );
396
+
397
+ it("guards writeCleanShutdownMarker on isOsSignal", () => {
398
+ expect(gatewaySource).toContain(
399
+ `const isOsSignal = signal === 'SIGTERM' || signal === 'SIGINT'`,
400
+ );
401
+ expect(gatewaySource).toContain(`if (isOsSignal) {`);
402
+ });
403
+
404
+ it("logs the skip path so crashes leave a journal trail", () => {
405
+ expect(gatewaySource).toContain(`shutdown.clean_marker_skipped`);
406
+ });
407
+
408
+ it("crash-handler signal labels stay distinguishable from OS signals", () => {
409
+ // shutdown() is called with these literal signal strings from the
410
+ // crash handlers. Each must NOT match isOsSignal above.
411
+ expect(gatewaySource).toMatch(/shutdown\(['"]uncaughtException['"]/);
412
+ expect(gatewaySource).toMatch(/shutdown\(['"]unhandledRejection['"]/);
413
+ });
414
+ });
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Contract test for `flushOnAgentDisconnect` — the gating policy applied
3
+ * when a client disconnects from the IPC server.
4
+ *
5
+ * Bug context (#553 hotfix): the gateway's `onClientDisconnected` used to
6
+ * unconditionally flush every active StatusReactionController to 👍 and
7
+ * dispose the progress driver. This fired on EVERY disconnect, including
8
+ * anonymous one-shot connections from `vendor/hindsight-memory`'s
9
+ * `recall.py` hook. The user-visible symptom: 👀 reaction (received) was
10
+ * immediately followed by 👍 (done) while the agent was still producing
11
+ * its reply, plus a duplicate edited-message bug from the re-created
12
+ * progress driver.
13
+ *
14
+ * The fix scopes the flush to clients whose `agentName` is non-null —
15
+ * i.e. clients that actually completed a `register` IPC handshake.
16
+ * Anonymous clients (recall.py and similar) are silently no-op.
17
+ */
18
+
19
+ import { describe, it, expect, vi } from 'vitest'
20
+ import { flushOnAgentDisconnect } from '../gateway/disconnect-flush.js'
21
+
22
+ interface FakeCtrl {
23
+ setDone: () => void
24
+ }
25
+ interface FakeStream {
26
+ isFinal: () => boolean
27
+ finalize: () => Promise<void>
28
+ }
29
+
30
+ function makeDeps(agentName: string | null) {
31
+ const setDoneA = vi.fn()
32
+ const setDoneB = vi.fn()
33
+ const finalizeA = vi.fn(async () => {})
34
+ const finalizeB = vi.fn(async () => {})
35
+ const clearActiveReactions = vi.fn()
36
+ const disposeProgressDriver = vi.fn()
37
+ const log = vi.fn()
38
+
39
+ const activeStatusReactions = new Map<string, FakeCtrl>([
40
+ ['chat1:thr1:msg1', { setDone: setDoneA }],
41
+ ['chat2:thr2:msg2', { setDone: setDoneB }],
42
+ ])
43
+ const activeReactionMsgIds = new Map<string, { chatId: string; messageId: number }>([
44
+ ['chat1:thr1:msg1', { chatId: 'chat1', messageId: 1 }],
45
+ ['chat2:thr2:msg2', { chatId: 'chat2', messageId: 2 }],
46
+ ])
47
+ const activeTurnStartedAt = new Map<string, number>([
48
+ ['chat1:thr1:msg1', 100],
49
+ ['chat2:thr2:msg2', 200],
50
+ ])
51
+ const activeDraftStreams = new Map<string, FakeStream>([
52
+ ['chat1:thr1:r1', { isFinal: () => false, finalize: finalizeA }],
53
+ ['chat2:thr2:r2', { isFinal: () => true, finalize: finalizeB }],
54
+ ])
55
+ const activeDraftParseModes = new Map<string, 'HTML' | 'MarkdownV2' | undefined>([
56
+ ['chat1:thr1:r1', 'HTML'],
57
+ ['chat2:thr2:r2', undefined],
58
+ ])
59
+
60
+ return {
61
+ spies: { setDoneA, setDoneB, finalizeA, finalizeB, clearActiveReactions, disposeProgressDriver, log },
62
+ deps: {
63
+ agentName,
64
+ activeStatusReactions,
65
+ activeReactionMsgIds,
66
+ activeTurnStartedAt,
67
+ activeDraftStreams,
68
+ activeDraftParseModes,
69
+ clearActiveReactions,
70
+ disposeProgressDriver,
71
+ log,
72
+ },
73
+ }
74
+ }
75
+
76
+ describe('flushOnAgentDisconnect — anonymous clients (the regression scenario)', () => {
77
+ it('returns false and does NOT touch any state when agentName is null', () => {
78
+ const { spies, deps } = makeDeps(null)
79
+
80
+ const ran = flushOnAgentDisconnect(deps)
81
+
82
+ expect(ran).toBe(false)
83
+ expect(spies.setDoneA).not.toHaveBeenCalled()
84
+ expect(spies.setDoneB).not.toHaveBeenCalled()
85
+ expect(spies.finalizeA).not.toHaveBeenCalled()
86
+ expect(spies.finalizeB).not.toHaveBeenCalled()
87
+ expect(spies.clearActiveReactions).not.toHaveBeenCalled()
88
+ expect(spies.disposeProgressDriver).not.toHaveBeenCalled()
89
+
90
+ // All maps untouched — sizes preserved.
91
+ expect(deps.activeStatusReactions.size).toBe(2)
92
+ expect(deps.activeReactionMsgIds.size).toBe(2)
93
+ expect(deps.activeTurnStartedAt.size).toBe(2)
94
+ expect(deps.activeDraftStreams.size).toBe(2)
95
+ expect(deps.activeDraftParseModes.size).toBe(2)
96
+
97
+ // But it should log so the operator can correlate the no-op decision.
98
+ expect(spies.log).toHaveBeenCalledTimes(1)
99
+ expect(spies.log.mock.calls[0][0]).toMatch(/anonymous client disconnect/i)
100
+ })
101
+
102
+ it('explicitly does not fire setDone on any controller for anonymous disconnects', () => {
103
+ // Tighter assertion of the user-visible bug: 👍 must NOT fire while the
104
+ // agent is producing its reply just because recall.py opened a socket.
105
+ const { spies, deps } = makeDeps(null)
106
+ flushOnAgentDisconnect(deps)
107
+ expect(spies.setDoneA).toHaveBeenCalledTimes(0)
108
+ expect(spies.setDoneB).toHaveBeenCalledTimes(0)
109
+ })
110
+ })
111
+
112
+ describe('flushOnAgentDisconnect — registered agent disconnects (existing behavior preserved)', () => {
113
+ it('returns true and flushes every status reaction for a real agent disconnect', () => {
114
+ const { spies, deps } = makeDeps('clerk')
115
+
116
+ const ran = flushOnAgentDisconnect(deps)
117
+
118
+ expect(ran).toBe(true)
119
+ expect(spies.setDoneA).toHaveBeenCalledTimes(1)
120
+ expect(spies.setDoneB).toHaveBeenCalledTimes(1)
121
+ expect(deps.activeStatusReactions.size).toBe(0)
122
+ expect(deps.activeReactionMsgIds.size).toBe(0)
123
+ expect(deps.activeTurnStartedAt.size).toBe(0)
124
+ })
125
+
126
+ it('disposes the progress driver and clears the on-disk reaction registry', () => {
127
+ const { spies, deps } = makeDeps('clerk')
128
+ flushOnAgentDisconnect(deps)
129
+ expect(spies.disposeProgressDriver).toHaveBeenCalledTimes(1)
130
+ expect(spies.clearActiveReactions).toHaveBeenCalledTimes(1)
131
+ })
132
+
133
+ it('finalizes only non-final draft streams and clears the maps', () => {
134
+ const { spies, deps } = makeDeps('clerk')
135
+ flushOnAgentDisconnect(deps)
136
+ // Stream A was non-final → finalize called.
137
+ expect(spies.finalizeA).toHaveBeenCalledTimes(1)
138
+ // Stream B was already final → finalize skipped.
139
+ expect(spies.finalizeB).not.toHaveBeenCalled()
140
+ // Both stream maps cleared regardless.
141
+ expect(deps.activeDraftStreams.size).toBe(0)
142
+ expect(deps.activeDraftParseModes.size).toBe(0)
143
+ })
144
+ })
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Tests the bridge's gateway→client validator.
3
+ *
4
+ * Mirror of ipc-validator.test.ts on the bridge side. The gateway's
5
+ * validator is strictly structural (no deep schema check); these tests
6
+ * pin down the exact shape the bridge will accept, so if the gateway
7
+ * ever drifts (e.g. renames `chatId` to `chat_id`) the bridge's parse
8
+ * fail mode is loud rather than a silent handler no-op.
9
+ */
10
+
11
+ import { describe, it, expect } from 'vitest'
12
+ import { validateGatewayMessage } from '../bridge/ipc-client.js'
13
+ import { allGatewayFixtures } from './protocol-fixtures.js'
14
+
15
+ describe('validateGatewayMessage', () => {
16
+ describe('generic rejection', () => {
17
+ it('rejects non-objects', () => {
18
+ expect(validateGatewayMessage(null)).toBe(false)
19
+ expect(validateGatewayMessage(undefined)).toBe(false)
20
+ expect(validateGatewayMessage(42)).toBe(false)
21
+ expect(validateGatewayMessage('str')).toBe(false)
22
+ })
23
+
24
+ it('rejects objects without a type field', () => {
25
+ expect(validateGatewayMessage({})).toBe(false)
26
+ expect(validateGatewayMessage({ chatId: 'x' })).toBe(false)
27
+ })
28
+
29
+ it('rejects unknown types', () => {
30
+ expect(validateGatewayMessage({ type: 'fake' })).toBe(false)
31
+ expect(validateGatewayMessage({ type: 'Inbound' })).toBe(false)
32
+ })
33
+ })
34
+
35
+ describe('all protocol fixtures pass', () => {
36
+ it.each(allGatewayFixtures.map((f) => [f.decoded.type, f] as const))(
37
+ '%s fixture is accepted',
38
+ (_type, fixture) => {
39
+ expect(validateGatewayMessage(JSON.parse(fixture.wire))).toBe(true)
40
+ },
41
+ )
42
+ })
43
+
44
+ describe('inbound', () => {
45
+ it('accepts minimal inbound (chatId + text)', () => {
46
+ expect(validateGatewayMessage({ type: 'inbound', chatId: 'c', text: 'hi' })).toBe(true)
47
+ })
48
+
49
+ it('rejects missing chatId', () => {
50
+ expect(validateGatewayMessage({ type: 'inbound', text: 'hi' })).toBe(false)
51
+ })
52
+
53
+ it('rejects non-string chatId', () => {
54
+ expect(validateGatewayMessage({ type: 'inbound', chatId: 42, text: 'hi' })).toBe(false)
55
+ })
56
+
57
+ it('rejects missing text', () => {
58
+ expect(validateGatewayMessage({ type: 'inbound', chatId: 'c' })).toBe(false)
59
+ })
60
+
61
+ it('rejects non-string text', () => {
62
+ expect(validateGatewayMessage({ type: 'inbound', chatId: 'c', text: 42 })).toBe(false)
63
+ })
64
+ })
65
+
66
+ describe('permission', () => {
67
+ it('accepts allow and deny', () => {
68
+ expect(
69
+ validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: 'allow' }),
70
+ ).toBe(true)
71
+ expect(
72
+ validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: 'deny' }),
73
+ ).toBe(true)
74
+ })
75
+
76
+ it('rejects unknown behavior', () => {
77
+ expect(
78
+ validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: 'maybe' }),
79
+ ).toBe(false)
80
+ })
81
+
82
+ it('rejects empty behavior', () => {
83
+ expect(
84
+ validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: '' }),
85
+ ).toBe(false)
86
+ })
87
+
88
+ it('rejects missing requestId', () => {
89
+ expect(validateGatewayMessage({ type: 'permission', behavior: 'allow' })).toBe(false)
90
+ })
91
+ })
92
+
93
+ describe('status', () => {
94
+ it('accepts any string status', () => {
95
+ expect(validateGatewayMessage({ type: 'status', status: 'anything' })).toBe(true)
96
+ })
97
+
98
+ it('rejects non-string status', () => {
99
+ expect(validateGatewayMessage({ type: 'status', status: 42 })).toBe(false)
100
+ })
101
+ })
102
+
103
+ describe('tool_call_result', () => {
104
+ it('accepts success result', () => {
105
+ expect(
106
+ validateGatewayMessage({ type: 'tool_call_result', id: 'x', success: true }),
107
+ ).toBe(true)
108
+ })
109
+
110
+ it('accepts failure result', () => {
111
+ expect(
112
+ validateGatewayMessage({
113
+ type: 'tool_call_result',
114
+ id: 'x',
115
+ success: false,
116
+ error: 'nope',
117
+ }),
118
+ ).toBe(true)
119
+ })
120
+
121
+ it('rejects missing id', () => {
122
+ expect(
123
+ validateGatewayMessage({ type: 'tool_call_result', success: true }),
124
+ ).toBe(false)
125
+ })
126
+
127
+ it('rejects non-boolean success', () => {
128
+ expect(
129
+ validateGatewayMessage({ type: 'tool_call_result', id: 'x', success: 'yes' }),
130
+ ).toBe(false)
131
+ })
132
+ })
133
+ })