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.
- package/LICENSE +21 -0
- package/README.md +447 -0
- package/bin/autoaccept.exp +81 -0
- package/bin/boot-self-test.sh +149 -0
- package/bin/bridge-watchdog.sh +967 -0
- package/bin/handoff-briefing.sh +206 -0
- package/bin/run-hook.sh +228 -0
- package/bin/switchroom.ts +4 -0
- package/bin/timezone-hook.sh +67 -0
- package/bin/user-profile-refresh-hook.sh +38 -0
- package/bin/workspace-dynamic-hook.sh +142 -0
- package/bin/workspace-stable-hook.sh +57 -0
- package/dist/cli/autoaccept-poll.js +118 -0
- package/dist/cli/switchroom.js +48557 -0
- package/package.json +95 -0
- package/profiles/_base/settings.json.hbs +15 -0
- package/profiles/_base/start.sh.hbs +383 -0
- package/profiles/_shared/telegram-style.md.hbs +140 -0
- package/profiles/coding/CLAUDE.md.hbs +57 -0
- package/profiles/coding/skills/architecture/SKILL.md +70 -0
- package/profiles/coding/skills/code-review/SKILL.md +58 -0
- package/profiles/coding/workspace/SOUL.md.hbs +25 -0
- package/profiles/default/CLAUDE.md +238 -0
- package/profiles/default/CLAUDE.md.hbs +113 -0
- package/profiles/default/workspace/CLAUDE.md.hbs +126 -0
- package/profiles/default/workspace/HEARTBEAT.md.hbs +40 -0
- package/profiles/default/workspace/IDENTITY.md.hbs +32 -0
- package/profiles/default/workspace/MEMORY.md.hbs +29 -0
- package/profiles/default/workspace/SOUL.md.hbs +61 -0
- package/profiles/default/workspace/TOOLS.md.hbs +29 -0
- package/profiles/default/workspace/USER.md.hbs +52 -0
- package/profiles/default/workspace/memory/.gitkeep +0 -0
- package/profiles/executive-assistant/CLAUDE.md.hbs +51 -0
- package/profiles/executive-assistant/skills/daily-briefing/SKILL.md +55 -0
- package/profiles/executive-assistant/skills/meeting-prep/SKILL.md +58 -0
- package/profiles/executive-assistant/workspace/SOUL.md.hbs +25 -0
- package/profiles/health-coach/CLAUDE.md.hbs +45 -0
- package/profiles/health-coach/skills/check-in/SKILL.md +41 -0
- package/profiles/health-coach/skills/weekly-review/SKILL.md +53 -0
- package/profiles/health-coach/workspace/SOUL.md.hbs +25 -0
- package/skills/buildkite-agent-infrastructure/SKILL.md +302 -0
- package/skills/buildkite-agent-infrastructure/agents/openai.yaml +6 -0
- package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-agent-infrastructure/references/audit-logging.md +87 -0
- package/skills/buildkite-agent-infrastructure/references/graphql-mutations.md +690 -0
- package/skills/buildkite-agent-infrastructure/references/instance-shapes.md +38 -0
- package/skills/buildkite-agent-infrastructure/references/pipeline-templates.md +73 -0
- package/skills/buildkite-agent-infrastructure/references/self-hosted-agents.md +137 -0
- package/skills/buildkite-agent-infrastructure/references/sso-saml.md +92 -0
- package/skills/buildkite-agent-runtime/SKILL.md +476 -0
- package/skills/buildkite-agent-runtime/agents/openai.yaml +6 -0
- package/skills/buildkite-agent-runtime/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-agent-runtime/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-agent-runtime/references/flag-reference.md +417 -0
- package/skills/buildkite-agent-runtime/references/patterns-and-recipes.md +555 -0
- package/skills/buildkite-api/SKILL.md +285 -0
- package/skills/buildkite-api/agents/openai.yaml +6 -0
- package/skills/buildkite-api/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-api/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-api/references/graphql-reference.md +195 -0
- package/skills/buildkite-api/references/patterns.md +44 -0
- package/skills/buildkite-api/references/webhooks.md +161 -0
- package/skills/buildkite-cli/SKILL.md +379 -0
- package/skills/buildkite-cli/agents/openai.yaml +6 -0
- package/skills/buildkite-cli/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-cli/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-cli/references/command-reference.md +181 -0
- package/skills/buildkite-migration/SKILL.md +182 -0
- package/skills/buildkite-pipelines/SKILL.md +464 -0
- package/skills/buildkite-pipelines/agents/openai.yaml +6 -0
- package/skills/buildkite-pipelines/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-pipelines/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-pipelines/examples/basic-pipeline.yml +24 -0
- package/skills/buildkite-pipelines/examples/optimized-pipeline.yml +100 -0
- package/skills/buildkite-pipelines/references/advanced-patterns.md +286 -0
- package/skills/buildkite-pipelines/references/retry-and-error-codes.md +131 -0
- package/skills/buildkite-pipelines/references/step-types-reference.md +225 -0
- package/skills/buildkite-secure-delivery/SKILL.md +168 -0
- package/skills/buildkite-secure-delivery/agents/openai.yaml +6 -0
- package/skills/buildkite-secure-delivery/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-secure-delivery/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-secure-delivery/references/oidc-cloud-providers.md +83 -0
- package/skills/buildkite-secure-delivery/references/package-publishing.md +100 -0
- package/skills/buildkite-test-engine/SKILL.md +239 -0
- package/skills/buildkite-test-engine/agents/openai.yaml +6 -0
- package/skills/buildkite-test-engine/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-test-engine/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-test-engine/examples/bktec-splitting.yml +16 -0
- package/skills/buildkite-test-engine/examples/collector-pipeline.yml +11 -0
- package/skills/buildkite-test-engine/references/collectors.md +198 -0
- package/skills/buildkite-test-engine/references/splitting-examples.md +93 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +590 -0
- package/skills/docx/VENDORED.md +32 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +135 -0
- package/skills/docx/scripts/comment.py +318 -0
- package/skills/docx/scripts/office/helpers/__init__.py +0 -0
- package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/docx/scripts/office/pack.py +159 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/scripts/office/soffice.py +183 -0
- package/skills/docx/scripts/office/unpack.py +132 -0
- package/skills/docx/scripts/office/validate.py +111 -0
- package/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/base.py +847 -0
- package/skills/docx/scripts/office/validators/docx.py +446 -0
- package/skills/docx/scripts/office/validators/pptx.py +275 -0
- package/skills/docx/scripts/office/validators/redlining.py +247 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/file-bug/SKILL.md +129 -0
- package/skills/humanizer/LICENSE +21 -0
- package/skills/humanizer/SKILL.md +559 -0
- package/skills/humanizer/VENDORED.md +38 -0
- package/skills/humanizer-calibrate/SKILL.md +144 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +236 -0
- package/skills/mcp-builder/VENDORED.md +32 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +314 -0
- package/skills/pdf/VENDORED.md +32 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +232 -0
- package/skills/pptx/VENDORED.md +32 -0
- package/skills/pptx/editing.md +205 -0
- package/skills/pptx/pptxgenjs.md +420 -0
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +195 -0
- package/skills/pptx/scripts/clean.py +286 -0
- package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/pptx/scripts/office/pack.py +159 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/office/soffice.py +183 -0
- package/skills/pptx/scripts/office/unpack.py +132 -0
- package/skills/pptx/scripts/office/validate.py +111 -0
- package/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/skills/pptx/scripts/office/validators/base.py +847 -0
- package/skills/pptx/scripts/office/validators/docx.py +446 -0
- package/skills/pptx/scripts/office/validators/pptx.py +275 -0
- package/skills/pptx/scripts/office/validators/redlining.py +247 -0
- package/skills/pptx/scripts/thumbnail.py +289 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/VENDORED.md +32 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/switchroom-architecture/SKILL.md +60 -0
- package/skills/switchroom-architecture/cascade.md +112 -0
- package/skills/switchroom-architecture/sub-agents.md +87 -0
- package/skills/switchroom-architecture/telegram.md +94 -0
- package/skills/switchroom-cli/SKILL.md +274 -0
- package/skills/switchroom-health/SKILL.md +101 -0
- package/skills/switchroom-install/SKILL.md +116 -0
- package/skills/switchroom-manage/SKILL.md +90 -0
- package/skills/switchroom-status/SKILL.md +69 -0
- package/skills/switchroom-status/scripts/status.sh +69 -0
- package/skills/telegram-test-harness/SKILL.md +191 -0
- package/skills/token-helpers/SKILL.md +73 -0
- package/skills/token-helpers/scripts/google-cal-token.sh +62 -0
- package/skills/token-helpers/scripts/ms-graph-token.sh +70 -0
- package/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/VENDORED.md +32 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +292 -0
- package/skills/xlsx/VENDORED.md +32 -0
- package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/xlsx/scripts/office/pack.py +159 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/xlsx/scripts/office/soffice.py +183 -0
- package/skills/xlsx/scripts/office/unpack.py +132 -0
- package/skills/xlsx/scripts/office/validate.py +111 -0
- package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/skills/xlsx/scripts/office/validators/base.py +847 -0
- package/skills/xlsx/scripts/office/validators/docx.py +446 -0
- package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
- package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
- package/skills/xlsx/scripts/recalc.py +184 -0
- package/telegram-plugin/.claude-plugin/plugin.json +20 -0
- package/telegram-plugin/.mcp.json +14 -0
- package/telegram-plugin/LICENSE +21 -0
- package/telegram-plugin/README.md +352 -0
- package/telegram-plugin/active-pins-sweep.ts +204 -0
- package/telegram-plugin/active-pins.ts +146 -0
- package/telegram-plugin/active-reactions-sweep.ts +79 -0
- package/telegram-plugin/active-reactions.ts +134 -0
- package/telegram-plugin/admin-commands/dispatch.test.ts +149 -0
- package/telegram-plugin/admin-commands/index.ts +106 -0
- package/telegram-plugin/answer-stream.ts +565 -0
- package/telegram-plugin/ask-user.ts +179 -0
- package/telegram-plugin/attachment-path.ts +80 -0
- package/telegram-plugin/auth-code-redact.ts +83 -0
- package/telegram-plugin/auth-dashboard.ts +1104 -0
- package/telegram-plugin/auth-slot-parser.ts +497 -0
- package/telegram-plugin/auto-fallback-dispatcher.ts +68 -0
- package/telegram-plugin/auto-fallback.ts +348 -0
- package/telegram-plugin/bridge/bridge.ts +687 -0
- package/telegram-plugin/bridge/ipc-client.ts +326 -0
- package/telegram-plugin/bun.lock +218 -0
- package/telegram-plugin/card-format.ts +62 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +56 -0
- package/telegram-plugin/channel-envelope-safety.ts +56 -0
- package/telegram-plugin/chat-lock.ts +65 -0
- package/telegram-plugin/context-exhaustion.ts +38 -0
- package/telegram-plugin/credits-watch.ts +220 -0
- package/telegram-plugin/dist/bridge/bridge.js +24758 -0
- package/telegram-plugin/dist/foreman/foreman.js +30723 -0
- package/telegram-plugin/dist/gateway/gateway.js +46497 -0
- package/telegram-plugin/dist/server.js +24551 -0
- package/telegram-plugin/dm-command-gate.ts +56 -0
- package/telegram-plugin/docs/gateway-server-split.md +133 -0
- package/telegram-plugin/docs/multi-agent-card-design.md +847 -0
- package/telegram-plugin/docs/pinned-progress-card-reliability.md +144 -0
- package/telegram-plugin/docs/stream-json-daemon-mode.md +477 -0
- package/telegram-plugin/docs/waiting-ux-spec.md +233 -0
- package/telegram-plugin/draft-stream.ts +442 -0
- package/telegram-plugin/draft-transport.ts +72 -0
- package/telegram-plugin/first-paint.ts +246 -0
- package/telegram-plugin/fleet-state.ts +246 -0
- package/telegram-plugin/foreman/foreman-create-flow.ts +202 -0
- package/telegram-plugin/foreman/foreman-handlers.ts +493 -0
- package/telegram-plugin/foreman/foreman.ts +1130 -0
- package/telegram-plugin/foreman/setup-flow.ts +345 -0
- package/telegram-plugin/foreman/setup-state.ts +239 -0
- package/telegram-plugin/foreman/state.ts +203 -0
- package/telegram-plugin/format.ts +685 -0
- package/telegram-plugin/gateway/access-validator.test.ts +95 -0
- package/telegram-plugin/gateway/access-validator.ts +37 -0
- package/telegram-plugin/gateway/boot-card.ts +582 -0
- package/telegram-plugin/gateway/boot-probes.ts +863 -0
- package/telegram-plugin/gateway/boot-reason.ts +51 -0
- package/telegram-plugin/gateway/boot-sweep-filter.test.ts +54 -0
- package/telegram-plugin/gateway/boot-sweep-filter.ts +32 -0
- package/telegram-plugin/gateway/clean-shutdown-marker.ts +183 -0
- package/telegram-plugin/gateway/disconnect-flush.ts +109 -0
- package/telegram-plugin/gateway/gateway.ts +10202 -0
- package/telegram-plugin/gateway/inbound-coalesce.ts +147 -0
- package/telegram-plugin/gateway/inject-handler.test.ts +221 -0
- package/telegram-plugin/gateway/inject-handler.ts +190 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +151 -0
- package/telegram-plugin/gateway/ipc-server.ts +494 -0
- package/telegram-plugin/gateway/pid-file.ts +103 -0
- package/telegram-plugin/gateway/poll-health.ts +156 -0
- package/telegram-plugin/gateway/preamble-suppressor.ts +154 -0
- package/telegram-plugin/gateway/quota-cache.ts +125 -0
- package/telegram-plugin/gateway/resolve-calling-subagent.ts +78 -0
- package/telegram-plugin/gateway/restart-watchdog.ts +200 -0
- package/telegram-plugin/gateway/session-marker.ts +83 -0
- package/telegram-plugin/gateway/shutdown-drain.ts +162 -0
- package/telegram-plugin/gateway/startup-mutex.ts +285 -0
- package/telegram-plugin/gateway/startup-network-retry.ts +142 -0
- package/telegram-plugin/gateway/turn-active-marker.ts +176 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +78 -0
- package/telegram-plugin/handoff-continuity.ts +200 -0
- package/telegram-plugin/history.ts +468 -0
- package/telegram-plugin/hooks/hooks.json +58 -0
- package/telegram-plugin/hooks/secret-guard-pretool.mjs +208 -0
- package/telegram-plugin/hooks/secret-scrub-stop.mjs +98 -0
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +111 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +296 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +261 -0
- package/telegram-plugin/html-sanitize.ts +244 -0
- package/telegram-plugin/idle-footer.ts +65 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +166 -0
- package/telegram-plugin/interrupt-marker.ts +66 -0
- package/telegram-plugin/issues-card.ts +371 -0
- package/telegram-plugin/issues-watcher.ts +125 -0
- package/telegram-plugin/model-unavailable.ts +325 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/operator-events-history.ts +94 -0
- package/telegram-plugin/operator-events.fixtures.json +161 -0
- package/telegram-plugin/operator-events.ts +421 -0
- package/telegram-plugin/package.json +55 -0
- package/telegram-plugin/permission-rule.ts +133 -0
- package/telegram-plugin/permission-title.ts +117 -0
- package/telegram-plugin/pin-event-log.ts +76 -0
- package/telegram-plugin/plugin-logger.ts +136 -0
- package/telegram-plugin/progress-card-driver.ts +2697 -0
- package/telegram-plugin/progress-card-pin-manager.ts +589 -0
- package/telegram-plugin/progress-card-pin-watchdog.ts +98 -0
- package/telegram-plugin/progress-card.ts +1409 -0
- package/telegram-plugin/pty-partial-handler.ts +247 -0
- package/telegram-plugin/pty-tail.ts +730 -0
- package/telegram-plugin/quota-check.ts +474 -0
- package/telegram-plugin/recent-outbound-dedup.ts +169 -0
- package/telegram-plugin/registry/api-registry.test.ts +201 -0
- package/telegram-plugin/registry/subagents-bugs.test.ts +454 -0
- package/telegram-plugin/registry/subagents-schema.ts +509 -0
- package/telegram-plugin/registry/subagents.test.ts +476 -0
- package/telegram-plugin/registry/turns-schema.test.ts +101 -0
- package/telegram-plugin/registry/turns-schema.ts +417 -0
- package/telegram-plugin/retry-api-call.ts +172 -0
- package/telegram-plugin/scripts/build.mjs +78 -0
- package/telegram-plugin/secret-detect/audit.ts +66 -0
- package/telegram-plugin/secret-detect/chunker.ts +37 -0
- package/telegram-plugin/secret-detect/entropy.ts +20 -0
- package/telegram-plugin/secret-detect/gitleaks-loader.ts +74 -0
- package/telegram-plugin/secret-detect/gitleaks.toml +27 -0
- package/telegram-plugin/secret-detect/index.ts +218 -0
- package/telegram-plugin/secret-detect/kv-scanner.ts +60 -0
- package/telegram-plugin/secret-detect/mask.ts +13 -0
- package/telegram-plugin/secret-detect/patterns.ts +115 -0
- package/telegram-plugin/secret-detect/pipeline.ts +144 -0
- package/telegram-plugin/secret-detect/rewrite.ts +26 -0
- package/telegram-plugin/secret-detect/secretlint-source.ts +95 -0
- package/telegram-plugin/secret-detect/slug.ts +44 -0
- package/telegram-plugin/secret-detect/staging.ts +85 -0
- package/telegram-plugin/secret-detect/suppressor.ts +34 -0
- package/telegram-plugin/secret-detect/url-redact.ts +60 -0
- package/telegram-plugin/secret-detect/vault-write.ts +56 -0
- package/telegram-plugin/server.js +41795 -0
- package/telegram-plugin/server.ts +171 -0
- package/telegram-plugin/session-tail.ts +884 -0
- package/telegram-plugin/shared/bot-runtime.ts +324 -0
- package/telegram-plugin/silent-reply.ts +58 -0
- package/telegram-plugin/slot-banner-driver.ts +147 -0
- package/telegram-plugin/slot-banner.ts +86 -0
- package/telegram-plugin/start.js +26 -0
- package/telegram-plugin/startup-reset.ts +45 -0
- package/telegram-plugin/status-reactions.ts +332 -0
- package/telegram-plugin/steering.ts +155 -0
- package/telegram-plugin/sticker-aliases.ts +249 -0
- package/telegram-plugin/stream-controller.ts +311 -0
- package/telegram-plugin/stream-reply-handler.ts +664 -0
- package/telegram-plugin/streaming-metrics.ts +134 -0
- package/telegram-plugin/streaming-report.ts +204 -0
- package/telegram-plugin/subagent-watcher.ts +880 -0
- package/telegram-plugin/telegram-button-constraints.ts +191 -0
- package/telegram-plugin/telegraph.ts +381 -0
- package/telegram-plugin/tests/HARNESS.md +340 -0
- package/telegram-plugin/tests/_progress-card-harness.ts +105 -0
- package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +211 -0
- package/telegram-plugin/tests/active-pins-sweep.test.ts +309 -0
- package/telegram-plugin/tests/active-pins.test.ts +187 -0
- package/telegram-plugin/tests/active-reactions-sweep.test.ts +116 -0
- package/telegram-plugin/tests/active-reactions.test.ts +198 -0
- package/telegram-plugin/tests/answer-stream-dedup.test.ts +352 -0
- package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +236 -0
- package/telegram-plugin/tests/answer-stream.test.ts +878 -0
- package/telegram-plugin/tests/ask-user.test.ts +203 -0
- package/telegram-plugin/tests/attachment-path.test.ts +199 -0
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +118 -0
- package/telegram-plugin/tests/auth-code-auto-capture.test.ts +144 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +248 -0
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +260 -0
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +140 -0
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +559 -0
- package/telegram-plugin/tests/auth-dashboard.test.ts +1045 -0
- package/telegram-plugin/tests/auth-login-url-button.test.ts +122 -0
- package/telegram-plugin/tests/auth-slot-commands.test.ts +640 -0
- package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +183 -0
- package/telegram-plugin/tests/auto-fallback.test.ts +381 -0
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +137 -0
- package/telegram-plugin/tests/boot-card-dedupe.test.ts +154 -0
- package/telegram-plugin/tests/boot-card-probe-target.test.ts +194 -0
- package/telegram-plugin/tests/boot-card-reason.test.ts +103 -0
- package/telegram-plugin/tests/boot-card-render.test.ts +219 -0
- package/telegram-plugin/tests/boot-probes.test.ts +451 -0
- package/telegram-plugin/tests/bot-api.harness.ts +116 -0
- package/telegram-plugin/tests/bot-runtime.test.ts +190 -0
- package/telegram-plugin/tests/bridge-anonymous-refuse.test.ts +60 -0
- package/telegram-plugin/tests/context-exhaustion.test.ts +114 -0
- package/telegram-plugin/tests/credits-watch.test.ts +221 -0
- package/telegram-plugin/tests/dm-command-gate.test.ts +176 -0
- package/telegram-plugin/tests/draft-stream.test.ts +752 -0
- package/telegram-plugin/tests/draft-transport.test.ts +141 -0
- package/telegram-plugin/tests/e2e.test.ts +436 -0
- package/telegram-plugin/tests/fake-bot-api.test.ts +213 -0
- package/telegram-plugin/tests/fake-bot-api.ts +617 -0
- package/telegram-plugin/tests/false-restart-banner.test.ts +253 -0
- package/telegram-plugin/tests/first-paint.test.ts +257 -0
- package/telegram-plugin/tests/fixtures/pty-tail-tmux-fragment.bin +6 -0
- package/telegram-plugin/tests/fixtures/service-log-current-claude-code.bin +3624 -0
- package/telegram-plugin/tests/fleet-state-watcher.test.ts +101 -0
- package/telegram-plugin/tests/fleet-state.test.ts +185 -0
- package/telegram-plugin/tests/foreman-create-flow.test.ts +359 -0
- package/telegram-plugin/tests/foreman-handlers.test.ts +347 -0
- package/telegram-plugin/tests/foreman-state.test.ts +164 -0
- package/telegram-plugin/tests/foreman-write-ops.test.ts +214 -0
- package/telegram-plugin/tests/gateway-409-retry-banner.test.ts +173 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +72 -0
- package/telegram-plugin/tests/gateway-bridge.test.ts +811 -0
- package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +414 -0
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +144 -0
- package/telegram-plugin/tests/gateway-message-validator.test.ts +133 -0
- package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +103 -0
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +127 -0
- package/telegram-plugin/tests/gateway-startup-mutex.test.ts +284 -0
- package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +185 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +72 -0
- package/telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts +125 -0
- package/telegram-plugin/tests/handoff-continuity.test.ts +249 -0
- package/telegram-plugin/tests/harness-ordering-invariants.test.ts +243 -0
- package/telegram-plugin/tests/harness-parse-mode-validation.test.ts +114 -0
- package/telegram-plugin/tests/history.test.ts +364 -0
- package/telegram-plugin/tests/html-balanced.ts +63 -0
- package/telegram-plugin/tests/html-sanitize.test.ts +146 -0
- package/telegram-plugin/tests/idle-footer-wiring.test.ts +88 -0
- package/telegram-plugin/tests/idle-footer.test.ts +66 -0
- package/telegram-plugin/tests/inbound-coalesce.test.ts +127 -0
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +150 -0
- package/telegram-plugin/tests/interrupt-marker.test.ts +126 -0
- package/telegram-plugin/tests/ipc-protocol.test.ts +218 -0
- package/telegram-plugin/tests/ipc-server-anonymous-refuse.test.ts +82 -0
- package/telegram-plugin/tests/ipc-server-client.test.ts +323 -0
- package/telegram-plugin/tests/ipc-server-race.test.ts +183 -0
- package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +91 -0
- package/telegram-plugin/tests/ipc-server-validate-pty-partial.test.ts +64 -0
- package/telegram-plugin/tests/ipc-server-validate-update-placeholder.test.ts +77 -0
- package/telegram-plugin/tests/ipc-validator.test.ts +274 -0
- package/telegram-plugin/tests/issues-card.test.ts +495 -0
- package/telegram-plugin/tests/issues-watcher.test.ts +165 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +303 -0
- package/telegram-plugin/tests/multi-turn-continuity.test.ts +159 -0
- package/telegram-plugin/tests/operator-events-history.test.ts +125 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +192 -0
- package/telegram-plugin/tests/operator-events.test.ts +331 -0
- package/telegram-plugin/tests/outbound-ordering.test.ts +293 -0
- package/telegram-plugin/tests/parse-mode-rotation.test.ts +164 -0
- package/telegram-plugin/tests/permission-rule.test.ts +121 -0
- package/telegram-plugin/tests/permission-title.test.ts +106 -0
- package/telegram-plugin/tests/pin-event-log.test.ts +124 -0
- package/telegram-plugin/tests/plugin-logger.test.ts +97 -0
- package/telegram-plugin/tests/poll-health.test.ts +86 -0
- package/telegram-plugin/tests/preamble-suppressor.test.ts +285 -0
- package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +73 -0
- package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +272 -0
- package/telegram-plugin/tests/progress-card-cross-turn.test.ts +258 -0
- package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +81 -0
- package/telegram-plugin/tests/progress-card-draft-flag.test.ts +80 -0
- package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +215 -0
- package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +123 -0
- package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +76 -0
- package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +62 -0
- package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +84 -0
- package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +139 -0
- package/telegram-plugin/tests/progress-card-pin-manager.test.ts +773 -0
- package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +66 -0
- package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +64 -0
- package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +190 -0
- package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +146 -0
- package/telegram-plugin/tests/progress-update.test.ts +236 -0
- package/telegram-plugin/tests/protocol-fixtures.test.ts +59 -0
- package/telegram-plugin/tests/protocol-fixtures.ts +198 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +326 -0
- package/telegram-plugin/tests/pty-tail-real-fixture.test.ts +114 -0
- package/telegram-plugin/tests/pty-tail-tmux-fragment.test.ts +71 -0
- package/telegram-plugin/tests/pty-tail.test.ts +525 -0
- package/telegram-plugin/tests/quota-cache.test.ts +187 -0
- package/telegram-plugin/tests/quota-check.test.ts +622 -0
- package/telegram-plugin/tests/races.test.ts +842 -0
- package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +123 -0
- package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +82 -0
- package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +114 -0
- package/telegram-plugin/tests/real-gateway-harness.ts +699 -0
- package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +313 -0
- package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +299 -0
- package/telegram-plugin/tests/real-gateway-spec.test.ts +487 -0
- package/telegram-plugin/tests/real-gateway.smoke.test.ts +101 -0
- package/telegram-plugin/tests/recent-outbound-dedup.test.ts +192 -0
- package/telegram-plugin/tests/registry-turns.test.ts +432 -0
- package/telegram-plugin/tests/reply-terminal-reaction.test.ts +149 -0
- package/telegram-plugin/tests/resolve-calling-subagent.test.ts +269 -0
- package/telegram-plugin/tests/restart-watchdog.test.ts +224 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +287 -0
- package/telegram-plugin/tests/secret-detect-audit.test.ts +58 -0
- package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +83 -0
- package/telegram-plugin/tests/secret-detect-gitleaks.test.ts +32 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +308 -0
- package/telegram-plugin/tests/secret-detect-pipeline.test.ts +123 -0
- package/telegram-plugin/tests/secret-detect-secretlint.test.ts +101 -0
- package/telegram-plugin/tests/secret-detect-staging.test.ts +45 -0
- package/telegram-plugin/tests/secret-detect-suppressor-no-silent-allow.test.ts +67 -0
- package/telegram-plugin/tests/secret-detect.test.ts +223 -0
- package/telegram-plugin/tests/secret-guard-pretool.test.ts +194 -0
- package/telegram-plugin/tests/send-typing-action-validation.test.ts +61 -0
- package/telegram-plugin/tests/session-tail-capped.test.ts +285 -0
- package/telegram-plugin/tests/session-tail.test.ts +524 -0
- package/telegram-plugin/tests/setup-flow.test.ts +510 -0
- package/telegram-plugin/tests/setup-state.test.ts +146 -0
- package/telegram-plugin/tests/silent-reply-guard.test.ts +122 -0
- package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +350 -0
- package/telegram-plugin/tests/slot-banner.test.ts +74 -0
- package/telegram-plugin/tests/snapshot-serializer.ts +79 -0
- package/telegram-plugin/tests/spawn-detached-cgroup-escape.test.ts +51 -0
- package/telegram-plugin/tests/status-accent.test.ts +186 -0
- package/telegram-plugin/tests/status-reactions-allowed-filter.test.ts +132 -0
- package/telegram-plugin/tests/status-reactions.test.ts +314 -0
- package/telegram-plugin/tests/steering.test.ts +282 -0
- package/telegram-plugin/tests/sticker-aliases.test.ts +232 -0
- package/telegram-plugin/tests/stream-controller-html-fallback.test.ts +127 -0
- package/telegram-plugin/tests/stream-controller.test.ts +262 -0
- package/telegram-plugin/tests/stream-reply-error-paths.test.ts +208 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +1292 -0
- package/telegram-plugin/tests/streaming-e2e.test.ts +389 -0
- package/telegram-plugin/tests/streaming-metrics.test.ts +201 -0
- package/telegram-plugin/tests/streaming-orchestration.test.ts +756 -0
- package/telegram-plugin/tests/subagent-registry-bugs.test.ts +725 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +213 -0
- package/telegram-plugin/tests/subagent-watcher-parent-marker.test.ts +274 -0
- package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +243 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +877 -0
- package/telegram-plugin/tests/subagents-schema-init-order.test.ts +109 -0
- package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +116 -0
- package/telegram-plugin/tests/telegram-button-constraints.test.ts +194 -0
- package/telegram-plugin/tests/telegram-format.test.ts +1093 -0
- package/telegram-plugin/tests/telegraph.test.ts +246 -0
- package/telegram-plugin/tests/tool-labels.test.ts +383 -0
- package/telegram-plugin/tests/turn-active-marker.test.ts +195 -0
- package/telegram-plugin/tests/turn-end-regressions.test.ts +489 -0
- package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +218 -0
- package/telegram-plugin/tests/turn-flush-dedup-controller.test.ts +144 -0
- package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +78 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +189 -0
- package/telegram-plugin/tests/turn-signal-tracker.test.ts +107 -0
- package/telegram-plugin/tests/turns-writer.test.ts +323 -0
- package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +131 -0
- package/telegram-plugin/tests/two-zone-bg-detection.test.ts +120 -0
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +87 -0
- package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +211 -0
- package/telegram-plugin/tests/two-zone-card-cap.test.ts +62 -0
- package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +101 -0
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +68 -0
- package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +110 -0
- package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +128 -0
- package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +58 -0
- package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +133 -0
- package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +155 -0
- package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +117 -0
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +143 -0
- package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +149 -0
- package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +101 -0
- package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +105 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +141 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +147 -0
- package/telegram-plugin/tests/update-factory-edited-and-reactions.test.ts +108 -0
- package/telegram-plugin/tests/update-factory.ts +305 -0
- package/telegram-plugin/tests/vault-grant-wizard.test.ts +84 -0
- package/telegram-plugin/tests/vault-grants-revoke.test.ts +265 -0
- package/telegram-plugin/tests/vault-subcommands.test.ts +234 -0
- package/telegram-plugin/tests/voice-transcribe.test.ts +196 -0
- package/telegram-plugin/tests/waiting-ux-harness.ts +381 -0
- package/telegram-plugin/tests/waiting-ux.e2e.test.ts +233 -0
- package/telegram-plugin/tests/welcome-text.test.ts +407 -0
- package/telegram-plugin/tool-error-filter.ts +89 -0
- package/telegram-plugin/tool-labels.ts +330 -0
- package/telegram-plugin/tool-names.ts +53 -0
- package/telegram-plugin/turn-flush-prose-recovery.ts +40 -0
- package/telegram-plugin/turn-flush-safety.ts +109 -0
- package/telegram-plugin/turn-signal-tracker.ts +79 -0
- package/telegram-plugin/two-zone-card.ts +249 -0
- package/telegram-plugin/typing-wrap.ts +92 -0
- package/telegram-plugin/voice-transcribe.ts +166 -0
- 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
|
+
}
|