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,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pro/Max plan quota check — hits /v1/messages with the Claude CLI's OAuth
|
|
3
|
+
* auth + header shape and reads the rate-limit utilization values from the
|
|
4
|
+
* response headers. This is the same mechanism the TUI's /usage panel uses.
|
|
5
|
+
*
|
|
6
|
+
* Why this module exists: before discovering the header surface, Switchroom
|
|
7
|
+
* only had ccusage-based dollar-cost tracking (what you spent), not what
|
|
8
|
+
* the Pro/Max plan's 5-hour and 7-day rolling windows actually show. Those
|
|
9
|
+
* utilization values never appear in request/response bodies, only in
|
|
10
|
+
* headers, and only when the request is authenticated with a subscription
|
|
11
|
+
* OAuth token and carries the CLI's exact user-agent + beta headers.
|
|
12
|
+
*
|
|
13
|
+
* Returning `{ ok: false, reason }` instead of throwing lets callers
|
|
14
|
+
* (greeting hook, /usage command) render a graceful fallback row without
|
|
15
|
+
* having to catch.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, existsSync } from "fs";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
import {
|
|
21
|
+
readAccountQuota,
|
|
22
|
+
snapshotFromQuotaUtilization,
|
|
23
|
+
writeAccountQuota,
|
|
24
|
+
} from "../src/auth/account-quota-store.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* OAuth beta flag — proves the request is coming from a subscription client.
|
|
28
|
+
* Plain bearer OAuth tokens without this header are rejected with
|
|
29
|
+
* "OAuth authentication is currently not supported".
|
|
30
|
+
*/
|
|
31
|
+
const OAUTH_BETA = "oauth-2025-04-20";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* User-agent the CLI sends. Kept in sync with observed traffic;
|
|
35
|
+
* the server is lenient on the version suffix but strict on the
|
|
36
|
+
* overall shape ("claude-cli/X.Y.Z (external, cli)").
|
|
37
|
+
*/
|
|
38
|
+
const DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default model for the probe. Picked to minimize spend — one input token,
|
|
42
|
+
* max_tokens=1, a Haiku model. The response body is discarded; we only
|
|
43
|
+
* care about the headers.
|
|
44
|
+
*/
|
|
45
|
+
const DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
|
|
46
|
+
|
|
47
|
+
export type QuotaUtilization = {
|
|
48
|
+
fiveHourUtilizationPct: number;
|
|
49
|
+
sevenDayUtilizationPct: number;
|
|
50
|
+
fiveHourResetAt: Date | null;
|
|
51
|
+
sevenDayResetAt: Date | null;
|
|
52
|
+
representativeClaim: string | null;
|
|
53
|
+
overageStatus: string | null;
|
|
54
|
+
overageDisabledReason: string | null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type QuotaResult =
|
|
58
|
+
| { ok: true; data: QuotaUtilization }
|
|
59
|
+
| { ok: false; reason: string };
|
|
60
|
+
|
|
61
|
+
export type FetchQuotaOptions = {
|
|
62
|
+
/**
|
|
63
|
+
* Path to the agent's Claude config dir (contains `.oauth-token`).
|
|
64
|
+
* Mutually exclusive with `accessToken`. One of the two must be set.
|
|
65
|
+
*/
|
|
66
|
+
claudeConfigDir?: string;
|
|
67
|
+
/**
|
|
68
|
+
* OAuth access token to probe with directly. Use this from the
|
|
69
|
+
* account-level path (`~/.switchroom/accounts/<label>/credentials.json`)
|
|
70
|
+
* where the credentials live in the new account model rather than
|
|
71
|
+
* a legacy `.oauth-token` file. Mutually exclusive with
|
|
72
|
+
* `claudeConfigDir`.
|
|
73
|
+
*/
|
|
74
|
+
accessToken?: string;
|
|
75
|
+
/** Override probe model. Defaults to haiku-4-5. */
|
|
76
|
+
model?: string;
|
|
77
|
+
/** Abort after this many ms. Defaults to 10s. */
|
|
78
|
+
timeoutMs?: number;
|
|
79
|
+
/** Override fetch for tests. */
|
|
80
|
+
fetchImpl?: typeof fetch;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function readOauthToken(claudeConfigDir: string): string | null {
|
|
84
|
+
const tokenFile = join(claudeConfigDir, ".oauth-token");
|
|
85
|
+
if (!existsSync(tokenFile)) return null;
|
|
86
|
+
try {
|
|
87
|
+
const raw = readFileSync(tokenFile, "utf-8").trim();
|
|
88
|
+
return raw.length > 0 ? raw : null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseFloatHeader(headers: Headers, name: string): number | null {
|
|
95
|
+
const v = headers.get(name);
|
|
96
|
+
if (v == null || v.trim().length === 0) return null;
|
|
97
|
+
const n = Number(v);
|
|
98
|
+
return Number.isFinite(n) ? n : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseEpochHeader(headers: Headers, name: string): Date | null {
|
|
102
|
+
const v = headers.get(name);
|
|
103
|
+
if (v == null) return null;
|
|
104
|
+
const n = Number(v);
|
|
105
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
106
|
+
return new Date(n * 1000);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function parseQuotaHeaders(headers: Headers): QuotaResult {
|
|
110
|
+
const fiveHour = parseFloatHeader(headers, "anthropic-ratelimit-unified-5h-utilization");
|
|
111
|
+
const sevenDay = parseFloatHeader(headers, "anthropic-ratelimit-unified-7d-utilization");
|
|
112
|
+
if (fiveHour == null && sevenDay == null) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
reason: "no unified rate-limit headers in response (API token, not OAuth?)",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
ok: true,
|
|
120
|
+
data: {
|
|
121
|
+
fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
|
|
122
|
+
sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
|
|
123
|
+
fiveHourResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-5h-reset"),
|
|
124
|
+
sevenDayResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-7d-reset"),
|
|
125
|
+
representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
|
|
126
|
+
overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
|
|
127
|
+
overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason"),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function fetchQuota(opts: FetchQuotaOptions): Promise<QuotaResult> {
|
|
133
|
+
// Resolve the bearer token from either an explicit accessToken
|
|
134
|
+
// (account-level path) or by reading `.oauth-token` from a Claude
|
|
135
|
+
// config dir (legacy per-agent path). Reject if neither is set or
|
|
136
|
+
// both are — keep the API contract narrow.
|
|
137
|
+
let token: string | null;
|
|
138
|
+
if (opts.accessToken && opts.claudeConfigDir) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
reason: "pass only one of `accessToken` or `claudeConfigDir`, not both",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (opts.accessToken) {
|
|
145
|
+
token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
|
|
146
|
+
} else if (opts.claudeConfigDir) {
|
|
147
|
+
token = readOauthToken(opts.claudeConfigDir);
|
|
148
|
+
} else {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
reason: "fetchQuota requires `accessToken` or `claudeConfigDir`",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (!token) {
|
|
155
|
+
return { ok: false, reason: "no OAuth token at .oauth-token" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 10_000);
|
|
160
|
+
|
|
161
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
162
|
+
let resp: Response;
|
|
163
|
+
try {
|
|
164
|
+
resp = await fetchFn("https://api.anthropic.com/v1/messages", {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: {
|
|
167
|
+
"anthropic-version": "2023-06-01",
|
|
168
|
+
"anthropic-beta": OAUTH_BETA,
|
|
169
|
+
"authorization": `Bearer ${token}`,
|
|
170
|
+
"x-app": "cli",
|
|
171
|
+
"user-agent": DEFAULT_USER_AGENT,
|
|
172
|
+
"content-type": "application/json",
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
model: opts.model ?? DEFAULT_PROBE_MODEL,
|
|
176
|
+
max_tokens: 1,
|
|
177
|
+
messages: [{ role: "user", content: "hi" }],
|
|
178
|
+
}),
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
});
|
|
181
|
+
} catch (err: unknown) {
|
|
182
|
+
const msg = (err as Error)?.message ?? String(err);
|
|
183
|
+
return { ok: false, reason: `request failed: ${msg}` };
|
|
184
|
+
} finally {
|
|
185
|
+
clearTimeout(timeout);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// We don't care whether the probe succeeded for message generation —
|
|
189
|
+
// Anthropic returns the rate-limit headers on both 2xx and rate-limited
|
|
190
|
+
// responses. Only bail if auth itself was rejected.
|
|
191
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
192
|
+
return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const parsed = parseQuotaHeaders(resp.headers);
|
|
196
|
+
if (!parsed.ok && resp.status >= 400) {
|
|
197
|
+
return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
|
|
198
|
+
}
|
|
199
|
+
return parsed;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Compact single-line representation for the session greeting.
|
|
204
|
+
* Example: "29% / 5h · 33% / 7d"
|
|
205
|
+
*/
|
|
206
|
+
export function formatQuotaLine(q: QuotaUtilization): string {
|
|
207
|
+
const fmt = (n: number) => `${Math.round(n)}%`;
|
|
208
|
+
return `${fmt(q.fiveHourUtilizationPct)} / 5h · ${fmt(q.sevenDayUtilizationPct)} / 7d`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Render a human-friendly "resets in …" countdown for a Date target.
|
|
213
|
+
* Exported so other surfaces (model-unavailable card, auth dashboard,
|
|
214
|
+
* banner helpers) speak the same dialect as `/usage`. Returns "—" for
|
|
215
|
+
* null targets and "resets now" once the target is in the past.
|
|
216
|
+
*/
|
|
217
|
+
export function formatResetRelative(target: Date | null, now: Date = new Date()): string {
|
|
218
|
+
if (!target) return "—";
|
|
219
|
+
const deltaMs = target.getTime() - now.getTime();
|
|
220
|
+
if (deltaMs <= 0) return "resets now";
|
|
221
|
+
const totalMin = Math.round(deltaMs / 60_000);
|
|
222
|
+
if (totalMin < 60) return `resets in ${totalMin}m`;
|
|
223
|
+
const hours = Math.floor(totalMin / 60);
|
|
224
|
+
const mins = totalMin % 60;
|
|
225
|
+
if (hours < 24) return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
|
|
226
|
+
const days = Math.floor(hours / 24);
|
|
227
|
+
const remH = hours % 24;
|
|
228
|
+
return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Multi-line Telegram HTML block for the /usage command. Shows both
|
|
233
|
+
* windows with their utilization percentages and reset countdowns,
|
|
234
|
+
* plus a representative-claim line if the server flagged one.
|
|
235
|
+
*/
|
|
236
|
+
export function formatQuotaBlock(q: QuotaUtilization, now: Date = new Date()): string {
|
|
237
|
+
const lines: string[] = [];
|
|
238
|
+
lines.push("<b>Claude plan quota</b>");
|
|
239
|
+
lines.push("");
|
|
240
|
+
lines.push(
|
|
241
|
+
`<b>5h window</b> ${Math.round(q.fiveHourUtilizationPct)}% · ${formatResetRelative(q.fiveHourResetAt, now)}`,
|
|
242
|
+
);
|
|
243
|
+
lines.push(
|
|
244
|
+
`<b>7d window</b> ${Math.round(q.sevenDayUtilizationPct)}% · ${formatResetRelative(q.sevenDayResetAt, now)}`,
|
|
245
|
+
);
|
|
246
|
+
if (q.representativeClaim) {
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push(`<i>Binding window: ${q.representativeClaim.replace(/_/g, " ")}</i>`);
|
|
249
|
+
}
|
|
250
|
+
if (q.overageStatus && q.overageStatus !== "allowed") {
|
|
251
|
+
const reason = q.overageDisabledReason ? ` (${q.overageDisabledReason})` : "";
|
|
252
|
+
lines.push(`<i>Overage: ${q.overageStatus}${reason}</i>`);
|
|
253
|
+
}
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* ── Account-level quota probe + short-lived cache ───────────────────── */
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Resolve an account's OAuth access token from
|
|
261
|
+
* `~/.switchroom/accounts/<label>/credentials.json` (new account model
|
|
262
|
+
* — see `reference/share-auth-across-the-fleet.md`). Returns null when
|
|
263
|
+
* the file is missing, malformed, or has no accessToken — caller
|
|
264
|
+
* surfaces a graceful "missing credentials" badge.
|
|
265
|
+
*
|
|
266
|
+
* Exported for unit testing; production callers go through
|
|
267
|
+
* {@link fetchAccountQuota}.
|
|
268
|
+
*/
|
|
269
|
+
export function readAccountAccessToken(
|
|
270
|
+
label: string,
|
|
271
|
+
home: string = (process.env.HOME ?? "/root"),
|
|
272
|
+
): string | null {
|
|
273
|
+
const credPath = join(home, ".switchroom", "accounts", label, "credentials.json");
|
|
274
|
+
if (!existsSync(credPath)) return null;
|
|
275
|
+
try {
|
|
276
|
+
const raw = readFileSync(credPath, "utf-8");
|
|
277
|
+
const parsed = JSON.parse(raw) as {
|
|
278
|
+
claudeAiOauth?: { accessToken?: string };
|
|
279
|
+
};
|
|
280
|
+
const token = parsed.claudeAiOauth?.accessToken?.trim();
|
|
281
|
+
return token && token.length > 0 ? token : null;
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Cache key per account label. The cached entry holds the result and
|
|
289
|
+
* the wall-clock timestamp it was fetched at, so the dashboard tap
|
|
290
|
+
* pattern (refresh-on-tap) doesn't trigger a fresh API call within the
|
|
291
|
+
* TTL window. Quota numbers don't move within a few seconds anyway.
|
|
292
|
+
*/
|
|
293
|
+
type AccountQuotaCacheEntry = {
|
|
294
|
+
fetchedAt: number;
|
|
295
|
+
result: QuotaResult;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/** TTL for the per-account quota cache — controls when
|
|
299
|
+
* `prefetchAccountQuotaIfStale` re-probes Anthropic and when
|
|
300
|
+
* `fetchAccountQuota`'s cache-bypass kicks in. 5 min: quota numbers
|
|
301
|
+
* don't move within a few minutes for human-scale usage; the
|
|
302
|
+
* prefetch fires on every dashboard render so the cache stays fresh
|
|
303
|
+
* whenever the operator interacts. The dashboard's sync read
|
|
304
|
+
* (`getCachedAccountQuota`) returns last-known data regardless of
|
|
305
|
+
* staleness — see that function's docstring for why. */
|
|
306
|
+
export const ACCOUNT_QUOTA_CACHE_TTL_MS = 5 * 60_000;
|
|
307
|
+
|
|
308
|
+
const accountQuotaCache = new Map<string, AccountQuotaCacheEntry>();
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Fetch quota for a global account by label. Wraps {@link fetchQuota}
|
|
312
|
+
* with token-resolution (`~/.switchroom/accounts/<label>/credentials.json`)
|
|
313
|
+
* and a short-lived in-process cache so repeat dashboard taps within
|
|
314
|
+
* the TTL don't re-hit the Anthropic API.
|
|
315
|
+
*
|
|
316
|
+
* Pass `force: true` to bypass the cache (used when the user
|
|
317
|
+
* explicitly taps "📊 Full quota" — they expect a live read).
|
|
318
|
+
*/
|
|
319
|
+
export async function fetchAccountQuota(
|
|
320
|
+
label: string,
|
|
321
|
+
opts: {
|
|
322
|
+
home?: string;
|
|
323
|
+
force?: boolean;
|
|
324
|
+
now?: () => number;
|
|
325
|
+
fetchImpl?: typeof fetch;
|
|
326
|
+
timeoutMs?: number;
|
|
327
|
+
} = {},
|
|
328
|
+
): Promise<QuotaResult> {
|
|
329
|
+
const now = opts.now?.() ?? Date.now();
|
|
330
|
+
if (!opts.force) {
|
|
331
|
+
const cached = accountQuotaCache.get(label);
|
|
332
|
+
if (cached && now - cached.fetchedAt < ACCOUNT_QUOTA_CACHE_TTL_MS) {
|
|
333
|
+
return cached.result;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const token = readAccountAccessToken(label, opts.home);
|
|
338
|
+
if (!token) {
|
|
339
|
+
const result: QuotaResult = {
|
|
340
|
+
ok: false,
|
|
341
|
+
reason: "no credentials.json or accessToken for account",
|
|
342
|
+
};
|
|
343
|
+
accountQuotaCache.set(label, { fetchedAt: now, result });
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const result = await fetchQuota({
|
|
348
|
+
accessToken: token,
|
|
349
|
+
fetchImpl: opts.fetchImpl,
|
|
350
|
+
timeoutMs: opts.timeoutMs,
|
|
351
|
+
});
|
|
352
|
+
accountQuotaCache.set(label, { fetchedAt: now, result });
|
|
353
|
+
// Persist the snapshot to disk so a future gateway restart can
|
|
354
|
+
// re-hydrate its in-process cache without an API call. Best-effort
|
|
355
|
+
// (write errors swallowed inside writeAccountQuota). Issue #708.
|
|
356
|
+
if (result.ok) {
|
|
357
|
+
try {
|
|
358
|
+
writeAccountQuota(
|
|
359
|
+
label,
|
|
360
|
+
snapshotFromQuotaUtilization(result.data, new Date(now)),
|
|
361
|
+
);
|
|
362
|
+
} catch {
|
|
363
|
+
/* best-effort */
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Re-hydrate the in-process account-quota cache from on-disk
|
|
371
|
+
* snapshots written by previous gateway lifetimes (issue #708).
|
|
372
|
+
* Called once at gateway boot so the boot card and the first /auth
|
|
373
|
+
* tap have data instantly — no need to wait for the background
|
|
374
|
+
* prefetch tick.
|
|
375
|
+
*
|
|
376
|
+
* Safe to call repeatedly: each label is set to the disk snapshot's
|
|
377
|
+
* `capturedAt` timestamp so a fresher live probe still wins on
|
|
378
|
+
* `now - fetchedAt < TTL` comparisons. When the disk snapshot is
|
|
379
|
+
* older than the TTL, the cache entry is still seeded — the background
|
|
380
|
+
* prefetch will replace it on the next tap.
|
|
381
|
+
*/
|
|
382
|
+
export function hydrateAccountQuotaCacheFromDisk(
|
|
383
|
+
labels: ReadonlyArray<string>,
|
|
384
|
+
home?: string,
|
|
385
|
+
): void {
|
|
386
|
+
for (const label of labels) {
|
|
387
|
+
if (accountQuotaCache.has(label)) continue;
|
|
388
|
+
const snap = readAccountQuota(label, home);
|
|
389
|
+
if (!snap) continue;
|
|
390
|
+
const fetchedAt = Date.parse(snap.capturedAt);
|
|
391
|
+
if (!Number.isFinite(fetchedAt)) continue;
|
|
392
|
+
const result: QuotaResult = {
|
|
393
|
+
ok: true,
|
|
394
|
+
data: {
|
|
395
|
+
fiveHourUtilizationPct: snap.fiveHourPct ?? 0,
|
|
396
|
+
sevenDayUtilizationPct: snap.sevenDayPct ?? 0,
|
|
397
|
+
fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
|
|
398
|
+
sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
|
|
399
|
+
representativeClaim: null,
|
|
400
|
+
overageStatus: null,
|
|
401
|
+
overageDisabledReason: null,
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
accountQuotaCache.set(label, { fetchedAt, result });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Test/utility helper — wipe the per-account quota cache. The
|
|
409
|
+
* gateway calls this on auth-account-level mutations (account add,
|
|
410
|
+
* account rm, account rename, refresh-accounts tick) so a stale
|
|
411
|
+
* pre-rename label doesn't survive into the dashboard. */
|
|
412
|
+
export function clearAccountQuotaCache(label?: string): void {
|
|
413
|
+
if (label == null) {
|
|
414
|
+
accountQuotaCache.clear();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
accountQuotaCache.delete(label);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Sync read of the account quota cache. Returns whatever's cached for
|
|
422
|
+
* this label — `null` only when there's NO entry at all. Stale-but-
|
|
423
|
+
* present cache entries are returned on purpose:
|
|
424
|
+
*
|
|
425
|
+
* - The dashboard renders sync; awaiting a fresh probe would block
|
|
426
|
+
* the user-visible message (and a probe can stall on Anthropic
|
|
427
|
+
* latency or network).
|
|
428
|
+
* - Showing yesterday's number is dramatically better UX than
|
|
429
|
+
* showing nothing — quota changes slowly enough that "the cached
|
|
430
|
+
* value" is almost always close to truth.
|
|
431
|
+
* - The background prefetch (`prefetchAccountQuotaIfStale`) keeps
|
|
432
|
+
* the cache fresh across renders. Within the 5-min TTL it
|
|
433
|
+
* no-ops; past the TTL it kicks off a fresh probe whose result
|
|
434
|
+
* is visible on the operator's NEXT render (refresh tap or
|
|
435
|
+
* auto-refresh after an action).
|
|
436
|
+
*
|
|
437
|
+
* Pre-v0.6.11 this function treated stale entries as a miss, which
|
|
438
|
+
* meant the boot-warmed cache vanished after 30s and the operator
|
|
439
|
+
* saw empty quota rows on the first /auth tap of any day after the
|
|
440
|
+
* gateway restart. That's the bug this docstring exists to keep
|
|
441
|
+
* fixed.
|
|
442
|
+
*/
|
|
443
|
+
export function getCachedAccountQuota(
|
|
444
|
+
_label: string,
|
|
445
|
+
_now: number = Date.now(),
|
|
446
|
+
): QuotaResult | null {
|
|
447
|
+
// Note the unused params — we keep the signature stable for callers
|
|
448
|
+
// that pass `now` (test helpers) even though we no longer use it.
|
|
449
|
+
const cached = accountQuotaCache.get(_label);
|
|
450
|
+
if (!cached) return null;
|
|
451
|
+
return cached.result;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Fire-and-forget background prefetch — kicks off
|
|
456
|
+
* `fetchAccountQuota` if the cache is cold/stale and discards the
|
|
457
|
+
* promise. Safe to call on every dashboard render: the cache TTL
|
|
458
|
+
* keeps the API call rate bounded to ~1 per account per 30s
|
|
459
|
+
* regardless of how many times the user taps /auth.
|
|
460
|
+
*
|
|
461
|
+
* Errors are swallowed (the next tap re-tries via the cache miss
|
|
462
|
+
* path); the dashboard's empty quota row is the user-visible
|
|
463
|
+
* "didn't probe yet" signal.
|
|
464
|
+
*/
|
|
465
|
+
export function prefetchAccountQuotaIfStale(
|
|
466
|
+
label: string,
|
|
467
|
+
opts: { home?: string; now?: () => number; fetchImpl?: typeof fetch } = {},
|
|
468
|
+
): void {
|
|
469
|
+
const now = opts.now?.() ?? Date.now();
|
|
470
|
+
const cached = accountQuotaCache.get(label);
|
|
471
|
+
if (cached && now - cached.fetchedAt < ACCOUNT_QUOTA_CACHE_TTL_MS) return;
|
|
472
|
+
// Don't await — background warm.
|
|
473
|
+
void fetchAccountQuota(label, opts).catch(() => {});
|
|
474
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound dedup window (#546).
|
|
3
|
+
*
|
|
4
|
+
* Closes the duplicate-reply class where:
|
|
5
|
+
*
|
|
6
|
+
* 1. Agent emits text that the gateway buffers as PTY-tail.
|
|
7
|
+
* 2. Bridge disconnects mid-flight (turn boundary, restart, etc.)
|
|
8
|
+
* before claude-code's reply / stream_reply tool_call lands.
|
|
9
|
+
* 3. Gateway's `turn-flush` backstop fires after a 500ms grace —
|
|
10
|
+
* sees no reply tool_call landed, sends the buffered text as
|
|
11
|
+
* its own message (HTML-rendered).
|
|
12
|
+
* 4. claude-code preserves the un-acked tool_call in its session
|
|
13
|
+
* and replays it on the next bridge connection. The replayed
|
|
14
|
+
* stream_reply lands as a SECOND message with the same content
|
|
15
|
+
* (raw markdown, since reply tools don't always render HTML).
|
|
16
|
+
*
|
|
17
|
+
* Smoking-gun evidence: klanker chat 8248703757, msgs 5025 + 5027,
|
|
18
|
+
* 11s apart. msg=5025 had `<b>...</b>` (turn-flush + markdownToHtml).
|
|
19
|
+
* msg=5027 had `**...**` (the raw markdown reply tool's payload).
|
|
20
|
+
* Same content, different formatting, two messages.
|
|
21
|
+
*
|
|
22
|
+
* Fix shape: maintain a small in-memory cache of "what we just sent"
|
|
23
|
+
* keyed by (chatId, threadId). Before any reply / stream_reply
|
|
24
|
+
* actually sends, check whether a recent outbound matches the
|
|
25
|
+
* normalised content. If so, skip the send and return a successful
|
|
26
|
+
* tool-call result so claude-code's retry loop closes cleanly.
|
|
27
|
+
*
|
|
28
|
+
* This is a SAFETY NET layer. The cleaner architectural fix would be
|
|
29
|
+
* "don't fire turn-flush when claude-code might retry" — but that
|
|
30
|
+
* needs reliable detection of the retry intent ahead of time, which
|
|
31
|
+
* we don't have. Dedup-after-the-fact is robust against the full
|
|
32
|
+
* range of "two paths, same content" failure modes.
|
|
33
|
+
*
|
|
34
|
+
* Pure module: no I/O, no globals, no clock reads beyond the caller-
|
|
35
|
+
* supplied `now`. Fully unit-testable.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** TTL after which a recorded outbound is forgotten. 60s catches the
|
|
39
|
+
* typical retry window (we've seen 9-11s in the wild) with margin
|
|
40
|
+
* for slower networks and avoids deduping legitimate later replies
|
|
41
|
+
* that happen to repeat content. */
|
|
42
|
+
export const DEFAULT_DEDUP_TTL_MS = 60_000
|
|
43
|
+
|
|
44
|
+
/** Minimum content length below which we don't bother deduping.
|
|
45
|
+
* Short replies (<= 24 chars) like "ok", "got it", "✅" frequently
|
|
46
|
+
* recur within seconds in normal multi-turn conversation; deduping
|
|
47
|
+
* them would suppress legitimate repeats. The bug class we're
|
|
48
|
+
* defending against involves multi-paragraph content, so the floor
|
|
49
|
+
* is conservative. */
|
|
50
|
+
export const DEDUP_MIN_CONTENT_LEN = 24
|
|
51
|
+
|
|
52
|
+
interface DedupEntry {
|
|
53
|
+
/** Normalized content hash (see `normalizeForDedup`). */
|
|
54
|
+
hash: string
|
|
55
|
+
/** Wall-clock ms when recorded. */
|
|
56
|
+
ts: number
|
|
57
|
+
/** First 80 chars of the original (un-normalized) text — for
|
|
58
|
+
* operator-facing log lines that show what got deduped. */
|
|
59
|
+
preview: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* In-memory dedup cache, keyed by `chatId|threadId`. Bounded by
|
|
64
|
+
* TTL eviction on every read; we don't cap entries because chat
|
|
65
|
+
* count per gateway is small (one per active conversation).
|
|
66
|
+
*/
|
|
67
|
+
export class OutboundDedupCache {
|
|
68
|
+
private readonly entries = new Map<string, DedupEntry[]>()
|
|
69
|
+
private readonly ttlMs: number
|
|
70
|
+
|
|
71
|
+
constructor(opts: { ttlMs?: number } = {}) {
|
|
72
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_DEDUP_TTL_MS
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Record an outbound message. Caller should invoke this after a
|
|
76
|
+
* successful send, regardless of which path sent it (turn-flush,
|
|
77
|
+
* executeReply, executeStreamReply, etc.). Short content is not
|
|
78
|
+
* recorded — see DEDUP_MIN_CONTENT_LEN. */
|
|
79
|
+
record(chatId: string, threadId: number | undefined, text: string, now: number): void {
|
|
80
|
+
if (text.length < DEDUP_MIN_CONTENT_LEN) return
|
|
81
|
+
const key = makeKey(chatId, threadId)
|
|
82
|
+
const list = this.entries.get(key) ?? []
|
|
83
|
+
this.evict(list, now)
|
|
84
|
+
list.push({
|
|
85
|
+
hash: normalizeForDedup(text),
|
|
86
|
+
ts: now,
|
|
87
|
+
preview: text.slice(0, 80),
|
|
88
|
+
})
|
|
89
|
+
this.entries.set(key, list)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Check whether the given text was already sent recently to the
|
|
93
|
+
* same chat. Returns the matched entry's preview + age on hit, or
|
|
94
|
+
* null on miss. Caller decides what to do with the answer
|
|
95
|
+
* (skip-send, log, etc.). */
|
|
96
|
+
check(
|
|
97
|
+
chatId: string,
|
|
98
|
+
threadId: number | undefined,
|
|
99
|
+
text: string,
|
|
100
|
+
now: number,
|
|
101
|
+
): { matched: true; preview: string; ageMs: number } | null {
|
|
102
|
+
if (text.length < DEDUP_MIN_CONTENT_LEN) return null
|
|
103
|
+
const key = makeKey(chatId, threadId)
|
|
104
|
+
const list = this.entries.get(key)
|
|
105
|
+
if (!list) return null
|
|
106
|
+
this.evict(list, now)
|
|
107
|
+
const candidateHash = normalizeForDedup(text)
|
|
108
|
+
for (const entry of list) {
|
|
109
|
+
if (entry.hash === candidateHash) {
|
|
110
|
+
return { matched: true, preview: entry.preview, ageMs: now - entry.ts }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Test-only: clear all entries. */
|
|
117
|
+
clear(): void {
|
|
118
|
+
this.entries.clear()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Test-only: count of live entries (post-eviction). */
|
|
122
|
+
size(now: number): number {
|
|
123
|
+
let total = 0
|
|
124
|
+
for (const list of this.entries.values()) {
|
|
125
|
+
this.evict(list, now)
|
|
126
|
+
total += list.length
|
|
127
|
+
}
|
|
128
|
+
return total
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private evict(list: DedupEntry[], now: number): void {
|
|
132
|
+
const cutoff = now - this.ttlMs
|
|
133
|
+
let i = 0
|
|
134
|
+
while (i < list.length && list[i].ts < cutoff) i++
|
|
135
|
+
if (i > 0) list.splice(0, i)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function makeKey(chatId: string, threadId: number | undefined): string {
|
|
140
|
+
return threadId == null ? chatId : `${chatId}|${threadId}`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Normalise text for content equality. The bug we're defending
|
|
145
|
+
* against produces the SAME content rendered two different ways:
|
|
146
|
+
* one path runs `markdownToHtml` (so `**foo**` becomes `<b>foo</b>`),
|
|
147
|
+
* the other doesn't. Both must hash identically.
|
|
148
|
+
*
|
|
149
|
+
* Steps:
|
|
150
|
+
* 1. Strip HTML tags (`<b>foo</b>` → `foo`).
|
|
151
|
+
* 2. Strip markdown markers (`**foo**` / `__foo__` / `` `foo` `` → `foo`).
|
|
152
|
+
* 3. Collapse all whitespace to single space + trim.
|
|
153
|
+
* 4. Lowercase (defensive — both renderers preserve case but a
|
|
154
|
+
* future formatter might title-case headings, etc.).
|
|
155
|
+
*
|
|
156
|
+
* NOT a hash function in the cryptographic sense — just a
|
|
157
|
+
* normalised-string comparison key. Identical content + identical
|
|
158
|
+
* normaliser → identical key.
|
|
159
|
+
*/
|
|
160
|
+
export function normalizeForDedup(text: string): string {
|
|
161
|
+
return text
|
|
162
|
+
.replace(/<\/?[a-zA-Z][^>]*>/g, '') // HTML tags
|
|
163
|
+
.replace(/&[a-zA-Z]+;|&#\d+;/g, ' ') // HTML entities → space
|
|
164
|
+
.replace(/(\*\*|__|`)+/g, '') // markdown bold/italic/code markers
|
|
165
|
+
.replace(/^[#>\-*+]\s+/gm, '') // markdown line prefixes
|
|
166
|
+
.replace(/\s+/g, ' ')
|
|
167
|
+
.trim()
|
|
168
|
+
.toLowerCase()
|
|
169
|
+
}
|