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,340 @@
|
|
|
1
|
+
# Telegram test harness
|
|
2
|
+
|
|
3
|
+
How to write deterministic integration tests for switchroom code that
|
|
4
|
+
talks to Telegram. Use this when you're touching anything that calls
|
|
5
|
+
`bot.api.*` or processes incoming Telegram updates.
|
|
6
|
+
|
|
7
|
+
## What's in the box
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| [`fake-bot-api.ts`](./fake-bot-api.ts) | Full mock of `bot.api.*`. Tracks chat model (sent[], pinned, reactions, deleted), supports fault injection with real `GrammyError` shapes, optional `holdNext` for in-flight ordering tests, optional `validateParseMode` for catching malformed MarkdownV2. **Use this for sequence/lifecycle tests.** |
|
|
12
|
+
| [`bot-api.harness.ts`](./bot-api.harness.ts) | Lighter mock — just `vi.fn()` stubs with sensible defaults. **Use this when you only need to assert on call shapes**, not chat-model state. |
|
|
13
|
+
| [`update-factory.ts`](./update-factory.ts) | Typed factories for Telegram `Update` objects: text messages, edited messages, message reactions, callback queries, photos, documents, my_chat_member events, forum-topic messages. |
|
|
14
|
+
| [`waiting-ux-harness.ts`](./waiting-ux-harness.ts) | Phase 1: real `StatusReactionController` + real `ProgressDriver` + recording fake bot + fake clock. Pin the four waiting-UX deadlines (Class A/B/C/F1–F4) under `vi.useFakeTimers()`. |
|
|
15
|
+
| [`real-gateway-harness.ts`](./real-gateway-harness.ts) | Phase 3: wraps `waiting-ux-harness` with the real production `InboundCoalescer` and real `flushOnAgentDisconnect`. IPC lifecycle simulation (`bridgeConnect`/`bridgeDisconnect`), opt-in `withDedup` for replay-suppression tests. **The default home for new lifecycle/timing tests.** |
|
|
16
|
+
| [`fake-bot-api.test.ts`](./fake-bot-api.test.ts) | Self-test of the fake bot — if this ever breaks, every test that depends on it is suspect. |
|
|
17
|
+
|
|
18
|
+
## Validation rule for new tests
|
|
19
|
+
|
|
20
|
+
**Every regression test must carry a `// fails when:` comment**
|
|
21
|
+
indicating the production change that would break the invariant. Then
|
|
22
|
+
mentally `git stash` that change and confirm the test fails. Without
|
|
23
|
+
this round-trip the test is theatre — it asserts what the code already
|
|
24
|
+
does, not what it must continue to do.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
it('👍 fires AT-OR-AFTER last delivery', async () => {
|
|
30
|
+
// fails when: a future refactor moves setDone() from the streamReply
|
|
31
|
+
// post-await branch back to the JSONL turn_end handler — exactly
|
|
32
|
+
// Bug D's failure mode.
|
|
33
|
+
...
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Decision: which mock?
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Are you asserting on a sequence/state that spans multiple API calls?
|
|
41
|
+
├── yes → fake-bot-api.ts
|
|
42
|
+
│ (use bot.messagesIn(), bot.isPinned(), bot.faults.next(...))
|
|
43
|
+
└── no → bot-api.harness.ts
|
|
44
|
+
(use bot.api.sendMessage.mock.calls, .toHaveBeenCalledWith)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The two are intentionally separate so existing tests don't pay the
|
|
48
|
+
chat-model overhead and new tests don't lose realism.
|
|
49
|
+
|
|
50
|
+
## Patterns
|
|
51
|
+
|
|
52
|
+
### Pattern 1 — assert on outbound API calls
|
|
53
|
+
|
|
54
|
+
Most common. You're testing a function that sends/edits messages.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createFakeBotApi, errors } from './fake-bot-api.js';
|
|
58
|
+
|
|
59
|
+
it('pins a banner when slot changes', async () => {
|
|
60
|
+
const bot = createFakeBotApi();
|
|
61
|
+
await refreshBanner({ bot, ownerChatId: 'c', /* ... */ });
|
|
62
|
+
expect(bot.api.sendMessage).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(bot.api.pinChatMessage).toHaveBeenCalledTimes(1);
|
|
64
|
+
expect(bot.isPinned('c', 500)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Pattern 2 — drive a real grammy bot via injected updates
|
|
69
|
+
|
|
70
|
+
When you want to test the *dispatcher* (which command handler fires for
|
|
71
|
+
which Update). The pattern: create a real `new Bot(token, { client })`
|
|
72
|
+
with a fetch-shim, then call `bot.handleUpdate(makeMessageUpdate(...))`.
|
|
73
|
+
|
|
74
|
+
See `streaming-e2e.test.ts` for a worked example. (Not always needed —
|
|
75
|
+
most tests can target the extracted handler directly with a fake bot.)
|
|
76
|
+
|
|
77
|
+
### Pattern 3 — fault injection
|
|
78
|
+
|
|
79
|
+
`fake-bot-api.ts` ships pre-built error factories matching real
|
|
80
|
+
GrammyError shapes:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
bot.faults.next('sendMessage', errors.floodWait(15)); // 429
|
|
84
|
+
bot.faults.next('editMessageText', errors.notModified()); // 400
|
|
85
|
+
bot.faults.next('pinChatMessage', errors.forbidden()); // 403
|
|
86
|
+
bot.faults.next('sendMessage', errors.networkError()); // fetch fail
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Faults are FIFO per method. Pull-once semantics — a fault fires on the
|
|
90
|
+
next matching call and is consumed.
|
|
91
|
+
|
|
92
|
+
### Pattern 4 — time control
|
|
93
|
+
|
|
94
|
+
Pair `vi.useFakeTimers()` with `microtaskFlush()` from
|
|
95
|
+
`bot-api.harness.ts` for deterministic async settling:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
vi.useFakeTimers();
|
|
99
|
+
fireStreamReply({ chat_id: 'c', text: 'partial' }); // doesn't await
|
|
100
|
+
await microtaskFlush();
|
|
101
|
+
vi.advanceTimersByTime(300);
|
|
102
|
+
await microtaskFlush();
|
|
103
|
+
expect(bot.api.editMessageText).toHaveBeenCalled();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Pattern 5 — multi-chat / forum-topic isolation
|
|
107
|
+
|
|
108
|
+
`update-factory.ts` exposes both private and forum chat defaults:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { makeMessageUpdate, makeTopicMessageUpdate } from './update-factory.js';
|
|
112
|
+
|
|
113
|
+
const dm = makeMessageUpdate({ text: '/auth status' });
|
|
114
|
+
const topicA = makeTopicMessageUpdate({ text: '/auth status', message_thread_id: 10 });
|
|
115
|
+
const topicB = makeTopicMessageUpdate({ text: '/auth status', message_thread_id: 20 });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`fake-bot-api.ts` keys its chat-model by `chat_id`, not `(chat_id,
|
|
119
|
+
thread_id)`. For per-thread isolation tests, assert on the `args` of
|
|
120
|
+
the call (e.g. `expect(call[2].message_thread_id).toBe(10)`).
|
|
121
|
+
|
|
122
|
+
## Coverage gaps & TODOs
|
|
123
|
+
|
|
124
|
+
These are deliberately not covered by the harness today; revisit when
|
|
125
|
+
the underlying feature lands or stabilises:
|
|
126
|
+
|
|
127
|
+
- **#479 — pre-alloc placeholder in groups**: write the test once
|
|
128
|
+
PR #487's gateway fix lands. Without the fix, asserting "placeholder
|
|
129
|
+
fires in groups" fails on main.
|
|
130
|
+
- **Forum-topic per-pin isolation**: `slot-banner.ts` is single-chat,
|
|
131
|
+
single-banner per gateway process (v1 scope of #421). When per-topic
|
|
132
|
+
pinning lands, extend `BannerState` to be keyed by `(chat,thread)` and
|
|
133
|
+
add isolation tests.
|
|
134
|
+
- **Real Telegram rendering** (markdown/HTML parse, link previews,
|
|
135
|
+
emoji reflow): not catchable by any HTTP-level mock. A nightly real-
|
|
136
|
+
test-bot smoke job is the proper home; out of scope for this harness.
|
|
137
|
+
|
|
138
|
+
## Where to add new e2e tests
|
|
139
|
+
|
|
140
|
+
Naming convention: `<feature>.e2e.test.ts` for tests that drive an
|
|
141
|
+
extracted handler against `fake-bot-api.ts`. Examples already in repo:
|
|
142
|
+
|
|
143
|
+
- `slot-banner-driver.e2e.test.ts` — banner pin/edit/unpin lifecycle
|
|
144
|
+
- `auto-fallback-dispatcher.e2e.test.ts` — quota notification dispatch
|
|
145
|
+
- `streaming-e2e.test.ts` — PTY → stream_reply → done sequencing
|
|
146
|
+
|
|
147
|
+
Keep pure-logic tests in `<feature>.test.ts` (no `.e2e`). Examples:
|
|
148
|
+
|
|
149
|
+
- `slot-banner.test.ts` — pure `decideBannerAction` state machine
|
|
150
|
+
- `auto-fallback.test.ts` — pure `evaluateFallbackTrigger` /
|
|
151
|
+
`performAutoFallback` plan logic
|
|
152
|
+
- `auth-slot-commands.test.ts` — `parseAuthSubCommand` decoder
|
|
153
|
+
|
|
154
|
+
The split keeps the e2e tests fast (no harness boot per pure-logic
|
|
155
|
+
case) and the pure tests honest (no accidental coupling to bot calls).
|
|
156
|
+
|
|
157
|
+
## Anti-patterns
|
|
158
|
+
|
|
159
|
+
- **Don't hand-roll a `BannerState` with an arbitrary `messageId`** and
|
|
160
|
+
expect editMessageText to succeed. The fake bot tracks sent ids and
|
|
161
|
+
throws `messageToEditNotFound` for unknown ones (this is realistic).
|
|
162
|
+
Either send a message first to seed the chat model, or use the
|
|
163
|
+
natural sequence (call refreshBanner once to pin, then test the next
|
|
164
|
+
transition).
|
|
165
|
+
- **Don't bypass `fake-bot-api.ts` and patch `globalThis.fetch`** to
|
|
166
|
+
intercept the real Bot API. Tests that do this couple to grammy
|
|
167
|
+
internals and break on grammy version bumps.
|
|
168
|
+
- **Don't assert on the entire Telegram payload** — assert on the
|
|
169
|
+
semantic fields (chat_id, text, parse_mode). Bot API adds optional
|
|
170
|
+
fields over time and full-payload snapshots churn.
|
|
171
|
+
|
|
172
|
+
## Pattern 7 — `holdNext`: park a call mid-flight
|
|
173
|
+
|
|
174
|
+
Some bugs are about ordering between an in-flight outbound and an
|
|
175
|
+
inbound event — the canonical example is Bug D (👍 fired while
|
|
176
|
+
`editMessageText` was still pending). Asserting "X happens BEFORE Y
|
|
177
|
+
resolves" with `vi.advanceTimersByTime` is fragile because the
|
|
178
|
+
production code's await boundaries shift with refactors.
|
|
179
|
+
|
|
180
|
+
`holdNext` parks the next matching call at a gate. The test fires the
|
|
181
|
+
unrelated event while the call is parked, then explicitly releases:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { createFakeBotApi } from './fake-bot-api.js'
|
|
185
|
+
|
|
186
|
+
const bot = createFakeBotApi()
|
|
187
|
+
const r = await bot.api.sendMessage('c1', 'long enough text content', {})
|
|
188
|
+
|
|
189
|
+
// Park the next editMessageText call.
|
|
190
|
+
const hold = bot.holdNext('editMessageText', 'c1')
|
|
191
|
+
|
|
192
|
+
// Start the edit — promise pending until release.
|
|
193
|
+
const editPromise = bot.api.editMessageText('c1', r.message_id, 'updated', {})
|
|
194
|
+
await Promise.resolve() // let the call enter its await
|
|
195
|
+
|
|
196
|
+
expect(hold.triggered()).toBe(true)
|
|
197
|
+
|
|
198
|
+
// Fire the unrelated event while the edit is parked.
|
|
199
|
+
await bot.api.setMessageReaction('c1', r.message_id, [{ type: 'emoji', emoji: '👍' }])
|
|
200
|
+
expect(bot.state.reactions.length).toBe(1)
|
|
201
|
+
// Edit hasn't landed yet:
|
|
202
|
+
expect(bot.textOf(r.message_id)).toBe('long enough text content')
|
|
203
|
+
|
|
204
|
+
// Release — edit completes.
|
|
205
|
+
hold.release()
|
|
206
|
+
await editPromise
|
|
207
|
+
expect(bot.textOf(r.message_id)).toBe('updated')
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`hold.release()` is the happy path; `hold.fail(err)` rejects the held
|
|
211
|
+
call with `err` if the test wants to simulate an in-flight failure.
|
|
212
|
+
`hold.triggered()` returns true once the held call enters its await —
|
|
213
|
+
useful for confirming "yes, the call is parked here" before firing the
|
|
214
|
+
follow-up event.
|
|
215
|
+
|
|
216
|
+
Holds are FIFO per method, just like faults. `bot.reset()` rejects any
|
|
217
|
+
unreleased holds so a leaked hold from one test doesn't hang the next.
|
|
218
|
+
|
|
219
|
+
## Pattern 8 — wired-in `OutboundDedupCache` for replay tests
|
|
220
|
+
|
|
221
|
+
The #546 bug class is "two paths emit the same content." The fix is
|
|
222
|
+
`OutboundDedupCache` (`telegram-plugin/recent-outbound-dedup.ts`) — a
|
|
223
|
+
process-wide cache keyed by `(chatId, threadId)` that suppresses
|
|
224
|
+
duplicate normalized content within a TTL.
|
|
225
|
+
|
|
226
|
+
The real-gateway harness wires this in opt-in:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { createRealGatewayHarness } from './real-gateway-harness.js'
|
|
230
|
+
|
|
231
|
+
const h = createRealGatewayHarness({ withDedup: true })
|
|
232
|
+
const r1 = await h.send({ chat_id: CHAT, text: 'long content...' })
|
|
233
|
+
const r2 = await h.send({ chat_id: CHAT, text: 'long content...' }) // suppressed
|
|
234
|
+
expect(r2).toBeNull()
|
|
235
|
+
expect(h.dedupSuppressedCount()).toBe(1)
|
|
236
|
+
expect(h.dedup!.size(Date.now())).toBe(1)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`harness.send()` is the dedup-aware "fresh send" path — always issues
|
|
240
|
+
a new `sendMessage`, never edits. Use it in replay-dup tests where
|
|
241
|
+
"two messages with the same content" means two distinct user-visible
|
|
242
|
+
messages, not a streaming edit-in-place. (For streaming-edit
|
|
243
|
+
behavior, use `harness.streamReply()` as before.)
|
|
244
|
+
|
|
245
|
+
`simulateRetryDup({ chat_id, text })` is a one-line scenario for the
|
|
246
|
+
full #546 reproducer: send → bridge cycle → send again → assert
|
|
247
|
+
suppressed. See `real-gateway-i6-turn-flush-replay-dedup.test.ts`.
|
|
248
|
+
|
|
249
|
+
## Pattern 9 — opt-in `validateParseMode` lenient validator
|
|
250
|
+
|
|
251
|
+
Real Telegram returns 400 on unbalanced MarkdownV2. The fake accepts
|
|
252
|
+
any string by default to keep 167 existing tests passing. New tests
|
|
253
|
+
opt in:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const bot = createFakeBotApi({ validateParseMode: 'lenient' })
|
|
257
|
+
await expect(
|
|
258
|
+
bot.api.sendMessage('c1', '*unbalanced markdown', { parse_mode: 'MarkdownV2' }),
|
|
259
|
+
).rejects.toMatchObject({ error_code: 400 })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Lenient mode catches the most common failure: unbalanced count of
|
|
263
|
+
marker characters (`*`, `_`, `` ` ``, `[`). Backslash-escaped markers
|
|
264
|
+
and content inside inline-code / fenced code blocks are exempt. It is
|
|
265
|
+
NOT a full Telegram parser — corner cases like nested entities or
|
|
266
|
+
custom emoji escapes won't trigger it. For those, use a real-DC
|
|
267
|
+
nightly job (out of scope for the in-process fake).
|
|
268
|
+
|
|
269
|
+
## Bug-class catalog
|
|
270
|
+
|
|
271
|
+
Each shipped bug class has a regression home. When fixing a new bug,
|
|
272
|
+
add the test next to its class. Update this table.
|
|
273
|
+
|
|
274
|
+
| Class | Example | Test home |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| Reaction timing desync | Bug D (👍 before delivery) | `real-gateway-ipc-lifecycle.test.ts` I3, `harness-ordering-invariants.test.ts` INV-1/INV-2 |
|
|
277
|
+
| IPC lifecycle leak | Bug A (anon disconnect flush) | `real-gateway-ipc-lifecycle.test.ts` I1, I2 |
|
|
278
|
+
| Legacy IPC type lethality | Bug B (`update_placeholder` crash) | `real-gateway-ipc-lifecycle.test.ts` I4 |
|
|
279
|
+
| Content-dup retry | #546 (turn-flush + replay) | `real-gateway-i6-turn-flush-replay-dedup.test.ts` |
|
|
280
|
+
| Respawn dedup defense | Bug C (wake-audit) | `real-gateway-i6-turn-flush-replay-dedup.test.ts` I5(b); profile-side fix lives elsewhere |
|
|
281
|
+
| Edit-on-deleted | latent | `harness-ordering-invariants.test.ts` INV-3 |
|
|
282
|
+
| Parse-mode malformed | latent | `harness-parse-mode-validation.test.ts` |
|
|
283
|
+
| Update factory shape | latent | `update-factory-edited-and-reactions.test.ts` |
|
|
284
|
+
|
|
285
|
+
## Pattern 6 — fixture-based integration tests for external-format parsers
|
|
286
|
+
|
|
287
|
+
When code parses output produced by an **external system you don't
|
|
288
|
+
control** (Claude Code's TUI, the Anthropic API stream-json, journalctl
|
|
289
|
+
output, `git log` text), unit tests with synthesised input are not
|
|
290
|
+
enough. The synthesis matches the test author's mental model of the
|
|
291
|
+
format — but the real format drifts on every upstream release, and
|
|
292
|
+
synthesised tests can't catch the drift.
|
|
293
|
+
|
|
294
|
+
**Lesson learned the hard way (PR #486):** `pty-tail.ts`'s
|
|
295
|
+
`V1Extractor` was tested against synthesised Claude Code TUI output
|
|
296
|
+
that "matched the real shape." Then Claude Code collapsed tool-call
|
|
297
|
+
rendering by default, the marker `switchroom-telegram - reply` stopped
|
|
298
|
+
appearing in the buffer, and V1Extractor silently started returning
|
|
299
|
+
null on every call. The IPC plumbing tests still passed (they fed
|
|
300
|
+
mock data); the bridge → gateway wiring tests still passed (they fed
|
|
301
|
+
mock partials). The only failure mode that matters — "in production,
|
|
302
|
+
does this actually emit anything?" — wasn't covered by any test.
|
|
303
|
+
|
|
304
|
+
**Pattern**: capture a real chunk of the external format as a fixture
|
|
305
|
+
and assert the parser produces a non-null result.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
// telegram-plugin/tests/fixtures/service-log-current-claude-code.bin
|
|
309
|
+
// ← captured via: tail -c 30000 ~/.switchroom/agents/<agent>/service.log
|
|
310
|
+
|
|
311
|
+
import { readFileSync } from 'node:fs'
|
|
312
|
+
const FIXTURE = readFileSync(
|
|
313
|
+
resolve(__dirname, 'fixtures', 'service-log-current-claude-code.bin'),
|
|
314
|
+
'utf8',
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
it('extractor handles current production output', async () => {
|
|
318
|
+
const term = await feedToTerm(FIXTURE)
|
|
319
|
+
const result = new V1Extractor().extract(term)
|
|
320
|
+
expect(result).not.toBeNull()
|
|
321
|
+
})
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Maintenance**: when upstream's format changes the test fails. The
|
|
325
|
+
failure tells you exactly what changed (message includes the byte
|
|
326
|
+
range that no longer matches). Either:
|
|
327
|
+
|
|
328
|
+
1. The format reverted (CI flake, just rerun)
|
|
329
|
+
2. The format drifted (update the parser AND recapture the fixture)
|
|
330
|
+
3. The feature stops working (remove the parser + dependents,
|
|
331
|
+
document why)
|
|
332
|
+
|
|
333
|
+
**Where to capture from**: the canonical source for each external
|
|
334
|
+
format. For PTY-tail it's `~/.switchroom/agents/<agent>/service.log`.
|
|
335
|
+
For Anthropic API stream-json it'd be a saved `--output-format
|
|
336
|
+
stream-json` dump. For journalctl, a captured `--since … -o cat`
|
|
337
|
+
window.
|
|
338
|
+
|
|
339
|
+
See `telegram-plugin/tests/pty-tail-real-fixture.test.ts` for the
|
|
340
|
+
worked example.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared harness for progress-card-driver tests added in PR-C2.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the inline harness used by progress-card-close-paths-converge,
|
|
5
|
+
* progress-card-driver-eviction, and the two-zone-* tests so the new
|
|
6
|
+
* tests don't drift in fake-clock semantics.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createProgressDriver, type ProgressDriver } from '../progress-card-driver.js'
|
|
10
|
+
import type { SessionEvent } from '../session-tail.js'
|
|
11
|
+
|
|
12
|
+
export interface DriverHarness {
|
|
13
|
+
driver: ProgressDriver
|
|
14
|
+
emits: Array<{ chatId: string; threadId?: string; turnKey: string; html: string; done: boolean }>
|
|
15
|
+
completions: string[]
|
|
16
|
+
advance: (ms: number) => void
|
|
17
|
+
getNow: () => number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HarnessOpts {
|
|
21
|
+
minIntervalMs?: number
|
|
22
|
+
coalesceMs?: number
|
|
23
|
+
initialDelayMs?: number
|
|
24
|
+
heartbeatMs?: number
|
|
25
|
+
maxIdleMs?: number
|
|
26
|
+
deferredCompletionTimeoutMs?: number
|
|
27
|
+
promoteAfterMs?: number
|
|
28
|
+
editBudgetThreshold?: number
|
|
29
|
+
editBudgetCoalesceMs?: number
|
|
30
|
+
maxConsecutive4xx?: number
|
|
31
|
+
onTurnComplete?: (s: { turnKey: string }) => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function makeHarness(opts: HarnessOpts = {}): DriverHarness {
|
|
35
|
+
let now = 1000
|
|
36
|
+
const timers: Array<{ fireAt: number; fn: () => void; ref: number; repeat?: number }> = []
|
|
37
|
+
let nextRef = 0
|
|
38
|
+
const emits: Array<{ chatId: string; threadId?: string; turnKey: string; html: string; done: boolean }> = []
|
|
39
|
+
const completions: string[] = []
|
|
40
|
+
|
|
41
|
+
const driver = createProgressDriver({
|
|
42
|
+
emit: (a) => emits.push(a),
|
|
43
|
+
minIntervalMs: opts.minIntervalMs ?? 0,
|
|
44
|
+
coalesceMs: opts.coalesceMs ?? 0,
|
|
45
|
+
initialDelayMs: opts.initialDelayMs ?? 0,
|
|
46
|
+
heartbeatMs: opts.heartbeatMs ?? 1_000,
|
|
47
|
+
maxIdleMs: opts.maxIdleMs ?? 30_000,
|
|
48
|
+
deferredCompletionTimeoutMs: opts.deferredCompletionTimeoutMs ?? 10_000,
|
|
49
|
+
promoteAfterMs: opts.promoteAfterMs,
|
|
50
|
+
editBudgetThreshold: opts.editBudgetThreshold,
|
|
51
|
+
editBudgetCoalesceMs: opts.editBudgetCoalesceMs,
|
|
52
|
+
maxConsecutive4xx: opts.maxConsecutive4xx,
|
|
53
|
+
onTurnComplete: opts.onTurnComplete ?? ((s) => completions.push(s.turnKey)),
|
|
54
|
+
now: () => now,
|
|
55
|
+
setTimeout: (fn, ms) => {
|
|
56
|
+
const ref = nextRef++
|
|
57
|
+
timers.push({ fireAt: now + ms, fn, ref })
|
|
58
|
+
return { ref }
|
|
59
|
+
},
|
|
60
|
+
clearTimeout: (h) => {
|
|
61
|
+
const ref = (h as { ref: number }).ref
|
|
62
|
+
const idx = timers.findIndex((t) => t.ref === ref)
|
|
63
|
+
if (idx !== -1) timers.splice(idx, 1)
|
|
64
|
+
},
|
|
65
|
+
setInterval: (fn, ms) => {
|
|
66
|
+
const ref = nextRef++
|
|
67
|
+
timers.push({ fireAt: now + ms, fn, ref, repeat: ms })
|
|
68
|
+
return { ref }
|
|
69
|
+
},
|
|
70
|
+
clearInterval: (h) => {
|
|
71
|
+
const ref = (h as { ref: number }).ref
|
|
72
|
+
const idx = timers.findIndex((t) => t.ref === ref)
|
|
73
|
+
if (idx !== -1) timers.splice(idx, 1)
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const advance = (ms: number): void => {
|
|
78
|
+
now += ms
|
|
79
|
+
for (;;) {
|
|
80
|
+
timers.sort((a, b) => a.fireAt - b.fireAt)
|
|
81
|
+
const next = timers[0]
|
|
82
|
+
if (!next || next.fireAt > now) break
|
|
83
|
+
if (next.repeat != null) {
|
|
84
|
+
next.fireAt += next.repeat
|
|
85
|
+
next.fn()
|
|
86
|
+
} else {
|
|
87
|
+
timers.shift()
|
|
88
|
+
next.fn()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { driver, emits, completions, advance, getNow: () => now }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let nextMsgId = 50_000
|
|
97
|
+
export function enqueue(chatId: string, threadId?: string): SessionEvent {
|
|
98
|
+
return {
|
|
99
|
+
kind: 'enqueue',
|
|
100
|
+
chatId,
|
|
101
|
+
messageId: String(nextMsgId++),
|
|
102
|
+
threadId: threadId ?? null,
|
|
103
|
+
rawContent: `<channel chat_id="${chatId}">go</channel>`,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #689 — boot-time orphan-pin reaper.
|
|
3
|
+
*
|
|
4
|
+
* Backstop for SIGKILL/OOM/panic where the SIGTERM handler (PR #690)
|
|
5
|
+
* never ran. PR #690 covers clean shutdowns: it walks `pinnedEntries()`
|
|
6
|
+
* in-memory before the process exits. But on a hard kill the in-memory
|
|
7
|
+
* map dies with the process, so the next boot must rely on the disk
|
|
8
|
+
* sidecar (`active-pins.ts`) to find the orphans and finalize them.
|
|
9
|
+
*
|
|
10
|
+
* Shape under test (the new `editBeforeUnpin` hook on `sweepActivePins`):
|
|
11
|
+
*
|
|
12
|
+
* 1. Pin lifecycle records to sidecar (already covered by
|
|
13
|
+
* active-pins.test.ts) — we just simulate "process died with
|
|
14
|
+
* sidecar populated" by calling `addActivePin` directly.
|
|
15
|
+
* 2. On next boot, `sweepActivePins` is invoked with an
|
|
16
|
+
* `editBeforeUnpin` callback that renders the banner.
|
|
17
|
+
* 3. Each entry: editFn runs first; unpin runs after; sidecar gets
|
|
18
|
+
* cleared.
|
|
19
|
+
* 4. Banner edit failure (e.g. "message to edit not found") is caught
|
|
20
|
+
* and the unpin still fires — frozen card is worse than no card.
|
|
21
|
+
* 5. Clean-shutdown unpin path (via pinManager.completeTurn) removes
|
|
22
|
+
* the sidecar entry, so a clean shutdown leaves nothing for the
|
|
23
|
+
* reaper to find — only crashes leave entries behind.
|
|
24
|
+
*/
|
|
25
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
26
|
+
import { mkdtempSync, rmSync, existsSync } from 'node:fs'
|
|
27
|
+
import { tmpdir } from 'node:os'
|
|
28
|
+
import { join } from 'node:path'
|
|
29
|
+
import {
|
|
30
|
+
addActivePin,
|
|
31
|
+
readActivePins,
|
|
32
|
+
writeActivePins,
|
|
33
|
+
ACTIVE_PINS_FILENAME,
|
|
34
|
+
type ActivePin,
|
|
35
|
+
} from '../active-pins.js'
|
|
36
|
+
import { sweepActivePins } from '../active-pins-sweep.js'
|
|
37
|
+
import {
|
|
38
|
+
createPinManager,
|
|
39
|
+
type PinManagerDeps,
|
|
40
|
+
type TimerHandle,
|
|
41
|
+
} from '../progress-card-pin-manager.js'
|
|
42
|
+
|
|
43
|
+
interface PendingTimer { fn: () => void; cancelled: boolean; fired: boolean }
|
|
44
|
+
|
|
45
|
+
function mkPinManagerHarness(overrides: Partial<PinManagerDeps> = {}) {
|
|
46
|
+
const timers: PendingTimer[] = []
|
|
47
|
+
const deps = {
|
|
48
|
+
pin: vi.fn(async () => true),
|
|
49
|
+
unpin: vi.fn(async () => true),
|
|
50
|
+
deleteMessage: vi.fn(async () => true),
|
|
51
|
+
addPin: vi.fn(),
|
|
52
|
+
removePin: vi.fn(),
|
|
53
|
+
log: vi.fn(),
|
|
54
|
+
}
|
|
55
|
+
const scheduleTimer = (fn: () => void): TimerHandle => {
|
|
56
|
+
const entry: PendingTimer = { fn, cancelled: false, fired: false }
|
|
57
|
+
timers.push(entry)
|
|
58
|
+
return { cancel() { entry.cancelled = true } }
|
|
59
|
+
}
|
|
60
|
+
const mgr = createPinManager({ ...deps, now: () => 10_000, scheduleTimer, ...overrides })
|
|
61
|
+
const fireTimers = (): void => {
|
|
62
|
+
for (const t of [...timers]) {
|
|
63
|
+
if (t.cancelled || t.fired) continue
|
|
64
|
+
t.fired = true
|
|
65
|
+
t.fn()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { mgr, deps, fireTimers }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('boot-time orphan-pin reaper (#689)', () => {
|
|
72
|
+
let tmp: string
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
tmp = mkdtempSync(join(tmpdir(), 'pin-reaper-'))
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
rmSync(tmp, { recursive: true, force: true })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const makePin = (overrides: Partial<ActivePin> = {}): ActivePin => ({
|
|
83
|
+
chatId: '100',
|
|
84
|
+
messageId: 42,
|
|
85
|
+
turnKey: '100:0:1',
|
|
86
|
+
pinnedAt: 1_700_000_000_000,
|
|
87
|
+
agentId: '__parent__',
|
|
88
|
+
...overrides,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('hard-kill mid-turn: next boot sees the sidecar entry, edits banner, then unpins', async () => {
|
|
92
|
+
// Simulate: prior process pinned but never got to unpin (SIGKILL/OOM).
|
|
93
|
+
addActivePin(tmp, makePin({ chatId: 'A', messageId: 101 }))
|
|
94
|
+
addActivePin(tmp, makePin({ chatId: 'B', messageId: 202, turnKey: 'B:0:1' }))
|
|
95
|
+
|
|
96
|
+
// Now boot: invoke the reaper.
|
|
97
|
+
const editCalls: Array<{ chatId: string; messageId: number; banner: string }> = []
|
|
98
|
+
const unpinCalls: Array<[string, number]> = []
|
|
99
|
+
const banner = '⚠️ <b>Restart interrupted this work</b>\n<i>SIGKILL: oom</i>'
|
|
100
|
+
|
|
101
|
+
const result = await sweepActivePins(
|
|
102
|
+
tmp,
|
|
103
|
+
async (chatId, messageId) => { unpinCalls.push([chatId, messageId]) },
|
|
104
|
+
{
|
|
105
|
+
editBeforeUnpin: async (pin) => {
|
|
106
|
+
editCalls.push({ chatId: pin.chatId, messageId: pin.messageId, banner })
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Both pins were edited THEN unpinned.
|
|
112
|
+
expect(editCalls.sort((a, b) => a.messageId - b.messageId)).toEqual([
|
|
113
|
+
{ chatId: 'A', messageId: 101, banner },
|
|
114
|
+
{ chatId: 'B', messageId: 202, banner },
|
|
115
|
+
])
|
|
116
|
+
expect(unpinCalls.sort()).toEqual([['A', 101], ['B', 202]])
|
|
117
|
+
expect(result.swept).toHaveLength(2)
|
|
118
|
+
expect(existsSync(join(tmp, ACTIVE_PINS_FILENAME))).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('banner edit failure does not block the unpin (frozen card is worse than no card)', async () => {
|
|
122
|
+
addActivePin(tmp, makePin({ chatId: 'A', messageId: 101 }))
|
|
123
|
+
|
|
124
|
+
const unpinCalls: Array<[string, number]> = []
|
|
125
|
+
const result = await sweepActivePins(
|
|
126
|
+
tmp,
|
|
127
|
+
async (chatId, messageId) => { unpinCalls.push([chatId, messageId]) },
|
|
128
|
+
{
|
|
129
|
+
editBeforeUnpin: async () => {
|
|
130
|
+
throw new Error('Bad Request: message to edit not found')
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
expect(unpinCalls).toEqual([['A', 101]])
|
|
136
|
+
expect(result.swept).toHaveLength(1)
|
|
137
|
+
expect(existsSync(join(tmp, ACTIVE_PINS_FILENAME))).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('edit fires BEFORE unpin (ordering matters — never unpin a card with stale Working… body)', async () => {
|
|
141
|
+
addActivePin(tmp, makePin({ chatId: 'A', messageId: 101 }))
|
|
142
|
+
|
|
143
|
+
const order: string[] = []
|
|
144
|
+
await sweepActivePins(
|
|
145
|
+
tmp,
|
|
146
|
+
async () => { order.push('unpin') },
|
|
147
|
+
{
|
|
148
|
+
editBeforeUnpin: async () => { order.push('edit') },
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
expect(order).toEqual(['edit', 'unpin'])
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('clean unpin path removes sidecar entry, leaving nothing for the reaper', async () => {
|
|
156
|
+
// Simulate the pinManager addPin/removePin sidecar wiring used in
|
|
157
|
+
// gateway.ts: addPin appends to disk, removePin filters out.
|
|
158
|
+
const h = mkPinManagerHarness({
|
|
159
|
+
addPin: (entry) => addActivePin(tmp, entry),
|
|
160
|
+
removePin: (chatId, messageId) => {
|
|
161
|
+
const next = readActivePins(tmp).filter(
|
|
162
|
+
(p) => !(p.chatId === chatId && p.messageId === messageId),
|
|
163
|
+
)
|
|
164
|
+
writeActivePins(tmp, next)
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Pin
|
|
169
|
+
h.mgr.considerPin({
|
|
170
|
+
chatId: 'A', threadId: '7', turnKey: 'A:7:1',
|
|
171
|
+
messageId: 101, isFirstEmit: true,
|
|
172
|
+
})
|
|
173
|
+
h.fireTimers()
|
|
174
|
+
await h.mgr.drainInFlight()
|
|
175
|
+
expect(readActivePins(tmp)).toHaveLength(1)
|
|
176
|
+
|
|
177
|
+
// Clean unpin (e.g. via completeTurn at end of turn)
|
|
178
|
+
h.mgr.completeTurn({ chatId: 'A', threadId: '7', turnKey: 'A:7:1' })
|
|
179
|
+
await h.mgr.drainInFlight()
|
|
180
|
+
|
|
181
|
+
// Sidecar is empty — the boot reaper would find nothing.
|
|
182
|
+
expect(readActivePins(tmp)).toEqual([])
|
|
183
|
+
|
|
184
|
+
// And confirm the reaper would no-op:
|
|
185
|
+
const editCalls: ActivePin[] = []
|
|
186
|
+
const result = await sweepActivePins(
|
|
187
|
+
tmp,
|
|
188
|
+
async () => {},
|
|
189
|
+
{ editBeforeUnpin: async (p) => { editCalls.push(p) } },
|
|
190
|
+
)
|
|
191
|
+
expect(editCalls).toEqual([])
|
|
192
|
+
expect(result.swept).toEqual([])
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('respects the timeout budget when banner edits hang', async () => {
|
|
196
|
+
addActivePin(tmp, makePin({ chatId: 'A', messageId: 101 }))
|
|
197
|
+
|
|
198
|
+
const result = await sweepActivePins(
|
|
199
|
+
tmp,
|
|
200
|
+
async () => {},
|
|
201
|
+
{
|
|
202
|
+
timeoutMs: 50,
|
|
203
|
+
editBeforeUnpin: () => new Promise(() => {}), // never resolves
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
expect(result.timedOut).toBe(true)
|
|
208
|
+
// Sidecar still cleared — Telegram unpin is idempotent.
|
|
209
|
+
expect(existsSync(join(tmp, ACTIVE_PINS_FILENAME))).toBe(false)
|
|
210
|
+
})
|
|
211
|
+
})
|