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,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup mutex for the telegram gateway — atomic single-writer guarantee
|
|
3
|
+
* on the per-agent PID file.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists (2026-04-23 incident):
|
|
6
|
+
* --------------------------------------
|
|
7
|
+
* Two pollers can race on Telegram's getUpdates long-poll. When the OLD
|
|
8
|
+
* gateway hasn't fully released its long-poll TCP connection before the
|
|
9
|
+
* NEW gateway boots, the new one gets `409: Conflict: terminated by other
|
|
10
|
+
* getUpdates request`. The gateway then enters an exponential-backoff
|
|
11
|
+
* retry loop. Earlier today we saw the OLD clerk-gateway looping on retry
|
|
12
|
+
* attempt 13 with 10–12s backoffs — i.e. two gateway processes alive at
|
|
13
|
+
* the same time, both polling.
|
|
14
|
+
*
|
|
15
|
+
* The PRs #45–#50 stack added a PID-file probe and a SIGTERM marker but
|
|
16
|
+
* did NOT add a real startup mutex. This module closes that gap.
|
|
17
|
+
*
|
|
18
|
+
* Algorithm (POSIX-portable, no fcntl):
|
|
19
|
+
* 1. Write our record to a uniquely-named tmp file (pid + nanoseconds).
|
|
20
|
+
* 2. fs.link(tmp, canonical) — atomic on every POSIX filesystem.
|
|
21
|
+
* - If link succeeds: we hold the lock. Unlink the tmp.
|
|
22
|
+
* - If link fails with EEXIST: another holder. Read the existing
|
|
23
|
+
* file, check whether the recorded PID is still alive
|
|
24
|
+
* (process.kill(pid, 0)). If alive → blocked. If dead → unlink
|
|
25
|
+
* the stale canonical and retry the link once.
|
|
26
|
+
*
|
|
27
|
+
* Releases happen on shutdown (SIGTERM/SIGINT/uncaught error) by
|
|
28
|
+
* unlinking the canonical path. We log every state transition; do NOT
|
|
29
|
+
* silently swallow filesystem errors.
|
|
30
|
+
*/
|
|
31
|
+
import {
|
|
32
|
+
link as linkAsync,
|
|
33
|
+
unlink as unlinkAsync,
|
|
34
|
+
writeFile as writeFileAsync,
|
|
35
|
+
readFile as readFileAsync,
|
|
36
|
+
} from "node:fs/promises";
|
|
37
|
+
|
|
38
|
+
export interface MutexRecord {
|
|
39
|
+
pid: number;
|
|
40
|
+
startedAtMs: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type AcquireOutcome =
|
|
44
|
+
| {
|
|
45
|
+
status: "acquired";
|
|
46
|
+
record: MutexRecord;
|
|
47
|
+
/** True if a stale prior record was cleaned up before acquiring. */
|
|
48
|
+
recoveredFrom?: MutexRecord;
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
status: "blocked";
|
|
52
|
+
holder: MutexRecord;
|
|
53
|
+
holderAgeSec: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface AcquireOptions {
|
|
57
|
+
/** Canonical path of the lock file (e.g. .../telegram/gateway.pid.json). */
|
|
58
|
+
path: string;
|
|
59
|
+
/** Record we'll try to write. */
|
|
60
|
+
record: MutexRecord;
|
|
61
|
+
/**
|
|
62
|
+
* PID liveness probe. Defaults to process.kill(pid, 0) semantics.
|
|
63
|
+
* Injectable so tests can simulate dead/alive PIDs without forking.
|
|
64
|
+
*/
|
|
65
|
+
isPidAlive?: (pid: number) => boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Logger. Defaults to process.stderr.write. Lines are pre-formatted
|
|
68
|
+
* with the `telegram gateway:` prefix to match journalctl style.
|
|
69
|
+
*/
|
|
70
|
+
log?: (line: string) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Agent name to include in log lines for journalctl filtering.
|
|
73
|
+
*/
|
|
74
|
+
agentName?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const DEFAULT_LOG = (line: string): void => {
|
|
78
|
+
process.stderr.write(line.endsWith("\n") ? line : line + "\n");
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const DEFAULT_IS_ALIVE = (pid: number): boolean => {
|
|
82
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
83
|
+
if (pid === process.pid) return true;
|
|
84
|
+
try {
|
|
85
|
+
process.kill(pid, 0);
|
|
86
|
+
return true;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
89
|
+
// EPERM means the process exists but we can't signal it (different
|
|
90
|
+
// user). Treat as alive — we'd rather block than collide with an
|
|
91
|
+
// unkillable holder.
|
|
92
|
+
if (code === "EPERM") return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
function fmtAgent(agentName: string | undefined): string {
|
|
98
|
+
return agentName ? ` agent=${agentName}` : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function tmpPath(canonical: string, pid: number): string {
|
|
102
|
+
// hrtime.bigint() guarantees uniqueness even under same-millisecond
|
|
103
|
+
// double-boot races (which is exactly what we're defending against).
|
|
104
|
+
return `${canonical}.tmp-${pid}-${process.hrtime.bigint().toString(36)}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function tryReadRecord(path: string): Promise<MutexRecord | null> {
|
|
108
|
+
try {
|
|
109
|
+
const raw = await readFileAsync(path, "utf-8");
|
|
110
|
+
const parsed = JSON.parse(raw) as Partial<MutexRecord>;
|
|
111
|
+
if (
|
|
112
|
+
typeof parsed.pid === "number" &&
|
|
113
|
+
typeof parsed.startedAtMs === "number" &&
|
|
114
|
+
Number.isFinite(parsed.pid) &&
|
|
115
|
+
Number.isFinite(parsed.startedAtMs)
|
|
116
|
+
) {
|
|
117
|
+
return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Atomically attempt to acquire the lock. Resolves with either
|
|
127
|
+
* `acquired` (we own the file now) or `blocked` (a live holder exists).
|
|
128
|
+
*
|
|
129
|
+
* Throws only on unrecoverable filesystem errors (ENOSPC, EROFS, EACCES
|
|
130
|
+
* on a directory that should be writable). The caller MUST treat thrown
|
|
131
|
+
* errors as fatal — the gateway should exit non-zero so systemd can
|
|
132
|
+
* apply its restart-burst backoff.
|
|
133
|
+
*/
|
|
134
|
+
export async function acquireStartupLock(
|
|
135
|
+
opts: AcquireOptions,
|
|
136
|
+
): Promise<AcquireOutcome> {
|
|
137
|
+
const log = opts.log ?? DEFAULT_LOG;
|
|
138
|
+
const isAlive = opts.isPidAlive ?? DEFAULT_IS_ALIVE;
|
|
139
|
+
const { path, record, agentName } = opts;
|
|
140
|
+
const agentTag = fmtAgent(agentName);
|
|
141
|
+
|
|
142
|
+
const tmp = tmpPath(path, record.pid);
|
|
143
|
+
const payload = JSON.stringify(record);
|
|
144
|
+
|
|
145
|
+
// Write the tmp file first. If this throws, the canonical isn't
|
|
146
|
+
// touched — caller can retry on a fresh boot.
|
|
147
|
+
await writeFileAsync(tmp, payload, { encoding: "utf-8", mode: 0o600 });
|
|
148
|
+
|
|
149
|
+
let recoveredFrom: MutexRecord | undefined;
|
|
150
|
+
|
|
151
|
+
// Try the atomic link. ONE retry after stale-recovery; we don't loop
|
|
152
|
+
// forever — if the second link also fails EEXIST we treat as blocked
|
|
153
|
+
// (something else won the race in the gap, and that something else is
|
|
154
|
+
// now the legitimate holder).
|
|
155
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
156
|
+
try {
|
|
157
|
+
await linkAsync(tmp, path);
|
|
158
|
+
// Won the race. Drop the tmp; canonical is the live lock now.
|
|
159
|
+
await unlinkAsync(tmp).catch(() => {
|
|
160
|
+
/* tmp cleanup is best-effort */
|
|
161
|
+
});
|
|
162
|
+
log(
|
|
163
|
+
`telegram gateway: boot.lock_acquired pid=${record.pid} started_at=${new Date(
|
|
164
|
+
record.startedAtMs,
|
|
165
|
+
).toISOString()}${agentTag}`,
|
|
166
|
+
);
|
|
167
|
+
return recoveredFrom
|
|
168
|
+
? { status: "acquired", record, recoveredFrom }
|
|
169
|
+
: { status: "acquired", record };
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
172
|
+
if (code !== "EEXIST") {
|
|
173
|
+
// Unrecoverable. Clean up tmp before propagating.
|
|
174
|
+
await unlinkAsync(tmp).catch(() => {});
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// EEXIST → inspect holder.
|
|
179
|
+
const holder = await tryReadRecord(path);
|
|
180
|
+
if (holder == null) {
|
|
181
|
+
// File exists but unreadable / corrupt. Treat as stale and
|
|
182
|
+
// unlink it. (Better than blocking forever on a garbage file.)
|
|
183
|
+
log(
|
|
184
|
+
`telegram gateway: boot.lock_corrupt_recovered path=${path}${agentTag}`,
|
|
185
|
+
);
|
|
186
|
+
await unlinkAsync(path).catch(() => {});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isAlive(holder.pid)) {
|
|
191
|
+
// Live holder. Drop tmp and report blocked.
|
|
192
|
+
await unlinkAsync(tmp).catch(() => {});
|
|
193
|
+
const ageSec = Math.max(
|
|
194
|
+
0,
|
|
195
|
+
Math.round((Date.now() - holder.startedAtMs) / 1000),
|
|
196
|
+
);
|
|
197
|
+
log(
|
|
198
|
+
`telegram gateway: boot.lock_blocked holder_pid=${holder.pid} holder_started_at=${new Date(
|
|
199
|
+
holder.startedAtMs,
|
|
200
|
+
).toISOString()} holder_age_sec=${ageSec}${agentTag}`,
|
|
201
|
+
);
|
|
202
|
+
return { status: "blocked", holder, holderAgeSec: ageSec };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Stale holder (PID dead). Unlink and loop once to retry the
|
|
206
|
+
// link. Log before unlink so the recovery is auditable.
|
|
207
|
+
log(
|
|
208
|
+
`telegram gateway: boot.lock_stale_recovered prior_pid=${holder.pid} prior_started_at=${new Date(
|
|
209
|
+
holder.startedAtMs,
|
|
210
|
+
).toISOString()}${agentTag}`,
|
|
211
|
+
);
|
|
212
|
+
try {
|
|
213
|
+
await unlinkAsync(path);
|
|
214
|
+
} catch (unlinkErr) {
|
|
215
|
+
const unlinkCode = (unlinkErr as NodeJS.ErrnoException).code;
|
|
216
|
+
if (unlinkCode !== "ENOENT") {
|
|
217
|
+
// Couldn't clean up — propagate. Tmp gets cleaned by finalizer.
|
|
218
|
+
await unlinkAsync(tmp).catch(() => {});
|
|
219
|
+
throw unlinkErr;
|
|
220
|
+
}
|
|
221
|
+
// ENOENT means someone else cleaned it; that's fine, retry link.
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
recoveredFrom = holder;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Reached only if both link attempts failed EEXIST. The second EEXIST
|
|
229
|
+
// implies a live process won the race after we cleared the stale
|
|
230
|
+
// file. Re-read and report as blocked.
|
|
231
|
+
await unlinkAsync(tmp).catch(() => {});
|
|
232
|
+
const finalHolder = await tryReadRecord(path);
|
|
233
|
+
if (finalHolder != null && isAlive(finalHolder.pid)) {
|
|
234
|
+
const ageSec = Math.max(
|
|
235
|
+
0,
|
|
236
|
+
Math.round((Date.now() - finalHolder.startedAtMs) / 1000),
|
|
237
|
+
);
|
|
238
|
+
log(
|
|
239
|
+
`telegram gateway: boot.lock_blocked holder_pid=${finalHolder.pid} holder_started_at=${new Date(
|
|
240
|
+
finalHolder.startedAtMs,
|
|
241
|
+
).toISOString()} holder_age_sec=${ageSec}${agentTag}`,
|
|
242
|
+
);
|
|
243
|
+
return { status: "blocked", holder: finalHolder, holderAgeSec: ageSec };
|
|
244
|
+
}
|
|
245
|
+
// Edge: file vanished between attempts. Treat as blocked-with-unknown
|
|
246
|
+
// rather than spinning further; systemd backoff is the right answer.
|
|
247
|
+
const fallback: MutexRecord = finalHolder ?? { pid: 0, startedAtMs: 0 };
|
|
248
|
+
log(
|
|
249
|
+
`telegram gateway: boot.lock_blocked_unknown_holder${agentTag}`,
|
|
250
|
+
);
|
|
251
|
+
return { status: "blocked", holder: fallback, holderAgeSec: 0 };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Release the lock by unlinking the canonical file. Logs and swallows
|
|
256
|
+
* ENOENT (already gone — fine). Other errors are logged but NOT thrown:
|
|
257
|
+
* shutdown paths run in finally blocks where throwing would skip
|
|
258
|
+
* subsequent cleanup steps.
|
|
259
|
+
*/
|
|
260
|
+
export async function releaseStartupLock(opts: {
|
|
261
|
+
path: string;
|
|
262
|
+
pid: number;
|
|
263
|
+
log?: (line: string) => void;
|
|
264
|
+
agentName?: string;
|
|
265
|
+
}): Promise<void> {
|
|
266
|
+
const log = opts.log ?? DEFAULT_LOG;
|
|
267
|
+
const agentTag = fmtAgent(opts.agentName);
|
|
268
|
+
try {
|
|
269
|
+
await unlinkAsync(opts.path);
|
|
270
|
+
log(`telegram gateway: shutdown.lock_released pid=${opts.pid}${agentTag}`);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
273
|
+
if (code === "ENOENT") {
|
|
274
|
+
// Already gone — log at info, not error. Common after a crash
|
|
275
|
+
// where someone else cleaned up.
|
|
276
|
+
log(
|
|
277
|
+
`telegram gateway: shutdown.lock_release_noop pid=${opts.pid}${agentTag}`,
|
|
278
|
+
);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
log(
|
|
282
|
+
`telegram gateway: shutdown.lock_release_failed pid=${opts.pid} code=${code ?? "unknown"} err=${(err as Error).message}${agentTag}`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded exponential-backoff retry for gateway startup network errors.
|
|
3
|
+
*
|
|
4
|
+
* On 2026-04-29 all five switchroom gateways silently broke at boot because
|
|
5
|
+
* `api.telegram.org` was unreachable for ~27 minutes after system boot (the
|
|
6
|
+
* network stack wasn't fully usable when `network-online.target` fired).
|
|
7
|
+
* Grammy threw `HttpError: Network request for 'deleteWebhook'/'getMe' failed!`
|
|
8
|
+
* and the gateway's catch block logged the error and **returned** — leaving the
|
|
9
|
+
* process alive but not polling. No crash, so systemd's `Restart=always` never
|
|
10
|
+
* fired. Telegram → agent delivery was dead until manual restarts.
|
|
11
|
+
*
|
|
12
|
+
* This module provides:
|
|
13
|
+
*
|
|
14
|
+
* `isBootNetworkError(err)` — recognises network-layer errors thrown by
|
|
15
|
+
* grammy's HttpError wrapper and by raw fetch/Node network failures.
|
|
16
|
+
*
|
|
17
|
+
* `STARTUP_RETRY_DELAYS_MS` — the chosen backoff schedule.
|
|
18
|
+
*
|
|
19
|
+
* `gatewayStartupRetry(fn, opts)` — drives the retry loop. Calls `fn()` up to
|
|
20
|
+
* `maxAttempts` times with delays from `delaysMs`. On success it resolves.
|
|
21
|
+
* On exhaustion it calls `opts.onExhausted()` (default: `process.exit(1)`)
|
|
22
|
+
* so systemd's `Restart=always` can restart the unit cleanly.
|
|
23
|
+
*
|
|
24
|
+
* The function is extracted from `gateway.ts`'s top-level IIFE so it can be
|
|
25
|
+
* unit-tested without spinning up the full bot runtime.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export interface StartupRetryOpts {
|
|
29
|
+
/**
|
|
30
|
+
* Delay schedule in milliseconds. Each attempt waits the corresponding
|
|
31
|
+
* element before the NEXT attempt. Length determines max extra attempts
|
|
32
|
+
* (total = delays.length + 1 initial attempt).
|
|
33
|
+
*
|
|
34
|
+
* Defaults to `STARTUP_RETRY_DELAYS_MS` (~2 min budget).
|
|
35
|
+
*/
|
|
36
|
+
delaysMs?: number[]
|
|
37
|
+
|
|
38
|
+
/** Inject a sleep helper so tests can use fake timers. */
|
|
39
|
+
sleep?: (ms: number) => Promise<void>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Called when all attempts are exhausted. Should NOT return (exit/throw).
|
|
43
|
+
* Defaults to `process.exit(1)`.
|
|
44
|
+
*/
|
|
45
|
+
onExhausted?: (lastError: unknown) => never
|
|
46
|
+
|
|
47
|
+
/** Log sink for retry progress messages. Defaults to process.stderr.write. */
|
|
48
|
+
log?: (line: string) => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default backoff schedule: 1 s, 2 s, 4 s, 8 s, 16 s, 32 s, 64 s.
|
|
53
|
+
* Total budget including 8 attempts: ~2 min 7 s. Chosen so a typical
|
|
54
|
+
* post-boot network settle (empirically <90 s) is covered with headroom.
|
|
55
|
+
*/
|
|
56
|
+
export const STARTUP_RETRY_DELAYS_MS: number[] = [
|
|
57
|
+
1_000,
|
|
58
|
+
2_000,
|
|
59
|
+
4_000,
|
|
60
|
+
8_000,
|
|
61
|
+
16_000,
|
|
62
|
+
32_000,
|
|
63
|
+
64_000,
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
const DEFAULT_SLEEP = (ms: number): Promise<void> =>
|
|
67
|
+
new Promise((resolve) => setTimeout(resolve, ms))
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns true if `err` is a transient network-level failure that the startup
|
|
71
|
+
* retry loop should absorb. Covers:
|
|
72
|
+
*
|
|
73
|
+
* - Grammy's `HttpError` (name === 'HttpError'), which wraps fetch/ECONN errors
|
|
74
|
+
* during `deleteWebhook` and `getMe`.
|
|
75
|
+
* - Raw Node/fetch errors: ECONNRESET, ETIMEDOUT, ENOTFOUND, ECONNREFUSED,
|
|
76
|
+
* fetch failed, etc.
|
|
77
|
+
*/
|
|
78
|
+
export function isBootNetworkError(err: unknown): boolean {
|
|
79
|
+
if (!(err instanceof Error)) return false
|
|
80
|
+
// Grammy wraps network errors in HttpError (name is set in the constructor)
|
|
81
|
+
if (err.name === 'HttpError') return true
|
|
82
|
+
const msg = err.message
|
|
83
|
+
return (
|
|
84
|
+
msg.includes('ECONNRESET') ||
|
|
85
|
+
msg.includes('ETIMEDOUT') ||
|
|
86
|
+
msg.includes('ENOTFOUND') ||
|
|
87
|
+
msg.includes('ECONNREFUSED') ||
|
|
88
|
+
msg.includes('fetch failed') ||
|
|
89
|
+
msg.includes('Network request')
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Attempt `fn()` and retry on `isBootNetworkError` failures using the
|
|
95
|
+
* provided delay schedule.
|
|
96
|
+
*
|
|
97
|
+
* - On success: returns whatever `fn()` resolved to.
|
|
98
|
+
* - On non-network error: re-throws immediately (not a transient boot issue).
|
|
99
|
+
* - On exhausted retries: calls `opts.onExhausted(lastError)` which must not
|
|
100
|
+
* return (it should exit or throw). The default is `process.exit(1)` so
|
|
101
|
+
* systemd's `Restart=always` picks up the dead unit.
|
|
102
|
+
*/
|
|
103
|
+
export async function gatewayStartupRetry<T>(
|
|
104
|
+
fn: () => Promise<T>,
|
|
105
|
+
opts: StartupRetryOpts = {},
|
|
106
|
+
): Promise<T> {
|
|
107
|
+
const delays = opts.delaysMs ?? STARTUP_RETRY_DELAYS_MS
|
|
108
|
+
const sleep = opts.sleep ?? DEFAULT_SLEEP
|
|
109
|
+
const onExhausted: (err: unknown) => never =
|
|
110
|
+
opts.onExhausted ??
|
|
111
|
+
((err: unknown) => {
|
|
112
|
+
process.stderr.write(
|
|
113
|
+
`telegram gateway: startup failed after ${delays.length + 1} attempts — exiting so systemd can restart: ${err}\n`,
|
|
114
|
+
)
|
|
115
|
+
process.exit(1)
|
|
116
|
+
})
|
|
117
|
+
const log =
|
|
118
|
+
opts.log ??
|
|
119
|
+
((line: string) => {
|
|
120
|
+
process.stderr.write(line.endsWith('\n') ? line : line + '\n')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const maxAttempts = delays.length + 1
|
|
124
|
+
let lastError: unknown
|
|
125
|
+
|
|
126
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
127
|
+
try {
|
|
128
|
+
return await fn()
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (!isBootNetworkError(err)) throw err
|
|
131
|
+
lastError = err
|
|
132
|
+
if (attempt >= maxAttempts) break
|
|
133
|
+
const delayMs = delays[attempt - 1]
|
|
134
|
+
log(
|
|
135
|
+
`telegram gateway: startup network error (attempt ${attempt}/${maxAttempts}), retrying in ${delayMs / 1000}s: ${err}`,
|
|
136
|
+
)
|
|
137
|
+
await sleep(delayMs)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return onExhausted(lastError)
|
|
142
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn-active liveness marker (#412).
|
|
3
|
+
*
|
|
4
|
+
* Writes `<STATE_DIR>/turn-active.json` on turn_start, touches its mtime
|
|
5
|
+
* on every tool_use, removes it on turn_complete. The watchdog
|
|
6
|
+
* (bin/bridge-watchdog.sh) reads the mtime: if the file exists AND its
|
|
7
|
+
* mtime is older than TURN_HANG_SECS (default 300s = 5min), the agent
|
|
8
|
+
* is wedged mid-turn and the watchdog restarts.
|
|
9
|
+
*
|
|
10
|
+
* Why this exists: PR #410 raised the journal-silence detector to 4000s
|
|
11
|
+
* to kill false positives on chat-cadence agents that legitimately
|
|
12
|
+
* idle for hours between turns. That left a gap — Stop-hook deadlocks
|
|
13
|
+
* (the original failure mode #116 tracked) are no longer caught under
|
|
14
|
+
* default thresholds.
|
|
15
|
+
*
|
|
16
|
+
* The distinguisher is "in-turn-and-silent" vs "between-turns-and-silent":
|
|
17
|
+
* the former is a wedge, the latter is healthy idle. This marker exists
|
|
18
|
+
* exactly during in-turn windows, so its staleness uniquely indicates
|
|
19
|
+
* the wedge.
|
|
20
|
+
*
|
|
21
|
+
* Pure file I/O. The actual hang-detection-and-restart loop lives in the
|
|
22
|
+
* bash watchdog, where it composes with the existing
|
|
23
|
+
* Restart=on-failure / journal-silence / bridge-disconnect detectors.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
closeSync,
|
|
28
|
+
existsSync,
|
|
29
|
+
mkdirSync,
|
|
30
|
+
openSync,
|
|
31
|
+
readFileSync,
|
|
32
|
+
statSync,
|
|
33
|
+
unlinkSync,
|
|
34
|
+
utimesSync,
|
|
35
|
+
writeFileSync,
|
|
36
|
+
} from "node:fs";
|
|
37
|
+
import { join } from "node:path";
|
|
38
|
+
|
|
39
|
+
export const TURN_ACTIVE_MARKER_FILE = "turn-active.json";
|
|
40
|
+
|
|
41
|
+
export interface TurnActiveMarker {
|
|
42
|
+
turnKey: string;
|
|
43
|
+
chatId: string;
|
|
44
|
+
threadId?: string | null;
|
|
45
|
+
startedAt: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write the marker file at turn-start. Idempotent — if the file
|
|
50
|
+
* already exists from a stale prior turn (unlikely; turn_complete
|
|
51
|
+
* removes it), the new write wins.
|
|
52
|
+
*/
|
|
53
|
+
export function writeTurnActiveMarker(stateDir: string, marker: TurnActiveMarker): void {
|
|
54
|
+
try {
|
|
55
|
+
mkdirSync(stateDir, { recursive: true });
|
|
56
|
+
writeFileSync(
|
|
57
|
+
join(stateDir, TURN_ACTIVE_MARKER_FILE),
|
|
58
|
+
JSON.stringify(marker, null, 2) + "\n",
|
|
59
|
+
{ mode: 0o600 },
|
|
60
|
+
);
|
|
61
|
+
} catch {
|
|
62
|
+
// Best-effort: marker file is a watchdog optimisation, not a
|
|
63
|
+
// correctness requirement. Don't break the turn-start path on
|
|
64
|
+
// disk-full, ENOSPC, etc.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Touch the marker file's mtime. Called on every tool_use event so an
|
|
70
|
+
* agent doing real work continually advances the mtime. The watchdog's
|
|
71
|
+
* threshold compares against this mtime.
|
|
72
|
+
*/
|
|
73
|
+
export function touchTurnActiveMarker(stateDir: string): void {
|
|
74
|
+
const path = join(stateDir, TURN_ACTIVE_MARKER_FILE);
|
|
75
|
+
if (!existsSync(path)) return;
|
|
76
|
+
const now = new Date();
|
|
77
|
+
try {
|
|
78
|
+
utimesSync(path, now, now);
|
|
79
|
+
} catch {
|
|
80
|
+
// utimesSync can fail on some filesystems; fall back to a tiny
|
|
81
|
+
// open-close cycle to bump the mtime via writes from the kernel side.
|
|
82
|
+
try {
|
|
83
|
+
const fd = openSync(path, "r+");
|
|
84
|
+
closeSync(fd);
|
|
85
|
+
} catch {
|
|
86
|
+
/* swallow — best-effort */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove the marker file at turn_complete. Absence of the file is the
|
|
93
|
+
* watchdog's signal that no turn is in flight (legitimate idle, no
|
|
94
|
+
* reason to suspect a hang).
|
|
95
|
+
*/
|
|
96
|
+
export function removeTurnActiveMarker(stateDir: string): void {
|
|
97
|
+
try {
|
|
98
|
+
unlinkSync(join(stateDir, TURN_ACTIVE_MARKER_FILE));
|
|
99
|
+
} catch {
|
|
100
|
+
// ENOENT is fine (already removed); other errors don't justify
|
|
101
|
+
// breaking the turn-end path.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sweep a stale marker file. Defence-in-depth backstop for #550 — when
|
|
107
|
+
* the primary `turn_end` removal path is silently skipped (e.g. SDK
|
|
108
|
+
* killed before the JSONL turn_duration record is written, or the
|
|
109
|
+
* progress-card driver's `forceCompleteTurn` no-ops because the card
|
|
110
|
+
* was already torn down), the marker leaks across restarts and the
|
|
111
|
+
* watchdog reads it as a hung turn.
|
|
112
|
+
*
|
|
113
|
+
* Removes the marker if EITHER:
|
|
114
|
+
* - mtime is older than `idleSweepMs` AND the caller asserts that no
|
|
115
|
+
* turn is currently in flight (`turnInFlight=false`), OR
|
|
116
|
+
* - mtime is older than `hardTtlMs` unconditionally (the absolute
|
|
117
|
+
* ceiling — anything older than this can't be a real turn).
|
|
118
|
+
*
|
|
119
|
+
* Both conditions are best-effort and idempotent. Returns true if the
|
|
120
|
+
* marker was removed, false otherwise.
|
|
121
|
+
*/
|
|
122
|
+
export function sweepStaleTurnActiveMarker(
|
|
123
|
+
stateDir: string,
|
|
124
|
+
opts: {
|
|
125
|
+
turnInFlight: boolean;
|
|
126
|
+
idleSweepMs: number;
|
|
127
|
+
hardTtlMs: number;
|
|
128
|
+
now?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Optional diagnostic callback invoked when the marker is removed.
|
|
131
|
+
* Best-effort: keeps the function pure (no logger coupling) while
|
|
132
|
+
* letting the caller emit a structured journalctl line. Must not
|
|
133
|
+
* throw — exceptions from this callback are swallowed.
|
|
134
|
+
*/
|
|
135
|
+
onRemove?: (info: {
|
|
136
|
+
ageMs: number;
|
|
137
|
+
reason: "idle-stale" | "hard-ttl";
|
|
138
|
+
payload: string | null;
|
|
139
|
+
}) => void;
|
|
140
|
+
},
|
|
141
|
+
): boolean {
|
|
142
|
+
const path = join(stateDir, TURN_ACTIVE_MARKER_FILE);
|
|
143
|
+
if (!existsSync(path)) return false;
|
|
144
|
+
const now = opts.now ?? Date.now();
|
|
145
|
+
try {
|
|
146
|
+
const st = statSync(path);
|
|
147
|
+
const ageMs = now - st.mtimeMs;
|
|
148
|
+
const hardExpired = ageMs > opts.hardTtlMs;
|
|
149
|
+
const idleExpired = !opts.turnInFlight && ageMs > opts.idleSweepMs;
|
|
150
|
+
if (!hardExpired && !idleExpired) return false;
|
|
151
|
+
// Best-effort read so the diagnostic callback can include the
|
|
152
|
+
// payload (turnKey, chatId, startedAt) for forensic logging.
|
|
153
|
+
let payload: string | null = null;
|
|
154
|
+
try {
|
|
155
|
+
payload = readFileSync(path, "utf8");
|
|
156
|
+
} catch {
|
|
157
|
+
/* unreadable — still drop the marker */
|
|
158
|
+
}
|
|
159
|
+
unlinkSync(path);
|
|
160
|
+
if (opts.onRemove) {
|
|
161
|
+
try {
|
|
162
|
+
opts.onRemove({
|
|
163
|
+
ageMs,
|
|
164
|
+
reason: hardExpired ? "hard-ttl" : "idle-stale",
|
|
165
|
+
payload,
|
|
166
|
+
});
|
|
167
|
+
} catch {
|
|
168
|
+
/* swallow — diagnostics must never break the sweep */
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
// ENOENT race or stat failure — nothing actionable.
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminating policy for the gateway's `unhandledRejection` handler.
|
|
3
|
+
*
|
|
4
|
+
* Background: gateway.ts crashes the process on every unhandledRejection
|
|
5
|
+
* (it calls `shutdown()` from the handler). Some Telegram API errors
|
|
6
|
+
* surface here as benign 400s — "message is not modified", "message to
|
|
7
|
+
* edit not found" — and crashing the gateway over them creates restart
|
|
8
|
+
* loops (issue #99 + lawgpt's 11:36 crash family).
|
|
9
|
+
*
|
|
10
|
+
* Pure helper so it can be tested without spinning up the gateway.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { GrammyError } from 'grammy'
|
|
14
|
+
|
|
15
|
+
export type RejectionAction = 'shutdown' | 'log_only'
|
|
16
|
+
|
|
17
|
+
export interface RejectionPolicyOptions {
|
|
18
|
+
/** Allow tests to inject error type detection without depending on grammy. */
|
|
19
|
+
isGrammyError?: (err: unknown) => boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Decide whether an unhandledRejection should crash the gateway.
|
|
24
|
+
*
|
|
25
|
+
* Returns:
|
|
26
|
+
* - `'log_only'` for benign Telegram 400s the bot already tolerates
|
|
27
|
+
* elsewhere (see retry-api-call.ts). Logging surfaces the leak; not
|
|
28
|
+
* crashing prevents restart loops.
|
|
29
|
+
* - `'shutdown'` for everything else. Genuine bugs still crash, which
|
|
30
|
+
* systemd will surface as a restart and we want that signal.
|
|
31
|
+
*
|
|
32
|
+
* The set of benign descriptions is intentionally narrow — only the
|
|
33
|
+
* specific 400s the wrapper already swallows. Any other 400 still
|
|
34
|
+
* triggers shutdown so we don't silently mask new bugs.
|
|
35
|
+
*/
|
|
36
|
+
export function classifyRejection(
|
|
37
|
+
err: unknown,
|
|
38
|
+
opts: RejectionPolicyOptions = {},
|
|
39
|
+
): RejectionAction {
|
|
40
|
+
const isGrammy =
|
|
41
|
+
opts.isGrammyError != null
|
|
42
|
+
? opts.isGrammyError(err)
|
|
43
|
+
: err instanceof GrammyError
|
|
44
|
+
|
|
45
|
+
if (!isGrammy) return 'shutdown'
|
|
46
|
+
|
|
47
|
+
const e = err as { error_code?: number; description?: string }
|
|
48
|
+
if (e.error_code !== 400) return 'shutdown'
|
|
49
|
+
|
|
50
|
+
const desc = (e.description ?? '').toLowerCase()
|
|
51
|
+
if (
|
|
52
|
+
desc.includes('message is not modified') ||
|
|
53
|
+
desc.includes('message to edit not found') ||
|
|
54
|
+
desc.includes('message to delete not found') ||
|
|
55
|
+
// HTML parse errors (e.g. formatDuration sub-second output like "<1s"
|
|
56
|
+
// interpreted as a tag). These are transient render bugs — log the
|
|
57
|
+
// failure so we can fix the root cause, but don't crash the gateway
|
|
58
|
+
// into a restart loop (issue #101).
|
|
59
|
+
desc.includes("can't parse entities") ||
|
|
60
|
+
desc.includes('unsupported start tag') ||
|
|
61
|
+
// 'chat not found' fires when an allowlisted chat id (typically a
|
|
62
|
+
// group in access.json/groups{}) is no longer reachable — bot was
|
|
63
|
+
// removed, group was deleted, or the id was stale config from a
|
|
64
|
+
// prior bot pairing. Boot-time pin sweep + various other checks
|
|
65
|
+
// call getChat against every allowlisted chat; a single unreachable
|
|
66
|
+
// entry was crashing the gateway into restart loops on ziggy
|
|
67
|
+
// (2026-05-02 12:05) even though boot-probe-failed already logged
|
|
68
|
+
// the failure structurally. The wrapped sweep handles the visible
|
|
69
|
+
// case; this policy entry covers any leaked rejection from the
|
|
70
|
+
// same family so a single bad chat id can't restart-loop the
|
|
71
|
+
// process. The visible boot-probe-failed log is the primary
|
|
72
|
+
// diagnostic; this is the can't-restart-loop guarantee.
|
|
73
|
+
desc.includes('chat not found')
|
|
74
|
+
) {
|
|
75
|
+
return 'log_only'
|
|
76
|
+
}
|
|
77
|
+
return 'shutdown'
|
|
78
|
+
}
|