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,1130 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Foreman — always-on admin bot for the switchroom fleet.
|
|
4
|
+
*
|
|
5
|
+
* Unlike per-agent gateways, the foreman is not bound to a single agent.
|
|
6
|
+
* It provides fleet-wide read-only and write visibility (Phase 3a + 3b).
|
|
7
|
+
*
|
|
8
|
+
* Configuration:
|
|
9
|
+
* ~/.switchroom/foreman/.env TELEGRAM_BOT_TOKEN=<token>
|
|
10
|
+
* ~/.switchroom/foreman/access.json { "allowFrom": ["<userId>"] }
|
|
11
|
+
*
|
|
12
|
+
* Phase 3a commands (read-only):
|
|
13
|
+
* /start, /help — greeting + command list
|
|
14
|
+
* /status, /list — fleet summary via `switchroom agent list --json`
|
|
15
|
+
* /logs <agent> [--tail N] — journalctl output, paginated > 3 KB
|
|
16
|
+
* /auth [agent] — fleet auth dashboard (per-agent, agent-name-parametric)
|
|
17
|
+
*
|
|
18
|
+
* Phase 3b commands (write):
|
|
19
|
+
* /restart <agent> — systemctl --user restart switchroom-<agent>
|
|
20
|
+
* /delete <agent> — 2-step confirm → archive dir + destroy unit
|
|
21
|
+
* /update — switchroom update (paginated output)
|
|
22
|
+
* /create-agent [name] — multi-turn flow: profile → bot token → OAuth
|
|
23
|
+
* /setup [slug] — guided new-agent wizard (slug → persona → model → emoji → token → allowlist → start)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { Bot, InlineKeyboard, type Context } from 'grammy'
|
|
27
|
+
import { readFileSync, writeFileSync, chmodSync, mkdirSync } from 'fs'
|
|
28
|
+
import { homedir } from 'os'
|
|
29
|
+
import { join, resolve } from 'path'
|
|
30
|
+
import { listWorktrees } from '../../src/worktree/list.js'
|
|
31
|
+
import { installPluginLogger } from '../plugin-logger.js'
|
|
32
|
+
import {
|
|
33
|
+
escapeHtmlForTg,
|
|
34
|
+
installTgPostLogger,
|
|
35
|
+
isAllowedSender,
|
|
36
|
+
makeSwitchroomExec,
|
|
37
|
+
makeSwitchroomExecCombined,
|
|
38
|
+
makeSwitchroomExecJson,
|
|
39
|
+
makeSwitchroomReply,
|
|
40
|
+
runPollingLoop,
|
|
41
|
+
} from '../shared/bot-runtime.js'
|
|
42
|
+
import {
|
|
43
|
+
assertSafeAgentName,
|
|
44
|
+
buildFleetSummary,
|
|
45
|
+
handleLogsCommand,
|
|
46
|
+
handleRestartCommand,
|
|
47
|
+
handleDeleteCommand,
|
|
48
|
+
executeDeleteAgent,
|
|
49
|
+
handleUpdateCommand,
|
|
50
|
+
handleVersionCommand,
|
|
51
|
+
} from './foreman-handlers.js'
|
|
52
|
+
import {
|
|
53
|
+
buildDashboard,
|
|
54
|
+
isQuotaHot,
|
|
55
|
+
type DashboardState,
|
|
56
|
+
type DashboardSlot,
|
|
57
|
+
type SlotHealth,
|
|
58
|
+
} from '../auth-dashboard.js'
|
|
59
|
+
import { parseAuthSubCommand } from '../auth-slot-parser.js'
|
|
60
|
+
import {
|
|
61
|
+
getState,
|
|
62
|
+
setState,
|
|
63
|
+
clearState,
|
|
64
|
+
listActiveFlows,
|
|
65
|
+
} from './state.js'
|
|
66
|
+
import {
|
|
67
|
+
startCreateFlow,
|
|
68
|
+
handleFlowText,
|
|
69
|
+
makeInitialState,
|
|
70
|
+
advanceState,
|
|
71
|
+
stepLabel,
|
|
72
|
+
} from './foreman-create-flow.js'
|
|
73
|
+
import { listAvailableProfiles } from '../../src/agents/profiles.js'
|
|
74
|
+
import { createAgent, completeCreation } from '../../src/agents/create-orchestrator.js'
|
|
75
|
+
import { validateBotToken } from '../../src/setup/telegram-api.js'
|
|
76
|
+
import { resolveAgentsDir, loadConfig } from '../../src/config/loader.js'
|
|
77
|
+
import {
|
|
78
|
+
getSetupState,
|
|
79
|
+
setSetupState,
|
|
80
|
+
clearSetupState,
|
|
81
|
+
listActiveSetupFlows,
|
|
82
|
+
} from './setup-state.js'
|
|
83
|
+
import {
|
|
84
|
+
startSetupFlow,
|
|
85
|
+
handleSetupText,
|
|
86
|
+
makeSetupInitialState,
|
|
87
|
+
advanceSetupState,
|
|
88
|
+
setupStepLabel,
|
|
89
|
+
} from './setup-flow.js'
|
|
90
|
+
|
|
91
|
+
// ─── Stderr logging ───────────────────────────────────────────────────────
|
|
92
|
+
installPluginLogger()
|
|
93
|
+
|
|
94
|
+
// ─── Config dir ───────────────────────────────────────────────────────────
|
|
95
|
+
const FOREMAN_DIR = process.env.SWITCHROOM_FOREMAN_DIR
|
|
96
|
+
?? join(homedir(), '.switchroom', 'foreman')
|
|
97
|
+
const ENV_FILE = join(FOREMAN_DIR, '.env')
|
|
98
|
+
const ACCESS_FILE = join(FOREMAN_DIR, 'access.json')
|
|
99
|
+
|
|
100
|
+
// ─── Load .env ────────────────────────────────────────────────────────────
|
|
101
|
+
try {
|
|
102
|
+
chmodSync(ENV_FILE, 0o600)
|
|
103
|
+
for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) {
|
|
104
|
+
const m = line.match(/^(\w+)=(.*)$/)
|
|
105
|
+
if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2]
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const code = (err as NodeJS.ErrnoException)?.code
|
|
109
|
+
if (code !== 'ENOENT') {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`foreman: warning — failed to load ${ENV_FILE}: ${(err as Error).message}\n`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── Bot token ────────────────────────────────────────────────────────────
|
|
117
|
+
const TOKEN = process.env.TELEGRAM_BOT_TOKEN
|
|
118
|
+
if (!TOKEN) {
|
|
119
|
+
process.stderr.write(
|
|
120
|
+
`foreman: TELEGRAM_BOT_TOKEN required\n` +
|
|
121
|
+
` set in ${ENV_FILE}\n` +
|
|
122
|
+
` format: TELEGRAM_BOT_TOKEN=123456789:AAH...\n`,
|
|
123
|
+
)
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Access list ──────────────────────────────────────────────────────────
|
|
128
|
+
function loadAllowFrom(): string[] {
|
|
129
|
+
try {
|
|
130
|
+
const raw = JSON.parse(readFileSync(ACCESS_FILE, 'utf8')) as { allowFrom?: unknown }
|
|
131
|
+
if (Array.isArray(raw.allowFrom)) {
|
|
132
|
+
return (raw.allowFrom as unknown[]).map(String)
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
/* fall through — return empty */
|
|
136
|
+
}
|
|
137
|
+
return []
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── CLI exec helpers ─────────────────────────────────────────────────────
|
|
141
|
+
const switchroomExec = makeSwitchroomExec()
|
|
142
|
+
const switchroomExecCombined = makeSwitchroomExecCombined()
|
|
143
|
+
const switchroomExecJson = makeSwitchroomExecJson()
|
|
144
|
+
|
|
145
|
+
// ─── Bot ──────────────────────────────────────────────────────────────────
|
|
146
|
+
const bot = new Bot(TOKEN)
|
|
147
|
+
installTgPostLogger(bot)
|
|
148
|
+
|
|
149
|
+
// No forum-topic routing in foreman — it's always a DM.
|
|
150
|
+
const switchroomReply = makeSwitchroomReply(() => undefined)
|
|
151
|
+
|
|
152
|
+
// ─── Auth guard middleware ────────────────────────────────────────────────
|
|
153
|
+
bot.use(async (ctx, next) => {
|
|
154
|
+
// Silently ignore any message that is not a private DM.
|
|
155
|
+
// If the foreman bot is ever added to a group, this prevents fleet info
|
|
156
|
+
// from leaking to all group members even when the sender is allowlisted.
|
|
157
|
+
if (ctx.chat?.type !== 'private') return
|
|
158
|
+
if (!ctx.from) return
|
|
159
|
+
const allowFrom = loadAllowFrom()
|
|
160
|
+
if (!isAllowedSender(ctx, allowFrom)) {
|
|
161
|
+
process.stderr.write(`foreman: rejected message from user ${ctx.from.id}\n`)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
await next()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/** Fetch auth dashboard state for a named agent. */
|
|
170
|
+
function fetchForemanDashboardState(agent: string): DashboardState | null {
|
|
171
|
+
type SlotListing = {
|
|
172
|
+
slots: Array<{
|
|
173
|
+
slot: string; active: boolean; health: string;
|
|
174
|
+
quota_exhausted_until?: number | null;
|
|
175
|
+
}>
|
|
176
|
+
}
|
|
177
|
+
let slots: DashboardSlot[] = []
|
|
178
|
+
try {
|
|
179
|
+
const listing = switchroomExecJson<SlotListing>(['auth', 'list', agent, '--json'])
|
|
180
|
+
if (listing && Array.isArray(listing.slots)) {
|
|
181
|
+
slots = listing.slots.map(s => ({
|
|
182
|
+
slot: s.slot,
|
|
183
|
+
active: s.active,
|
|
184
|
+
health: (s.health as SlotHealth) ?? 'missing',
|
|
185
|
+
quotaExhaustedUntil: s.quota_exhausted_until ?? null,
|
|
186
|
+
fiveHourPct: null,
|
|
187
|
+
sevenDayPct: null,
|
|
188
|
+
}))
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let plan: string | null = null
|
|
195
|
+
let rateLimitTier: string | null = null
|
|
196
|
+
try {
|
|
197
|
+
type AuthStatusResp = {
|
|
198
|
+
agents: Array<{ name: string; subscription_type: string | null; rate_limit_tier?: string | null }>
|
|
199
|
+
}
|
|
200
|
+
const statusData = switchroomExecJson<AuthStatusResp>(['auth', 'status'])
|
|
201
|
+
const thisAgent = statusData?.agents?.find(a => a.name === agent)
|
|
202
|
+
if (thisAgent?.subscription_type) plan = thisAgent.subscription_type
|
|
203
|
+
if (thisAgent?.rate_limit_tier) rateLimitTier = thisAgent.rate_limit_tier
|
|
204
|
+
} catch { /* best-effort */ }
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
agent,
|
|
208
|
+
bankId: agent,
|
|
209
|
+
plan,
|
|
210
|
+
rateLimitTier,
|
|
211
|
+
slots,
|
|
212
|
+
quotaHot: isQuotaHot(slots),
|
|
213
|
+
generatedAt: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── /start ──────────────────────────────────────────────────────────────
|
|
218
|
+
bot.command('start', async ctx => {
|
|
219
|
+
await switchroomReply(ctx, [
|
|
220
|
+
'<b>Foreman — switchroom fleet admin</b>',
|
|
221
|
+
'',
|
|
222
|
+
'Read-only commands:',
|
|
223
|
+
' /status, /list — fleet summary',
|
|
224
|
+
' /logs <agent> [--tail N] — last N log lines (default 50)',
|
|
225
|
+
' /auth [agent] — auth dashboard',
|
|
226
|
+
' /version — show versions + running agent health',
|
|
227
|
+
'',
|
|
228
|
+
'Write commands:',
|
|
229
|
+
' /restart <agent> — restart an agent',
|
|
230
|
+
' /delete <agent> — delete an agent (2-step confirm)',
|
|
231
|
+
' /update — update switchroom',
|
|
232
|
+
' /setup [slug] — guided new-agent wizard',
|
|
233
|
+
' /create-agent [name] — create a new agent (legacy multi-turn)',
|
|
234
|
+
].join('\n'), { html: true })
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// ─── /help ───────────────────────────────────────────────────────────────
|
|
238
|
+
bot.command('help', async ctx => {
|
|
239
|
+
await switchroomReply(ctx, [
|
|
240
|
+
'<b>Foreman commands</b>',
|
|
241
|
+
'',
|
|
242
|
+
'<b>Fleet info:</b>',
|
|
243
|
+
'/status, /list — show fleet status',
|
|
244
|
+
'/logs <agent> [--tail N] — show agent journal logs',
|
|
245
|
+
'/auth [agent] — auth slot dashboard for an agent',
|
|
246
|
+
'',
|
|
247
|
+
'<b>Fleet management:</b>',
|
|
248
|
+
'/restart <agent> — restart an agent via systemctl',
|
|
249
|
+
'/delete <agent> — delete agent (confirms, then archives dir)',
|
|
250
|
+
'/update — pull latest switchroom + reconcile agents',
|
|
251
|
+
'/setup [slug] — guided wizard: slug → persona → model → emoji → token → start',
|
|
252
|
+
'/create-agent [name] — legacy interactive new-agent wizard',
|
|
253
|
+
'',
|
|
254
|
+
'<b>Examples:</b>',
|
|
255
|
+
'<code>/logs gymbro --tail 100</code>',
|
|
256
|
+
'<code>/restart gymbro</code>',
|
|
257
|
+
'<code>/setup gymbro</code>',
|
|
258
|
+
].join('\n'), { html: true })
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// ─── /status + /list ──────────────────────────────────────────────────────
|
|
262
|
+
bot.command(['status', 'list'], async ctx => {
|
|
263
|
+
const summary = buildFleetSummary(switchroomExecJson)
|
|
264
|
+
await switchroomReply(ctx, summary, { html: true })
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// ─── /logs ───────────────────────────────────────────────────────────────
|
|
268
|
+
bot.command('logs', async ctx => {
|
|
269
|
+
const result = handleLogsCommand((ctx.match ?? '') as string)
|
|
270
|
+
for (const reply of result.replies) {
|
|
271
|
+
await switchroomReply(ctx, reply.text, { html: reply.html })
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// ─── /auth ────────────────────────────────────────────────────────────────
|
|
276
|
+
bot.command('auth', async ctx => {
|
|
277
|
+
const rawArgs = ((ctx.match ?? '') as string).trim()
|
|
278
|
+
|
|
279
|
+
// Determine which agents to show
|
|
280
|
+
let agentNames: string[]
|
|
281
|
+
|
|
282
|
+
if (rawArgs) {
|
|
283
|
+
// User specified an agent name. parseAuthSubCommand needs the
|
|
284
|
+
// arguments split into parts and a fallback agent name. The 'status'
|
|
285
|
+
// intent kind has no `agent` field — narrow before reading it.
|
|
286
|
+
const parts = rawArgs.split(/\s+/)
|
|
287
|
+
const parsed = parseAuthSubCommand(parts, parts[0] ?? '')
|
|
288
|
+
const agentArg = ('agent' in parsed ? parsed.agent : undefined) || parts[0] || ''
|
|
289
|
+
try { assertSafeAgentName(agentArg) } catch {
|
|
290
|
+
await switchroomReply(ctx, 'Invalid agent name.', { html: true })
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
agentNames = [agentArg]
|
|
294
|
+
} else {
|
|
295
|
+
// Enumerate all agents
|
|
296
|
+
try {
|
|
297
|
+
const data = switchroomExecJson<{ agents: Array<{ name: string }> }>(['agent', 'list'])
|
|
298
|
+
agentNames = data?.agents?.map(a => a.name) ?? []
|
|
299
|
+
} catch {
|
|
300
|
+
agentNames = []
|
|
301
|
+
}
|
|
302
|
+
if (agentNames.length === 0) {
|
|
303
|
+
await switchroomReply(ctx, '<i>No agents found. Try <code>/auth <agentname></code>.</i>', { html: true })
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Render dashboard per agent
|
|
309
|
+
for (const agent of agentNames) {
|
|
310
|
+
const state = fetchForemanDashboardState(agent)
|
|
311
|
+
if (!state) {
|
|
312
|
+
await switchroomReply(ctx,
|
|
313
|
+
`<b>/auth ${escapeHtmlForTg(agent)}</b> — no data (agent missing or CLI unreachable)`,
|
|
314
|
+
{ html: true },
|
|
315
|
+
)
|
|
316
|
+
continue
|
|
317
|
+
}
|
|
318
|
+
const { text, keyboard } = buildDashboard(state)
|
|
319
|
+
await ctx.reply(text, { parse_mode: 'HTML', reply_markup: keyboard, link_preview_options: { is_disabled: true } })
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// ─── /restart ─────────────────────────────────────────────────────────────
|
|
324
|
+
bot.command('restart', async ctx => {
|
|
325
|
+
const result = handleRestartCommand((ctx.match ?? '') as string)
|
|
326
|
+
await switchroomReply(ctx, result.text, { html: result.html })
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
// ─── /delete ──────────────────────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* In-memory map of chatId → pending delete agent name.
|
|
333
|
+
* Cleared when the user confirms or the conversation moves on.
|
|
334
|
+
* For lightweight 2-step confirm — no SQLite needed since this is ephemeral.
|
|
335
|
+
*/
|
|
336
|
+
const pendingDeletes = new Map<string, string>()
|
|
337
|
+
|
|
338
|
+
bot.command('delete', async ctx => {
|
|
339
|
+
const result = handleDeleteCommand((ctx.match ?? '') as string)
|
|
340
|
+
for (const reply of result.replies) {
|
|
341
|
+
await switchroomReply(ctx, reply.text, { html: reply.html })
|
|
342
|
+
}
|
|
343
|
+
if (result.needsConfirm && result.agentForConfirm) {
|
|
344
|
+
const chatId = String(ctx.chat!.id)
|
|
345
|
+
pendingDeletes.set(chatId, result.agentForConfirm)
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
// ─── /update ──────────────────────────────────────────────────────────────
|
|
350
|
+
bot.command('update', async ctx => {
|
|
351
|
+
await switchroomReply(ctx, 'Running <code>switchroom update</code>…', { html: true })
|
|
352
|
+
const result = handleUpdateCommand(switchroomExecCombined)
|
|
353
|
+
for (const reply of result.replies) {
|
|
354
|
+
await switchroomReply(ctx, reply.text, { html: reply.html })
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// ─── /version ─────────────────────────────────────────────────────────────
|
|
359
|
+
bot.command('version', async ctx => {
|
|
360
|
+
const result = handleVersionCommand(switchroomExecCombined)
|
|
361
|
+
for (const reply of result.replies) {
|
|
362
|
+
await switchroomReply(ctx, reply.text, { html: reply.html })
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// ─── /worktrees ───────────────────────────────────────────────────────────
|
|
367
|
+
bot.command('worktrees', async ctx => {
|
|
368
|
+
try {
|
|
369
|
+
const { worktrees } = listWorktrees()
|
|
370
|
+
if (worktrees.length === 0) {
|
|
371
|
+
await switchroomReply(ctx, 'No active worktrees.', { html: false })
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
const lines = ['<b>Active worktrees</b>', '']
|
|
375
|
+
for (const w of worktrees) {
|
|
376
|
+
const ageMin = Math.floor(w.ageSeconds / 60)
|
|
377
|
+
const hbMin = Math.floor(w.heartbeatAgeSeconds / 60)
|
|
378
|
+
const owner = w.ownerAgent ? ` (${escapeHtmlForTg(w.ownerAgent)})` : ''
|
|
379
|
+
lines.push(
|
|
380
|
+
`• <code>${escapeHtmlForTg(w.repoName)}</code>${owner} — branch <code>${escapeHtmlForTg(w.branch)}</code>, age ${ageMin}m, hb ${hbMin}m`,
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
await switchroomReply(ctx, lines.join('\n'), { html: true })
|
|
384
|
+
} catch (err) {
|
|
385
|
+
await switchroomReply(ctx, `<b>worktrees failed:</b> ${escapeHtmlForTg((err as Error).message)}`, { html: true })
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// ─── /setup ───────────────────────────────────────────────────────────────
|
|
390
|
+
//
|
|
391
|
+
// Guided wizard: slug → persona name → model → emoji → bot token → allowlist
|
|
392
|
+
// confirmation → reconcile (createAgent) + start.
|
|
393
|
+
//
|
|
394
|
+
// Deferral notes:
|
|
395
|
+
// // TODO(#188): BotFather auto-flow — currently user creates bot manually
|
|
396
|
+
// // TODO(#189): OAuth code paste step — currently shows manual terminal instruction
|
|
397
|
+
// // TODO(#190): Skills selector — currently shows placeholder message
|
|
398
|
+
|
|
399
|
+
bot.command(['setup', 'createagent'], async ctx => {
|
|
400
|
+
const chatId = String(ctx.chat!.id)
|
|
401
|
+
const inlineSlug = ((ctx.match ?? '') as string).trim().split(/\s+/)[0] || null
|
|
402
|
+
|
|
403
|
+
// If there's already an active setup flow, remind the user
|
|
404
|
+
const existing = getSetupState(chatId)
|
|
405
|
+
if (existing && existing.step !== 'done') {
|
|
406
|
+
await switchroomReply(ctx, [
|
|
407
|
+
`A setup wizard is already in progress for <b>${escapeHtmlForTg(existing.slug ?? '?')}</b> (${setupStepLabel(existing.step)}).`,
|
|
408
|
+
'',
|
|
409
|
+
'Continue by sending your answer, or type <code>cancel</code> to abort.',
|
|
410
|
+
].join('\n'), { html: true })
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const action = startSetupFlow(inlineSlug)
|
|
415
|
+
|
|
416
|
+
if (action.kind === 'error') {
|
|
417
|
+
await switchroomReply(ctx, action.message, { html: true })
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (action.kind === 'ask-slug') {
|
|
422
|
+
const state = makeSetupInitialState(chatId, null)
|
|
423
|
+
setSetupState(state)
|
|
424
|
+
await switchroomReply(ctx, [
|
|
425
|
+
'<b>New agent wizard</b>',
|
|
426
|
+
'',
|
|
427
|
+
'Step 1/5: What slug (short name) should this agent use?',
|
|
428
|
+
'<i>e.g. <code>gymbro</code> — lowercase, hyphens/underscores OK, max 51 chars</i>',
|
|
429
|
+
'',
|
|
430
|
+
'Type <code>cancel</code> at any time to abort.',
|
|
431
|
+
].join('\n'), { html: true })
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (action.kind === 'ask-persona') {
|
|
436
|
+
const state = makeSetupInitialState(chatId, inlineSlug)
|
|
437
|
+
setSetupState(state)
|
|
438
|
+
await switchroomReply(ctx, [
|
|
439
|
+
`<b>New agent wizard</b> — slug: <code>${escapeHtmlForTg(inlineSlug!)}</code>`,
|
|
440
|
+
'',
|
|
441
|
+
'Step 2/5: What should this agent\'s persona name be?',
|
|
442
|
+
'<i>e.g. <code>Gym Bro</code> — displayed in greetings and topics</i>',
|
|
443
|
+
].join('\n'), { html: true })
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
// ─── /cancel (setup wizard abort) ────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
bot.command('cancel', async ctx => {
|
|
451
|
+
const chatId = String(ctx.chat!.id)
|
|
452
|
+
const setupState = getSetupState(chatId)
|
|
453
|
+
if (setupState && setupState.step !== 'done') {
|
|
454
|
+
clearSetupState(chatId)
|
|
455
|
+
await switchroomReply(ctx, 'Setup wizard cancelled. Type /setup to start a new one.', { html: false })
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
// No active setup flow — check create-agent flow
|
|
459
|
+
const createState = getState(chatId)
|
|
460
|
+
if (createState && createState.step !== 'done') {
|
|
461
|
+
clearState(chatId)
|
|
462
|
+
await switchroomReply(ctx, 'Create-agent flow cancelled.', { html: false })
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
await switchroomReply(ctx, 'No active wizard to cancel.', { html: false })
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// ─── /create-agent ────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
bot.command('create_agent', async ctx => {
|
|
471
|
+
await handleCreateAgentCommand(ctx, (ctx.match ?? '') as string)
|
|
472
|
+
})
|
|
473
|
+
// Also register with hyphen (Telegram normalises _ and - differently per client)
|
|
474
|
+
bot.command('create-agent', async ctx => {
|
|
475
|
+
await handleCreateAgentCommand(ctx, (ctx.match ?? '') as string)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
async function handleCreateAgentCommand(ctx: Context, match: string): Promise<void> {
|
|
479
|
+
const chatId = String(ctx.chat!.id)
|
|
480
|
+
const inlineName = match.trim().split(/\s+/)[0] || null
|
|
481
|
+
|
|
482
|
+
let profiles: string[]
|
|
483
|
+
try {
|
|
484
|
+
profiles = listAvailableProfiles()
|
|
485
|
+
} catch {
|
|
486
|
+
await switchroomReply(ctx, 'Could not load profiles. Is switchroom installed correctly?', { html: false })
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const action = startCreateFlow(inlineName, profiles)
|
|
491
|
+
|
|
492
|
+
if (action.kind === 'error') {
|
|
493
|
+
await switchroomReply(ctx, action.message, { html: true })
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (action.kind === 'ask-name') {
|
|
498
|
+
const state = makeInitialState(chatId, null)
|
|
499
|
+
setState(state)
|
|
500
|
+
await switchroomReply(ctx, 'What should the new agent be named? (lowercase, hyphens/underscores OK)', { html: false })
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (action.kind === 'ask-profile') {
|
|
505
|
+
const state = makeInitialState(chatId, inlineName)
|
|
506
|
+
setState(state)
|
|
507
|
+
const kb = new InlineKeyboard()
|
|
508
|
+
for (const p of profiles) {
|
|
509
|
+
kb.text(p, `cf:profile:${p}`).row()
|
|
510
|
+
}
|
|
511
|
+
await ctx.reply(
|
|
512
|
+
`Choose a profile for <b>${escapeHtmlForTg(inlineName!)}</b>:`,
|
|
513
|
+
{ parse_mode: 'HTML', reply_markup: kb },
|
|
514
|
+
)
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ─── Create-agent: callback_query for profile selection ───────────────────
|
|
520
|
+
|
|
521
|
+
bot.on('callback_query:data', async ctx => {
|
|
522
|
+
// Defense-in-depth: the global bot.use middleware already fires a
|
|
523
|
+
// `ctx.chat?.type !== 'private'` check, but callback_query updates from
|
|
524
|
+
// inline messages can arrive without a ctx.chat (callback_query.message
|
|
525
|
+
// is populated but ctx.chat may be undefined in edge cases). The global
|
|
526
|
+
// guard does `undefined !== 'private'` = true = ALLOW, so re-check here
|
|
527
|
+
// explicitly. If this isn't a private chat, silently drop.
|
|
528
|
+
if (ctx.chat?.type !== 'private') {
|
|
529
|
+
await ctx.answerCallbackQuery().catch(() => {})
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
const data = ctx.callbackQuery.data
|
|
533
|
+
const chatId = String(ctx.chat?.id ?? ctx.callbackQuery.from.id)
|
|
534
|
+
|
|
535
|
+
if (data.startsWith('cf:profile:')) {
|
|
536
|
+
const profile = data.slice('cf:profile:'.length)
|
|
537
|
+
await ctx.answerCallbackQuery()
|
|
538
|
+
|
|
539
|
+
const state = getState(chatId)
|
|
540
|
+
if (!state || (state.step !== 'asked-name' && state.step !== 'asked-profile')) {
|
|
541
|
+
await ctx.reply('No active create-agent flow. Use /create-agent to start.')
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const profiles = listAvailableProfiles()
|
|
546
|
+
if (!profiles.includes(profile)) {
|
|
547
|
+
await ctx.reply('Unknown profile. Use /create-agent to restart.')
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const updated = advanceState(state, { step: 'asked-bot-token', profile })
|
|
552
|
+
setState(updated)
|
|
553
|
+
|
|
554
|
+
await ctx.reply(
|
|
555
|
+
`Profile <b>${escapeHtmlForTg(profile)}</b> selected.\n\nPaste the BotFather token for the new agent's Telegram bot:\n<i>(Note: this token will be visible in this chat)</i>`,
|
|
556
|
+
{ parse_mode: 'HTML' },
|
|
557
|
+
)
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Unknown callback — ignore
|
|
562
|
+
await ctx.answerCallbackQuery()
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
// ─── Inbound text router for multi-turn flows ─────────────────────────────
|
|
566
|
+
|
|
567
|
+
bot.on('message:text', async ctx => {
|
|
568
|
+
if (ctx.chat?.type !== 'private') return
|
|
569
|
+
const chatId = String(ctx.chat.id)
|
|
570
|
+
const text = ctx.message.text ?? ''
|
|
571
|
+
|
|
572
|
+
// 1. Check for pending delete confirmation
|
|
573
|
+
const pendingDelete = pendingDeletes.get(chatId)
|
|
574
|
+
if (pendingDelete && text.trim().toUpperCase() === 'YES') {
|
|
575
|
+
pendingDeletes.delete(chatId)
|
|
576
|
+
const result = executeDeleteAgent(pendingDelete, switchroomExec)
|
|
577
|
+
for (const reply of result.replies) {
|
|
578
|
+
await switchroomReply(ctx, reply.text, { html: reply.html })
|
|
579
|
+
}
|
|
580
|
+
return
|
|
581
|
+
}
|
|
582
|
+
if (pendingDelete) {
|
|
583
|
+
// Any non-YES text cancels the pending delete
|
|
584
|
+
pendingDeletes.delete(chatId)
|
|
585
|
+
await switchroomReply(ctx, 'Deletion cancelled.', { html: false })
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
// No pendingDelete on this chat. If the user's text is `YES` or `YES.`,
|
|
589
|
+
// they probably typed it expecting to confirm a delete that was queued
|
|
590
|
+
// before a foreman restart (pendingDeletes is in-memory; #28 item 7).
|
|
591
|
+
// Pre-fix this fell through and eventually rendered "Unknown command",
|
|
592
|
+
// which left the user wondering whether the delete went through. Surface
|
|
593
|
+
// a clear "no pending delete" message instead.
|
|
594
|
+
if (/^yes\.?$/i.test(text.trim())) {
|
|
595
|
+
await switchroomReply(
|
|
596
|
+
ctx,
|
|
597
|
+
'There is no pending delete to confirm — the foreman may have restarted since you ran <code>/delete</code>. Re-run <code>/delete <agent></code> if you still want to delete.',
|
|
598
|
+
{ html: true },
|
|
599
|
+
)
|
|
600
|
+
return
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 2. Check for active /setup wizard flow
|
|
604
|
+
const setupState = getSetupState(chatId)
|
|
605
|
+
if (setupState && setupState.step !== 'done') {
|
|
606
|
+
await handleSetupFlowText(ctx, chatId, text, setupState)
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 3. Check for active create-agent flow
|
|
611
|
+
const flowState = getState(chatId)
|
|
612
|
+
if (flowState && flowState.step !== 'done') {
|
|
613
|
+
await handleCreateFlowText(ctx, chatId, text, flowState)
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 4. Unknown text
|
|
618
|
+
await switchroomReply(ctx, 'Unknown command. Try /help.', { html: true })
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
// ─── Setup wizard: text handler ───────────────────────────────────────────
|
|
622
|
+
|
|
623
|
+
async function handleSetupFlowText(
|
|
624
|
+
ctx: Context,
|
|
625
|
+
chatId: string,
|
|
626
|
+
text: string,
|
|
627
|
+
setupState: NonNullable<ReturnType<typeof getSetupState>>,
|
|
628
|
+
): Promise<void> {
|
|
629
|
+
const callerId = String(ctx.from?.id ?? '')
|
|
630
|
+
// #190: pass live profiles into the wizard so the asked-profile step can
|
|
631
|
+
// validate against the operator's actual profile set.
|
|
632
|
+
const profiles = listAvailableProfiles()
|
|
633
|
+
const action = handleSetupText({ state: setupState, text, callerId, profiles })
|
|
634
|
+
|
|
635
|
+
switch (action.kind) {
|
|
636
|
+
// ── Slug step ──────────────────────────────────────────────────────────
|
|
637
|
+
case 'ask-persona': {
|
|
638
|
+
const updated = advanceSetupState(setupState, { step: 'asked-persona', slug: action.slug })
|
|
639
|
+
setSetupState(updated)
|
|
640
|
+
await switchroomReply(ctx, [
|
|
641
|
+
`Slug: <code>${escapeHtmlForTg(action.slug)}</code>`,
|
|
642
|
+
'',
|
|
643
|
+
'Step 2/6: What persona name should this agent have?',
|
|
644
|
+
'<i>e.g. <code>Gym Bro</code> — displayed in greetings</i>',
|
|
645
|
+
].join('\n'), { html: true })
|
|
646
|
+
return
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ── Persona step ───────────────────────────────────────────────────────
|
|
650
|
+
case 'ask-model': {
|
|
651
|
+
const updated = advanceSetupState(setupState, {
|
|
652
|
+
step: 'asked-model',
|
|
653
|
+
slug: action.slug,
|
|
654
|
+
persona: action.persona,
|
|
655
|
+
})
|
|
656
|
+
setSetupState(updated)
|
|
657
|
+
await switchroomReply(ctx, [
|
|
658
|
+
`Persona: <b>${escapeHtmlForTg(action.persona)}</b>`,
|
|
659
|
+
'',
|
|
660
|
+
'Step 3/6: Which Claude model should this agent use?',
|
|
661
|
+
'Options: <code>sonnet</code>, <code>opus</code>, <code>haiku</code>, or a full model ID.',
|
|
662
|
+
'Type <code>skip</code> to use the profile default.',
|
|
663
|
+
].join('\n'), { html: true })
|
|
664
|
+
return
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// ── Model step ─────────────────────────────────────────────────────────
|
|
668
|
+
case 'ask-emoji': {
|
|
669
|
+
const updated = advanceSetupState(setupState, {
|
|
670
|
+
step: 'asked-emoji',
|
|
671
|
+
model: action.model,
|
|
672
|
+
})
|
|
673
|
+
setSetupState(updated)
|
|
674
|
+
const modelNote = action.model
|
|
675
|
+
? `Model: <code>${escapeHtmlForTg(action.model)}</code>`
|
|
676
|
+
: 'Model: <i>profile default</i>'
|
|
677
|
+
await switchroomReply(ctx, [
|
|
678
|
+
modelNote,
|
|
679
|
+
'',
|
|
680
|
+
'Step 4/6: What emoji should represent this agent\'s Telegram topic?',
|
|
681
|
+
'Type <code>skip</code> to use the default.',
|
|
682
|
+
].join('\n'), { html: true })
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ── Emoji step → Profile selector (#190) ───────────────────────────────
|
|
687
|
+
case 'ask-profile': {
|
|
688
|
+
const updated = advanceSetupState(setupState, {
|
|
689
|
+
step: 'asked-profile',
|
|
690
|
+
emoji: action.emoji,
|
|
691
|
+
})
|
|
692
|
+
setSetupState(updated)
|
|
693
|
+
const emojiNote = action.emoji
|
|
694
|
+
? `Emoji: ${action.emoji}`
|
|
695
|
+
: 'Emoji: <i>default</i>'
|
|
696
|
+
await switchroomReply(ctx, [
|
|
697
|
+
emojiNote,
|
|
698
|
+
'',
|
|
699
|
+
`Step 5/6: Choose a profile for this agent.`,
|
|
700
|
+
`Available: ${action.profiles.map(p => `<code>${escapeHtmlForTg(p)}</code>`).join(', ')}`,
|
|
701
|
+
'',
|
|
702
|
+
'<i>The profile sets the agent\'s base persona, system prompt, and skill bundle. Pick <code>default</code> if unsure.</i>',
|
|
703
|
+
].join('\n'), { html: true })
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ── Profile step ───────────────────────────────────────────────────────
|
|
708
|
+
case 'ask-bot-token': {
|
|
709
|
+
const updated = advanceSetupState(setupState, {
|
|
710
|
+
step: 'asked-bot-token',
|
|
711
|
+
profile: action.profile,
|
|
712
|
+
})
|
|
713
|
+
setSetupState(updated)
|
|
714
|
+
// TODO(#188): BotFather auto-flow — currently user creates bot manually
|
|
715
|
+
await switchroomReply(ctx, [
|
|
716
|
+
`Profile: <code>${escapeHtmlForTg(action.profile)}</code>`,
|
|
717
|
+
'',
|
|
718
|
+
'Step 6/6: Paste the BotFather token for the new agent\'s bot.',
|
|
719
|
+
'',
|
|
720
|
+
'<b>To create a bot:</b>',
|
|
721
|
+
'1. Open @BotFather in Telegram',
|
|
722
|
+
'2. Send <code>/newbot</code> and follow the prompts',
|
|
723
|
+
'3. Copy and paste the token here',
|
|
724
|
+
'',
|
|
725
|
+
'<i>Note: the token will be briefly visible in this chat.</i>',
|
|
726
|
+
].join('\n'), { html: true })
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// ── Bot-token step ─────────────────────────────────────────────────────
|
|
731
|
+
case 'confirm-allowlist': {
|
|
732
|
+
const botToken = text.trim()
|
|
733
|
+
const updated = advanceSetupState(setupState, {
|
|
734
|
+
step: 'confirming-allowlist',
|
|
735
|
+
botToken,
|
|
736
|
+
})
|
|
737
|
+
setSetupState(updated)
|
|
738
|
+
await switchroomReply(ctx, [
|
|
739
|
+
'Token received.',
|
|
740
|
+
'',
|
|
741
|
+
`Your Telegram user ID is <code>${escapeHtmlForTg(action.callerId)}</code>.`,
|
|
742
|
+
'',
|
|
743
|
+
'Reply <b>yes</b> to set this as the only allowed user for the new agent,',
|
|
744
|
+
'or paste a different user ID.',
|
|
745
|
+
].join('\n'), { html: true })
|
|
746
|
+
return
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ── Allowlist confirmation step → provision agent (#189: split path) ──
|
|
750
|
+
//
|
|
751
|
+
// Pre-fix this was a single 'call-reconcile' that ran createAgent inline
|
|
752
|
+
// and told the user to run `switchroom auth code` from a terminal. Per
|
|
753
|
+
// #189 + #190, the flow now splits into three coordinated steps:
|
|
754
|
+
//
|
|
755
|
+
// call-create-agent → run createAgent (returns loginUrl)
|
|
756
|
+
// ask-oauth-code → render the URL prompt; user pastes back
|
|
757
|
+
// call-complete-creation → run completeCreation + start the agent
|
|
758
|
+
//
|
|
759
|
+
// Mirrors the create-flow's existing call-create-agent/asked-oauth-code/
|
|
760
|
+
// call-complete-creation triad. The two flows have parallel state
|
|
761
|
+
// machines and parallel orchestrator handlers.
|
|
762
|
+
case 'call-create-agent': {
|
|
763
|
+
const { slug, persona, model, emoji, profile, botToken, allowedUserId } = action
|
|
764
|
+
void model; void emoji; void allowedUserId // captured in state earlier; createAgent reads from yaml
|
|
765
|
+
const updated = advanceSetupState(setupState, {
|
|
766
|
+
step: 'reconciling',
|
|
767
|
+
allowedUserId,
|
|
768
|
+
})
|
|
769
|
+
setSetupState(updated)
|
|
770
|
+
|
|
771
|
+
await switchroomReply(ctx, `Validating token…`, { html: false })
|
|
772
|
+
|
|
773
|
+
// Validate token first
|
|
774
|
+
let botInfo: { username: string } | null = null
|
|
775
|
+
try {
|
|
776
|
+
botInfo = await validateBotToken(botToken)
|
|
777
|
+
} catch (err) {
|
|
778
|
+
const updatedBack = advanceSetupState(updated, { step: 'asked-bot-token' })
|
|
779
|
+
setSetupState(updatedBack)
|
|
780
|
+
await switchroomReply(ctx, [
|
|
781
|
+
`Token rejected by Telegram — ${escapeHtmlForTg((err as Error).message)}`,
|
|
782
|
+
'',
|
|
783
|
+
'Please get a fresh token from @BotFather and paste it here:',
|
|
784
|
+
].join('\n'), { html: true })
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const botUsername = botInfo?.username ?? null
|
|
789
|
+
await switchroomReply(ctx, `Token OK (@${escapeHtmlForTg(botUsername ?? '?')}). Provisioning agent <b>${escapeHtmlForTg(slug)}</b>…`, { html: true })
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
const result = await createAgent({
|
|
793
|
+
name: slug,
|
|
794
|
+
profile,
|
|
795
|
+
telegramBotToken: botToken,
|
|
796
|
+
rollbackOnFail: true,
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
if (result.loginUrl) {
|
|
800
|
+
// #189: in-wizard OAuth path. Stash the auth session + URL on the
|
|
801
|
+
// wizard state and ask the user to paste the code in chat.
|
|
802
|
+
const oauthState = advanceSetupState(updated, {
|
|
803
|
+
step: 'asked-oauth-code',
|
|
804
|
+
authSessionName: result.sessionName,
|
|
805
|
+
loginUrl: result.loginUrl,
|
|
806
|
+
})
|
|
807
|
+
setSetupState(oauthState)
|
|
808
|
+
const kb = new InlineKeyboard().url('Open OAuth URL', result.loginUrl)
|
|
809
|
+
await ctx.reply(
|
|
810
|
+
[
|
|
811
|
+
`<b>${escapeHtmlForTg(persona)}</b> (@${escapeHtmlForTg(botUsername ?? slug)}) is scaffolded!`,
|
|
812
|
+
'',
|
|
813
|
+
'<b>Step 6 of 6 — OAuth:</b>',
|
|
814
|
+
'Open the URL below, log in to Claude, then paste the code back here.',
|
|
815
|
+
].join('\n'),
|
|
816
|
+
{ parse_mode: 'HTML', reply_markup: kb },
|
|
817
|
+
)
|
|
818
|
+
} else {
|
|
819
|
+
// No loginUrl returned — agent doesn't need OAuth (rare). Mark done.
|
|
820
|
+
const doneState = advanceSetupState(updated, { step: 'done' })
|
|
821
|
+
setSetupState(doneState)
|
|
822
|
+
clearSetupState(chatId)
|
|
823
|
+
await switchroomReply(ctx, [
|
|
824
|
+
`<b>${escapeHtmlForTg(persona)}</b> (@${escapeHtmlForTg(botUsername ?? slug)}) is scaffolded and ready!`,
|
|
825
|
+
'',
|
|
826
|
+
'<i>Skills can be added later via yaml or future /skills command.</i>',
|
|
827
|
+
].join('\n'), { html: true })
|
|
828
|
+
}
|
|
829
|
+
} catch (err) {
|
|
830
|
+
// Rollback happened inside createAgent — reset to bot-token step
|
|
831
|
+
const updatedBack = advanceSetupState(updated, { step: 'asked-bot-token' })
|
|
832
|
+
setSetupState(updatedBack)
|
|
833
|
+
await switchroomReply(ctx, [
|
|
834
|
+
`<b>Provisioning failed:</b> ${escapeHtmlForTg((err as Error).message)}`,
|
|
835
|
+
'',
|
|
836
|
+
'To retry, paste a bot token again. Or type <code>cancel</code> to abort.',
|
|
837
|
+
].join('\n'), { html: true })
|
|
838
|
+
}
|
|
839
|
+
return
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// ── ask-oauth-code is currently dispatched only by call-create-agent
|
|
843
|
+
// above (which goes straight to the InlineKeyboard render before
|
|
844
|
+
// setting state). The handler below covers any future path that
|
|
845
|
+
// emits it as a discrete action — currently dead-code-style
|
|
846
|
+
// safety, but keeping the case keeps the union check exhaustive.
|
|
847
|
+
case 'ask-oauth-code': {
|
|
848
|
+
const updated = advanceSetupState(setupState, {
|
|
849
|
+
step: 'asked-oauth-code',
|
|
850
|
+
loginUrl: action.loginUrl,
|
|
851
|
+
})
|
|
852
|
+
setSetupState(updated)
|
|
853
|
+
const promptLines = action.loginUrl
|
|
854
|
+
? ['Open this URL to log in, then paste the code back here:']
|
|
855
|
+
: ['Auth session started. Paste the OAuth code back here:']
|
|
856
|
+
if (action.loginUrl) {
|
|
857
|
+
const kb = new InlineKeyboard().url('Open OAuth URL', action.loginUrl)
|
|
858
|
+
await ctx.reply(promptLines.join('\n'), { reply_markup: kb })
|
|
859
|
+
} else {
|
|
860
|
+
await switchroomReply(ctx, promptLines.join('\n'), { html: false })
|
|
861
|
+
}
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ── OAuth-code paste step → run completeCreation + start the agent ────
|
|
866
|
+
case 'call-complete-creation': {
|
|
867
|
+
const { slug, persona, code } = action
|
|
868
|
+
await switchroomReply(ctx, 'Submitting OAuth code…', { html: false })
|
|
869
|
+
try {
|
|
870
|
+
const result = await completeCreation(slug, code)
|
|
871
|
+
if (result.outcome.kind === 'success' && result.started) {
|
|
872
|
+
const doneState = advanceSetupState(setupState, { step: 'done' })
|
|
873
|
+
setSetupState(doneState)
|
|
874
|
+
clearSetupState(chatId)
|
|
875
|
+
await switchroomReply(ctx, [
|
|
876
|
+
`<b>${escapeHtmlForTg(persona)}</b> is online! DM its bot to say hi.`,
|
|
877
|
+
'',
|
|
878
|
+
'<i>Skills can be added later via yaml or future /skills command.</i>',
|
|
879
|
+
].join('\n'), { html: true })
|
|
880
|
+
} else if (result.outcome.kind === 'success') {
|
|
881
|
+
const doneState = advanceSetupState(setupState, { step: 'done' })
|
|
882
|
+
setSetupState(doneState)
|
|
883
|
+
clearSetupState(chatId)
|
|
884
|
+
await switchroomReply(ctx, [
|
|
885
|
+
`Auth succeeded but agent start failed.`,
|
|
886
|
+
'',
|
|
887
|
+
`Try: <code>switchroom agent start ${escapeHtmlForTg(slug)}</code>`,
|
|
888
|
+
].join('\n'), { html: true })
|
|
889
|
+
} else {
|
|
890
|
+
// Bad code — stay in asked-oauth-code so the user can paste again.
|
|
891
|
+
await switchroomReply(
|
|
892
|
+
ctx,
|
|
893
|
+
`Code rejected (${result.outcome.kind}). Paste the code again, or type <code>cancel</code> to abort:`,
|
|
894
|
+
{ html: true },
|
|
895
|
+
)
|
|
896
|
+
}
|
|
897
|
+
} catch (err) {
|
|
898
|
+
await switchroomReply(ctx, [
|
|
899
|
+
`<b>completeCreation failed:</b> ${escapeHtmlForTg((err as Error).message)}`,
|
|
900
|
+
'',
|
|
901
|
+
'Type <code>cancel</code> to abort, or paste the code again to retry:',
|
|
902
|
+
].join('\n'), { html: true })
|
|
903
|
+
}
|
|
904
|
+
return
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// ── Error (validation failure, stayInStep re-prompt) ──────────────────
|
|
908
|
+
case 'error': {
|
|
909
|
+
if (!action.stayInStep) {
|
|
910
|
+
clearSetupState(chatId)
|
|
911
|
+
}
|
|
912
|
+
await switchroomReply(ctx, action.message, { html: true })
|
|
913
|
+
return
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// ── Cancel ─────────────────────────────────────────────────────────────
|
|
917
|
+
case 'cancel': {
|
|
918
|
+
clearSetupState(chatId)
|
|
919
|
+
if (action.reason === 'user-cancelled') {
|
|
920
|
+
await switchroomReply(ctx, 'Setup wizard cancelled. Type /setup to start over.', { html: false })
|
|
921
|
+
} else {
|
|
922
|
+
await switchroomReply(ctx, `Setup wizard stopped (${action.reason}). Type /setup to start over.`, { html: false })
|
|
923
|
+
}
|
|
924
|
+
return
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// ── Done (shouldn't reach here via text) ──────────────────────────────
|
|
928
|
+
case 'done': {
|
|
929
|
+
clearSetupState(chatId)
|
|
930
|
+
await switchroomReply(ctx, 'Setup already complete. Type /setup to create another agent.', { html: false })
|
|
931
|
+
return
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async function handleCreateFlowText(
|
|
937
|
+
ctx: Context,
|
|
938
|
+
chatId: string,
|
|
939
|
+
text: string,
|
|
940
|
+
flowState: NonNullable<ReturnType<typeof getState>>,
|
|
941
|
+
): Promise<void> {
|
|
942
|
+
const profiles = listAvailableProfiles()
|
|
943
|
+
const action = handleFlowText({ state: flowState, text, profiles })
|
|
944
|
+
|
|
945
|
+
switch (action.kind) {
|
|
946
|
+
case 'ask-name':
|
|
947
|
+
await switchroomReply(ctx, 'What should the new agent be named?', { html: false })
|
|
948
|
+
return
|
|
949
|
+
|
|
950
|
+
case 'ask-profile': {
|
|
951
|
+
// Update name in state if we just got it
|
|
952
|
+
const updatedName = text.trim()
|
|
953
|
+
const newState = advanceState(flowState, { step: 'asked-profile', name: updatedName })
|
|
954
|
+
setState(newState)
|
|
955
|
+
const kb = new InlineKeyboard()
|
|
956
|
+
for (const p of profiles) {
|
|
957
|
+
kb.text(p, `cf:profile:${p}`).row()
|
|
958
|
+
}
|
|
959
|
+
await ctx.reply(
|
|
960
|
+
`Choose a profile for <b>${escapeHtmlForTg(updatedName)}</b>:`,
|
|
961
|
+
{ parse_mode: 'HTML', reply_markup: kb },
|
|
962
|
+
)
|
|
963
|
+
return
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
case 'ask-bot-token': {
|
|
967
|
+
const newState = advanceState(flowState, { step: 'asked-bot-token', profile: action.profile })
|
|
968
|
+
setState(newState)
|
|
969
|
+
await switchroomReply(ctx, `Profile <b>${escapeHtmlForTg(action.profile)}</b> selected.\n\nPaste the BotFather token for <b>${escapeHtmlForTg(action.name)}</b>'s Telegram bot:\n<i>(Note: this token will be visible in this chat)</i>`, { html: true })
|
|
970
|
+
return
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
case 'call-create-agent': {
|
|
974
|
+
const { name, profile, botToken } = action
|
|
975
|
+
// Pre-#28 fix this called validateBotToken here AND createAgent
|
|
976
|
+
// (via validateBotTokenMatchesAgent at create-orchestrator.ts:150)
|
|
977
|
+
// would call it again — two sequential Telegram getMe() requests in
|
|
978
|
+
// the happy path. We now trust the orchestrator's check and surface
|
|
979
|
+
// its error if it fails. The /setup flow at line 723 keeps its own
|
|
980
|
+
// pre-check because it uses the returned botInfo.username for UX.
|
|
981
|
+
await switchroomReply(ctx, `Creating agent <b>${escapeHtmlForTg(name)}</b>…`, { html: true })
|
|
982
|
+
try {
|
|
983
|
+
const result = await createAgent({
|
|
984
|
+
name,
|
|
985
|
+
profile,
|
|
986
|
+
telegramBotToken: botToken,
|
|
987
|
+
// Clean up scaffold/systemd/yaml on mid-flow failure so the user
|
|
988
|
+
// can retry /create-agent with the same name without conflicts.
|
|
989
|
+
rollbackOnFail: true,
|
|
990
|
+
})
|
|
991
|
+
const newState = advanceState(flowState, {
|
|
992
|
+
step: 'asked-oauth-code',
|
|
993
|
+
name,
|
|
994
|
+
profile,
|
|
995
|
+
botToken,
|
|
996
|
+
authSessionName: result.sessionName,
|
|
997
|
+
loginUrl: result.loginUrl ?? null,
|
|
998
|
+
})
|
|
999
|
+
setState(newState)
|
|
1000
|
+
|
|
1001
|
+
if (result.loginUrl) {
|
|
1002
|
+
const kb = new InlineKeyboard().url('Open OAuth URL', result.loginUrl)
|
|
1003
|
+
await ctx.reply(
|
|
1004
|
+
'Open this URL to log in, then paste the code back here:',
|
|
1005
|
+
{ reply_markup: kb },
|
|
1006
|
+
)
|
|
1007
|
+
} else {
|
|
1008
|
+
await switchroomReply(ctx, 'Auth session started. Paste the OAuth code back here:', { html: false })
|
|
1009
|
+
}
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
await switchroomReply(ctx, `<b>createAgent failed:</b> ${escapeHtmlForTg((err as Error).message)}`, { html: true })
|
|
1012
|
+
clearState(chatId)
|
|
1013
|
+
}
|
|
1014
|
+
return
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
case 'call-complete-creation': {
|
|
1018
|
+
const { name, code } = action
|
|
1019
|
+
await switchroomReply(ctx, 'Submitting OAuth code…', { html: false })
|
|
1020
|
+
try {
|
|
1021
|
+
const result = await completeCreation(name, code)
|
|
1022
|
+
if (result.outcome.kind === 'success' && result.started) {
|
|
1023
|
+
clearState(chatId)
|
|
1024
|
+
await switchroomReply(ctx, `<b>${escapeHtmlForTg(name)}</b> is online! DM its bot to say hi.`, { html: true })
|
|
1025
|
+
} else if (result.outcome.kind === 'success') {
|
|
1026
|
+
clearState(chatId)
|
|
1027
|
+
await switchroomReply(ctx, `Auth succeeded but agent start failed. Try: <code>switchroom agent start ${escapeHtmlForTg(name)}</code>`, { html: true })
|
|
1028
|
+
} else {
|
|
1029
|
+
// Bad code — stay in asked-oauth-code step
|
|
1030
|
+
await switchroomReply(ctx, `Code rejected (${result.outcome.kind}). Paste the code again, or use /create-agent to restart:`, { html: false })
|
|
1031
|
+
}
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
await switchroomReply(ctx, `<b>completeCreation failed:</b> ${escapeHtmlForTg((err as Error).message)}`, { html: true })
|
|
1034
|
+
clearState(chatId)
|
|
1035
|
+
}
|
|
1036
|
+
return
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
case 'error': {
|
|
1040
|
+
await switchroomReply(ctx, action.message, { html: true })
|
|
1041
|
+
if (!action.stayInStep) {
|
|
1042
|
+
clearState(chatId)
|
|
1043
|
+
}
|
|
1044
|
+
return
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
case 'cancel':
|
|
1048
|
+
case 'done':
|
|
1049
|
+
// No active flow — fall through to unknown command
|
|
1050
|
+
await switchroomReply(ctx, 'Unknown command. Try /help.', { html: true })
|
|
1051
|
+
return
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// ─── Startup ──────────────────────────────────────────────────────────────
|
|
1056
|
+
process.on('unhandledRejection', err => {
|
|
1057
|
+
process.stderr.write(`foreman: unhandled rejection: ${err}\n`)
|
|
1058
|
+
})
|
|
1059
|
+
process.on('uncaughtException', err => {
|
|
1060
|
+
process.stderr.write(`foreman: uncaught exception: ${err}\n`)
|
|
1061
|
+
})
|
|
1062
|
+
|
|
1063
|
+
void runPollingLoop(bot, {
|
|
1064
|
+
onReady: (username) => {
|
|
1065
|
+
process.stderr.write(`foreman: ready as @${username}\n`)
|
|
1066
|
+
},
|
|
1067
|
+
onOneTimeSetup: async (username) => {
|
|
1068
|
+
process.stderr.write(`foreman: one-time setup done @${username}\n`)
|
|
1069
|
+
// Register bot commands so they show in the Telegram UI
|
|
1070
|
+
try {
|
|
1071
|
+
await bot.api.setMyCommands([
|
|
1072
|
+
{ command: 'start', description: 'Start / intro' },
|
|
1073
|
+
{ command: 'help', description: 'Command list' },
|
|
1074
|
+
{ command: 'status', description: 'Fleet status' },
|
|
1075
|
+
{ command: 'list', description: 'Fleet status (alias)' },
|
|
1076
|
+
{ command: 'logs', description: 'Agent logs: /logs <agent> [--tail N]' },
|
|
1077
|
+
{ command: 'auth', description: 'Auth dashboard: /auth [agent]' },
|
|
1078
|
+
{ command: 'restart', description: 'Restart agent: /restart <agent>' },
|
|
1079
|
+
{ command: 'delete', description: 'Delete agent (with confirm): /delete <agent>' },
|
|
1080
|
+
{ command: 'update', description: 'Update switchroom' },
|
|
1081
|
+
{ command: 'version', description: 'Show versions + running agent health' },
|
|
1082
|
+
{ command: 'worktrees', description: 'List active git worktrees claimed by sub-agents' },
|
|
1083
|
+
{ command: 'create_agent', description: 'Create new agent: /create-agent [name]' },
|
|
1084
|
+
{ command: 'setup', description: 'New agent wizard: /setup [slug]' },
|
|
1085
|
+
{ command: 'cancel', description: 'Cancel active wizard' },
|
|
1086
|
+
])
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
process.stderr.write(`foreman: setMyCommands failed: ${err}\n`)
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// Resume any in-progress setup wizard flows that survived a restart
|
|
1092
|
+
try {
|
|
1093
|
+
const activeSetupFlows = listActiveSetupFlows(60 * 60 * 1000) // 1 hour
|
|
1094
|
+
for (const flow of activeSetupFlows) {
|
|
1095
|
+
try {
|
|
1096
|
+
await bot.api.sendMessage(
|
|
1097
|
+
flow.chatId,
|
|
1098
|
+
`Picking up /setup wizard for <b>${escapeHtmlForTg(flow.slug ?? '?')}</b> (${setupStepLabel(flow.step)})…\n\nType your response to continue, or /cancel to abort.`,
|
|
1099
|
+
{ parse_mode: 'HTML' },
|
|
1100
|
+
)
|
|
1101
|
+
} catch (err) {
|
|
1102
|
+
process.stderr.write(`foreman: failed to resume setup flow for chat ${flow.chatId}: ${err}\n`)
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
process.stderr.write(`foreman: failed to list active setup flows: ${err}\n`)
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Resume any in-progress create-agent flows that survived a restart
|
|
1110
|
+
try {
|
|
1111
|
+
const activeFlows = listActiveFlows(60 * 60 * 1000) // 1 hour
|
|
1112
|
+
for (const flow of activeFlows) {
|
|
1113
|
+
try {
|
|
1114
|
+
await bot.api.sendMessage(
|
|
1115
|
+
flow.chatId,
|
|
1116
|
+
`Picking up create-agent flow for <b>${escapeHtmlForTg(flow.name ?? '?')}</b> (${stepLabel(flow.step)})…\n\nType your response to continue, or /create-agent to restart.`,
|
|
1117
|
+
{ parse_mode: 'HTML' },
|
|
1118
|
+
)
|
|
1119
|
+
} catch (err) {
|
|
1120
|
+
process.stderr.write(`foreman: failed to resume flow for chat ${flow.chatId}: ${err}\n`)
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
process.stderr.write(`foreman: failed to list active flows: ${err}\n`)
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
on409: (attempt, delayMs) => {
|
|
1128
|
+
process.stderr.write(`foreman: 409 Conflict attempt=${attempt} retry_in_ms=${delayMs}\n`)
|
|
1129
|
+
},
|
|
1130
|
+
})
|