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,880 @@
1
+ /**
2
+ * Background sub-agent visibility — registry + directory watcher.
3
+ *
4
+ * Watches the subagents/ directory under each active session dir for new
5
+ * agent-<id>.jsonl files. For each discovered sub-agent it:
6
+ * 1. Registers it in an in-memory registry.
7
+ * 2. Tails the JSONL to count tool calls and detect turn_end.
8
+ * 3. Emits inline notifications for stall / completion state transitions.
9
+ *
10
+ * Phase 3 of #333: when a sub-agent JSONL's size advances (mtime equivalent),
11
+ * the watcher writes `last_activity_at = <timestamp>` to the matching
12
+ * `subagents` row in the registry DB via `bumpSubagentActivity`. If the row
13
+ * does not yet exist (Phase 2 Pre hook hasn't fired), the update is a no-op
14
+ * and the event is logged — no INSERT here, identity belongs to Phase 2.
15
+ *
16
+ * Sub-agent state is surfaced to the user via the progress card's
17
+ * [Sub-agents · N running] block (progress-card.ts), not a separate pinned
18
+ * card. See issue #142.
19
+ *
20
+ * Architecture notes:
21
+ * - Option B from the spec: filesystem-driven, no IPC contract.
22
+ * - The registry is independent of the progress-card driver — it watches
23
+ * the subagents/ directories directly, not the parent session JSONL.
24
+ * - Privacy: tool counts + descriptions only — no tool args or file content.
25
+ *
26
+ * Integration: call `startSubagentWatcher(config)` once at gateway startup
27
+ * (after the bot is ready). Call `.stop()` on shutdown.
28
+ */
29
+
30
+ import {
31
+ existsSync,
32
+ openSync,
33
+ readSync,
34
+ statSync,
35
+ closeSync,
36
+ watch,
37
+ readdirSync,
38
+ readFileSync,
39
+ type FSWatcher,
40
+ } from 'fs'
41
+ import { basename, join } from 'path'
42
+ import { homedir } from 'os'
43
+ import { projectSubagentLine } from './session-tail.js'
44
+ import { sanitiseToolArg } from './fleet-state.js'
45
+ import { escapeHtml, truncate } from './card-format.js'
46
+ import { bumpSubagentActivity, recordSubagentStall, recordSubagentEnd, reapStuckRunningRows } from './registry/subagents-schema.js'
47
+ import { touchTurnActiveMarker } from './gateway/turn-active-marker.js'
48
+
49
+ // ─── Types ───────────────────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Minimal DB interface needed by the watcher for Phase 3 liveness writes.
53
+ * Structurally compatible with the wider `SqliteDatabase` shape used by
54
+ * `registry/subagents-schema.ts` so call sites can pass either without
55
+ * casting. Tests can implement just the subset they need (TypeScript's
56
+ * structural typing handles the rest).
57
+ */
58
+ export interface SubagentLivenessDb {
59
+ exec(sql: string): void
60
+ prepare(sql: string): {
61
+ run(...params: unknown[]): unknown
62
+ all(...params: unknown[]): unknown[]
63
+ get(...params: unknown[]): unknown
64
+ }
65
+ transaction(fn: (...args: unknown[]) => unknown): (...args: unknown[]) => unknown
66
+ close(): void
67
+ }
68
+
69
+ export type WorkerState = 'running' | 'done' | 'failed'
70
+
71
+ export interface WorkerEntry {
72
+ /** Sub-agent JSONL file stem, e.g. "a75d4757a81e7b1f8". */
73
+ readonly agentId: string
74
+ /** File path of the JSONL. */
75
+ readonly filePath: string
76
+ /** Short description — from the sub-agent's first text/narrative line. */
77
+ description: string
78
+ /** Current lifecycle state. */
79
+ state: WorkerState
80
+ readonly dispatchedAt: number
81
+ lastActivityAt: number
82
+ /** Number of tool calls seen so far. */
83
+ toolCount: number
84
+ /** True once a stall notification has been sent (suppresses repeat). */
85
+ stallNotified: boolean
86
+ /** True once a completion notification has been sent. */
87
+ completionNotified: boolean
88
+ /** Short summary from last completed tool / narrative, for completion message. */
89
+ lastSummaryLine: string
90
+ /**
91
+ * Most recent tool call observed on this sub-agent's JSONL tail —
92
+ * tool name + sanitised arg for fleet-row display (P0 of #662). Null
93
+ * before any `sub_agent_tool_use` event has been seen. Replace-on-write;
94
+ * the renderer only ever shows the latest.
95
+ */
96
+ lastTool: { name: string; sanitisedArg: string } | null
97
+ /**
98
+ * True if the underlying JSONL file existed before the watcher started.
99
+ * Historical entries are tracked for late state transitions but are
100
+ * excluded from the active-workers card — the sub-agent process is long
101
+ * dead, the file is just left over from a prior session.
102
+ */
103
+ historical: boolean
104
+ }
105
+
106
+ export interface SubagentWatcherConfig {
107
+ /**
108
+ * Agent home directory (e.g. `/home/user/.switchroom/agents/klanker`).
109
+ * Used to derive `.claude/projects/<cwd>/` dirs to watch.
110
+ */
111
+ agentDir: string
112
+ /**
113
+ * Send a fresh (non-edit) Telegram message. For stall / completion
114
+ * state-transition notifications.
115
+ */
116
+ sendNotification: (text: string) => void
117
+ /**
118
+ * How often to re-scan for new subagent dirs (ms). Default 1000.
119
+ */
120
+ rescanMs?: number
121
+ /**
122
+ * How long without JSONL activity before a worker is considered stalled (ms).
123
+ * Default 60_000.
124
+ */
125
+ stallThresholdMs?: number
126
+ /**
127
+ * Reaper TTL (ms): background rows in `status='running'` whose
128
+ * `last_activity_at` (or `started_at` if liveness never wrote) is older
129
+ * than this are transitioned to `status='stalled'` with a result_summary
130
+ * explaining the reap. Default 1h. The reaper exists because the normal
131
+ * stall + completion paths both look up rows by `jsonl_agent_id`; if
132
+ * backfill never linked the JSONL to the row, neither path can update
133
+ * it and it sits in `running` forever (issue #522).
134
+ */
135
+ reaperTtlMs?: number
136
+ /**
137
+ * How often to run the reaper (ms). Default 15 minutes. Also runs once
138
+ * synchronously at watcher startup to catch rows left over from a
139
+ * previous gateway process.
140
+ */
141
+ reaperIntervalMs?: number
142
+ /**
143
+ * Optional registry DB for Phase 3 liveness writes. When provided, the
144
+ * watcher calls `bumpSubagentActivity` each time a sub-agent JSONL grows
145
+ * (i.e. mtime advances). If the matching row does not yet exist (Phase 2
146
+ * Pre hook hasn't fired), the UPDATE is a no-op and the event is logged.
147
+ * Passing `null` or omitting this field disables DB writes entirely.
148
+ */
149
+ db?: SubagentLivenessDb | null
150
+ /**
151
+ * Parent agent's state directory — the directory containing the parent's
152
+ * `turn-active.json` marker (issue #412). When provided, every time a
153
+ * **foreground** sub-agent's JSONL grows, the watcher touches the parent
154
+ * marker's mtime so the watchdog (`bin/bridge-watchdog.sh`) doesn't read
155
+ * the parent as wedged just because all the in-turn activity is happening
156
+ * inside a sub-agent that hasn't emitted a JSONL line for a while
157
+ * (issue #501). Background sub-agents are EXCLUDED — they have their own
158
+ * lifecycle decoupled from the parent's turn boundary, and refreshing the
159
+ * parent's marker on background activity would mask real parent-side hangs.
160
+ * If unset, the touch is skipped (preserves pre-#501 behaviour).
161
+ */
162
+ parentStateDir?: string | null
163
+ /** Optional logger for debug output. */
164
+ log?: (msg: string) => void
165
+ /**
166
+ * Option C: callback fired when a stall is detected for a running sub-agent.
167
+ * Called with the sub-agent's agentId, idle ms, and description string.
168
+ * Wired to `progressDriver.onSubAgentStall` in gateway.ts so the progress
169
+ * card re-renders with a visible ⚠️ stall indicator even when the bridge
170
+ * has disconnected. The `stallNotified` flag prevents duplicate calls for
171
+ * the same sub-agent across subsequent poll ticks.
172
+ */
173
+ onStall?: (agentId: string, idleMs: number, description: string) => void
174
+ /** `Date.now` override for tests. */
175
+ now?: () => number
176
+ /** `setInterval` override for tests. */
177
+ setInterval?: (fn: () => void, ms: number) => { ref: unknown }
178
+ clearInterval?: (ref: unknown) => void
179
+ /** `setTimeout` override for tests. */
180
+ setTimeout?: (fn: () => void, ms: number) => { ref: unknown }
181
+ clearTimeout?: (ref: unknown) => void
182
+ /**
183
+ * `fs` overrides for tests. ESM namespace exports are not configurable so
184
+ * `vi.spyOn(fs, ...)` doesn't work — tests inject a mock object here
185
+ * instead. Defaults to the real `node:fs` functions.
186
+ */
187
+ fs?: {
188
+ existsSync: typeof existsSync
189
+ readdirSync: typeof readdirSync
190
+ statSync: typeof statSync
191
+ openSync: typeof openSync
192
+ closeSync: typeof closeSync
193
+ readSync: typeof readSync
194
+ watch: typeof watch
195
+ }
196
+ }
197
+
198
+ export interface SubagentWatcherHandle {
199
+ stop(): void
200
+ /** Snapshot of current registry for tests/inspection. */
201
+ getRegistry(): ReadonlyMap<string, WorkerEntry>
202
+ }
203
+
204
+ // ─── Constants ───────────────────────────────────────────────────────────────
205
+
206
+ const DEFAULT_RESCAN_MS = 1000
207
+ const DEFAULT_STALL_THRESHOLD_MS = 60_000
208
+ const DEFAULT_REAPER_TTL_MS = 60 * 60_000 // 1 hour
209
+ const DEFAULT_REAPER_INTERVAL_MS = 15 * 60_000 // 15 minutes
210
+ /**
211
+ * Grace period between a sub-agent transitioning to terminal state
212
+ * (`done` / `failed`) and the watcher closing its FSWatcher + dropping
213
+ * its Map entries. The grace lets late writes (a final `turn_end`
214
+ * marker landing in the same poll tick as the completion event, the
215
+ * registry-DB UPDATE finishing, a downstream consumer reading the
216
+ * tail one more time) flush without losing data.
217
+ *
218
+ * Pre-fix the per-subagent FSWatcher lived for the entire process
219
+ * lifetime, so a long-running gateway with sustained sub-agent load
220
+ * accumulated FDs until it hit `ulimit -n` (default 1024 on Linux)
221
+ * and the process started failing every fs.watch call. See MEM1 in
222
+ * the overnight forensic audit on #472.
223
+ */
224
+ const TERMINAL_CLEANUP_GRACE_MS = 30_000
225
+
226
+ // ─── JSONL tail per sub-agent ─────────────────────────────────────────────
227
+
228
+ interface SubTail {
229
+ cursor: number
230
+ pendingPartial: string
231
+ hasEmittedStart: boolean
232
+ watcher: FSWatcher | null
233
+ }
234
+
235
+ interface FsLike {
236
+ existsSync: typeof existsSync
237
+ readdirSync: typeof readdirSync
238
+ statSync: typeof statSync
239
+ openSync: typeof openSync
240
+ closeSync: typeof closeSync
241
+ readSync: typeof readSync
242
+ watch: typeof watch
243
+ }
244
+
245
+ /**
246
+ * Backfill `jsonl_agent_id` for a sub-agent row that was inserted by the
247
+ * PreToolUse hook (keyed on tool_use_id) but didn't yet know the JSONL stem.
248
+ *
249
+ * Strategy: read the `agent-<id>.meta.json` sibling Claude Code writes next
250
+ * to each sub-agent JSONL. It carries the same `{ agentType, description }`
251
+ * pair the parent passed to the Agent() tool. We match that pair to the
252
+ * most-recent row in `subagents` where `jsonl_agent_id IS NULL` and link them.
253
+ *
254
+ * Edge cases:
255
+ * - meta.json missing or unreadable: no-op (the row stays unlinked; liveness
256
+ * writes from this agent's JSONL won't land, but the system stays correct).
257
+ * - Multiple in-flight rows with identical (agent_type, description): the
258
+ * most recently started one wins (FIFO matches dispatch order in practice).
259
+ * - Row already linked to a different agentId: SQL `WHERE jsonl_agent_id IS
260
+ * NULL` skips it. Re-runs are safe.
261
+ */
262
+ function backfillJsonlAgentId(
263
+ db: SubagentLivenessDb,
264
+ jsonlPath: string,
265
+ agentId: string,
266
+ log?: (msg: string) => void,
267
+ ): void {
268
+ const metaPath = jsonlPath.replace(/\.jsonl$/, '.meta.json')
269
+ let meta: { agentType?: string; description?: string }
270
+ try {
271
+ const raw = readFileSync(metaPath, 'utf8')
272
+ meta = JSON.parse(raw)
273
+ } catch {
274
+ log?.(`subagent-watcher: backfill skip ${agentId} — meta.json not readable at ${metaPath}`)
275
+ return
276
+ }
277
+ if (!meta.agentType && !meta.description) {
278
+ log?.(`subagent-watcher: backfill skip ${agentId} — meta.json has no agentType/description`)
279
+ return
280
+ }
281
+
282
+ // Already linked? Nothing to do.
283
+ const already = db
284
+ .prepare('SELECT id FROM subagents WHERE jsonl_agent_id = ? LIMIT 1')
285
+ .get(agentId)
286
+ if (already != null) return
287
+
288
+ // Find the most-recent matching unmatched row.
289
+ const candidate = db
290
+ .prepare(`
291
+ SELECT id FROM subagents
292
+ WHERE jsonl_agent_id IS NULL
293
+ AND agent_type IS ?
294
+ AND description IS ?
295
+ ORDER BY started_at DESC
296
+ LIMIT 1
297
+ `)
298
+ .get(meta.agentType ?? null, meta.description ?? null) as { id: string } | null
299
+
300
+ if (candidate == null) {
301
+ log?.(`subagent-watcher: backfill no candidate for ${agentId} (type=${meta.agentType} desc=${meta.description})`)
302
+ return
303
+ }
304
+
305
+ db
306
+ .prepare('UPDATE subagents SET jsonl_agent_id = ? WHERE id = ?')
307
+ .run(agentId, candidate.id)
308
+ log?.(`subagent-watcher: backfill linked ${agentId} → ${candidate.id}`)
309
+ }
310
+
311
+ function readSubTail(
312
+ entry: WorkerEntry,
313
+ tail: SubTail,
314
+ now: number,
315
+ onDescriptionUpdate: (desc: string) => void,
316
+ fs: FsLike,
317
+ log?: (msg: string) => void,
318
+ db?: SubagentLivenessDb | null,
319
+ parentStateDir?: string | null,
320
+ ): void {
321
+ try {
322
+ const stat = fs.statSync(entry.filePath)
323
+ if (stat.size < tail.cursor) {
324
+ tail.cursor = 0
325
+ tail.pendingPartial = ''
326
+ }
327
+ if (stat.size === tail.cursor) return
328
+
329
+ const buf = Buffer.alloc(stat.size - tail.cursor)
330
+ const fd = fs.openSync(entry.filePath, 'r')
331
+ try {
332
+ fs.readSync(fd, buf, 0, buf.length, tail.cursor)
333
+ } finally {
334
+ fs.closeSync(fd)
335
+ }
336
+ tail.cursor = stat.size
337
+
338
+ // Phase 3 (#333): JSONL grew → write liveness update to the registry DB.
339
+ // Bug fix (#1): DB rows are keyed on tool_use_id (e.g. "toolu_…") but the
340
+ // watcher only knows the JSONL filename stem (e.g. "a37ad763…"). We look up
341
+ // the row by jsonl_agent_id and bump using the actual tool_use_id PK.
342
+ // If the row doesn't exist yet (Phase 2 Pre hook hasn't fired), the UPDATE
343
+ // is a no-op — log and continue, don't INSERT here.
344
+ //
345
+ // Issue #501: also use the row to decide whether the sub-agent is
346
+ // foreground; if so, refresh the PARENT's `turn-active.json` mtime so the
347
+ // watchdog doesn't kill the parent during a long-running foreground
348
+ // sub-agent that the parent is awaiting. Background sub-agents are
349
+ // excluded — they have their own lifecycle and shouldn't mask
350
+ // parent-side hangs.
351
+ let isForeground = false
352
+ if (db != null) {
353
+ try {
354
+ const existing = db
355
+ .prepare('SELECT id, background FROM subagents WHERE jsonl_agent_id = ?')
356
+ .get(entry.agentId) as { id: string; background: number } | null
357
+ if (existing == null) {
358
+ log?.(`subagent-watcher: liveness skip ${entry.agentId} — row not in DB yet (Phase 2 Pre hook pending)`)
359
+ } else {
360
+ bumpSubagentActivity(db, { id: existing.id, ts: now })
361
+ isForeground = existing.background === 0
362
+ }
363
+ } catch (dbErr) {
364
+ log?.(`subagent-watcher: liveness write error ${entry.agentId}: ${(dbErr as Error).message}`)
365
+ }
366
+ }
367
+
368
+ // Issue #501 fix: foreground sub-agent activity refreshes the parent's
369
+ // turn-active marker. Without this, a foreground sub-agent doing pure
370
+ // computation or waiting on a slow API for >300s would let the marker
371
+ // age past TURN_HANG_SECS, and the watchdog would kill the parent even
372
+ // though real work is happening. The watchdog's multi-signal progress
373
+ // gate (PR #557) already protects most cases via JSONL liveness, but a
374
+ // sub-agent that goes silent for the threshold window is the one
375
+ // remaining gap this fix closes.
376
+ if (isForeground && parentStateDir) {
377
+ try {
378
+ touchTurnActiveMarker(parentStateDir)
379
+ } catch (touchErr) {
380
+ log?.(`subagent-watcher: parent marker touch error ${entry.agentId}: ${(touchErr as Error).message}`)
381
+ }
382
+ }
383
+
384
+ const text = tail.pendingPartial + buf.toString('utf-8')
385
+ const lines = text.split('\n')
386
+ tail.pendingPartial = lines.pop() ?? ''
387
+
388
+ const startState = { hasEmittedStart: tail.hasEmittedStart }
389
+ for (const line of lines) {
390
+ if (!line) continue
391
+ const events = projectSubagentLine(line, entry.agentId, startState)
392
+ for (const ev of events) {
393
+ entry.lastActivityAt = now
394
+ if (ev.kind === 'sub_agent_tool_use') {
395
+ entry.toolCount++
396
+ // P0 of #662: surface the most recent tool name + sanitised
397
+ // arg so the driver's fleet-state shadow can render the
398
+ // last-tool column on the v2 status card. Sanitiser lives in
399
+ // fleet-state.ts to keep the watcher dependency surface small.
400
+ entry.lastTool = {
401
+ name: ev.toolName,
402
+ sanitisedArg: sanitiseToolArg(ev.toolName, ev.input ?? {}),
403
+ }
404
+ } else if (ev.kind === 'sub_agent_text') {
405
+ // Do NOT overwrite description with narrative text — description is
406
+ // set at dispatch time (from the parent Agent/Task tool_use input)
407
+ // and must remain stable. Overwriting it with the sub-agent's first
408
+ // narrative line caused a race-condition-dependent display (issue #352).
409
+ entry.lastSummaryLine = ev.text.split('\n')[0].trim().slice(0, 120)
410
+ } else if (ev.kind === 'sub_agent_turn_end') {
411
+ if (entry.state === 'running') {
412
+ entry.state = 'done'
413
+ // Bug 2 fix (#333): mark the DB row completed via watcher's turn_end
414
+ // observation. This is the authoritative completion signal for
415
+ // background agents (whose PostToolUse fires on "launched" not "done").
416
+ // For foreground agents PostToolUse may have already marked the row —
417
+ // recordSubagentEnd is idempotent so the second write is a safe no-op.
418
+ if (db != null) {
419
+ try {
420
+ const rowRef = db
421
+ .prepare('SELECT id FROM subagents WHERE jsonl_agent_id = ?')
422
+ .get(entry.agentId) as { id: string } | null
423
+ if (rowRef != null) {
424
+ recordSubagentEnd(db, {
425
+ id: rowRef.id,
426
+ endedAt: now,
427
+ status: 'completed',
428
+ })
429
+ }
430
+ } catch (dbErr) {
431
+ log?.(`subagent-watcher: turn_end DB write error ${entry.agentId}: ${(dbErr as Error).message}`)
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+ }
438
+ tail.hasEmittedStart = startState.hasEmittedStart
439
+ } catch (err) {
440
+ log?.(`subagent-watcher: read error ${entry.agentId}: ${(err as Error).message}`)
441
+ }
442
+ }
443
+
444
+ // ─── Main watcher factory ─────────────────────────────────────────────────
445
+
446
+ export function startSubagentWatcher(config: SubagentWatcherConfig): SubagentWatcherHandle {
447
+ const agentDir = config.agentDir
448
+ const stallThresholdMs = config.stallThresholdMs ?? DEFAULT_STALL_THRESHOLD_MS
449
+ const reaperTtlMs = config.reaperTtlMs ?? DEFAULT_REAPER_TTL_MS
450
+ const reaperIntervalMs = config.reaperIntervalMs ?? DEFAULT_REAPER_INTERVAL_MS
451
+ const rescanMs = config.rescanMs ?? DEFAULT_RESCAN_MS
452
+ const log = config.log
453
+ const db = config.db ?? null
454
+ const parentStateDir = config.parentStateDir ?? null
455
+ const nowFn = config.now ?? (() => Date.now())
456
+
457
+ const setI = config.setInterval ?? ((fn, ms) => {
458
+ const h = setInterval(fn, ms)
459
+ return { ref: h }
460
+ })
461
+ const clearI = config.clearInterval ?? ((ref) => {
462
+ clearInterval((ref as { ref: ReturnType<typeof setInterval> }).ref)
463
+ })
464
+ const setT = config.setTimeout ?? ((fn, ms) => {
465
+ const h = setTimeout(fn, ms)
466
+ return { ref: h }
467
+ })
468
+ const clearT = config.clearTimeout ?? ((ref) => {
469
+ clearTimeout((ref as { ref: ReturnType<typeof setTimeout> }).ref)
470
+ })
471
+
472
+ // fs DI: tests pass a mock; production uses the real node:fs functions.
473
+ const fs = config.fs ?? {
474
+ existsSync,
475
+ readdirSync,
476
+ statSync,
477
+ openSync,
478
+ closeSync,
479
+ readSync,
480
+ watch,
481
+ }
482
+
483
+ // Registry: agentId → WorkerEntry
484
+ const registry = new Map<string, WorkerEntry>()
485
+ // Per-agent tail state
486
+ const tails = new Map<string, SubTail>()
487
+ // Dir-level FSWatcher for the subagents/ directory
488
+ const dirWatchers = new Map<string, FSWatcher>()
489
+ // Known subagent files: filePath → true
490
+ const knownFiles = new Set<string>()
491
+ // Pending deferred-cleanups for terminal-state sub-agents. Keyed by
492
+ // agentId so a re-transition (shouldn't happen, but defensively) or
493
+ // a stop() call can cancel pending timers cleanly. See MEM1 fix.
494
+ const pendingCloses = new Map<string, { ref: unknown }>()
495
+ /**
496
+ * Files that existed before the watcher started (boot-time snapshot).
497
+ * The `historical` flag on each entry suppresses two notification paths:
498
+ * - Stall detection (see `checkStalls` — historical entries can't stall
499
+ * because they predate the watcher session).
500
+ * - Past-completion replay: if a historical file was already `done` at
501
+ * boot, `completionNotified` is set immediately so the eventual
502
+ * state-transition pass doesn't fire "Worker done" for work that
503
+ * finished before we started watching.
504
+ * Historical files that are still in-flight at boot DO fire completion
505
+ * when they eventually report done — that transition is meaningful.
506
+ */
507
+ const historicalFiles = new Set<string>()
508
+ /**
509
+ * True while the initial boot scan is running. During this window every
510
+ * newly discovered file is added to historicalFiles.
511
+ */
512
+ let bootScanInProgress = true
513
+
514
+ let stopped = false
515
+
516
+ // ─── Per-agent registration ─────────────────────────────────────────────
517
+
518
+ function registerAgent(filePath: string, agentId: string): void {
519
+ if (registry.has(agentId)) return
520
+ const n = nowFn()
521
+ const isHistorical = historicalFiles.has(filePath)
522
+ log?.(`subagent-watcher: registering agent ${agentId}${isHistorical ? ' (historical — pre-existing at boot)' : ''}`)
523
+
524
+ const entry: WorkerEntry = {
525
+ agentId,
526
+ filePath,
527
+ description: 'sub-agent',
528
+ state: 'running',
529
+ dispatchedAt: n,
530
+ lastActivityAt: n,
531
+ toolCount: 0,
532
+ stallNotified: false,
533
+ completionNotified: false,
534
+ lastSummaryLine: '',
535
+ lastTool: null,
536
+ historical: isHistorical,
537
+ }
538
+ registry.set(agentId, entry)
539
+
540
+ // Backfill jsonl_agent_id linkage. The PreToolUse hook inserts the row
541
+ // keyed on tool_use_id and doesn't know the JSONL stem yet (the JSONL
542
+ // doesn't exist when PreToolUse fires). We bridge that gap here: read
543
+ // the meta.json sibling Claude Code writes alongside the JSONL, match
544
+ // the (agentType, description) pair against the most-recent unmatched
545
+ // row in the registry, and link them by setting jsonl_agent_id.
546
+ if (db != null && !isHistorical) {
547
+ try {
548
+ backfillJsonlAgentId(db, filePath, agentId, log)
549
+ } catch (err) {
550
+ log?.(`subagent-watcher: backfill error for ${agentId}: ${(err as Error).message}`)
551
+ }
552
+ }
553
+
554
+ const tail: SubTail = {
555
+ cursor: 0, // read from start to capture description
556
+ pendingPartial: '',
557
+ hasEmittedStart: false,
558
+ watcher: null,
559
+ }
560
+ tails.set(agentId, tail)
561
+
562
+ // Initial read
563
+ readSubTail(entry, tail, n, (desc) => {
564
+ log?.(`subagent-watcher: description updated for ${agentId}: ${desc}`)
565
+ }, fs, log, db, parentStateDir)
566
+
567
+ // If the JSONL already contained a turn_end at registration time
568
+ // (file written-then-watched), fire the state-transition + completion
569
+ // notification now. Otherwise the FSWatcher callback handles it on
570
+ // subsequent writes.
571
+ //
572
+ // Historical files that are already done at startup do NOT get a
573
+ // completion notification either — they finished before this session.
574
+ // Only transitions that happen AFTER startup (e.g. a pre-existing
575
+ // in-flight agent that finishes while we're watching) fire.
576
+ if (isHistorical && entry.state === 'done') {
577
+ // Already finished before we started — mark as notified so we
578
+ // don't fire a spurious completion notification later, and
579
+ // schedule cleanup so the FSWatcher we just opened doesn't leak
580
+ // forever. See MEM1 fix.
581
+ entry.completionNotified = true
582
+ scheduleTerminalCleanup(agentId)
583
+ } else {
584
+ maybySendStateTransition(agentId)
585
+ }
586
+
587
+ // Set up FSWatcher
588
+ try {
589
+ tail.watcher = fs.watch(filePath, () => {
590
+ if (stopped) return
591
+ const entry = registry.get(agentId)
592
+ const t = tails.get(agentId)
593
+ if (!entry || !t) return
594
+ readSubTail(entry, t, nowFn(), (desc) => {
595
+ log?.(`subagent-watcher: description updated for ${agentId}: ${desc}`)
596
+ }, fs, log, db, parentStateDir)
597
+ maybySendStateTransition(agentId)
598
+ })
599
+ } catch (err) {
600
+ log?.(`subagent-watcher: fs.watch failed for ${agentId}: ${(err as Error).message}`)
601
+ }
602
+ }
603
+
604
+ // ─── State-transition notifications ─────────────────────────────────────
605
+
606
+ function maybySendStateTransition(agentId: string): void {
607
+ const entry = registry.get(agentId)
608
+ if (!entry) return
609
+
610
+ if (entry.state === 'done' && !entry.completionNotified) {
611
+ entry.completionNotified = true
612
+ const desc = escapeHtml(truncate(entry.description, 80))
613
+ const summary = entry.lastSummaryLine
614
+ ? ` — ${escapeHtml(truncate(entry.lastSummaryLine, 120))}`
615
+ : ''
616
+ const tools = entry.toolCount > 0 ? ` (${entry.toolCount} tools)` : ''
617
+ try {
618
+ config.sendNotification(`✓ Worker done: ${desc}${tools}${summary}`)
619
+ } catch (err) {
620
+ log?.(`subagent-watcher: completion notification error: ${(err as Error).message}`)
621
+ }
622
+ scheduleTerminalCleanup(agentId)
623
+ }
624
+ // Defensive: if state ever flips to 'failed' (currently no caller
625
+ // sets this, but the type allows it), still clean up the FSWatcher.
626
+ if (entry.state === 'failed') {
627
+ scheduleTerminalCleanup(agentId)
628
+ }
629
+ }
630
+
631
+ // ─── Per-agent cleanup ──────────────────────────────────────────────────
632
+
633
+ /**
634
+ * Schedule a deferred close of the per-subagent FSWatcher + Map
635
+ * entries `TERMINAL_CLEANUP_GRACE_MS` after the sub-agent transitions
636
+ * to terminal state. Idempotent — repeated calls for the same agent
637
+ * cancel the previous timer and reset the grace window.
638
+ */
639
+ function scheduleTerminalCleanup(agentId: string): void {
640
+ if (stopped) return
641
+ const existing = pendingCloses.get(agentId)
642
+ if (existing) {
643
+ clearT(existing)
644
+ }
645
+ const handle = setT(() => {
646
+ pendingCloses.delete(agentId)
647
+ cleanupTerminalAgent(agentId)
648
+ }, TERMINAL_CLEANUP_GRACE_MS)
649
+ pendingCloses.set(agentId, handle)
650
+ }
651
+
652
+ /**
653
+ * Close the FSWatcher and drop Map entries for a terminal sub-agent.
654
+ * Safe to call multiple times: each Map operation is a no-op for an
655
+ * already-deleted key.
656
+ */
657
+ function cleanupTerminalAgent(agentId: string): void {
658
+ const tail = tails.get(agentId)
659
+ if (tail?.watcher) {
660
+ try { tail.watcher.close() } catch { /* ignore */ }
661
+ tail.watcher = null
662
+ }
663
+ tails.delete(agentId)
664
+ const entry = registry.get(agentId)
665
+ if (entry?.filePath) {
666
+ knownFiles.delete(entry.filePath)
667
+ }
668
+ registry.delete(agentId)
669
+ log?.(`subagent-watcher: cleaned up terminal agent ${agentId}`)
670
+ }
671
+
672
+ // ─── Stall detection ────────────────────────────────────────────────────
673
+
674
+ function checkStalls(): void {
675
+ const n = nowFn()
676
+ for (const entry of registry.values()) {
677
+ if (entry.state !== 'running') continue
678
+ if (entry.historical) continue
679
+ if (entry.stallNotified) continue
680
+ const idleMs = n - entry.lastActivityAt
681
+ if (idleMs >= stallThresholdMs) {
682
+ entry.stallNotified = true
683
+ const desc = escapeHtml(truncate(entry.description, 80))
684
+ const idleSec = Math.floor(idleMs / 1000)
685
+ log?.(`subagent-watcher: stall detected for ${entry.agentId} (idle ${idleSec}s): ${desc}`)
686
+ // Bug 3 fix (#333): persist the stall into the registry DB.
687
+ // Look up the row by jsonl_agent_id to get the tool_use_id PK.
688
+ if (db != null) {
689
+ try {
690
+ const rowRef = db
691
+ .prepare('SELECT id FROM subagents WHERE jsonl_agent_id = ?')
692
+ .get(entry.agentId) as { id: string } | null
693
+ if (rowRef != null) {
694
+ recordSubagentStall(db, { id: rowRef.id, stalledAt: n })
695
+ }
696
+ } catch (dbErr) {
697
+ log?.(`subagent-watcher: stall DB write error ${entry.agentId}: ${(dbErr as Error).message}`)
698
+ }
699
+ }
700
+ // Option C (#393): push the stall into the progress-card driver so
701
+ // the pinned card re-renders with a ⚠️ stall indicator. This fires
702
+ // even when the bridge has disconnected (dispose preserved the chat
703
+ // state for pendingCompletion chats).
704
+ if (config.onStall != null) {
705
+ try {
706
+ config.onStall(entry.agentId, idleMs, entry.description)
707
+ } catch (cbErr) {
708
+ log?.(`subagent-watcher: onStall callback error ${entry.agentId}: ${(cbErr as Error).message}`)
709
+ }
710
+ }
711
+ }
712
+ }
713
+ }
714
+
715
+ // ─── Subagents dir scanner ───────────────────────────────────────────────
716
+
717
+ /**
718
+ * The subagents directory for a given session lives at:
719
+ * <agentDir>/.claude/projects/<sanitized-cwd>/<sessionId>/subagents/
720
+ *
721
+ * We walk: <agentDir>/.claude/projects/ → each project dir → each session dir
722
+ * → subagents/ → agent-*.jsonl
723
+ */
724
+ function rescanSubagentDirs(): void {
725
+ if (stopped) return
726
+ const claudeHome = join(agentDir, '.claude')
727
+ const projectsRoot = join(claudeHome, 'projects')
728
+ if (!fs.existsSync(projectsRoot)) return
729
+
730
+ let projectDirs: string[]
731
+ try {
732
+ projectDirs = fs.readdirSync(projectsRoot) as string[]
733
+ } catch { return }
734
+
735
+ for (const pDir of projectDirs) {
736
+ const projectPath = join(projectsRoot, pDir)
737
+ let sessionDirs: string[]
738
+ try {
739
+ sessionDirs = fs.readdirSync(projectPath) as string[]
740
+ } catch { continue }
741
+
742
+ for (const sDir of sessionDirs) {
743
+ // Session dirs are UUID-like; skip known non-session entries
744
+ if (sDir.endsWith('.jsonl')) continue
745
+ const subagentsPath = join(projectPath, sDir, 'subagents')
746
+ if (!fs.existsSync(subagentsPath)) continue
747
+
748
+ // Watch the subagents dir for new files if not already watching
749
+ if (!dirWatchers.has(subagentsPath)) {
750
+ try {
751
+ const w = fs.watch(subagentsPath, (_event, filename) => {
752
+ if (!filename || !filename.toString().startsWith('agent-') || !filename.toString().endsWith('.jsonl')) return
753
+ const filePath = join(subagentsPath, filename.toString())
754
+ if (!knownFiles.has(filePath)) {
755
+ scanSubagentsDir(subagentsPath)
756
+ }
757
+ })
758
+ dirWatchers.set(subagentsPath, w)
759
+ log?.(`subagent-watcher: watching dir ${subagentsPath}`)
760
+ } catch (err) {
761
+ log?.(`subagent-watcher: dir watch failed ${subagentsPath}: ${(err as Error).message}`)
762
+ }
763
+ }
764
+
765
+ // Scan existing files
766
+ scanSubagentsDir(subagentsPath)
767
+ }
768
+ }
769
+ }
770
+
771
+ function scanSubagentsDir(subagentsPath: string): void {
772
+ let entries: string[]
773
+ try {
774
+ entries = fs.readdirSync(subagentsPath) as string[]
775
+ } catch { return }
776
+
777
+ for (const e of entries) {
778
+ if (!e.startsWith('agent-') || !e.endsWith('.jsonl')) continue
779
+ const filePath = join(subagentsPath, e)
780
+ if (knownFiles.has(filePath)) continue
781
+ knownFiles.add(filePath)
782
+ // During the initial boot scan, mark every discovered file as
783
+ // historical so stall-detection and completion notifications are
784
+ // suppressed for pre-existing JSONLs (months of session history
785
+ // would otherwise flood the chat on every restart).
786
+ if (bootScanInProgress) {
787
+ historicalFiles.add(filePath)
788
+ }
789
+ const agentId = e.slice('agent-'.length, -'.jsonl'.length)
790
+ registerAgent(filePath, agentId)
791
+ }
792
+ }
793
+
794
+ // ─── Main poll loop ──────────────────────────────────────────────────────
795
+
796
+ function poll(): void {
797
+ if (stopped) return
798
+
799
+ // Rescan for new sub-agent dirs
800
+ rescanSubagentDirs()
801
+
802
+ // Defensive read for any running agents (in case fs.watch missed events)
803
+ const n = nowFn()
804
+ for (const [agentId, entry] of registry) {
805
+ if (entry.state !== 'running') continue
806
+ const tail = tails.get(agentId)
807
+ if (!tail) continue
808
+ readSubTail(entry, tail, n, (desc) => {
809
+ log?.(`subagent-watcher: description updated for ${agentId}: ${desc}`)
810
+ }, fs, log, db, parentStateDir)
811
+ maybySendStateTransition(agentId)
812
+ }
813
+
814
+ // Stall detection
815
+ checkStalls()
816
+ }
817
+
818
+ // Initial boot scan: discover pre-existing files and mark them historical
819
+ // so we don't replay stalls or past completions for past sessions.
820
+ rescanSubagentDirs()
821
+ bootScanInProgress = false
822
+
823
+ // ─── Reaper for stuck-running rows (issue #522) ─────────────────────────
824
+ // Background subagents whose JSONL was never linked to their registry row
825
+ // (backfill failed) are invisible to the normal stall + completion paths,
826
+ // both of which look up rows by `jsonl_agent_id`. Without this reaper they
827
+ // sit in `status='running'` forever. Run once at startup to clean up rows
828
+ // left by a previous gateway, then on a periodic timer.
829
+ function runReaper(): void {
830
+ if (db == null) return
831
+ try {
832
+ const result = reapStuckRunningRows(db, { ttlMs: reaperTtlMs, now: nowFn() })
833
+ if (result.reaped > 0) {
834
+ log?.(`subagent-watcher: reaper transitioned ${result.reaped} stuck-running row(s) to stalled (ttl=${Math.round(reaperTtlMs / 60_000)}min)`)
835
+ }
836
+ } catch (err) {
837
+ log?.(`subagent-watcher: reaper error: ${(err as Error).message}`)
838
+ }
839
+ }
840
+ runReaper()
841
+
842
+ // Register the poll interval BEFORE the reaper interval. Existing tests'
843
+ // harness `poll()` helper grabs `intervals[0]` and fires it, treating the
844
+ // first-registered interval as the poll loop. Keep the reaper second to
845
+ // preserve that contract.
846
+ const pollHandle = setI(poll, rescanMs)
847
+ const reaperHandle = setI(runReaper, reaperIntervalMs)
848
+
849
+ return {
850
+ stop(): void {
851
+ stopped = true
852
+ clearI(pollHandle)
853
+ clearI(reaperHandle)
854
+ // Cancel any pending deferred-cleanup timers — the unconditional
855
+ // close loop below covers their work and we don't want straggler
856
+ // setTimeout callbacks firing after the watcher is supposedly stopped.
857
+ for (const handle of pendingCloses.values()) {
858
+ clearT(handle)
859
+ }
860
+ pendingCloses.clear()
861
+ for (const w of dirWatchers.values()) {
862
+ try { w.close() } catch { /* ignore */ }
863
+ }
864
+ dirWatchers.clear()
865
+ for (const tail of tails.values()) {
866
+ if (tail.watcher) {
867
+ try { tail.watcher.close() } catch { /* ignore */ }
868
+ tail.watcher = null
869
+ }
870
+ }
871
+ tails.clear()
872
+ registry.clear()
873
+ knownFiles.clear()
874
+ },
875
+
876
+ getRegistry(): ReadonlyMap<string, WorkerEntry> {
877
+ return registry
878
+ },
879
+ }
880
+ }