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,884 @@
1
+ /**
2
+ * Tails Claude Code's per-session JSONL file in real time and emits
3
+ * structured turn-lifecycle events.
4
+ *
5
+ * Why this exists: Claude Code's `--channels` daemon mode does NOT support
6
+ * `--output-format stream-json`, so we can't get streaming events from
7
+ * stdout. But Claude Code DOES write a transcript file to disk under
8
+ * `$CLAUDE_CONFIG_DIR/projects/<sanitized-cwd>/<sessionId>.jsonl`, flushed
9
+ * every 100ms (verified from cli.js source). Each line is one event:
10
+ *
11
+ * - { type: "queue-operation", operation: "enqueue" | "dequeue", content }
12
+ * - { type: "user", message: { content: [{ type: "tool_result", tool_use_id }] }}
13
+ * - { type: "assistant", message: { content: [{ type: "tool_use", name, ... }, { type: "thinking" }, { type: "text", text }] }}
14
+ * - { type: "system", subtype: "turn_duration", durationMs }
15
+ *
16
+ * Per-token text deltas are NOT in this file — assistant messages are
17
+ * written whole, after the SDK call completes. So we get richer reaction
18
+ * states (thinking → tool_use → reply → done) but not character streaming.
19
+ *
20
+ * The cwd encoding mirrors Claude Code's `VX()` helper: every non-alphanumeric
21
+ * char in the original cwd becomes a `-`. We replicate that here so we can
22
+ * locate the projects dir without parsing TUI output or shelling out.
23
+ */
24
+
25
+ import {
26
+ closeSync,
27
+ existsSync,
28
+ openSync,
29
+ readdirSync,
30
+ readSync,
31
+ statSync,
32
+ watch,
33
+ type FSWatcher,
34
+ } from 'fs'
35
+ import { homedir } from 'os'
36
+ import { basename, join } from 'path'
37
+ import { isMultiAgentEnabled } from './progress-card.js'
38
+ import { classifyClaudeError, type OperatorEventKind } from './operator-events.js'
39
+
40
+ /** Match Claude Code's cli.js VX() function. */
41
+ export function sanitizeCwdToProjectName(cwd: string): string {
42
+ return cwd.replace(/[^a-zA-Z0-9]/g, '-')
43
+ }
44
+
45
+ /** Resolve the projects directory for a given cwd. */
46
+ export function getProjectsDirForCwd(
47
+ cwd: string = process.cwd(),
48
+ claudeHome: string = process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'),
49
+ ): string {
50
+ return join(claudeHome, 'projects', sanitizeCwdToProjectName(cwd))
51
+ }
52
+
53
+ /**
54
+ * Find the session file Claude Code is currently writing to. Returns the
55
+ * most recently modified .jsonl in the projects dir, or null if none yet
56
+ * exists. Re-call this periodically — Claude Code may rotate to a new
57
+ * session id mid-process (compaction, /clear).
58
+ */
59
+ export function findActiveSessionFile(projectsDir: string): string | null {
60
+ if (!existsSync(projectsDir)) return null
61
+ let entries: string[]
62
+ try {
63
+ entries = readdirSync(projectsDir)
64
+ } catch {
65
+ return null
66
+ }
67
+ let bestPath: string | null = null
68
+ let bestMtime = 0
69
+ for (const e of entries) {
70
+ if (!e.endsWith('.jsonl')) continue
71
+ const p = join(projectsDir, e)
72
+ try {
73
+ const s = statSync(p)
74
+ if (s.mtimeMs > bestMtime) {
75
+ bestMtime = s.mtimeMs
76
+ bestPath = p
77
+ }
78
+ } catch { /* ignore */ }
79
+ }
80
+ return bestPath
81
+ }
82
+
83
+ // ─── Event types we project up to consumers ─────────────────────────────────
84
+
85
+ export type SessionEvent =
86
+ | { kind: 'enqueue'; chatId: string | null; messageId: string | null; threadId: string | null; rawContent: string; isSync?: boolean }
87
+ | { kind: 'dequeue' }
88
+ | { kind: 'thinking' }
89
+ | { kind: 'tool_use'; toolName: string; toolUseId?: string | null; input?: Record<string, unknown> }
90
+ | { kind: 'text'; text: string }
91
+ | { kind: 'tool_result'; toolUseId: string; toolName: string | null; isError?: boolean; errorText?: string }
92
+ | { kind: 'turn_end'; durationMs: number }
93
+ // Multi-agent: sub-agent-scoped events. agentId is the sub-agent JSONL
94
+ // filename stem (e.g. "aac6f1…"). Routed through the same ingest path
95
+ // as parent events; the reducer fans them out to per-sub-agent state.
96
+ | { kind: 'sub_agent_started'; agentId: string; firstPromptText: string; subagentType?: string }
97
+ | { kind: 'sub_agent_tool_use'; agentId: string; toolUseId: string | null; toolName: string; input?: Record<string, unknown> }
98
+ | { kind: 'sub_agent_text'; agentId: string; text: string }
99
+ | { kind: 'sub_agent_narrative'; agentId: string; text: string }
100
+ | { kind: 'sub_agent_tool_result'; agentId: string; toolUseId: string; isError?: boolean; errorText?: string }
101
+ | { kind: 'sub_agent_turn_end'; agentId: string }
102
+ | { kind: 'sub_agent_nested_spawn'; agentId: string }
103
+ /**
104
+ * Emitted when a sub-agent JSONL has >= CAP_TOOL_USE_THRESHOLD tool_use
105
+ * records but no terminal record (no `type:result`, `subtype:end`, or
106
+ * `type:final`). This indicates the sub-agent was killed mid-flight
107
+ * (parent restart, watchdog SIGTERM, etc.) before writing its completion.
108
+ * The progress-card driver transitions the fleet member to `capped` state
109
+ * so the card surface shows a terminal "capped" row instead of hanging
110
+ * "running" forever.
111
+ */
112
+ | { kind: 'sub_agent_capped'; agentId: string; toolUseCount: number }
113
+
114
+ /**
115
+ * Parse the inbound channel XML wrapper to pull out chat_id, message_id,
116
+ * and message_thread_id. The MCP plugin produces this XML on every
117
+ * inbound notification, so it's reliably present in queue-operation enqueue.
118
+ */
119
+ function parseChannelMeta(content: string): {
120
+ chatId: string | null
121
+ messageId: string | null
122
+ threadId: string | null
123
+ } {
124
+ // Look for `chat_id="..."` etc in the channel XML tag
125
+ const grab = (key: string): string | null => {
126
+ const m = content.match(new RegExp(`${key}="([^"]+)"`))
127
+ return m ? m[1] : null
128
+ }
129
+ return {
130
+ chatId: grab('chat_id'),
131
+ messageId: grab('message_id'),
132
+ threadId: grab('message_thread_id'),
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Hard cap on a single JSONL line before we parse it. Transcript entries
138
+ * can embed large tool outputs; a pathological 100 MB line would OOM the
139
+ * plugin on parse. 2 MB is comfortably above any realistic Claude output
140
+ * chunk and keeps memory predictable under a corrupted or malicious file.
141
+ */
142
+ const MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024
143
+
144
+ /** Max chars we capture from a tool error for pattern matching. */
145
+ const MAX_ERROR_TEXT_CHARS = 500
146
+
147
+ /**
148
+ * Extract a plain-text representation of tool_result `content` for error
149
+ * classification. The field can be:
150
+ * - a string (simple text)
151
+ * - an array of Anthropic content blocks (e.g. [{type:'text', text:'…'}])
152
+ * Returns the first MAX_ERROR_TEXT_CHARS characters — enough for pattern
153
+ * matching while keeping SessionEvent objects lean.
154
+ */
155
+ function extractToolResultErrorText(content: unknown): string {
156
+ if (typeof content === 'string') {
157
+ return content.slice(0, MAX_ERROR_TEXT_CHARS)
158
+ }
159
+ if (Array.isArray(content)) {
160
+ const parts: string[] = []
161
+ for (const block of content) {
162
+ if (typeof block === 'object' && block != null) {
163
+ const b = block as Record<string, unknown>
164
+ if (b.type === 'text' && typeof b.text === 'string') {
165
+ parts.push(b.text)
166
+ }
167
+ }
168
+ }
169
+ return parts.join('\n').slice(0, MAX_ERROR_TEXT_CHARS)
170
+ }
171
+ return ''
172
+ }
173
+
174
+ /**
175
+ * Project a single transcript line into a SessionEvent (or null if it's
176
+ * uninteresting noise). Caller is responsible for the JSON parse — if a
177
+ * line is not valid JSON we skip it.
178
+ */
179
+ export function projectTranscriptLine(line: string): SessionEvent[] {
180
+ if (line.length > MAX_JSONL_LINE_BYTES) return []
181
+ let obj: Record<string, unknown>
182
+ try {
183
+ obj = JSON.parse(line)
184
+ } catch {
185
+ return []
186
+ }
187
+ const type = obj.type as string | undefined
188
+ if (!type) return []
189
+
190
+ // queue-operation: inbound message lifecycle
191
+ if (type === 'queue-operation') {
192
+ const op = obj.operation as string | undefined
193
+ if (op === 'enqueue') {
194
+ const content = (obj.content as string | undefined) ?? ''
195
+ const { chatId, messageId, threadId } = parseChannelMeta(content)
196
+ return [{ kind: 'enqueue', chatId, messageId, threadId, rawContent: content }]
197
+ }
198
+ if (op === 'dequeue') {
199
+ return [{ kind: 'dequeue' }]
200
+ }
201
+ return []
202
+ }
203
+
204
+ // assistant: turn output (thinking, text, tool_use)
205
+ if (type === 'assistant') {
206
+ const message = obj.message as Record<string, unknown> | undefined
207
+ const content = message?.content as Array<Record<string, unknown>> | undefined
208
+ if (!Array.isArray(content)) return []
209
+ const events: SessionEvent[] = []
210
+ for (const c of content) {
211
+ const ct = c.type as string | undefined
212
+ if (ct === 'thinking') {
213
+ events.push({ kind: 'thinking' })
214
+ } else if (ct === 'tool_use') {
215
+ const input = c.input as Record<string, unknown> | undefined
216
+ events.push({
217
+ kind: 'tool_use',
218
+ toolName: (c.name as string | undefined) ?? '',
219
+ // Claude Code content blocks carry a stable `id` for each
220
+ // tool_use (e.g. "toolu_01ABC..."). Surfacing it here lets
221
+ // the progress-card reducer pair tool_result events by id
222
+ // instead of by running-item order, which is the only
223
+ // correct pairing when the model emits parallel tool_use
224
+ // calls within a single assistant message.
225
+ toolUseId: (c.id as string | undefined) ?? null,
226
+ input: input && typeof input === 'object' ? input : undefined,
227
+ })
228
+ } else if (ct === 'text') {
229
+ const text = (c.text as string | undefined) ?? ''
230
+ events.push({ kind: 'text', text })
231
+ }
232
+ }
233
+ return events
234
+ }
235
+
236
+ // user: contains tool_results
237
+ if (type === 'user') {
238
+ const message = obj.message as Record<string, unknown> | undefined
239
+ const content = message?.content as Array<Record<string, unknown>> | undefined
240
+ if (!Array.isArray(content)) return []
241
+ const events: SessionEvent[] = []
242
+ for (const c of content) {
243
+ if (c.type === 'tool_result') {
244
+ const isError = c.is_error === true ? true : undefined
245
+ events.push({
246
+ kind: 'tool_result',
247
+ toolUseId: (c.tool_use_id as string | undefined) ?? '',
248
+ toolName: null,
249
+ isError,
250
+ errorText: isError ? extractToolResultErrorText(c.content) : undefined,
251
+ })
252
+ }
253
+ }
254
+ return events
255
+ }
256
+
257
+ // system turn_duration: marks the end of a turn (after the model has
258
+ // produced its final response — useful as a backstop for "done")
259
+ if (type === 'system' && obj.subtype === 'turn_duration') {
260
+ return [
261
+ { kind: 'turn_end', durationMs: (obj.durationMs as number | undefined) ?? 0 },
262
+ ]
263
+ }
264
+
265
+ return []
266
+ }
267
+
268
+ /**
269
+ * Project a single line from a sub-agent JSONL into SessionEvent(s).
270
+ *
271
+ * Sub-agent JSONLs (under `<sessionId>/subagents/agent-<agentId>.jsonl`)
272
+ * use the same line shapes as the parent transcript but with `isSidechain: true`
273
+ * and an `agentId` field on every line. The first `type=user` message in
274
+ * the file holds the full prompt text the parent passed in via the
275
+ * `Agent` tool — that's our correlation key.
276
+ *
277
+ * Caller passes the `agentId` extracted from the filename and a stateful
278
+ * `hasEmittedStart` flag (one per file) so the very first user message
279
+ * fires `sub_agent_started` exactly once. Subsequent user messages carry
280
+ * tool_results.
281
+ *
282
+ * Sub-agents that themselves spawn more Agent/Task calls fire a
283
+ * `sub_agent_nested_spawn` event so the parent sub-agent line can render
284
+ * `(spawned N)`. We do NOT expose nested sub-agent activity as top-level
285
+ * rows — the design doc explicitly punts on recursion (§5.5).
286
+ */
287
+ export function projectSubagentLine(
288
+ line: string,
289
+ agentId: string,
290
+ state: { hasEmittedStart: boolean },
291
+ ): SessionEvent[] {
292
+ let obj: Record<string, unknown>
293
+ try {
294
+ obj = JSON.parse(line)
295
+ } catch {
296
+ return []
297
+ }
298
+ const type = obj.type as string | undefined
299
+ if (!type) return []
300
+
301
+ if (type === 'user') {
302
+ const message = obj.message as Record<string, unknown> | undefined
303
+ const content = message?.content
304
+ // First user message: the prompt body. Claude Code writes it as a
305
+ // string for the kickoff message, then as content arrays of
306
+ // tool_results for subsequent user messages.
307
+ if (!state.hasEmittedStart) {
308
+ state.hasEmittedStart = true
309
+ let promptText = ''
310
+ if (typeof content === 'string') {
311
+ promptText = content
312
+ } else if (Array.isArray(content)) {
313
+ // Some shapes wrap the prompt in a [{type: 'text', text: '…'}]
314
+ // block. Handle defensively.
315
+ for (const c of content) {
316
+ if (typeof c === 'object' && c != null && (c as Record<string, unknown>).type === 'text') {
317
+ promptText = String((c as Record<string, unknown>).text ?? '')
318
+ break
319
+ }
320
+ }
321
+ }
322
+ return [{ kind: 'sub_agent_started', agentId, firstPromptText: promptText }]
323
+ }
324
+ // Subsequent user messages = tool_results
325
+ if (!Array.isArray(content)) return []
326
+ const events: SessionEvent[] = []
327
+ for (const c of content) {
328
+ if (typeof c !== 'object' || c == null) continue
329
+ const cc = c as Record<string, unknown>
330
+ if (cc.type === 'tool_result') {
331
+ const isError = cc.is_error === true ? true : undefined
332
+ events.push({
333
+ kind: 'sub_agent_tool_result',
334
+ agentId,
335
+ toolUseId: (cc.tool_use_id as string | undefined) ?? '',
336
+ isError,
337
+ errorText: isError ? extractToolResultErrorText(cc.content) : undefined,
338
+ })
339
+ }
340
+ }
341
+ return events
342
+ }
343
+
344
+ if (type === 'assistant') {
345
+ const message = obj.message as Record<string, unknown> | undefined
346
+ const content = message?.content as Array<Record<string, unknown>> | undefined
347
+ if (!Array.isArray(content)) return []
348
+ const events: SessionEvent[] = []
349
+ for (const c of content) {
350
+ const ct = c.type as string | undefined
351
+ if (ct === 'tool_use') {
352
+ const name = (c.name as string | undefined) ?? ''
353
+ // Nested Agent/Task call inside a sub-agent: track ONLY as a
354
+ // nested_spawn count (renders as "(spawned N)" suffix on the
355
+ // parent sub-agent line). Per design §5.5 we do NOT expose
356
+ // sub-sub-agent activity as the parent sub-agent's currentTool —
357
+ // that would surface the sub-sub-agent's description and break
358
+ // the "no recursion in rendering" rule.
359
+ if (name === 'Agent' || name === 'Task') {
360
+ events.push({ kind: 'sub_agent_nested_spawn', agentId })
361
+ } else {
362
+ events.push({
363
+ kind: 'sub_agent_tool_use',
364
+ agentId,
365
+ toolUseId: (c.id as string | undefined) ?? null,
366
+ toolName: name,
367
+ input: (c.input as Record<string, unknown> | undefined) ?? undefined,
368
+ })
369
+ }
370
+ } else if (ct === 'text') {
371
+ // Surface the sub-agent's natural preamble text so the
372
+ // progress-card reducer can pair it with the next
373
+ // sub_agent_tool_use — same UX as the parent's preamble→tool_use
374
+ // pairing (see 3ad8436). Order matters: text and tool_use blocks
375
+ // in the SAME assistant message must be emitted in source order
376
+ // so the reducer consumes the preamble on the immediately-next
377
+ // tool_use and sibling tool_uses fall back to filename/pattern.
378
+ const text = (c.text as string | undefined) ?? ''
379
+ events.push({ kind: 'sub_agent_text', agentId, text })
380
+ }
381
+ }
382
+ return events
383
+ }
384
+
385
+ if (type === 'system' && obj.subtype === 'turn_duration') {
386
+ return [{ kind: 'sub_agent_turn_end', agentId }]
387
+ }
388
+
389
+ return []
390
+ }
391
+
392
+ // ─── Error detection for operator events ──────────────────────────────────
393
+
394
+ /**
395
+ * Inspect a raw JSONL line for Anthropic API error shapes and return the
396
+ * classified kind + the raw error object if one is found.
397
+ *
398
+ * Claude Code can write several error-bearing line shapes:
399
+ * - { type: "api_error", error: { type: "...", message: "..." } }
400
+ * - { type: "error", error: { type: "...", message: "..." } }
401
+ * - Any line where obj.error is a non-null object with a recognized type
402
+ *
403
+ * Returns null when no actionable error is detected (routine lines).
404
+ * Never throws — delegates to classifyClaudeError's own safety guarantee.
405
+ */
406
+ export function detectErrorInTranscriptLine(
407
+ line: string,
408
+ ): { kind: OperatorEventKind; raw: unknown; detail: string } | null {
409
+ if (!line || line.length > 2 * 1024 * 1024) return null
410
+ let obj: Record<string, unknown>
411
+ try {
412
+ obj = JSON.parse(line)
413
+ } catch {
414
+ return null
415
+ }
416
+ if (typeof obj !== 'object' || obj == null) return null
417
+
418
+ const type = obj.type as string | undefined
419
+
420
+ // Explicit error line types from Claude Code JSONL
421
+ const isErrorLine = type === 'api_error' || type === 'error'
422
+
423
+ // Also detect lines where obj.error is a non-null object (embedded error)
424
+ const embeddedError =
425
+ typeof obj.error === 'object' && obj.error != null ? obj.error : null
426
+
427
+ if (!isErrorLine && !embeddedError) return null
428
+
429
+ const raw = embeddedError ?? obj
430
+
431
+ // For api_error/error wrapper lines, the nested error object carries the
432
+ // real error type (e.g. rate_limit_error). Classify the nested error when
433
+ // present; fall back to the full object for status-code-based fallback.
434
+ const kind = classifyClaudeError(embeddedError ?? obj)
435
+
436
+ // Detail: prefer message from nested error or top-level
437
+ const detail =
438
+ extractDetailMessage(embeddedError as Record<string, unknown> | null) ??
439
+ extractDetailMessage(obj) ??
440
+ String(type ?? '')
441
+
442
+ return { kind, raw, detail }
443
+ }
444
+
445
+ function extractDetailMessage(obj: Record<string, unknown> | null): string | null {
446
+ if (!obj) return null
447
+ const msg = obj.message
448
+ return typeof msg === 'string' && msg.length > 0 ? msg : null
449
+ }
450
+
451
+ // ─── The tail watcher ─────────────────────────────────────────────────────
452
+
453
+ /** Emitted to onOperatorEvent when the tail detects a Claude API error. */
454
+ export interface TailOperatorEvent {
455
+ kind: OperatorEventKind
456
+ detail: string
457
+ raw: unknown
458
+ }
459
+
460
+ export interface SessionTailConfig {
461
+ /** Working directory of the Claude Code process. Defaults to process.cwd(). */
462
+ cwd?: string
463
+ /** CLAUDE_CONFIG_DIR override. Defaults to env or ~/.claude. */
464
+ claudeHome?: string
465
+ /** How often to re-scan for a new active session file (ms). Default 500. */
466
+ rescanIntervalMs?: number
467
+ /** Optional logger. */
468
+ log?: (msg: string) => void
469
+ /** Called for each parsed event. */
470
+ onEvent: (event: SessionEvent) => void
471
+ /**
472
+ * Called when an Anthropic API error is detected in the JSONL transcript.
473
+ * Phase 4a: session-tail emits; the gateway subscription is wired in Phase 4b.
474
+ * TODO(Phase 4b): wire this to the gateway's emitOperatorEvent pipeline.
475
+ */
476
+ onOperatorEvent?: (event: TailOperatorEvent) => void
477
+ }
478
+
479
+ export interface SessionTailHandle {
480
+ stop(): void
481
+ /** Returns the current active file path, or null if none. */
482
+ getActiveFile(): string | null
483
+ }
484
+
485
+ /**
486
+ * Start tailing the active Claude Code session file. The tailer:
487
+ * 1. Polls the projects dir for the most recent .jsonl
488
+ * 2. Opens it, seeks to current end (only NEW events are reported), and
489
+ * watches for size changes via fs.watch() — falling back to a 100ms
490
+ * poll on systems where fs.watch is unreliable (network mounts, WSL).
491
+ * 3. On each size change, reads the appended bytes, splits on newlines,
492
+ * parses each line, projects to SessionEvents, fires onEvent.
493
+ * 4. If a NEWER session file appears, re-targets it (catches /clear and
494
+ * compaction-driven rotations).
495
+ */
496
+ export function startSessionTail(config: SessionTailConfig): SessionTailHandle {
497
+ const cwd = config.cwd ?? process.cwd()
498
+ const claudeHome = config.claudeHome ?? process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')
499
+ const projectsDir = getProjectsDirForCwd(cwd, claudeHome)
500
+ const rescanMs = config.rescanIntervalMs ?? 500
501
+ const log = config.log
502
+ const onEvent = config.onEvent
503
+ const onOperatorEvent = config.onOperatorEvent
504
+
505
+ log?.(`session-tail: projectsDir=${projectsDir}`)
506
+
507
+ let currentFile: string | null = null
508
+ let cursor = 0 // byte offset of next read
509
+ let watcher: FSWatcher | null = null
510
+ let pollTimer: ReturnType<typeof setInterval> | null = null
511
+ let stopped = false
512
+ let pendingPartial = '' // last read may end mid-line; stash for next read
513
+
514
+ // Per-file cursor + partial bookkeeping. This is the Bug 1 fix: when
515
+ // Claude Code's Agent/Task tool spawns a sub-agent, that sub-agent
516
+ // writes its OWN session JSONL which briefly becomes newest-mtime in
517
+ // the projects dir. Without per-file tracking, `findActiveSessionFile`
518
+ // flips to the sub-agent JSONL, `attachToFile` seeks to its end, and
519
+ // when the parent JSONL reclaims newest-mtime we'd seek to ITS end
520
+ // too — missing every event written while we were attached elsewhere
521
+ // (critical ones like tool_result and turn_end). Tracking cursors per
522
+ // file by absolute path lets us pick up exactly where we left off on
523
+ // re-attach.
524
+ const fileCursors = new Map<string, { cursor: number; pendingPartial: string }>()
525
+
526
+ function readNew(): void {
527
+ if (stopped || !currentFile) return
528
+ try {
529
+ const stat = statSync(currentFile)
530
+ if (stat.size < cursor) {
531
+ // File was truncated/replaced — reset cursor and clear any
532
+ // stored per-file state for this path.
533
+ cursor = 0
534
+ pendingPartial = ''
535
+ if (currentFile != null) fileCursors.delete(currentFile)
536
+ }
537
+ if (stat.size === cursor) return
538
+ const buf = Buffer.alloc(stat.size - cursor)
539
+ const fd = openSync(currentFile, 'r')
540
+ try {
541
+ readSync(fd, buf, 0, buf.length, cursor)
542
+ } finally {
543
+ closeSync(fd)
544
+ }
545
+ cursor = stat.size
546
+ const text = pendingPartial + buf.toString('utf-8')
547
+ // Last segment may be a partial line if the writer flushed mid-line
548
+ const lines = text.split('\n')
549
+ pendingPartial = lines.pop() ?? ''
550
+ for (const line of lines) {
551
+ if (!line) continue
552
+ const events = projectTranscriptLine(line)
553
+ for (const ev of events) {
554
+ try {
555
+ onEvent(ev)
556
+ } catch (err) {
557
+ log?.(`session-tail: onEvent threw: ${(err as Error).message}`)
558
+ }
559
+ }
560
+ // Operator-event detection: check for API error shapes in the line.
561
+ // This runs even when projectTranscriptLine returns [] (unknown types).
562
+ if (onOperatorEvent) {
563
+ try {
564
+ const errEvent = detectErrorInTranscriptLine(line)
565
+ if (errEvent) {
566
+ onOperatorEvent(errEvent)
567
+ }
568
+ } catch (err) {
569
+ log?.(`session-tail: onOperatorEvent threw: ${(err as Error).message}`)
570
+ }
571
+ }
572
+ }
573
+ } catch (err) {
574
+ log?.(`session-tail: read failed: ${(err as Error).message}`)
575
+ }
576
+ }
577
+
578
+ function attachToFile(file: string): void {
579
+ if (currentFile === file) return
580
+ // Save state for the file we're switching AWAY from, so that if we
581
+ // later re-attach (e.g. a sub-agent briefly led on mtime, now the
582
+ // parent leads again) we resume from exactly where we stopped.
583
+ if (currentFile != null) {
584
+ fileCursors.set(currentFile, { cursor, pendingPartial })
585
+ }
586
+ if (watcher) {
587
+ try { watcher.close() } catch { /* ignore */ }
588
+ watcher = null
589
+ }
590
+ currentFile = file
591
+ const prior = fileCursors.get(file)
592
+ if (prior != null) {
593
+ // Re-attach: pick up exactly where we left off so we don't skip
594
+ // events written while we were watching a different file.
595
+ cursor = prior.cursor
596
+ pendingPartial = prior.pendingPartial
597
+ log?.(`session-tail: re-attached to ${file} (cursor=${cursor}, restored)`)
598
+ } else {
599
+ // First attach to this file — seek to current end so we only see
600
+ // new events, not history.
601
+ pendingPartial = ''
602
+ try {
603
+ cursor = statSync(file).size
604
+ } catch {
605
+ cursor = 0
606
+ }
607
+ log?.(`session-tail: attached to ${file} (cursor=${cursor})`)
608
+ }
609
+ try {
610
+ watcher = watch(file, () => readNew())
611
+ } catch (err) {
612
+ log?.(`session-tail: fs.watch failed (${(err as Error).message}), polling instead`)
613
+ }
614
+ }
615
+
616
+ // ─── Sub-agent JSONL tailing (multi-agent path, gated by feature flag) ──
617
+ //
618
+ // Each sub-agent gets its own per-file tailer keyed by absolute path.
619
+ // We poll the `<sessionId>/subagents/` directory on every rescan (cheap,
620
+ // a few stat calls) so newly-created sub-agent JSONLs are picked up
621
+ // even when fs.watch on the parent dir is unreliable. Once attached,
622
+ // a per-file watch + cursor handles incremental reads exactly the way
623
+ // the parent tail does — and exactly the same per-file cursor map
624
+ // pattern from PR #25 protects against re-attach truncation.
625
+ const multiAgent = isMultiAgentEnabled()
626
+
627
+ /**
628
+ * Minimum tool_use count that — combined with a missing terminal record —
629
+ * classifies a sub-agent transcript as truncated/capped rather than merely
630
+ * in-flight. The triage report (#650) observed truncation at 31–294 tool
631
+ * uses; 30 is chosen as the lower bound to avoid false-positives on short
632
+ * sub-agents that are still legitimately running when first reaped.
633
+ */
634
+ const CAP_TOOL_USE_THRESHOLD = 30
635
+
636
+ interface SubTail {
637
+ agentId: string
638
+ file: string
639
+ cursor: number
640
+ pendingPartial: string
641
+ hasEmittedStart: boolean
642
+ watcher: FSWatcher | null
643
+ /**
644
+ * Last wall-clock time the file's byte count actually advanced.
645
+ * Used for idle-based FSWatcher cleanup — sub-agents that haven't
646
+ * written in IDLE_FSWATCH_TTL_MS get their watcher closed and the
647
+ * SubTail entry dropped. The rescan loop re-attaches if the file
648
+ * grows again. See MEM2 in the overnight forensic audit on #472.
649
+ */
650
+ lastActivityAt: number
651
+ /** Running count of tool_use records observed in this sub-agent's JSONL. */
652
+ toolUseCount: number
653
+ /** True once a terminal record (type:result / subtype:end / type:final) is seen. */
654
+ hasSeenTerminal: boolean
655
+ /** True once we have emitted sub_agent_capped for this sub-agent. */
656
+ cappedEmitted: boolean
657
+ }
658
+ const subTails = new Map<string, SubTail>() // keyed by absolute file path
659
+
660
+ /**
661
+ * Idle window before a sub-agent FSWatcher is considered safe to
662
+ * close. Sub-agents finish in seconds-to-minutes; 5 min is well
663
+ * past the 99th-percentile completion time and cheap on the rare
664
+ * very-long task (rescanSubagents picks the file back up on the
665
+ * next tick if it grows).
666
+ */
667
+ const IDLE_FSWATCH_TTL_MS = 5 * 60 * 1000
668
+
669
+ function readSub(t: SubTail): void {
670
+ if (stopped) return
671
+ try {
672
+ const stat = statSync(t.file)
673
+ if (stat.size < t.cursor) {
674
+ t.cursor = 0
675
+ t.pendingPartial = ''
676
+ }
677
+ if (stat.size === t.cursor) return
678
+ const buf = Buffer.alloc(stat.size - t.cursor)
679
+ const fd = openSync(t.file, 'r')
680
+ try {
681
+ readSync(fd, buf, 0, buf.length, t.cursor)
682
+ } finally {
683
+ closeSync(fd)
684
+ }
685
+ t.cursor = stat.size
686
+ t.lastActivityAt = Date.now()
687
+ const text = t.pendingPartial + buf.toString('utf-8')
688
+ const lines = text.split('\n')
689
+ t.pendingPartial = lines.pop() ?? ''
690
+ const startState = { hasEmittedStart: t.hasEmittedStart }
691
+ for (const line of lines) {
692
+ if (!line) continue
693
+ // Track terminal record presence: a sub-agent JSONL terminal line
694
+ // has type=result, subtype=end, or type=final. These indicate the
695
+ // harness wrote a proper completion record, so the sub-agent is NOT
696
+ // capped even if tool_use count is high.
697
+ if (!t.hasSeenTerminal) {
698
+ try {
699
+ const raw = JSON.parse(line) as Record<string, unknown>
700
+ if (
701
+ raw.type === 'result' ||
702
+ raw.type === 'final' ||
703
+ raw.type === 'error' ||
704
+ raw.type === 'cancel' ||
705
+ (raw.type === 'system' && raw.subtype === 'end') ||
706
+ raw.subtype === 'end'
707
+ ) {
708
+ t.hasSeenTerminal = true
709
+ }
710
+ } catch { /* ignore parse errors — projectSubagentLine will handle */ }
711
+ }
712
+ const events = projectSubagentLine(line, t.agentId, startState)
713
+ for (const ev of events) {
714
+ // Count tool_use events for capped-detection heuristic.
715
+ if (ev.kind === 'sub_agent_tool_use') {
716
+ t.toolUseCount++
717
+ }
718
+ // sub_agent_turn_end is a synthetic terminal — the parent saw a
719
+ // system:turn_duration line, meaning the harness completed normally.
720
+ if (ev.kind === 'sub_agent_turn_end') {
721
+ t.hasSeenTerminal = true
722
+ }
723
+ try {
724
+ onEvent(ev)
725
+ } catch (err) {
726
+ log?.(`session-tail: sub onEvent threw: ${(err as Error).message}`)
727
+ }
728
+ }
729
+ }
730
+ t.hasEmittedStart = startState.hasEmittedStart
731
+ } catch (err) {
732
+ log?.(`session-tail: sub read failed: ${(err as Error).message}`)
733
+ }
734
+ }
735
+
736
+ function attachSub(file: string, agentId: string): void {
737
+ if (subTails.has(file)) return
738
+ let cursor = 0
739
+ try {
740
+ cursor = statSync(file).size
741
+ } catch { /* ignore */ }
742
+ // Sub-agent JSONLs are typically created and immediately written; we
743
+ // start at byte 0 so we DON'T miss the first user-message line that
744
+ // carries the prompt text needed for correlation. This differs from
745
+ // the parent tail which seeks to end (parent has long history).
746
+ const t: SubTail = {
747
+ agentId,
748
+ file,
749
+ cursor: 0, // intentionally 0: read from start to capture prompt
750
+ pendingPartial: '',
751
+ hasEmittedStart: false,
752
+ watcher: null,
753
+ lastActivityAt: Date.now(),
754
+ toolUseCount: 0,
755
+ hasSeenTerminal: false,
756
+ cappedEmitted: false,
757
+ }
758
+ void cursor
759
+ try {
760
+ t.watcher = watch(file, () => readSub(t))
761
+ } catch (err) {
762
+ log?.(`session-tail: sub fs.watch failed (${(err as Error).message})`)
763
+ }
764
+ subTails.set(file, t)
765
+ log?.(`session-tail: attached sub ${agentId} (${file})`)
766
+ readSub(t)
767
+ }
768
+
769
+ /**
770
+ * Drop sub-tails whose underlying file hasn't grown in
771
+ * IDLE_FSWATCH_TTL_MS. Closes the FSWatcher (releasing the FD) and
772
+ * removes the entry from `subTails`. If the file later grows again
773
+ * — unusual but possible if a sub-agent resumes — `rescanSubagents`
774
+ * will re-attach on its next tick.
775
+ *
776
+ * Pre-MEM2 fix the per-file FSWatcher lived for the entire process
777
+ * lifetime. With the subagent-watcher (MEM1) ALSO holding a watcher
778
+ * on the same file, the FD bleed was doubled.
779
+ */
780
+ function reapIdleSubTails(): void {
781
+ if (subTails.size === 0) return
782
+ const cutoff = Date.now() - IDLE_FSWATCH_TTL_MS
783
+ for (const [file, t] of subTails) {
784
+ if (t.lastActivityAt < cutoff) {
785
+ // Before reaping: check whether this looks like a capped transcript.
786
+ // A sub-agent with >= CAP_TOOL_USE_THRESHOLD tool_uses and no terminal
787
+ // record was most likely killed mid-flight. Emit sub_agent_capped once
788
+ // so the progress-card driver can transition the fleet member to a
789
+ // terminal "capped" state rather than leaving it stuck at "running".
790
+ if (!t.hasSeenTerminal && !t.cappedEmitted && t.toolUseCount >= CAP_TOOL_USE_THRESHOLD) {
791
+ t.cappedEmitted = true
792
+ try {
793
+ onEvent({ kind: 'sub_agent_capped', agentId: t.agentId, toolUseCount: t.toolUseCount })
794
+ } catch (err) {
795
+ log?.(`session-tail: sub_agent_capped onEvent threw: ${(err as Error).message}`)
796
+ }
797
+ log?.(`session-tail: sub ${t.agentId} capped (${t.toolUseCount} tool_uses, no terminal record)`)
798
+ }
799
+ if (t.watcher) {
800
+ try { t.watcher.close() } catch { /* ignore */ }
801
+ t.watcher = null
802
+ }
803
+ subTails.delete(file)
804
+ log?.(`session-tail: reaped idle sub ${t.agentId} (${file})`)
805
+ }
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Sub-agent dir lives next to the parent JSONL: if the parent file is
811
+ * `<projectsDir>/<sessionId>.jsonl`, sub-agents live under
812
+ * `<projectsDir>/<sessionId>/subagents/agent-<agentId>.jsonl`.
813
+ *
814
+ * Claude Code 2.1.x has been observed to use this layout. If a future
815
+ * release renames `agent-*.jsonl`, the glob check below is the only
816
+ * place to update.
817
+ */
818
+ function rescanSubagents(): void {
819
+ if (!multiAgent) return
820
+ if (!currentFile) return
821
+ const sessionId = basename(currentFile, '.jsonl')
822
+ const subDir = join(projectsDir, sessionId, 'subagents')
823
+ if (!existsSync(subDir)) return
824
+ let entries: string[]
825
+ try {
826
+ entries = readdirSync(subDir)
827
+ } catch { return }
828
+ for (const e of entries) {
829
+ if (!e.startsWith('agent-') || !e.endsWith('.jsonl')) continue
830
+ const agentId = e.slice('agent-'.length, -'.jsonl'.length)
831
+ const file = join(subDir, e)
832
+ if (!subTails.has(file)) {
833
+ attachSub(file, agentId)
834
+ } else {
835
+ // Already attached — defensive read in case fs.watch missed.
836
+ readSub(subTails.get(file)!)
837
+ }
838
+ }
839
+ }
840
+
841
+ function rescan(): void {
842
+ if (stopped) return
843
+ const file = findActiveSessionFile(projectsDir)
844
+ if (!file) return
845
+ if (file !== currentFile) {
846
+ attachToFile(file)
847
+ }
848
+ // Always read in case fs.watch missed an event (common on WSL/network mounts)
849
+ readNew()
850
+ rescanSubagents()
851
+ // MEM2: reap subtails whose underlying JSONL has been idle for a
852
+ // while. The reap is guarded by IDLE_FSWATCH_TTL_MS (5 min by
853
+ // default) so steady-state workloads don't thrash.
854
+ reapIdleSubTails()
855
+ }
856
+
857
+ // Initial pass
858
+ rescan()
859
+ pollTimer = setInterval(rescan, rescanMs)
860
+
861
+ return {
862
+ stop(): void {
863
+ stopped = true
864
+ if (watcher) {
865
+ try { watcher.close() } catch { /* ignore */ }
866
+ watcher = null
867
+ }
868
+ for (const t of subTails.values()) {
869
+ if (t.watcher) {
870
+ try { t.watcher.close() } catch { /* ignore */ }
871
+ t.watcher = null
872
+ }
873
+ }
874
+ subTails.clear()
875
+ if (pollTimer) {
876
+ clearInterval(pollTimer)
877
+ pollTimer = null
878
+ }
879
+ },
880
+ getActiveFile(): string | null {
881
+ return currentFile
882
+ },
883
+ }
884
+ }