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,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted pin-lifecycle manager for the progress-card driver.
|
|
3
|
+
*
|
|
4
|
+
* Previously lived inline in gateway.ts (progressDriver setup block) —
|
|
5
|
+
* reaching into module-level state (`progressPinnedMsgIds`,
|
|
6
|
+
* `unpinnedTurnKeys`, `lockedBot`, `resolveAgentDirFromEnv`) and making
|
|
7
|
+
* the full first-emit → pin → edit → turn-end → unpin sequence
|
|
8
|
+
* unreachable from tests without spinning up the whole bot runner.
|
|
9
|
+
*
|
|
10
|
+
* This module exposes the same behaviour behind a pure interface, with
|
|
11
|
+
* bot-API + sidecar writes injected as callbacks. Contract (tested in
|
|
12
|
+
* progress-card-pin-manager.test.ts):
|
|
13
|
+
*
|
|
14
|
+
* considerPin(candidate)
|
|
15
|
+
* - On `isFirstEmit === true` for a (turnKey, agentId) pair not yet
|
|
16
|
+
* pinned:
|
|
17
|
+
* records the pinned message id
|
|
18
|
+
* records an active-pin sidecar entry (if addPin is wired)
|
|
19
|
+
* calls bot.pin(chatId, messageId, { disable_notification: true })
|
|
20
|
+
* on pin rejection: calls removePin to keep the sidecar consistent
|
|
21
|
+
* - On subsequent emits for the same (turnKey, agentId): no-op
|
|
22
|
+
* (idempotent).
|
|
23
|
+
*
|
|
24
|
+
* completeTurn({ chatId, threadId, turnKey, agentId? })
|
|
25
|
+
* - Looks up pinnedMessageId for (turnKey, agentId); if present:
|
|
26
|
+
* unpins exactly once (duplicate completeTurn calls are safe)
|
|
27
|
+
* calls removePin to clear the sidecar
|
|
28
|
+
* - The unpinned set entry is cleared so a future re-use of the same
|
|
29
|
+
* composite key (unlikely but cheap) starts fresh.
|
|
30
|
+
*
|
|
31
|
+
* completeAllForTurn({ turnKey })
|
|
32
|
+
* - Catastrophic-cleanup helper. Unpins every pinned card for a turn,
|
|
33
|
+
* across all agentIds. Used on bridge-disconnect / forced shutdown
|
|
34
|
+
* paths where individual sub_agent_turn_end events may never land.
|
|
35
|
+
*
|
|
36
|
+
* unpinForChat(chatId, threadId)
|
|
37
|
+
* - External hook for context-exhaustion / /restart: unpins every
|
|
38
|
+
* currently-pinned (turnKey, agentId) matching the chat+thread
|
|
39
|
+
* prefix.
|
|
40
|
+
*
|
|
41
|
+
* Per-agent cards (#per-agent-cards): the manager keys pin state on the
|
|
42
|
+
* composite (turnKey, agentId) so a parent card and its sub-agent cards
|
|
43
|
+
* can co-exist independently in the same turn. Callers that don't yet
|
|
44
|
+
* thread agentId through (e.g. legacy single-card-per-turn callers) get
|
|
45
|
+
* a stable default sentinel — see `PARENT_AGENT_ID` below — so existing
|
|
46
|
+
* behaviour is preserved without modification.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sentinel agent id for the "parent" / single-card-per-turn case. Used
|
|
51
|
+
* as the default when callers don't yet pass an explicit agentId, so the
|
|
52
|
+
* composite-key bookkeeping degrades to the original turnKey-only
|
|
53
|
+
* behaviour for legacy call sites.
|
|
54
|
+
*/
|
|
55
|
+
export const PARENT_AGENT_ID = '__parent__'
|
|
56
|
+
|
|
57
|
+
export interface PinCandidate {
|
|
58
|
+
readonly chatId: string
|
|
59
|
+
readonly threadId?: string
|
|
60
|
+
readonly turnKey: string
|
|
61
|
+
readonly messageId: number
|
|
62
|
+
readonly isFirstEmit: boolean
|
|
63
|
+
/**
|
|
64
|
+
* Per-agent identity. Defaults to {@link PARENT_AGENT_ID} when omitted
|
|
65
|
+
* — callers that haven't yet been threaded for per-agent cards behave
|
|
66
|
+
* as before (one pin per turnKey).
|
|
67
|
+
*/
|
|
68
|
+
readonly agentId?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ActivePinEntry {
|
|
72
|
+
readonly chatId: string
|
|
73
|
+
readonly messageId: number
|
|
74
|
+
readonly turnKey: string
|
|
75
|
+
readonly pinnedAt: number
|
|
76
|
+
/**
|
|
77
|
+
* Stored verbatim alongside the entry. Existing sidecar files written
|
|
78
|
+
* before the per-agent split have no agentId; readers should treat a
|
|
79
|
+
* missing field as {@link PARENT_AGENT_ID}.
|
|
80
|
+
*/
|
|
81
|
+
readonly agentId?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface TimerHandle {
|
|
85
|
+
cancel(): void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface PinManagerDeps {
|
|
89
|
+
/** Underlying `bot.api.pinChatMessage` wrapper. */
|
|
90
|
+
pin: (
|
|
91
|
+
chatId: string,
|
|
92
|
+
messageId: number,
|
|
93
|
+
opts?: { disable_notification?: boolean },
|
|
94
|
+
) => Promise<unknown>
|
|
95
|
+
/** Underlying `bot.api.unpinChatMessage` wrapper. */
|
|
96
|
+
unpin: (chatId: string, messageId: number) => Promise<unknown>
|
|
97
|
+
/** Optional: persist a pin to the sidecar. Skipped when not wired. */
|
|
98
|
+
addPin?: (entry: ActivePinEntry) => void
|
|
99
|
+
/** Optional: remove from the sidecar. Skipped when not wired. */
|
|
100
|
+
removePin?: (chatId: string, messageId: number) => void
|
|
101
|
+
/**
|
|
102
|
+
* Optional: `bot.api.deleteMessage` wrapper. When wired, the manager
|
|
103
|
+
* deletes the "Clerk pinned ..." service message that Telegram posts
|
|
104
|
+
* automatically after each pin. Skipped when not wired.
|
|
105
|
+
*/
|
|
106
|
+
deleteMessage?: (chatId: string, messageId: number) => Promise<unknown>
|
|
107
|
+
/** Logger for pin/unpin failures. Receives lines with trailing newline. */
|
|
108
|
+
log?: (line: string) => void
|
|
109
|
+
/** Clock injection for test determinism. Defaults to `Date.now`. */
|
|
110
|
+
now?: () => number
|
|
111
|
+
/**
|
|
112
|
+
* How long to wait after the first emit before actually pinning. The
|
|
113
|
+
* driver's `initialDelayMs` already suppresses the card entirely for
|
|
114
|
+
* fast turns, so by the time first-emit fires the turn is already
|
|
115
|
+
* considered slow and the pin can follow immediately. Defaults to 0.
|
|
116
|
+
*/
|
|
117
|
+
pinDelayMs?: number
|
|
118
|
+
/**
|
|
119
|
+
* Injectable timer scheduler. Defaults to `setTimeout` + `clearTimeout`.
|
|
120
|
+
* Tests pass a fake that captures callbacks and fires them manually so
|
|
121
|
+
* they can assert on the before/after states without real clocks.
|
|
122
|
+
*/
|
|
123
|
+
scheduleTimer?: (fn: () => void, ms: number) => TimerHandle
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface CompleteTurnArgs {
|
|
127
|
+
chatId: string
|
|
128
|
+
threadId?: string
|
|
129
|
+
turnKey: string
|
|
130
|
+
/** Defaults to {@link PARENT_AGENT_ID}. */
|
|
131
|
+
agentId?: string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface PinManager {
|
|
135
|
+
/** Decide whether to pin based on an emit's metadata. Idempotent. */
|
|
136
|
+
considerPin(candidate: PinCandidate): void
|
|
137
|
+
/**
|
|
138
|
+
* Called from `onTurnComplete` (parent) or from `sub_agent_turn_end`
|
|
139
|
+
* (per-agent) — unpins the (turnKey, agentId) composite's pinned card.
|
|
140
|
+
* `agentId` defaults to {@link PARENT_AGENT_ID} for legacy single-card
|
|
141
|
+
* call sites.
|
|
142
|
+
*/
|
|
143
|
+
completeTurn(args: CompleteTurnArgs): void
|
|
144
|
+
/**
|
|
145
|
+
* Catastrophic-cleanup helper. Unpins every pinned card under a turnKey
|
|
146
|
+
* — across all agentIds. Used when a parent turn ends without per-agent
|
|
147
|
+
* sub_agent_turn_end events arriving (bridge disconnect, gateway crash,
|
|
148
|
+
* forced shutdown). Distinct from `completeTurn`, which targets a single
|
|
149
|
+
* card.
|
|
150
|
+
*/
|
|
151
|
+
completeAllForTurn(args: { chatId: string; threadId?: string; turnKey: string }): void
|
|
152
|
+
/**
|
|
153
|
+
* External hook. Unpins every currently-pinned (turnKey, agentId)
|
|
154
|
+
* matching the chat (and optional thread). Used by
|
|
155
|
+
* context-exhaustion / external cancellation paths that need to clear
|
|
156
|
+
* all active pins for a chat.
|
|
157
|
+
*/
|
|
158
|
+
unpinForChat(chatId: string, threadId: number | undefined): void
|
|
159
|
+
/**
|
|
160
|
+
* Hook for the grammY `message:pinned_message` update. When Telegram
|
|
161
|
+
* auto-posts the "Clerk pinned ..." service message after a pin we
|
|
162
|
+
* made, the gateway calls this with the service message id and the id
|
|
163
|
+
* of the pinned message it wraps. The manager deletes the service
|
|
164
|
+
* message immediately if it matches one of our tracked pins.
|
|
165
|
+
*/
|
|
166
|
+
captureServiceMessage(args: {
|
|
167
|
+
chatId: string
|
|
168
|
+
pinnedMessageId: number
|
|
169
|
+
serviceMessageId: number
|
|
170
|
+
}): void
|
|
171
|
+
/**
|
|
172
|
+
* Register a pin made outside the per-turn `considerPin()` path so
|
|
173
|
+
* `captureServiceMessage()` will recognise its service message and
|
|
174
|
+
* delete it. Used by the worker / sub-agent card (issue #94), which
|
|
175
|
+
* pins through the gateway directly rather than the progress-card
|
|
176
|
+
* pin candidate flow. Idempotent — calling twice with the same
|
|
177
|
+
* (chatId, messageId) is a no-op.
|
|
178
|
+
*/
|
|
179
|
+
trackExternalPin(chatId: string, messageId: number): void
|
|
180
|
+
/**
|
|
181
|
+
* Drop an external pin from tracking. Call when the corresponding
|
|
182
|
+
* pinned message is unpinned/deleted by its owner so the manager
|
|
183
|
+
* doesn't keep a stale reference. Idempotent.
|
|
184
|
+
*/
|
|
185
|
+
untrackExternalPin(chatId: string, messageId: number): void
|
|
186
|
+
/**
|
|
187
|
+
* Test-only: snapshot the unique turnKeys that currently have at
|
|
188
|
+
* least one pinned card. With per-agent cards this may collapse
|
|
189
|
+
* multiple composite entries down to a single turnKey.
|
|
190
|
+
*/
|
|
191
|
+
pinnedTurnKeys(): ReadonlyArray<string>
|
|
192
|
+
/**
|
|
193
|
+
* Test-only: snapshot the agentIds currently pinned under a turnKey.
|
|
194
|
+
* Empty array when nothing is pinned for that turn.
|
|
195
|
+
*/
|
|
196
|
+
pinnedAgentIds(turnKey: string): ReadonlyArray<string>
|
|
197
|
+
/**
|
|
198
|
+
* Test-only: look up the pinned message id for a (turnKey, agentId).
|
|
199
|
+
* `agentId` defaults to {@link PARENT_AGENT_ID} for backward compat.
|
|
200
|
+
*/
|
|
201
|
+
pinnedMessageId(turnKey: string, agentId?: string): number | undefined
|
|
202
|
+
/**
|
|
203
|
+
* Snapshot of every pinned card currently tracked by the manager.
|
|
204
|
+
* Used by the gateway shutdown path (#689) to render a final
|
|
205
|
+
* "Restart interrupted" frame on each pinned card and unpin it
|
|
206
|
+
* synchronously, so cards don't freeze on "Working…" forever after
|
|
207
|
+
* a SIGTERM mid-turn.
|
|
208
|
+
*/
|
|
209
|
+
pinnedEntries(): ReadonlyArray<{
|
|
210
|
+
turnKey: string
|
|
211
|
+
agentId: string
|
|
212
|
+
chatId: string
|
|
213
|
+
threadId?: string
|
|
214
|
+
messageId: number
|
|
215
|
+
}>
|
|
216
|
+
/**
|
|
217
|
+
* Test hook to await all in-flight pin/unpin promises. Production
|
|
218
|
+
* callers don't need this; tests can call it to drain the fire-and-
|
|
219
|
+
* forget `.catch()` chains before asserting on side effects.
|
|
220
|
+
*/
|
|
221
|
+
drainInFlight(): Promise<void>
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Composite key for the per-agent pin maps. Uses a `::` separator that
|
|
226
|
+
* cannot appear in a turnKey (which is `${chatId}:${threadId}:${seq}`)
|
|
227
|
+
* or in any agentId (JSONL filename stems are slug-safe).
|
|
228
|
+
*/
|
|
229
|
+
function pinKey(turnKey: string, agentId: string): string {
|
|
230
|
+
return `${turnKey}::${agentId}`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function createPinManager(deps: PinManagerDeps): PinManager {
|
|
234
|
+
const now = deps.now ?? Date.now
|
|
235
|
+
const log = deps.log ?? (() => {})
|
|
236
|
+
const pinDelayMs = deps.pinDelayMs ?? 0
|
|
237
|
+
const scheduleTimer: (fn: () => void, ms: number) => TimerHandle =
|
|
238
|
+
deps.scheduleTimer ??
|
|
239
|
+
((fn, ms) => {
|
|
240
|
+
const t = setTimeout(fn, ms)
|
|
241
|
+
return { cancel: () => clearTimeout(t) }
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// (turnKey, agentId) -> pinned message id. Populated only after a
|
|
245
|
+
// successful pin call returns; cleared on completeTurn for that
|
|
246
|
+
// composite.
|
|
247
|
+
const pinned = new Map<string, number>()
|
|
248
|
+
// Parallel to `pinned`: (turnKey, agentId) -> {chatId, threadId?}. Lets
|
|
249
|
+
// the shutdown flush (#689) reconstruct the chat coordinates without
|
|
250
|
+
// parsing the turnKey (which is ambiguous when no threadId is present:
|
|
251
|
+
// `chatId:seq` vs `chatId:threadId:seq`).
|
|
252
|
+
const pinnedMeta = new Map<string, { chatId: string; threadId?: string }>()
|
|
253
|
+
// (turnKey, agentId) -> pending pin state. Holds the candidate +
|
|
254
|
+
// timer handle while we wait pinDelayMs before actually calling the
|
|
255
|
+
// Telegram pin API. If completeTurn fires before the timer, we cancel
|
|
256
|
+
// and never pin — fast turns stay silent. Removed when the timer
|
|
257
|
+
// fires (moved to `pinned`) or when completeTurn cancels it.
|
|
258
|
+
const pendingPins = new Map<
|
|
259
|
+
string,
|
|
260
|
+
{ chatId: string; threadId?: string; messageId: number; turnKey: string; agentId: string; timer: TimerHandle }
|
|
261
|
+
>()
|
|
262
|
+
// Composite keys whose unpin has already fired. Guards against
|
|
263
|
+
// duplicate completeTurn calls causing a second unpin.
|
|
264
|
+
const unpinned = new Set<string>()
|
|
265
|
+
// `${chatId}:${pinnedMessageId}` -> service-message id. Populated when
|
|
266
|
+
// the grammY `pinned_message` update handler forwards the wrapper
|
|
267
|
+
// message to us. We delete on capture; this map exists only so a late
|
|
268
|
+
// unpin path can also scrub the service message if the capture-delete
|
|
269
|
+
// somehow failed.
|
|
270
|
+
const serviceMessages = new Map<string, number>()
|
|
271
|
+
// Pins that the manager DIDN'T make through `considerPin()` but should
|
|
272
|
+
// still recognise so their "Clerk pinned …" service message gets
|
|
273
|
+
// deleted. Issue #94: the worker / sub-agent card pins via the gateway
|
|
274
|
+
// directly; without this set, captureServiceMessage would skip its
|
|
275
|
+
// service message and the user sees the system-message noise that the
|
|
276
|
+
// main card already suppresses. Keyed by `${chatId}:${messageId}`.
|
|
277
|
+
const externalPins = new Set<string>()
|
|
278
|
+
// Fire-and-forget promises we want tests to be able to drain.
|
|
279
|
+
const inFlight = new Set<Promise<unknown>>()
|
|
280
|
+
|
|
281
|
+
function serviceKey(chatId: string, pinnedMessageId: number): string {
|
|
282
|
+
return `${chatId}:${pinnedMessageId}`
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function deleteServiceMessage(chatId: string, serviceMessageId: number): void {
|
|
286
|
+
if (!deps.deleteMessage) return
|
|
287
|
+
const p = deps.deleteMessage(chatId, serviceMessageId).catch((err: Error) => {
|
|
288
|
+
log(`telegram gateway: progress-card pin service-msg delete failed: ${err?.message ?? err}\n`)
|
|
289
|
+
})
|
|
290
|
+
track(p)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function track(p: Promise<unknown>): void {
|
|
294
|
+
inFlight.add(p)
|
|
295
|
+
// Remove on settle — including rejections, which are caught by the
|
|
296
|
+
// callers. Using `then(..., ...)` rather than `finally` so a late
|
|
297
|
+
// thrown error inside finally can't corrupt the tracking set.
|
|
298
|
+
p.then(
|
|
299
|
+
() => { inFlight.delete(p) },
|
|
300
|
+
() => { inFlight.delete(p) },
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function doUnpin(turnKey: string, agentId: string, chatId: string, pinnedId: number): void {
|
|
305
|
+
const key = pinKey(turnKey, agentId)
|
|
306
|
+
if (unpinned.has(key)) return
|
|
307
|
+
unpinned.add(key)
|
|
308
|
+
log(`telegram gateway: progress-card: unpin turnKey=${turnKey} agentId=${agentId} msgId=${pinnedId}\n`)
|
|
309
|
+
pinned.delete(key)
|
|
310
|
+
pinnedMeta.delete(key)
|
|
311
|
+
const svcKey = serviceKey(chatId, pinnedId)
|
|
312
|
+
const svcId = serviceMessages.get(svcKey)
|
|
313
|
+
if (svcId != null) {
|
|
314
|
+
serviceMessages.delete(svcKey)
|
|
315
|
+
deleteServiceMessage(chatId, svcId)
|
|
316
|
+
}
|
|
317
|
+
const unpinStart = now()
|
|
318
|
+
const p = deps.unpin(chatId, pinnedId)
|
|
319
|
+
.then(() => {
|
|
320
|
+
const ms = now() - unpinStart
|
|
321
|
+
log(`telegram gateway: progress-card: unpin OK turnKey=${turnKey} agentId=${agentId} msgId=${pinnedId} durationMs=${ms}\n`)
|
|
322
|
+
})
|
|
323
|
+
.catch((err: Error) => {
|
|
324
|
+
const ms = now() - unpinStart
|
|
325
|
+
log(`telegram gateway: progress-card unpin failed turnKey=${turnKey} agentId=${agentId} msgId=${pinnedId} durationMs=${ms} error="${err?.message ?? err}"\n`)
|
|
326
|
+
})
|
|
327
|
+
.finally(() => {
|
|
328
|
+
// Keep the sidecar consistent whether the API call succeeded
|
|
329
|
+
// or failed — the sidecar exists to recover after we lose the
|
|
330
|
+
// in-memory map across a restart, so removing on unpin-attempt
|
|
331
|
+
// is the correct boundary.
|
|
332
|
+
if (deps.removePin) deps.removePin(chatId, pinnedId)
|
|
333
|
+
})
|
|
334
|
+
track(p)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function firePin(turnKey: string, agentId: string, chatId: string, threadId: string | undefined, messageId: number): void {
|
|
338
|
+
// Called when the pin-delay timer fires. Promote from pendingPins
|
|
339
|
+
// into pinned, then issue the Telegram pin API call.
|
|
340
|
+
const key = pinKey(turnKey, agentId)
|
|
341
|
+
pendingPins.delete(key)
|
|
342
|
+
if (pinned.has(key) || unpinned.has(key)) {
|
|
343
|
+
// Either we already pinned (shouldn't happen — timer is the only
|
|
344
|
+
// path that sets `pinned`) or the turn completed between scheduling
|
|
345
|
+
// and firing and the pending entry was cleared elsewhere. Bail.
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
pinned.set(key, messageId)
|
|
349
|
+
pinnedMeta.set(key, threadId != null ? { chatId, threadId } : { chatId })
|
|
350
|
+
log(`telegram gateway: progress-card: pinned turnKey=${turnKey} agentId=${agentId} msgId=${messageId}\n`)
|
|
351
|
+
if (deps.addPin) {
|
|
352
|
+
deps.addPin({
|
|
353
|
+
chatId,
|
|
354
|
+
messageId,
|
|
355
|
+
turnKey,
|
|
356
|
+
pinnedAt: now(),
|
|
357
|
+
agentId,
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
const pinStart = now()
|
|
361
|
+
const p = deps.pin(chatId, messageId, { disable_notification: true })
|
|
362
|
+
.then(() => {
|
|
363
|
+
const ms = now() - pinStart
|
|
364
|
+
log(`telegram gateway: progress-card: pin OK turnKey=${turnKey} agentId=${agentId} msgId=${messageId} durationMs=${ms}\n`)
|
|
365
|
+
})
|
|
366
|
+
.catch(
|
|
367
|
+
(err: Error) => {
|
|
368
|
+
const ms = now() - pinStart
|
|
369
|
+
const errMsg = err?.message ?? String(err)
|
|
370
|
+
const line = `telegram gateway: progress-card pin failed chatId=${chatId} msgId=${messageId} turnKey=${turnKey} agentId=${agentId} durationMs=${ms} error="${errMsg}"\n`
|
|
371
|
+
log(line)
|
|
372
|
+
console.warn(line.replace(/\n$/, ''))
|
|
373
|
+
// Pin API failed — drop from the in-memory map so a later
|
|
374
|
+
// unpin attempt doesn't fire `deps.unpin` for a message we
|
|
375
|
+
// never actually pinned. Do NOT add to `unpinned` — we never
|
|
376
|
+
// issued an unpin. Sidecar is also cleared for consistency.
|
|
377
|
+
pinned.delete(key)
|
|
378
|
+
pinnedMeta.delete(key)
|
|
379
|
+
if (deps.removePin) deps.removePin(chatId, messageId)
|
|
380
|
+
},
|
|
381
|
+
)
|
|
382
|
+
track(p)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
considerPin(c) {
|
|
387
|
+
if (!c.isFirstEmit) return
|
|
388
|
+
const agentId = c.agentId ?? PARENT_AGENT_ID
|
|
389
|
+
const key = pinKey(c.turnKey, agentId)
|
|
390
|
+
if (pinned.has(key)) return
|
|
391
|
+
if (pendingPins.has(key)) return
|
|
392
|
+
// Schedule the pin via the injected timer. Fast-turn suppression is
|
|
393
|
+
// owned upstream by the driver's `initialDelayMs` — by the time
|
|
394
|
+
// considerPin sees isFirstEmit=true the card has already been
|
|
395
|
+
// published, so pinDelayMs defaults to 0 (fire on next tick).
|
|
396
|
+
// The indirection remains so tests and callers can still override
|
|
397
|
+
// with a positive value if they want a pre-pin visual buffer.
|
|
398
|
+
const timer = scheduleTimer(() => {
|
|
399
|
+
firePin(c.turnKey, agentId, c.chatId, c.threadId, c.messageId)
|
|
400
|
+
}, pinDelayMs)
|
|
401
|
+
pendingPins.set(key, {
|
|
402
|
+
chatId: c.chatId,
|
|
403
|
+
...(c.threadId != null ? { threadId: c.threadId } : {}),
|
|
404
|
+
messageId: c.messageId,
|
|
405
|
+
turnKey: c.turnKey,
|
|
406
|
+
agentId,
|
|
407
|
+
timer,
|
|
408
|
+
})
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
completeTurn({ chatId, turnKey, agentId }) {
|
|
412
|
+
const aid = agentId ?? PARENT_AGENT_ID
|
|
413
|
+
const key = pinKey(turnKey, aid)
|
|
414
|
+
// Fast-turn path: if the pin is still pending, cancel the timer
|
|
415
|
+
// and we're done — no pin ever landed, no unpin needed.
|
|
416
|
+
const pending = pendingPins.get(key)
|
|
417
|
+
if (pending != null) {
|
|
418
|
+
pending.timer.cancel()
|
|
419
|
+
pendingPins.delete(key)
|
|
420
|
+
}
|
|
421
|
+
const pinnedId = pinned.get(key)
|
|
422
|
+
if (pinnedId != null) doUnpin(turnKey, aid, chatId, pinnedId)
|
|
423
|
+
// Once the turn is complete we never see the same composite again
|
|
424
|
+
// (driver generates a fresh sequence for the turn, agentIds are
|
|
425
|
+
// stable per agent lifetime). Clearing the flag keeps the set from
|
|
426
|
+
// growing unbounded over a long-running gateway.
|
|
427
|
+
unpinned.delete(key)
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
completeAllForTurn({ chatId, turnKey }) {
|
|
431
|
+
// Snapshot composites for this turn before mutating — pendingPins
|
|
432
|
+
// and pinned will both shrink as we go.
|
|
433
|
+
const matchingPending: Array<{ key: string; agentId: string }> = []
|
|
434
|
+
for (const [key, entry] of pendingPins) {
|
|
435
|
+
if (entry.turnKey === turnKey) matchingPending.push({ key, agentId: entry.agentId })
|
|
436
|
+
}
|
|
437
|
+
for (const { key } of matchingPending) {
|
|
438
|
+
const pending = pendingPins.get(key)
|
|
439
|
+
if (pending != null) {
|
|
440
|
+
pending.timer.cancel()
|
|
441
|
+
pendingPins.delete(key)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const matchingPinned: Array<{ agentId: string; pinnedId: number }> = []
|
|
445
|
+
for (const [key, pinnedId] of pinned) {
|
|
446
|
+
// Composite keys are `${turnKey}::${agentId}` — split on `::`.
|
|
447
|
+
const sep = key.lastIndexOf('::')
|
|
448
|
+
if (sep < 0) continue
|
|
449
|
+
const tk = key.slice(0, sep)
|
|
450
|
+
if (tk !== turnKey) continue
|
|
451
|
+
const agentId = key.slice(sep + 2)
|
|
452
|
+
matchingPinned.push({ agentId, pinnedId })
|
|
453
|
+
}
|
|
454
|
+
for (const { agentId, pinnedId } of matchingPinned) {
|
|
455
|
+
doUnpin(turnKey, agentId, chatId, pinnedId)
|
|
456
|
+
}
|
|
457
|
+
// Mirror completeTurn's housekeeping: clear unpinned-set entries
|
|
458
|
+
// for this turn so a future reuse (unlikely) starts fresh.
|
|
459
|
+
for (const key of [...unpinned]) {
|
|
460
|
+
const sep = key.lastIndexOf('::')
|
|
461
|
+
if (sep < 0) continue
|
|
462
|
+
if (key.slice(0, sep) === turnKey) unpinned.delete(key)
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
unpinForChat(chatId, threadId) {
|
|
467
|
+
const base = threadId != null ? `${chatId}:${threadId}` : chatId
|
|
468
|
+
// Cancel any pending (not-yet-fired) timers for this chat/thread
|
|
469
|
+
// first — otherwise they'd pin after we thought we cleaned up.
|
|
470
|
+
const pendingMatching: string[] = []
|
|
471
|
+
for (const [key, entry] of pendingPins) {
|
|
472
|
+
if (entry.turnKey.startsWith(`${base}:`)) pendingMatching.push(key)
|
|
473
|
+
}
|
|
474
|
+
for (const key of pendingMatching) {
|
|
475
|
+
const pending = pendingPins.get(key)
|
|
476
|
+
if (pending != null) {
|
|
477
|
+
pending.timer.cancel()
|
|
478
|
+
pendingPins.delete(key)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Snapshot the entries so doUnpin's map mutation doesn't invalidate
|
|
482
|
+
// iteration mid-loop.
|
|
483
|
+
const matching: Array<{ turnKey: string; agentId: string; pinnedId: number }> = []
|
|
484
|
+
for (const [key, pinnedId] of pinned) {
|
|
485
|
+
const sep = key.lastIndexOf('::')
|
|
486
|
+
if (sep < 0) continue
|
|
487
|
+
const tk = key.slice(0, sep)
|
|
488
|
+
if (!tk.startsWith(`${base}:`)) continue
|
|
489
|
+
const agentId = key.slice(sep + 2)
|
|
490
|
+
matching.push({ turnKey: tk, agentId, pinnedId })
|
|
491
|
+
}
|
|
492
|
+
for (const { turnKey, agentId, pinnedId } of matching) {
|
|
493
|
+
doUnpin(turnKey, agentId, chatId, pinnedId)
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
captureServiceMessage({ chatId, pinnedMessageId, serviceMessageId }) {
|
|
498
|
+
// Only act on service messages that wrap one of our tracked pins —
|
|
499
|
+
// otherwise we'd be deleting arbitrary pin-service messages in the
|
|
500
|
+
// chat, which could include user-initiated pins.
|
|
501
|
+
//
|
|
502
|
+
// We match against two sets: per-turn progress-card pins (managed
|
|
503
|
+
// through `considerPin`) AND externally-registered pins (the
|
|
504
|
+
// worker / sub-agent card, registered via `trackExternalPin` —
|
|
505
|
+
// see issue #94).
|
|
506
|
+
let matched = false
|
|
507
|
+
for (const [, msgId] of pinned) {
|
|
508
|
+
if (msgId === pinnedMessageId) { matched = true; break }
|
|
509
|
+
}
|
|
510
|
+
if (!matched && externalPins.has(serviceKey(chatId, pinnedMessageId))) {
|
|
511
|
+
matched = true
|
|
512
|
+
}
|
|
513
|
+
if (!matched) return
|
|
514
|
+
const key = serviceKey(chatId, pinnedMessageId)
|
|
515
|
+
serviceMessages.set(key, serviceMessageId)
|
|
516
|
+
deleteServiceMessage(chatId, serviceMessageId)
|
|
517
|
+
// Also drop the tracked id — if the delete succeeded there's
|
|
518
|
+
// nothing left to scrub on unpin. If it failed the catch logged it
|
|
519
|
+
// and a retry on unpin wouldn't help (Telegram pin-service messages
|
|
520
|
+
// don't age back into existence).
|
|
521
|
+
serviceMessages.delete(key)
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
trackExternalPin(chatId, messageId) {
|
|
525
|
+
externalPins.add(serviceKey(chatId, messageId))
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
untrackExternalPin(chatId, messageId) {
|
|
529
|
+
externalPins.delete(serviceKey(chatId, messageId))
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
pinnedTurnKeys() {
|
|
533
|
+
const seen = new Set<string>()
|
|
534
|
+
for (const key of pinned.keys()) {
|
|
535
|
+
const sep = key.lastIndexOf('::')
|
|
536
|
+
if (sep < 0) continue
|
|
537
|
+
seen.add(key.slice(0, sep))
|
|
538
|
+
}
|
|
539
|
+
return [...seen]
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
pinnedAgentIds(turnKey) {
|
|
543
|
+
const out: string[] = []
|
|
544
|
+
for (const key of pinned.keys()) {
|
|
545
|
+
const sep = key.lastIndexOf('::')
|
|
546
|
+
if (sep < 0) continue
|
|
547
|
+
if (key.slice(0, sep) !== turnKey) continue
|
|
548
|
+
out.push(key.slice(sep + 2))
|
|
549
|
+
}
|
|
550
|
+
return out
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
pinnedMessageId(turnKey, agentId) {
|
|
554
|
+
return pinned.get(pinKey(turnKey, agentId ?? PARENT_AGENT_ID))
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
pinnedEntries() {
|
|
558
|
+
const out: Array<{
|
|
559
|
+
turnKey: string
|
|
560
|
+
agentId: string
|
|
561
|
+
chatId: string
|
|
562
|
+
threadId?: string
|
|
563
|
+
messageId: number
|
|
564
|
+
}> = []
|
|
565
|
+
for (const [key, messageId] of pinned) {
|
|
566
|
+
const sep = key.lastIndexOf('::')
|
|
567
|
+
if (sep < 0) continue
|
|
568
|
+
const turnKey = key.slice(0, sep)
|
|
569
|
+
const agentId = key.slice(sep + 2)
|
|
570
|
+
const meta = pinnedMeta.get(key)
|
|
571
|
+
if (meta == null) continue
|
|
572
|
+
out.push({
|
|
573
|
+
turnKey,
|
|
574
|
+
agentId,
|
|
575
|
+
chatId: meta.chatId,
|
|
576
|
+
...(meta.threadId != null ? { threadId: meta.threadId } : {}),
|
|
577
|
+
messageId,
|
|
578
|
+
})
|
|
579
|
+
}
|
|
580
|
+
return out
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
async drainInFlight() {
|
|
584
|
+
// Copy so we don't race with track() adding more while we await.
|
|
585
|
+
const snapshot = [...inFlight]
|
|
586
|
+
await Promise.allSettled(snapshot)
|
|
587
|
+
},
|
|
588
|
+
}
|
|
589
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress-card pin watchdog.
|
|
3
|
+
*
|
|
4
|
+
* The pin manager (progress-card-pin-manager.ts) owns the pin/unpin
|
|
5
|
+
* sequence at turn boundaries. Between those boundaries, nothing in
|
|
6
|
+
* our code path re-checks whether the pin Telegram actually shows the
|
|
7
|
+
* user still matches the one we pinned — but in practice it can
|
|
8
|
+
* drift:
|
|
9
|
+
*
|
|
10
|
+
* - Another user (or the bot itself via a different surface) pins
|
|
11
|
+
* a message, displacing ours. Telegram keeps ours in history but
|
|
12
|
+
* the chat header now points elsewhere.
|
|
13
|
+
* - A stale early-unpin fires on a pending turn (the bug this
|
|
14
|
+
* watchdog backstops) — we've removed the known code paths but
|
|
15
|
+
* want defense in depth.
|
|
16
|
+
* - The user manually unpins mid-turn.
|
|
17
|
+
*
|
|
18
|
+
* The watchdog runs on every progress-card heartbeat emit. It is
|
|
19
|
+
* rate-limited per turnKey (default 30s) so we don't hammer
|
|
20
|
+
* getChat. When it sees a mismatch, it re-pins the card so the user
|
|
21
|
+
* keeps seeing "work in progress" at the top of the chat until the
|
|
22
|
+
* turn actually completes.
|
|
23
|
+
*
|
|
24
|
+
* Contract (covered by progress-card-pin-watchdog.test.ts):
|
|
25
|
+
*
|
|
26
|
+
* verify({ chatId, turnKey, expectedMessageId })
|
|
27
|
+
* - First call for a turnKey always probes.
|
|
28
|
+
* - Subsequent calls within `intervalMs` are silent no-ops.
|
|
29
|
+
* - On probe: if getCurrentPinned returns the expected id, no
|
|
30
|
+
* action. Otherwise re-pin. Errors from either API call are
|
|
31
|
+
* caught and logged — never thrown.
|
|
32
|
+
*
|
|
33
|
+
* clear(turnKey)
|
|
34
|
+
* - Drops the rate-limit timestamp so a future turn reusing the
|
|
35
|
+
* key starts fresh. Called from onTurnComplete.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export interface PinWatchdogDeps {
|
|
39
|
+
/**
|
|
40
|
+
* Read the id of whatever Telegram currently shows pinned at the
|
|
41
|
+
* top of `chatId`. Implementations typically call
|
|
42
|
+
* `bot.api.getChat(chatId).pinned_message?.message_id`. Return
|
|
43
|
+
* undefined when nothing is pinned.
|
|
44
|
+
*/
|
|
45
|
+
getCurrentPinned: (chatId: string) => Promise<number | undefined>
|
|
46
|
+
/** Re-pin if we detect a mismatch. Mirrors `pinChatMessage`. */
|
|
47
|
+
pin: (
|
|
48
|
+
chatId: string,
|
|
49
|
+
messageId: number,
|
|
50
|
+
opts?: { disable_notification?: boolean },
|
|
51
|
+
) => Promise<unknown>
|
|
52
|
+
/** Minimum interval between getChat probes per turnKey. Default 30s. */
|
|
53
|
+
intervalMs?: number
|
|
54
|
+
/** Clock injection for test determinism. Defaults to `Date.now`. */
|
|
55
|
+
now?: () => number
|
|
56
|
+
/** Receives log lines with trailing newline. */
|
|
57
|
+
log?: (line: string) => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PinWatchdog {
|
|
61
|
+
verify(args: {
|
|
62
|
+
chatId: string
|
|
63
|
+
turnKey: string
|
|
64
|
+
expectedMessageId: number
|
|
65
|
+
}): Promise<void>
|
|
66
|
+
/** Clear state for a turnKey (call on turn completion). */
|
|
67
|
+
clear(turnKey: string): void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createPinWatchdog(deps: PinWatchdogDeps): PinWatchdog {
|
|
71
|
+
const interval = deps.intervalMs ?? 30_000
|
|
72
|
+
const now = deps.now ?? Date.now
|
|
73
|
+
const log = deps.log ?? (() => {})
|
|
74
|
+
// turnKey -> last probe timestamp. Cleared on turn completion.
|
|
75
|
+
const lastProbedAt = new Map<string, number>()
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
async verify({ chatId, turnKey, expectedMessageId }) {
|
|
79
|
+
const last = lastProbedAt.get(turnKey)
|
|
80
|
+
const t = now()
|
|
81
|
+
// First call always probes. Subsequent calls wait for the
|
|
82
|
+
// interval so a chatty turn doesn't saturate getChat.
|
|
83
|
+
if (last != null && t - last < interval) return
|
|
84
|
+
lastProbedAt.set(turnKey, t)
|
|
85
|
+
try {
|
|
86
|
+
const currentPinned = await deps.getCurrentPinned(chatId)
|
|
87
|
+
if (currentPinned === expectedMessageId) return
|
|
88
|
+
await deps.pin(chatId, expectedMessageId, { disable_notification: true })
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
91
|
+
log(`telegram gateway: progress-card watchdog failed: ${msg}\n`)
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
clear(turnKey) {
|
|
95
|
+
lastProbedAt.delete(turnKey)
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|