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,233 @@
|
|
|
1
|
+
# Waiting-for-reply UX — v2 spec (three-class contract)
|
|
2
|
+
|
|
3
|
+
Tracks: [#545](https://github.com/mekenthompson/switchroom/issues/545),
|
|
4
|
+
[#553](https://github.com/mekenthompson/switchroom/issues/553) (PR series)
|
|
5
|
+
|
|
6
|
+
This document codifies the user-perceived contract for what happens
|
|
7
|
+
between "I sent a Telegram message" and "the agent's reply is locked
|
|
8
|
+
in." The contract varies by **turn class**. The v2 rewrite (#553)
|
|
9
|
+
sharpens the gates: tools alone never trigger the progress card,
|
|
10
|
+
placeholder text is removed entirely, and sub-agents (= background
|
|
11
|
+
workers) are the single concept for parallel work.
|
|
12
|
+
|
|
13
|
+
## Three turn classes
|
|
14
|
+
|
|
15
|
+
### Class A — Instant (<2s, NO tools)
|
|
16
|
+
|
|
17
|
+
| Surface | Contract |
|
|
18
|
+
| ----------------- | ---------------------------------------------------------------------- |
|
|
19
|
+
| Status reaction | 👀 within 800ms of inbound. Terminates with 👍. |
|
|
20
|
+
| Progress card | **Never rendered.** Suppressed regardless of `initialDelayMs`. |
|
|
21
|
+
| Placeholder text | **Never sent.** No `🔵 thinking` / `📚 recalling memories` / `💭 thinking`. |
|
|
22
|
+
| Answer text | First answer-text edit lands within **1500ms** of inbound (Class A budget; pinned in #553 PR 3). |
|
|
23
|
+
| Ladder | 👀 → 👍. Optional 🤔 if the controller debounce window is crossed. |
|
|
24
|
+
|
|
25
|
+
User experience: feels like a chat partner typing back instantly.
|
|
26
|
+
|
|
27
|
+
### Class B — Short (2–60s, tools, NO sub-agents)
|
|
28
|
+
|
|
29
|
+
| Surface | Contract |
|
|
30
|
+
| ----------------- | ---------------------------------------------------------------------- |
|
|
31
|
+
| Status reaction | 👀 within 800ms. Ladder progresses through 🤔 / tool-glyphs (🔥/✍/👨💻/⚡) before 👍. **Must NOT collapse straight to 👍.** |
|
|
32
|
+
| Progress card | **Never rendered.** The card gate is `(elapsed >= 60s) OR (sub-agent appeared)` — tools alone do NOT trigger it. |
|
|
33
|
+
| Placeholder text | **Never sent.** |
|
|
34
|
+
| Answer text | First answer-text edit lands within **3000ms** of inbound (Class B/C budget; pinned in #553 PR 3). Streams progressively as the model produces tokens. |
|
|
35
|
+
| Final | 👍 + locked stream answer. |
|
|
36
|
+
|
|
37
|
+
User experience: live ladder of tool reactions, answer text starts
|
|
38
|
+
streaming as soon as the model resumes, no fake "thinking" spacers.
|
|
39
|
+
|
|
40
|
+
### Class C — Long-running (>60s OR sub-agents/background workers)
|
|
41
|
+
|
|
42
|
+
| Surface | Contract |
|
|
43
|
+
| ----------------- | ---------------------------------------------------------------------- |
|
|
44
|
+
| Status reaction | 👀 within 800ms. Ladder throughout. Settles to 👍 only after full quiescence (all sub-agents terminal). |
|
|
45
|
+
| Progress card | Renders the moment the gate trips: `(elapsed >= 60s) OR (any sub-agent has appeared)`. Stays pinned-feel and stable. |
|
|
46
|
+
| Card "Done" stamp | **≥ last sub-agent terminal timestamp.** Card never marks Done while a sub-agent is still in flight. |
|
|
47
|
+
| Sub-agent header | Header count == rendered-list-length. **No drift between summary and bullets.** |
|
|
48
|
+
| Placeholder text | **Never sent.** |
|
|
49
|
+
|
|
50
|
+
A "background worker" ≡ a sub-agent dispatched with
|
|
51
|
+
`Agent({ run_in_background: true })`. There is no separate concept —
|
|
52
|
+
the card gate, the bullet list, and the quiescence check all key on
|
|
53
|
+
the sub-agent stream.
|
|
54
|
+
|
|
55
|
+
## Key invariants (v2)
|
|
56
|
+
|
|
57
|
+
1. **No placeholder strings** — `🔵 thinking`, `📚 recalling memories`,
|
|
58
|
+
and `💭 thinking` must never appear in any `sendMessage` /
|
|
59
|
+
`editMessageText` payload at any point in any turn class. PR 5
|
|
60
|
+
removes the production code that emits them.
|
|
61
|
+
2. **Card gate** — `(elapsed >= 60s) OR (any sub-agent has appeared)`.
|
|
62
|
+
Tool-use count, tool category, and parent narrative content are
|
|
63
|
+
NOT inputs to the gate.
|
|
64
|
+
3. **First-answer-text deadline** — Class A: <1500ms. Class B/C:
|
|
65
|
+
<3000ms (committed in #553 PR 3). Budget: 500ms inbound coalesce +
|
|
66
|
+
~1s minInitialChars-driven first send + ~1.5s model TTFT for short
|
|
67
|
+
replies.
|
|
68
|
+
4. **Sub-agent header == list length** — every render of the card.
|
|
69
|
+
|
|
70
|
+
## PR 1 — foundation: spec + harness extensions (this PR)
|
|
71
|
+
|
|
72
|
+
PR 1 ships:
|
|
73
|
+
|
|
74
|
+
- This rewritten spec — supersedes the v1 four-failure-mode framing.
|
|
75
|
+
- Three new helpers on `tests/real-gateway-harness.ts`:
|
|
76
|
+
- `expectNoPlaceholderEdits(chatId)` — returns recorded calls whose
|
|
77
|
+
payload matches a banned placeholder string. Tests assert
|
|
78
|
+
`toEqual([])`.
|
|
79
|
+
- `expectNoCardSent(chatId)` — wraps `progressCardSendMs` for
|
|
80
|
+
assertion-friendly use (`.toBeNull()`).
|
|
81
|
+
- `firstAnswerTextMs(chatId)` — first `sendMessage` /
|
|
82
|
+
`editMessageText` whose payload is neither a card payload nor a
|
|
83
|
+
placeholder string.
|
|
84
|
+
- A new RED test file `tests/real-gateway-spec.test.ts` (all
|
|
85
|
+
`describe.skip`'d) pinning the three-class contract:
|
|
86
|
+
- Class A — 5 tests
|
|
87
|
+
- Class B — 4 tests
|
|
88
|
+
- Class C — 5 tests
|
|
89
|
+
Each carries a `// TODO(#553-PR-N)` marker for which subsequent PR
|
|
90
|
+
un-skips it.
|
|
91
|
+
|
|
92
|
+
PR 1 does NOT change production code. The existing F1/F2/F3/F4
|
|
93
|
+
regression tests stay green; the new spec tests are skipped, so they
|
|
94
|
+
do not gate CI yet.
|
|
95
|
+
|
|
96
|
+
## PR 2–5 — implementation roadmap
|
|
97
|
+
|
|
98
|
+
| PR | Scope | Un-skips | Status |
|
|
99
|
+
| --- | -------------------------------------------------------------------- | --------------------------------------------------- | -------- |
|
|
100
|
+
| 2 | Kill instant-draft placeholder; preserve early-ack 👀 | Class A no-placeholder, Class B no-placeholder | shipped |
|
|
101
|
+
| 3 | First-answer-text deadline implementation; tighten <Ns numbers | Class A/B/C answer-text-deadline assertions | shipped |
|
|
102
|
+
| 4 | Card-gate rewrite to `(>=60s) OR (sub-agent appeared)` | Class B no-card; Class C card-gate tests | shipped |
|
|
103
|
+
| 5 | Remove `🔵 thinking` / `📚 recalling memories` / `💭 thinking` strings; sub-agent header = list length | Remaining no-placeholder + sub-agent count tests | shipped |
|
|
104
|
+
|
|
105
|
+
**PR 4 status:** shipped the card-gate rewrite. Defaults change
|
|
106
|
+
from `initialDelayMs=30_000, promoteAfterMs=5_000, promoteOnParentToolCount=3`
|
|
107
|
+
to `initialDelayMs=60_000, promoteAfterMs=0 (disabled), promoteOnParentToolCount=0
|
|
108
|
+
(disabled)`. The Class B "no card rendered" and Class C "card renders on
|
|
109
|
+
sub-agent" / "card renders after 60s" tests are un-skipped. F3's late-card
|
|
110
|
+
symptom (long single-tool turn shows no card) is now intentional spec
|
|
111
|
+
behaviour rather than a bug — see `real-gateway-f3-late-card.test.ts`
|
|
112
|
+
header for the reframe.
|
|
113
|
+
|
|
114
|
+
**PR 5 status (this PR):** ships the placeholder-text removal. The
|
|
115
|
+
pre-allocated draft path, the `update_placeholder` IPC handler, the
|
|
116
|
+
placeholder heartbeat (`placeholder-heartbeat.ts`), the phase enrichment
|
|
117
|
+
(`placeholder-phase.ts`), the `forum-topic-placeholder.ts` substitute,
|
|
118
|
+
and all associated state in `gateway.ts` are deleted. recall.py's
|
|
119
|
+
`update_placeholder` IPC calls now no-op cleanly because the gateway
|
|
120
|
+
no longer registers an `onUpdatePlaceholder` handler — the IPC client
|
|
121
|
+
emits a fire-and-forget JSON line, and the gateway dispatch silently
|
|
122
|
+
ignores the unknown message type. Hindsight (`vendor/hindsight-memory/`)
|
|
123
|
+
is untouched. The Class A/B/C no-placeholder assertions and the Class C
|
|
124
|
+
sub-agent-header-equals-list-length assertion are un-skipped.
|
|
125
|
+
|
|
126
|
+
## IPC + bridge lifecycle invariants
|
|
127
|
+
|
|
128
|
+
Pinned by `tests/real-gateway-ipc-lifecycle.test.ts`. These five
|
|
129
|
+
invariants codify behaviors that the user-perceived waiting-UX spec
|
|
130
|
+
above implicitly relies on but which live one layer down — at the
|
|
131
|
+
IPC server / bridge connect-disconnect boundary. Multiple production
|
|
132
|
+
bugs in 2026-05 traced back to the absence of test coverage at this
|
|
133
|
+
layer; this section is the canonical write-up of what the gateway
|
|
134
|
+
must guarantee.
|
|
135
|
+
|
|
136
|
+
- **I1 — Anonymous IPC client lifecycle is observably invisible.**
|
|
137
|
+
A client that connects but never registers an agent (e.g. recall.py
|
|
138
|
+
sending a one-shot legacy IPC message) MUST NOT mutate any active
|
|
139
|
+
status reactions, MUST NOT dispose the progress driver, and MUST
|
|
140
|
+
NOT close any draft streams. The disconnect path of an unregistered
|
|
141
|
+
client is a no-op on user-visible state. (Bug A — premature 👍 on
|
|
142
|
+
recall.py disconnect, fixed in PR #600.)
|
|
143
|
+
|
|
144
|
+
- **I2 — Per-agent disconnect isolation.** When agent X's bridge
|
|
145
|
+
disconnects, only X's status reactions get flushed to `setDone()`.
|
|
146
|
+
Agent Y's active reactions stay untouched. Switchroom is single-
|
|
147
|
+
agent-per-gateway today, but the invariant pins the right
|
|
148
|
+
semantics so multi-agent gateways cannot regress silently.
|
|
149
|
+
|
|
150
|
+
- **I3 — 👍 fires AFTER real delivery, not after JSONL `turn_end`.**
|
|
151
|
+
For any reply tool path (`reply`, `stream_reply done=true`), the
|
|
152
|
+
controller's `setDone()` MUST happen at-or-after the timestamp of
|
|
153
|
+
the final outbound to Telegram for that reply. Firing on the
|
|
154
|
+
JSONL `turn_end` event before the round-trip completes produces a
|
|
155
|
+
visible flash of 👍 with no reply text. (Bug D + Bug Z.)
|
|
156
|
+
|
|
157
|
+
- **I4 — Legacy IPC types are tolerated, not lethal.** Any message
|
|
158
|
+
type the gateway no longer handles (e.g. `update_placeholder`
|
|
159
|
+
after #553 PR 5) MUST be soft-accepted: the validator returns
|
|
160
|
+
false, `processBuffer` logs and continues, the connection stays
|
|
161
|
+
open, and no active state is mutated. (Bug B — gateway crash on
|
|
162
|
+
recall.py's `update_placeholder` after PR 5; fixed in PR #600.)
|
|
163
|
+
|
|
164
|
+
- **I5 — Wake-audit dedup.** The `.wake-audit-pending` sentinel and
|
|
165
|
+
audit cycle MUST NOT re-fire mid-conversation under `--continue`
|
|
166
|
+
respawn. (Bug C — duplicate greeting reply on respawn. The actual
|
|
167
|
+
fix lives in profiles, not in the gateway, but the regression
|
|
168
|
+
surface is observable here as "duplicate outbound for the same
|
|
169
|
+
logical turn" and so the test invariant lives in this file.)
|
|
170
|
+
|
|
171
|
+
The test file documents which invariants currently have green tests,
|
|
172
|
+
which depend on in-flight PRs to land first, and which are `.skip`'d
|
|
173
|
+
pending the matching fix branch. See the file header for the
|
|
174
|
+
bug-to-PR map.
|
|
175
|
+
|
|
176
|
+
## Failure-mode history (F1–F4, fixed in earlier #553 PRs)
|
|
177
|
+
|
|
178
|
+
The v1 spec framed the rewrite around four observed regressions from
|
|
179
|
+
the 2026-04-30 live demo. They are all fixed; the regression tests
|
|
180
|
+
(`tests/real-gateway-f1-ladder-integrity.test.ts`,
|
|
181
|
+
`real-gateway-f2-instant-draft.test.ts`, `real-gateway-f3-late-card.test.ts`,
|
|
182
|
+
`real-gateway-f4-interim-text.test.ts`) stay in place to keep the gaps closed.
|
|
183
|
+
|
|
184
|
+
| ID | Symptom | Class | Status |
|
|
185
|
+
| --- | -------------------------------------------------------------------------- | ----- | ------------------------------------------------- |
|
|
186
|
+
| F1 | Ladder collapses straight to 👍 (skips 👀 → 🤔 → 🔥) | B | Fixed — `StatusReactionController.finishWithState` flushes pending pre-terminal emoji. |
|
|
187
|
+
| F2 | No instant draft / typing signal — chat sits silent "for ages" | All | Fixed — `handleInboundCoalesced` fires 👀 directly on raw arrival before the coalesce buffer. |
|
|
188
|
+
| F3 | Progress card renders late (after turn_end, or never on long turns) | C | Fixed under v1 rules with a 5s time-promote in the driver; **superseded by PR 4**, which replaces the gate with `(>=60s) OR (sub-agent)`. |
|
|
189
|
+
| F4 | Pre-tool preamble static — one preamble then silence | B | Regression-guarded only; not reproducible deterministically. The v2 contract sidesteps F4 by tightening the first-answer-text deadline (Class B/C, PR 3). |
|
|
190
|
+
|
|
191
|
+
The F1/F2/F3/F4 tests remain green throughout the v2 rewrite — they
|
|
192
|
+
encode tighter invariants than the v2 spec relaxes. PR 4's gate
|
|
193
|
+
change does not regress F3 (the F3 long-single-tool case crosses the
|
|
194
|
+
60s threshold).
|
|
195
|
+
|
|
196
|
+
## Test methodology
|
|
197
|
+
|
|
198
|
+
- **Time control**: `vi.useFakeTimers()` + `vi.setSystemTime` for
|
|
199
|
+
deterministic wall-clock assertions. The harness records every
|
|
200
|
+
outbound `bot.api` call with `Date.now()` at invocation, so all
|
|
201
|
+
deadlines are reproducibly measurable.
|
|
202
|
+
- **Recorder** (existing): `firstReactionMs`, `progressCardSendMs`,
|
|
203
|
+
`reactionSequence`, `lastReactionEmoji`, `edits`, `sentTexts`.
|
|
204
|
+
- **Recorder** (PR 1 additions): `expectNoPlaceholderEdits`,
|
|
205
|
+
`expectNoCardSent`, `firstAnswerTextMs`.
|
|
206
|
+
- **Production wiring**: the harness uses the actual
|
|
207
|
+
`StatusReactionController`, `createProgressDriver`, and
|
|
208
|
+
`createInboundCoalescer` from production — not mocks. The bot.api
|
|
209
|
+
layer below is a recording fake.
|
|
210
|
+
- **Out of scope**: foreman queue, IPC bridge, auth, history. Those
|
|
211
|
+
do not influence the user-perceived waiting UX.
|
|
212
|
+
|
|
213
|
+
## CI gate
|
|
214
|
+
|
|
215
|
+
The harness runs as part of the root vitest suite via `npm test` →
|
|
216
|
+
`vitest run`. PR 1's spec tests are `describe.skip`'d and do not
|
|
217
|
+
fail CI; PRs 2–5 un-skip them as the production code lands.
|
|
218
|
+
|
|
219
|
+
## Phase 1 / Phase 3 history (legacy)
|
|
220
|
+
|
|
221
|
+
For posterity:
|
|
222
|
+
|
|
223
|
+
- **Phase 1** (#547): `tests/waiting-ux.e2e.test.ts` — controller +
|
|
224
|
+
driver in isolation, hand-written `feedSessionEvent` adapter. F2
|
|
225
|
+
passed trivially because the harness called `setQueued()`
|
|
226
|
+
synchronously inside `inbound()`.
|
|
227
|
+
- **Phase 3** (#553 PR 1, original): `tests/real-gateway-harness.ts`
|
|
228
|
+
composed the production `InboundCoalescer` before the Phase 1
|
|
229
|
+
controller stack, exposing the real F2 gap (👀 only fired after
|
|
230
|
+
the coalesce window). F2's fix landed against this harness.
|
|
231
|
+
- **v2 rewrite** (this PR series, also numbered #553): same Phase 3
|
|
232
|
+
harness, plus three v2 helpers; new spec test file pins the
|
|
233
|
+
three-class contract.
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-in-place streaming for Telegram messages.
|
|
3
|
+
*
|
|
4
|
+
* Ports the throttle/flush pattern from openclaw's
|
|
5
|
+
* src/channels/draft-stream-loop.ts. The loop holds a single `pendingText`
|
|
6
|
+
* snapshot (NOT a queue — only the latest matters) plus a single in-flight
|
|
7
|
+
* promise. update(text) either fires immediately if the throttle window
|
|
8
|
+
* is open, or schedules a setTimeout for the remaining ms. When the
|
|
9
|
+
* in-flight call resolves, if pendingText changed during flight it loops
|
|
10
|
+
* once more without waiting.
|
|
11
|
+
*
|
|
12
|
+
* This is what makes the experience feel responsive without burning
|
|
13
|
+
* Telegram's 1-edit-per-second-per-message rate limit. The latest delta
|
|
14
|
+
* always lands within ~1s, with at most one outstanding API call.
|
|
15
|
+
*
|
|
16
|
+
* In our model-driven architecture (no inference hooks), the controller
|
|
17
|
+
* is driven by the model calling stream_reply(text, done) multiple times
|
|
18
|
+
* during a long task. First call → sendMessage (or sendMessageDraft in DMs).
|
|
19
|
+
* Subsequent calls → throttled editMessageText (or sendMessageDraft). done=true
|
|
20
|
+
* → flush, materialize as a fresh sendMessage (push notification), clear draft.
|
|
21
|
+
*
|
|
22
|
+
* Transport selection:
|
|
23
|
+
* - previewTransport: "auto" (default) — use draft in DMs only
|
|
24
|
+
* - previewTransport: "draft" — always use draft (if API available)
|
|
25
|
+
* - previewTransport: "message" — always use sendMessage/editMessageText
|
|
26
|
+
*
|
|
27
|
+
* Forum topics (message_thread_id set) force message transport because
|
|
28
|
+
* sendMessageDraft does not support threads. The caller (stream-controller.ts)
|
|
29
|
+
* handles this by passing previewTransport: "message" for threaded chats.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
shouldFallbackFromDraftTransport,
|
|
34
|
+
allocateDraftId,
|
|
35
|
+
} from './draft-transport.js'
|
|
36
|
+
|
|
37
|
+
const TELEGRAM_MAX_CHARS = 4096
|
|
38
|
+
const DEFAULT_THROTTLE_MS = 1000
|
|
39
|
+
const MIN_THROTTLE_MS = 250
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Send the first message in a stream. Receives the rendered text plus a
|
|
43
|
+
* thread_id (forum topic) and returns the new Telegram message_id.
|
|
44
|
+
*/
|
|
45
|
+
export type StreamSendFn = (text: string) => Promise<number>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Edit an existing stream message. Receives the message_id and rendered text.
|
|
49
|
+
*/
|
|
50
|
+
export type StreamEditFn = (messageId: number, text: string) => Promise<void>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional sendMessageDraft callback. When present and the transport is
|
|
54
|
+
* "draft", this is called instead of sendMessage/editMessageText.
|
|
55
|
+
* Signature mirrors Telegram's sendMessageDraft Bot API method.
|
|
56
|
+
*/
|
|
57
|
+
export type StreamDraftFn = (
|
|
58
|
+
chatId: string,
|
|
59
|
+
draftId: number,
|
|
60
|
+
text: string,
|
|
61
|
+
params?: { message_thread_id?: number },
|
|
62
|
+
) => Promise<unknown>
|
|
63
|
+
|
|
64
|
+
export interface DraftStreamConfig {
|
|
65
|
+
/** Throttle window in ms. Floored at 250. Default 1000. */
|
|
66
|
+
throttleMs?: number
|
|
67
|
+
/**
|
|
68
|
+
* Maximum total characters before hard-stopping the stream. Default 4096
|
|
69
|
+
* (Telegram's limit). When exceeded, future updates are ignored — the
|
|
70
|
+
* caller should fall back to a fresh sendMessage.
|
|
71
|
+
*/
|
|
72
|
+
maxChars?: number
|
|
73
|
+
/**
|
|
74
|
+
* Optional debounce window applied BEFORE the first send of a stream.
|
|
75
|
+
* When > 0, the first update() defers the send by idleMs, restarting
|
|
76
|
+
* the timer on each additional update that arrives during the window.
|
|
77
|
+
* Useful when the caller bursts several update() calls at turn start
|
|
78
|
+
* and you'd rather collapse them into a single send than pay the
|
|
79
|
+
* latency of an immediate first-fire + follow-up edit.
|
|
80
|
+
*
|
|
81
|
+
* Default 0 (no pre-send debounce — first update fires immediately).
|
|
82
|
+
* Only affects the first send; subsequent edits use throttleMs.
|
|
83
|
+
*
|
|
84
|
+
* NOTE: This debounce only applies to message transport. Draft transport
|
|
85
|
+
* fires immediately on the first update because drafts are ephemeral —
|
|
86
|
+
* the throttle/flush loop already collapses bursts into 1 API call/sec
|
|
87
|
+
* via throttleMs.
|
|
88
|
+
*/
|
|
89
|
+
idleMs?: number
|
|
90
|
+
/**
|
|
91
|
+
* Transport selector.
|
|
92
|
+
* - "auto" (default): use draft transport when isPrivateChat=true AND
|
|
93
|
+
* sendMessageDraft is provided; otherwise use message transport.
|
|
94
|
+
* - "draft": always prefer draft (falls back to message if sendMessageDraft absent).
|
|
95
|
+
* - "message": always use sendMessage/editMessageText.
|
|
96
|
+
*/
|
|
97
|
+
previewTransport?: 'auto' | 'message' | 'draft'
|
|
98
|
+
/**
|
|
99
|
+
* True if the current chat is a private DM. Used by "auto" transport to
|
|
100
|
+
* decide whether to activate draft. Has no effect when previewTransport
|
|
101
|
+
* is "draft" or "message".
|
|
102
|
+
*/
|
|
103
|
+
isPrivateChat?: boolean
|
|
104
|
+
/**
|
|
105
|
+
* sendMessageDraft callback. When absent, the stream falls back to
|
|
106
|
+
* sendMessage/editMessageText regardless of previewTransport.
|
|
107
|
+
*/
|
|
108
|
+
sendMessageDraft?: StreamDraftFn
|
|
109
|
+
/**
|
|
110
|
+
* The Telegram chat id string — required when sendMessageDraft is provided,
|
|
111
|
+
* so the draft can be cleared on finalize.
|
|
112
|
+
*/
|
|
113
|
+
chatId?: string
|
|
114
|
+
/** Optional logger for debugging. Receives one string per event. */
|
|
115
|
+
log?: (msg: string) => void
|
|
116
|
+
/** Optional warning logger. Used for transport fallback notices. */
|
|
117
|
+
warn?: (msg: string) => void
|
|
118
|
+
/**
|
|
119
|
+
* If set, the stream is initialized as if a previous send had landed
|
|
120
|
+
* with this `message_id` — the FIRST update() call invokes `edit`
|
|
121
|
+
* against this id rather than `send`. Used by callers (notably the
|
|
122
|
+
* gateway's progress-card emit) that know the anchor message id from
|
|
123
|
+
* an external source (e.g. the pin manager) and want to guarantee a
|
|
124
|
+
* subsequent emit edits in place rather than creating a fresh
|
|
125
|
+
* sendMessage. This closes the "done=true → activeDraftStreams entry
|
|
126
|
+
* deleted → next emit creates fresh sendMessage" duplicate-message
|
|
127
|
+
* class (issue #626). The not-found fallback at the edit site
|
|
128
|
+
* (line ~280: re-send on `MESSAGE_ID_INVALID`) gracefully handles a
|
|
129
|
+
* stale id — the bad edit fails once, then a fresh send fires.
|
|
130
|
+
*/
|
|
131
|
+
initialMessageId?: number | null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface DraftStreamHandle {
|
|
135
|
+
/**
|
|
136
|
+
* Push a new full-text snapshot. The loop holds only the latest. Returns
|
|
137
|
+
* a promise that resolves once this update has either (a) been sent or
|
|
138
|
+
* (b) been superseded by a later update.
|
|
139
|
+
*/
|
|
140
|
+
update(text: string): Promise<void>
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Mark the stream as final. Flushes any pending text and rejects all
|
|
144
|
+
* future update() calls. Returns a promise that resolves once the final
|
|
145
|
+
* edit has landed (or the initial send if no edits ever fired).
|
|
146
|
+
*/
|
|
147
|
+
finalize(): Promise<void>
|
|
148
|
+
|
|
149
|
+
/** Returns the captured Telegram message_id, or null if nothing has sent yet. */
|
|
150
|
+
getMessageId(): number | null
|
|
151
|
+
|
|
152
|
+
/** True if finalize() has been called. */
|
|
153
|
+
isFinal(): boolean
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a draft stream bound to a specific Telegram chat+thread.
|
|
158
|
+
*
|
|
159
|
+
* The first update() call invokes `send` to create the message. All
|
|
160
|
+
* subsequent calls invoke `edit` against the captured message_id.
|
|
161
|
+
*
|
|
162
|
+
* When sendMessageDraft is provided (and transport allows it), intermediate
|
|
163
|
+
* updates use the draft API instead of sendMessage/editMessageText. On
|
|
164
|
+
* finalize(), a real sendMessage is sent for push notification, then the
|
|
165
|
+
* draft is cleared best-effort.
|
|
166
|
+
*/
|
|
167
|
+
export function createDraftStream(
|
|
168
|
+
send: StreamSendFn,
|
|
169
|
+
edit: StreamEditFn,
|
|
170
|
+
config: DraftStreamConfig = {},
|
|
171
|
+
): DraftStreamHandle {
|
|
172
|
+
const throttleMs = Math.max(MIN_THROTTLE_MS, config.throttleMs ?? DEFAULT_THROTTLE_MS)
|
|
173
|
+
const maxChars = config.maxChars ?? TELEGRAM_MAX_CHARS
|
|
174
|
+
const idleMs = Math.max(0, config.idleMs ?? 0)
|
|
175
|
+
const log = config.log
|
|
176
|
+
const warn = config.warn
|
|
177
|
+
const draftApi = config.sendMessageDraft
|
|
178
|
+
const chatId = config.chatId ?? ''
|
|
179
|
+
|
|
180
|
+
// Resolve transport
|
|
181
|
+
const requestedTransport = config.previewTransport ?? 'auto'
|
|
182
|
+
const prefersDraft =
|
|
183
|
+
requestedTransport === 'draft'
|
|
184
|
+
? true
|
|
185
|
+
: requestedTransport === 'message'
|
|
186
|
+
? false
|
|
187
|
+
: (config.isPrivateChat === true) // 'auto': DM only
|
|
188
|
+
|
|
189
|
+
// Footgun guard: caller asked for "auto" + provided sendMessageDraft but
|
|
190
|
+
// forgot isPrivateChat. They almost certainly wanted draft in DMs but will
|
|
191
|
+
// silently get message transport everywhere. Warn so the bug is visible.
|
|
192
|
+
if (
|
|
193
|
+
requestedTransport === 'auto'
|
|
194
|
+
&& draftApi != null
|
|
195
|
+
&& config.isPrivateChat === undefined
|
|
196
|
+
) {
|
|
197
|
+
warn?.('draft-stream: previewTransport="auto" with sendMessageDraft but isPrivateChat undefined — defaulting to message transport')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Use draft transport only if we have the API
|
|
201
|
+
let usesDraftTransport = prefersDraft && draftApi != null
|
|
202
|
+
let draftId: number | undefined = usesDraftTransport
|
|
203
|
+
? allocateDraftId()
|
|
204
|
+
: undefined
|
|
205
|
+
|
|
206
|
+
if (prefersDraft && !usesDraftTransport) {
|
|
207
|
+
warn?.('draft-stream: sendMessageDraft unavailable; falling back to sendMessage/editMessageText')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let messageId: number | null = config.initialMessageId ?? null
|
|
211
|
+
let pendingText: string | null = null
|
|
212
|
+
let lastSentText: string | null = null
|
|
213
|
+
let lastSentAt = 0
|
|
214
|
+
let inFlight: Promise<void> | null = null
|
|
215
|
+
let scheduledTimer: ReturnType<typeof setTimeout> | null = null
|
|
216
|
+
let final = false
|
|
217
|
+
let stopped = false
|
|
218
|
+
|
|
219
|
+
// Tracks pending update() calls so caller can `await` the next flush
|
|
220
|
+
const waiters: Array<() => void> = []
|
|
221
|
+
|
|
222
|
+
function notifyWaiters(): void {
|
|
223
|
+
const w = waiters.splice(0)
|
|
224
|
+
for (const fn of w) {
|
|
225
|
+
try {
|
|
226
|
+
fn()
|
|
227
|
+
} catch { /* ignore waiter errors */ }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function sendViaDraft(textToSend: string): Promise<boolean> {
|
|
232
|
+
if (!draftApi || draftId == null) return false
|
|
233
|
+
try {
|
|
234
|
+
await draftApi(chatId, draftId, textToSend)
|
|
235
|
+
log?.(`stream → draft (id: ${draftId}, ${textToSend.length} chars)`)
|
|
236
|
+
return true
|
|
237
|
+
} catch (err) {
|
|
238
|
+
if (shouldFallbackFromDraftTransport(err)) {
|
|
239
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
240
|
+
warn?.(`draft-stream: sendMessageDraft rejected — falling back to sendMessage/editMessageText (${msg})`)
|
|
241
|
+
usesDraftTransport = false
|
|
242
|
+
draftId = undefined
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
throw err
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function flush(): Promise<void> {
|
|
250
|
+
if (stopped) {
|
|
251
|
+
notifyWaiters()
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
if (pendingText == null) {
|
|
255
|
+
notifyWaiters()
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
const textToSend = pendingText
|
|
259
|
+
pendingText = null
|
|
260
|
+
|
|
261
|
+
if (textToSend === lastSentText) {
|
|
262
|
+
// Nothing actually changed — skip the API call but free waiters
|
|
263
|
+
notifyWaiters()
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (textToSend.length > maxChars) {
|
|
268
|
+
log?.(`stream stopped: text exceeds ${maxChars} chars`)
|
|
269
|
+
stopped = true
|
|
270
|
+
notifyWaiters()
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
if (usesDraftTransport) {
|
|
276
|
+
const ok = await sendViaDraft(textToSend)
|
|
277
|
+
if (!ok) {
|
|
278
|
+
// Draft failed with a permanent error → fell back to message transport.
|
|
279
|
+
// Replay this text via message transport.
|
|
280
|
+
await sendViaMessage(textToSend)
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
await sendViaMessage(textToSend)
|
|
284
|
+
}
|
|
285
|
+
lastSentText = textToSend
|
|
286
|
+
lastSentAt = Date.now()
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const msg = (err as Error).message ?? String(err)
|
|
289
|
+
if (/\bmessage is not modified\b/i.test(msg)) {
|
|
290
|
+
lastSentText = textToSend
|
|
291
|
+
lastSentAt = Date.now()
|
|
292
|
+
log?.(`stream → not modified (id: ${messageId})`)
|
|
293
|
+
} else if (
|
|
294
|
+
/\bmessage to edit not found\b/i.test(msg)
|
|
295
|
+
|| /\bMESSAGE_ID_INVALID\b/i.test(msg)
|
|
296
|
+
) {
|
|
297
|
+
log?.(`stream → message not found (id: ${messageId}), re-sending`)
|
|
298
|
+
messageId = null
|
|
299
|
+
lastSentText = null
|
|
300
|
+
if (pendingText == null) pendingText = textToSend
|
|
301
|
+
} else {
|
|
302
|
+
log?.(`stream → edit failed: ${msg}`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
notifyWaiters()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function sendViaMessage(textToSend: string): Promise<void> {
|
|
310
|
+
if (messageId == null) {
|
|
311
|
+
messageId = await send(textToSend)
|
|
312
|
+
log?.(`stream → sent (id: ${messageId}, ${textToSend.length} chars)`)
|
|
313
|
+
} else {
|
|
314
|
+
await edit(messageId, textToSend)
|
|
315
|
+
log?.(`stream → edited (id: ${messageId}, ${textToSend.length} chars)`)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function flushLoop(): Promise<void> {
|
|
320
|
+
// Drain any updates that arrived during the in-flight call.
|
|
321
|
+
while (pendingText != null && !stopped) {
|
|
322
|
+
await flush()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function schedule(): void {
|
|
327
|
+
if (scheduledTimer != null) return
|
|
328
|
+
if (stopped) return
|
|
329
|
+
const sinceLast = Date.now() - lastSentAt
|
|
330
|
+
const delay = Math.max(0, throttleMs - sinceLast)
|
|
331
|
+
scheduledTimer = setTimeout(() => {
|
|
332
|
+
scheduledTimer = null
|
|
333
|
+
if (inFlight) {
|
|
334
|
+
// The in-flight loop will pick up pendingText after it resolves.
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
inFlight = flushLoop().finally(() => {
|
|
338
|
+
inFlight = null
|
|
339
|
+
})
|
|
340
|
+
}, delay)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
update(text: string): Promise<void> {
|
|
345
|
+
if (final || stopped) return Promise.resolve()
|
|
346
|
+
pendingText = text
|
|
347
|
+
const waitPromise = new Promise<void>(resolve => {
|
|
348
|
+
waiters.push(resolve)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
// Pre-send idle debounce: for the FIRST send of a stream, optionally
|
|
352
|
+
// defer by idleMs so a burst of update() calls collapses into one
|
|
353
|
+
// send. Each incoming update resets the timer. Once the initial
|
|
354
|
+
// send has landed (messageId != null OR draft has fired), this path
|
|
355
|
+
// is skipped and the regular throttle kicks in.
|
|
356
|
+
if (idleMs > 0 && messageId == null && !usesDraftTransport && inFlight == null) {
|
|
357
|
+
if (scheduledTimer != null) clearTimeout(scheduledTimer)
|
|
358
|
+
scheduledTimer = setTimeout(() => {
|
|
359
|
+
scheduledTimer = null
|
|
360
|
+
inFlight = flushLoop().finally(() => { inFlight = null })
|
|
361
|
+
}, idleMs)
|
|
362
|
+
return waitPromise
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// If nothing in flight and the throttle window is open, fire now.
|
|
366
|
+
if (inFlight == null && Date.now() - lastSentAt >= throttleMs) {
|
|
367
|
+
inFlight = flushLoop().finally(() => {
|
|
368
|
+
inFlight = null
|
|
369
|
+
})
|
|
370
|
+
} else if (inFlight == null) {
|
|
371
|
+
schedule()
|
|
372
|
+
} else {
|
|
373
|
+
// inFlight is set — the current flushLoop is running. Previous
|
|
374
|
+
// versions of this code relied on flushLoop's while(pendingText
|
|
375
|
+
// != null) to pick up the new text, but there's a race: if
|
|
376
|
+
// update() fires AFTER the while's final (null) check but
|
|
377
|
+
// BEFORE the flushLoop promise settles, the new pendingText
|
|
378
|
+
// lands in a shell with no one looking at it, and the waiter
|
|
379
|
+
// hangs forever. Chain a follow-up flush off the current
|
|
380
|
+
// flushLoop so the new text is guaranteed to be drained.
|
|
381
|
+
inFlight.then(() => {
|
|
382
|
+
if (stopped || pendingText == null) return
|
|
383
|
+
if (inFlight != null) return // a new flushLoop already started
|
|
384
|
+
if (Date.now() - lastSentAt >= throttleMs) {
|
|
385
|
+
inFlight = flushLoop().finally(() => { inFlight = null })
|
|
386
|
+
} else {
|
|
387
|
+
schedule()
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
return waitPromise
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
async finalize(): Promise<void> {
|
|
395
|
+
if (final) return
|
|
396
|
+
final = true
|
|
397
|
+
// Drain any pending updates
|
|
398
|
+
if (scheduledTimer != null) {
|
|
399
|
+
clearTimeout(scheduledTimer)
|
|
400
|
+
scheduledTimer = null
|
|
401
|
+
}
|
|
402
|
+
if (inFlight) {
|
|
403
|
+
await inFlight
|
|
404
|
+
}
|
|
405
|
+
if (pendingText != null && !stopped) {
|
|
406
|
+
await flush()
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Draft transport: materialize as a real sendMessage for push notification,
|
|
410
|
+
// then clear the draft best-effort.
|
|
411
|
+
if (usesDraftTransport && draftApi != null) {
|
|
412
|
+
const textToMaterialize = lastSentText
|
|
413
|
+
if (textToMaterialize) {
|
|
414
|
+
try {
|
|
415
|
+
messageId = await send(textToMaterialize)
|
|
416
|
+
log?.(`stream → materialized (id: ${messageId}, ${textToMaterialize.length} chars)`)
|
|
417
|
+
} catch (err) {
|
|
418
|
+
warn?.(`draft-stream: materialize sendMessage failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
419
|
+
}
|
|
420
|
+
// Clear draft best-effort (cosmetic — Telegram input area cleanup)
|
|
421
|
+
if (draftId != null) {
|
|
422
|
+
try {
|
|
423
|
+
await draftApi(chatId, draftId, '')
|
|
424
|
+
} catch {
|
|
425
|
+
// Best-effort — ignore failures
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
log?.(`stream finalized (id: ${messageId})`)
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
getMessageId(): number | null {
|
|
435
|
+
return messageId
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
isFinal(): boolean {
|
|
439
|
+
return final
|
|
440
|
+
},
|
|
441
|
+
}
|
|
442
|
+
}
|