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,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
|
+
}
|