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,967 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Watchdog: restarts switchroom agent services whose Telegram bridge has
|
|
3
|
+
# disconnected from the gateway, OR whose journal output has been silent
|
|
4
|
+
# for too long (indicating an internally-frozen agent that systemd still
|
|
5
|
+
# reports as "active (running)"). Designed to run on a systemd timer.
|
|
6
|
+
#
|
|
7
|
+
# For each agent, checks whether the gateway is up and has an active bridge.
|
|
8
|
+
# If the gateway is healthy but the bridge is disconnected (or never connected),
|
|
9
|
+
# restarts the agent service so Claude Code gets a fresh MCP server.
|
|
10
|
+
#
|
|
11
|
+
# Journal-silence check (2026-04-26, issue #116): Three klanker hangs in
|
|
12
|
+
# 10 hours exposed a class of failure where the agent process is
|
|
13
|
+
# "active (running)" to systemd but internally frozen — no journal output
|
|
14
|
+
# for many minutes, manual restart the only recovery. Two hangs were on the
|
|
15
|
+
# Stop-hook ladder ("running stop hooks 0/N"); one was mid-task at 1.0 GB
|
|
16
|
+
# RSS. The watchdog now also checks journal-output freshness per-agent and
|
|
17
|
+
# restarts via `switchroom agent restart <agent>` when an agent has been
|
|
18
|
+
# silent for JOURNAL_SILENCE_SECS (default 600s) and has cleared the uptime
|
|
19
|
+
# grace. Sustained suspicion via a state file under
|
|
20
|
+
# /run/user/<uid>/switchroom-watchdog/ prevents transient quiet from
|
|
21
|
+
# triggering.
|
|
22
|
+
#
|
|
23
|
+
# Agent discovery: enumerates ALL active switchroom-*-gateway.service units
|
|
24
|
+
# and derives the agent name + gateway-log path from each. This replaces the
|
|
25
|
+
# previous hardcoded (agent, log) list which rotted any time an agent was
|
|
26
|
+
# renamed or added — e.g. on 2026-04-21 the old list still held "assistant"
|
|
27
|
+
# (since renamed to "clerk") and silently skipped the new "lawgpt" agent
|
|
28
|
+
# entirely, leaving both in a stale-bridge state for hours while klanker
|
|
29
|
+
# (still on the list) kept getting healed.
|
|
30
|
+
#
|
|
31
|
+
# False-restart fix (2026-04-22): the bridge IPC flaps `registered ↔
|
|
32
|
+
# disconnected` rapidly across Claude Code turn boundaries. The old
|
|
33
|
+
# `tail -1` heuristic caught transient disconnect states and restarted
|
|
34
|
+
# otherwise-healthy agents. On 2026-04-21 20:12–20:26 AEST this produced
|
|
35
|
+
# 3 spurious restarts of klanker mid-CPU-heavy-work. The watchdog now
|
|
36
|
+
# requires SUSTAINED disconnection (>= DISCONNECT_GRACE_SECS across
|
|
37
|
+
# consecutive ticks) and an uptime grace (>= UPTIME_GRACE_SECS since
|
|
38
|
+
# the agent service started) before acting.
|
|
39
|
+
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
|
|
42
|
+
# Tunables. Expressed as env-overridable so the test harness can drive
|
|
43
|
+
# edge cases without mutating the script.
|
|
44
|
+
: "${UPTIME_GRACE_SECS:=90}" # skip checks for this long after agent (re)start
|
|
45
|
+
: "${DISCONNECT_GRACE_SECS:=600}" # require disconnection to persist this long before restarting
|
|
46
|
+
: "${LIVENESS_GRACE_SECS:=30}" # liveness file mtime must be recent before we treat bridge as dead
|
|
47
|
+
# Journal-silence thresholds. Defaults raised from 600s to 4000s on
|
|
48
|
+
# 2026-04-30 (issue #405). The previous 600s default opened a trap zone
|
|
49
|
+
# where any agent whose latest journal entry sat between
|
|
50
|
+
# JOURNAL_SILENCE_SECS (600s) and RECENT_ACTIVITY_WINDOW_SECS (3600s)
|
|
51
|
+
# was eligible for restart. Normal chat-cadence agents (10–60 min between
|
|
52
|
+
# user messages) land in that zone every cycle, producing ~208 false
|
|
53
|
+
# restarts/24h on a typical host. With both defaults at 4000s (> the
|
|
54
|
+
# 3600s recent-activity window), the trap zone closes: by the time
|
|
55
|
+
# silence reaches 4000s, the latest entry is already past the
|
|
56
|
+
# recent-activity gate and gets treated as idle. The hang detector is
|
|
57
|
+
# effectively inert under defaults — operators who want it active must
|
|
58
|
+
# opt in by lowering these values via env, and `Restart=on-failure` in
|
|
59
|
+
# the unit file still catches actual crashes. See issue #405 for the
|
|
60
|
+
# worked example showing the 21.5-min restart cadence the trap zone
|
|
61
|
+
# produced.
|
|
62
|
+
: "${JOURNAL_SILENCE_SECS:=4000}" # seconds of journal silence before suspecting a hang
|
|
63
|
+
: "${JOURNAL_SILENCE_HARD_SECS:=4000}" # seconds the silence_since marker must predate before restarting
|
|
64
|
+
# Recent-activity gate: only treat journal-silence as suspect-hang when the
|
|
65
|
+
# agent had ANY log activity within this window. Distinguishes "hung mid-task"
|
|
66
|
+
# (last log moments ago, then silence) from "genuinely idle" (no logs in
|
|
67
|
+
# hours/days — agent waiting for the next user message). Default 1h: long
|
|
68
|
+
# enough to span a normal session but short enough that a long overnight idle
|
|
69
|
+
# doesn't get falsely flagged.
|
|
70
|
+
: "${RECENT_ACTIVITY_WINDOW_SECS:=3600}"
|
|
71
|
+
# Turn-active marker check (issue #412): the gateway writes a per-agent
|
|
72
|
+
# `turn-active.json` at turn-start, touches its mtime on every tool_use,
|
|
73
|
+
# and removes it on turn_complete. If the file exists AND its mtime
|
|
74
|
+
# hasn't advanced in TURN_HANG_SECS, the agent is wedged mid-turn —
|
|
75
|
+
# distinguishable from "legitimately idle" because legitimate idle
|
|
76
|
+
# leaves no marker file at all. Default 5 min: bigger than the slowest
|
|
77
|
+
# legitimate single-tool turn (a long Bash compile maybe) but tight
|
|
78
|
+
# enough to catch Stop-hook deadlocks before the user notices.
|
|
79
|
+
: "${TURN_HANG_SECS:=300}"
|
|
80
|
+
# Forward-progress liveness window. The gateway only bumps
|
|
81
|
+
# `turn-active.json` mtime on PARENT-stream tool_use events; when the
|
|
82
|
+
# parent dispatches a Task() to a sub-agent, the marker goes stale
|
|
83
|
+
# even while real work is happening. The bridge can also flap
|
|
84
|
+
# (transient socket close, MCP plugin restart) while a sub-agent
|
|
85
|
+
# keeps working. Before any restart path acts, probe the agent's
|
|
86
|
+
# `.claude/projects/**/*.jsonl` AND `.claude/tasks/**/*.json` files:
|
|
87
|
+
# if EITHER was modified within JSONL_LIVENESS_SECS, the agent is
|
|
88
|
+
# making forward progress and the restart is a false positive.
|
|
89
|
+
#
|
|
90
|
+
# Two independent fingerprints means a wedged agent has to be silent
|
|
91
|
+
# on BOTH to be killed — much stronger evidence than a single signal.
|
|
92
|
+
# 60s matches the in-flight detector's "recent" semantics in
|
|
93
|
+
# src/agents/in-flight.ts (30s window + 60s tick spread).
|
|
94
|
+
#
|
|
95
|
+
# (Name kept as JSONL_LIVENESS_SECS for back-compat with operators
|
|
96
|
+
# who already set it via env; the value gates both fingerprints.)
|
|
97
|
+
: "${JSONL_LIVENESS_SECS:=60}"
|
|
98
|
+
|
|
99
|
+
# Per-agent watchdog state lives under /run/user/$UID/switchroom-watchdog/
|
|
100
|
+
# (tmpfs, cleared on logout — correct: we don't want stale silence markers
|
|
101
|
+
# surviving restarts). mkdir -p is idempotent.
|
|
102
|
+
# WATCHDOG_STATE_DIR is env-overridable for the test harness.
|
|
103
|
+
UID_VAL="${UID:-$(id -u)}"
|
|
104
|
+
: "${WATCHDOG_STATE_DIR:=/run/user/${UID_VAL}/switchroom-watchdog}"
|
|
105
|
+
mkdir -p "$WATCHDOG_STATE_DIR" 2>/dev/null || true
|
|
106
|
+
|
|
107
|
+
now_epoch() { date +%s; }
|
|
108
|
+
|
|
109
|
+
# Unified logging — every decision goes to journalctl with the
|
|
110
|
+
# `switchroom-watchdog` tag AND to the unit's own stdout (which is
|
|
111
|
+
# also captured by journal via StandardOutput=journal). Use level tags
|
|
112
|
+
# (`detect`, `restart`, `skip`, `error`) so `journalctl -t
|
|
113
|
+
# switchroom-watchdog | grep '\[restart\]'` is a clean audit trail of
|
|
114
|
+
# every action this watchdog took.
|
|
115
|
+
wd_log() {
|
|
116
|
+
local level="$1"
|
|
117
|
+
shift
|
|
118
|
+
local msg="$*"
|
|
119
|
+
logger -t switchroom-watchdog "[$level] $msg" 2>/dev/null || true
|
|
120
|
+
# Stdout (not stderr) matches the prior `echo` lines so existing
|
|
121
|
+
# systemd journal capture (StandardOutput=journal) and the test
|
|
122
|
+
# harness that reads stdout from execFileSync both see the line.
|
|
123
|
+
echo "$(date -Iseconds) watchdog [$level] $msg"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Returns 0 (true) iff the agent shows ANY of two independent
|
|
127
|
+
# forward-progress fingerprints within the last `$2` seconds:
|
|
128
|
+
#
|
|
129
|
+
# 1. `.claude/projects/**/*.jsonl` — Claude Code appends to these
|
|
130
|
+
# transcripts on every event (model output, tool_use, sub-agent
|
|
131
|
+
# activity). Fresh mtime ⇒ the model or a sub-agent is alive.
|
|
132
|
+
# 2. `.claude/tasks/<session>/*.json` — TodoWrite / Task-tool state
|
|
133
|
+
# files. Updated independently of the transcript stream when the
|
|
134
|
+
# agent is iterating on a task list. Catches the case where the
|
|
135
|
+
# transcript momentarily quiets (large model thinking pause)
|
|
136
|
+
# while the agent is still progressing through todos.
|
|
137
|
+
#
|
|
138
|
+
# OR semantics: a wedged agent has to be silent on BOTH to be
|
|
139
|
+
# declared dead. Two uncorrelated fingerprints make false positives
|
|
140
|
+
# (kill while still working) much rarer than a single signal.
|
|
141
|
+
#
|
|
142
|
+
# `find -mmin` minimum granularity is minutes; round up to be
|
|
143
|
+
# conservative (better to defer a restart by an extra minute than to
|
|
144
|
+
# kill a live sub-agent). Both probes are bounded — quit on first
|
|
145
|
+
# match — so this stays O(1)-ish even on busy projects.
|
|
146
|
+
agent_has_recent_progress() {
|
|
147
|
+
local agent_name="$1"
|
|
148
|
+
local within_secs="$2"
|
|
149
|
+
local agent_root="${HOME}/.switchroom/agents/${agent_name}/.claude"
|
|
150
|
+
[[ -d "$agent_root" ]] || return 1
|
|
151
|
+
local mmin=$(( (within_secs + 59) / 60 ))
|
|
152
|
+
[[ "$mmin" -lt 1 ]] && mmin=1
|
|
153
|
+
|
|
154
|
+
# Signal 1: transcript JSONL writes (parent or sub-agent).
|
|
155
|
+
local hit
|
|
156
|
+
hit=$(find "${agent_root}/projects" -name '*.jsonl' -mmin "-${mmin}" -print -quit 2>/dev/null)
|
|
157
|
+
[[ -n "$hit" ]] && return 0
|
|
158
|
+
|
|
159
|
+
# Signal 2: TodoWrite/Task state JSON updates.
|
|
160
|
+
hit=$(find "${agent_root}/tasks" -name '*.json' -mmin "-${mmin}" -print -quit 2>/dev/null)
|
|
161
|
+
[[ -n "$hit" ]] && return 0
|
|
162
|
+
|
|
163
|
+
return 1
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# ─── Forensic observation helpers ──────────────────────────────────────
|
|
167
|
+
# Composed at every restart/detect/skip log line so `journalctl -t
|
|
168
|
+
# switchroom-watchdog` carries enough context to reconstruct WHY any
|
|
169
|
+
# action was taken without re-deriving from kernel/process state
|
|
170
|
+
# after the fact (which is impossible — the process is gone after a
|
|
171
|
+
# restart). Each helper is best-effort, returns a compact key=value
|
|
172
|
+
# fragment, and never fails the script.
|
|
173
|
+
|
|
174
|
+
# Resolve the most-interesting PID in the agent's systemd cgroup —
|
|
175
|
+
# i.e., the actual `claude` process, not the start.sh / `script -qfc`
|
|
176
|
+
# PTY wrappers that systemd reports as MainPID. Strategy:
|
|
177
|
+
#
|
|
178
|
+
# 1. Look up the unit's cgroup path via systemctl.
|
|
179
|
+
# 2. Read `/sys/fs/cgroup/<cgroup>/cgroup.procs` for the full list.
|
|
180
|
+
# 3. Pick the PID with the largest RSS — claude is reliably the
|
|
181
|
+
# memory-heaviest member of the cgroup (start.sh: ~2MB, script:
|
|
182
|
+
# ~1MB, claude: hundreds of MB to multiple GB).
|
|
183
|
+
#
|
|
184
|
+
# Falls back to MainPID if the cgroup walk fails (rare — only when
|
|
185
|
+
# cgroup v2 isn't mounted at /sys/fs/cgroup or systemd reports an
|
|
186
|
+
# unusual unit layout). Returns 0 when nothing resolvable.
|
|
187
|
+
agent_main_pid() {
|
|
188
|
+
local name="$1"
|
|
189
|
+
local unit="switchroom-${name}.service"
|
|
190
|
+
local cgroup
|
|
191
|
+
cgroup=$(systemctl --user show "$unit" -p ControlGroup --value 2>/dev/null)
|
|
192
|
+
if [[ -n "$cgroup" && -r "/sys/fs/cgroup${cgroup}/cgroup.procs" ]]; then
|
|
193
|
+
# Pick the PID whose RSS (in KB) is largest. ps -o rss= prints
|
|
194
|
+
# just the rss column; pair with -p PID-list to score them.
|
|
195
|
+
local pids
|
|
196
|
+
pids=$(tr '\n' ' ' < "/sys/fs/cgroup${cgroup}/cgroup.procs" 2>/dev/null)
|
|
197
|
+
if [[ -n "$pids" ]]; then
|
|
198
|
+
local heaviest
|
|
199
|
+
heaviest=$(ps -o pid=,rss= -p $pids 2>/dev/null \
|
|
200
|
+
| awk 'BEGIN{best_pid=0; best_rss=0} {if ($2+0 > best_rss) {best_rss=$2+0; best_pid=$1+0}} END{print best_pid}')
|
|
201
|
+
if [[ "${heaviest:-0}" -gt 0 ]]; then
|
|
202
|
+
echo "$heaviest"
|
|
203
|
+
return 0
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
207
|
+
systemctl --user show "$unit" -p MainPID --value 2>/dev/null || echo 0
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Process-state snapshot: state letter (R running, S sleeping, D
|
|
211
|
+
# uninterruptible sleep — usually I/O wait or kernel stuck, Z zombie,
|
|
212
|
+
# T stopped), CPU%, RSS in MB. State `D` for >30s is the smoking-gun
|
|
213
|
+
# signature of a genuinely wedged process (the original #116 hangs).
|
|
214
|
+
# Reads /proc/<pid>/stat for the state letter (field 3) and uses
|
|
215
|
+
# `ps -o` for CPU/RSS — both cheap and free of GNU/BSD portability
|
|
216
|
+
# pitfalls on Linux.
|
|
217
|
+
agent_proc_snapshot() {
|
|
218
|
+
local pid="$1"
|
|
219
|
+
if [[ -z "$pid" || "$pid" == "0" ]]; then
|
|
220
|
+
echo "pid=0 state=missing"
|
|
221
|
+
return 0
|
|
222
|
+
fi
|
|
223
|
+
if [[ ! -r "/proc/${pid}/stat" ]]; then
|
|
224
|
+
echo "pid=${pid} state=gone"
|
|
225
|
+
return 0
|
|
226
|
+
fi
|
|
227
|
+
# /proc/<pid>/stat field 3 is the state letter. The comm field
|
|
228
|
+
# (field 2) is parenthesized and may contain spaces — strip it
|
|
229
|
+
# before splitting so awk indexing is reliable.
|
|
230
|
+
local stat_state
|
|
231
|
+
stat_state=$(awk '{
|
|
232
|
+
line=$0;
|
|
233
|
+
sub(/.*\) /, "", line);
|
|
234
|
+
split(line, a, " ");
|
|
235
|
+
print a[1];
|
|
236
|
+
}' "/proc/${pid}/stat" 2>/dev/null || echo "?")
|
|
237
|
+
local cpu rss
|
|
238
|
+
read -r cpu rss < <(ps -o pcpu=,rss= -p "$pid" 2>/dev/null | awk '{print $1, $2}')
|
|
239
|
+
cpu="${cpu:-?}"
|
|
240
|
+
rss="${rss:-0}"
|
|
241
|
+
local rss_mb=$(( rss / 1024 ))
|
|
242
|
+
echo "pid=${pid} state=${stat_state} cpu=${cpu}% rss_mb=${rss_mb}"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Per-fingerprint freshness summary. Reports the age (in seconds) of
|
|
246
|
+
# the newest JSONL transcript and tasks-state file under the agent's
|
|
247
|
+
# `.claude/` tree. A wedged process shows both ages climbing past the
|
|
248
|
+
# threshold; a working sub-agent shows at least one stays small.
|
|
249
|
+
agent_progress_snapshot() {
|
|
250
|
+
local name="$1"
|
|
251
|
+
local agent_root="${HOME}/.switchroom/agents/${name}/.claude"
|
|
252
|
+
if [[ ! -d "$agent_root" ]]; then
|
|
253
|
+
echo "jsonl_age=- tasks_age=-"
|
|
254
|
+
return 0
|
|
255
|
+
fi
|
|
256
|
+
local now
|
|
257
|
+
now=$(now_epoch)
|
|
258
|
+
# Newest JSONL mtime (may be empty if no project history yet).
|
|
259
|
+
local newest_jsonl_mtime
|
|
260
|
+
newest_jsonl_mtime=$(find "${agent_root}/projects" -name '*.jsonl' \
|
|
261
|
+
-printf '%T@\n' 2>/dev/null | awk 'BEGIN{m=0} {if ($1+0 > m) m=$1+0} END{print int(m)}')
|
|
262
|
+
local newest_tasks_mtime
|
|
263
|
+
newest_tasks_mtime=$(find "${agent_root}/tasks" -name '*.json' \
|
|
264
|
+
-printf '%T@\n' 2>/dev/null | awk 'BEGIN{m=0} {if ($1+0 > m) m=$1+0} END{print int(m)}')
|
|
265
|
+
local jsonl_age="-"
|
|
266
|
+
local tasks_age="-"
|
|
267
|
+
if [[ "${newest_jsonl_mtime:-0}" -gt 0 ]]; then
|
|
268
|
+
jsonl_age=$(( now - newest_jsonl_mtime ))s
|
|
269
|
+
fi
|
|
270
|
+
if [[ "${newest_tasks_mtime:-0}" -gt 0 ]]; then
|
|
271
|
+
tasks_age=$(( now - newest_tasks_mtime ))s
|
|
272
|
+
fi
|
|
273
|
+
echo "jsonl_age=${jsonl_age} tasks_age=${tasks_age}"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Compose the full forensic observation line: process state + the
|
|
277
|
+
# two progress fingerprints. Embedded in every action log message so
|
|
278
|
+
# the journal entry is self-contained — operators don't need to
|
|
279
|
+
# re-run probes after the fact (which would be useless if the
|
|
280
|
+
# process has been restarted in the meantime).
|
|
281
|
+
agent_observation() {
|
|
282
|
+
local name="$1"
|
|
283
|
+
local pid
|
|
284
|
+
pid=$(agent_main_pid "$name")
|
|
285
|
+
local proc progress
|
|
286
|
+
proc=$(agent_proc_snapshot "$pid")
|
|
287
|
+
progress=$(agent_progress_snapshot "$name")
|
|
288
|
+
echo "${proc} ${progress}"
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Stamp a clean-shutdown.json marker into the agent's telegram state
|
|
292
|
+
# dir BEFORE issuing a restart, so the next greeting card can render
|
|
293
|
+
# "Restarted <reason>". Mirrors the inline jq/printf logic that lived
|
|
294
|
+
# in the bridge-disconnect path; pulled into a function so every
|
|
295
|
+
# restart path stamps consistently. Best-effort: never fails.
|
|
296
|
+
stamp_restart_reason() {
|
|
297
|
+
local marker="$1"
|
|
298
|
+
local reason="$2"
|
|
299
|
+
local ts_ms
|
|
300
|
+
ts_ms=$(( $(date +%s) * 1000 ))
|
|
301
|
+
local tmp="${marker}.tmp-$$"
|
|
302
|
+
if command -v jq >/dev/null 2>&1; then
|
|
303
|
+
jq -n --argjson ts "$ts_ms" --arg reason "$reason" \
|
|
304
|
+
'{ts: $ts, signal: "SIGTERM", reason: $reason}' > "$tmp" 2>/dev/null \
|
|
305
|
+
&& mv -f "$tmp" "$marker" 2>/dev/null || rm -f "$tmp" 2>/dev/null || true
|
|
306
|
+
else
|
|
307
|
+
local esc_reason
|
|
308
|
+
esc_reason=$(printf '%s' "$reason" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
309
|
+
printf '{"ts":%s,"signal":"SIGTERM","reason":"%s"}' "$ts_ms" "$esc_reason" > "$tmp" 2>/dev/null \
|
|
310
|
+
&& mv -f "$tmp" "$marker" 2>/dev/null || rm -f "$tmp" 2>/dev/null || true
|
|
311
|
+
fi
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# ─── Crash-time tmux pane capture (#725 PR-2) ──────────────────────────
|
|
315
|
+
#
|
|
316
|
+
# Snapshot the agent's tmux pane scrollback to
|
|
317
|
+
# `<agentDir>/crash-reports/<ISO8601>-<reason>.txt` immediately
|
|
318
|
+
# before a watchdog-triggered restart. Gives RCA tooling the live
|
|
319
|
+
# screen state at the moment of the kill.
|
|
320
|
+
#
|
|
321
|
+
# Mirror of `src/agents/tmux.ts#captureAgentPane`. Same socket
|
|
322
|
+
# convention (`switchroom-<agent>`), same target session
|
|
323
|
+
# (`<agent>`), same output dir, same header. Keep the two paths in
|
|
324
|
+
# sync — RCA tooling reads from one stream regardless of which
|
|
325
|
+
# crash path produced the file.
|
|
326
|
+
#
|
|
327
|
+
# Best-effort: every step is `|| true`-ish so a missing socket /
|
|
328
|
+
# tmux / write failure NEVER blocks the restart. Operator-initiated
|
|
329
|
+
# restarts (`switchroom agent restart <agent>`) do NOT call this —
|
|
330
|
+
# only watchdog-triggered restart paths do, since clean restarts
|
|
331
|
+
# aren't crashes.
|
|
332
|
+
#
|
|
333
|
+
# Retention: 20 newest .txt files; size cap: 10MB per file
|
|
334
|
+
# (post-header bytes; tmux history-limit is 100k lines so worst-case
|
|
335
|
+
# ANSI-heavy panes can spike beyond that).
|
|
336
|
+
capture_pane_before_restart() {
|
|
337
|
+
local agent="$1"
|
|
338
|
+
local reason="$2"
|
|
339
|
+
local agent_dir="${HOME}/.switchroom/agents/${agent}"
|
|
340
|
+
local socket="switchroom-${agent}"
|
|
341
|
+
local out_dir="${agent_dir}/crash-reports"
|
|
342
|
+
local ts
|
|
343
|
+
ts="$(date -u +%Y-%m-%dT%H-%M-%SZ)"
|
|
344
|
+
local out="${out_dir}/${ts}-${reason}.txt"
|
|
345
|
+
mkdir -p "$out_dir" 2>/dev/null || true
|
|
346
|
+
{
|
|
347
|
+
printf '# agent: %s\n# reason: %s\n# captured-at: %s\n# tmux-socket: %s\n\n' \
|
|
348
|
+
"$agent" "$reason" "$ts" "$socket"
|
|
349
|
+
timeout 5 tmux -L "$socket" capture-pane -p -S - -t "$agent" 2>&1 \
|
|
350
|
+
| head -c 10485760 \
|
|
351
|
+
|| echo "[capture-pane failed: $?]"
|
|
352
|
+
} > "$out" 2>/dev/null || true
|
|
353
|
+
# Retention: keep newest 20 .txt files in the dir.
|
|
354
|
+
ls -1t "$out_dir"/*.txt 2>/dev/null | tail -n +21 | xargs -r rm -f 2>/dev/null || true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# ─── Restart rate cap ──────────────────────────────────────────────────
|
|
358
|
+
#
|
|
359
|
+
# Belt-and-suspenders for runaway restart loops (#550 follow-up). Even
|
|
360
|
+
# with the in-flight detector + progress-fingerprint defences above,
|
|
361
|
+
# there are pathological combinations (e.g. a stuck marker file the
|
|
362
|
+
# sweep can't clear, a bridge that ESTAB-flaps once a minute) where
|
|
363
|
+
# the watchdog could chew through Claude quota by restarting the same
|
|
364
|
+
# agent N times an hour — every restart loads model context fresh.
|
|
365
|
+
#
|
|
366
|
+
# Rule: a single agent cannot be restarted by THIS watchdog more than
|
|
367
|
+
# `MAX_RESTARTS_PER_WINDOW` times within `RESTART_RATE_WINDOW_SECS`.
|
|
368
|
+
# When the cap trips, the restart is logged-and-skipped with a clear
|
|
369
|
+
# `restart-rate-capped` reason so an operator can see the throttle
|
|
370
|
+
# fired in `journalctl -t switchroom-watchdog | grep rate-capped`.
|
|
371
|
+
#
|
|
372
|
+
# The cap covers ALL three restart paths (bridge-disconnect, turn-hang,
|
|
373
|
+
# journal-silence) plus the service-inactive heal — anything that
|
|
374
|
+
# would cost a fresh `claude` startup. systemd's own
|
|
375
|
+
# StartLimitBurst/IntervalSec is not enough on its own because each
|
|
376
|
+
# `switchroom agent restart` resets that counter.
|
|
377
|
+
#
|
|
378
|
+
# State file: `${WATCHDOG_STATE_DIR}/${agent}.restarts` — newline-
|
|
379
|
+
# separated epoch timestamps, trimmed to the window on every check.
|
|
380
|
+
# tmpfs → cleared on logout, which is what we want (don't carry a
|
|
381
|
+
# stale 30-min window across a reboot).
|
|
382
|
+
: "${MAX_RESTARTS_PER_WINDOW:=5}"
|
|
383
|
+
: "${RESTART_RATE_WINDOW_SECS:=1800}"
|
|
384
|
+
|
|
385
|
+
# Returns 0 (allow) when the agent is under the cap. Returns 1 (block)
|
|
386
|
+
# and emits a `[skip]` log line when the cap would be exceeded. Pure
|
|
387
|
+
# read — does NOT record the restart; call restart_rate_record on the
|
|
388
|
+
# allowed path.
|
|
389
|
+
restart_rate_check() {
|
|
390
|
+
local agent="$1"
|
|
391
|
+
local reason_tag="$2"
|
|
392
|
+
local rate_file="${WATCHDOG_STATE_DIR}/${agent}.restarts"
|
|
393
|
+
[[ -f "$rate_file" ]] || return 0
|
|
394
|
+
local now cutoff count=0
|
|
395
|
+
now=$(now_epoch)
|
|
396
|
+
cutoff=$(( now - RESTART_RATE_WINDOW_SECS ))
|
|
397
|
+
while IFS= read -r ts; do
|
|
398
|
+
[[ "$ts" =~ ^[0-9]+$ ]] || continue
|
|
399
|
+
(( ts >= cutoff )) && count=$(( count + 1 ))
|
|
400
|
+
done < "$rate_file"
|
|
401
|
+
if (( count >= MAX_RESTARTS_PER_WINDOW )); then
|
|
402
|
+
wd_log skip "agent=${agent} reason=${reason_tag} decision=restart-rate-capped recent=${count} max=${MAX_RESTARTS_PER_WINDOW} window=${RESTART_RATE_WINDOW_SECS}s (operator intervention required — investigate before clearing ${rate_file})"
|
|
403
|
+
return 1
|
|
404
|
+
fi
|
|
405
|
+
return 0
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# Append a restart timestamp and trim to the window. Best-effort I/O.
|
|
409
|
+
restart_rate_record() {
|
|
410
|
+
local agent="$1"
|
|
411
|
+
local rate_file="${WATCHDOG_STATE_DIR}/${agent}.restarts"
|
|
412
|
+
local now cutoff
|
|
413
|
+
now=$(now_epoch)
|
|
414
|
+
cutoff=$(( now - RESTART_RATE_WINDOW_SECS ))
|
|
415
|
+
local tmp="${rate_file}.tmp-$$"
|
|
416
|
+
{
|
|
417
|
+
if [[ -f "$rate_file" ]]; then
|
|
418
|
+
while IFS= read -r ts; do
|
|
419
|
+
[[ "$ts" =~ ^[0-9]+$ ]] || continue
|
|
420
|
+
(( ts >= cutoff )) && echo "$ts"
|
|
421
|
+
done < "$rate_file"
|
|
422
|
+
fi
|
|
423
|
+
echo "$now"
|
|
424
|
+
} > "$tmp" 2>/dev/null && mv -f "$tmp" "$rate_file" 2>/dev/null || rm -f "$tmp" 2>/dev/null || true
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# Discover active gateway units. systemd's list-units output includes only
|
|
428
|
+
# currently-loaded units; we filter to the switchroom-*-gateway.service
|
|
429
|
+
# pattern and strip the prefix/suffix to get the agent name.
|
|
430
|
+
mapfile -t gateway_services < <(
|
|
431
|
+
systemctl --user list-units --type=service --state=active --no-legend --plain 2>/dev/null \
|
|
432
|
+
| awk '{print $1}' \
|
|
433
|
+
| grep -E '^switchroom-.+-gateway\.service$' || true
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
if [[ ${#gateway_services[@]} -eq 0 ]]; then
|
|
437
|
+
# No active gateways — nothing to watch. Exit cleanly so the timer
|
|
438
|
+
# keeps firing; transient absences (deploy windows) shouldn't error.
|
|
439
|
+
exit 0
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
for gateway_svc in "${gateway_services[@]}"; do
|
|
443
|
+
# Extract agent name: switchroom-<agent>-gateway.service → <agent>
|
|
444
|
+
agent="${gateway_svc#switchroom-}"
|
|
445
|
+
agent="${agent%-gateway.service}"
|
|
446
|
+
agent_svc="switchroom-${agent}.service"
|
|
447
|
+
|
|
448
|
+
# Resolve the gateway's WorkingDirectory to locate its telegram state
|
|
449
|
+
# dir. The gateway's gateway.log lives under WorkingDirectory/gateway.log
|
|
450
|
+
# (the unit generator in src/agents/systemd.ts sets WorkingDirectory to
|
|
451
|
+
# the agent's telegram/ subdir; see generateGatewayUnit).
|
|
452
|
+
gateway_state_dir="$(
|
|
453
|
+
systemctl --user show "$gateway_svc" -p WorkingDirectory --value 2>/dev/null
|
|
454
|
+
)"
|
|
455
|
+
if [[ -z "$gateway_state_dir" ]]; then
|
|
456
|
+
wd_log error "agent=${agent} gateway has no WorkingDirectory; skipping"
|
|
457
|
+
continue
|
|
458
|
+
fi
|
|
459
|
+
gateway_log="${gateway_state_dir}/gateway.log"
|
|
460
|
+
# Sidecar file where we remember when the disconnected state started,
|
|
461
|
+
# so we can detect SUSTAINED disconnection across ticks. Lives in the
|
|
462
|
+
# same per-agent state dir so it's self-cleaning when an agent is
|
|
463
|
+
# removed.
|
|
464
|
+
disconnect_marker="${gateway_state_dir}/.watchdog-disconnect-since"
|
|
465
|
+
|
|
466
|
+
if [[ ! -f "$gateway_log" ]]; then
|
|
467
|
+
# Log file missing — gateway probably hasn't written a full turn yet.
|
|
468
|
+
# Skip this tick; we'll try again in 60s.
|
|
469
|
+
continue
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
# If the agent service itself is inactive but the gateway is up,
|
|
473
|
+
# treat that as a stale-bridge scenario too and restart it.
|
|
474
|
+
#
|
|
475
|
+
# Why: the agent service has `Restart=on-failure` in its unit (not
|
|
476
|
+
# `Restart=always`) so a clean 0-exit of start.sh leaves it inactive.
|
|
477
|
+
# That happens when Claude Code exits normally mid-session for any
|
|
478
|
+
# reason (including external kill that start.sh handles gracefully).
|
|
479
|
+
# Without this heal path the watchdog's earlier skip-if-inactive
|
|
480
|
+
# guard left agents dead indefinitely.
|
|
481
|
+
#
|
|
482
|
+
# Production incident: 2026-04-22 ~03:44 AEST clerk's start.sh
|
|
483
|
+
# exited with status=0/SUCCESS and the service went inactive. The
|
|
484
|
+
# gateway stayed up; bridge was disconnected; systemd did nothing.
|
|
485
|
+
if ! systemctl --user is-active --quiet "$agent_svc" 2>/dev/null; then
|
|
486
|
+
# Also skip if the service is marked failed (start-limit-hit etc.)
|
|
487
|
+
# — that needs operator intervention, not a restart loop.
|
|
488
|
+
state="$(systemctl --user show "$agent_svc" -p ActiveState --value 2>/dev/null)"
|
|
489
|
+
if [[ "$state" == "failed" ]]; then
|
|
490
|
+
wd_log skip "agent=${agent} reason=service-failed decision=needs-operator-reset state=${state} $(agent_progress_snapshot "$agent") (unit in failed state; needs operator reset-failed)"
|
|
491
|
+
continue
|
|
492
|
+
fi
|
|
493
|
+
if ! restart_rate_check "$agent" "service-inactive"; then
|
|
494
|
+
continue
|
|
495
|
+
fi
|
|
496
|
+
wd_log restart "agent=${agent} reason=service-inactive state=${state} action=start $(agent_progress_snapshot "$agent") (agent service is inactive)"
|
|
497
|
+
restart_rate_record "$agent"
|
|
498
|
+
systemctl --user start "$agent_svc" || {
|
|
499
|
+
wd_log error "agent=${agent} systemctl start failed"
|
|
500
|
+
}
|
|
501
|
+
continue
|
|
502
|
+
fi
|
|
503
|
+
|
|
504
|
+
# Uptime grace: freshly-started agents haven't had time to register
|
|
505
|
+
# their bridge yet. systemctl emits ActiveEnterTimestamp in a format
|
|
506
|
+
# like "Tue 2026-04-21 20:23:38 AEST"; ActiveEnterTimestampMonotonic
|
|
507
|
+
# is easier to parse (microseconds since boot) but comparing to
|
|
508
|
+
# wall-clock uptime is cross-platform-icky. We use the wall-clock
|
|
509
|
+
# field and parse it with `date -d`, which systemd's format supports.
|
|
510
|
+
active_enter_ts="$(
|
|
511
|
+
systemctl --user show "$agent_svc" -p ActiveEnterTimestamp --value 2>/dev/null
|
|
512
|
+
)"
|
|
513
|
+
if [[ -n "$active_enter_ts" ]]; then
|
|
514
|
+
# `date -d ""` fails; guard the empty case.
|
|
515
|
+
active_enter_epoch="$(date -d "$active_enter_ts" +%s 2>/dev/null || echo 0)"
|
|
516
|
+
if [[ "$active_enter_epoch" -gt 0 ]]; then
|
|
517
|
+
uptime_secs=$(( $(now_epoch) - active_enter_epoch ))
|
|
518
|
+
if [[ "$uptime_secs" -lt "$UPTIME_GRACE_SECS" ]]; then
|
|
519
|
+
# Agent just started — give it time to come up. Clear any
|
|
520
|
+
# stale disconnect marker from a previous cycle too, so the
|
|
521
|
+
# grace window really is a clean slate.
|
|
522
|
+
rm -f "$disconnect_marker" 2>/dev/null || true
|
|
523
|
+
continue
|
|
524
|
+
fi
|
|
525
|
+
fi
|
|
526
|
+
fi
|
|
527
|
+
|
|
528
|
+
# Check the IPC socket for an actual ESTAB connection from the
|
|
529
|
+
# agent's bridge. This is authoritative — if there's a live unix
|
|
530
|
+
# socket, the bridge is connected right now. If not, it isn't.
|
|
531
|
+
#
|
|
532
|
+
# Why not just grep the gateway log: log grep used to be the check,
|
|
533
|
+
# but it had a subtle bug. After a gateway restart, the log persists
|
|
534
|
+
# across the restart (the gateway's `tee $LOG_PATH` appends). The
|
|
535
|
+
# last "bridge registered" event might be from BEFORE the restart,
|
|
536
|
+
# so `tail -1` reports it as healthy even though the agent hasn't
|
|
537
|
+
# reconnected yet. Production incident 2026-04-22 ~07:20: clerk was
|
|
538
|
+
# stuck with 0 IPC connections but watchdog said healthy because
|
|
539
|
+
# the pre-restart "bridge registered" was the latest in the log.
|
|
540
|
+
#
|
|
541
|
+
# ss -x reads kernel-level socket state so it's immune to log
|
|
542
|
+
# staleness. Unix sockets are visible without sudo for the owner.
|
|
543
|
+
gateway_sock="${gateway_state_dir}/gateway.sock"
|
|
544
|
+
if [[ ! -S "$gateway_sock" ]]; then
|
|
545
|
+
# Socket file doesn't exist — gateway hasn't fully started or is
|
|
546
|
+
# shutting down. Skip this tick; try again in 60s.
|
|
547
|
+
continue
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
ipc_estab_count=$(
|
|
551
|
+
ss -x 2>/dev/null \
|
|
552
|
+
| awk -v sock="$gateway_sock" '$1 == "u_str" && $2 == "ESTAB" && index($0, sock) { n++ } END { print n+0 }'
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
if (( ipc_estab_count > 0 )); then
|
|
556
|
+
bridge_healthy=true
|
|
557
|
+
else
|
|
558
|
+
# ESTAB == 0: socket is disconnected. Before declaring the bridge dead,
|
|
559
|
+
# check the liveness file the bridge writes on every heartbeat tick (~5s).
|
|
560
|
+
# A recent mtime means the bridge process is alive but temporarily
|
|
561
|
+
# reconnecting (e.g. after a gateway restart) — restarting the agent
|
|
562
|
+
# here would be wasteful and would kill any in-flight Claude turn.
|
|
563
|
+
liveness_file="${gateway_state_dir}/.bridge-alive"
|
|
564
|
+
bridge_healthy=false
|
|
565
|
+
if [[ -f "$liveness_file" ]]; then
|
|
566
|
+
liveness_mtime=$(stat -c %Y "$liveness_file" 2>/dev/null || echo 0)
|
|
567
|
+
liveness_age=$(( $(now_epoch) - liveness_mtime ))
|
|
568
|
+
if (( liveness_age < LIVENESS_GRACE_SECS )); then
|
|
569
|
+
bridge_healthy=true
|
|
570
|
+
wd_log skip "agent=${agent} reason=bridge-socket-flap decision=liveness-file-fresh liveness_age=${liveness_age}s threshold=${LIVENESS_GRACE_SECS}s $(agent_observation "$agent") (liveness file is fresh)"
|
|
571
|
+
fi
|
|
572
|
+
fi
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
if [[ "$bridge_healthy" == true ]]; then
|
|
576
|
+
# Healthy — wipe the disconnect marker so the next disconnect
|
|
577
|
+
# starts a fresh grace window.
|
|
578
|
+
rm -f "$disconnect_marker" 2>/dev/null || true
|
|
579
|
+
continue
|
|
580
|
+
fi
|
|
581
|
+
|
|
582
|
+
# Disconnected. Has it been sustained long enough to act?
|
|
583
|
+
now="$(now_epoch)"
|
|
584
|
+
if [[ -f "$disconnect_marker" ]]; then
|
|
585
|
+
disc_since="$(cat "$disconnect_marker" 2>/dev/null || echo "$now")"
|
|
586
|
+
# Paranoia: if the file got corrupted (non-numeric), treat as now.
|
|
587
|
+
if ! [[ "$disc_since" =~ ^[0-9]+$ ]]; then
|
|
588
|
+
disc_since="$now"
|
|
589
|
+
echo "$now" > "$disconnect_marker"
|
|
590
|
+
fi
|
|
591
|
+
else
|
|
592
|
+
# First observation of disconnect on this tick. Record it and wait.
|
|
593
|
+
echo "$now" > "$disconnect_marker"
|
|
594
|
+
disc_since="$now"
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
disc_duration=$(( now - disc_since ))
|
|
598
|
+
if [[ "$disc_duration" -lt "$DISCONNECT_GRACE_SECS" ]]; then
|
|
599
|
+
# Transient flap — the bridge IPC disconnects across Claude Code
|
|
600
|
+
# turn boundaries. Don't restart yet; give it another tick or two.
|
|
601
|
+
continue
|
|
602
|
+
fi
|
|
603
|
+
|
|
604
|
+
# Progress gate — same defence as turn-hang/journal-silence. A
|
|
605
|
+
# bridge can flap (MCP plugin crash, transient socket close)
|
|
606
|
+
# while a sub-agent is still doing real work. Without this gate
|
|
607
|
+
# the bridge-disconnect path would kill any in-flight sub-agent
|
|
608
|
+
# whenever the bridge had a bad minute. Skip the restart if any
|
|
609
|
+
# forward-progress fingerprint is fresh and just keep the
|
|
610
|
+
# disconnect marker around — next tick will re-evaluate.
|
|
611
|
+
observation=$(agent_observation "$agent")
|
|
612
|
+
if agent_has_recent_progress "$agent" "$JSONL_LIVENESS_SECS"; then
|
|
613
|
+
wd_log skip "agent=${agent} reason=bridge-disconnect disc_duration=${disc_duration}s threshold=${DISCONNECT_GRACE_SECS}s decision=defer-progress-fresh ${observation}"
|
|
614
|
+
continue
|
|
615
|
+
fi
|
|
616
|
+
|
|
617
|
+
wd_log detect "agent=${agent} reason=bridge-disconnect disc_duration=${disc_duration}s threshold=${DISCONNECT_GRACE_SECS}s ${observation}"
|
|
618
|
+
if ! restart_rate_check "$agent" "bridge-disconnect"; then
|
|
619
|
+
continue
|
|
620
|
+
fi
|
|
621
|
+
wd_log restart "agent=${agent} reason=bridge-disconnect disc_duration=${disc_duration}s threshold=${DISCONNECT_GRACE_SECS}s ${observation}"
|
|
622
|
+
capture_pane_before_restart "$agent" "bridge-disconnect"
|
|
623
|
+
restart_rate_record "$agent"
|
|
624
|
+
# Clear the marker so post-restart we don't immediately re-trip on
|
|
625
|
+
# the still-old tail. The uptime grace will cover the startup window
|
|
626
|
+
# anyway, but removing the marker keeps state clean.
|
|
627
|
+
rm -f "$disconnect_marker" 2>/dev/null || true
|
|
628
|
+
# Stamp WHY before killing so the next agent greeting card can show
|
|
629
|
+
# "Restarted watchdog: bridge disconnected for ${disc_duration}s".
|
|
630
|
+
# The gateway's own SIGTERM handler writes `clean-shutdown.json` on
|
|
631
|
+
# shutdown too — but its marker carries no `reason`, so the greeting
|
|
632
|
+
# omits the row.
|
|
633
|
+
stamp_restart_reason \
|
|
634
|
+
"${gateway_state_dir}/clean-shutdown.json" \
|
|
635
|
+
"watchdog: bridge disconnected for ${disc_duration}s"
|
|
636
|
+
# Route through `switchroom agent restart` (not raw systemctl) for
|
|
637
|
+
# parity with the turn-hang and journal-silence paths: the CLI's
|
|
638
|
+
# in-flight guard is one more belt-and-suspenders check, and config
|
|
639
|
+
# reconciliation runs on every lifecycle transition per the project
|
|
640
|
+
# contract. Falls back to systemctl if the CLI isn't on PATH.
|
|
641
|
+
switchroom_cli=""
|
|
642
|
+
for candidate in "${HOME}/.bun/bin/switchroom" "${HOME}/.local/bin/switchroom"; do
|
|
643
|
+
if [[ -x "$candidate" ]]; then
|
|
644
|
+
switchroom_cli="$candidate"
|
|
645
|
+
break
|
|
646
|
+
fi
|
|
647
|
+
done
|
|
648
|
+
if [[ -z "$switchroom_cli" ]] && command -v switchroom >/dev/null 2>&1; then
|
|
649
|
+
switchroom_cli="$(command -v switchroom)"
|
|
650
|
+
fi
|
|
651
|
+
if [[ -n "$switchroom_cli" ]]; then
|
|
652
|
+
"$switchroom_cli" agent restart "$agent" || {
|
|
653
|
+
wd_log error "agent=${agent} switchroom agent restart failed; falling back to systemctl --user restart"
|
|
654
|
+
systemctl --user restart "$agent_svc" || true
|
|
655
|
+
}
|
|
656
|
+
else
|
|
657
|
+
wd_log error "agent=${agent} switchroom CLI not on PATH; using systemctl restart fallback"
|
|
658
|
+
systemctl --user restart "$agent_svc" || true
|
|
659
|
+
fi
|
|
660
|
+
done
|
|
661
|
+
|
|
662
|
+
# ─── Auth refresh tick ───────────────────────────────────────────────────────
|
|
663
|
+
#
|
|
664
|
+
# Wire `switchroom auth refresh-tick` into every watchdog cycle (issue #429
|
|
665
|
+
# Phase 1). The command is idempotent and cheap when tokens are healthy, so
|
|
666
|
+
# it's safe to run once per watchdog tick (≈60s).
|
|
667
|
+
#
|
|
668
|
+
# Two independently-tunable knobs (both default to 600, but for different
|
|
669
|
+
# reasons — coincidence, not coupling):
|
|
670
|
+
#
|
|
671
|
+
# AUTH_REFRESH_INTERVAL_SECS — how often the watchdog runs the CLI at all.
|
|
672
|
+
# Gated by a state-file timestamp; the CLI is skipped entirely until this
|
|
673
|
+
# many seconds have passed since the last run. Default 600s (10 min).
|
|
674
|
+
#
|
|
675
|
+
# AUTH_REFRESH_THRESHOLD_MS — how close to expiry a token must be before
|
|
676
|
+
# the CLI actually contacts the OAuth endpoint to refresh it. Passed as
|
|
677
|
+
# --threshold-ms. Default 600000 ms (10 min). Operators who want earlier
|
|
678
|
+
# proactive refreshes (e.g. 1800000 ms = 30 min) can raise this without
|
|
679
|
+
# touching the run cadence, and vice-versa.
|
|
680
|
+
#
|
|
681
|
+
# Disabled by setting WATCHDOG_REFRESH_AUTH=0 (default on).
|
|
682
|
+
: "${WATCHDOG_REFRESH_AUTH:=1}"
|
|
683
|
+
: "${AUTH_REFRESH_INTERVAL_SECS:=600}"
|
|
684
|
+
: "${AUTH_REFRESH_THRESHOLD_MS:=600000}"
|
|
685
|
+
|
|
686
|
+
if [[ "${WATCHDOG_REFRESH_AUTH}" == "1" ]]; then
|
|
687
|
+
auth_refresh_marker="${WATCHDOG_STATE_DIR}/.auth-refresh-last"
|
|
688
|
+
last_refresh=0
|
|
689
|
+
if [[ -f "$auth_refresh_marker" ]]; then
|
|
690
|
+
last_refresh="$(cat "$auth_refresh_marker" 2>/dev/null || echo 0)"
|
|
691
|
+
[[ "$last_refresh" =~ ^[0-9]+$ ]] || last_refresh=0
|
|
692
|
+
fi
|
|
693
|
+
now_for_auth="$(now_epoch)"
|
|
694
|
+
auth_age=$(( now_for_auth - last_refresh ))
|
|
695
|
+
if [[ "$auth_age" -ge "$AUTH_REFRESH_INTERVAL_SECS" ]]; then
|
|
696
|
+
# Resolve the switchroom CLI (same pattern as restart paths above).
|
|
697
|
+
switchroom_cli_auth=""
|
|
698
|
+
for candidate in "${HOME}/.bun/bin/switchroom" "${HOME}/.local/bin/switchroom"; do
|
|
699
|
+
if [[ -x "$candidate" ]]; then
|
|
700
|
+
switchroom_cli_auth="$candidate"
|
|
701
|
+
break
|
|
702
|
+
fi
|
|
703
|
+
done
|
|
704
|
+
if [[ -z "$switchroom_cli_auth" ]] && command -v switchroom >/dev/null 2>&1; then
|
|
705
|
+
switchroom_cli_auth="$(command -v switchroom)"
|
|
706
|
+
fi
|
|
707
|
+
if [[ -n "$switchroom_cli_auth" ]]; then
|
|
708
|
+
wd_log detect "auth-refresh age=${auth_age}s threshold=${AUTH_REFRESH_INTERVAL_SECS}s decision=run-refresh-tick"
|
|
709
|
+
if "$switchroom_cli_auth" auth refresh-tick --threshold-ms "${AUTH_REFRESH_THRESHOLD_MS}" >/dev/null 2>&1; then
|
|
710
|
+
echo "$now_for_auth" > "$auth_refresh_marker"
|
|
711
|
+
wd_log skip "auth-refresh decision=tick-complete threshold_ms=${AUTH_REFRESH_THRESHOLD_MS}"
|
|
712
|
+
else
|
|
713
|
+
wd_log error "auth-refresh switchroom auth refresh-tick exited non-zero (partial failures are logged by the CLI; state file not updated)"
|
|
714
|
+
fi
|
|
715
|
+
else
|
|
716
|
+
wd_log error "auth-refresh switchroom CLI not on PATH; skipping refresh tick"
|
|
717
|
+
fi
|
|
718
|
+
fi
|
|
719
|
+
fi
|
|
720
|
+
|
|
721
|
+
# ─── Journal-silence check ───────────────────────────────────────────────────
|
|
722
|
+
#
|
|
723
|
+
# Independent of the bridge-disconnect check above. For each active
|
|
724
|
+
# switchroom-<agent>.service unit (NOT the gateway), verify that it has
|
|
725
|
+
# emitted at least one journal entry within JOURNAL_SILENCE_SECS. If an
|
|
726
|
+
# agent has been silent longer than that AND uptime has cleared
|
|
727
|
+
# UPTIME_GRACE_SECS, record a silence_since marker in the watchdog state
|
|
728
|
+
# dir. Once the marker is older than JOURNAL_SILENCE_HARD_SECS, restart
|
|
729
|
+
# via `switchroom agent restart <agent>` (the contracted reconcile+restart
|
|
730
|
+
# path; NOT raw systemctl restart, which would bypass switchroom's
|
|
731
|
+
# config reconciliation).
|
|
732
|
+
#
|
|
733
|
+
# Why `switchroom agent restart` rather than `systemctl --user restart`:
|
|
734
|
+
# the project contract is that all lifecycle transitions go through the
|
|
735
|
+
# switchroom CLI so that config reconciliation always runs. Raw systemctl
|
|
736
|
+
# calls skip that step and can leave units with stale unit files.
|
|
737
|
+
|
|
738
|
+
mapfile -t agent_services < <(
|
|
739
|
+
systemctl --user list-units --type=service --state=active --no-legend --plain 2>/dev/null \
|
|
740
|
+
| awk '{print $1}' \
|
|
741
|
+
| grep -E '^switchroom-.+\.service$' \
|
|
742
|
+
| grep -v -E '^switchroom-(gateway|vault-broker|foreman)\.service$' \
|
|
743
|
+
| grep -v -E '^switchroom-.+-gateway\.service$' \
|
|
744
|
+
| grep -v -E '^switchroom-.+-cron-[0-9]+\.service$' || true
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
for agent_svc in "${agent_services[@]}"; do
|
|
748
|
+
# Extract agent name: switchroom-<agent>.service → <agent>
|
|
749
|
+
agent="${agent_svc#switchroom-}"
|
|
750
|
+
agent="${agent%.service}"
|
|
751
|
+
|
|
752
|
+
silence_marker="${WATCHDOG_STATE_DIR}/${agent}.silence_since"
|
|
753
|
+
|
|
754
|
+
# Uptime grace: same logic as the bridge check. Fresh agents haven't
|
|
755
|
+
# had time to settle into a normal logging cadence.
|
|
756
|
+
active_enter_ts="$(
|
|
757
|
+
systemctl --user show "$agent_svc" -p ActiveEnterTimestamp --value 2>/dev/null
|
|
758
|
+
)"
|
|
759
|
+
if [[ -n "$active_enter_ts" ]]; then
|
|
760
|
+
active_enter_epoch="$(date -d "$active_enter_ts" +%s 2>/dev/null || echo 0)"
|
|
761
|
+
if [[ "$active_enter_epoch" -gt 0 ]]; then
|
|
762
|
+
uptime_secs=$(( $(now_epoch) - active_enter_epoch ))
|
|
763
|
+
if [[ "$uptime_secs" -lt "$UPTIME_GRACE_SECS" ]]; then
|
|
764
|
+
# Clear stale silence marker on fresh start so the grace window
|
|
765
|
+
# is a clean slate.
|
|
766
|
+
rm -f "$silence_marker" 2>/dev/null || true
|
|
767
|
+
continue
|
|
768
|
+
fi
|
|
769
|
+
fi
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
# Issue #412: turn-active marker hang detector. The gateway writes
|
|
773
|
+
# `<agentDir>/telegram/turn-active.json` at turn-start, bumps its
|
|
774
|
+
# mtime on every tool_use, and removes it on turn_complete. If the
|
|
775
|
+
# file is older than TURN_HANG_SECS, the agent is wedged mid-turn —
|
|
776
|
+
# distinguishable from healthy idle because healthy idle leaves no
|
|
777
|
+
# marker file at all. This closes the gap left when JOURNAL_SILENCE_SECS
|
|
778
|
+
# was raised to 4000s (PR #410) to kill chat-cadence false positives.
|
|
779
|
+
agent_state_dir="${HOME}/.switchroom/agents/${agent}/telegram"
|
|
780
|
+
turn_active_file="${agent_state_dir}/turn-active.json"
|
|
781
|
+
if [[ -f "$turn_active_file" ]]; then
|
|
782
|
+
turn_mtime=$(stat -c %Y "$turn_active_file" 2>/dev/null || echo 0)
|
|
783
|
+
if [[ "$turn_mtime" -gt 0 ]]; then
|
|
784
|
+
turn_age=$(( $(now_epoch) - turn_mtime ))
|
|
785
|
+
if [[ "$turn_age" -ge "$TURN_HANG_SECS" ]]; then
|
|
786
|
+
# Progress gate — sub-agent activity does NOT bump the
|
|
787
|
+
# parent's turn-active marker, so a stale marker plus fresh
|
|
788
|
+
# JSONL writes means a sub-agent (or the main turn) is doing
|
|
789
|
+
# real work and a restart would kill it mid-flight. This was
|
|
790
|
+
# the dominant false-positive path observed in the journal
|
|
791
|
+
# 2026-05-02 (finn/klanker restarted while sub-agents had
|
|
792
|
+
# `last activity: 0s ago` per the in-flight detector).
|
|
793
|
+
observation=$(agent_observation "$agent")
|
|
794
|
+
if agent_has_recent_progress "$agent" "$JSONL_LIVENESS_SECS"; then
|
|
795
|
+
wd_log skip "agent=${agent} reason=turn-hang turn_age=${turn_age}s threshold=${TURN_HANG_SECS}s decision=defer-progress-fresh ${observation}"
|
|
796
|
+
continue
|
|
797
|
+
fi
|
|
798
|
+
wd_log detect "agent=${agent} reason=turn-hang turn_age=${turn_age}s threshold=${TURN_HANG_SECS}s ${observation} (no progress fingerprints within ${JSONL_LIVENESS_SECS}s — wedged mid-turn)"
|
|
799
|
+
if ! restart_rate_check "$agent" "turn-hang"; then
|
|
800
|
+
continue
|
|
801
|
+
fi
|
|
802
|
+
# Stamp the reason BEFORE the restart so the next greeting
|
|
803
|
+
# card renders "Restarted watchdog: …".
|
|
804
|
+
stamp_restart_reason \
|
|
805
|
+
"${agent_state_dir}/clean-shutdown.json" \
|
|
806
|
+
"watchdog: turn-active marker stale ${turn_age}s with no JSONL activity"
|
|
807
|
+
wd_log restart "agent=${agent} reason=turn-hang turn_age=${turn_age}s threshold=${TURN_HANG_SECS}s ${observation}"
|
|
808
|
+
capture_pane_before_restart "$agent" "turn-hang"
|
|
809
|
+
restart_rate_record "$agent"
|
|
810
|
+
# Resolve the switchroom CLI (same belt-and-suspenders as below)
|
|
811
|
+
switchroom_cli=""
|
|
812
|
+
for candidate in "${HOME}/.bun/bin/switchroom" "${HOME}/.local/bin/switchroom"; do
|
|
813
|
+
if [[ -x "$candidate" ]]; then
|
|
814
|
+
switchroom_cli="$candidate"
|
|
815
|
+
break
|
|
816
|
+
fi
|
|
817
|
+
done
|
|
818
|
+
if [[ -z "$switchroom_cli" ]] && command -v switchroom >/dev/null 2>&1; then
|
|
819
|
+
switchroom_cli="$(command -v switchroom)"
|
|
820
|
+
fi
|
|
821
|
+
if [[ -n "$switchroom_cli" ]]; then
|
|
822
|
+
"$switchroom_cli" agent restart "$agent" || {
|
|
823
|
+
wd_log error "agent=${agent} switchroom agent restart failed; falling back to systemctl --user restart"
|
|
824
|
+
systemctl --user restart "$agent_svc" || true
|
|
825
|
+
}
|
|
826
|
+
else
|
|
827
|
+
wd_log error "agent=${agent} switchroom CLI not on PATH; using systemctl restart fallback"
|
|
828
|
+
systemctl --user restart "$agent_svc" || true
|
|
829
|
+
fi
|
|
830
|
+
# Restarted — skip remaining checks for this agent this tick.
|
|
831
|
+
continue
|
|
832
|
+
fi
|
|
833
|
+
fi
|
|
834
|
+
fi
|
|
835
|
+
|
|
836
|
+
# Read the timestamp of the most recent journal entry from this unit.
|
|
837
|
+
# --output=short-unix gives "EPOCH.USEC MESSAGE" format; we grab the
|
|
838
|
+
# leading integer epoch seconds.
|
|
839
|
+
latest_journal_line="$(
|
|
840
|
+
journalctl --user -u "$agent_svc" -n 1 --output=short-unix --no-pager 2>/dev/null || true
|
|
841
|
+
)"
|
|
842
|
+
latest_journal_epoch=0
|
|
843
|
+
if [[ -n "$latest_journal_line" ]]; then
|
|
844
|
+
# short-unix format: "1745632800.123456 hostname unit[pid]: message"
|
|
845
|
+
# Extract the leading epoch (integer part before the dot or space).
|
|
846
|
+
candidate="$(echo "$latest_journal_line" | awk '{print $1}' | cut -d. -f1)"
|
|
847
|
+
if [[ "$candidate" =~ ^[0-9]+$ ]]; then
|
|
848
|
+
latest_journal_epoch="$candidate"
|
|
849
|
+
fi
|
|
850
|
+
fi
|
|
851
|
+
|
|
852
|
+
now="$(now_epoch)"
|
|
853
|
+
if [[ "$latest_journal_epoch" -eq 0 ]]; then
|
|
854
|
+
# No journal entries at all — possibly a very new unit that hasn't
|
|
855
|
+
# logged yet. Treat conservatively: skip this tick (uptime grace
|
|
856
|
+
# should have caught a genuine fresh start above, so this branch
|
|
857
|
+
# mostly hits units that truly haven't logged due to a bug — still
|
|
858
|
+
# give them one tick of benefit of the doubt).
|
|
859
|
+
continue
|
|
860
|
+
fi
|
|
861
|
+
|
|
862
|
+
journal_age=$(( now - latest_journal_epoch ))
|
|
863
|
+
|
|
864
|
+
if [[ "$journal_age" -lt "$JOURNAL_SILENCE_SECS" ]]; then
|
|
865
|
+
# Journal is fresh — clear any stale silence marker and move on.
|
|
866
|
+
rm -f "$silence_marker" 2>/dev/null || true
|
|
867
|
+
continue
|
|
868
|
+
fi
|
|
869
|
+
|
|
870
|
+
# Recent-activity gate: only suspect a hang if the agent had log activity
|
|
871
|
+
# within RECENT_ACTIVITY_WINDOW_SECS. A genuinely idle agent (e.g. a
|
|
872
|
+
# personal agent that hasn't received a message in hours/days) has its
|
|
873
|
+
# latest journal entry far in the past — restarting it would just churn
|
|
874
|
+
# state for no reason. A hung agent, by contrast, was active before
|
|
875
|
+
# freezing, so its most recent entry is recent (within the window).
|
|
876
|
+
#
|
|
877
|
+
# Implementation: if `journal_age >= RECENT_ACTIVITY_WINDOW_SECS`, the
|
|
878
|
+
# latest entry is older than the window, so by definition there's no
|
|
879
|
+
# activity inside it. Treat as idle — clear any stale marker and skip.
|
|
880
|
+
if [[ "$journal_age" -ge "$RECENT_ACTIVITY_WINDOW_SECS" ]]; then
|
|
881
|
+
rm -f "$silence_marker" 2>/dev/null || true
|
|
882
|
+
continue
|
|
883
|
+
fi
|
|
884
|
+
|
|
885
|
+
# Journal has been silent for >= JOURNAL_SILENCE_SECS but the agent had
|
|
886
|
+
# activity within RECENT_ACTIVITY_WINDOW_SECS. Record the first
|
|
887
|
+
# observation so we can require sustained silence.
|
|
888
|
+
if [[ -f "$silence_marker" ]]; then
|
|
889
|
+
silence_since="$(cat "$silence_marker" 2>/dev/null || echo "$now")"
|
|
890
|
+
if ! [[ "$silence_since" =~ ^[0-9]+$ ]]; then
|
|
891
|
+
silence_since="$now"
|
|
892
|
+
echo "$now" > "$silence_marker"
|
|
893
|
+
fi
|
|
894
|
+
else
|
|
895
|
+
echo "$now" > "$silence_marker"
|
|
896
|
+
silence_since="$now"
|
|
897
|
+
wd_log detect "agent=${agent} reason=journal-silence journal_age=${journal_age}s threshold=${JOURNAL_SILENCE_SECS}s decision=record-silence-marker $(agent_observation "$agent") (will restart after ${JOURNAL_SILENCE_HARD_SECS}s of sustained silence)"
|
|
898
|
+
continue
|
|
899
|
+
fi
|
|
900
|
+
|
|
901
|
+
silence_duration=$(( now - silence_since ))
|
|
902
|
+
if [[ "$silence_duration" -lt "$JOURNAL_SILENCE_HARD_SECS" ]]; then
|
|
903
|
+
# Silence not yet sustained long enough to act.
|
|
904
|
+
continue
|
|
905
|
+
fi
|
|
906
|
+
|
|
907
|
+
# Progress gate — same defence as the turn-hang path. A silent
|
|
908
|
+
# agent journal can co-exist with a busy sub-agent (the parent's
|
|
909
|
+
# stdout goes quiet while the sub-agent runs). If JSONL or tasks
|
|
910
|
+
# writes are happening, real work is in progress; don't restart.
|
|
911
|
+
observation=$(agent_observation "$agent")
|
|
912
|
+
if agent_has_recent_progress "$agent" "$JSONL_LIVENESS_SECS"; then
|
|
913
|
+
wd_log skip "agent=${agent} reason=journal-silence journal_age=${journal_age}s silence_duration=${silence_duration}s threshold=${JOURNAL_SILENCE_HARD_SECS}s decision=defer-progress-fresh ${observation}"
|
|
914
|
+
rm -f "$silence_marker" 2>/dev/null || true
|
|
915
|
+
continue
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
# The agent has been journal-silent for >= JOURNAL_SILENCE_HARD_SECS
|
|
919
|
+
# AND has cleared the uptime grace AND has no progress fingerprints.
|
|
920
|
+
# This matches the production hang pattern (issue #116). Restart
|
|
921
|
+
# via the switchroom CLI.
|
|
922
|
+
wd_log detect "agent=${agent} reason=journal-silence journal_age=${journal_age}s silence_duration=${silence_duration}s threshold=${JOURNAL_SILENCE_HARD_SECS}s ${observation} (no progress fingerprints — wedged)"
|
|
923
|
+
if ! restart_rate_check "$agent" "journal-silence"; then
|
|
924
|
+
continue
|
|
925
|
+
fi
|
|
926
|
+
agent_state_dir="${HOME}/.switchroom/agents/${agent}/telegram"
|
|
927
|
+
stamp_restart_reason \
|
|
928
|
+
"${agent_state_dir}/clean-shutdown.json" \
|
|
929
|
+
"watchdog: journal silent for ${journal_age}s with no progress activity"
|
|
930
|
+
wd_log restart "agent=${agent} reason=journal-silence journal_age=${journal_age}s silence_duration=${silence_duration}s threshold=${JOURNAL_SILENCE_HARD_SECS}s ${observation}"
|
|
931
|
+
capture_pane_before_restart "$agent" "journal-silence"
|
|
932
|
+
restart_rate_record "$agent"
|
|
933
|
+
rm -f "$silence_marker" 2>/dev/null || true
|
|
934
|
+
|
|
935
|
+
# Use `switchroom agent restart` (not raw systemctl) — the project
|
|
936
|
+
# contract is that all agent lifecycle transitions go through the CLI
|
|
937
|
+
# so config reconciliation always runs.
|
|
938
|
+
#
|
|
939
|
+
# Belt-and-suspenders CLI resolution (issue #406): the systemd .service
|
|
940
|
+
# unit pins Environment=PATH=~/.bun/bin:..., but if a hand-installed
|
|
941
|
+
# legacy unit is still on disk the PATH may be empty. Probe the two
|
|
942
|
+
# known install locations directly before falling back to PATH lookup,
|
|
943
|
+
# so a silent PATH gap can't silently downgrade us to the systemctl
|
|
944
|
+
# fallback (which bypasses reconcile).
|
|
945
|
+
switchroom_cli=""
|
|
946
|
+
for candidate in "${HOME}/.bun/bin/switchroom" "${HOME}/.local/bin/switchroom"; do
|
|
947
|
+
if [[ -x "$candidate" ]]; then
|
|
948
|
+
switchroom_cli="$candidate"
|
|
949
|
+
break
|
|
950
|
+
fi
|
|
951
|
+
done
|
|
952
|
+
if [[ -z "$switchroom_cli" ]] && command -v switchroom >/dev/null 2>&1; then
|
|
953
|
+
switchroom_cli="$(command -v switchroom)"
|
|
954
|
+
fi
|
|
955
|
+
|
|
956
|
+
if [[ -n "$switchroom_cli" ]]; then
|
|
957
|
+
"$switchroom_cli" agent restart "$agent" || {
|
|
958
|
+
wd_log error "agent=${agent} switchroom agent restart failed; falling back to systemctl --user restart"
|
|
959
|
+
systemctl --user restart "$agent_svc" || true
|
|
960
|
+
}
|
|
961
|
+
else
|
|
962
|
+
# Fallback: if the switchroom CLI isn't on PATH (unusual), use systemctl
|
|
963
|
+
# directly and log the degraded path.
|
|
964
|
+
wd_log error "agent=${agent} switchroom CLI not on PATH; using systemctl restart fallback"
|
|
965
|
+
systemctl --user restart "$agent_svc" || true
|
|
966
|
+
fi
|
|
967
|
+
done
|