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,1130 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Foreman — always-on admin bot for the switchroom fleet.
4
+ *
5
+ * Unlike per-agent gateways, the foreman is not bound to a single agent.
6
+ * It provides fleet-wide read-only and write visibility (Phase 3a + 3b).
7
+ *
8
+ * Configuration:
9
+ * ~/.switchroom/foreman/.env TELEGRAM_BOT_TOKEN=<token>
10
+ * ~/.switchroom/foreman/access.json { "allowFrom": ["<userId>"] }
11
+ *
12
+ * Phase 3a commands (read-only):
13
+ * /start, /help — greeting + command list
14
+ * /status, /list — fleet summary via `switchroom agent list --json`
15
+ * /logs <agent> [--tail N] — journalctl output, paginated > 3 KB
16
+ * /auth [agent] — fleet auth dashboard (per-agent, agent-name-parametric)
17
+ *
18
+ * Phase 3b commands (write):
19
+ * /restart <agent> — systemctl --user restart switchroom-<agent>
20
+ * /delete <agent> — 2-step confirm → archive dir + destroy unit
21
+ * /update — switchroom update (paginated output)
22
+ * /create-agent [name] — multi-turn flow: profile → bot token → OAuth
23
+ * /setup [slug] — guided new-agent wizard (slug → persona → model → emoji → token → allowlist → start)
24
+ */
25
+
26
+ import { Bot, InlineKeyboard, type Context } from 'grammy'
27
+ import { readFileSync, writeFileSync, chmodSync, mkdirSync } from 'fs'
28
+ import { homedir } from 'os'
29
+ import { join, resolve } from 'path'
30
+ import { listWorktrees } from '../../src/worktree/list.js'
31
+ import { installPluginLogger } from '../plugin-logger.js'
32
+ import {
33
+ escapeHtmlForTg,
34
+ installTgPostLogger,
35
+ isAllowedSender,
36
+ makeSwitchroomExec,
37
+ makeSwitchroomExecCombined,
38
+ makeSwitchroomExecJson,
39
+ makeSwitchroomReply,
40
+ runPollingLoop,
41
+ } from '../shared/bot-runtime.js'
42
+ import {
43
+ assertSafeAgentName,
44
+ buildFleetSummary,
45
+ handleLogsCommand,
46
+ handleRestartCommand,
47
+ handleDeleteCommand,
48
+ executeDeleteAgent,
49
+ handleUpdateCommand,
50
+ handleVersionCommand,
51
+ } from './foreman-handlers.js'
52
+ import {
53
+ buildDashboard,
54
+ isQuotaHot,
55
+ type DashboardState,
56
+ type DashboardSlot,
57
+ type SlotHealth,
58
+ } from '../auth-dashboard.js'
59
+ import { parseAuthSubCommand } from '../auth-slot-parser.js'
60
+ import {
61
+ getState,
62
+ setState,
63
+ clearState,
64
+ listActiveFlows,
65
+ } from './state.js'
66
+ import {
67
+ startCreateFlow,
68
+ handleFlowText,
69
+ makeInitialState,
70
+ advanceState,
71
+ stepLabel,
72
+ } from './foreman-create-flow.js'
73
+ import { listAvailableProfiles } from '../../src/agents/profiles.js'
74
+ import { createAgent, completeCreation } from '../../src/agents/create-orchestrator.js'
75
+ import { validateBotToken } from '../../src/setup/telegram-api.js'
76
+ import { resolveAgentsDir, loadConfig } from '../../src/config/loader.js'
77
+ import {
78
+ getSetupState,
79
+ setSetupState,
80
+ clearSetupState,
81
+ listActiveSetupFlows,
82
+ } from './setup-state.js'
83
+ import {
84
+ startSetupFlow,
85
+ handleSetupText,
86
+ makeSetupInitialState,
87
+ advanceSetupState,
88
+ setupStepLabel,
89
+ } from './setup-flow.js'
90
+
91
+ // ─── Stderr logging ───────────────────────────────────────────────────────
92
+ installPluginLogger()
93
+
94
+ // ─── Config dir ───────────────────────────────────────────────────────────
95
+ const FOREMAN_DIR = process.env.SWITCHROOM_FOREMAN_DIR
96
+ ?? join(homedir(), '.switchroom', 'foreman')
97
+ const ENV_FILE = join(FOREMAN_DIR, '.env')
98
+ const ACCESS_FILE = join(FOREMAN_DIR, 'access.json')
99
+
100
+ // ─── Load .env ────────────────────────────────────────────────────────────
101
+ try {
102
+ chmodSync(ENV_FILE, 0o600)
103
+ for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
104
+ const m = line.match(/^(\w+)=(.*)$/)
105
+ if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2]
106
+ }
107
+ } catch (err) {
108
+ const code = (err as NodeJS.ErrnoException)?.code
109
+ if (code !== 'ENOENT') {
110
+ process.stderr.write(
111
+ `foreman: warning — failed to load ${ENV_FILE}: ${(err as Error).message}\n`,
112
+ )
113
+ }
114
+ }
115
+
116
+ // ─── Bot token ────────────────────────────────────────────────────────────
117
+ const TOKEN = process.env.TELEGRAM_BOT_TOKEN
118
+ if (!TOKEN) {
119
+ process.stderr.write(
120
+ `foreman: TELEGRAM_BOT_TOKEN required\n` +
121
+ ` set in ${ENV_FILE}\n` +
122
+ ` format: TELEGRAM_BOT_TOKEN=123456789:AAH...\n`,
123
+ )
124
+ process.exit(1)
125
+ }
126
+
127
+ // ─── Access list ──────────────────────────────────────────────────────────
128
+ function loadAllowFrom(): string[] {
129
+ try {
130
+ const raw = JSON.parse(readFileSync(ACCESS_FILE, 'utf8')) as { allowFrom?: unknown }
131
+ if (Array.isArray(raw.allowFrom)) {
132
+ return (raw.allowFrom as unknown[]).map(String)
133
+ }
134
+ } catch {
135
+ /* fall through — return empty */
136
+ }
137
+ return []
138
+ }
139
+
140
+ // ─── CLI exec helpers ─────────────────────────────────────────────────────
141
+ const switchroomExec = makeSwitchroomExec()
142
+ const switchroomExecCombined = makeSwitchroomExecCombined()
143
+ const switchroomExecJson = makeSwitchroomExecJson()
144
+
145
+ // ─── Bot ──────────────────────────────────────────────────────────────────
146
+ const bot = new Bot(TOKEN)
147
+ installTgPostLogger(bot)
148
+
149
+ // No forum-topic routing in foreman — it's always a DM.
150
+ const switchroomReply = makeSwitchroomReply(() => undefined)
151
+
152
+ // ─── Auth guard middleware ────────────────────────────────────────────────
153
+ bot.use(async (ctx, next) => {
154
+ // Silently ignore any message that is not a private DM.
155
+ // If the foreman bot is ever added to a group, this prevents fleet info
156
+ // from leaking to all group members even when the sender is allowlisted.
157
+ if (ctx.chat?.type !== 'private') return
158
+ if (!ctx.from) return
159
+ const allowFrom = loadAllowFrom()
160
+ if (!isAllowedSender(ctx, allowFrom)) {
161
+ process.stderr.write(`foreman: rejected message from user ${ctx.from.id}\n`)
162
+ return
163
+ }
164
+ await next()
165
+ })
166
+
167
+ // ─── Helpers ─────────────────────────────────────────────────────────────
168
+
169
+ /** Fetch auth dashboard state for a named agent. */
170
+ function fetchForemanDashboardState(agent: string): DashboardState | null {
171
+ type SlotListing = {
172
+ slots: Array<{
173
+ slot: string; active: boolean; health: string;
174
+ quota_exhausted_until?: number | null;
175
+ }>
176
+ }
177
+ let slots: DashboardSlot[] = []
178
+ try {
179
+ const listing = switchroomExecJson<SlotListing>(['auth', 'list', agent, '--json'])
180
+ if (listing && Array.isArray(listing.slots)) {
181
+ slots = listing.slots.map(s => ({
182
+ slot: s.slot,
183
+ active: s.active,
184
+ health: (s.health as SlotHealth) ?? 'missing',
185
+ quotaExhaustedUntil: s.quota_exhausted_until ?? null,
186
+ fiveHourPct: null,
187
+ sevenDayPct: null,
188
+ }))
189
+ }
190
+ } catch {
191
+ return null
192
+ }
193
+
194
+ let plan: string | null = null
195
+ let rateLimitTier: string | null = null
196
+ try {
197
+ type AuthStatusResp = {
198
+ agents: Array<{ name: string; subscription_type: string | null; rate_limit_tier?: string | null }>
199
+ }
200
+ const statusData = switchroomExecJson<AuthStatusResp>(['auth', 'status'])
201
+ const thisAgent = statusData?.agents?.find(a => a.name === agent)
202
+ if (thisAgent?.subscription_type) plan = thisAgent.subscription_type
203
+ if (thisAgent?.rate_limit_tier) rateLimitTier = thisAgent.rate_limit_tier
204
+ } catch { /* best-effort */ }
205
+
206
+ return {
207
+ agent,
208
+ bankId: agent,
209
+ plan,
210
+ rateLimitTier,
211
+ slots,
212
+ quotaHot: isQuotaHot(slots),
213
+ generatedAt: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
214
+ }
215
+ }
216
+
217
+ // ─── /start ──────────────────────────────────────────────────────────────
218
+ bot.command('start', async ctx => {
219
+ await switchroomReply(ctx, [
220
+ '<b>Foreman — switchroom fleet admin</b>',
221
+ '',
222
+ 'Read-only commands:',
223
+ ' /status, /list — fleet summary',
224
+ ' /logs &lt;agent&gt; [--tail N] — last N log lines (default 50)',
225
+ ' /auth [agent] — auth dashboard',
226
+ ' /version — show versions + running agent health',
227
+ '',
228
+ 'Write commands:',
229
+ ' /restart &lt;agent&gt; — restart an agent',
230
+ ' /delete &lt;agent&gt; — delete an agent (2-step confirm)',
231
+ ' /update — update switchroom',
232
+ ' /setup [slug] — guided new-agent wizard',
233
+ ' /create-agent [name] — create a new agent (legacy multi-turn)',
234
+ ].join('\n'), { html: true })
235
+ })
236
+
237
+ // ─── /help ───────────────────────────────────────────────────────────────
238
+ bot.command('help', async ctx => {
239
+ await switchroomReply(ctx, [
240
+ '<b>Foreman commands</b>',
241
+ '',
242
+ '<b>Fleet info:</b>',
243
+ '/status, /list — show fleet status',
244
+ '/logs &lt;agent&gt; [--tail N] — show agent journal logs',
245
+ '/auth [agent] — auth slot dashboard for an agent',
246
+ '',
247
+ '<b>Fleet management:</b>',
248
+ '/restart &lt;agent&gt; — restart an agent via systemctl',
249
+ '/delete &lt;agent&gt; — delete agent (confirms, then archives dir)',
250
+ '/update — pull latest switchroom + reconcile agents',
251
+ '/setup [slug] — guided wizard: slug → persona → model → emoji → token → start',
252
+ '/create-agent [name] — legacy interactive new-agent wizard',
253
+ '',
254
+ '<b>Examples:</b>',
255
+ '<code>/logs gymbro --tail 100</code>',
256
+ '<code>/restart gymbro</code>',
257
+ '<code>/setup gymbro</code>',
258
+ ].join('\n'), { html: true })
259
+ })
260
+
261
+ // ─── /status + /list ──────────────────────────────────────────────────────
262
+ bot.command(['status', 'list'], async ctx => {
263
+ const summary = buildFleetSummary(switchroomExecJson)
264
+ await switchroomReply(ctx, summary, { html: true })
265
+ })
266
+
267
+ // ─── /logs ───────────────────────────────────────────────────────────────
268
+ bot.command('logs', async ctx => {
269
+ const result = handleLogsCommand((ctx.match ?? '') as string)
270
+ for (const reply of result.replies) {
271
+ await switchroomReply(ctx, reply.text, { html: reply.html })
272
+ }
273
+ })
274
+
275
+ // ─── /auth ────────────────────────────────────────────────────────────────
276
+ bot.command('auth', async ctx => {
277
+ const rawArgs = ((ctx.match ?? '') as string).trim()
278
+
279
+ // Determine which agents to show
280
+ let agentNames: string[]
281
+
282
+ if (rawArgs) {
283
+ // User specified an agent name. parseAuthSubCommand needs the
284
+ // arguments split into parts and a fallback agent name. The 'status'
285
+ // intent kind has no `agent` field — narrow before reading it.
286
+ const parts = rawArgs.split(/\s+/)
287
+ const parsed = parseAuthSubCommand(parts, parts[0] ?? '')
288
+ const agentArg = ('agent' in parsed ? parsed.agent : undefined) || parts[0] || ''
289
+ try { assertSafeAgentName(agentArg) } catch {
290
+ await switchroomReply(ctx, 'Invalid agent name.', { html: true })
291
+ return
292
+ }
293
+ agentNames = [agentArg]
294
+ } else {
295
+ // Enumerate all agents
296
+ try {
297
+ const data = switchroomExecJson<{ agents: Array<{ name: string }> }>(['agent', 'list'])
298
+ agentNames = data?.agents?.map(a => a.name) ?? []
299
+ } catch {
300
+ agentNames = []
301
+ }
302
+ if (agentNames.length === 0) {
303
+ await switchroomReply(ctx, '<i>No agents found. Try <code>/auth &lt;agentname&gt;</code>.</i>', { html: true })
304
+ return
305
+ }
306
+ }
307
+
308
+ // Render dashboard per agent
309
+ for (const agent of agentNames) {
310
+ const state = fetchForemanDashboardState(agent)
311
+ if (!state) {
312
+ await switchroomReply(ctx,
313
+ `<b>/auth ${escapeHtmlForTg(agent)}</b> — no data (agent missing or CLI unreachable)`,
314
+ { html: true },
315
+ )
316
+ continue
317
+ }
318
+ const { text, keyboard } = buildDashboard(state)
319
+ await ctx.reply(text, { parse_mode: 'HTML', reply_markup: keyboard, link_preview_options: { is_disabled: true } })
320
+ }
321
+ })
322
+
323
+ // ─── /restart ─────────────────────────────────────────────────────────────
324
+ bot.command('restart', async ctx => {
325
+ const result = handleRestartCommand((ctx.match ?? '') as string)
326
+ await switchroomReply(ctx, result.text, { html: result.html })
327
+ })
328
+
329
+ // ─── /delete ──────────────────────────────────────────────────────────────
330
+
331
+ /**
332
+ * In-memory map of chatId → pending delete agent name.
333
+ * Cleared when the user confirms or the conversation moves on.
334
+ * For lightweight 2-step confirm — no SQLite needed since this is ephemeral.
335
+ */
336
+ const pendingDeletes = new Map<string, string>()
337
+
338
+ bot.command('delete', async ctx => {
339
+ const result = handleDeleteCommand((ctx.match ?? '') as string)
340
+ for (const reply of result.replies) {
341
+ await switchroomReply(ctx, reply.text, { html: reply.html })
342
+ }
343
+ if (result.needsConfirm && result.agentForConfirm) {
344
+ const chatId = String(ctx.chat!.id)
345
+ pendingDeletes.set(chatId, result.agentForConfirm)
346
+ }
347
+ })
348
+
349
+ // ─── /update ──────────────────────────────────────────────────────────────
350
+ bot.command('update', async ctx => {
351
+ await switchroomReply(ctx, 'Running <code>switchroom update</code>…', { html: true })
352
+ const result = handleUpdateCommand(switchroomExecCombined)
353
+ for (const reply of result.replies) {
354
+ await switchroomReply(ctx, reply.text, { html: reply.html })
355
+ }
356
+ })
357
+
358
+ // ─── /version ─────────────────────────────────────────────────────────────
359
+ bot.command('version', async ctx => {
360
+ const result = handleVersionCommand(switchroomExecCombined)
361
+ for (const reply of result.replies) {
362
+ await switchroomReply(ctx, reply.text, { html: reply.html })
363
+ }
364
+ })
365
+
366
+ // ─── /worktrees ───────────────────────────────────────────────────────────
367
+ bot.command('worktrees', async ctx => {
368
+ try {
369
+ const { worktrees } = listWorktrees()
370
+ if (worktrees.length === 0) {
371
+ await switchroomReply(ctx, 'No active worktrees.', { html: false })
372
+ return
373
+ }
374
+ const lines = ['<b>Active worktrees</b>', '']
375
+ for (const w of worktrees) {
376
+ const ageMin = Math.floor(w.ageSeconds / 60)
377
+ const hbMin = Math.floor(w.heartbeatAgeSeconds / 60)
378
+ const owner = w.ownerAgent ? ` (${escapeHtmlForTg(w.ownerAgent)})` : ''
379
+ lines.push(
380
+ `• <code>${escapeHtmlForTg(w.repoName)}</code>${owner} — branch <code>${escapeHtmlForTg(w.branch)}</code>, age ${ageMin}m, hb ${hbMin}m`,
381
+ )
382
+ }
383
+ await switchroomReply(ctx, lines.join('\n'), { html: true })
384
+ } catch (err) {
385
+ await switchroomReply(ctx, `<b>worktrees failed:</b> ${escapeHtmlForTg((err as Error).message)}`, { html: true })
386
+ }
387
+ })
388
+
389
+ // ─── /setup ───────────────────────────────────────────────────────────────
390
+ //
391
+ // Guided wizard: slug → persona name → model → emoji → bot token → allowlist
392
+ // confirmation → reconcile (createAgent) + start.
393
+ //
394
+ // Deferral notes:
395
+ // // TODO(#188): BotFather auto-flow — currently user creates bot manually
396
+ // // TODO(#189): OAuth code paste step — currently shows manual terminal instruction
397
+ // // TODO(#190): Skills selector — currently shows placeholder message
398
+
399
+ bot.command(['setup', 'createagent'], async ctx => {
400
+ const chatId = String(ctx.chat!.id)
401
+ const inlineSlug = ((ctx.match ?? '') as string).trim().split(/\s+/)[0] || null
402
+
403
+ // If there's already an active setup flow, remind the user
404
+ const existing = getSetupState(chatId)
405
+ if (existing && existing.step !== 'done') {
406
+ await switchroomReply(ctx, [
407
+ `A setup wizard is already in progress for <b>${escapeHtmlForTg(existing.slug ?? '?')}</b> (${setupStepLabel(existing.step)}).`,
408
+ '',
409
+ 'Continue by sending your answer, or type <code>cancel</code> to abort.',
410
+ ].join('\n'), { html: true })
411
+ return
412
+ }
413
+
414
+ const action = startSetupFlow(inlineSlug)
415
+
416
+ if (action.kind === 'error') {
417
+ await switchroomReply(ctx, action.message, { html: true })
418
+ return
419
+ }
420
+
421
+ if (action.kind === 'ask-slug') {
422
+ const state = makeSetupInitialState(chatId, null)
423
+ setSetupState(state)
424
+ await switchroomReply(ctx, [
425
+ '<b>New agent wizard</b>',
426
+ '',
427
+ 'Step 1/5: What slug (short name) should this agent use?',
428
+ '<i>e.g. <code>gymbro</code> — lowercase, hyphens/underscores OK, max 51 chars</i>',
429
+ '',
430
+ 'Type <code>cancel</code> at any time to abort.',
431
+ ].join('\n'), { html: true })
432
+ return
433
+ }
434
+
435
+ if (action.kind === 'ask-persona') {
436
+ const state = makeSetupInitialState(chatId, inlineSlug)
437
+ setSetupState(state)
438
+ await switchroomReply(ctx, [
439
+ `<b>New agent wizard</b> — slug: <code>${escapeHtmlForTg(inlineSlug!)}</code>`,
440
+ '',
441
+ 'Step 2/5: What should this agent\'s persona name be?',
442
+ '<i>e.g. <code>Gym Bro</code> — displayed in greetings and topics</i>',
443
+ ].join('\n'), { html: true })
444
+ return
445
+ }
446
+ })
447
+
448
+ // ─── /cancel (setup wizard abort) ────────────────────────────────────────
449
+
450
+ bot.command('cancel', async ctx => {
451
+ const chatId = String(ctx.chat!.id)
452
+ const setupState = getSetupState(chatId)
453
+ if (setupState && setupState.step !== 'done') {
454
+ clearSetupState(chatId)
455
+ await switchroomReply(ctx, 'Setup wizard cancelled. Type /setup to start a new one.', { html: false })
456
+ return
457
+ }
458
+ // No active setup flow — check create-agent flow
459
+ const createState = getState(chatId)
460
+ if (createState && createState.step !== 'done') {
461
+ clearState(chatId)
462
+ await switchroomReply(ctx, 'Create-agent flow cancelled.', { html: false })
463
+ return
464
+ }
465
+ await switchroomReply(ctx, 'No active wizard to cancel.', { html: false })
466
+ })
467
+
468
+ // ─── /create-agent ────────────────────────────────────────────────────────
469
+
470
+ bot.command('create_agent', async ctx => {
471
+ await handleCreateAgentCommand(ctx, (ctx.match ?? '') as string)
472
+ })
473
+ // Also register with hyphen (Telegram normalises _ and - differently per client)
474
+ bot.command('create-agent', async ctx => {
475
+ await handleCreateAgentCommand(ctx, (ctx.match ?? '') as string)
476
+ })
477
+
478
+ async function handleCreateAgentCommand(ctx: Context, match: string): Promise<void> {
479
+ const chatId = String(ctx.chat!.id)
480
+ const inlineName = match.trim().split(/\s+/)[0] || null
481
+
482
+ let profiles: string[]
483
+ try {
484
+ profiles = listAvailableProfiles()
485
+ } catch {
486
+ await switchroomReply(ctx, 'Could not load profiles. Is switchroom installed correctly?', { html: false })
487
+ return
488
+ }
489
+
490
+ const action = startCreateFlow(inlineName, profiles)
491
+
492
+ if (action.kind === 'error') {
493
+ await switchroomReply(ctx, action.message, { html: true })
494
+ return
495
+ }
496
+
497
+ if (action.kind === 'ask-name') {
498
+ const state = makeInitialState(chatId, null)
499
+ setState(state)
500
+ await switchroomReply(ctx, 'What should the new agent be named? (lowercase, hyphens/underscores OK)', { html: false })
501
+ return
502
+ }
503
+
504
+ if (action.kind === 'ask-profile') {
505
+ const state = makeInitialState(chatId, inlineName)
506
+ setState(state)
507
+ const kb = new InlineKeyboard()
508
+ for (const p of profiles) {
509
+ kb.text(p, `cf:profile:${p}`).row()
510
+ }
511
+ await ctx.reply(
512
+ `Choose a profile for <b>${escapeHtmlForTg(inlineName!)}</b>:`,
513
+ { parse_mode: 'HTML', reply_markup: kb },
514
+ )
515
+ return
516
+ }
517
+ }
518
+
519
+ // ─── Create-agent: callback_query for profile selection ───────────────────
520
+
521
+ bot.on('callback_query:data', async ctx => {
522
+ // Defense-in-depth: the global bot.use middleware already fires a
523
+ // `ctx.chat?.type !== 'private'` check, but callback_query updates from
524
+ // inline messages can arrive without a ctx.chat (callback_query.message
525
+ // is populated but ctx.chat may be undefined in edge cases). The global
526
+ // guard does `undefined !== 'private'` = true = ALLOW, so re-check here
527
+ // explicitly. If this isn't a private chat, silently drop.
528
+ if (ctx.chat?.type !== 'private') {
529
+ await ctx.answerCallbackQuery().catch(() => {})
530
+ return
531
+ }
532
+ const data = ctx.callbackQuery.data
533
+ const chatId = String(ctx.chat?.id ?? ctx.callbackQuery.from.id)
534
+
535
+ if (data.startsWith('cf:profile:')) {
536
+ const profile = data.slice('cf:profile:'.length)
537
+ await ctx.answerCallbackQuery()
538
+
539
+ const state = getState(chatId)
540
+ if (!state || (state.step !== 'asked-name' && state.step !== 'asked-profile')) {
541
+ await ctx.reply('No active create-agent flow. Use /create-agent to start.')
542
+ return
543
+ }
544
+
545
+ const profiles = listAvailableProfiles()
546
+ if (!profiles.includes(profile)) {
547
+ await ctx.reply('Unknown profile. Use /create-agent to restart.')
548
+ return
549
+ }
550
+
551
+ const updated = advanceState(state, { step: 'asked-bot-token', profile })
552
+ setState(updated)
553
+
554
+ await ctx.reply(
555
+ `Profile <b>${escapeHtmlForTg(profile)}</b> selected.\n\nPaste the BotFather token for the new agent's Telegram bot:\n<i>(Note: this token will be visible in this chat)</i>`,
556
+ { parse_mode: 'HTML' },
557
+ )
558
+ return
559
+ }
560
+
561
+ // Unknown callback — ignore
562
+ await ctx.answerCallbackQuery()
563
+ })
564
+
565
+ // ─── Inbound text router for multi-turn flows ─────────────────────────────
566
+
567
+ bot.on('message:text', async ctx => {
568
+ if (ctx.chat?.type !== 'private') return
569
+ const chatId = String(ctx.chat.id)
570
+ const text = ctx.message.text ?? ''
571
+
572
+ // 1. Check for pending delete confirmation
573
+ const pendingDelete = pendingDeletes.get(chatId)
574
+ if (pendingDelete && text.trim().toUpperCase() === 'YES') {
575
+ pendingDeletes.delete(chatId)
576
+ const result = executeDeleteAgent(pendingDelete, switchroomExec)
577
+ for (const reply of result.replies) {
578
+ await switchroomReply(ctx, reply.text, { html: reply.html })
579
+ }
580
+ return
581
+ }
582
+ if (pendingDelete) {
583
+ // Any non-YES text cancels the pending delete
584
+ pendingDeletes.delete(chatId)
585
+ await switchroomReply(ctx, 'Deletion cancelled.', { html: false })
586
+ return
587
+ }
588
+ // No pendingDelete on this chat. If the user's text is `YES` or `YES.`,
589
+ // they probably typed it expecting to confirm a delete that was queued
590
+ // before a foreman restart (pendingDeletes is in-memory; #28 item 7).
591
+ // Pre-fix this fell through and eventually rendered "Unknown command",
592
+ // which left the user wondering whether the delete went through. Surface
593
+ // a clear "no pending delete" message instead.
594
+ if (/^yes\.?$/i.test(text.trim())) {
595
+ await switchroomReply(
596
+ ctx,
597
+ 'There is no pending delete to confirm — the foreman may have restarted since you ran <code>/delete</code>. Re-run <code>/delete &lt;agent&gt;</code> if you still want to delete.',
598
+ { html: true },
599
+ )
600
+ return
601
+ }
602
+
603
+ // 2. Check for active /setup wizard flow
604
+ const setupState = getSetupState(chatId)
605
+ if (setupState && setupState.step !== 'done') {
606
+ await handleSetupFlowText(ctx, chatId, text, setupState)
607
+ return
608
+ }
609
+
610
+ // 3. Check for active create-agent flow
611
+ const flowState = getState(chatId)
612
+ if (flowState && flowState.step !== 'done') {
613
+ await handleCreateFlowText(ctx, chatId, text, flowState)
614
+ return
615
+ }
616
+
617
+ // 4. Unknown text
618
+ await switchroomReply(ctx, 'Unknown command. Try /help.', { html: true })
619
+ })
620
+
621
+ // ─── Setup wizard: text handler ───────────────────────────────────────────
622
+
623
+ async function handleSetupFlowText(
624
+ ctx: Context,
625
+ chatId: string,
626
+ text: string,
627
+ setupState: NonNullable<ReturnType<typeof getSetupState>>,
628
+ ): Promise<void> {
629
+ const callerId = String(ctx.from?.id ?? '')
630
+ // #190: pass live profiles into the wizard so the asked-profile step can
631
+ // validate against the operator's actual profile set.
632
+ const profiles = listAvailableProfiles()
633
+ const action = handleSetupText({ state: setupState, text, callerId, profiles })
634
+
635
+ switch (action.kind) {
636
+ // ── Slug step ──────────────────────────────────────────────────────────
637
+ case 'ask-persona': {
638
+ const updated = advanceSetupState(setupState, { step: 'asked-persona', slug: action.slug })
639
+ setSetupState(updated)
640
+ await switchroomReply(ctx, [
641
+ `Slug: <code>${escapeHtmlForTg(action.slug)}</code>`,
642
+ '',
643
+ 'Step 2/6: What persona name should this agent have?',
644
+ '<i>e.g. <code>Gym Bro</code> — displayed in greetings</i>',
645
+ ].join('\n'), { html: true })
646
+ return
647
+ }
648
+
649
+ // ── Persona step ───────────────────────────────────────────────────────
650
+ case 'ask-model': {
651
+ const updated = advanceSetupState(setupState, {
652
+ step: 'asked-model',
653
+ slug: action.slug,
654
+ persona: action.persona,
655
+ })
656
+ setSetupState(updated)
657
+ await switchroomReply(ctx, [
658
+ `Persona: <b>${escapeHtmlForTg(action.persona)}</b>`,
659
+ '',
660
+ 'Step 3/6: Which Claude model should this agent use?',
661
+ 'Options: <code>sonnet</code>, <code>opus</code>, <code>haiku</code>, or a full model ID.',
662
+ 'Type <code>skip</code> to use the profile default.',
663
+ ].join('\n'), { html: true })
664
+ return
665
+ }
666
+
667
+ // ── Model step ─────────────────────────────────────────────────────────
668
+ case 'ask-emoji': {
669
+ const updated = advanceSetupState(setupState, {
670
+ step: 'asked-emoji',
671
+ model: action.model,
672
+ })
673
+ setSetupState(updated)
674
+ const modelNote = action.model
675
+ ? `Model: <code>${escapeHtmlForTg(action.model)}</code>`
676
+ : 'Model: <i>profile default</i>'
677
+ await switchroomReply(ctx, [
678
+ modelNote,
679
+ '',
680
+ 'Step 4/6: What emoji should represent this agent\'s Telegram topic?',
681
+ 'Type <code>skip</code> to use the default.',
682
+ ].join('\n'), { html: true })
683
+ return
684
+ }
685
+
686
+ // ── Emoji step → Profile selector (#190) ───────────────────────────────
687
+ case 'ask-profile': {
688
+ const updated = advanceSetupState(setupState, {
689
+ step: 'asked-profile',
690
+ emoji: action.emoji,
691
+ })
692
+ setSetupState(updated)
693
+ const emojiNote = action.emoji
694
+ ? `Emoji: ${action.emoji}`
695
+ : 'Emoji: <i>default</i>'
696
+ await switchroomReply(ctx, [
697
+ emojiNote,
698
+ '',
699
+ `Step 5/6: Choose a profile for this agent.`,
700
+ `Available: ${action.profiles.map(p => `<code>${escapeHtmlForTg(p)}</code>`).join(', ')}`,
701
+ '',
702
+ '<i>The profile sets the agent\'s base persona, system prompt, and skill bundle. Pick <code>default</code> if unsure.</i>',
703
+ ].join('\n'), { html: true })
704
+ return
705
+ }
706
+
707
+ // ── Profile step ───────────────────────────────────────────────────────
708
+ case 'ask-bot-token': {
709
+ const updated = advanceSetupState(setupState, {
710
+ step: 'asked-bot-token',
711
+ profile: action.profile,
712
+ })
713
+ setSetupState(updated)
714
+ // TODO(#188): BotFather auto-flow — currently user creates bot manually
715
+ await switchroomReply(ctx, [
716
+ `Profile: <code>${escapeHtmlForTg(action.profile)}</code>`,
717
+ '',
718
+ 'Step 6/6: Paste the BotFather token for the new agent\'s bot.',
719
+ '',
720
+ '<b>To create a bot:</b>',
721
+ '1. Open @BotFather in Telegram',
722
+ '2. Send <code>/newbot</code> and follow the prompts',
723
+ '3. Copy and paste the token here',
724
+ '',
725
+ '<i>Note: the token will be briefly visible in this chat.</i>',
726
+ ].join('\n'), { html: true })
727
+ return
728
+ }
729
+
730
+ // ── Bot-token step ─────────────────────────────────────────────────────
731
+ case 'confirm-allowlist': {
732
+ const botToken = text.trim()
733
+ const updated = advanceSetupState(setupState, {
734
+ step: 'confirming-allowlist',
735
+ botToken,
736
+ })
737
+ setSetupState(updated)
738
+ await switchroomReply(ctx, [
739
+ 'Token received.',
740
+ '',
741
+ `Your Telegram user ID is <code>${escapeHtmlForTg(action.callerId)}</code>.`,
742
+ '',
743
+ 'Reply <b>yes</b> to set this as the only allowed user for the new agent,',
744
+ 'or paste a different user ID.',
745
+ ].join('\n'), { html: true })
746
+ return
747
+ }
748
+
749
+ // ── Allowlist confirmation step → provision agent (#189: split path) ──
750
+ //
751
+ // Pre-fix this was a single 'call-reconcile' that ran createAgent inline
752
+ // and told the user to run `switchroom auth code` from a terminal. Per
753
+ // #189 + #190, the flow now splits into three coordinated steps:
754
+ //
755
+ // call-create-agent → run createAgent (returns loginUrl)
756
+ // ask-oauth-code → render the URL prompt; user pastes back
757
+ // call-complete-creation → run completeCreation + start the agent
758
+ //
759
+ // Mirrors the create-flow's existing call-create-agent/asked-oauth-code/
760
+ // call-complete-creation triad. The two flows have parallel state
761
+ // machines and parallel orchestrator handlers.
762
+ case 'call-create-agent': {
763
+ const { slug, persona, model, emoji, profile, botToken, allowedUserId } = action
764
+ void model; void emoji; void allowedUserId // captured in state earlier; createAgent reads from yaml
765
+ const updated = advanceSetupState(setupState, {
766
+ step: 'reconciling',
767
+ allowedUserId,
768
+ })
769
+ setSetupState(updated)
770
+
771
+ await switchroomReply(ctx, `Validating token…`, { html: false })
772
+
773
+ // Validate token first
774
+ let botInfo: { username: string } | null = null
775
+ try {
776
+ botInfo = await validateBotToken(botToken)
777
+ } catch (err) {
778
+ const updatedBack = advanceSetupState(updated, { step: 'asked-bot-token' })
779
+ setSetupState(updatedBack)
780
+ await switchroomReply(ctx, [
781
+ `Token rejected by Telegram — ${escapeHtmlForTg((err as Error).message)}`,
782
+ '',
783
+ 'Please get a fresh token from @BotFather and paste it here:',
784
+ ].join('\n'), { html: true })
785
+ return
786
+ }
787
+
788
+ const botUsername = botInfo?.username ?? null
789
+ await switchroomReply(ctx, `Token OK (@${escapeHtmlForTg(botUsername ?? '?')}). Provisioning agent <b>${escapeHtmlForTg(slug)}</b>…`, { html: true })
790
+
791
+ try {
792
+ const result = await createAgent({
793
+ name: slug,
794
+ profile,
795
+ telegramBotToken: botToken,
796
+ rollbackOnFail: true,
797
+ })
798
+
799
+ if (result.loginUrl) {
800
+ // #189: in-wizard OAuth path. Stash the auth session + URL on the
801
+ // wizard state and ask the user to paste the code in chat.
802
+ const oauthState = advanceSetupState(updated, {
803
+ step: 'asked-oauth-code',
804
+ authSessionName: result.sessionName,
805
+ loginUrl: result.loginUrl,
806
+ })
807
+ setSetupState(oauthState)
808
+ const kb = new InlineKeyboard().url('Open OAuth URL', result.loginUrl)
809
+ await ctx.reply(
810
+ [
811
+ `<b>${escapeHtmlForTg(persona)}</b> (@${escapeHtmlForTg(botUsername ?? slug)}) is scaffolded!`,
812
+ '',
813
+ '<b>Step 6 of 6 — OAuth:</b>',
814
+ 'Open the URL below, log in to Claude, then paste the code back here.',
815
+ ].join('\n'),
816
+ { parse_mode: 'HTML', reply_markup: kb },
817
+ )
818
+ } else {
819
+ // No loginUrl returned — agent doesn't need OAuth (rare). Mark done.
820
+ const doneState = advanceSetupState(updated, { step: 'done' })
821
+ setSetupState(doneState)
822
+ clearSetupState(chatId)
823
+ await switchroomReply(ctx, [
824
+ `<b>${escapeHtmlForTg(persona)}</b> (@${escapeHtmlForTg(botUsername ?? slug)}) is scaffolded and ready!`,
825
+ '',
826
+ '<i>Skills can be added later via yaml or future /skills command.</i>',
827
+ ].join('\n'), { html: true })
828
+ }
829
+ } catch (err) {
830
+ // Rollback happened inside createAgent — reset to bot-token step
831
+ const updatedBack = advanceSetupState(updated, { step: 'asked-bot-token' })
832
+ setSetupState(updatedBack)
833
+ await switchroomReply(ctx, [
834
+ `<b>Provisioning failed:</b> ${escapeHtmlForTg((err as Error).message)}`,
835
+ '',
836
+ 'To retry, paste a bot token again. Or type <code>cancel</code> to abort.',
837
+ ].join('\n'), { html: true })
838
+ }
839
+ return
840
+ }
841
+
842
+ // ── ask-oauth-code is currently dispatched only by call-create-agent
843
+ // above (which goes straight to the InlineKeyboard render before
844
+ // setting state). The handler below covers any future path that
845
+ // emits it as a discrete action — currently dead-code-style
846
+ // safety, but keeping the case keeps the union check exhaustive.
847
+ case 'ask-oauth-code': {
848
+ const updated = advanceSetupState(setupState, {
849
+ step: 'asked-oauth-code',
850
+ loginUrl: action.loginUrl,
851
+ })
852
+ setSetupState(updated)
853
+ const promptLines = action.loginUrl
854
+ ? ['Open this URL to log in, then paste the code back here:']
855
+ : ['Auth session started. Paste the OAuth code back here:']
856
+ if (action.loginUrl) {
857
+ const kb = new InlineKeyboard().url('Open OAuth URL', action.loginUrl)
858
+ await ctx.reply(promptLines.join('\n'), { reply_markup: kb })
859
+ } else {
860
+ await switchroomReply(ctx, promptLines.join('\n'), { html: false })
861
+ }
862
+ return
863
+ }
864
+
865
+ // ── OAuth-code paste step → run completeCreation + start the agent ────
866
+ case 'call-complete-creation': {
867
+ const { slug, persona, code } = action
868
+ await switchroomReply(ctx, 'Submitting OAuth code…', { html: false })
869
+ try {
870
+ const result = await completeCreation(slug, code)
871
+ if (result.outcome.kind === 'success' && result.started) {
872
+ const doneState = advanceSetupState(setupState, { step: 'done' })
873
+ setSetupState(doneState)
874
+ clearSetupState(chatId)
875
+ await switchroomReply(ctx, [
876
+ `<b>${escapeHtmlForTg(persona)}</b> is online! DM its bot to say hi.`,
877
+ '',
878
+ '<i>Skills can be added later via yaml or future /skills command.</i>',
879
+ ].join('\n'), { html: true })
880
+ } else if (result.outcome.kind === 'success') {
881
+ const doneState = advanceSetupState(setupState, { step: 'done' })
882
+ setSetupState(doneState)
883
+ clearSetupState(chatId)
884
+ await switchroomReply(ctx, [
885
+ `Auth succeeded but agent start failed.`,
886
+ '',
887
+ `Try: <code>switchroom agent start ${escapeHtmlForTg(slug)}</code>`,
888
+ ].join('\n'), { html: true })
889
+ } else {
890
+ // Bad code — stay in asked-oauth-code so the user can paste again.
891
+ await switchroomReply(
892
+ ctx,
893
+ `Code rejected (${result.outcome.kind}). Paste the code again, or type <code>cancel</code> to abort:`,
894
+ { html: true },
895
+ )
896
+ }
897
+ } catch (err) {
898
+ await switchroomReply(ctx, [
899
+ `<b>completeCreation failed:</b> ${escapeHtmlForTg((err as Error).message)}`,
900
+ '',
901
+ 'Type <code>cancel</code> to abort, or paste the code again to retry:',
902
+ ].join('\n'), { html: true })
903
+ }
904
+ return
905
+ }
906
+
907
+ // ── Error (validation failure, stayInStep re-prompt) ──────────────────
908
+ case 'error': {
909
+ if (!action.stayInStep) {
910
+ clearSetupState(chatId)
911
+ }
912
+ await switchroomReply(ctx, action.message, { html: true })
913
+ return
914
+ }
915
+
916
+ // ── Cancel ─────────────────────────────────────────────────────────────
917
+ case 'cancel': {
918
+ clearSetupState(chatId)
919
+ if (action.reason === 'user-cancelled') {
920
+ await switchroomReply(ctx, 'Setup wizard cancelled. Type /setup to start over.', { html: false })
921
+ } else {
922
+ await switchroomReply(ctx, `Setup wizard stopped (${action.reason}). Type /setup to start over.`, { html: false })
923
+ }
924
+ return
925
+ }
926
+
927
+ // ── Done (shouldn't reach here via text) ──────────────────────────────
928
+ case 'done': {
929
+ clearSetupState(chatId)
930
+ await switchroomReply(ctx, 'Setup already complete. Type /setup to create another agent.', { html: false })
931
+ return
932
+ }
933
+ }
934
+ }
935
+
936
+ async function handleCreateFlowText(
937
+ ctx: Context,
938
+ chatId: string,
939
+ text: string,
940
+ flowState: NonNullable<ReturnType<typeof getState>>,
941
+ ): Promise<void> {
942
+ const profiles = listAvailableProfiles()
943
+ const action = handleFlowText({ state: flowState, text, profiles })
944
+
945
+ switch (action.kind) {
946
+ case 'ask-name':
947
+ await switchroomReply(ctx, 'What should the new agent be named?', { html: false })
948
+ return
949
+
950
+ case 'ask-profile': {
951
+ // Update name in state if we just got it
952
+ const updatedName = text.trim()
953
+ const newState = advanceState(flowState, { step: 'asked-profile', name: updatedName })
954
+ setState(newState)
955
+ const kb = new InlineKeyboard()
956
+ for (const p of profiles) {
957
+ kb.text(p, `cf:profile:${p}`).row()
958
+ }
959
+ await ctx.reply(
960
+ `Choose a profile for <b>${escapeHtmlForTg(updatedName)}</b>:`,
961
+ { parse_mode: 'HTML', reply_markup: kb },
962
+ )
963
+ return
964
+ }
965
+
966
+ case 'ask-bot-token': {
967
+ const newState = advanceState(flowState, { step: 'asked-bot-token', profile: action.profile })
968
+ setState(newState)
969
+ await switchroomReply(ctx, `Profile <b>${escapeHtmlForTg(action.profile)}</b> selected.\n\nPaste the BotFather token for <b>${escapeHtmlForTg(action.name)}</b>'s Telegram bot:\n<i>(Note: this token will be visible in this chat)</i>`, { html: true })
970
+ return
971
+ }
972
+
973
+ case 'call-create-agent': {
974
+ const { name, profile, botToken } = action
975
+ // Pre-#28 fix this called validateBotToken here AND createAgent
976
+ // (via validateBotTokenMatchesAgent at create-orchestrator.ts:150)
977
+ // would call it again — two sequential Telegram getMe() requests in
978
+ // the happy path. We now trust the orchestrator's check and surface
979
+ // its error if it fails. The /setup flow at line 723 keeps its own
980
+ // pre-check because it uses the returned botInfo.username for UX.
981
+ await switchroomReply(ctx, `Creating agent <b>${escapeHtmlForTg(name)}</b>…`, { html: true })
982
+ try {
983
+ const result = await createAgent({
984
+ name,
985
+ profile,
986
+ telegramBotToken: botToken,
987
+ // Clean up scaffold/systemd/yaml on mid-flow failure so the user
988
+ // can retry /create-agent with the same name without conflicts.
989
+ rollbackOnFail: true,
990
+ })
991
+ const newState = advanceState(flowState, {
992
+ step: 'asked-oauth-code',
993
+ name,
994
+ profile,
995
+ botToken,
996
+ authSessionName: result.sessionName,
997
+ loginUrl: result.loginUrl ?? null,
998
+ })
999
+ setState(newState)
1000
+
1001
+ if (result.loginUrl) {
1002
+ const kb = new InlineKeyboard().url('Open OAuth URL', result.loginUrl)
1003
+ await ctx.reply(
1004
+ 'Open this URL to log in, then paste the code back here:',
1005
+ { reply_markup: kb },
1006
+ )
1007
+ } else {
1008
+ await switchroomReply(ctx, 'Auth session started. Paste the OAuth code back here:', { html: false })
1009
+ }
1010
+ } catch (err) {
1011
+ await switchroomReply(ctx, `<b>createAgent failed:</b> ${escapeHtmlForTg((err as Error).message)}`, { html: true })
1012
+ clearState(chatId)
1013
+ }
1014
+ return
1015
+ }
1016
+
1017
+ case 'call-complete-creation': {
1018
+ const { name, code } = action
1019
+ await switchroomReply(ctx, 'Submitting OAuth code…', { html: false })
1020
+ try {
1021
+ const result = await completeCreation(name, code)
1022
+ if (result.outcome.kind === 'success' && result.started) {
1023
+ clearState(chatId)
1024
+ await switchroomReply(ctx, `<b>${escapeHtmlForTg(name)}</b> is online! DM its bot to say hi.`, { html: true })
1025
+ } else if (result.outcome.kind === 'success') {
1026
+ clearState(chatId)
1027
+ await switchroomReply(ctx, `Auth succeeded but agent start failed. Try: <code>switchroom agent start ${escapeHtmlForTg(name)}</code>`, { html: true })
1028
+ } else {
1029
+ // Bad code — stay in asked-oauth-code step
1030
+ await switchroomReply(ctx, `Code rejected (${result.outcome.kind}). Paste the code again, or use /create-agent to restart:`, { html: false })
1031
+ }
1032
+ } catch (err) {
1033
+ await switchroomReply(ctx, `<b>completeCreation failed:</b> ${escapeHtmlForTg((err as Error).message)}`, { html: true })
1034
+ clearState(chatId)
1035
+ }
1036
+ return
1037
+ }
1038
+
1039
+ case 'error': {
1040
+ await switchroomReply(ctx, action.message, { html: true })
1041
+ if (!action.stayInStep) {
1042
+ clearState(chatId)
1043
+ }
1044
+ return
1045
+ }
1046
+
1047
+ case 'cancel':
1048
+ case 'done':
1049
+ // No active flow — fall through to unknown command
1050
+ await switchroomReply(ctx, 'Unknown command. Try /help.', { html: true })
1051
+ return
1052
+ }
1053
+ }
1054
+
1055
+ // ─── Startup ──────────────────────────────────────────────────────────────
1056
+ process.on('unhandledRejection', err => {
1057
+ process.stderr.write(`foreman: unhandled rejection: ${err}\n`)
1058
+ })
1059
+ process.on('uncaughtException', err => {
1060
+ process.stderr.write(`foreman: uncaught exception: ${err}\n`)
1061
+ })
1062
+
1063
+ void runPollingLoop(bot, {
1064
+ onReady: (username) => {
1065
+ process.stderr.write(`foreman: ready as @${username}\n`)
1066
+ },
1067
+ onOneTimeSetup: async (username) => {
1068
+ process.stderr.write(`foreman: one-time setup done @${username}\n`)
1069
+ // Register bot commands so they show in the Telegram UI
1070
+ try {
1071
+ await bot.api.setMyCommands([
1072
+ { command: 'start', description: 'Start / intro' },
1073
+ { command: 'help', description: 'Command list' },
1074
+ { command: 'status', description: 'Fleet status' },
1075
+ { command: 'list', description: 'Fleet status (alias)' },
1076
+ { command: 'logs', description: 'Agent logs: /logs <agent> [--tail N]' },
1077
+ { command: 'auth', description: 'Auth dashboard: /auth [agent]' },
1078
+ { command: 'restart', description: 'Restart agent: /restart <agent>' },
1079
+ { command: 'delete', description: 'Delete agent (with confirm): /delete <agent>' },
1080
+ { command: 'update', description: 'Update switchroom' },
1081
+ { command: 'version', description: 'Show versions + running agent health' },
1082
+ { command: 'worktrees', description: 'List active git worktrees claimed by sub-agents' },
1083
+ { command: 'create_agent', description: 'Create new agent: /create-agent [name]' },
1084
+ { command: 'setup', description: 'New agent wizard: /setup [slug]' },
1085
+ { command: 'cancel', description: 'Cancel active wizard' },
1086
+ ])
1087
+ } catch (err) {
1088
+ process.stderr.write(`foreman: setMyCommands failed: ${err}\n`)
1089
+ }
1090
+
1091
+ // Resume any in-progress setup wizard flows that survived a restart
1092
+ try {
1093
+ const activeSetupFlows = listActiveSetupFlows(60 * 60 * 1000) // 1 hour
1094
+ for (const flow of activeSetupFlows) {
1095
+ try {
1096
+ await bot.api.sendMessage(
1097
+ flow.chatId,
1098
+ `Picking up /setup wizard for <b>${escapeHtmlForTg(flow.slug ?? '?')}</b> (${setupStepLabel(flow.step)})…\n\nType your response to continue, or /cancel to abort.`,
1099
+ { parse_mode: 'HTML' },
1100
+ )
1101
+ } catch (err) {
1102
+ process.stderr.write(`foreman: failed to resume setup flow for chat ${flow.chatId}: ${err}\n`)
1103
+ }
1104
+ }
1105
+ } catch (err) {
1106
+ process.stderr.write(`foreman: failed to list active setup flows: ${err}\n`)
1107
+ }
1108
+
1109
+ // Resume any in-progress create-agent flows that survived a restart
1110
+ try {
1111
+ const activeFlows = listActiveFlows(60 * 60 * 1000) // 1 hour
1112
+ for (const flow of activeFlows) {
1113
+ try {
1114
+ await bot.api.sendMessage(
1115
+ flow.chatId,
1116
+ `Picking up create-agent flow for <b>${escapeHtmlForTg(flow.name ?? '?')}</b> (${stepLabel(flow.step)})…\n\nType your response to continue, or /create-agent to restart.`,
1117
+ { parse_mode: 'HTML' },
1118
+ )
1119
+ } catch (err) {
1120
+ process.stderr.write(`foreman: failed to resume flow for chat ${flow.chatId}: ${err}\n`)
1121
+ }
1122
+ }
1123
+ } catch (err) {
1124
+ process.stderr.write(`foreman: failed to list active flows: ${err}\n`)
1125
+ }
1126
+ },
1127
+ on409: (attempt, delayMs) => {
1128
+ process.stderr.write(`foreman: 409 Conflict attempt=${attempt} retry_in_ms=${delayMs}\n`)
1129
+ },
1130
+ })