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,730 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-time PTY tail with character-level model output extraction.
|
|
3
|
+
*
|
|
4
|
+
* The deep-research finding: Claude Code's --channels mode does NOT
|
|
5
|
+
* support --output-format stream-json, and the session JSONL writes
|
|
6
|
+
* whole assistant messages atomically (no per-token deltas). The only
|
|
7
|
+
* way to get character-level streaming text out of a long-running
|
|
8
|
+
* Claude Code daemon is to capture its PTY output (which we already
|
|
9
|
+
* do via `script -qfc ... service.log`) and parse the rendered TUI.
|
|
10
|
+
*
|
|
11
|
+
* Critical observation from the live server's service.log: when the
|
|
12
|
+
* model is generating a reply via the switchroom-telegram MCP tool, Claude
|
|
13
|
+
* Code's Ink TUI renders the in-progress tool call as:
|
|
14
|
+
*
|
|
15
|
+
* ● switchroom-telegram - reply (MCP)(chat_id: "...", text: "Yes — I can
|
|
16
|
+
* attach files to replies. Images send
|
|
17
|
+
* as inline photos...")
|
|
18
|
+
*
|
|
19
|
+
* The text parameter expands character-by-character as the model
|
|
20
|
+
* streams. By tailing service.log, feeding the bytes into a headless
|
|
21
|
+
* xterm.js, and scanning the resulting buffer for `● switchroom-telegram -
|
|
22
|
+
* reply` blocks, we can extract the streaming reply text in real time.
|
|
23
|
+
*
|
|
24
|
+
* Architecture:
|
|
25
|
+
*
|
|
26
|
+
* ┌─────────────────┐ bytes ┌─────────────────┐
|
|
27
|
+
* │ service.log ├────────────▶│ @xterm/headless │
|
|
28
|
+
* │ (script -qfc) │ │ Terminal │
|
|
29
|
+
* └─────────────────┘ └────────┬────────┘
|
|
30
|
+
* │ rendered buffer
|
|
31
|
+
* ▼
|
|
32
|
+
* ┌─────────────────┐
|
|
33
|
+
* │ MessageRegion │
|
|
34
|
+
* │ Extractor │
|
|
35
|
+
* └────────┬────────┘
|
|
36
|
+
* │ partial text
|
|
37
|
+
* ▼
|
|
38
|
+
* ┌─────────────────┐
|
|
39
|
+
* │ throttled │
|
|
40
|
+
* │ onPartial(text) │
|
|
41
|
+
* └─────────────────┘
|
|
42
|
+
*
|
|
43
|
+
* The extractor is isolated behind a versioned interface so that when
|
|
44
|
+
* Claude Code's TUI layout changes (Ink upgrades, marker tweaks, etc.),
|
|
45
|
+
* we can swap implementations without touching the tail loop. The
|
|
46
|
+
* extractor returns null when it can't confidently identify the message
|
|
47
|
+
* region; the consumer should treat null as "no streaming this turn,
|
|
48
|
+
* fall back to JSONL-only progress signals".
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { existsSync, readFileSync, statSync, watch, openSync, readSync, closeSync, type FSWatcher } from 'fs'
|
|
52
|
+
import { Terminal } from '@xterm/headless'
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* How many trailing bytes of the log to replay into xterm.js at attach
|
|
56
|
+
* time. Must be large enough to contain at least one full-screen Ink
|
|
57
|
+
* redraw — Ink's renderer is differential and emits cursor-forward
|
|
58
|
+
* escapes for unchanged cells, so without a baseline the terminal
|
|
59
|
+
* ends up with blank cells where the "● switchroom-telegram - stream_reply"
|
|
60
|
+
* marker characters should be, and the v1 extractor's substring match
|
|
61
|
+
* silently misses every partial. 1 MB is empirically ~15–30 s of
|
|
62
|
+
* steady output, comfortably covering a full-frame redraw.
|
|
63
|
+
*/
|
|
64
|
+
const PRELOAD_BYTES = 1_000_000
|
|
65
|
+
|
|
66
|
+
// ─── MessageRegionExtractor interface ─────────────────────────────────────
|
|
67
|
+
//
|
|
68
|
+
// Versioned. When Claude Code's TUI changes break the v1 extractor, ship
|
|
69
|
+
// a v2 alongside and switch the default. The interface is intentionally
|
|
70
|
+
// minimal so each implementation can use its own internal heuristics.
|
|
71
|
+
|
|
72
|
+
export interface MessageRegionExtractor {
|
|
73
|
+
/** Identifier for logging / version pinning. */
|
|
74
|
+
readonly version: string
|
|
75
|
+
/**
|
|
76
|
+
* Inspect the terminal buffer and return the current in-flight reply
|
|
77
|
+
* text the model is generating, or null if no reply is currently
|
|
78
|
+
* being composed.
|
|
79
|
+
*
|
|
80
|
+
* Implementations should be cheap (called after every byte batch) and
|
|
81
|
+
* deterministic (same buffer state → same return value).
|
|
82
|
+
*/
|
|
83
|
+
extract(terminal: Terminal): string | null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Debug knob ──────────────────────────────────────────────────────────
|
|
87
|
+
//
|
|
88
|
+
// Set SWITCHROOM_PTY_DEBUG=1 to dump the bottom of the terminal buffer to
|
|
89
|
+
// stderr whenever V1Extractor returns null. Used to diagnose extractor
|
|
90
|
+
// misses when Claude Code changes its TUI rendering (e.g. when channels
|
|
91
|
+
// mode shows MCP tool calls in a different shape than legacy mode).
|
|
92
|
+
//
|
|
93
|
+
// Off by default — when unset there is zero overhead beyond an env-var
|
|
94
|
+
// check. When on, dumps are throttled to once per 2s per process so we
|
|
95
|
+
// don't flood stderr while the extractor scans every ~150ms.
|
|
96
|
+
const PTY_DEBUG = process.env.SWITCHROOM_PTY_DEBUG === '1'
|
|
97
|
+
let lastDebugDumpAt = 0
|
|
98
|
+
const DEBUG_DUMP_THROTTLE_MS = 2000
|
|
99
|
+
const DEBUG_DUMP_LINES = 25
|
|
100
|
+
|
|
101
|
+
function debugDumpBufferOnMiss(buf: { length: number; getLine: (i: number) => { translateToString: (trim: boolean) => string } | undefined }): void {
|
|
102
|
+
if (!PTY_DEBUG) return
|
|
103
|
+
const now = Date.now()
|
|
104
|
+
if (now - lastDebugDumpAt < DEBUG_DUMP_THROTTLE_MS) return
|
|
105
|
+
lastDebugDumpAt = now
|
|
106
|
+
const start = Math.max(0, buf.length - DEBUG_DUMP_LINES)
|
|
107
|
+
const lines: string[] = []
|
|
108
|
+
for (let i = start; i < buf.length; i++) {
|
|
109
|
+
const t = buf.getLine(i)?.translateToString(true) ?? ''
|
|
110
|
+
if (t.trim() === '') continue
|
|
111
|
+
lines.push(`L${i}: ${t}`)
|
|
112
|
+
}
|
|
113
|
+
process.stderr.write(`pty-tail: V1Extractor miss — bottom ${lines.length} non-empty lines:\n${lines.join('\n')}\n---end pty miss dump---\n`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* v1 extractor for Claude Code 2.1.x.
|
|
118
|
+
*
|
|
119
|
+
* Heuristic: scan the buffer from the bottom for the most recent line
|
|
120
|
+
* that contains `● switchroom-telegram - reply (MCP)` or
|
|
121
|
+
* `● switchroom-telegram - stream_reply (MCP)`. Once found, locate the
|
|
122
|
+
* `text: "` literal and extract the value using an escape-aware
|
|
123
|
+
* character walk that terminates at the first UNESCAPED closing `"`.
|
|
124
|
+
*
|
|
125
|
+
* This matters because the model frequently passes `text` as a
|
|
126
|
+
* non-final parameter (e.g. `chat_id: "123", text: "hello", reply_to: "456"`).
|
|
127
|
+
* Earlier versions of this extractor looked for the `")` close-paren
|
|
128
|
+
* sequence, which terminated at the END of the whole tool call rather
|
|
129
|
+
* than the end of the text string — causing everything after the real
|
|
130
|
+
* `text` value (e.g. `, reply_to: "456"`) to leak into the "extracted"
|
|
131
|
+
* preview and ultimately surface as a garbled duplicate Telegram message.
|
|
132
|
+
*
|
|
133
|
+
* The walk also handles Claude Code's JSON escapes (`\"`, `\n`, `\t`,
|
|
134
|
+
* `\\`) so a text value that contains literal quotes renders correctly
|
|
135
|
+
* in the preview instead of truncating at the first inner quote.
|
|
136
|
+
*
|
|
137
|
+
* Falls back to scanning for any `● switchroom-telegram - reply` substring
|
|
138
|
+
* even if it's not the very first character of the line — Ink sometimes
|
|
139
|
+
* indents tool calls under thinking blocks.
|
|
140
|
+
*/
|
|
141
|
+
export class V1Extractor implements MessageRegionExtractor {
|
|
142
|
+
readonly version = 'v1-claude-code-2.1.x'
|
|
143
|
+
|
|
144
|
+
extract(terminal: Terminal): string | null {
|
|
145
|
+
const buf = terminal.buffer.active
|
|
146
|
+
// Walk from the bottom looking for the start of a reply block. We
|
|
147
|
+
// care about the MOST RECENT one because earlier turns are stale.
|
|
148
|
+
let startLine = -1
|
|
149
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
150
|
+
const text = buf.getLine(i)?.translateToString(true) ?? ''
|
|
151
|
+
if (
|
|
152
|
+
text.includes('switchroom-telegram - reply') ||
|
|
153
|
+
text.includes('switchroom-telegram - stream_reply')
|
|
154
|
+
) {
|
|
155
|
+
startLine = i
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (startLine < 0) {
|
|
160
|
+
debugDumpBufferOnMiss(buf)
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Concatenate the start line + continuation lines into one logical
|
|
165
|
+
// string. Continuation lines from Ink for tool params are indented
|
|
166
|
+
// by ~30 spaces; the exact count varies with terminal width. Use a
|
|
167
|
+
// heuristic: a line is a continuation iff its first non-space char
|
|
168
|
+
// appears later than column 5 AND it doesn't start with the bullet
|
|
169
|
+
// `●` (which would mean a new entry).
|
|
170
|
+
const lines: string[] = []
|
|
171
|
+
for (let i = startLine; i < buf.length; i++) {
|
|
172
|
+
const text = buf.getLine(i)?.translateToString(true) ?? ''
|
|
173
|
+
if (i === startLine) {
|
|
174
|
+
lines.push(text)
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
// Continuation: heavy indentation, no leading bullet
|
|
178
|
+
const trimmed = text.replace(/^\s+/, '')
|
|
179
|
+
if (trimmed === '') {
|
|
180
|
+
// Empty line — probably end of the tool block
|
|
181
|
+
break
|
|
182
|
+
}
|
|
183
|
+
// A bullet (●) or tool-result marker (⎿) anywhere in the leading
|
|
184
|
+
// whitespace means a new section. Note Ink often indents bullets
|
|
185
|
+
// by ~2 cols, so checking startsWith('●') would miss them — use
|
|
186
|
+
// a regex that allows leading whitespace.
|
|
187
|
+
if (/^\s*●/.test(text) || /^\s*⎿/.test(text)) {
|
|
188
|
+
break
|
|
189
|
+
}
|
|
190
|
+
const leadingSpaces = text.length - trimmed.length
|
|
191
|
+
if (leadingSpaces < 4) {
|
|
192
|
+
// Not a continuation
|
|
193
|
+
break
|
|
194
|
+
}
|
|
195
|
+
lines.push(text)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Now find `text: "` in the concatenated content
|
|
199
|
+
const joined = lines.join('\n')
|
|
200
|
+
const textIdx = joined.indexOf('text: "')
|
|
201
|
+
if (textIdx < 0) return null
|
|
202
|
+
const afterOpen = textIdx + 'text: "'.length
|
|
203
|
+
|
|
204
|
+
// Escape-aware string walk. Starting right after the opening quote
|
|
205
|
+
// of the text parameter, consume characters one at a time. A `\`
|
|
206
|
+
// byte escapes the next char (`\"` → `"`, `\n` → newline, `\\` →
|
|
207
|
+
// backslash, and any unknown sequence is preserved verbatim). An
|
|
208
|
+
// UNESCAPED `"` terminates the string — this is the real end of
|
|
209
|
+
// the text parameter value, regardless of whether it's followed by
|
|
210
|
+
// `)` (text was last) or `,` (text was followed by another param).
|
|
211
|
+
// If we exhaust the buffer without finding the terminator, the
|
|
212
|
+
// model is mid-stream and we return everything captured so far.
|
|
213
|
+
let extracted = ''
|
|
214
|
+
let pos = afterOpen
|
|
215
|
+
while (pos < joined.length) {
|
|
216
|
+
const ch = joined[pos]
|
|
217
|
+
if (ch === '\\' && pos + 1 < joined.length) {
|
|
218
|
+
const next = joined[pos + 1]
|
|
219
|
+
if (next === '"') extracted += '"'
|
|
220
|
+
else if (next === 'n') extracted += '\n'
|
|
221
|
+
else if (next === 't') extracted += '\t'
|
|
222
|
+
else if (next === 'r') extracted += '\r'
|
|
223
|
+
else if (next === '\\') extracted += '\\'
|
|
224
|
+
else extracted += '\\' + next
|
|
225
|
+
pos += 2
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
if (ch === '"') {
|
|
229
|
+
// Unescaped closing quote — end of the text parameter value.
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
extracted += ch
|
|
233
|
+
pos++
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Strip Ink's continuation-line indentation. Each non-first line
|
|
237
|
+
// has ~30 spaces of leading whitespace; collapse to a single space
|
|
238
|
+
// (Ink visually flows the text, so newlines are not semantic).
|
|
239
|
+
const cleaned = extracted
|
|
240
|
+
.split('\n')
|
|
241
|
+
.map((l, idx) => (idx === 0 ? l : l.replace(/^\s+/, '')))
|
|
242
|
+
.join(' ')
|
|
243
|
+
.replace(/\s+/g, ' ')
|
|
244
|
+
.trim()
|
|
245
|
+
|
|
246
|
+
if (cleaned === '') return null
|
|
247
|
+
return cleaned
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── ToolActivityExtractor ────────────────────────────────────────────────
|
|
252
|
+
//
|
|
253
|
+
// Design note (2026-04-13): during tool-heavy turns that end with a single
|
|
254
|
+
// `reply` call, the V1Extractor above emits nothing until the very end —
|
|
255
|
+
// the user sees a gap, which reads as "the bot is hung". We fix that by
|
|
256
|
+
// ALSO surfacing tool-call activity ("Bash: git status", "Read: foo.ts",
|
|
257
|
+
// "Grep: pattern") as short one-liners, so the plugin can push a live
|
|
258
|
+
// status via stream_reply on a separate lane.
|
|
259
|
+
//
|
|
260
|
+
// Chosen lane approach (docs were ambiguous — picking what's cleaner given
|
|
261
|
+
// the stream-reply-handler `lane` parameter that landed 2026-04-13): emit
|
|
262
|
+
// activity lines OUT-OF-BAND via a second callback (`onActivity`), separate
|
|
263
|
+
// from the reply-text `onPartial`. The consumer (server.ts) routes these
|
|
264
|
+
// to a dedicated `"activity"` lane via stream_reply(lane: "activity"). This
|
|
265
|
+
// keeps the existing reply/stream_reply path untouched (all current tests
|
|
266
|
+
// still pass unmodified), and avoids mixing status noise into the answer
|
|
267
|
+
// text buffer.
|
|
268
|
+
|
|
269
|
+
export interface ToolActivityExtractor {
|
|
270
|
+
readonly version: string
|
|
271
|
+
/**
|
|
272
|
+
* Return a SHORT (<100 char) human-readable status string for the most
|
|
273
|
+
* recent tool-call bullet in the buffer, or null if none / the same as
|
|
274
|
+
* the last extraction (dedup is the consumer's job — extractor just
|
|
275
|
+
* surfaces the current top-of-stack activity).
|
|
276
|
+
*/
|
|
277
|
+
extract(terminal: Terminal): string | null
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* v1 activity extractor. Scans the buffer bottom-up for Claude Code's Ink
|
|
282
|
+
* tool-call bullet pattern:
|
|
283
|
+
*
|
|
284
|
+
* ● Bash(git status)
|
|
285
|
+
* ● Read(/path/to/file.ts)
|
|
286
|
+
* ● Grep(pattern, path: "...")
|
|
287
|
+
* ● Glob(**\/*.ts)
|
|
288
|
+
* ● switchroom-telegram - reply (MCP)(...)
|
|
289
|
+
*
|
|
290
|
+
* For the core tools we render a short verbed one-liner ("Running Bash:
|
|
291
|
+
* git status", "Reading file.ts", "Searching with Grep: pattern"). For
|
|
292
|
+
* switchroom-telegram tool calls we return null — those are already surfaced
|
|
293
|
+
* by V1Extractor on the main lane, and echoing them on the activity lane
|
|
294
|
+
* would be confusing.
|
|
295
|
+
*/
|
|
296
|
+
export class V1ToolActivityExtractor implements ToolActivityExtractor {
|
|
297
|
+
readonly version = 'v1-tool-activity'
|
|
298
|
+
|
|
299
|
+
extract(terminal: Terminal): string | null {
|
|
300
|
+
const buf = terminal.buffer.active
|
|
301
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
302
|
+
const raw = buf.getLine(i)?.translateToString(true) ?? ''
|
|
303
|
+
// Find a bullet anywhere on the line (Ink may indent).
|
|
304
|
+
const bulletIdx = raw.indexOf('●')
|
|
305
|
+
if (bulletIdx < 0) continue
|
|
306
|
+
const after = raw.slice(bulletIdx + 1).trimStart()
|
|
307
|
+
if (after === '') continue
|
|
308
|
+
|
|
309
|
+
// Skip switchroom-telegram tool calls — V1Extractor owns those.
|
|
310
|
+
if (after.startsWith('switchroom-telegram')) return null
|
|
311
|
+
|
|
312
|
+
// Match `ToolName(` or `ToolName -` patterns. Accept the conventional
|
|
313
|
+
// Claude Code tool names; anything else is "Running <Tool>".
|
|
314
|
+
const m = after.match(/^([A-Za-z_][A-Za-z0-9_-]*)[\s(]/)
|
|
315
|
+
if (!m) continue
|
|
316
|
+
const tool = m[1]
|
|
317
|
+
|
|
318
|
+
// Grab the inside of the first (...) group if present, keeping only
|
|
319
|
+
// up to the first comma for a short preview.
|
|
320
|
+
const parenOpen = after.indexOf('(')
|
|
321
|
+
let inner = ''
|
|
322
|
+
if (parenOpen >= 0) {
|
|
323
|
+
// Simple depth-aware walk: stop at the matching close paren. Good
|
|
324
|
+
// enough for a short status preview.
|
|
325
|
+
let depth = 0
|
|
326
|
+
let end = -1
|
|
327
|
+
for (let j = parenOpen; j < after.length; j++) {
|
|
328
|
+
const ch = after[j]
|
|
329
|
+
if (ch === '(') depth++
|
|
330
|
+
else if (ch === ')') {
|
|
331
|
+
depth--
|
|
332
|
+
if (depth === 0) { end = j; break }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
inner = end > parenOpen ? after.slice(parenOpen + 1, end) : after.slice(parenOpen + 1)
|
|
336
|
+
// Trim to first meaningful arg (up to first ", " at depth 0).
|
|
337
|
+
const commaIdx = inner.indexOf(', ')
|
|
338
|
+
if (commaIdx > 0) inner = inner.slice(0, commaIdx)
|
|
339
|
+
inner = inner.trim()
|
|
340
|
+
// Strip surrounding quotes from a single-arg string.
|
|
341
|
+
if (inner.startsWith('"') && inner.endsWith('"') && inner.length >= 2) {
|
|
342
|
+
inner = inner.slice(1, -1)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Truncate overlong inner strings.
|
|
347
|
+
const MAX = 80
|
|
348
|
+
if (inner.length > MAX) inner = inner.slice(0, MAX - 1) + '…'
|
|
349
|
+
|
|
350
|
+
// Verbed phrasing per tool.
|
|
351
|
+
const verb = activityVerb(tool)
|
|
352
|
+
const phrase = inner.length > 0 ? `${verb}: ${inner}` : verb
|
|
353
|
+
// Final guardrail: one line, no control chars.
|
|
354
|
+
return phrase.replace(/\s+/g, ' ').trim().slice(0, 120)
|
|
355
|
+
}
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Activity-line prefixes produced by the noisy core tools (Bash, Read,
|
|
362
|
+
* Write, Edit, Grep, Glob). The PTY tail extracts an activity line per
|
|
363
|
+
* tool call, but surfacing each one to the user is noise — the user
|
|
364
|
+
* wants human-meaningful rollups ("Running sub-agent...",
|
|
365
|
+
* "Fetching URL...") not per-tool narration of "Running Bash: cd ...".
|
|
366
|
+
*
|
|
367
|
+
* Used by `shouldSuppressToolActivity` to filter at the consumer layer.
|
|
368
|
+
* The extractor itself still returns these lines unchanged, so anything
|
|
369
|
+
* that wants the raw stream (tests, telemetry) keeps working.
|
|
370
|
+
*/
|
|
371
|
+
export const NOISY_TOOL_ACTIVITY_PREFIXES: readonly string[] = [
|
|
372
|
+
'Running Bash',
|
|
373
|
+
'Reading file',
|
|
374
|
+
'Writing file',
|
|
375
|
+
'Editing file',
|
|
376
|
+
'Searching with Grep',
|
|
377
|
+
'Searching with Glob',
|
|
378
|
+
// Claude Code's in-progress spinner uses bare verbs ("Reading…",
|
|
379
|
+
// "Writing…", "Editing…", "Searching…") which the bullet-line regex
|
|
380
|
+
// captures as the tool token. activityVerb falls through to the
|
|
381
|
+
// default `Running ${tool}`, producing these synthetic prefixes.
|
|
382
|
+
'Running Reading',
|
|
383
|
+
'Running Writing',
|
|
384
|
+
'Running Editing',
|
|
385
|
+
'Running Searching',
|
|
386
|
+
'Running Read',
|
|
387
|
+
'Running Write',
|
|
388
|
+
'Running Edit',
|
|
389
|
+
'Running Grep',
|
|
390
|
+
'Running Glob',
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Substrings that mark a Claude Code TUI keyboard hint the Telegram
|
|
395
|
+
* user cannot action ("ctrl+o to expand", "esc to interrupt",
|
|
396
|
+
* "shift+tab to cycle", etc.). If any of these appear anywhere in an
|
|
397
|
+
* activity line, the line is suppressed — surfacing the hint is
|
|
398
|
+
* confusing UX (user pokes at their phone, nothing happens) and the
|
|
399
|
+
* line carries no information beyond "Claude is doing something".
|
|
400
|
+
*/
|
|
401
|
+
export const TUI_HINT_MARKERS: readonly string[] = [
|
|
402
|
+
'ctrl+',
|
|
403
|
+
'ctrl-',
|
|
404
|
+
'esc to ',
|
|
405
|
+
'shift+',
|
|
406
|
+
'shift-',
|
|
407
|
+
'alt+',
|
|
408
|
+
'tab to ',
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* True if an activity line is per-tool narration for a noisy core tool
|
|
413
|
+
* (Bash/Read/Write/Edit/Grep/Glob) that should NOT be surfaced to the
|
|
414
|
+
* Telegram activity lane. Human-meaningful rollups ("Running sub-agent",
|
|
415
|
+
* "Fetching URL", "Searching the web", and anything unknown mapped to
|
|
416
|
+
* "Running <CustomTool>") pass through. Also suppresses any line that
|
|
417
|
+
* carries a Claude Code TUI keyboard hint, regardless of tool prefix.
|
|
418
|
+
*/
|
|
419
|
+
export function shouldSuppressToolActivity(line: string): boolean {
|
|
420
|
+
const lower = line.toLowerCase()
|
|
421
|
+
for (const marker of TUI_HINT_MARKERS) {
|
|
422
|
+
if (lower.includes(marker)) return true
|
|
423
|
+
}
|
|
424
|
+
for (const prefix of NOISY_TOOL_ACTIVITY_PREFIXES) {
|
|
425
|
+
if (line === prefix) return true
|
|
426
|
+
if (line.startsWith(prefix + ':')) return true
|
|
427
|
+
}
|
|
428
|
+
return false
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function activityVerb(tool: string): string {
|
|
432
|
+
switch (tool) {
|
|
433
|
+
case 'Bash': return 'Running Bash'
|
|
434
|
+
case 'Read': return 'Reading file'
|
|
435
|
+
case 'Write': return 'Writing file'
|
|
436
|
+
case 'Edit': return 'Editing file'
|
|
437
|
+
case 'Grep': return 'Searching with Grep'
|
|
438
|
+
case 'Glob': return 'Searching with Glob'
|
|
439
|
+
case 'WebFetch': return 'Fetching URL'
|
|
440
|
+
case 'WebSearch': return 'Searching the web'
|
|
441
|
+
case 'Task': return 'Running sub-agent'
|
|
442
|
+
default: return `Running ${tool}`
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ─── PTY tail ────────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
export interface PtyTailConfig {
|
|
449
|
+
/** Absolute path to the file we tail. Usually `<agentDir>/service.log`. */
|
|
450
|
+
logFile: string
|
|
451
|
+
/** Throttle for partial text emission. Default 750 ms. */
|
|
452
|
+
throttleMs?: number
|
|
453
|
+
/** Terminal cols/rows the script wrapper uses. Default 132x40. */
|
|
454
|
+
cols?: number
|
|
455
|
+
rows?: number
|
|
456
|
+
/** Pluggable extractor. Default V1Extractor. */
|
|
457
|
+
extractor?: MessageRegionExtractor
|
|
458
|
+
/** Optional logger. */
|
|
459
|
+
log?: (msg: string) => void
|
|
460
|
+
/** Called when extracted text changes. Receives the FULL current text. */
|
|
461
|
+
onPartial: (text: string) => void
|
|
462
|
+
/** Called when the model finishes a turn (extracted text reaches a stable terminal). */
|
|
463
|
+
onFinal?: (text: string) => void
|
|
464
|
+
/**
|
|
465
|
+
* Optional second extractor that surfaces tool-call activity ("Running
|
|
466
|
+
* Bash: ls", "Reading file: foo.ts"). When provided, the tail runs both
|
|
467
|
+
* extractors on every byte batch and fires `onActivity` (deduped + same
|
|
468
|
+
* throttle) when the activity line changes. Independent of onPartial —
|
|
469
|
+
* the consumer chooses how to route it (typically a separate lane).
|
|
470
|
+
*/
|
|
471
|
+
activityExtractor?: ToolActivityExtractor
|
|
472
|
+
/** Called when the activity extractor's output changes (deduped + throttled). */
|
|
473
|
+
onActivity?: (text: string) => void
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface PtyTailHandle {
|
|
477
|
+
stop(): void
|
|
478
|
+
/** Get the current cumulative extracted text, or null. */
|
|
479
|
+
getCurrentText(): string | null
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Start tailing a PTY-captured log file. Re-feeds the terminal emulator
|
|
484
|
+
* with new bytes as they arrive, runs the extractor on every batch, and
|
|
485
|
+
* fires onPartial whenever the result changes.
|
|
486
|
+
*
|
|
487
|
+
* Robustness: if the log file doesn't exist yet, polls for it. If the
|
|
488
|
+
* file is truncated/replaced (logrotate), resets the cursor. The
|
|
489
|
+
* terminal emulator state persists across rotations — that's
|
|
490
|
+
* intentional for now, since the typical case is no rotation. A
|
|
491
|
+
* smarter version could reset the terminal on truncation but it adds
|
|
492
|
+
* complexity for an edge case.
|
|
493
|
+
*/
|
|
494
|
+
export function startPtyTail(config: PtyTailConfig): PtyTailHandle {
|
|
495
|
+
const throttleMs = config.throttleMs ?? 150
|
|
496
|
+
const extractor = config.extractor ?? new V1Extractor()
|
|
497
|
+
const activityExtractor = config.activityExtractor ?? null
|
|
498
|
+
const onActivity = config.onActivity ?? null
|
|
499
|
+
const log = config.log
|
|
500
|
+
const cols = config.cols ?? 132
|
|
501
|
+
const rows = config.rows ?? 40
|
|
502
|
+
|
|
503
|
+
// Bound the in-memory terminal buffer. 1000 lines at 132 cols is ~150 KB
|
|
504
|
+
// per PTY session; 5000 lines let a 60-minute run of a chatty tool grow
|
|
505
|
+
// past 300 MB and made extraction O(buffer). Very old scrollback isn't
|
|
506
|
+
// useful for progress extraction anyway — the extractor only reads from
|
|
507
|
+
// the current cursor region.
|
|
508
|
+
const term = new Terminal({
|
|
509
|
+
cols,
|
|
510
|
+
rows,
|
|
511
|
+
scrollback: 1000,
|
|
512
|
+
allowProposedApi: true,
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
let cursor = 0
|
|
516
|
+
let lastEmittedText: string | null = null
|
|
517
|
+
let lastEmittedActivity: string | null = null
|
|
518
|
+
let lastEmitAt = 0
|
|
519
|
+
let lastActivityEmitAt = 0
|
|
520
|
+
let pendingEmit: ReturnType<typeof setTimeout> | null = null
|
|
521
|
+
let pendingActivity: ReturnType<typeof setTimeout> | null = null
|
|
522
|
+
let stopped = false
|
|
523
|
+
let watcher: FSWatcher | null = null
|
|
524
|
+
let pollTimer: ReturnType<typeof setInterval> | null = null
|
|
525
|
+
|
|
526
|
+
function emitIfChanged(): void {
|
|
527
|
+
if (stopped) return
|
|
528
|
+
const text = extractor.extract(term)
|
|
529
|
+
if (text === lastEmittedText) return
|
|
530
|
+
if (text == null) {
|
|
531
|
+
// Extractor lost the region — could mean turn ended. If we had a
|
|
532
|
+
// last emitted text, keep it; the JSONL backstop will finalize.
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
lastEmittedText = text
|
|
536
|
+
lastEmitAt = Date.now()
|
|
537
|
+
try {
|
|
538
|
+
config.onPartial(text)
|
|
539
|
+
} catch (err) {
|
|
540
|
+
log?.(`pty-tail: onPartial threw: ${(err as Error).message}`)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function emitActivityIfChanged(): void {
|
|
545
|
+
if (stopped) return
|
|
546
|
+
if (activityExtractor == null || onActivity == null) return
|
|
547
|
+
const text = activityExtractor.extract(term)
|
|
548
|
+
if (text == null) return
|
|
549
|
+
// Dedup: identical to last emission → skip.
|
|
550
|
+
if (text === lastEmittedActivity) return
|
|
551
|
+
lastEmittedActivity = text
|
|
552
|
+
lastActivityEmitAt = Date.now()
|
|
553
|
+
try {
|
|
554
|
+
onActivity(text)
|
|
555
|
+
} catch (err) {
|
|
556
|
+
log?.(`pty-tail: onActivity threw: ${(err as Error).message}`)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function scheduleActivityEmit(): void {
|
|
561
|
+
if (activityExtractor == null || onActivity == null) return
|
|
562
|
+
if (pendingActivity != null) return
|
|
563
|
+
const sinceLast = Date.now() - lastActivityEmitAt
|
|
564
|
+
if (sinceLast >= throttleMs) {
|
|
565
|
+
emitActivityIfChanged()
|
|
566
|
+
return
|
|
567
|
+
}
|
|
568
|
+
pendingActivity = setTimeout(() => {
|
|
569
|
+
pendingActivity = null
|
|
570
|
+
emitActivityIfChanged()
|
|
571
|
+
}, Math.max(0, throttleMs - sinceLast))
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function scheduleEmit(): void {
|
|
575
|
+
// Always try to surface tool activity alongside reply text. Independent
|
|
576
|
+
// throttle/dedup — activity changes on every new tool-call bullet, while
|
|
577
|
+
// reply text grows character-by-character.
|
|
578
|
+
scheduleActivityEmit()
|
|
579
|
+
if (pendingEmit != null) return
|
|
580
|
+
// Fire immediately if the throttle window is open (this is critical
|
|
581
|
+
// for first-paint latency — without this, the very first emit waits
|
|
582
|
+
// a full throttleMs even though there's nothing to throttle against).
|
|
583
|
+
const sinceLastEmit = Date.now() - lastEmitAt
|
|
584
|
+
if (sinceLastEmit >= throttleMs) {
|
|
585
|
+
emitIfChanged()
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
pendingEmit = setTimeout(() => {
|
|
589
|
+
pendingEmit = null
|
|
590
|
+
emitIfChanged()
|
|
591
|
+
}, Math.max(0, throttleMs - sinceLastEmit))
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function readNew(): void {
|
|
595
|
+
if (stopped) return
|
|
596
|
+
if (!existsSync(config.logFile)) return
|
|
597
|
+
let stat
|
|
598
|
+
try {
|
|
599
|
+
stat = statSync(config.logFile)
|
|
600
|
+
} catch {
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
if (stat.size < cursor) {
|
|
604
|
+
// Truncated/rotated — reset cursor
|
|
605
|
+
cursor = 0
|
|
606
|
+
log?.(`pty-tail: log file shrank from ${cursor} to ${stat.size} bytes — resetting cursor`)
|
|
607
|
+
}
|
|
608
|
+
if (stat.size === cursor) return
|
|
609
|
+
|
|
610
|
+
const toRead = stat.size - cursor
|
|
611
|
+
const buf = Buffer.alloc(toRead)
|
|
612
|
+
let fd: number
|
|
613
|
+
try {
|
|
614
|
+
fd = openSync(config.logFile, 'r')
|
|
615
|
+
} catch {
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
readSync(fd, buf, 0, toRead, cursor)
|
|
620
|
+
} finally {
|
|
621
|
+
closeSync(fd)
|
|
622
|
+
}
|
|
623
|
+
cursor = stat.size
|
|
624
|
+
|
|
625
|
+
// Feed into xterm. The Terminal.write() call queues bytes through
|
|
626
|
+
// the parser; subsequent buffer.active reads see the new state.
|
|
627
|
+
// Schedule the extraction on the throttle to coalesce rapid bursts.
|
|
628
|
+
term.write(buf, () => {
|
|
629
|
+
scheduleEmit()
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function attachWatcher(): void {
|
|
634
|
+
if (!existsSync(config.logFile)) return
|
|
635
|
+
if (watcher) return
|
|
636
|
+
let size = 0
|
|
637
|
+
try {
|
|
638
|
+
size = statSync(config.logFile).size
|
|
639
|
+
} catch {
|
|
640
|
+
// File vanished between existsSync and statSync — bail out, pollTimer
|
|
641
|
+
// will retry on the next tick.
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Preload the tail end of the log into the terminal emulator BEFORE we
|
|
646
|
+
// start tailing fresh bytes. Critical for Ink: its renderer uses
|
|
647
|
+
// *differential* updates (cursor-forward escapes for unchanged cells),
|
|
648
|
+
// so a fresh terminal starting at EOF sees `\e[1C` skipping over cells
|
|
649
|
+
// that Ink assumes already contain characters from a prior full frame.
|
|
650
|
+
// Without a baseline, marker strings like "switchroom-telegram" render
|
|
651
|
+
// with gaps (e.g. "switchroom te egram") and the extractor's substring
|
|
652
|
+
// check fails → no partials ever emit.
|
|
653
|
+
//
|
|
654
|
+
// 1 MB is deliberately generous: even a few seconds of steady Ink
|
|
655
|
+
// output is enough to capture a full-frame redraw that initializes
|
|
656
|
+
// every cell. We suppress the first onPartial by seeding
|
|
657
|
+
// `lastEmittedText` from the post-preload extract result, so the
|
|
658
|
+
// very next real extract (from a new tool call) is what actually
|
|
659
|
+
// fires onPartial.
|
|
660
|
+
const preloadBytes = Math.min(size, PRELOAD_BYTES)
|
|
661
|
+
const preloadFrom = size - preloadBytes
|
|
662
|
+
if (preloadBytes > 0) {
|
|
663
|
+
try {
|
|
664
|
+
const fd = openSync(config.logFile, 'r')
|
|
665
|
+
try {
|
|
666
|
+
const buf = Buffer.alloc(preloadBytes)
|
|
667
|
+
readSync(fd, buf, 0, preloadBytes, preloadFrom)
|
|
668
|
+
term.write(buf, () => {
|
|
669
|
+
// Seed lastEmittedText with whatever the extractor sees after
|
|
670
|
+
// the baseline is loaded. Any NEW render (newer than this
|
|
671
|
+
// baseline) will produce a different extract result and fire
|
|
672
|
+
// onPartial naturally.
|
|
673
|
+
const seeded = extractor.extract(term)
|
|
674
|
+
if (seeded != null) {
|
|
675
|
+
lastEmittedText = seeded
|
|
676
|
+
log?.(`pty-tail: preload seeded lastEmittedText (${seeded.length} chars)`)
|
|
677
|
+
}
|
|
678
|
+
})
|
|
679
|
+
} finally {
|
|
680
|
+
closeSync(fd)
|
|
681
|
+
}
|
|
682
|
+
} catch (err) {
|
|
683
|
+
log?.(`pty-tail: preload failed: ${(err as Error).message}`)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
cursor = size
|
|
688
|
+
log?.(`pty-tail: attached to ${config.logFile} (cursor=${cursor}, preloaded=${preloadBytes})`)
|
|
689
|
+
try {
|
|
690
|
+
watcher = watch(config.logFile, () => readNew())
|
|
691
|
+
} catch (err) {
|
|
692
|
+
log?.(`pty-tail: fs.watch failed (${(err as Error).message}), polling only`)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Initial scan + retry loop in case the log file doesn't exist yet.
|
|
697
|
+
// Poll interval is 200ms — short enough that fs.watch misses don't
|
|
698
|
+
// add visible latency, infrequent enough that idle CPU stays minimal.
|
|
699
|
+
attachWatcher()
|
|
700
|
+
pollTimer = setInterval(() => {
|
|
701
|
+
if (!watcher) attachWatcher()
|
|
702
|
+
readNew()
|
|
703
|
+
}, 200)
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
stop(): void {
|
|
707
|
+
stopped = true
|
|
708
|
+
if (watcher) {
|
|
709
|
+
try { watcher.close() } catch { /* ignore */ }
|
|
710
|
+
watcher = null
|
|
711
|
+
}
|
|
712
|
+
if (pollTimer) {
|
|
713
|
+
clearInterval(pollTimer)
|
|
714
|
+
pollTimer = null
|
|
715
|
+
}
|
|
716
|
+
if (pendingEmit) {
|
|
717
|
+
clearTimeout(pendingEmit)
|
|
718
|
+
pendingEmit = null
|
|
719
|
+
}
|
|
720
|
+
if (pendingActivity) {
|
|
721
|
+
clearTimeout(pendingActivity)
|
|
722
|
+
pendingActivity = null
|
|
723
|
+
}
|
|
724
|
+
try { term.dispose() } catch { /* ignore */ }
|
|
725
|
+
},
|
|
726
|
+
getCurrentText(): string | null {
|
|
727
|
+
return lastEmittedText
|
|
728
|
+
},
|
|
729
|
+
}
|
|
730
|
+
}
|