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,877 @@
1
+ /**
2
+ * Unit tests for the subagent-watcher module.
3
+ *
4
+ * Covers:
5
+ * - Registry transitions (register, tool_use, turn_end)
6
+ * - JSONL tail parsing (description from sub_agent_text, toolCount from sub_agent_tool_use)
7
+ * - Stall detection (stall notification after stallThresholdMs idle)
8
+ * - Completion notification (sent once on state=done)
9
+ * - Historical-vs-active filter (pre-existing files do not fire stalls/completions)
10
+ */
11
+
12
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
13
+ import * as fs from 'fs'
14
+ import { mkdtempSync, mkdirSync, writeFileSync, appendFileSync, rmSync } from 'fs'
15
+ import { tmpdir } from 'os'
16
+ import { join } from 'path'
17
+ import { startSubagentWatcher, type WorkerEntry } from '../subagent-watcher.js'
18
+
19
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
20
+
21
+ function makeEntry(overrides: Partial<WorkerEntry> = {}): WorkerEntry {
22
+ return {
23
+ agentId: 'test-agent-01',
24
+ filePath: '/tmp/agent-test-agent-01.jsonl',
25
+ description: 'Build the feature',
26
+ state: 'running',
27
+ dispatchedAt: 1000,
28
+ lastActivityAt: 1000,
29
+ toolCount: 0,
30
+ stallNotified: false,
31
+ completionNotified: false,
32
+ lastSummaryLine: '',
33
+ lastTool: null,
34
+ historical: false,
35
+ ...overrides,
36
+ }
37
+ }
38
+
39
+ // ─── startSubagentWatcher harness ────────────────────────────────────────────
40
+
41
+ /**
42
+ * Minimal harness to drive the watcher without real filesystem or timers.
43
+ *
44
+ * We mock:
45
+ * - fs.existsSync, fs.readdirSync → control which dirs/files are "on disk"
46
+ * - fs.statSync → control file sizes (drives JSONL read)
47
+ * - fs.openSync, fs.readSync, fs.closeSync → feed JSONL content
48
+ * - fs.watch → stub (returns a fake watcher)
49
+ * - Date.now → injected via config.now
50
+ * - setInterval / clearInterval → injected via config
51
+ */
52
+
53
+ function buildJSONL(...lines: object[]): string {
54
+ return lines.map((l) => JSON.stringify(l)).join('\n') + '\n'
55
+ }
56
+
57
+ function subAgentUserMsg(promptText: string) {
58
+ return { type: 'user', message: { content: [{ type: 'text', text: promptText }] } }
59
+ }
60
+
61
+ function subAgentAssistantText(text: string) {
62
+ return {
63
+ type: 'assistant',
64
+ message: { content: [{ type: 'text', text }] },
65
+ }
66
+ }
67
+
68
+ function subAgentToolUse(name: string, id: string) {
69
+ return {
70
+ type: 'assistant',
71
+ message: { content: [{ type: 'tool_use', name, id, input: {} }] },
72
+ }
73
+ }
74
+
75
+ function subAgentTurnDuration() {
76
+ return { type: 'system', subtype: 'turn_duration', durationMs: 5000 }
77
+ }
78
+
79
+ interface WatcherHarness {
80
+ notifications: string[]
81
+ logs: string[]
82
+ advance: (ms: number) => void
83
+ // Trigger the poll timer manually
84
+ poll: () => void
85
+ // Expose the watcher
86
+ watcher: ReturnType<typeof startSubagentWatcher>
87
+ // Current mocked time
88
+ now: () => number
89
+ // Mutable fs object — tests can override .readSync, .statSync etc.
90
+ // for per-test customization (the watcher reads each method on every call,
91
+ // so reassigning is picked up immediately).
92
+ mockFs: {
93
+ existsSync: typeof fs.existsSync
94
+ readdirSync: typeof fs.readdirSync
95
+ statSync: typeof fs.statSync
96
+ openSync: typeof fs.openSync
97
+ closeSync: typeof fs.closeSync
98
+ readSync: typeof fs.readSync
99
+ watch: typeof fs.watch
100
+ }
101
+ }
102
+
103
+ function makeHarness(opts: {
104
+ agentDir?: string
105
+ files?: Record<string, string> // filePath → JSONL content
106
+ dirs?: Record<string, string[]> // dirPath → list of filenames
107
+ existingDirs?: string[]
108
+ stallThresholdMs?: number
109
+ rescanMs?: number
110
+ }): WatcherHarness {
111
+ const {
112
+ agentDir = '/home/user/.switchroom/agents/myagent',
113
+ files = {},
114
+ dirs = {},
115
+ existingDirs = [],
116
+ stallThresholdMs = 60_000,
117
+ rescanMs = 500,
118
+ } = opts
119
+
120
+ let currentTime = 1000
121
+ const notifications: string[] = []
122
+ const logs: string[] = []
123
+
124
+ // Track all JSONL content per path for statSync + read simulation
125
+ const fileContents: Map<string, Buffer> = new Map()
126
+ for (const [path, content] of Object.entries(files)) {
127
+ fileContents.set(path, Buffer.from(content, 'utf-8'))
128
+ }
129
+
130
+ // Build a mock fs object — injected via watcher config (ESM namespace
131
+ // exports are not configurable so vi.spyOn(fs, ...) doesn't work).
132
+ const fakeWatchers: Array<{ close: () => void }> = []
133
+ // Track which path was last opened so readSync can serve the right content.
134
+ // The mock fd is always 42; we only ever have one open file at a time.
135
+ let lastOpenedPath: string | null = null
136
+ const mockFs = {
137
+ existsSync: ((p: fs.PathLike) => {
138
+ const ps = String(p)
139
+ if (existingDirs.includes(ps)) return true
140
+ if (dirs[ps] !== undefined) return true
141
+ if (fileContents.has(ps)) return true
142
+ for (const fp of fileContents.keys()) {
143
+ if (fp.startsWith(ps + '/')) return true
144
+ }
145
+ return false
146
+ }) as typeof fs.existsSync,
147
+ readdirSync: ((p: fs.PathLike) => {
148
+ const ps = String(p)
149
+ if (dirs[ps]) return dirs[ps]
150
+ const children = new Set<string>()
151
+ for (const fp of fileContents.keys()) {
152
+ if (fp.startsWith(ps + '/')) {
153
+ const rest = fp.slice(ps.length + 1)
154
+ const part = rest.split('/')[0]
155
+ if (part) children.add(part)
156
+ }
157
+ }
158
+ return Array.from(children)
159
+ }) as unknown as typeof fs.readdirSync,
160
+ statSync: ((p: fs.PathLike) => {
161
+ const ps = String(p)
162
+ const content = fileContents.get(ps)
163
+ return { size: content?.length ?? 0 } as fs.Stats
164
+ }) as typeof fs.statSync,
165
+ openSync: ((p: fs.PathLike) => {
166
+ lastOpenedPath = String(p)
167
+ return 42
168
+ }) as unknown as typeof fs.openSync,
169
+ closeSync: (() => {
170
+ lastOpenedPath = null
171
+ }) as typeof fs.closeSync,
172
+ readSync: ((
173
+ _fd: number,
174
+ buf: NodeJS.ArrayBufferView,
175
+ offset: number,
176
+ length: number,
177
+ position: number | null,
178
+ ): number => {
179
+ // Serve content from fileContents for the currently open file.
180
+ const content = lastOpenedPath != null ? fileContents.get(lastOpenedPath) : undefined
181
+ if (!content) return 0
182
+ const pos = position ?? 0
183
+ const src = content.slice(pos, pos + length)
184
+ ;(src as Buffer).copy(buf as Buffer, offset)
185
+ return src.length
186
+ }) as unknown as typeof fs.readSync,
187
+ watch: (() => {
188
+ const w = { close: vi.fn() }
189
+ fakeWatchers.push(w)
190
+ return w as unknown as fs.FSWatcher
191
+ }) as unknown as typeof fs.watch,
192
+ }
193
+
194
+ // Injected timers
195
+ const intervals: Array<{ fn: () => void; ms: number; ref: number; fireAt: number }> = []
196
+ const timeouts: Array<{ fn: () => void; ref: number; fireAt: number }> = []
197
+ let nextRef = 1
198
+
199
+ const watcher = startSubagentWatcher({
200
+ agentDir,
201
+ sendNotification: (text) => notifications.push(text),
202
+ stallThresholdMs,
203
+ rescanMs,
204
+ now: () => currentTime,
205
+ setInterval: (fn, ms) => {
206
+ const ref = nextRef++
207
+ intervals.push({ fn, ms, ref, fireAt: currentTime + ms })
208
+ return { ref }
209
+ },
210
+ clearInterval: (handle) => {
211
+ const { ref } = handle as { ref: number }
212
+ const idx = intervals.findIndex((i) => i.ref === ref)
213
+ if (idx !== -1) intervals.splice(idx, 1)
214
+ },
215
+ setTimeout: (fn, ms) => {
216
+ const ref = nextRef++
217
+ timeouts.push({ fn, ref, fireAt: currentTime + ms })
218
+ return { ref }
219
+ },
220
+ clearTimeout: (handle) => {
221
+ const { ref } = handle as { ref: number }
222
+ const idx = timeouts.findIndex((t) => t.ref === ref)
223
+ if (idx !== -1) timeouts.splice(idx, 1)
224
+ },
225
+ fs: mockFs,
226
+ log: (msg: string) => { logs.push(msg) },
227
+ })
228
+
229
+ const advance = (ms: number): void => {
230
+ currentTime += ms
231
+ // Fire any intervals whose fireAt <= currentTime
232
+ for (;;) {
233
+ intervals.sort((a, b) => a.fireAt - b.fireAt)
234
+ const next = intervals[0]
235
+ if (!next || next.fireAt > currentTime) break
236
+ next.fireAt += next.ms
237
+ next.fn()
238
+ }
239
+ // Fire any one-shot timeouts whose fireAt <= currentTime — drain
240
+ // the queue (oneshots, so remove on fire).
241
+ for (;;) {
242
+ timeouts.sort((a, b) => a.fireAt - b.fireAt)
243
+ const next = timeouts[0]
244
+ if (!next || next.fireAt > currentTime) break
245
+ timeouts.shift()
246
+ next.fn()
247
+ }
248
+ }
249
+
250
+ const poll = (): void => {
251
+ const pollInterval = intervals[0]
252
+ if (pollInterval) pollInterval.fn()
253
+ }
254
+
255
+ return {
256
+ notifications,
257
+ logs,
258
+ advance,
259
+ poll,
260
+ watcher,
261
+ now: () => currentTime,
262
+ mockFs,
263
+ fakeWatchers,
264
+ pendingTimeouts: () => timeouts.length,
265
+ }
266
+ }
267
+
268
+ // ─── Tests ───────────────────────────────────────────────────────────────────
269
+
270
+ describe('startSubagentWatcher', () => {
271
+ beforeEach(() => {
272
+ vi.restoreAllMocks()
273
+ })
274
+
275
+ it('does nothing when the agent dir has no .claude/projects', () => {
276
+ const h = makeHarness({ agentDir: '/nonexistent', existingDirs: [] })
277
+ h.poll()
278
+ expect(h.notifications).toHaveLength(0)
279
+ h.watcher.stop()
280
+ })
281
+
282
+ it('detects a new subagent JSONL created after startup', () => {
283
+ // Watcher starts with an empty subagents dir, then a new file appears.
284
+ const agentDir = '/home/user/.switchroom/agents/myagent'
285
+ const projectsRoot = `${agentDir}/.claude/projects`
286
+ const projectDir = `${projectsRoot}/myproject`
287
+ const sessionDir = `${projectDir}/session-abc123`
288
+ const subagentsDir = `${sessionDir}/subagents`
289
+ const jsonlPath = `${subagentsDir}/agent-deadbeef.jsonl`
290
+ const content = buildJSONL(subAgentUserMsg('Fix the tests please'))
291
+
292
+ const h = makeHarness({
293
+ agentDir,
294
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
295
+ dirs: {
296
+ [projectsRoot]: ['myproject'],
297
+ [projectDir]: ['session-abc123'],
298
+ // subagentsDir is empty at startup
299
+ [subagentsDir]: [],
300
+ },
301
+ files: {},
302
+ })
303
+
304
+ // No notifications during boot
305
+ expect(h.notifications).toHaveLength(0)
306
+
307
+ // Simulate the new file appearing after startup
308
+ h.mockFs.readdirSync = ((p: unknown) => {
309
+ const ps = String(p)
310
+ if (ps === subagentsDir) return ['agent-deadbeef.jsonl']
311
+ if (ps === projectsRoot) return ['myproject']
312
+ if (ps === projectDir) return ['session-abc123']
313
+ return []
314
+ }) as unknown as typeof fs.readdirSync
315
+ h.mockFs.existsSync = ((p: unknown) => {
316
+ const ps = String(p)
317
+ return [projectsRoot, projectDir, sessionDir, subagentsDir, jsonlPath].includes(ps)
318
+ }) as typeof fs.existsSync
319
+ const contentBuf = Buffer.from(content, 'utf-8')
320
+ h.mockFs.statSync = ((p: unknown) => {
321
+ if (String(p) === jsonlPath) return { size: contentBuf.length } as import('fs').Stats
322
+ return { size: 0 } as import('fs').Stats
323
+ }) as typeof fs.statSync
324
+
325
+ h.poll()
326
+
327
+ const entry = h.watcher.getRegistry().get('deadbeef')
328
+ expect(entry).toBeDefined()
329
+ expect(entry?.historical).toBe(false)
330
+ expect(entry?.state).toBe('running')
331
+
332
+ h.watcher.stop()
333
+ })
334
+
335
+ // The next three tests use a real tmp dir + real files + real fs (no
336
+ // injection). The over-mocked harness can't reproduce the read-sequence
337
+ // statefully — real fs is simpler and more accurate.
338
+ describe('with real tmp filesystem', () => {
339
+ let tmpRoot = ''
340
+ const startedWatchers: Array<{ stop(): void }> = []
341
+
342
+ beforeEach(() => {
343
+ tmpRoot = mkdtempSync(join(tmpdir(), 'switchroom-watcher-test-'))
344
+ })
345
+
346
+ afterEach(() => {
347
+ while (startedWatchers.length) {
348
+ try { startedWatchers.pop()?.stop() } catch { /* ignore */ }
349
+ }
350
+ try { rmSync(tmpRoot, { recursive: true, force: true }) } catch { /* ignore */ }
351
+ })
352
+
353
+ function setupRealFs(jsonlContent: string, agentId: string): {
354
+ agentDir: string
355
+ jsonlPath: string
356
+ } {
357
+ const agentDir = join(tmpRoot, 'agent')
358
+ const subagentsDir = join(agentDir, '.claude', 'projects', 'p1', 'session-abc', 'subagents')
359
+ mkdirSync(subagentsDir, { recursive: true })
360
+ const jsonlPath = join(subagentsDir, `agent-${agentId}.jsonl`)
361
+ writeFileSync(jsonlPath, jsonlContent)
362
+ return { agentDir, jsonlPath }
363
+ }
364
+
365
+ function startWatcherSync(opts: { agentDir: string }): {
366
+ notifications: string[]
367
+ poll: () => void
368
+ watcher: ReturnType<typeof startSubagentWatcher>
369
+ fireScheduledCleanups: () => number
370
+ } {
371
+ const notifications: string[] = []
372
+ const intervals: Array<{ fn: () => void; ref: number }> = []
373
+ const timeouts: Array<{ fn: () => void; ref: number }> = []
374
+ let nextRef = 1
375
+ const watcher = startSubagentWatcher({
376
+ agentDir: opts.agentDir,
377
+ sendNotification: (text) => notifications.push(text),
378
+ stallThresholdMs: 60_000,
379
+ rescanMs: 500,
380
+ now: () => Date.now(),
381
+ setInterval: (fn) => {
382
+ const ref = nextRef++
383
+ intervals.push({ fn, ref })
384
+ return { ref }
385
+ },
386
+ clearInterval: (handle) => {
387
+ const { ref } = handle as { ref: number }
388
+ const idx = intervals.findIndex((i) => i.ref === ref)
389
+ if (idx !== -1) intervals.splice(idx, 1)
390
+ },
391
+ setTimeout: (fn) => {
392
+ const ref = nextRef++
393
+ timeouts.push({ fn, ref })
394
+ return { ref }
395
+ },
396
+ clearTimeout: (handle) => {
397
+ const { ref } = handle as { ref: number }
398
+ const idx = timeouts.findIndex((t) => t.ref === ref)
399
+ if (idx !== -1) timeouts.splice(idx, 1)
400
+ },
401
+ log: () => {},
402
+ })
403
+ startedWatchers.push(watcher)
404
+ return {
405
+ notifications,
406
+ poll: () => intervals[0]?.fn(),
407
+ watcher,
408
+ // Drain any scheduled deferred-cleanups regardless of fireAt time
409
+ // (tests use this to advance past the 30s grace deterministically).
410
+ fireScheduledCleanups: () => {
411
+ let fired = 0
412
+ while (timeouts.length) {
413
+ const next = timeouts.shift()!
414
+ next.fn()
415
+ fired++
416
+ }
417
+ return fired
418
+ },
419
+ }
420
+ }
421
+
422
+ it('does NOT overwrite description with sub_agent_text narrative (#352)', () => {
423
+ // Pre-#352 the watcher would overwrite a sub-agent's dispatch
424
+ // description with the first narrative line it saw. That made identical
425
+ // dispatches render differently depending on which event reached the
426
+ // watcher first — a race-condition-dependent UX bug.
427
+ //
428
+ // Post-#352 the description must remain whatever the watcher started
429
+ // with (the dispatch description set by the parent Agent tool_use
430
+ // input, or the placeholder when the watcher is bootstrapped without
431
+ // one). Narrative text is recorded in `lastSummaryLine` instead.
432
+ const content = buildJSONL(
433
+ subAgentUserMsg('Do the thing'),
434
+ subAgentAssistantText('I will implement the feature now'),
435
+ )
436
+ const { agentDir } = setupRealFs(content, 'deadbeef')
437
+ const h = startWatcherSync({ agentDir })
438
+ h.poll()
439
+ const entry = h.watcher.getRegistry().get('deadbeef')
440
+ expect(entry).toBeDefined()
441
+ // Description stays as the bootstrap value — the watcher must NOT
442
+ // promote the narrative line into the description field.
443
+ expect(entry?.description).not.toMatch(/I will implement/)
444
+ // Narrative text still flows into lastSummaryLine for telemetry.
445
+ expect(entry?.lastSummaryLine).toMatch(/I will implement/)
446
+ })
447
+
448
+ it('counts tools from sub_agent_tool_use events', () => {
449
+ const content = buildJSONL(
450
+ subAgentUserMsg('Fix things'),
451
+ subAgentToolUse('Read', 'id1'),
452
+ subAgentToolUse('Bash', 'id2'),
453
+ subAgentToolUse('Edit', 'id3'),
454
+ )
455
+ const { agentDir } = setupRealFs(content, 'deadbeef')
456
+ const h = startWatcherSync({ agentDir })
457
+ h.poll()
458
+ const entry = h.watcher.getRegistry().get('deadbeef')
459
+ expect(entry).toBeDefined()
460
+ expect(entry?.toolCount).toBe(3)
461
+ })
462
+
463
+ it('does NOT emit completion notification for a file already done at startup', () => {
464
+ // File pre-exists with turn_end already written — agent was done before
465
+ // the watcher started. No completion notification should fire.
466
+ const content = buildJSONL(
467
+ subAgentUserMsg('Do the task'),
468
+ subAgentTurnDuration(),
469
+ )
470
+ const { agentDir } = setupRealFs(content, 'deadbeef')
471
+ const h = startWatcherSync({ agentDir })
472
+ h.poll()
473
+ const entry = h.watcher.getRegistry().get('deadbeef')
474
+ expect(entry).toBeDefined()
475
+ expect(entry?.state).toBe('done')
476
+ // Already done at boot → historical → no completion notification
477
+ const completionNotifs = h.notifications.filter((n) => n.includes('Worker done'))
478
+ expect(completionNotifs).toHaveLength(0)
479
+ })
480
+
481
+ it('emits completion notification when a NEW subagent finishes', () => {
482
+ // File does NOT exist at startup. Watcher starts, then file appears
483
+ // with an in-flight status. Then turn_end is appended — we should
484
+ // get a completion notification.
485
+ const agentDir = join(tmpRoot, 'agent')
486
+ const subagentsDir = join(agentDir, '.claude', 'projects', 'p1', 'session-abc', 'subagents')
487
+ mkdirSync(subagentsDir, { recursive: true })
488
+ const jsonlPath = join(subagentsDir, 'agent-newagent.jsonl')
489
+
490
+ // Write just the initial user message (in-flight state)
491
+ const initialContent = buildJSONL(subAgentUserMsg('Do the task'))
492
+
493
+ const h = startWatcherSync({ agentDir })
494
+
495
+ // Write file AFTER watcher starts (post-startup, so not historical)
496
+ writeFileSync(jsonlPath, initialContent)
497
+ h.poll()
498
+
499
+ const entry = h.watcher.getRegistry().get('newagent')
500
+ expect(entry).toBeDefined()
501
+ expect(entry?.state).toBe('running')
502
+ expect(entry?.historical).toBe(false)
503
+
504
+ // Now append turn_end to simulate agent finishing
505
+ appendFileSync(jsonlPath, buildJSONL(subAgentTurnDuration()))
506
+ h.poll()
507
+
508
+ const completionNotifs = h.notifications.filter((n) => n.includes('Worker done'))
509
+ expect(completionNotifs).toHaveLength(1)
510
+ })
511
+
512
+ it('drops the FSWatcher + Map entries after terminal-state grace fires (MEM1)', () => {
513
+ // Pre-MEM1 fix: per-subagent FSWatcher entries lived for the
514
+ // entire process lifetime. With sustained sub-agent load a
515
+ // long-running gateway hit ulimit -n. This test pins the deferred
516
+ // cleanup contract: completion → fire grace timer → tails/registry
517
+ // entries removed → underlying FSWatcher closed.
518
+ const agentDir = join(tmpRoot, 'agent')
519
+ const subagentsDir = join(agentDir, '.claude', 'projects', 'p1', 'session-abc', 'subagents')
520
+ mkdirSync(subagentsDir, { recursive: true })
521
+ const jsonlPath = join(subagentsDir, 'agent-cleanme.jsonl')
522
+ writeFileSync(jsonlPath, buildJSONL(subAgentUserMsg('Do the task')))
523
+
524
+ const h = startWatcherSync({ agentDir })
525
+
526
+ // Discover + register the agent (running state).
527
+ h.poll()
528
+ expect(h.watcher.getRegistry().has('cleanme')).toBe(true)
529
+
530
+ // Append turn_end → done state → completion notification + scheduled cleanup.
531
+ appendFileSync(jsonlPath, buildJSONL(subAgentTurnDuration()))
532
+ h.poll()
533
+ expect(h.notifications.some((n) => n.includes('Worker done'))).toBe(true)
534
+ // Registry still has it during the 30s grace window.
535
+ expect(h.watcher.getRegistry().has('cleanme')).toBe(true)
536
+
537
+ // Drain pending timeouts (simulates 30s elapsing).
538
+ const fired = h.fireScheduledCleanups()
539
+ expect(fired).toBeGreaterThan(0)
540
+
541
+ // Post-grace: registry entry gone, downstream consumers see no
542
+ // dangling FSWatcher.
543
+ expect(h.watcher.getRegistry().has('cleanme')).toBe(false)
544
+ })
545
+
546
+ it('cleans up historical-and-already-done agents after grace (MEM1)', () => {
547
+ // Historical files (pre-existing at boot, already done) used to
548
+ // keep their FSWatcher open forever — they bypass the
549
+ // maybySendStateTransition done branch because completionNotified
550
+ // is set to true in the registerAgent path. Cleanup must still
551
+ // schedule there.
552
+ const agentDir = join(tmpRoot, 'agent')
553
+ const subagentsDir = join(agentDir, '.claude', 'projects', 'p1', 'session-abc', 'subagents')
554
+ mkdirSync(subagentsDir, { recursive: true })
555
+ const jsonlPath = join(subagentsDir, 'agent-historical.jsonl')
556
+ // Already-done at boot: contains turn_end already.
557
+ writeFileSync(jsonlPath, buildJSONL(
558
+ subAgentUserMsg('From a prior session'),
559
+ subAgentTurnDuration(),
560
+ ))
561
+
562
+ const h = startWatcherSync({ agentDir })
563
+
564
+ // Boot scan picks it up as historical-and-done; no completion
565
+ // notification fires (would be a spurious replay).
566
+ expect(h.notifications.filter((n) => n.includes('Worker done'))).toHaveLength(0)
567
+ expect(h.watcher.getRegistry().has('historical')).toBe(true)
568
+
569
+ // Cleanup is still scheduled (the FSWatcher would otherwise leak).
570
+ const fired = h.fireScheduledCleanups()
571
+ expect(fired).toBeGreaterThan(0)
572
+ expect(h.watcher.getRegistry().has('historical')).toBe(false)
573
+ })
574
+ })
575
+
576
+ it('emits stall notification after stallThresholdMs idle', () => {
577
+ const agentDir = '/home/user/.switchroom/agents/myagent'
578
+ const projectsRoot = `${agentDir}/.claude/projects`
579
+ const projectDir = `${projectsRoot}/myproject`
580
+ const sessionDir = `${projectDir}/session-abc123`
581
+ const subagentsDir = `${sessionDir}/subagents`
582
+ const jsonlPath = `${subagentsDir}/agent-deadbeef.jsonl`
583
+
584
+ // Only the initial user message — no tool_use or turn_end
585
+ const content = buildJSONL(subAgentUserMsg('Run a long task'))
586
+
587
+ const h = makeHarness({
588
+ agentDir,
589
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
590
+ dirs: {
591
+ [projectsRoot]: ['myproject'],
592
+ [projectDir]: ['session-abc123'],
593
+ [subagentsDir]: ['agent-deadbeef.jsonl'],
594
+ },
595
+ files: { [jsonlPath]: content },
596
+ stallThresholdMs: 60_000,
597
+ rescanMs: 500,
598
+ })
599
+
600
+ // Initial poll — registers the agent (as historical, since the file
601
+ // already exists at boot). Flip historical=false to simulate an entry
602
+ // that was discovered post-boot, which is the only case stalls fire.
603
+ h.poll()
604
+ const entry = h.watcher.getRegistry().get('deadbeef')
605
+ if (entry) entry.historical = false
606
+
607
+ // Advance past stall threshold without any new JSONL activity
608
+ h.advance(65_000)
609
+
610
+ const stallLogs = h.logs.filter((n) => n.includes('stall detected'))
611
+ expect(stallLogs.length).toBeGreaterThanOrEqual(1)
612
+ expect(stallLogs[0]).toContain('stall detected')
613
+
614
+ h.watcher.stop()
615
+ })
616
+
617
+ it('suppresses stall notifications for historical entries', () => {
618
+ // Historical entries (file existed at watcher boot) must NOT fire
619
+ // stall notifications. The sub-agent process is long dead; the file
620
+ // is just left over from a prior session. With many historicals
621
+ // present at restart, firing stalls for each would flood the chat.
622
+ const agentDir = '/home/user/.switchroom/agents/myagent'
623
+ const projectsRoot = `${agentDir}/.claude/projects`
624
+ const projectDir = `${projectsRoot}/myproject`
625
+ const sessionDir = `${projectDir}/session-abc123`
626
+ const subagentsDir = `${sessionDir}/subagents`
627
+ const jsonlPath = `${subagentsDir}/agent-deadbeef.jsonl`
628
+ const content = buildJSONL(subAgentUserMsg('Old task'))
629
+
630
+ const h = makeHarness({
631
+ agentDir,
632
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
633
+ dirs: {
634
+ [projectsRoot]: ['myproject'],
635
+ [projectDir]: ['session-abc123'],
636
+ [subagentsDir]: ['agent-deadbeef.jsonl'],
637
+ },
638
+ files: { [jsonlPath]: content },
639
+ stallThresholdMs: 60_000,
640
+ })
641
+
642
+ h.poll()
643
+ h.advance(65_000) // past stall threshold
644
+
645
+ const stallLogs = h.logs.filter((n) => n.includes('stall detected'))
646
+ expect(stallLogs).toHaveLength(0)
647
+
648
+ h.watcher.stop()
649
+ })
650
+
651
+ it('does not emit stall notification twice', () => {
652
+ const agentDir = '/home/user/.switchroom/agents/myagent'
653
+ const projectsRoot = `${agentDir}/.claude/projects`
654
+ const projectDir = `${projectsRoot}/myproject`
655
+ const sessionDir = `${projectDir}/session-abc123`
656
+ const subagentsDir = `${sessionDir}/subagents`
657
+ const jsonlPath = `${subagentsDir}/agent-deadbeef.jsonl`
658
+
659
+ const content = buildJSONL(subAgentUserMsg('Long task'))
660
+
661
+ const h = makeHarness({
662
+ agentDir,
663
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
664
+ dirs: {
665
+ [projectsRoot]: ['myproject'],
666
+ [projectDir]: ['session-abc123'],
667
+ [subagentsDir]: ['agent-deadbeef.jsonl'],
668
+ },
669
+ files: { [jsonlPath]: content },
670
+ stallThresholdMs: 60_000,
671
+ })
672
+
673
+ h.poll()
674
+ const entry = h.watcher.getRegistry().get('deadbeef')
675
+ if (entry) entry.historical = false
676
+
677
+ h.advance(65_000)
678
+ h.advance(65_000) // advance past threshold AGAIN
679
+
680
+ const stallLogs = h.logs.filter((n) => n.includes('stall detected'))
681
+ expect(stallLogs.length).toBe(1)
682
+
683
+ h.watcher.stop()
684
+ })
685
+
686
+ it('does not duplicate workers registered from same file', () => {
687
+ // File exists at startup → historical. Repeated polls should not
688
+ // re-register the agent or emit extra notifications.
689
+ const agentDir = '/home/user/.switchroom/agents/myagent'
690
+ const projectsRoot = `${agentDir}/.claude/projects`
691
+ const projectDir = `${projectsRoot}/myproject`
692
+ const sessionDir = `${projectDir}/session-abc123`
693
+ const subagentsDir = `${sessionDir}/subagents`
694
+ const jsonlPath = `${subagentsDir}/agent-deadbeef.jsonl`
695
+
696
+ const content = buildJSONL(subAgentUserMsg('Do it'))
697
+
698
+ const h = makeHarness({
699
+ agentDir,
700
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
701
+ dirs: {
702
+ [projectsRoot]: ['myproject'],
703
+ [projectDir]: ['session-abc123'],
704
+ [subagentsDir]: ['agent-deadbeef.jsonl'],
705
+ },
706
+ files: { [jsonlPath]: content },
707
+ })
708
+
709
+ h.poll()
710
+ h.poll() // second poll — should not re-register
711
+ h.poll()
712
+
713
+ const registry = h.watcher.getRegistry()
714
+ expect(registry.size).toBe(1)
715
+
716
+ h.watcher.stop()
717
+ })
718
+
719
+ it('stop() cleans up and stops poll timers', () => {
720
+ const h = makeHarness({})
721
+ h.watcher.stop()
722
+
723
+ // After stop, advancing should not trigger anything new
724
+ const notifsBefore = h.notifications.length
725
+ h.advance(100_000)
726
+ expect(h.notifications.length).toBe(notifsBefore)
727
+ })
728
+
729
+ // ─── Historical-vs-active filter regression tests ────────────────────────
730
+
731
+ describe('historical-vs-active filter', () => {
732
+ /**
733
+ * Pre-existing JSONL files at watcher boot are tagged historical=true.
734
+ * Stalls and completion notifications are gated on !historical so a
735
+ * restart with months of session history doesn't flood the chat.
736
+ */
737
+
738
+ it('pre-existing JSONL files at startup are tagged historical', () => {
739
+ const agentDir = '/home/user/.switchroom/agents/myagent'
740
+ const projectsRoot = `${agentDir}/.claude/projects`
741
+ const projectDir = `${projectsRoot}/myproject`
742
+ const sessionDir = `${projectDir}/session-abc123`
743
+ const subagentsDir = `${sessionDir}/subagents`
744
+ const jsonlA = `${subagentsDir}/agent-hist-aaaa.jsonl`
745
+ const jsonlB = `${subagentsDir}/agent-hist-bbbb.jsonl`
746
+
747
+ const content = buildJSONL(subAgentUserMsg('Old task'))
748
+
749
+ const h = makeHarness({
750
+ agentDir,
751
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
752
+ dirs: {
753
+ [projectsRoot]: ['myproject'],
754
+ [projectDir]: ['session-abc123'],
755
+ [subagentsDir]: ['agent-hist-aaaa.jsonl', 'agent-hist-bbbb.jsonl'],
756
+ },
757
+ files: {
758
+ [jsonlA]: content,
759
+ [jsonlB]: content,
760
+ },
761
+ })
762
+
763
+ const registry = h.watcher.getRegistry()
764
+ expect(registry.size).toBe(2)
765
+ for (const entry of registry.values()) {
766
+ expect(entry.historical).toBe(true)
767
+ }
768
+
769
+ h.watcher.stop()
770
+ })
771
+
772
+ it('JSONL file created after startup is tagged non-historical', () => {
773
+ const agentDir = '/home/user/.switchroom/agents/myagent'
774
+ const projectsRoot = `${agentDir}/.claude/projects`
775
+ const projectDir = `${projectsRoot}/myproject`
776
+ const sessionDir = `${projectDir}/session-abc123`
777
+ const subagentsDir = `${sessionDir}/subagents`
778
+ const newJsonl = `${subagentsDir}/agent-new-cccc.jsonl`
779
+
780
+ const content = buildJSONL(subAgentUserMsg('Fresh task'))
781
+
782
+ const h = makeHarness({
783
+ agentDir,
784
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
785
+ dirs: {
786
+ [projectsRoot]: ['myproject'],
787
+ [projectDir]: ['session-abc123'],
788
+ [subagentsDir]: [],
789
+ },
790
+ files: {},
791
+ })
792
+
793
+ h.mockFs.readdirSync = ((p: unknown) => {
794
+ if (String(p) === subagentsDir) return ['agent-new-cccc.jsonl']
795
+ if (String(p) === projectsRoot) return ['myproject']
796
+ if (String(p) === projectDir) return ['session-abc123']
797
+ return []
798
+ }) as unknown as typeof import('fs').readdirSync
799
+ h.mockFs.existsSync = ((p: unknown) => {
800
+ const ps = String(p)
801
+ return [projectsRoot, projectDir, sessionDir, subagentsDir, newJsonl].includes(ps)
802
+ }) as typeof import('fs').existsSync
803
+ h.mockFs.statSync = ((p: unknown) => {
804
+ const ps = String(p)
805
+ if (ps === newJsonl) return { size: Buffer.from(content, 'utf-8').length } as import('fs').Stats
806
+ return { size: 0 } as import('fs').Stats
807
+ }) as typeof import('fs').statSync
808
+
809
+ h.poll()
810
+
811
+ const entry = h.watcher.getRegistry().get('new-cccc')
812
+ expect(entry).toBeDefined()
813
+ expect(entry?.historical).toBe(false)
814
+
815
+ h.watcher.stop()
816
+ })
817
+
818
+ it('pre-existing in-flight agent that finishes after restart fires completion', () => {
819
+ // Historical at boot. Then writes turn_end. Completion notification
820
+ // still fires for the state transition (the file was in-flight at
821
+ // boot, so the transition is meaningful even if the entry is tagged
822
+ // historical for stall-suppression purposes).
823
+ const agentDir = '/home/user/.switchroom/agents/myagent'
824
+ const projectsRoot = `${agentDir}/.claude/projects`
825
+ const projectDir = `${projectsRoot}/myproject`
826
+ const sessionDir = `${projectDir}/session-abc123`
827
+ const subagentsDir = `${sessionDir}/subagents`
828
+ const jsonlPath = `${subagentsDir}/agent-inflight-dddd.jsonl`
829
+
830
+ const initialContent = buildJSONL(subAgentUserMsg('Important in-flight task'))
831
+ const initialBuf = Buffer.from(initialContent, 'utf-8')
832
+
833
+ let currentContent = initialBuf
834
+
835
+ const h = makeHarness({
836
+ agentDir,
837
+ existingDirs: [projectsRoot, projectDir, sessionDir, subagentsDir],
838
+ dirs: {
839
+ [projectsRoot]: ['myproject'],
840
+ [projectDir]: ['session-abc123'],
841
+ [subagentsDir]: ['agent-inflight-dddd.jsonl'],
842
+ },
843
+ files: { [jsonlPath]: initialContent },
844
+ })
845
+
846
+ const entry = h.watcher.getRegistry().get('inflight-dddd')
847
+ expect(entry).toBeDefined()
848
+ expect(entry?.state).toBe('running')
849
+
850
+ const finishedContent = initialContent + buildJSONL(subAgentTurnDuration())
851
+ currentContent = Buffer.from(finishedContent, 'utf-8')
852
+ h.mockFs.statSync = ((p: unknown) => {
853
+ if (String(p) === jsonlPath) return { size: currentContent.length } as import('fs').Stats
854
+ return { size: 0 } as import('fs').Stats
855
+ }) as typeof import('fs').statSync
856
+ h.mockFs.readSync = ((
857
+ _fd: number,
858
+ buf: NodeJS.ArrayBufferView,
859
+ offset: number,
860
+ length: number,
861
+ position: number | null,
862
+ ): number => {
863
+ const pos = position ?? 0
864
+ const src = currentContent.slice(pos, pos + length)
865
+ Buffer.from(src).copy(buf as Buffer, offset)
866
+ return src.length
867
+ }) as unknown as typeof import('fs').readSync
868
+
869
+ h.poll()
870
+
871
+ const completionNotifs = h.notifications.filter((n) => n.includes('Worker done'))
872
+ expect(completionNotifs).toHaveLength(1)
873
+
874
+ h.watcher.stop()
875
+ })
876
+ })
877
+ })