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,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for the clean-shutdown marker that suppresses the
|
|
3
|
+
* "⚡ Recovered from unexpected restart" banner on planned shutdowns.
|
|
4
|
+
*
|
|
5
|
+
* Background — 2026-04-23 UX gap:
|
|
6
|
+
* The boot path posts a recovery banner whenever it doesn't find a
|
|
7
|
+
* /restart user-marker. That assumption was fine for crashes but
|
|
8
|
+
* wrong for deliberate restarts (`systemctl --user restart …`,
|
|
9
|
+
* `switchroom agent restart`, Coolify redeploys, anything that sends
|
|
10
|
+
* SIGTERM cleanly). PR #52's drain coordinator made these planned
|
|
11
|
+
* shutdowns common enough that every redeploy looked unexpected.
|
|
12
|
+
*
|
|
13
|
+
* Fix: SIGTERM/SIGINT writes a separate sentinel file BEFORE draining;
|
|
14
|
+
* boot reads it and suppresses the banner if fresh.
|
|
15
|
+
*
|
|
16
|
+
* These tests pin the contract:
|
|
17
|
+
* - read/write/clear roundtrip works
|
|
18
|
+
* - malformed JSON returns null instead of crashing boot
|
|
19
|
+
* - the boot decision (shouldSuppressRecoveryBanner) is correct for
|
|
20
|
+
* present-fresh / present-stale / absent / clock-skewed inputs
|
|
21
|
+
*
|
|
22
|
+
* Integration with the gateway boot path is structurally tested via
|
|
23
|
+
* the same pattern as gateway-startup-mutex.test.ts: extract the pure
|
|
24
|
+
* helper, unit-test it, trust the wiring via live verification.
|
|
25
|
+
*
|
|
26
|
+
* Run with:
|
|
27
|
+
* bun test telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts
|
|
28
|
+
*/
|
|
29
|
+
import { describe, it, expect, afterEach } from "bun:test";
|
|
30
|
+
import { mkdtempSync, writeFileSync, existsSync, rmSync, readFileSync } from "fs";
|
|
31
|
+
import { join } from "path";
|
|
32
|
+
import { tmpdir } from "os";
|
|
33
|
+
import {
|
|
34
|
+
writeCleanShutdownMarker,
|
|
35
|
+
readCleanShutdownMarker,
|
|
36
|
+
clearCleanShutdownMarker,
|
|
37
|
+
shouldSuppressRecoveryBanner,
|
|
38
|
+
resolveShutdownMarker,
|
|
39
|
+
DEFAULT_MAX_AGE_MS,
|
|
40
|
+
EXTERNAL_RESTART_FALLBACK_REASON,
|
|
41
|
+
REASON_PRESERVE_MAX_AGE_MS,
|
|
42
|
+
type CleanShutdownMarker,
|
|
43
|
+
} from "../gateway/clean-shutdown-marker.js";
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
function tmpMarkerPath(): { dir: string; path: string } {
|
|
50
|
+
const dir = mkdtempSync(join(tmpdir(), "gw-clean-shutdown-test-"));
|
|
51
|
+
return { dir, path: join(dir, "clean-shutdown.json") };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const cleanups: string[] = [];
|
|
55
|
+
function track(dir: string): void { cleanups.push(dir); }
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
while (cleanups.length > 0) {
|
|
59
|
+
const dir = cleanups.pop();
|
|
60
|
+
if (dir) {
|
|
61
|
+
try { rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// File I/O round-trip
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
describe("clean-shutdown-marker file I/O", () => {
|
|
71
|
+
it("writes a marker with ts + signal that read returns verbatim", () => {
|
|
72
|
+
const { dir, path } = tmpMarkerPath();
|
|
73
|
+
track(dir);
|
|
74
|
+
|
|
75
|
+
const marker: CleanShutdownMarker = { ts: 1_700_000_000_000, signal: "SIGTERM" };
|
|
76
|
+
writeCleanShutdownMarker(path, marker);
|
|
77
|
+
|
|
78
|
+
expect(existsSync(path)).toBe(true);
|
|
79
|
+
const got = readCleanShutdownMarker(path);
|
|
80
|
+
expect(got).not.toBeNull();
|
|
81
|
+
expect(got?.ts).toBe(marker.ts);
|
|
82
|
+
expect(got?.signal).toBe("SIGTERM");
|
|
83
|
+
// Default schema: no reason field.
|
|
84
|
+
expect(got?.reason).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("preserves the optional reason field", () => {
|
|
88
|
+
const { dir, path } = tmpMarkerPath();
|
|
89
|
+
track(dir);
|
|
90
|
+
|
|
91
|
+
writeCleanShutdownMarker(path, { ts: Date.now(), signal: "SIGINT", reason: "deploy" });
|
|
92
|
+
const got = readCleanShutdownMarker(path);
|
|
93
|
+
expect(got?.reason).toBe("deploy");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("clearCleanShutdownMarker deletes the file", () => {
|
|
97
|
+
const { dir, path } = tmpMarkerPath();
|
|
98
|
+
track(dir);
|
|
99
|
+
|
|
100
|
+
writeCleanShutdownMarker(path, { ts: Date.now(), signal: "SIGTERM" });
|
|
101
|
+
expect(existsSync(path)).toBe(true);
|
|
102
|
+
|
|
103
|
+
clearCleanShutdownMarker(path);
|
|
104
|
+
expect(existsSync(path)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("clearCleanShutdownMarker is a no-op when the file is already absent", () => {
|
|
108
|
+
const { dir, path } = tmpMarkerPath();
|
|
109
|
+
track(dir);
|
|
110
|
+
|
|
111
|
+
// File never existed.
|
|
112
|
+
expect(existsSync(path)).toBe(false);
|
|
113
|
+
expect(() => clearCleanShutdownMarker(path)).not.toThrow();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("readCleanShutdownMarker returns null for a missing file (no crash)", () => {
|
|
117
|
+
const { dir, path } = tmpMarkerPath();
|
|
118
|
+
track(dir);
|
|
119
|
+
|
|
120
|
+
expect(readCleanShutdownMarker(path)).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("readCleanShutdownMarker returns null for malformed JSON (no crash on boot)", () => {
|
|
124
|
+
const { dir, path } = tmpMarkerPath();
|
|
125
|
+
track(dir);
|
|
126
|
+
|
|
127
|
+
writeFileSync(path, "{ this is not json", "utf-8");
|
|
128
|
+
expect(readCleanShutdownMarker(path)).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("readCleanShutdownMarker returns null when ts is missing or non-numeric", () => {
|
|
132
|
+
const { dir, path } = tmpMarkerPath();
|
|
133
|
+
track(dir);
|
|
134
|
+
|
|
135
|
+
writeFileSync(path, JSON.stringify({ signal: "SIGTERM" }), "utf-8");
|
|
136
|
+
expect(readCleanShutdownMarker(path)).toBeNull();
|
|
137
|
+
|
|
138
|
+
writeFileSync(path, JSON.stringify({ ts: "nope", signal: "SIGTERM" }), "utf-8");
|
|
139
|
+
expect(readCleanShutdownMarker(path)).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("readCleanShutdownMarker returns null when signal is missing or empty", () => {
|
|
143
|
+
const { dir, path } = tmpMarkerPath();
|
|
144
|
+
track(dir);
|
|
145
|
+
|
|
146
|
+
writeFileSync(path, JSON.stringify({ ts: Date.now() }), "utf-8");
|
|
147
|
+
expect(readCleanShutdownMarker(path)).toBeNull();
|
|
148
|
+
|
|
149
|
+
writeFileSync(path, JSON.stringify({ ts: Date.now(), signal: "" }), "utf-8");
|
|
150
|
+
expect(readCleanShutdownMarker(path)).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("write is atomic: tmp file does not survive a successful write", () => {
|
|
154
|
+
const { dir, path } = tmpMarkerPath();
|
|
155
|
+
track(dir);
|
|
156
|
+
|
|
157
|
+
writeCleanShutdownMarker(path, { ts: Date.now(), signal: "SIGTERM" });
|
|
158
|
+
// After a successful write, only the final path should exist — no
|
|
159
|
+
// straggling .tmp-*. (Glob-ish check via reading the dir.)
|
|
160
|
+
const fs = require("node:fs") as typeof import("node:fs");
|
|
161
|
+
const entries = fs.readdirSync(dir);
|
|
162
|
+
const tmps = entries.filter((e) => e.includes(".tmp-"));
|
|
163
|
+
expect(tmps.length).toBe(0);
|
|
164
|
+
expect(entries).toContain("clean-shutdown.json");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Boot decision: shouldSuppressRecoveryBanner
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
describe("shouldSuppressRecoveryBanner", () => {
|
|
173
|
+
it("returns false when no marker is present (true crash or first boot)", () => {
|
|
174
|
+
expect(shouldSuppressRecoveryBanner(null, Date.now())).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("returns true for a fresh marker (age < default 60s)", () => {
|
|
178
|
+
const now = 1_700_000_000_000;
|
|
179
|
+
const marker: CleanShutdownMarker = { ts: now - 5_000, signal: "SIGTERM" };
|
|
180
|
+
expect(shouldSuppressRecoveryBanner(marker, now)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("returns true at the very edge of the window (age = 0)", () => {
|
|
184
|
+
const now = 1_700_000_000_000;
|
|
185
|
+
const marker: CleanShutdownMarker = { ts: now, signal: "SIGTERM" };
|
|
186
|
+
expect(shouldSuppressRecoveryBanner(marker, now)).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("returns false when marker age equals maxAgeMs (boundary)", () => {
|
|
190
|
+
const now = 1_700_000_000_000;
|
|
191
|
+
const marker: CleanShutdownMarker = { ts: now - DEFAULT_MAX_AGE_MS, signal: "SIGTERM" };
|
|
192
|
+
expect(shouldSuppressRecoveryBanner(marker, now)).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("returns false for a stale marker (age > default 60s)", () => {
|
|
196
|
+
const now = 1_700_000_000_000;
|
|
197
|
+
const marker: CleanShutdownMarker = { ts: now - 90_000, signal: "SIGTERM" };
|
|
198
|
+
expect(shouldSuppressRecoveryBanner(marker, now)).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("respects a custom maxAgeMs", () => {
|
|
202
|
+
const now = 1_700_000_000_000;
|
|
203
|
+
const marker: CleanShutdownMarker = { ts: now - 30_000, signal: "SIGTERM" };
|
|
204
|
+
// Fresh under default (60s) but stale under a 10s window.
|
|
205
|
+
expect(shouldSuppressRecoveryBanner(marker, now, 60_000)).toBe(true);
|
|
206
|
+
expect(shouldSuppressRecoveryBanner(marker, now, 10_000)).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("treats clock skew (negative age) as stale to avoid false suppression", () => {
|
|
210
|
+
// If the marker says the future, something is wrong with one of the
|
|
211
|
+
// clocks — don't suppress the banner. Operator probably wants to
|
|
212
|
+
// know about it.
|
|
213
|
+
const now = 1_700_000_000_000;
|
|
214
|
+
const marker: CleanShutdownMarker = { ts: now + 60_000, signal: "SIGTERM" };
|
|
215
|
+
expect(shouldSuppressRecoveryBanner(marker, now)).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("works for both SIGTERM and SIGINT signals (signal value is opaque)", () => {
|
|
219
|
+
const now = 1_700_000_000_000;
|
|
220
|
+
expect(shouldSuppressRecoveryBanner({ ts: now, signal: "SIGTERM" }, now)).toBe(true);
|
|
221
|
+
expect(shouldSuppressRecoveryBanner({ ts: now, signal: "SIGINT" }, now)).toBe(true);
|
|
222
|
+
expect(shouldSuppressRecoveryBanner({ ts: now, signal: "SIGUSR1" }, now)).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("resolveShutdownMarker (SIGTERM-handler sequencing)", () => {
|
|
227
|
+
// These tests pin the fix for the 2026-04-24 bug where bare
|
|
228
|
+
// `systemctl restart switchroom-<name>-gateway` produced a reasonless
|
|
229
|
+
// marker AND the shutdown handler clobbered any reason stamped by a
|
|
230
|
+
// preceding CLI/watchdog/user-slash initiator. The resolver preserves
|
|
231
|
+
// fresh reasons and falls back to "systemctl: external restart" so
|
|
232
|
+
// the next greeting card always has a non-empty Restarted row for
|
|
233
|
+
// planned shutdowns.
|
|
234
|
+
|
|
235
|
+
it("preserves a fresh prior reason (cooperative race with a CLI/user stamp)", () => {
|
|
236
|
+
const now = 1_700_000_000_000;
|
|
237
|
+
const prior: CleanShutdownMarker = {
|
|
238
|
+
ts: now - 500, // 500ms old — CLI just stamped
|
|
239
|
+
signal: "SIGTERM",
|
|
240
|
+
reason: "cli: deploying abc1234",
|
|
241
|
+
};
|
|
242
|
+
const next = resolveShutdownMarker(prior, "SIGTERM", now);
|
|
243
|
+
expect(next.reason).toBe("cli: deploying abc1234");
|
|
244
|
+
expect(next.signal).toBe("SIGTERM");
|
|
245
|
+
expect(next.ts).toBe(now);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("preserves user-slash attribution when the gateway stamps before spawning the CLI", () => {
|
|
249
|
+
// /restart, /reconcile, /new, /reset paths in gateway.ts call
|
|
250
|
+
// stampUserRestartReason() which writes the marker, then spawn the
|
|
251
|
+
// detached CLI which stamps its own. By the time SIGTERM arrives,
|
|
252
|
+
// the most recent marker should already carry "user: /restart from
|
|
253
|
+
// chat" (preserved by preserveExisting in writeRestartReasonMarker).
|
|
254
|
+
// The SIGTERM handler must then preserve it a second time.
|
|
255
|
+
const now = 1_700_000_000_000;
|
|
256
|
+
const prior: CleanShutdownMarker = {
|
|
257
|
+
ts: now - 2_000,
|
|
258
|
+
signal: "SIGTERM",
|
|
259
|
+
reason: "user: /restart from chat",
|
|
260
|
+
};
|
|
261
|
+
const next = resolveShutdownMarker(prior, "SIGTERM", now);
|
|
262
|
+
expect(next.reason).toBe("user: /restart from chat");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("falls back to 'systemctl: external restart' when no prior marker exists", () => {
|
|
266
|
+
// Bare `systemctl restart switchroom-<name>-gateway` from an admin
|
|
267
|
+
// terminal — no CLI, no watchdog, no slash command stamped anything.
|
|
268
|
+
// Before this fix the marker was `{ts, signal}` with no reason and
|
|
269
|
+
// the greeting silently omitted the Restarted row. This is the
|
|
270
|
+
// 04:57 2026-04-24 bug Ken hit when applying an EnvironmentFile
|
|
271
|
+
// change.
|
|
272
|
+
const now = 1_700_000_000_000;
|
|
273
|
+
const next = resolveShutdownMarker(null, "SIGTERM", now);
|
|
274
|
+
expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
|
|
275
|
+
expect(next.signal).toBe("SIGTERM");
|
|
276
|
+
expect(next.ts).toBe(now);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("falls back when a prior marker is missing its reason field", () => {
|
|
280
|
+
// A reasonless prior marker means an earlier SIGTERM-only path
|
|
281
|
+
// wrote it (pre-fix gateway) or an initiator stamped ts+signal
|
|
282
|
+
// without a reason. Either way, we have no attribution to preserve
|
|
283
|
+
// — use the fallback so the greeting is never silent.
|
|
284
|
+
const now = 1_700_000_000_000;
|
|
285
|
+
const prior: CleanShutdownMarker = { ts: now - 1_000, signal: "SIGTERM" };
|
|
286
|
+
const next = resolveShutdownMarker(prior, "SIGTERM", now);
|
|
287
|
+
expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("falls back when the prior reason is stale (>30s)", () => {
|
|
291
|
+
// A stale prior reason almost certainly belongs to an earlier
|
|
292
|
+
// shutdown cycle, not the current one. Using it would mis-attribute
|
|
293
|
+
// today's restart to yesterday's initiator. The 30s window matches
|
|
294
|
+
// writeRestartReasonMarker's preserveExisting guard in
|
|
295
|
+
// src/agents/lifecycle.ts.
|
|
296
|
+
const now = 1_700_000_000_000;
|
|
297
|
+
const prior: CleanShutdownMarker = {
|
|
298
|
+
ts: now - 60_000,
|
|
299
|
+
signal: "SIGTERM",
|
|
300
|
+
reason: "cli: deploying 0badc0de",
|
|
301
|
+
};
|
|
302
|
+
const next = resolveShutdownMarker(prior, "SIGTERM", now);
|
|
303
|
+
expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("falls back when the prior reason is in the future (clock skew)", () => {
|
|
307
|
+
// A negative age indicates clock skew; don't trust it.
|
|
308
|
+
const now = 1_700_000_000_000;
|
|
309
|
+
const prior: CleanShutdownMarker = {
|
|
310
|
+
ts: now + 5_000,
|
|
311
|
+
signal: "SIGTERM",
|
|
312
|
+
reason: "cli: restart",
|
|
313
|
+
};
|
|
314
|
+
const next = resolveShutdownMarker(prior, "SIGTERM", now);
|
|
315
|
+
expect(next.reason).toBe(EXTERNAL_RESTART_FALLBACK_REASON);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("respects a custom maxPreserveAgeMs", () => {
|
|
319
|
+
const now = 1_700_000_000_000;
|
|
320
|
+
const prior: CleanShutdownMarker = {
|
|
321
|
+
ts: now - 10_000,
|
|
322
|
+
signal: "SIGTERM",
|
|
323
|
+
reason: "watchdog: bridge disconnected for 145s",
|
|
324
|
+
};
|
|
325
|
+
// Fresh under the default 30s guard, stale under a 5s guard.
|
|
326
|
+
expect(resolveShutdownMarker(prior, "SIGTERM", now).reason).toBe(
|
|
327
|
+
"watchdog: bridge disconnected for 145s",
|
|
328
|
+
);
|
|
329
|
+
expect(resolveShutdownMarker(prior, "SIGTERM", now, 5_000).reason).toBe(
|
|
330
|
+
EXTERNAL_RESTART_FALLBACK_REASON,
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("copies the incoming signal onto the resolved marker (SIGTERM vs SIGINT)", () => {
|
|
335
|
+
const now = 1_700_000_000_000;
|
|
336
|
+
expect(resolveShutdownMarker(null, "SIGTERM", now).signal).toBe("SIGTERM");
|
|
337
|
+
expect(resolveShutdownMarker(null, "SIGINT", now).signal).toBe("SIGINT");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("REASON_PRESERVE_MAX_AGE_MS matches the lifecycle.ts cooperative window", () => {
|
|
341
|
+
// Load-bearing constant — if the two windows drift out of sync, the
|
|
342
|
+
// CLI-stamps-then-SIGTERM race gets harder to reason about. Pin it.
|
|
343
|
+
expect(REASON_PRESERVE_MAX_AGE_MS).toBe(30_000);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe("gateway.ts shutdown-handler wiring (source-level)", () => {
|
|
348
|
+
// Source-grep pins so a future refactor can't silently drop the
|
|
349
|
+
// reason-preserving + fallback-writing behaviour the 2026-04-24 fix
|
|
350
|
+
// added to the SIGTERM handler.
|
|
351
|
+
const gatewaySource = readFileSync(
|
|
352
|
+
join(import.meta.dir, "..", "gateway", "gateway.ts"),
|
|
353
|
+
"utf8",
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
it("reads any prior marker before writing the new one", () => {
|
|
357
|
+
// The handler MUST readCleanShutdownMarker first — writing without
|
|
358
|
+
// the readback clobbers reasons stamped by CLI/watchdog/user-slash
|
|
359
|
+
// initiators that ran microseconds before SIGTERM.
|
|
360
|
+
expect(gatewaySource).toMatch(
|
|
361
|
+
/const prior = readCleanShutdownMarker\(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH\)/,
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("routes the marker computation through resolveShutdownMarker", () => {
|
|
366
|
+
expect(gatewaySource).toMatch(
|
|
367
|
+
/const next = resolveShutdownMarker\(prior, signal, Date\.now\(\)\)/,
|
|
368
|
+
);
|
|
369
|
+
expect(gatewaySource).toMatch(
|
|
370
|
+
/writeCleanShutdownMarker\(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH, next\)/,
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("logs the resolved reason + preserved flag so journals are diagnosable", () => {
|
|
375
|
+
// The journal line is what operators grep when "why did the greeting
|
|
376
|
+
// say X?" questions come up. Keep it present.
|
|
377
|
+
expect(gatewaySource).toContain("shutdown.clean_marker_written");
|
|
378
|
+
expect(gatewaySource).toContain("reason=${JSON.stringify(next.reason ?? '')}");
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("gateway.ts crash-path guard (source-level)", () => {
|
|
383
|
+
// Regression for the reviewer-found bug in PR #55: shutdown() is called
|
|
384
|
+
// for OS signals AND for uncaughtException/unhandledRejection (PR #53 nit
|
|
385
|
+
// fix). The marker write must be GATED on signal value — otherwise a
|
|
386
|
+
// crash writes the marker and silently suppresses its own recovery
|
|
387
|
+
// banner at the next boot, defeating the entire feature.
|
|
388
|
+
//
|
|
389
|
+
// Pinned via source-level grep because the gateway.ts shutdown() function
|
|
390
|
+
// can't be unit-tested in isolation without mocking the entire bot/IPC
|
|
391
|
+
// surface. If anyone removes the guard, this test fails.
|
|
392
|
+
const gatewaySource = readFileSync(
|
|
393
|
+
join(import.meta.dir, "..", "gateway", "gateway.ts"),
|
|
394
|
+
"utf8",
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
it("guards writeCleanShutdownMarker on isOsSignal", () => {
|
|
398
|
+
expect(gatewaySource).toContain(
|
|
399
|
+
`const isOsSignal = signal === 'SIGTERM' || signal === 'SIGINT'`,
|
|
400
|
+
);
|
|
401
|
+
expect(gatewaySource).toContain(`if (isOsSignal) {`);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("logs the skip path so crashes leave a journal trail", () => {
|
|
405
|
+
expect(gatewaySource).toContain(`shutdown.clean_marker_skipped`);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("crash-handler signal labels stay distinguishable from OS signals", () => {
|
|
409
|
+
// shutdown() is called with these literal signal strings from the
|
|
410
|
+
// crash handlers. Each must NOT match isOsSignal above.
|
|
411
|
+
expect(gatewaySource).toMatch(/shutdown\(['"]uncaughtException['"]/);
|
|
412
|
+
expect(gatewaySource).toMatch(/shutdown\(['"]unhandledRejection['"]/);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test for `flushOnAgentDisconnect` — the gating policy applied
|
|
3
|
+
* when a client disconnects from the IPC server.
|
|
4
|
+
*
|
|
5
|
+
* Bug context (#553 hotfix): the gateway's `onClientDisconnected` used to
|
|
6
|
+
* unconditionally flush every active StatusReactionController to 👍 and
|
|
7
|
+
* dispose the progress driver. This fired on EVERY disconnect, including
|
|
8
|
+
* anonymous one-shot connections from `vendor/hindsight-memory`'s
|
|
9
|
+
* `recall.py` hook. The user-visible symptom: 👀 reaction (received) was
|
|
10
|
+
* immediately followed by 👍 (done) while the agent was still producing
|
|
11
|
+
* its reply, plus a duplicate edited-message bug from the re-created
|
|
12
|
+
* progress driver.
|
|
13
|
+
*
|
|
14
|
+
* The fix scopes the flush to clients whose `agentName` is non-null —
|
|
15
|
+
* i.e. clients that actually completed a `register` IPC handshake.
|
|
16
|
+
* Anonymous clients (recall.py and similar) are silently no-op.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
20
|
+
import { flushOnAgentDisconnect } from '../gateway/disconnect-flush.js'
|
|
21
|
+
|
|
22
|
+
interface FakeCtrl {
|
|
23
|
+
setDone: () => void
|
|
24
|
+
}
|
|
25
|
+
interface FakeStream {
|
|
26
|
+
isFinal: () => boolean
|
|
27
|
+
finalize: () => Promise<void>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeDeps(agentName: string | null) {
|
|
31
|
+
const setDoneA = vi.fn()
|
|
32
|
+
const setDoneB = vi.fn()
|
|
33
|
+
const finalizeA = vi.fn(async () => {})
|
|
34
|
+
const finalizeB = vi.fn(async () => {})
|
|
35
|
+
const clearActiveReactions = vi.fn()
|
|
36
|
+
const disposeProgressDriver = vi.fn()
|
|
37
|
+
const log = vi.fn()
|
|
38
|
+
|
|
39
|
+
const activeStatusReactions = new Map<string, FakeCtrl>([
|
|
40
|
+
['chat1:thr1:msg1', { setDone: setDoneA }],
|
|
41
|
+
['chat2:thr2:msg2', { setDone: setDoneB }],
|
|
42
|
+
])
|
|
43
|
+
const activeReactionMsgIds = new Map<string, { chatId: string; messageId: number }>([
|
|
44
|
+
['chat1:thr1:msg1', { chatId: 'chat1', messageId: 1 }],
|
|
45
|
+
['chat2:thr2:msg2', { chatId: 'chat2', messageId: 2 }],
|
|
46
|
+
])
|
|
47
|
+
const activeTurnStartedAt = new Map<string, number>([
|
|
48
|
+
['chat1:thr1:msg1', 100],
|
|
49
|
+
['chat2:thr2:msg2', 200],
|
|
50
|
+
])
|
|
51
|
+
const activeDraftStreams = new Map<string, FakeStream>([
|
|
52
|
+
['chat1:thr1:r1', { isFinal: () => false, finalize: finalizeA }],
|
|
53
|
+
['chat2:thr2:r2', { isFinal: () => true, finalize: finalizeB }],
|
|
54
|
+
])
|
|
55
|
+
const activeDraftParseModes = new Map<string, 'HTML' | 'MarkdownV2' | undefined>([
|
|
56
|
+
['chat1:thr1:r1', 'HTML'],
|
|
57
|
+
['chat2:thr2:r2', undefined],
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
spies: { setDoneA, setDoneB, finalizeA, finalizeB, clearActiveReactions, disposeProgressDriver, log },
|
|
62
|
+
deps: {
|
|
63
|
+
agentName,
|
|
64
|
+
activeStatusReactions,
|
|
65
|
+
activeReactionMsgIds,
|
|
66
|
+
activeTurnStartedAt,
|
|
67
|
+
activeDraftStreams,
|
|
68
|
+
activeDraftParseModes,
|
|
69
|
+
clearActiveReactions,
|
|
70
|
+
disposeProgressDriver,
|
|
71
|
+
log,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('flushOnAgentDisconnect — anonymous clients (the regression scenario)', () => {
|
|
77
|
+
it('returns false and does NOT touch any state when agentName is null', () => {
|
|
78
|
+
const { spies, deps } = makeDeps(null)
|
|
79
|
+
|
|
80
|
+
const ran = flushOnAgentDisconnect(deps)
|
|
81
|
+
|
|
82
|
+
expect(ran).toBe(false)
|
|
83
|
+
expect(spies.setDoneA).not.toHaveBeenCalled()
|
|
84
|
+
expect(spies.setDoneB).not.toHaveBeenCalled()
|
|
85
|
+
expect(spies.finalizeA).not.toHaveBeenCalled()
|
|
86
|
+
expect(spies.finalizeB).not.toHaveBeenCalled()
|
|
87
|
+
expect(spies.clearActiveReactions).not.toHaveBeenCalled()
|
|
88
|
+
expect(spies.disposeProgressDriver).not.toHaveBeenCalled()
|
|
89
|
+
|
|
90
|
+
// All maps untouched — sizes preserved.
|
|
91
|
+
expect(deps.activeStatusReactions.size).toBe(2)
|
|
92
|
+
expect(deps.activeReactionMsgIds.size).toBe(2)
|
|
93
|
+
expect(deps.activeTurnStartedAt.size).toBe(2)
|
|
94
|
+
expect(deps.activeDraftStreams.size).toBe(2)
|
|
95
|
+
expect(deps.activeDraftParseModes.size).toBe(2)
|
|
96
|
+
|
|
97
|
+
// But it should log so the operator can correlate the no-op decision.
|
|
98
|
+
expect(spies.log).toHaveBeenCalledTimes(1)
|
|
99
|
+
expect(spies.log.mock.calls[0][0]).toMatch(/anonymous client disconnect/i)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('explicitly does not fire setDone on any controller for anonymous disconnects', () => {
|
|
103
|
+
// Tighter assertion of the user-visible bug: 👍 must NOT fire while the
|
|
104
|
+
// agent is producing its reply just because recall.py opened a socket.
|
|
105
|
+
const { spies, deps } = makeDeps(null)
|
|
106
|
+
flushOnAgentDisconnect(deps)
|
|
107
|
+
expect(spies.setDoneA).toHaveBeenCalledTimes(0)
|
|
108
|
+
expect(spies.setDoneB).toHaveBeenCalledTimes(0)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('flushOnAgentDisconnect — registered agent disconnects (existing behavior preserved)', () => {
|
|
113
|
+
it('returns true and flushes every status reaction for a real agent disconnect', () => {
|
|
114
|
+
const { spies, deps } = makeDeps('clerk')
|
|
115
|
+
|
|
116
|
+
const ran = flushOnAgentDisconnect(deps)
|
|
117
|
+
|
|
118
|
+
expect(ran).toBe(true)
|
|
119
|
+
expect(spies.setDoneA).toHaveBeenCalledTimes(1)
|
|
120
|
+
expect(spies.setDoneB).toHaveBeenCalledTimes(1)
|
|
121
|
+
expect(deps.activeStatusReactions.size).toBe(0)
|
|
122
|
+
expect(deps.activeReactionMsgIds.size).toBe(0)
|
|
123
|
+
expect(deps.activeTurnStartedAt.size).toBe(0)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('disposes the progress driver and clears the on-disk reaction registry', () => {
|
|
127
|
+
const { spies, deps } = makeDeps('clerk')
|
|
128
|
+
flushOnAgentDisconnect(deps)
|
|
129
|
+
expect(spies.disposeProgressDriver).toHaveBeenCalledTimes(1)
|
|
130
|
+
expect(spies.clearActiveReactions).toHaveBeenCalledTimes(1)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('finalizes only non-final draft streams and clears the maps', () => {
|
|
134
|
+
const { spies, deps } = makeDeps('clerk')
|
|
135
|
+
flushOnAgentDisconnect(deps)
|
|
136
|
+
// Stream A was non-final → finalize called.
|
|
137
|
+
expect(spies.finalizeA).toHaveBeenCalledTimes(1)
|
|
138
|
+
// Stream B was already final → finalize skipped.
|
|
139
|
+
expect(spies.finalizeB).not.toHaveBeenCalled()
|
|
140
|
+
// Both stream maps cleared regardless.
|
|
141
|
+
expect(deps.activeDraftStreams.size).toBe(0)
|
|
142
|
+
expect(deps.activeDraftParseModes.size).toBe(0)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests the bridge's gateway→client validator.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of ipc-validator.test.ts on the bridge side. The gateway's
|
|
5
|
+
* validator is strictly structural (no deep schema check); these tests
|
|
6
|
+
* pin down the exact shape the bridge will accept, so if the gateway
|
|
7
|
+
* ever drifts (e.g. renames `chatId` to `chat_id`) the bridge's parse
|
|
8
|
+
* fail mode is loud rather than a silent handler no-op.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from 'vitest'
|
|
12
|
+
import { validateGatewayMessage } from '../bridge/ipc-client.js'
|
|
13
|
+
import { allGatewayFixtures } from './protocol-fixtures.js'
|
|
14
|
+
|
|
15
|
+
describe('validateGatewayMessage', () => {
|
|
16
|
+
describe('generic rejection', () => {
|
|
17
|
+
it('rejects non-objects', () => {
|
|
18
|
+
expect(validateGatewayMessage(null)).toBe(false)
|
|
19
|
+
expect(validateGatewayMessage(undefined)).toBe(false)
|
|
20
|
+
expect(validateGatewayMessage(42)).toBe(false)
|
|
21
|
+
expect(validateGatewayMessage('str')).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('rejects objects without a type field', () => {
|
|
25
|
+
expect(validateGatewayMessage({})).toBe(false)
|
|
26
|
+
expect(validateGatewayMessage({ chatId: 'x' })).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('rejects unknown types', () => {
|
|
30
|
+
expect(validateGatewayMessage({ type: 'fake' })).toBe(false)
|
|
31
|
+
expect(validateGatewayMessage({ type: 'Inbound' })).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('all protocol fixtures pass', () => {
|
|
36
|
+
it.each(allGatewayFixtures.map((f) => [f.decoded.type, f] as const))(
|
|
37
|
+
'%s fixture is accepted',
|
|
38
|
+
(_type, fixture) => {
|
|
39
|
+
expect(validateGatewayMessage(JSON.parse(fixture.wire))).toBe(true)
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('inbound', () => {
|
|
45
|
+
it('accepts minimal inbound (chatId + text)', () => {
|
|
46
|
+
expect(validateGatewayMessage({ type: 'inbound', chatId: 'c', text: 'hi' })).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('rejects missing chatId', () => {
|
|
50
|
+
expect(validateGatewayMessage({ type: 'inbound', text: 'hi' })).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('rejects non-string chatId', () => {
|
|
54
|
+
expect(validateGatewayMessage({ type: 'inbound', chatId: 42, text: 'hi' })).toBe(false)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('rejects missing text', () => {
|
|
58
|
+
expect(validateGatewayMessage({ type: 'inbound', chatId: 'c' })).toBe(false)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('rejects non-string text', () => {
|
|
62
|
+
expect(validateGatewayMessage({ type: 'inbound', chatId: 'c', text: 42 })).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('permission', () => {
|
|
67
|
+
it('accepts allow and deny', () => {
|
|
68
|
+
expect(
|
|
69
|
+
validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: 'allow' }),
|
|
70
|
+
).toBe(true)
|
|
71
|
+
expect(
|
|
72
|
+
validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: 'deny' }),
|
|
73
|
+
).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('rejects unknown behavior', () => {
|
|
77
|
+
expect(
|
|
78
|
+
validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: 'maybe' }),
|
|
79
|
+
).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('rejects empty behavior', () => {
|
|
83
|
+
expect(
|
|
84
|
+
validateGatewayMessage({ type: 'permission', requestId: 'r', behavior: '' }),
|
|
85
|
+
).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('rejects missing requestId', () => {
|
|
89
|
+
expect(validateGatewayMessage({ type: 'permission', behavior: 'allow' })).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('status', () => {
|
|
94
|
+
it('accepts any string status', () => {
|
|
95
|
+
expect(validateGatewayMessage({ type: 'status', status: 'anything' })).toBe(true)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('rejects non-string status', () => {
|
|
99
|
+
expect(validateGatewayMessage({ type: 'status', status: 42 })).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('tool_call_result', () => {
|
|
104
|
+
it('accepts success result', () => {
|
|
105
|
+
expect(
|
|
106
|
+
validateGatewayMessage({ type: 'tool_call_result', id: 'x', success: true }),
|
|
107
|
+
).toBe(true)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('accepts failure result', () => {
|
|
111
|
+
expect(
|
|
112
|
+
validateGatewayMessage({
|
|
113
|
+
type: 'tool_call_result',
|
|
114
|
+
id: 'x',
|
|
115
|
+
success: false,
|
|
116
|
+
error: 'nope',
|
|
117
|
+
}),
|
|
118
|
+
).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('rejects missing id', () => {
|
|
122
|
+
expect(
|
|
123
|
+
validateGatewayMessage({ type: 'tool_call_result', success: true }),
|
|
124
|
+
).toBe(false)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('rejects non-boolean success', () => {
|
|
128
|
+
expect(
|
|
129
|
+
validateGatewayMessage({ type: 'tool_call_result', id: 'x', success: 'yes' }),
|
|
130
|
+
).toBe(false)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
})
|