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,730 @@
1
+ /**
2
+ * Real-time PTY tail with character-level model output extraction.
3
+ *
4
+ * The deep-research finding: Claude Code's --channels mode does NOT
5
+ * support --output-format stream-json, and the session JSONL writes
6
+ * whole assistant messages atomically (no per-token deltas). The only
7
+ * way to get character-level streaming text out of a long-running
8
+ * Claude Code daemon is to capture its PTY output (which we already
9
+ * do via `script -qfc ... service.log`) and parse the rendered TUI.
10
+ *
11
+ * Critical observation from the live server's service.log: when the
12
+ * model is generating a reply via the switchroom-telegram MCP tool, Claude
13
+ * Code's Ink TUI renders the in-progress tool call as:
14
+ *
15
+ * ● switchroom-telegram - reply (MCP)(chat_id: "...", text: "Yes — I can
16
+ * attach files to replies. Images send
17
+ * as inline photos...")
18
+ *
19
+ * The text parameter expands character-by-character as the model
20
+ * streams. By tailing service.log, feeding the bytes into a headless
21
+ * xterm.js, and scanning the resulting buffer for `● switchroom-telegram -
22
+ * reply` blocks, we can extract the streaming reply text in real time.
23
+ *
24
+ * Architecture:
25
+ *
26
+ * ┌─────────────────┐ bytes ┌─────────────────┐
27
+ * │ service.log ├────────────▶│ @xterm/headless │
28
+ * │ (script -qfc) │ │ Terminal │
29
+ * └─────────────────┘ └────────┬────────┘
30
+ * │ rendered buffer
31
+ * ▼
32
+ * ┌─────────────────┐
33
+ * │ MessageRegion │
34
+ * │ Extractor │
35
+ * └────────┬────────┘
36
+ * │ partial text
37
+ * ▼
38
+ * ┌─────────────────┐
39
+ * │ throttled │
40
+ * │ onPartial(text) │
41
+ * └─────────────────┘
42
+ *
43
+ * The extractor is isolated behind a versioned interface so that when
44
+ * Claude Code's TUI layout changes (Ink upgrades, marker tweaks, etc.),
45
+ * we can swap implementations without touching the tail loop. The
46
+ * extractor returns null when it can't confidently identify the message
47
+ * region; the consumer should treat null as "no streaming this turn,
48
+ * fall back to JSONL-only progress signals".
49
+ */
50
+
51
+ import { existsSync, readFileSync, statSync, watch, openSync, readSync, closeSync, type FSWatcher } from 'fs'
52
+ import { Terminal } from '@xterm/headless'
53
+
54
+ /**
55
+ * How many trailing bytes of the log to replay into xterm.js at attach
56
+ * time. Must be large enough to contain at least one full-screen Ink
57
+ * redraw — Ink's renderer is differential and emits cursor-forward
58
+ * escapes for unchanged cells, so without a baseline the terminal
59
+ * ends up with blank cells where the "● switchroom-telegram - stream_reply"
60
+ * marker characters should be, and the v1 extractor's substring match
61
+ * silently misses every partial. 1 MB is empirically ~15–30 s of
62
+ * steady output, comfortably covering a full-frame redraw.
63
+ */
64
+ const PRELOAD_BYTES = 1_000_000
65
+
66
+ // ─── MessageRegionExtractor interface ─────────────────────────────────────
67
+ //
68
+ // Versioned. When Claude Code's TUI changes break the v1 extractor, ship
69
+ // a v2 alongside and switch the default. The interface is intentionally
70
+ // minimal so each implementation can use its own internal heuristics.
71
+
72
+ export interface MessageRegionExtractor {
73
+ /** Identifier for logging / version pinning. */
74
+ readonly version: string
75
+ /**
76
+ * Inspect the terminal buffer and return the current in-flight reply
77
+ * text the model is generating, or null if no reply is currently
78
+ * being composed.
79
+ *
80
+ * Implementations should be cheap (called after every byte batch) and
81
+ * deterministic (same buffer state → same return value).
82
+ */
83
+ extract(terminal: Terminal): string | null
84
+ }
85
+
86
+ // ─── Debug knob ──────────────────────────────────────────────────────────
87
+ //
88
+ // Set SWITCHROOM_PTY_DEBUG=1 to dump the bottom of the terminal buffer to
89
+ // stderr whenever V1Extractor returns null. Used to diagnose extractor
90
+ // misses when Claude Code changes its TUI rendering (e.g. when channels
91
+ // mode shows MCP tool calls in a different shape than legacy mode).
92
+ //
93
+ // Off by default — when unset there is zero overhead beyond an env-var
94
+ // check. When on, dumps are throttled to once per 2s per process so we
95
+ // don't flood stderr while the extractor scans every ~150ms.
96
+ const PTY_DEBUG = process.env.SWITCHROOM_PTY_DEBUG === '1'
97
+ let lastDebugDumpAt = 0
98
+ const DEBUG_DUMP_THROTTLE_MS = 2000
99
+ const DEBUG_DUMP_LINES = 25
100
+
101
+ function debugDumpBufferOnMiss(buf: { length: number; getLine: (i: number) => { translateToString: (trim: boolean) => string } | undefined }): void {
102
+ if (!PTY_DEBUG) return
103
+ const now = Date.now()
104
+ if (now - lastDebugDumpAt < DEBUG_DUMP_THROTTLE_MS) return
105
+ lastDebugDumpAt = now
106
+ const start = Math.max(0, buf.length - DEBUG_DUMP_LINES)
107
+ const lines: string[] = []
108
+ for (let i = start; i < buf.length; i++) {
109
+ const t = buf.getLine(i)?.translateToString(true) ?? ''
110
+ if (t.trim() === '') continue
111
+ lines.push(`L${i}: ${t}`)
112
+ }
113
+ process.stderr.write(`pty-tail: V1Extractor miss — bottom ${lines.length} non-empty lines:\n${lines.join('\n')}\n---end pty miss dump---\n`)
114
+ }
115
+
116
+ /**
117
+ * v1 extractor for Claude Code 2.1.x.
118
+ *
119
+ * Heuristic: scan the buffer from the bottom for the most recent line
120
+ * that contains `● switchroom-telegram - reply (MCP)` or
121
+ * `● switchroom-telegram - stream_reply (MCP)`. Once found, locate the
122
+ * `text: "` literal and extract the value using an escape-aware
123
+ * character walk that terminates at the first UNESCAPED closing `"`.
124
+ *
125
+ * This matters because the model frequently passes `text` as a
126
+ * non-final parameter (e.g. `chat_id: "123", text: "hello", reply_to: "456"`).
127
+ * Earlier versions of this extractor looked for the `")` close-paren
128
+ * sequence, which terminated at the END of the whole tool call rather
129
+ * than the end of the text string — causing everything after the real
130
+ * `text` value (e.g. `, reply_to: "456"`) to leak into the "extracted"
131
+ * preview and ultimately surface as a garbled duplicate Telegram message.
132
+ *
133
+ * The walk also handles Claude Code's JSON escapes (`\"`, `\n`, `\t`,
134
+ * `\\`) so a text value that contains literal quotes renders correctly
135
+ * in the preview instead of truncating at the first inner quote.
136
+ *
137
+ * Falls back to scanning for any `● switchroom-telegram - reply` substring
138
+ * even if it's not the very first character of the line — Ink sometimes
139
+ * indents tool calls under thinking blocks.
140
+ */
141
+ export class V1Extractor implements MessageRegionExtractor {
142
+ readonly version = 'v1-claude-code-2.1.x'
143
+
144
+ extract(terminal: Terminal): string | null {
145
+ const buf = terminal.buffer.active
146
+ // Walk from the bottom looking for the start of a reply block. We
147
+ // care about the MOST RECENT one because earlier turns are stale.
148
+ let startLine = -1
149
+ for (let i = buf.length - 1; i >= 0; i--) {
150
+ const text = buf.getLine(i)?.translateToString(true) ?? ''
151
+ if (
152
+ text.includes('switchroom-telegram - reply') ||
153
+ text.includes('switchroom-telegram - stream_reply')
154
+ ) {
155
+ startLine = i
156
+ break
157
+ }
158
+ }
159
+ if (startLine < 0) {
160
+ debugDumpBufferOnMiss(buf)
161
+ return null
162
+ }
163
+
164
+ // Concatenate the start line + continuation lines into one logical
165
+ // string. Continuation lines from Ink for tool params are indented
166
+ // by ~30 spaces; the exact count varies with terminal width. Use a
167
+ // heuristic: a line is a continuation iff its first non-space char
168
+ // appears later than column 5 AND it doesn't start with the bullet
169
+ // `●` (which would mean a new entry).
170
+ const lines: string[] = []
171
+ for (let i = startLine; i < buf.length; i++) {
172
+ const text = buf.getLine(i)?.translateToString(true) ?? ''
173
+ if (i === startLine) {
174
+ lines.push(text)
175
+ continue
176
+ }
177
+ // Continuation: heavy indentation, no leading bullet
178
+ const trimmed = text.replace(/^\s+/, '')
179
+ if (trimmed === '') {
180
+ // Empty line — probably end of the tool block
181
+ break
182
+ }
183
+ // A bullet (●) or tool-result marker (⎿) anywhere in the leading
184
+ // whitespace means a new section. Note Ink often indents bullets
185
+ // by ~2 cols, so checking startsWith('●') would miss them — use
186
+ // a regex that allows leading whitespace.
187
+ if (/^\s*●/.test(text) || /^\s*⎿/.test(text)) {
188
+ break
189
+ }
190
+ const leadingSpaces = text.length - trimmed.length
191
+ if (leadingSpaces < 4) {
192
+ // Not a continuation
193
+ break
194
+ }
195
+ lines.push(text)
196
+ }
197
+
198
+ // Now find `text: "` in the concatenated content
199
+ const joined = lines.join('\n')
200
+ const textIdx = joined.indexOf('text: "')
201
+ if (textIdx < 0) return null
202
+ const afterOpen = textIdx + 'text: "'.length
203
+
204
+ // Escape-aware string walk. Starting right after the opening quote
205
+ // of the text parameter, consume characters one at a time. A `\`
206
+ // byte escapes the next char (`\"` → `"`, `\n` → newline, `\\` →
207
+ // backslash, and any unknown sequence is preserved verbatim). An
208
+ // UNESCAPED `"` terminates the string — this is the real end of
209
+ // the text parameter value, regardless of whether it's followed by
210
+ // `)` (text was last) or `,` (text was followed by another param).
211
+ // If we exhaust the buffer without finding the terminator, the
212
+ // model is mid-stream and we return everything captured so far.
213
+ let extracted = ''
214
+ let pos = afterOpen
215
+ while (pos < joined.length) {
216
+ const ch = joined[pos]
217
+ if (ch === '\\' && pos + 1 < joined.length) {
218
+ const next = joined[pos + 1]
219
+ if (next === '"') extracted += '"'
220
+ else if (next === 'n') extracted += '\n'
221
+ else if (next === 't') extracted += '\t'
222
+ else if (next === 'r') extracted += '\r'
223
+ else if (next === '\\') extracted += '\\'
224
+ else extracted += '\\' + next
225
+ pos += 2
226
+ continue
227
+ }
228
+ if (ch === '"') {
229
+ // Unescaped closing quote — end of the text parameter value.
230
+ break
231
+ }
232
+ extracted += ch
233
+ pos++
234
+ }
235
+
236
+ // Strip Ink's continuation-line indentation. Each non-first line
237
+ // has ~30 spaces of leading whitespace; collapse to a single space
238
+ // (Ink visually flows the text, so newlines are not semantic).
239
+ const cleaned = extracted
240
+ .split('\n')
241
+ .map((l, idx) => (idx === 0 ? l : l.replace(/^\s+/, '')))
242
+ .join(' ')
243
+ .replace(/\s+/g, ' ')
244
+ .trim()
245
+
246
+ if (cleaned === '') return null
247
+ return cleaned
248
+ }
249
+ }
250
+
251
+ // ─── ToolActivityExtractor ────────────────────────────────────────────────
252
+ //
253
+ // Design note (2026-04-13): during tool-heavy turns that end with a single
254
+ // `reply` call, the V1Extractor above emits nothing until the very end —
255
+ // the user sees a gap, which reads as "the bot is hung". We fix that by
256
+ // ALSO surfacing tool-call activity ("Bash: git status", "Read: foo.ts",
257
+ // "Grep: pattern") as short one-liners, so the plugin can push a live
258
+ // status via stream_reply on a separate lane.
259
+ //
260
+ // Chosen lane approach (docs were ambiguous — picking what's cleaner given
261
+ // the stream-reply-handler `lane` parameter that landed 2026-04-13): emit
262
+ // activity lines OUT-OF-BAND via a second callback (`onActivity`), separate
263
+ // from the reply-text `onPartial`. The consumer (server.ts) routes these
264
+ // to a dedicated `"activity"` lane via stream_reply(lane: "activity"). This
265
+ // keeps the existing reply/stream_reply path untouched (all current tests
266
+ // still pass unmodified), and avoids mixing status noise into the answer
267
+ // text buffer.
268
+
269
+ export interface ToolActivityExtractor {
270
+ readonly version: string
271
+ /**
272
+ * Return a SHORT (<100 char) human-readable status string for the most
273
+ * recent tool-call bullet in the buffer, or null if none / the same as
274
+ * the last extraction (dedup is the consumer's job — extractor just
275
+ * surfaces the current top-of-stack activity).
276
+ */
277
+ extract(terminal: Terminal): string | null
278
+ }
279
+
280
+ /**
281
+ * v1 activity extractor. Scans the buffer bottom-up for Claude Code's Ink
282
+ * tool-call bullet pattern:
283
+ *
284
+ * ● Bash(git status)
285
+ * ● Read(/path/to/file.ts)
286
+ * ● Grep(pattern, path: "...")
287
+ * ● Glob(**\/*.ts)
288
+ * ● switchroom-telegram - reply (MCP)(...)
289
+ *
290
+ * For the core tools we render a short verbed one-liner ("Running Bash:
291
+ * git status", "Reading file.ts", "Searching with Grep: pattern"). For
292
+ * switchroom-telegram tool calls we return null — those are already surfaced
293
+ * by V1Extractor on the main lane, and echoing them on the activity lane
294
+ * would be confusing.
295
+ */
296
+ export class V1ToolActivityExtractor implements ToolActivityExtractor {
297
+ readonly version = 'v1-tool-activity'
298
+
299
+ extract(terminal: Terminal): string | null {
300
+ const buf = terminal.buffer.active
301
+ for (let i = buf.length - 1; i >= 0; i--) {
302
+ const raw = buf.getLine(i)?.translateToString(true) ?? ''
303
+ // Find a bullet anywhere on the line (Ink may indent).
304
+ const bulletIdx = raw.indexOf('●')
305
+ if (bulletIdx < 0) continue
306
+ const after = raw.slice(bulletIdx + 1).trimStart()
307
+ if (after === '') continue
308
+
309
+ // Skip switchroom-telegram tool calls — V1Extractor owns those.
310
+ if (after.startsWith('switchroom-telegram')) return null
311
+
312
+ // Match `ToolName(` or `ToolName -` patterns. Accept the conventional
313
+ // Claude Code tool names; anything else is "Running <Tool>".
314
+ const m = after.match(/^([A-Za-z_][A-Za-z0-9_-]*)[\s(]/)
315
+ if (!m) continue
316
+ const tool = m[1]
317
+
318
+ // Grab the inside of the first (...) group if present, keeping only
319
+ // up to the first comma for a short preview.
320
+ const parenOpen = after.indexOf('(')
321
+ let inner = ''
322
+ if (parenOpen >= 0) {
323
+ // Simple depth-aware walk: stop at the matching close paren. Good
324
+ // enough for a short status preview.
325
+ let depth = 0
326
+ let end = -1
327
+ for (let j = parenOpen; j < after.length; j++) {
328
+ const ch = after[j]
329
+ if (ch === '(') depth++
330
+ else if (ch === ')') {
331
+ depth--
332
+ if (depth === 0) { end = j; break }
333
+ }
334
+ }
335
+ inner = end > parenOpen ? after.slice(parenOpen + 1, end) : after.slice(parenOpen + 1)
336
+ // Trim to first meaningful arg (up to first ", " at depth 0).
337
+ const commaIdx = inner.indexOf(', ')
338
+ if (commaIdx > 0) inner = inner.slice(0, commaIdx)
339
+ inner = inner.trim()
340
+ // Strip surrounding quotes from a single-arg string.
341
+ if (inner.startsWith('"') && inner.endsWith('"') && inner.length >= 2) {
342
+ inner = inner.slice(1, -1)
343
+ }
344
+ }
345
+
346
+ // Truncate overlong inner strings.
347
+ const MAX = 80
348
+ if (inner.length > MAX) inner = inner.slice(0, MAX - 1) + '…'
349
+
350
+ // Verbed phrasing per tool.
351
+ const verb = activityVerb(tool)
352
+ const phrase = inner.length > 0 ? `${verb}: ${inner}` : verb
353
+ // Final guardrail: one line, no control chars.
354
+ return phrase.replace(/\s+/g, ' ').trim().slice(0, 120)
355
+ }
356
+ return null
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Activity-line prefixes produced by the noisy core tools (Bash, Read,
362
+ * Write, Edit, Grep, Glob). The PTY tail extracts an activity line per
363
+ * tool call, but surfacing each one to the user is noise — the user
364
+ * wants human-meaningful rollups ("Running sub-agent...",
365
+ * "Fetching URL...") not per-tool narration of "Running Bash: cd ...".
366
+ *
367
+ * Used by `shouldSuppressToolActivity` to filter at the consumer layer.
368
+ * The extractor itself still returns these lines unchanged, so anything
369
+ * that wants the raw stream (tests, telemetry) keeps working.
370
+ */
371
+ export const NOISY_TOOL_ACTIVITY_PREFIXES: readonly string[] = [
372
+ 'Running Bash',
373
+ 'Reading file',
374
+ 'Writing file',
375
+ 'Editing file',
376
+ 'Searching with Grep',
377
+ 'Searching with Glob',
378
+ // Claude Code's in-progress spinner uses bare verbs ("Reading…",
379
+ // "Writing…", "Editing…", "Searching…") which the bullet-line regex
380
+ // captures as the tool token. activityVerb falls through to the
381
+ // default `Running ${tool}`, producing these synthetic prefixes.
382
+ 'Running Reading',
383
+ 'Running Writing',
384
+ 'Running Editing',
385
+ 'Running Searching',
386
+ 'Running Read',
387
+ 'Running Write',
388
+ 'Running Edit',
389
+ 'Running Grep',
390
+ 'Running Glob',
391
+ ]
392
+
393
+ /**
394
+ * Substrings that mark a Claude Code TUI keyboard hint the Telegram
395
+ * user cannot action ("ctrl+o to expand", "esc to interrupt",
396
+ * "shift+tab to cycle", etc.). If any of these appear anywhere in an
397
+ * activity line, the line is suppressed — surfacing the hint is
398
+ * confusing UX (user pokes at their phone, nothing happens) and the
399
+ * line carries no information beyond "Claude is doing something".
400
+ */
401
+ export const TUI_HINT_MARKERS: readonly string[] = [
402
+ 'ctrl+',
403
+ 'ctrl-',
404
+ 'esc to ',
405
+ 'shift+',
406
+ 'shift-',
407
+ 'alt+',
408
+ 'tab to ',
409
+ ]
410
+
411
+ /**
412
+ * True if an activity line is per-tool narration for a noisy core tool
413
+ * (Bash/Read/Write/Edit/Grep/Glob) that should NOT be surfaced to the
414
+ * Telegram activity lane. Human-meaningful rollups ("Running sub-agent",
415
+ * "Fetching URL", "Searching the web", and anything unknown mapped to
416
+ * "Running <CustomTool>") pass through. Also suppresses any line that
417
+ * carries a Claude Code TUI keyboard hint, regardless of tool prefix.
418
+ */
419
+ export function shouldSuppressToolActivity(line: string): boolean {
420
+ const lower = line.toLowerCase()
421
+ for (const marker of TUI_HINT_MARKERS) {
422
+ if (lower.includes(marker)) return true
423
+ }
424
+ for (const prefix of NOISY_TOOL_ACTIVITY_PREFIXES) {
425
+ if (line === prefix) return true
426
+ if (line.startsWith(prefix + ':')) return true
427
+ }
428
+ return false
429
+ }
430
+
431
+ function activityVerb(tool: string): string {
432
+ switch (tool) {
433
+ case 'Bash': return 'Running Bash'
434
+ case 'Read': return 'Reading file'
435
+ case 'Write': return 'Writing file'
436
+ case 'Edit': return 'Editing file'
437
+ case 'Grep': return 'Searching with Grep'
438
+ case 'Glob': return 'Searching with Glob'
439
+ case 'WebFetch': return 'Fetching URL'
440
+ case 'WebSearch': return 'Searching the web'
441
+ case 'Task': return 'Running sub-agent'
442
+ default: return `Running ${tool}`
443
+ }
444
+ }
445
+
446
+ // ─── PTY tail ────────────────────────────────────────────────────────────
447
+
448
+ export interface PtyTailConfig {
449
+ /** Absolute path to the file we tail. Usually `<agentDir>/service.log`. */
450
+ logFile: string
451
+ /** Throttle for partial text emission. Default 750 ms. */
452
+ throttleMs?: number
453
+ /** Terminal cols/rows the script wrapper uses. Default 132x40. */
454
+ cols?: number
455
+ rows?: number
456
+ /** Pluggable extractor. Default V1Extractor. */
457
+ extractor?: MessageRegionExtractor
458
+ /** Optional logger. */
459
+ log?: (msg: string) => void
460
+ /** Called when extracted text changes. Receives the FULL current text. */
461
+ onPartial: (text: string) => void
462
+ /** Called when the model finishes a turn (extracted text reaches a stable terminal). */
463
+ onFinal?: (text: string) => void
464
+ /**
465
+ * Optional second extractor that surfaces tool-call activity ("Running
466
+ * Bash: ls", "Reading file: foo.ts"). When provided, the tail runs both
467
+ * extractors on every byte batch and fires `onActivity` (deduped + same
468
+ * throttle) when the activity line changes. Independent of onPartial —
469
+ * the consumer chooses how to route it (typically a separate lane).
470
+ */
471
+ activityExtractor?: ToolActivityExtractor
472
+ /** Called when the activity extractor's output changes (deduped + throttled). */
473
+ onActivity?: (text: string) => void
474
+ }
475
+
476
+ export interface PtyTailHandle {
477
+ stop(): void
478
+ /** Get the current cumulative extracted text, or null. */
479
+ getCurrentText(): string | null
480
+ }
481
+
482
+ /**
483
+ * Start tailing a PTY-captured log file. Re-feeds the terminal emulator
484
+ * with new bytes as they arrive, runs the extractor on every batch, and
485
+ * fires onPartial whenever the result changes.
486
+ *
487
+ * Robustness: if the log file doesn't exist yet, polls for it. If the
488
+ * file is truncated/replaced (logrotate), resets the cursor. The
489
+ * terminal emulator state persists across rotations — that's
490
+ * intentional for now, since the typical case is no rotation. A
491
+ * smarter version could reset the terminal on truncation but it adds
492
+ * complexity for an edge case.
493
+ */
494
+ export function startPtyTail(config: PtyTailConfig): PtyTailHandle {
495
+ const throttleMs = config.throttleMs ?? 150
496
+ const extractor = config.extractor ?? new V1Extractor()
497
+ const activityExtractor = config.activityExtractor ?? null
498
+ const onActivity = config.onActivity ?? null
499
+ const log = config.log
500
+ const cols = config.cols ?? 132
501
+ const rows = config.rows ?? 40
502
+
503
+ // Bound the in-memory terminal buffer. 1000 lines at 132 cols is ~150 KB
504
+ // per PTY session; 5000 lines let a 60-minute run of a chatty tool grow
505
+ // past 300 MB and made extraction O(buffer). Very old scrollback isn't
506
+ // useful for progress extraction anyway — the extractor only reads from
507
+ // the current cursor region.
508
+ const term = new Terminal({
509
+ cols,
510
+ rows,
511
+ scrollback: 1000,
512
+ allowProposedApi: true,
513
+ })
514
+
515
+ let cursor = 0
516
+ let lastEmittedText: string | null = null
517
+ let lastEmittedActivity: string | null = null
518
+ let lastEmitAt = 0
519
+ let lastActivityEmitAt = 0
520
+ let pendingEmit: ReturnType<typeof setTimeout> | null = null
521
+ let pendingActivity: ReturnType<typeof setTimeout> | null = null
522
+ let stopped = false
523
+ let watcher: FSWatcher | null = null
524
+ let pollTimer: ReturnType<typeof setInterval> | null = null
525
+
526
+ function emitIfChanged(): void {
527
+ if (stopped) return
528
+ const text = extractor.extract(term)
529
+ if (text === lastEmittedText) return
530
+ if (text == null) {
531
+ // Extractor lost the region — could mean turn ended. If we had a
532
+ // last emitted text, keep it; the JSONL backstop will finalize.
533
+ return
534
+ }
535
+ lastEmittedText = text
536
+ lastEmitAt = Date.now()
537
+ try {
538
+ config.onPartial(text)
539
+ } catch (err) {
540
+ log?.(`pty-tail: onPartial threw: ${(err as Error).message}`)
541
+ }
542
+ }
543
+
544
+ function emitActivityIfChanged(): void {
545
+ if (stopped) return
546
+ if (activityExtractor == null || onActivity == null) return
547
+ const text = activityExtractor.extract(term)
548
+ if (text == null) return
549
+ // Dedup: identical to last emission → skip.
550
+ if (text === lastEmittedActivity) return
551
+ lastEmittedActivity = text
552
+ lastActivityEmitAt = Date.now()
553
+ try {
554
+ onActivity(text)
555
+ } catch (err) {
556
+ log?.(`pty-tail: onActivity threw: ${(err as Error).message}`)
557
+ }
558
+ }
559
+
560
+ function scheduleActivityEmit(): void {
561
+ if (activityExtractor == null || onActivity == null) return
562
+ if (pendingActivity != null) return
563
+ const sinceLast = Date.now() - lastActivityEmitAt
564
+ if (sinceLast >= throttleMs) {
565
+ emitActivityIfChanged()
566
+ return
567
+ }
568
+ pendingActivity = setTimeout(() => {
569
+ pendingActivity = null
570
+ emitActivityIfChanged()
571
+ }, Math.max(0, throttleMs - sinceLast))
572
+ }
573
+
574
+ function scheduleEmit(): void {
575
+ // Always try to surface tool activity alongside reply text. Independent
576
+ // throttle/dedup — activity changes on every new tool-call bullet, while
577
+ // reply text grows character-by-character.
578
+ scheduleActivityEmit()
579
+ if (pendingEmit != null) return
580
+ // Fire immediately if the throttle window is open (this is critical
581
+ // for first-paint latency — without this, the very first emit waits
582
+ // a full throttleMs even though there's nothing to throttle against).
583
+ const sinceLastEmit = Date.now() - lastEmitAt
584
+ if (sinceLastEmit >= throttleMs) {
585
+ emitIfChanged()
586
+ return
587
+ }
588
+ pendingEmit = setTimeout(() => {
589
+ pendingEmit = null
590
+ emitIfChanged()
591
+ }, Math.max(0, throttleMs - sinceLastEmit))
592
+ }
593
+
594
+ function readNew(): void {
595
+ if (stopped) return
596
+ if (!existsSync(config.logFile)) return
597
+ let stat
598
+ try {
599
+ stat = statSync(config.logFile)
600
+ } catch {
601
+ return
602
+ }
603
+ if (stat.size < cursor) {
604
+ // Truncated/rotated — reset cursor
605
+ cursor = 0
606
+ log?.(`pty-tail: log file shrank from ${cursor} to ${stat.size} bytes — resetting cursor`)
607
+ }
608
+ if (stat.size === cursor) return
609
+
610
+ const toRead = stat.size - cursor
611
+ const buf = Buffer.alloc(toRead)
612
+ let fd: number
613
+ try {
614
+ fd = openSync(config.logFile, 'r')
615
+ } catch {
616
+ return
617
+ }
618
+ try {
619
+ readSync(fd, buf, 0, toRead, cursor)
620
+ } finally {
621
+ closeSync(fd)
622
+ }
623
+ cursor = stat.size
624
+
625
+ // Feed into xterm. The Terminal.write() call queues bytes through
626
+ // the parser; subsequent buffer.active reads see the new state.
627
+ // Schedule the extraction on the throttle to coalesce rapid bursts.
628
+ term.write(buf, () => {
629
+ scheduleEmit()
630
+ })
631
+ }
632
+
633
+ function attachWatcher(): void {
634
+ if (!existsSync(config.logFile)) return
635
+ if (watcher) return
636
+ let size = 0
637
+ try {
638
+ size = statSync(config.logFile).size
639
+ } catch {
640
+ // File vanished between existsSync and statSync — bail out, pollTimer
641
+ // will retry on the next tick.
642
+ return
643
+ }
644
+
645
+ // Preload the tail end of the log into the terminal emulator BEFORE we
646
+ // start tailing fresh bytes. Critical for Ink: its renderer uses
647
+ // *differential* updates (cursor-forward escapes for unchanged cells),
648
+ // so a fresh terminal starting at EOF sees `\e[1C` skipping over cells
649
+ // that Ink assumes already contain characters from a prior full frame.
650
+ // Without a baseline, marker strings like "switchroom-telegram" render
651
+ // with gaps (e.g. "switchroom te egram") and the extractor's substring
652
+ // check fails → no partials ever emit.
653
+ //
654
+ // 1 MB is deliberately generous: even a few seconds of steady Ink
655
+ // output is enough to capture a full-frame redraw that initializes
656
+ // every cell. We suppress the first onPartial by seeding
657
+ // `lastEmittedText` from the post-preload extract result, so the
658
+ // very next real extract (from a new tool call) is what actually
659
+ // fires onPartial.
660
+ const preloadBytes = Math.min(size, PRELOAD_BYTES)
661
+ const preloadFrom = size - preloadBytes
662
+ if (preloadBytes > 0) {
663
+ try {
664
+ const fd = openSync(config.logFile, 'r')
665
+ try {
666
+ const buf = Buffer.alloc(preloadBytes)
667
+ readSync(fd, buf, 0, preloadBytes, preloadFrom)
668
+ term.write(buf, () => {
669
+ // Seed lastEmittedText with whatever the extractor sees after
670
+ // the baseline is loaded. Any NEW render (newer than this
671
+ // baseline) will produce a different extract result and fire
672
+ // onPartial naturally.
673
+ const seeded = extractor.extract(term)
674
+ if (seeded != null) {
675
+ lastEmittedText = seeded
676
+ log?.(`pty-tail: preload seeded lastEmittedText (${seeded.length} chars)`)
677
+ }
678
+ })
679
+ } finally {
680
+ closeSync(fd)
681
+ }
682
+ } catch (err) {
683
+ log?.(`pty-tail: preload failed: ${(err as Error).message}`)
684
+ }
685
+ }
686
+
687
+ cursor = size
688
+ log?.(`pty-tail: attached to ${config.logFile} (cursor=${cursor}, preloaded=${preloadBytes})`)
689
+ try {
690
+ watcher = watch(config.logFile, () => readNew())
691
+ } catch (err) {
692
+ log?.(`pty-tail: fs.watch failed (${(err as Error).message}), polling only`)
693
+ }
694
+ }
695
+
696
+ // Initial scan + retry loop in case the log file doesn't exist yet.
697
+ // Poll interval is 200ms — short enough that fs.watch misses don't
698
+ // add visible latency, infrequent enough that idle CPU stays minimal.
699
+ attachWatcher()
700
+ pollTimer = setInterval(() => {
701
+ if (!watcher) attachWatcher()
702
+ readNew()
703
+ }, 200)
704
+
705
+ return {
706
+ stop(): void {
707
+ stopped = true
708
+ if (watcher) {
709
+ try { watcher.close() } catch { /* ignore */ }
710
+ watcher = null
711
+ }
712
+ if (pollTimer) {
713
+ clearInterval(pollTimer)
714
+ pollTimer = null
715
+ }
716
+ if (pendingEmit) {
717
+ clearTimeout(pendingEmit)
718
+ pendingEmit = null
719
+ }
720
+ if (pendingActivity) {
721
+ clearTimeout(pendingActivity)
722
+ pendingActivity = null
723
+ }
724
+ try { term.dispose() } catch { /* ignore */ }
725
+ },
726
+ getCurrentText(): string | null {
727
+ return lastEmittedText
728
+ },
729
+ }
730
+ }