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,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared bot runtime helpers — extracted from gateway.ts so both the
|
|
3
|
+
* per-agent gateway and the foreman bot can share the same core plumbing
|
|
4
|
+
* without duplicating code.
|
|
5
|
+
*
|
|
6
|
+
* What lives here:
|
|
7
|
+
* - `createRobustApiCall` — thin re-export of createRetryApiCall pre-wired
|
|
8
|
+
* with stderr logging (mirrors how gateway.ts constructs `robustApiCall`).
|
|
9
|
+
* - `makeSwitchroomExec` / `makeSwitchroomExecCombined` — factory fns for
|
|
10
|
+
* the switchroom CLI exec helpers (callers pass their own CLI path / config
|
|
11
|
+
* env so each process can be configured independently).
|
|
12
|
+
* - `escapeHtmlForTg`, `preBlock`, `stripAnsi`, `formatSwitchroomOutput` —
|
|
13
|
+
* pure text-formatting helpers used by both gateways.
|
|
14
|
+
* - `makeSwitchroomReply` — factory that returns a `switchroomReply`-like
|
|
15
|
+
* function bound to a thread-resolver; gateway keeps its own resolver.
|
|
16
|
+
* - `runPollingLoop` — thin wrapper around the grammyjs/runner `run()` call
|
|
17
|
+
* with built-in 409 retry logic, matching the loop in gateway.ts.
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT: This module MUST NOT import anything from gateway.ts — the
|
|
20
|
+
* dependency is the other way around. Only import from grammy, node builtins,
|
|
21
|
+
* or other telegram-plugin/shared or telegram-plugin/*.ts modules.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { GrammyError, type Bot, type Context } from 'grammy'
|
|
25
|
+
import { run, type RunnerHandle } from '@grammyjs/runner'
|
|
26
|
+
import { execFileSync, spawnSync } from 'child_process'
|
|
27
|
+
import { createHash } from 'crypto'
|
|
28
|
+
import { clearStaleTelegramPollingState } from '../startup-reset.js'
|
|
29
|
+
import { createRetryApiCall } from '../retry-api-call.js'
|
|
30
|
+
|
|
31
|
+
// ─── tg-post observability transformer ────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Installs an API transformer on the bot that emits one stderr line per
|
|
35
|
+
* outbound Telegram Bot API POST. This is the single catchment point for
|
|
36
|
+
* correlating user-visible duplicate-message reports (switchroom #656,
|
|
37
|
+
* #657) against the actual outbound calls — the transformer runs inside
|
|
38
|
+
* grammY immediately before each HTTP POST and again on the response, so
|
|
39
|
+
* it sees every call regardless of whether it was routed through the
|
|
40
|
+
* `robustApiCall` retry helper or made directly via `bot.api.*`.
|
|
41
|
+
*
|
|
42
|
+
* Log shape (one line per POST, on both success and failure):
|
|
43
|
+
*
|
|
44
|
+
* tg-post method=<m> chat=<id> thread=<id|-> parse_mode=<HTML|MarkdownV2|none> bytes=<n> hash=<sha1-12> status=<ok|err> err=<class-or--> code=<http-or--> desc=<short|-->
|
|
45
|
+
*
|
|
46
|
+
* Body content is never logged — only its length and a 12-char sha1 prefix
|
|
47
|
+
* so we can recognise repeated identical sends without leaking PII. The
|
|
48
|
+
* `code` field carries the Telegram error_code (400/403/429/etc.) on
|
|
49
|
+
* failure and the short `desc` is the first ~80 chars of the API
|
|
50
|
+
* description — together these let us correlate "duplicate message"
|
|
51
|
+
* reports with the precise rejection reason (issue #657).
|
|
52
|
+
*
|
|
53
|
+
* Pure observability: no behaviour change, no error swallowing, no retry
|
|
54
|
+
* effects. The transformer always re-throws after logging.
|
|
55
|
+
*/
|
|
56
|
+
export function installTgPostLogger(bot: Bot): void {
|
|
57
|
+
bot.api.config.use(async (prev, method, payload, signal) => {
|
|
58
|
+
const p = (payload ?? {}) as Record<string, unknown>
|
|
59
|
+
const chat = p.chat_id != null ? String(p.chat_id) : '-'
|
|
60
|
+
const thread = p.message_thread_id != null ? String(p.message_thread_id) : '-'
|
|
61
|
+
const parseMode = (p.parse_mode as string | undefined) ?? 'none'
|
|
62
|
+
const text = typeof p.text === 'string' ? p.text : ''
|
|
63
|
+
const bytes = text.length
|
|
64
|
+
const hash = bytes > 0
|
|
65
|
+
? createHash('sha1').update(text).digest('hex').slice(0, 12)
|
|
66
|
+
: '-'
|
|
67
|
+
try {
|
|
68
|
+
const res = await prev(method, payload, signal)
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
`tg-post method=${method} chat=${chat} thread=${thread} parse_mode=${parseMode} bytes=${bytes} hash=${hash} status=ok err=- code=- desc=-\n`,
|
|
71
|
+
)
|
|
72
|
+
return res
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const errClass = err instanceof GrammyError
|
|
75
|
+
? `grammy_${(err as GrammyError).error_code}`
|
|
76
|
+
: (err as { constructor?: { name?: string } } | null)?.constructor?.name ?? 'Error'
|
|
77
|
+
const code = err instanceof GrammyError ? String((err as GrammyError).error_code) : '-'
|
|
78
|
+
const rawDesc = err instanceof GrammyError
|
|
79
|
+
? (err as GrammyError).description
|
|
80
|
+
: (err instanceof Error ? err.message : '')
|
|
81
|
+
// Sanitise the description for single-line log output — collapse
|
|
82
|
+
// whitespace, strip newlines, cap at 80 chars. PII-safe: Telegram
|
|
83
|
+
// error descriptions are server-generated and don't echo body.
|
|
84
|
+
const desc = rawDesc
|
|
85
|
+
? rawDesc.replace(/\s+/g, ' ').slice(0, 80).replace(/[\r\n]/g, ' ') || '-'
|
|
86
|
+
: '-'
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`tg-post method=${method} chat=${chat} thread=${thread} parse_mode=${parseMode} bytes=${bytes} hash=${hash} status=err err=${errClass} code=${code} desc=${desc}\n`,
|
|
89
|
+
)
|
|
90
|
+
throw err
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── robustApiCall factory ────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates a robust API call wrapper pre-wired with stderr logging.
|
|
99
|
+
* This is exactly how gateway.ts constructs its `robustApiCall`.
|
|
100
|
+
*
|
|
101
|
+
* Usage:
|
|
102
|
+
* const robustApiCall = createRobustApiCall()
|
|
103
|
+
*/
|
|
104
|
+
export function createRobustApiCall() {
|
|
105
|
+
return createRetryApiCall({
|
|
106
|
+
log: (line) => process.stderr.write(line),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── HTML escape helpers ─────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
export function escapeHtmlForTg(text: string): string {
|
|
113
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function preBlock(text: string): string {
|
|
117
|
+
return '<pre>' + escapeHtmlForTg(text) + '</pre>'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function stripAnsi(text: string): string {
|
|
121
|
+
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function formatSwitchroomOutput(output: string, maxLen = 4000): string {
|
|
125
|
+
const trimmed = output.trim()
|
|
126
|
+
if (trimmed.length <= maxLen) return trimmed
|
|
127
|
+
return trimmed.slice(0, maxLen - 20) + '\n... (truncated)'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── CLI exec factories ───────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
export interface CliConfig {
|
|
133
|
+
/** Path to the switchroom CLI binary. Defaults to 'switchroom'. */
|
|
134
|
+
cliPath?: string
|
|
135
|
+
/** Optional --config path forwarded to every CLI invocation. */
|
|
136
|
+
configPath?: string
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Returns a function that calls the CLI and returns stdout. */
|
|
140
|
+
export function makeSwitchroomExec(cfg: CliConfig = {}) {
|
|
141
|
+
const cli = cfg.cliPath ?? process.env.SWITCHROOM_CLI_PATH ?? 'switchroom'
|
|
142
|
+
const config = cfg.configPath ?? process.env.SWITCHROOM_CONFIG
|
|
143
|
+
|
|
144
|
+
return function switchroomExec(args: string[], timeoutMs = 15000): string {
|
|
145
|
+
const fullArgs = config ? ['--config', config, ...args] : args
|
|
146
|
+
return execFileSync(cli, fullArgs, {
|
|
147
|
+
encoding: 'utf-8',
|
|
148
|
+
timeout: timeoutMs,
|
|
149
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
150
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Returns a function that calls the CLI with stderr merged into stdout. */
|
|
156
|
+
export function makeSwitchroomExecCombined(cfg: CliConfig = {}) {
|
|
157
|
+
const cli = cfg.cliPath ?? process.env.SWITCHROOM_CLI_PATH ?? 'switchroom'
|
|
158
|
+
const config = cfg.configPath ?? process.env.SWITCHROOM_CONFIG
|
|
159
|
+
|
|
160
|
+
// Pre-#28 fix this used `execSync(\`${quoted} 2>&1\`, { shell: '/bin/bash' })`,
|
|
161
|
+
// hand-quoting each argument. The shell-quoting was correct today, but the
|
|
162
|
+
// structural shape meant any future caller passing user-controlled input
|
|
163
|
+
// would re-introduce a command-injection class of bug. spawnSync with
|
|
164
|
+
// argv array eliminates the shell entirely; we then concat stdout + stderr
|
|
165
|
+
// ourselves to preserve the merged-output contract callers depend on.
|
|
166
|
+
return function switchroomExecCombined(args: string[], timeoutMs = 15000): string {
|
|
167
|
+
const fullArgs = config ? ['--config', config, ...args] : args
|
|
168
|
+
const result = spawnSync(cli, fullArgs, {
|
|
169
|
+
encoding: 'utf-8',
|
|
170
|
+
timeout: timeoutMs,
|
|
171
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
172
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
173
|
+
})
|
|
174
|
+
const stdout = (result.stdout as string | undefined) ?? ''
|
|
175
|
+
const stderr = (result.stderr as string | undefined) ?? ''
|
|
176
|
+
const merged = stderr.length > 0 ? stdout + stderr : stdout
|
|
177
|
+
if (result.error) throw result.error
|
|
178
|
+
if (result.status !== 0) {
|
|
179
|
+
// Mirror execSync's behaviour: throw on non-zero exit, attaching the
|
|
180
|
+
// merged output so callers (which catch and inspect .stdout) can read it.
|
|
181
|
+
const err = new Error(`Command failed: ${cli} ${fullArgs.join(' ')}`) as Error & {
|
|
182
|
+
stdout?: string
|
|
183
|
+
stderr?: string
|
|
184
|
+
status?: number | null
|
|
185
|
+
}
|
|
186
|
+
err.stdout = merged
|
|
187
|
+
err.stderr = stderr
|
|
188
|
+
err.status = result.status
|
|
189
|
+
throw err
|
|
190
|
+
}
|
|
191
|
+
return merged
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Returns a CLI exec wrapper that parses JSON output (--json flag). */
|
|
196
|
+
export function makeSwitchroomExecJson(cfg: CliConfig = {}) {
|
|
197
|
+
const exec = makeSwitchroomExec(cfg)
|
|
198
|
+
return function switchroomExecJson<T = unknown>(args: string[]): T | null {
|
|
199
|
+
try {
|
|
200
|
+
const output = exec([...args, '--json'])
|
|
201
|
+
return JSON.parse(output) as T
|
|
202
|
+
} catch {
|
|
203
|
+
return null
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── Reply helper factory ─────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
import { InlineKeyboard } from 'grammy'
|
|
211
|
+
|
|
212
|
+
export type SwitchroomReplyMarkup =
|
|
213
|
+
| InlineKeyboard
|
|
214
|
+
| { force_reply: true; input_field_placeholder?: string; selective?: boolean }
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Creates a `switchroomReply` function that sends an HTML reply to the
|
|
218
|
+
* chat in `ctx`, optionally threaded.
|
|
219
|
+
*
|
|
220
|
+
* @param resolveThreadId - returns the thread ID to use for the given
|
|
221
|
+
* chat_id + optional explicit thread (mirrors gateway's resolveThreadId).
|
|
222
|
+
* Pass `() => undefined` for bots that don't use forum topics.
|
|
223
|
+
*/
|
|
224
|
+
export function makeSwitchroomReply(
|
|
225
|
+
resolveThreadId: (chatId: string, explicit?: number | null) => number | undefined,
|
|
226
|
+
) {
|
|
227
|
+
return async function switchroomReply(
|
|
228
|
+
ctx: Context,
|
|
229
|
+
text: string,
|
|
230
|
+
options: { html?: boolean; reply_markup?: SwitchroomReplyMarkup } = {},
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
const chatId = String(ctx.chat!.id)
|
|
233
|
+
const threadId = resolveThreadId(chatId, ctx.message?.message_thread_id)
|
|
234
|
+
await ctx.reply(text, {
|
|
235
|
+
...(threadId != null ? { message_thread_id: threadId } : {}),
|
|
236
|
+
...(options.html ? { parse_mode: 'HTML' as const, link_preview_options: { is_disabled: true } } : {}),
|
|
237
|
+
...(options.reply_markup ? { reply_markup: options.reply_markup } : {}),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ─── Polling loop ─────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
export interface PollingLoopCallbacks {
|
|
245
|
+
/** Fired once after `getMe()` on the first (non-409) attempt. */
|
|
246
|
+
onReady?: (botUsername: string, botId: number) => void | Promise<void>
|
|
247
|
+
/**
|
|
248
|
+
* Fired exactly once per process lifetime (not on 409 retries) after
|
|
249
|
+
* `onReady`. Use for one-time startup work (command registration, sweeps,
|
|
250
|
+
* intervals).
|
|
251
|
+
*/
|
|
252
|
+
onOneTimeSetup?: (botUsername: string) => void | Promise<void>
|
|
253
|
+
/** Fired when the polling loop exits cleanly (runner task resolved). */
|
|
254
|
+
onStop?: () => void | Promise<void>
|
|
255
|
+
/** Called each time a 409 is detected (useful for logging). */
|
|
256
|
+
on409?: (attempt: number, delayMs: number) => void
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Runs a grammyjs/runner polling loop with built-in 409 retry backoff,
|
|
261
|
+
* matching the loop structure in gateway.ts.
|
|
262
|
+
*
|
|
263
|
+
* Returns the RunnerHandle so callers can call `.stop()` on SIGTERM.
|
|
264
|
+
*
|
|
265
|
+
* The promise resolves when the polling loop exits cleanly.
|
|
266
|
+
* The promise rejects on non-409, non-Aborted errors.
|
|
267
|
+
*/
|
|
268
|
+
export async function runPollingLoop(
|
|
269
|
+
bot: Bot,
|
|
270
|
+
callbacks: PollingLoopCallbacks = {},
|
|
271
|
+
): Promise<void> {
|
|
272
|
+
let didOneTimeSetup = false
|
|
273
|
+
|
|
274
|
+
for (let attempt = 1; ; attempt++) {
|
|
275
|
+
try {
|
|
276
|
+
await clearStaleTelegramPollingState(bot.api)
|
|
277
|
+
|
|
278
|
+
const me = await bot.api.getMe()
|
|
279
|
+
process.stderr.write(`bot-runtime: polling as @${me.username}\n`)
|
|
280
|
+
|
|
281
|
+
if (callbacks.onReady) {
|
|
282
|
+
await callbacks.onReady(me.username ?? '', me.id)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!didOneTimeSetup) {
|
|
286
|
+
didOneTimeSetup = true
|
|
287
|
+
if (callbacks.onOneTimeSetup) {
|
|
288
|
+
await callbacks.onOneTimeSetup(me.username ?? '')
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
process.stderr.write(`bot-runtime: starting runner pid=${process.pid}\n`)
|
|
293
|
+
const handle: RunnerHandle = run(bot)
|
|
294
|
+
await handle.task()
|
|
295
|
+
if (callbacks.onStop) await callbacks.onStop()
|
|
296
|
+
return
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (err instanceof GrammyError && err.error_code === 409) {
|
|
299
|
+
const delay = Math.min(1000 * attempt, 15000)
|
|
300
|
+
if (callbacks.on409) callbacks.on409(attempt, delay)
|
|
301
|
+
process.stderr.write(
|
|
302
|
+
`bot-runtime: 409 Conflict attempt=${attempt} retry_in_ms=${delay}\n`,
|
|
303
|
+
)
|
|
304
|
+
await new Promise(r => setTimeout(r, delay))
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
if (err instanceof Error && err.message === 'Aborted delay') return
|
|
308
|
+
process.stderr.write(`bot-runtime: polling failed: ${err}\n`)
|
|
309
|
+
throw err
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ─── Access guard ─────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Returns true if the sender's user ID is in the allowFrom list.
|
|
318
|
+
* Used by both gateway and foreman for auth gating.
|
|
319
|
+
*/
|
|
320
|
+
export function isAllowedSender(ctx: Context, allowFrom: string[]): boolean {
|
|
321
|
+
const from = ctx.from
|
|
322
|
+
if (!from) return false
|
|
323
|
+
return allowFrom.includes(String(from.id))
|
|
324
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Silent-reply markers + allowlist guard.
|
|
3
|
+
*
|
|
4
|
+
* Lives in its own module (separate from server.ts) so that tests and
|
|
5
|
+
* other importers can pull these helpers in without booting the
|
|
6
|
+
* full MCP server — server.ts has top-level side effects (env load,
|
|
7
|
+
* TELEGRAM_BOT_TOKEN check, history.db open, session-tail spawn) that
|
|
8
|
+
* are inappropriate for a unit-test import boundary.
|
|
9
|
+
*
|
|
10
|
+
* Sprint1 review finding #6: an earlier revision of the reply /
|
|
11
|
+
* stream_reply tool handlers returned the silent-reply ack BEFORE
|
|
12
|
+
* calling `assertAllowedChat`, so unauthorised chats could bypass the
|
|
13
|
+
* outbound allowlist by having the agent emit `NO_REPLY`. The ack
|
|
14
|
+
* itself is a cross-chat signal (it confirms to the LLM that the chat
|
|
15
|
+
* exists and is reachable) even though no Telegram message is sent, so
|
|
16
|
+
* we must refuse disallowed chats *before* producing it. The
|
|
17
|
+
* guardSilentReply helper locks that ordering in.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const SILENT_REPLY_MARKERS = new Set(['NO_REPLY', 'HEARTBEAT_OK'])
|
|
21
|
+
|
|
22
|
+
// Derive the char-length bound from the marker set so adding a new
|
|
23
|
+
// marker doesn't silently desync with a hand-tuned constant.
|
|
24
|
+
const SILENT_REPLY_MAX_LEN = Math.max(
|
|
25
|
+
...Array.from(SILENT_REPLY_MARKERS, (m) => m.length),
|
|
26
|
+
) + 2 // small buffer for trailing punctuation callers might add accidentally
|
|
27
|
+
|
|
28
|
+
export function isSilentReplyMarker(text: string | undefined): boolean {
|
|
29
|
+
if (typeof text !== 'string') return false
|
|
30
|
+
const trimmed = text.trim()
|
|
31
|
+
if (trimmed.length === 0) return false
|
|
32
|
+
if (trimmed.length > SILENT_REPLY_MAX_LEN) return false
|
|
33
|
+
// Case-insensitive match: models occasionally emit `no_reply` or
|
|
34
|
+
// `NoReply`. Require letters/underscores/digits only so legitimate
|
|
35
|
+
// prose that happens to contain "NO_REPLY was suggested" still sends.
|
|
36
|
+
return SILENT_REPLY_MARKERS.has(trimmed.toUpperCase())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decide whether a `reply`/`stream_reply` invocation should be short-
|
|
41
|
+
* circuited as a silent-reply ack, enforcing the allowlist FIRST.
|
|
42
|
+
*
|
|
43
|
+
* `assertAllowed` throws when `chat_id` is not on the allowlist; callers
|
|
44
|
+
* let that propagate so the MCP tool call fails loudly.
|
|
45
|
+
*/
|
|
46
|
+
export function guardSilentReply(params: {
|
|
47
|
+
chat_id: string
|
|
48
|
+
text: string | undefined
|
|
49
|
+
hasFiles: boolean
|
|
50
|
+
assertAllowed: (chat_id: string) => void
|
|
51
|
+
}): { kind: 'silent'; markerText: string } | { kind: 'continue' } {
|
|
52
|
+
const { chat_id, text, hasFiles, assertAllowed } = params
|
|
53
|
+
if (hasFiles) return { kind: 'continue' }
|
|
54
|
+
if (!isSilentReplyMarker(text)) return { kind: 'continue' }
|
|
55
|
+
// Allowlist check BEFORE returning the ack — see docblock above.
|
|
56
|
+
assertAllowed(chat_id)
|
|
57
|
+
return { kind: 'silent', markerText: (text as string).trim() }
|
|
58
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot-banner driver — executes the BannerAction state transition
|
|
3
|
+
* against a Telegram Bot API. Extracted from gateway.ts so the
|
|
4
|
+
* dispatch is testable end-to-end via `tests/fake-bot-api.ts`.
|
|
5
|
+
*
|
|
6
|
+
* The pure decision lives in `slot-banner.ts` (decideBannerAction).
|
|
7
|
+
* This module is the side-effecting half: takes a `bot` dependency,
|
|
8
|
+
* executes the action, returns the next state. The state itself
|
|
9
|
+
* stays in the caller (gateway.ts holds a module-global `let
|
|
10
|
+
* pinnedBannerState` and re-passes it on every call).
|
|
11
|
+
*
|
|
12
|
+
* Error-handling contract: API failures are reported via `onError`
|
|
13
|
+
* but never throw. The caller decides logging cadence (gateway logs
|
|
14
|
+
* to stderr; tests can assert via the callback). On a pin failure
|
|
15
|
+
* mid-sequence (sendMessage succeeded but pinChatMessage failed),
|
|
16
|
+
* the prior state is preserved so we don't claim ownership of an
|
|
17
|
+
* unpinned message.
|
|
18
|
+
*
|
|
19
|
+
* See #421 (banner pin lifecycle) and JTBD
|
|
20
|
+
* `reference/track-plan-quota-live.md` ("at a glance").
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { BannerState } from './slot-banner.js';
|
|
24
|
+
import { decideBannerAction } from './slot-banner.js';
|
|
25
|
+
|
|
26
|
+
/** Minimal subset of grammy's `bot.api` we depend on. Letting tests
|
|
27
|
+
* swap in `fake-bot-api.ts` without dragging in the full Bot type. */
|
|
28
|
+
export interface BannerBotApi {
|
|
29
|
+
sendMessage(
|
|
30
|
+
chat_id: string | number,
|
|
31
|
+
text: string,
|
|
32
|
+
opts?: Record<string, unknown>,
|
|
33
|
+
): Promise<{ message_id: number }>;
|
|
34
|
+
editMessageText(
|
|
35
|
+
chat_id: string | number,
|
|
36
|
+
message_id: number,
|
|
37
|
+
text: string,
|
|
38
|
+
opts?: Record<string, unknown>,
|
|
39
|
+
): Promise<unknown>;
|
|
40
|
+
pinChatMessage(
|
|
41
|
+
chat_id: string | number,
|
|
42
|
+
message_id: number,
|
|
43
|
+
opts?: Record<string, unknown>,
|
|
44
|
+
): Promise<unknown>;
|
|
45
|
+
unpinChatMessage(
|
|
46
|
+
chat_id: string | number,
|
|
47
|
+
message_id: number,
|
|
48
|
+
): Promise<unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface BannerBot {
|
|
52
|
+
api: BannerBotApi;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RefreshBannerArgs {
|
|
56
|
+
bot: BannerBot;
|
|
57
|
+
ownerChatId: string;
|
|
58
|
+
agentName: string;
|
|
59
|
+
/** Active slot reported by `currentActiveSlot(agentDir)`. `null`
|
|
60
|
+
* means we couldn't read one — treated like default state (unpins
|
|
61
|
+
* any existing banner; never pins). */
|
|
62
|
+
currentSlot: string | null;
|
|
63
|
+
defaultSlot: string;
|
|
64
|
+
/** State the gateway is holding from the last call. Pass `null`
|
|
65
|
+
* on first call. */
|
|
66
|
+
prevState: BannerState | null;
|
|
67
|
+
/** Optional API-failure observer. Phase identifies which Bot API
|
|
68
|
+
* call failed so the caller can log meaningfully. Default: silent. */
|
|
69
|
+
onError?: (phase: 'pin' | 'edit' | 'unpin', err: unknown) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Execute the next banner-state transition. Returns the new
|
|
74
|
+
* `BannerState` (or `null` when unpinned). Always resolves; never
|
|
75
|
+
* throws — API errors are routed through `onError`.
|
|
76
|
+
*
|
|
77
|
+
* On pin-mid-sequence failure (sendMessage succeeded but
|
|
78
|
+
* pinChatMessage failed), the function returns the *prior* state
|
|
79
|
+
* unchanged. Otherwise the gateway would track a message_id it
|
|
80
|
+
* never managed to pin, and the next refresh would think a banner
|
|
81
|
+
* exists and try to edit/unpin it.
|
|
82
|
+
*/
|
|
83
|
+
export async function refreshBanner(
|
|
84
|
+
args: RefreshBannerArgs,
|
|
85
|
+
): Promise<BannerState | null> {
|
|
86
|
+
const action = decideBannerAction(
|
|
87
|
+
args.prevState,
|
|
88
|
+
args.currentSlot,
|
|
89
|
+
args.agentName,
|
|
90
|
+
args.defaultSlot,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (action.kind === 'noop') return args.prevState;
|
|
94
|
+
|
|
95
|
+
if (action.kind === 'unpin') {
|
|
96
|
+
try {
|
|
97
|
+
await args.bot.api.unpinChatMessage(args.ownerChatId, action.messageId);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
args.onError?.('unpin', err);
|
|
100
|
+
}
|
|
101
|
+
// Even if unpin failed, drop our claim — the message may have been
|
|
102
|
+
// unpinned out-of-band (operator did it manually) and re-pinning
|
|
103
|
+
// would be more confusing than surfacing it again later.
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (action.kind === 'pin') {
|
|
108
|
+
let sent: { message_id: number };
|
|
109
|
+
try {
|
|
110
|
+
sent = await args.bot.api.sendMessage(args.ownerChatId, action.text, {
|
|
111
|
+
parse_mode: 'HTML',
|
|
112
|
+
link_preview_options: { is_disabled: true },
|
|
113
|
+
});
|
|
114
|
+
} catch (err) {
|
|
115
|
+
args.onError?.('pin', err);
|
|
116
|
+
return args.prevState;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await args.bot.api.pinChatMessage(args.ownerChatId, sent.message_id, {
|
|
120
|
+
disable_notification: true,
|
|
121
|
+
});
|
|
122
|
+
} catch (err) {
|
|
123
|
+
args.onError?.('pin', err);
|
|
124
|
+
// sendMessage succeeded but pin failed — don't claim the message.
|
|
125
|
+
return args.prevState;
|
|
126
|
+
}
|
|
127
|
+
return { messageId: sent.message_id, slot: action.slot };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// action.kind === 'edit'
|
|
131
|
+
try {
|
|
132
|
+
await args.bot.api.editMessageText(
|
|
133
|
+
args.ownerChatId,
|
|
134
|
+
action.messageId,
|
|
135
|
+
action.text,
|
|
136
|
+
{
|
|
137
|
+
parse_mode: 'HTML',
|
|
138
|
+
link_preview_options: { is_disabled: true },
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
return { messageId: action.messageId, slot: action.slot };
|
|
142
|
+
} catch (err) {
|
|
143
|
+
args.onError?.('edit', err);
|
|
144
|
+
// Edit failed — keep the prior state so the next refresh tries again.
|
|
145
|
+
return args.prevState;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned slot banner — pure decision logic.
|
|
3
|
+
*
|
|
4
|
+
* The gateway pins a banner in the owner chat when the agent is
|
|
5
|
+
* running on a non-default account slot (e.g. after auto-fallback
|
|
6
|
+
* swapped away from `default`). The banner unpins when the agent
|
|
7
|
+
* returns to `default`. This gives the user an always-visible answer
|
|
8
|
+
* to "what slot am I on right now?" exactly when it's not what they
|
|
9
|
+
* expect, and zero noise when everything is normal.
|
|
10
|
+
*
|
|
11
|
+
* This module is dependency-free so it's testable in isolation; the
|
|
12
|
+
* gateway translates `BannerAction` into actual Telegram API calls.
|
|
13
|
+
*
|
|
14
|
+
* v1 scope: one banner per gateway process, in the owner chat
|
|
15
|
+
* (access.allowFrom[0]). Per-topic forum support and multi-chat
|
|
16
|
+
* pinning are tracked as #421 follow-ups.
|
|
17
|
+
*
|
|
18
|
+
* See #421 (Switchroom).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type BannerState = {
|
|
22
|
+
/** Telegram message_id of the currently pinned banner. */
|
|
23
|
+
messageId: number;
|
|
24
|
+
/** The slot name shown by the pinned message — used to skip
|
|
25
|
+
* redundant edits when the slot hasn't changed. */
|
|
26
|
+
slot: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type BannerAction =
|
|
30
|
+
| { kind: 'noop'; reason: string }
|
|
31
|
+
/** Pin a fresh banner. Caller sends + pins, then records the
|
|
32
|
+
* resulting message_id back into BannerState. */
|
|
33
|
+
| { kind: 'pin'; text: string; slot: string }
|
|
34
|
+
/** Edit the existing pinned banner's text. */
|
|
35
|
+
| { kind: 'edit'; messageId: number; text: string; slot: string }
|
|
36
|
+
/** Unpin + forget. Caller unpins (best-effort) and clears state. */
|
|
37
|
+
| { kind: 'unpin'; messageId: number };
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decide what to do with the banner given the current active slot,
|
|
41
|
+
* the default slot, and the previously-pinned banner state.
|
|
42
|
+
*/
|
|
43
|
+
export function decideBannerAction(
|
|
44
|
+
prev: BannerState | null,
|
|
45
|
+
currentSlot: string | null,
|
|
46
|
+
agentName: string,
|
|
47
|
+
defaultSlot: string,
|
|
48
|
+
): BannerAction {
|
|
49
|
+
// Default state (or no slot yet): no banner needed. If one is
|
|
50
|
+
// pinned from a prior failover, unpin so the chat is clean again.
|
|
51
|
+
if (currentSlot === null || currentSlot === defaultSlot) {
|
|
52
|
+
if (prev) return { kind: 'unpin', messageId: prev.messageId };
|
|
53
|
+
return { kind: 'noop', reason: 'on default slot, nothing pinned' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Non-default state. Either pin fresh or edit existing.
|
|
57
|
+
const text = formatBannerHtml(agentName, currentSlot, defaultSlot);
|
|
58
|
+
if (!prev) return { kind: 'pin', text, slot: currentSlot };
|
|
59
|
+
if (prev.slot === currentSlot) {
|
|
60
|
+
return { kind: 'noop', reason: 'banner already shows current slot' };
|
|
61
|
+
}
|
|
62
|
+
return { kind: 'edit', messageId: prev.messageId, text, slot: currentSlot };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The banner body. Kept short — the user reads this at a glance,
|
|
67
|
+
* and pinned messages eat vertical space at the top of the chat.
|
|
68
|
+
*/
|
|
69
|
+
export function formatBannerHtml(
|
|
70
|
+
agentName: string,
|
|
71
|
+
currentSlot: string,
|
|
72
|
+
defaultSlot: string,
|
|
73
|
+
): string {
|
|
74
|
+
return [
|
|
75
|
+
`📌 <b>${escapeHtml(agentName)}</b> is running on slot <code>${escapeHtml(currentSlot)}</code>`,
|
|
76
|
+
`<i>(failover from <code>${escapeHtml(defaultSlot)}</code>)</i>`,
|
|
77
|
+
].join(' ');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function escapeHtml(text: string): string {
|
|
81
|
+
return text
|
|
82
|
+
.replace(/&/g, '&')
|
|
83
|
+
.replace(/</g, '<')
|
|
84
|
+
.replace(/>/g, '>')
|
|
85
|
+
.replace(/"/g, '"');
|
|
86
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// MCP-server entry shim. Resolved by `bun run start` (see package.json).
|
|
3
|
+
//
|
|
4
|
+
// Strategic packaging fix (#634): the production MCP launcher invokes
|
|
5
|
+
// `bun run --cwd <pluginDir> --silent start`, which executes this file.
|
|
6
|
+
// We prefer the bundled `dist/server.js` because the npm package's
|
|
7
|
+
// `files` array doesn't include `src/` — direct `.ts` execution from
|
|
8
|
+
// the global install fails with `Cannot find module '../../src/...'`
|
|
9
|
+
// against the bundle's cross-imports. The bundle resolves them at
|
|
10
|
+
// build time so dist runs everywhere.
|
|
11
|
+
//
|
|
12
|
+
// Dev workspaces that haven't run `bun run build` yet (or operators
|
|
13
|
+
// running pre-#634 packages) fall back to the .ts source. The fallback
|
|
14
|
+
// is the legacy behavior — preserves dev ergonomics where editing
|
|
15
|
+
// .ts and restarting an agent picks up changes without an explicit
|
|
16
|
+
// build step (modulo the documented `bun run build` cycle).
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { resolve, dirname } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const distPath = resolve(here, "dist/server.js");
|
|
23
|
+
const sourcePath = resolve(here, "server.ts");
|
|
24
|
+
|
|
25
|
+
const target = existsSync(distPath) ? distPath : sourcePath;
|
|
26
|
+
await import(target);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clear stale Telegram polling state on gateway startup.
|
|
3
|
+
*
|
|
4
|
+
* The Telegram Bot API tracks one active long-poll session per bot
|
|
5
|
+
* token. If the previous gateway process crashed mid-poll (e.g. systemd
|
|
6
|
+
* killed it with SIGKILL after a start-timeout, which is exactly what
|
|
7
|
+
* happened to klanker on 2026-04-21 at 16:35 AEST), the API-side slot
|
|
8
|
+
* can remain occupied for several minutes. The new gateway then races
|
|
9
|
+
* that orphan on every getUpdates call and gets back a 409 Conflict
|
|
10
|
+
* ("terminated by other getUpdates request"). Grammy retries with
|
|
11
|
+
* backoff but the loop can persist for hours.
|
|
12
|
+
*
|
|
13
|
+
* deleteWebhook is a no-op when no webhook is configured but it also
|
|
14
|
+
* invalidates any active long-poll claim. Combined with
|
|
15
|
+
* drop_pending_updates, it gives us a clean slate regardless of how
|
|
16
|
+
* the previous process died. Safe to call on every startup — it's
|
|
17
|
+
* idempotent and has no user-visible side effects beyond clearing the
|
|
18
|
+
* (probably-empty) pending-updates queue.
|
|
19
|
+
*
|
|
20
|
+
* Reference: reference/restart-and-know-what-im-running.md — "silent
|
|
21
|
+
* respawn. Agent comes back and the user has to guess whether it's
|
|
22
|
+
* the same agent." A gateway stuck in a 409 loop is exactly that
|
|
23
|
+
* failure mode.
|
|
24
|
+
*
|
|
25
|
+
* Lives in a separate module (not gateway.ts) so the test file can
|
|
26
|
+
* import it without firing gateway.ts's top-level IIFE that starts
|
|
27
|
+
* the bot.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export interface DeleteWebhookCapable {
|
|
31
|
+
deleteWebhook: (opts: { drop_pending_updates: boolean }) => Promise<unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function clearStaleTelegramPollingState(
|
|
35
|
+
api: DeleteWebhookCapable,
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
try {
|
|
38
|
+
await api.deleteWebhook({ drop_pending_updates: true });
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Best-effort — if deleteWebhook fails the runner will still start
|
|
41
|
+
// and grammy's built-in 409 handling kicks in. Log so we can
|
|
42
|
+
// diagnose if this becomes a recurring failure.
|
|
43
|
+
process.stderr.write(`telegram gateway: deleteWebhook on startup failed: ${err}\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|