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,1409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress card renderer โ event-driven alternative to PTY-snapshot streaming.
|
|
3
|
+
*
|
|
4
|
+
* Turns a stream of `SessionEvent`s (from session-tail.ts) into a stable,
|
|
5
|
+
* flicker-free Telegram HTML message that edits in place as the turn
|
|
6
|
+
* progresses. Solves the root cause of the PTY-stream flicker: Ink's
|
|
7
|
+
* differential re-renders mutate the last line multiple times per tool
|
|
8
|
+
* call, so snapshot-edit based on TUI text wobbles. Event reducer only
|
|
9
|
+
* mutates state on *transitions* (tool start, tool finish, stage change),
|
|
10
|
+
* so nothing above the active line ever rewrites.
|
|
11
|
+
*
|
|
12
|
+
* Pure functions โ no IO, no globals, no timers. The outer loop owns
|
|
13
|
+
* flush cadence (500ms hard floor between edits, coalesce 400ms bursts,
|
|
14
|
+
* fire immediately on stage change).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { SessionEvent } from './session-tail.js'
|
|
18
|
+
import { toolLabel, isHumanDescription } from './tool-labels.js'
|
|
19
|
+
import {
|
|
20
|
+
formatDuration as sharedFormatDuration,
|
|
21
|
+
escapeHtml as sharedEscapeHtml,
|
|
22
|
+
truncate as sharedTruncate,
|
|
23
|
+
} from './card-format.js'
|
|
24
|
+
import { isBenignToolError } from './tool-error-filter.js'
|
|
25
|
+
import { renderTwoZoneCard } from './two-zone-card.js'
|
|
26
|
+
import type { FleetMember } from './fleet-state.js'
|
|
27
|
+
|
|
28
|
+
// โโโ Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
29
|
+
|
|
30
|
+
export type ItemState = 'pending' | 'running' | 'done' | 'failed'
|
|
31
|
+
|
|
32
|
+
export interface ChecklistItem {
|
|
33
|
+
/** Index within the current turn โ sequential, stable. */
|
|
34
|
+
readonly id: number
|
|
35
|
+
/**
|
|
36
|
+
* Claude Code tool_use content-block id (e.g. "toolu_01ABCโฆ"). Used
|
|
37
|
+
* to pair the tool_result back to its tool_use by id rather than by
|
|
38
|
+
* running-item order โ required for correct handling of parallel
|
|
39
|
+
* tool_use calls within a single assistant message. Null when the
|
|
40
|
+
* session JSONL line omitted it (older event shape or synthetic test
|
|
41
|
+
* events), in which case the reducer falls back to FIFO pairing.
|
|
42
|
+
*/
|
|
43
|
+
readonly toolUseId: string | null
|
|
44
|
+
/** Claude Code tool name, e.g. "Read", "Bash", "Grep". */
|
|
45
|
+
readonly tool: string
|
|
46
|
+
/** Short human-readable label derived from the tool's input (file path,
|
|
47
|
+
* command, query, etc.). Empty string when the tool has no natural
|
|
48
|
+
* label (e.g. TodoWrite) or input was missing. */
|
|
49
|
+
readonly label: string
|
|
50
|
+
/**
|
|
51
|
+
* True when the label came from a human-authored `description` field
|
|
52
|
+
* (Bash/BashOutput/Task/Agent with a non-empty description). The
|
|
53
|
+
* renderer uses this to suppress the tool-name prefix so the card reads
|
|
54
|
+
* "Check commit state" instead of "Bash Check commit state".
|
|
55
|
+
*/
|
|
56
|
+
readonly humanAuthored: boolean
|
|
57
|
+
/** Current state. */
|
|
58
|
+
readonly state: ItemState
|
|
59
|
+
/** Unix ms when tool_use fired. */
|
|
60
|
+
readonly startedAt: number
|
|
61
|
+
/** Unix ms when tool_result arrived. Only set on done/failed. */
|
|
62
|
+
readonly finishedAt?: number
|
|
63
|
+
/**
|
|
64
|
+
* Multi-agent: for `Agent`/`Task` tool_use items only, the `agentId`
|
|
65
|
+
* of the correlated sub-agent. Set when `sub_agent_started` lands and
|
|
66
|
+
* matches this item's prompt text. Renderer (later PR) uses it to
|
|
67
|
+
* keep the [Main] line in ๐ค (not โ
) until the parent's tool_result
|
|
68
|
+
* arrives. Null until correlation succeeds.
|
|
69
|
+
*/
|
|
70
|
+
readonly spawnedAgentId?: string | null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type Stage = 'plan' | 'run' | 'done'
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Task-list item, mirroring the Claude Code `TodoWrite` tool's atomic
|
|
77
|
+
* todo schema. Populated by `tool_use` (or `sub_agent_tool_use`) events
|
|
78
|
+
* with `toolName === 'TodoWrite'`. Used by the per-agent card render to
|
|
79
|
+
* draw the โผ / โป / โ block under the status row.
|
|
80
|
+
*
|
|
81
|
+
* `content` is the imperative subject ("Refactor pin manager"); the
|
|
82
|
+
* card renders `activeForm` ("Refactoring pin manager") for the
|
|
83
|
+
* in-progress task and `content` for everything else.
|
|
84
|
+
*
|
|
85
|
+
* Token-count, thinking-duration, and the per-task elapsed counter are
|
|
86
|
+
* intentionally not tracked here โ those signals require ingestion
|
|
87
|
+
* changes (token counts aren't in the JSONL today; thinking is a
|
|
88
|
+
* boolean) and are deferred to a follow-up.
|
|
89
|
+
*/
|
|
90
|
+
export type TaskState = 'pending' | 'in_progress' | 'completed'
|
|
91
|
+
|
|
92
|
+
export interface TaskItem {
|
|
93
|
+
readonly content: string
|
|
94
|
+
readonly activeForm: string
|
|
95
|
+
readonly state: TaskState
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Multi-agent foundation (gated by PROGRESS_CARD_MULTI_AGENT=1):
|
|
100
|
+
*
|
|
101
|
+
* Per-sub-agent state, populated by the new `sub_agent_*` events. Today
|
|
102
|
+
* (in this PR) the renderer doesn't read any of it โ it lives alongside
|
|
103
|
+
* the existing per-tool checklist purely as a structural foundation. The
|
|
104
|
+
* later renderer PR consumes `subAgents` and `pendingAgentSpawns` to draw
|
|
105
|
+
* the two-section [Main] / [Sub-agents] card.
|
|
106
|
+
*
|
|
107
|
+
* `parentToolUseId` links a sub-agent back to the parent's `Agent`/`Task`
|
|
108
|
+
* tool_use that spawned it, established by prompt-text correlation in the
|
|
109
|
+
* correlation PR. Null while we haven't yet seen the parent (rare reverse
|
|
110
|
+
* race) or when correlation fails entirely (orphan).
|
|
111
|
+
*/
|
|
112
|
+
export interface SubAgentState {
|
|
113
|
+
readonly agentId: string
|
|
114
|
+
readonly description: string
|
|
115
|
+
readonly subagentType?: string
|
|
116
|
+
readonly parentToolUseId: string | null
|
|
117
|
+
readonly state: ItemState
|
|
118
|
+
readonly startedAt: number
|
|
119
|
+
readonly finishedAt?: number
|
|
120
|
+
readonly toolCount: number
|
|
121
|
+
/**
|
|
122
|
+
* Monotonically-increasing version counter bumped only on milestone
|
|
123
|
+
* transitions: sub-agent started, finished (done/failed). NOT bumped
|
|
124
|
+
* on per-tool ticks (sub_agent_tool_use, sub_agent_tool_result,
|
|
125
|
+
* sub_agent_text). The render layer uses this to avoid re-rendering
|
|
126
|
+
* the `<blockquote expandable>` section on every throttle tick, which
|
|
127
|
+
* would re-collapse the user's expanded view.
|
|
128
|
+
*/
|
|
129
|
+
readonly milestoneVersion: number
|
|
130
|
+
/**
|
|
131
|
+
* The first user-message text from the sub-agent's JSONL โ kept so the
|
|
132
|
+
* reverse-race adoption path (orphan first, parent later) can match
|
|
133
|
+
* against incoming pendingAgentSpawns entries.
|
|
134
|
+
*/
|
|
135
|
+
readonly firstPromptText?: string
|
|
136
|
+
readonly currentTool?: {
|
|
137
|
+
readonly tool: string
|
|
138
|
+
readonly label: string
|
|
139
|
+
readonly humanAuthored: boolean
|
|
140
|
+
readonly toolUseId: string
|
|
141
|
+
readonly startedAt: number
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Per-sub-agent analogue of ProgressCardState.pendingPreamble: the most
|
|
145
|
+
* recent single-line `text` block THIS sub-agent emitted that hasn't
|
|
146
|
+
* yet been paired to a `sub_agent_tool_use`. Set on every
|
|
147
|
+
* `sub_agent_text` event; consumed and cleared by the NEXT
|
|
148
|
+
* `sub_agent_tool_use` for the same agent (sibling tool_uses in the
|
|
149
|
+
* same batch get the filename fallback). Cleared on
|
|
150
|
+
* `sub_agent_turn_end` / `turn_end`. Lives per-agent โ a preamble from
|
|
151
|
+
* sub-agent A must not leak onto sub-agent B's tool_use.
|
|
152
|
+
*/
|
|
153
|
+
readonly pendingPreamble?: string | null
|
|
154
|
+
/**
|
|
155
|
+
* The sub-agent's first narrative/text line, captured on the first
|
|
156
|
+
* `sub_agent_text` event for this agent. Used as a description fallback
|
|
157
|
+
* when correlation fails (orphan sub-agents) so the user still sees
|
|
158
|
+
* something meaningful instead of "(uncorrelated)". Never cleared.
|
|
159
|
+
*/
|
|
160
|
+
readonly firstNarrativeText?: string
|
|
161
|
+
/**
|
|
162
|
+
* Most-recent narrative line pushed via the gateway's `progress_update`
|
|
163
|
+
* MCP tool (issue #305 Option A). Distinct from:
|
|
164
|
+
* - `firstNarrativeText` โ one-shot, used as description fallback
|
|
165
|
+
* - `pendingPreamble` โ one-shot pre-tool narration from session-tail
|
|
166
|
+
* `currentNarrative` is replace-on-each-call (last write wins). Cleared
|
|
167
|
+
* naturally on terminal-state render via the existing branch.
|
|
168
|
+
*/
|
|
169
|
+
readonly currentNarrative?: string | null
|
|
170
|
+
/**
|
|
171
|
+
* The tool most recently completed by this sub-agent. Captured on
|
|
172
|
+
* `sub_agent_tool_result` (before the toolUseId match clears
|
|
173
|
+
* `currentTool`). Used by the render fallback chain when the sub-agent
|
|
174
|
+
* is running-but-between-tools so we show the last thing it did rather
|
|
175
|
+
* than the bare "(idle)" string.
|
|
176
|
+
*/
|
|
177
|
+
readonly lastCompletedTool?: {
|
|
178
|
+
readonly tool: string
|
|
179
|
+
readonly label: string
|
|
180
|
+
readonly humanAuthored: boolean
|
|
181
|
+
readonly finishedAt: number
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Issue #352: ring buffer of the last 2 completed tools, ordered oldest
|
|
185
|
+
* first. Combined with `lastCompletedTool` and `currentTool` this lets
|
|
186
|
+
* the expandable section show up to 3 recent actions with strikethrough
|
|
187
|
+
* for completed items and a `โณ` arrow for the active one.
|
|
188
|
+
*
|
|
189
|
+
* Only `lastCompletedTool` is used for the between-tool fallback chain
|
|
190
|
+
* outside the expandable; `recentCompletedTools` is purely for the
|
|
191
|
+
* expandable view (issue #352).
|
|
192
|
+
*/
|
|
193
|
+
readonly recentCompletedTools: ReadonlyArray<{
|
|
194
|
+
readonly tool: string
|
|
195
|
+
readonly label: string
|
|
196
|
+
readonly humanAuthored: boolean
|
|
197
|
+
readonly finishedAt: number
|
|
198
|
+
}>
|
|
199
|
+
/** Sub-sub-agents observed (rendered as `(spawned N)` only, not as rows). */
|
|
200
|
+
readonly nestedSpawnCount: number
|
|
201
|
+
/**
|
|
202
|
+
* Gap 4 (cold-JSONL detection): wall-clock ms of the most recent sub-agent
|
|
203
|
+
* event (sub_agent_tool_use, sub_agent_tool_result, sub_agent_text, etc.).
|
|
204
|
+
* Set on every event that updates this sub-agent's state. When the driver
|
|
205
|
+
* observes that a running sub-agent's `lastEventAt` is more than
|
|
206
|
+
* `coldSubAgentThresholdMs` (default 30s) in the past, it synthesises a
|
|
207
|
+
* `sub_agent_turn_end` so the deferred-completion path can proceed.
|
|
208
|
+
*/
|
|
209
|
+
readonly lastEventAt?: number
|
|
210
|
+
/**
|
|
211
|
+
* TodoWrite-driven task list for the per-agent card render. Atomic
|
|
212
|
+
* replacement: each `sub_agent_tool_use` with `toolName === 'TodoWrite'`
|
|
213
|
+
* overwrites the slice with the parsed `input.todos` array. Empty
|
|
214
|
+
* until the sub-agent calls TodoWrite at least once.
|
|
215
|
+
*/
|
|
216
|
+
readonly tasks: ReadonlyArray<TaskItem>
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* A parent `Agent`/`Task` tool_use whose sub-agent JSONL hasn't appeared
|
|
221
|
+
* yet. Once `sub_agent_started` arrives with matching `firstPromptText`
|
|
222
|
+
* the entry is moved into `subAgents` and removed from this map.
|
|
223
|
+
*/
|
|
224
|
+
export interface PendingAgentSpawn {
|
|
225
|
+
readonly parentToolUseId: string
|
|
226
|
+
readonly description: string
|
|
227
|
+
readonly subagentType?: string
|
|
228
|
+
readonly promptText: string
|
|
229
|
+
readonly startedAt: number
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface NarrativeStep {
|
|
233
|
+
readonly id: number
|
|
234
|
+
readonly text: string
|
|
235
|
+
/**
|
|
236
|
+
* State machine:
|
|
237
|
+
* - `active`: the narrative step is currently the latest, actively narrating.
|
|
238
|
+
* - `done`: the step is complete (next text event or turn_end fired, and no
|
|
239
|
+
* background sub-agents are pending).
|
|
240
|
+
* - `awaiting-subagent`: the step dispatched one or more background
|
|
241
|
+
* sub-agents (Agent/Task tool_use) that haven't reached terminal state
|
|
242
|
+
* yet. Rendered identically to `active` (โ) so the card never shows
|
|
243
|
+
* "done" while sub-agents are still running. Transitions to `done` once
|
|
244
|
+
* all entries in `awaitingSubAgentIds` have completed.
|
|
245
|
+
*/
|
|
246
|
+
readonly state: 'done' | 'active' | 'awaiting-subagent'
|
|
247
|
+
readonly startedAt: number
|
|
248
|
+
readonly toolCount: number
|
|
249
|
+
/**
|
|
250
|
+
* Agent/Task `toolUseId`s from the parent turn that this narrative step
|
|
251
|
+
* triggered but whose sub-agents haven't yet been correlated (i.e. the
|
|
252
|
+
* `sub_agent_started` event hasn't landed yet). When correlation arrives,
|
|
253
|
+
* the entry migrates from here to `awaitingSubAgentIds`. Allows the step
|
|
254
|
+
* to know about in-flight spawns even before the sub-agent JSONL appears.
|
|
255
|
+
*/
|
|
256
|
+
readonly pendingAgentToolUseIds: ReadonlyArray<string>
|
|
257
|
+
/**
|
|
258
|
+
* `agentId`s of sub-agents spawned during this narrative step that are
|
|
259
|
+
* still running. When this set becomes empty and the step is in
|
|
260
|
+
* `awaiting-subagent` state, it flips to `done`.
|
|
261
|
+
*/
|
|
262
|
+
readonly awaitingSubAgentIds: ReadonlyArray<string>
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface ProgressCardState {
|
|
266
|
+
/** Unix ms when the turn started (enqueue event). 0 when idle. */
|
|
267
|
+
readonly turnStartedAt: number
|
|
268
|
+
/** User's inbound message text, truncated. */
|
|
269
|
+
readonly userRequest?: string
|
|
270
|
+
/** Ordered checklist items โ never reorder, only append and transition. */
|
|
271
|
+
readonly items: ReadonlyArray<ChecklistItem>
|
|
272
|
+
/** Current high-level stage. */
|
|
273
|
+
readonly stage: Stage
|
|
274
|
+
/** Whether the model is currently in a thinking block. */
|
|
275
|
+
readonly thinking: boolean
|
|
276
|
+
/** Latest short `text` content from the assistant (for the thought line). */
|
|
277
|
+
readonly latestText?: string
|
|
278
|
+
/**
|
|
279
|
+
* The most recent single-line `text` block the model emitted that
|
|
280
|
+
* hasn't yet been paired to a `tool_use`. Used by the file/search
|
|
281
|
+
* tools (Read/Write/Edit/Grep/Glob) to show the model's natural
|
|
282
|
+
* preamble ("I'll check foo.ts") instead of the filename fallback
|
|
283
|
+
* in the checklist. Set on every `text` event; consumed and cleared
|
|
284
|
+
* by the NEXT `tool_use` (so sibling tool_uses in the same batch do
|
|
285
|
+
* NOT reuse it). Cleared unconditionally on `turn_end` / `enqueue`.
|
|
286
|
+
*/
|
|
287
|
+
readonly pendingPreamble?: string | null
|
|
288
|
+
/** Narrative steps derived from assistant text blocks. */
|
|
289
|
+
readonly narratives: ReadonlyArray<NarrativeStep>
|
|
290
|
+
/**
|
|
291
|
+
* Multi-agent: per-sub-agent state, keyed by `agentId` (sub-agent JSONL
|
|
292
|
+
* filename stem). Empty in single-agent turns. Always present so the
|
|
293
|
+
* shape is stable across flag-on / flag-off renders.
|
|
294
|
+
*/
|
|
295
|
+
readonly subAgents: ReadonlyMap<string, SubAgentState>
|
|
296
|
+
/**
|
|
297
|
+
* Multi-agent: parent Agent/Task tool_uses awaiting a sub-agent JSONL
|
|
298
|
+
* to correlate with. Keyed by the parent's `toolUseId`.
|
|
299
|
+
*/
|
|
300
|
+
readonly pendingAgentSpawns: ReadonlyMap<string, PendingAgentSpawn>
|
|
301
|
+
/**
|
|
302
|
+
* Parent-agent TodoWrite-driven task list for the per-agent card
|
|
303
|
+
* render. Atomic replacement: each `tool_use` with `toolName ===
|
|
304
|
+
* 'TodoWrite'` overwrites the slice with the parsed `input.todos`
|
|
305
|
+
* array. Empty until the parent calls TodoWrite at least once.
|
|
306
|
+
*/
|
|
307
|
+
readonly tasks: ReadonlyArray<TaskItem>
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* True when any sub-agent โ correlated or orphan โ is still running.
|
|
312
|
+
*
|
|
313
|
+
* Used as both the **display** gate (keep the card showing "Workingโฆ" with
|
|
314
|
+
* sub-agent rows) and the **defer** gate (hold `pendingCompletion` past
|
|
315
|
+
* parent turn_end so the card stays pinned until the last sub-agent reports
|
|
316
|
+
* done). Orphans (parentToolUseId == null, e.g. from
|
|
317
|
+
* `Agent({run_in_background: true})`) gate both, so background dispatches
|
|
318
|
+
* stay visible past parent turn-end (#87).
|
|
319
|
+
*
|
|
320
|
+
* Historical context: an earlier design excluded orphans from the defer
|
|
321
|
+
* gate because their `sub_agent_turn_end` could go missing if the parent
|
|
322
|
+
* turn rolled over (ghost-pin risk, #31 / #43). That risk is now bounded
|
|
323
|
+
* by `closeZombie` on next enqueue + the `maxIdleMs` heartbeat ceiling, so
|
|
324
|
+
* orphans gate the defer like correlated sub-agents do.
|
|
325
|
+
*/
|
|
326
|
+
export function hasAnyRunningSubAgent(state: ProgressCardState): boolean {
|
|
327
|
+
for (const sa of state.subAgents.values()) {
|
|
328
|
+
if (sa.state === 'running') return true
|
|
329
|
+
}
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function initialState(): ProgressCardState {
|
|
334
|
+
return {
|
|
335
|
+
turnStartedAt: 0,
|
|
336
|
+
items: [],
|
|
337
|
+
narratives: [],
|
|
338
|
+
stage: 'plan',
|
|
339
|
+
thinking: false,
|
|
340
|
+
subAgents: new Map(),
|
|
341
|
+
pendingAgentSpawns: new Map(),
|
|
342
|
+
tasks: [],
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Parse a `TodoWrite` tool_use input into a `TaskItem[]`. Returns null
|
|
348
|
+
* when the input shape doesn't match (no array, malformed entries) so
|
|
349
|
+
* the caller can leave the existing tasks slice unchanged. Callers
|
|
350
|
+
* should treat null as "not a recognised TodoWrite payload" rather than
|
|
351
|
+
* "empty list" โ TodoWrite never legitimately fires with no todos
|
|
352
|
+
* (it's an atomic-replace tool).
|
|
353
|
+
*/
|
|
354
|
+
export function parseTodoWriteInput(
|
|
355
|
+
input: Record<string, unknown> | undefined,
|
|
356
|
+
): TaskItem[] | null {
|
|
357
|
+
if (input == null) return null
|
|
358
|
+
const raw = (input as { todos?: unknown }).todos
|
|
359
|
+
if (!Array.isArray(raw)) return null
|
|
360
|
+
const out: TaskItem[] = []
|
|
361
|
+
for (const item of raw) {
|
|
362
|
+
if (item == null || typeof item !== 'object') continue
|
|
363
|
+
const o = item as Record<string, unknown>
|
|
364
|
+
const content = typeof o.content === 'string' ? o.content : null
|
|
365
|
+
const activeForm = typeof o.activeForm === 'string' ? o.activeForm : null
|
|
366
|
+
const status = typeof o.status === 'string' ? o.status : null
|
|
367
|
+
if (content == null || activeForm == null) continue
|
|
368
|
+
const state: TaskState =
|
|
369
|
+
status === 'in_progress' ? 'in_progress'
|
|
370
|
+
: status === 'completed' ? 'completed'
|
|
371
|
+
: 'pending'
|
|
372
|
+
out.push({ content, activeForm, state })
|
|
373
|
+
}
|
|
374
|
+
return out
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Multi-agent sub-section in progress cards. Always enabled โ the two-
|
|
379
|
+
* section [Main]/[Sub-agents] layout activates automatically when sub-
|
|
380
|
+
* agent events are present, and is invisible otherwise. Can be forced
|
|
381
|
+
* off with PROGRESS_CARD_MULTI_AGENT=0 for debugging.
|
|
382
|
+
*/
|
|
383
|
+
export function isMultiAgentEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
384
|
+
return env.PROGRESS_CARD_MULTI_AGENT !== '0'
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// โโโ Reducer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Decide what state an `active` NarrativeStep should transition to when it
|
|
391
|
+
* would normally flip to `done` (on a new `text` event or `turn_end`).
|
|
392
|
+
*
|
|
393
|
+
* If the narrative has dispatched background sub-agents that are still
|
|
394
|
+
* running (i.e. `awaitingSubAgentIds` overlap with sub-agents in `running`
|
|
395
|
+
* state, or `pendingAgentToolUseIds` haven't yet been correlated), we keep
|
|
396
|
+
* it in `awaiting-subagent` rather than immediately marking it `done`.
|
|
397
|
+
*
|
|
398
|
+
* Foreground Agent/Task calls complete before the tool_result returns, so
|
|
399
|
+
* they won't appear in `awaitingSubAgentIds` by the time we reach here โ
|
|
400
|
+
* those flip straight to `done` as before (#324 fix, no regression).
|
|
401
|
+
*/
|
|
402
|
+
function narrativeTransitionFromActive(
|
|
403
|
+
n: NarrativeStep,
|
|
404
|
+
subAgents: ReadonlyMap<string, SubAgentState>,
|
|
405
|
+
): NarrativeStep {
|
|
406
|
+
// Any still-running sub-agents this narrative is waiting for?
|
|
407
|
+
const hasRunningAwaited = n.awaitingSubAgentIds.some(
|
|
408
|
+
id => subAgents.get(id)?.state === 'running',
|
|
409
|
+
)
|
|
410
|
+
// Any agent tool_use that hasn't yet been correlated to a sub_agent_started?
|
|
411
|
+
// (Rare race: tool_use fired but sub_agent_started hasn't landed yet.)
|
|
412
|
+
const hasPendingCorrelation = n.pendingAgentToolUseIds.length > 0
|
|
413
|
+
if (hasRunningAwaited || hasPendingCorrelation) {
|
|
414
|
+
return { ...n, state: 'awaiting-subagent' }
|
|
415
|
+
}
|
|
416
|
+
return { ...n, state: 'done' }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function extractNarrativeLabel(text: string): string {
|
|
420
|
+
const trimmed = text.trim()
|
|
421
|
+
if (!trimmed) return ''
|
|
422
|
+
const line = trimmed.split('\n')[0]
|
|
423
|
+
return line.length > 200 ? line.slice(0, 199) + 'โฆ' : line
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Fold a single event into the state. Events outside the turn lifecycle
|
|
428
|
+
* (stale tool_result before enqueue, duplicate turn_end, etc.) are no-ops.
|
|
429
|
+
*/
|
|
430
|
+
export function reduce(
|
|
431
|
+
state: ProgressCardState,
|
|
432
|
+
event: SessionEvent,
|
|
433
|
+
now: number,
|
|
434
|
+
): ProgressCardState {
|
|
435
|
+
switch (event.kind) {
|
|
436
|
+
case 'enqueue': {
|
|
437
|
+
// New turn starts. Reset state entirely. Extract a short summary
|
|
438
|
+
// from the enqueue's raw content (strip the channel XML wrapper).
|
|
439
|
+
return {
|
|
440
|
+
...initialState(),
|
|
441
|
+
turnStartedAt: now,
|
|
442
|
+
userRequest: extractUserText(event.rawContent),
|
|
443
|
+
stage: 'plan',
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
case 'thinking': {
|
|
448
|
+
if (state.turnStartedAt === 0) return state
|
|
449
|
+
return { ...state, thinking: true, stage: state.stage === 'plan' ? 'plan' : state.stage }
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
case 'text': {
|
|
453
|
+
if (state.turnStartedAt === 0) return state
|
|
454
|
+
// Stash the raw text as a candidate preamble for the next
|
|
455
|
+
// tool_use. toolLabel() applies its own single-line + length
|
|
456
|
+
// gate, so we pass the full text through here and let the label
|
|
457
|
+
// layer decide. Multi-line narrative text will be rejected there
|
|
458
|
+
// and the filename/pattern fallback wins โ which is what we
|
|
459
|
+
// want for "here's my plan: <long paragraph>" style narration.
|
|
460
|
+
const pendingPreamble = event.text
|
|
461
|
+
const label = extractNarrativeLabel(event.text)
|
|
462
|
+
if (!label) {
|
|
463
|
+
return { ...state, latestText: event.text, thinking: false, pendingPreamble }
|
|
464
|
+
}
|
|
465
|
+
const prevNarratives = state.narratives.map(n =>
|
|
466
|
+
n.state === 'active' ? narrativeTransitionFromActive(n, state.subAgents) : n,
|
|
467
|
+
)
|
|
468
|
+
const newNarrative: NarrativeStep = {
|
|
469
|
+
id: prevNarratives.length,
|
|
470
|
+
text: label,
|
|
471
|
+
state: 'active',
|
|
472
|
+
startedAt: now,
|
|
473
|
+
toolCount: 0,
|
|
474
|
+
pendingAgentToolUseIds: [],
|
|
475
|
+
awaitingSubAgentIds: [],
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
...state,
|
|
479
|
+
narratives: [...prevNarratives, newNarrative],
|
|
480
|
+
latestText: event.text,
|
|
481
|
+
thinking: false,
|
|
482
|
+
pendingPreamble,
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case 'tool_use': {
|
|
487
|
+
if (state.turnStartedAt === 0) return state
|
|
488
|
+
// Append the new item as running. We do NOT defensively close out
|
|
489
|
+
// prior still-running items: Claude Code emits parallel tool_use
|
|
490
|
+
// blocks within a single assistant message (e.g. Bash + Read
|
|
491
|
+
// batched), and those arrive as separate SessionEvents. Prior
|
|
492
|
+
// logic that auto-closed running items on each new tool_use
|
|
493
|
+
// mis-paired the subsequent tool_results โ the first result would
|
|
494
|
+
// land on the WRONG item (by FIFO fallback) because its
|
|
495
|
+
// toolUseId-matched item had already been force-done. Pairing is
|
|
496
|
+
// now entirely up to tool_result (by toolUseId when available).
|
|
497
|
+
// Consume pendingPreamble exactly once โ the first tool_use after
|
|
498
|
+
// the text block pairs with it; any sibling tool_uses in the same
|
|
499
|
+
// assistant message fall back to the filename/pattern label. This
|
|
500
|
+
// is why we capture before building the item and clear it in the
|
|
501
|
+
// returned state below.
|
|
502
|
+
const preamble = state.pendingPreamble ?? undefined
|
|
503
|
+
const nextItem: ChecklistItem = {
|
|
504
|
+
id: state.items.length,
|
|
505
|
+
toolUseId: event.toolUseId ?? null,
|
|
506
|
+
tool: event.toolName,
|
|
507
|
+
label: toolLabel(event.toolName, event.input, preamble),
|
|
508
|
+
humanAuthored: isHumanDescription(event.toolName, event.input),
|
|
509
|
+
state: 'running',
|
|
510
|
+
startedAt: now,
|
|
511
|
+
}
|
|
512
|
+
// Multi-agent: if this is an Agent/Task tool_use, stage a pending
|
|
513
|
+
// spawn awaiting the matching sub-agent JSONL. Correlation key is
|
|
514
|
+
// the prompt text (the sub-agent's first user message contains
|
|
515
|
+
// exactly this string). Reverse-race: if a sub-agent already
|
|
516
|
+
// landed as orphan with this prompt text, adopt it now.
|
|
517
|
+
let pendingAgentSpawns = state.pendingAgentSpawns
|
|
518
|
+
let subAgents = state.subAgents
|
|
519
|
+
if (
|
|
520
|
+
(event.toolName === 'Agent' || event.toolName === 'Task') &&
|
|
521
|
+
event.toolUseId &&
|
|
522
|
+
event.input
|
|
523
|
+
) {
|
|
524
|
+
const promptText = String(event.input.prompt ?? '')
|
|
525
|
+
const description = String(event.input.description ?? '') || promptText.slice(0, 50)
|
|
526
|
+
const subagentType =
|
|
527
|
+
typeof event.input.subagent_type === 'string'
|
|
528
|
+
? (event.input.subagent_type as string)
|
|
529
|
+
: undefined
|
|
530
|
+
// Reverse-race adoption: scan orphan sub-agents (parentToolUseId
|
|
531
|
+
// null) for a prompt-text match. When multiple orphans match the
|
|
532
|
+
// same prompt (parallel Agent calls with identical `prompt`), we
|
|
533
|
+
// pair the oldest orphan first โ `startedAt` as tiebreaker rather
|
|
534
|
+
// than JS Map insertion order, which depends on JSONL file-watch
|
|
535
|
+
// delivery order and can scramble the pairing across concurrent
|
|
536
|
+
// sub-agent processes.
|
|
537
|
+
let adopted = false
|
|
538
|
+
let bestAgentId: string | null = null
|
|
539
|
+
let bestStartedAt = Number.POSITIVE_INFINITY
|
|
540
|
+
for (const [agentId, sa] of subAgents) {
|
|
541
|
+
if (sa.parentToolUseId == null && sa.firstPromptText === promptText) {
|
|
542
|
+
if (sa.startedAt < bestStartedAt) {
|
|
543
|
+
bestStartedAt = sa.startedAt
|
|
544
|
+
bestAgentId = agentId
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (bestAgentId != null) {
|
|
549
|
+
const sa = subAgents.get(bestAgentId)!
|
|
550
|
+
const next = new Map(subAgents)
|
|
551
|
+
next.set(bestAgentId, {
|
|
552
|
+
...sa,
|
|
553
|
+
parentToolUseId: event.toolUseId,
|
|
554
|
+
description,
|
|
555
|
+
subagentType,
|
|
556
|
+
})
|
|
557
|
+
subAgents = next
|
|
558
|
+
adopted = true
|
|
559
|
+
process.stderr.write(`telegram gateway: progress-card: tool_use โ agent correlation toolUseId=${event.toolUseId} agentId=${bestAgentId} (reverse-race adopt orphan)\n`)
|
|
560
|
+
}
|
|
561
|
+
if (!adopted) {
|
|
562
|
+
process.stderr.write(`telegram gateway: progress-card: tool_use โ agent correlation toolUseId=${event.toolUseId} agentId=pending (awaiting sub_agent_started)\n`)
|
|
563
|
+
const nextPending = new Map(pendingAgentSpawns)
|
|
564
|
+
nextPending.set(event.toolUseId, {
|
|
565
|
+
parentToolUseId: event.toolUseId,
|
|
566
|
+
description,
|
|
567
|
+
subagentType,
|
|
568
|
+
promptText,
|
|
569
|
+
startedAt: now,
|
|
570
|
+
})
|
|
571
|
+
pendingAgentSpawns = nextPending
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
let narratives = state.narratives
|
|
575
|
+
if (narratives.length > 0) {
|
|
576
|
+
const last = narratives[narratives.length - 1]
|
|
577
|
+
if (last.state === 'active') {
|
|
578
|
+
const isAgentCall =
|
|
579
|
+
(event.toolName === 'Agent' || event.toolName === 'Task') &&
|
|
580
|
+
!!event.toolUseId
|
|
581
|
+
const updatedLast: NarrativeStep = {
|
|
582
|
+
...last,
|
|
583
|
+
toolCount: last.toolCount + 1,
|
|
584
|
+
// When the active narrative just triggered a background Agent/Task
|
|
585
|
+
// call, record the toolUseId so that when sub_agent_started
|
|
586
|
+
// correlates it, we can link the sub-agent to this narrative step.
|
|
587
|
+
pendingAgentToolUseIds: isAgentCall
|
|
588
|
+
? [...last.pendingAgentToolUseIds, event.toolUseId!]
|
|
589
|
+
: last.pendingAgentToolUseIds,
|
|
590
|
+
}
|
|
591
|
+
narratives = [...narratives.slice(0, -1), updatedLast]
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Cap the raw item history. Only the last MAX_VISIBLE_ITEMS are
|
|
595
|
+
// rendered (see renderChecklist), and pairing of tool_result to
|
|
596
|
+
// tool_use happens by toolUseId โ not by position โ so keeping
|
|
597
|
+
// thousands of historical entries around only slows rendering and
|
|
598
|
+
// leaks memory on long turns. We keep ~10ร the visible window so
|
|
599
|
+
// pairings for results arriving after many intervening tool uses
|
|
600
|
+
// still find their running partner.
|
|
601
|
+
const ITEM_HISTORY_CAP = MAX_VISIBLE_ITEMS * 10
|
|
602
|
+
const appended = [...state.items, nextItem]
|
|
603
|
+
const boundedItems =
|
|
604
|
+
appended.length > ITEM_HISTORY_CAP
|
|
605
|
+
? appended.slice(appended.length - ITEM_HISTORY_CAP)
|
|
606
|
+
: appended
|
|
607
|
+
// TodoWrite is the atomic-replace task-list tool โ its input.todos
|
|
608
|
+
// is the canonical task-list state at this point in the turn. Lift
|
|
609
|
+
// it into a state slice so the per-agent card can render the
|
|
610
|
+
// โผ / โป / โ block. When the input shape doesn't match (older
|
|
611
|
+
// event shapes, synthetic test events without input) we leave the
|
|
612
|
+
// existing tasks slice untouched.
|
|
613
|
+
let tasks = state.tasks
|
|
614
|
+
if (event.toolName === 'TodoWrite') {
|
|
615
|
+
const parsed = parseTodoWriteInput(event.input)
|
|
616
|
+
if (parsed != null) tasks = parsed
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
...state,
|
|
620
|
+
items: boundedItems,
|
|
621
|
+
narratives,
|
|
622
|
+
stage: 'run',
|
|
623
|
+
thinking: false,
|
|
624
|
+
pendingAgentSpawns,
|
|
625
|
+
subAgents,
|
|
626
|
+
pendingPreamble: null,
|
|
627
|
+
tasks,
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
case 'tool_result': {
|
|
632
|
+
if (state.turnStartedAt === 0) return state
|
|
633
|
+
// Pair by tool_use_id when present: the model can emit parallel
|
|
634
|
+
// tool_use calls in a single assistant message, so FIFO pairing
|
|
635
|
+
// by running-item order is not sufficient. Falls back to the
|
|
636
|
+
// oldest running item when the result has no toolUseId or no
|
|
637
|
+
// running item matches (older JSONL shape, synthetic test events).
|
|
638
|
+
// is_error=true on the tool_result JSONL line flips state to
|
|
639
|
+
// 'failed' (โ).
|
|
640
|
+
let idx = -1
|
|
641
|
+
if (event.toolUseId) {
|
|
642
|
+
idx = state.items.findIndex(
|
|
643
|
+
(it) => it.state === 'running' && it.toolUseId === event.toolUseId,
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
if (idx === -1) {
|
|
647
|
+
idx = state.items.findIndex((it) => it.state === 'running')
|
|
648
|
+
}
|
|
649
|
+
if (idx === -1) return state
|
|
650
|
+
const items = state.items.slice()
|
|
651
|
+
// tool_result with is_error=true โ 'failed' (โ), unless the error
|
|
652
|
+
// text matches a benign pattern (file-not-found, no-match, etc) in
|
|
653
|
+
// which case render 'done' (โ
) โ see tool-error-filter.ts.
|
|
654
|
+
//
|
|
655
|
+
// Fail-closed semantics: when isError=true but errorText is missing
|
|
656
|
+
// or empty (older JSONL shapes, malformed lines, tools that error
|
|
657
|
+
// without output), keep the 'failed' state. Suppression requires
|
|
658
|
+
// *evidence* the error is benign; absence of evidence stays loud.
|
|
659
|
+
const nextState: ItemState =
|
|
660
|
+
event.isError === true
|
|
661
|
+
? (event.errorText && isBenignToolError(event.errorText) ? 'done' : 'failed')
|
|
662
|
+
: 'done'
|
|
663
|
+
items[idx] = { ...items[idx], state: nextState, finishedAt: now }
|
|
664
|
+
// Multi-agent: a parent Agent/Task tool_result is the authoritative
|
|
665
|
+
// close-out for its sub-agent. Find any sub-agent linked to this
|
|
666
|
+
// toolUseId (via parentToolUseId) and finalize it. Also clear any
|
|
667
|
+
// matching pendingAgentSpawn (sub-agent JSONL never appeared).
|
|
668
|
+
let subAgents = state.subAgents
|
|
669
|
+
let pendingAgentSpawns = state.pendingAgentSpawns
|
|
670
|
+
if (event.toolUseId) {
|
|
671
|
+
for (const [agentId, sa] of subAgents) {
|
|
672
|
+
if (sa.parentToolUseId === event.toolUseId) {
|
|
673
|
+
const next = new Map(subAgents)
|
|
674
|
+
// Bump milestoneVersion โ parent tool_result is a milestone transition.
|
|
675
|
+
next.set(agentId, {
|
|
676
|
+
...sa,
|
|
677
|
+
state: nextState,
|
|
678
|
+
finishedAt: now,
|
|
679
|
+
milestoneVersion: (sa.milestoneVersion ?? 0) + 1,
|
|
680
|
+
})
|
|
681
|
+
subAgents = next
|
|
682
|
+
break
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (pendingAgentSpawns.has(event.toolUseId)) {
|
|
686
|
+
const next = new Map(pendingAgentSpawns)
|
|
687
|
+
next.delete(event.toolUseId)
|
|
688
|
+
pendingAgentSpawns = next
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { ...state, items, subAgents, pendingAgentSpawns }
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
case 'sub_agent_started': {
|
|
695
|
+
if (state.turnStartedAt === 0) return state
|
|
696
|
+
// Already known? (Defensive โ re-attach can re-emit.)
|
|
697
|
+
if (state.subAgents.has(event.agentId)) return state
|
|
698
|
+
// Try to correlate by prompt-text against pendingAgentSpawns. On
|
|
699
|
+
// hit: move pending โ subAgents, link the matching [Main]
|
|
700
|
+
// ChecklistItem via spawnedAgentId, and consume the pending entry.
|
|
701
|
+
// On miss: register as orphan; the parent's tool_use may arrive
|
|
702
|
+
// later (reverse race) and adopt via the tool_use case.
|
|
703
|
+
let parentToolUseId: string | null = null
|
|
704
|
+
let description = '(uncorrelated)'
|
|
705
|
+
let subagentType: string | undefined
|
|
706
|
+
let pendingAgentSpawns = state.pendingAgentSpawns
|
|
707
|
+
let items = state.items
|
|
708
|
+
for (const [parentId, pending] of pendingAgentSpawns) {
|
|
709
|
+
if (pending.promptText === event.firstPromptText) {
|
|
710
|
+
parentToolUseId = parentId
|
|
711
|
+
description = pending.description
|
|
712
|
+
subagentType = pending.subagentType
|
|
713
|
+
const nextPending = new Map(pendingAgentSpawns)
|
|
714
|
+
nextPending.delete(parentId)
|
|
715
|
+
pendingAgentSpawns = nextPending
|
|
716
|
+
// Link the [Main] checklist item back so renderer can keep
|
|
717
|
+
// its ๐ค state consistent.
|
|
718
|
+
items = items.map((it) =>
|
|
719
|
+
it.toolUseId === parentId
|
|
720
|
+
? { ...it, spawnedAgentId: event.agentId }
|
|
721
|
+
: it,
|
|
722
|
+
)
|
|
723
|
+
break
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
const sub: SubAgentState = {
|
|
727
|
+
agentId: event.agentId,
|
|
728
|
+
description,
|
|
729
|
+
subagentType: subagentType ?? event.subagentType,
|
|
730
|
+
parentToolUseId,
|
|
731
|
+
state: 'running',
|
|
732
|
+
startedAt: now,
|
|
733
|
+
toolCount: 0,
|
|
734
|
+
firstPromptText: event.firstPromptText,
|
|
735
|
+
nestedSpawnCount: 0,
|
|
736
|
+
milestoneVersion: 1,
|
|
737
|
+
lastEventAt: now,
|
|
738
|
+
recentCompletedTools: [],
|
|
739
|
+
tasks: [],
|
|
740
|
+
}
|
|
741
|
+
const subAgents = new Map(state.subAgents)
|
|
742
|
+
subAgents.set(event.agentId, sub)
|
|
743
|
+
// Log correlation result. For orphans: include the promptText prefix
|
|
744
|
+
// and the count of pending spawns so callers can diagnose WHY the
|
|
745
|
+
// match failed (empty pendingAgentSpawns = no parent tool_use arrived
|
|
746
|
+
// yet; promptText mismatch = race between spawn and text delivery).
|
|
747
|
+
if (parentToolUseId != null) {
|
|
748
|
+
process.stderr.write(`telegram gateway: progress-card: sub_agent_started agentId=${event.agentId} correlated=yes parentToolUseId=${parentToolUseId}\n`)
|
|
749
|
+
} else {
|
|
750
|
+
const promptSnip = (event.firstPromptText ?? '').slice(0, 80).replace(/\n/g, ' ')
|
|
751
|
+
const pendingCount = state.pendingAgentSpawns.size
|
|
752
|
+
process.stderr.write(`telegram gateway: progress-card: sub_agent_started agentId=${event.agentId} correlated=orphan pendingSpawns=${pendingCount} promptSnip="${promptSnip}" โ NOTE: orphan sub-agents no longer gate parent turn_end defer (#31 fix)\n`)
|
|
753
|
+
}
|
|
754
|
+
// Gate parent narrative steps: if a narrative has a pendingAgentToolUseId
|
|
755
|
+
// matching this new sub-agent's parentToolUseId, migrate it from
|
|
756
|
+
// pendingAgentToolUseIds โ awaitingSubAgentIds so the narrative knows
|
|
757
|
+
// which agentId to watch for completion (fixes #324).
|
|
758
|
+
const narratives = parentToolUseId != null
|
|
759
|
+
? state.narratives.map(n => {
|
|
760
|
+
if (n.pendingAgentToolUseIds.includes(parentToolUseId)) {
|
|
761
|
+
return {
|
|
762
|
+
...n,
|
|
763
|
+
pendingAgentToolUseIds: n.pendingAgentToolUseIds.filter(id => id !== parentToolUseId),
|
|
764
|
+
awaitingSubAgentIds: [...n.awaitingSubAgentIds, event.agentId],
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return n
|
|
768
|
+
})
|
|
769
|
+
: state.narratives
|
|
770
|
+
return { ...state, subAgents, pendingAgentSpawns, items, narratives }
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
case 'sub_agent_text': {
|
|
774
|
+
// Per-sub-agent analogue of the parent `text` case: stash the raw
|
|
775
|
+
// text as a candidate preamble for THIS sub-agent's next
|
|
776
|
+
// sub_agent_tool_use. toolLabel() applies the single-line + length
|
|
777
|
+
// gate so we pass the full text through unfiltered. No-op if the
|
|
778
|
+
// sub-agent isn't known yet (defensive: sub_agent_started should
|
|
779
|
+
// always precede sub_agent_text in the same JSONL).
|
|
780
|
+
const sa = state.subAgents.get(event.agentId)
|
|
781
|
+
if (!sa) return state
|
|
782
|
+
const next = new Map(state.subAgents)
|
|
783
|
+
next.set(event.agentId, {
|
|
784
|
+
...sa,
|
|
785
|
+
pendingPreamble: event.text,
|
|
786
|
+
// Capture the first narrative line for the description-fallback
|
|
787
|
+
// chain. Once set, never overwrite โ we want the sub-agent's
|
|
788
|
+
// initial framing, not its latest chatter.
|
|
789
|
+
firstNarrativeText: sa.firstNarrativeText ?? event.text,
|
|
790
|
+
lastEventAt: now,
|
|
791
|
+
})
|
|
792
|
+
return { ...state, subAgents: next }
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
case 'sub_agent_narrative': {
|
|
796
|
+
// Issue #305 Option A: most-recent-wins narrative line pushed by the
|
|
797
|
+
// sub-agent via the gateway's `progress_update` MCP tool. Replace-only
|
|
798
|
+
// (last write wins); no milestoneVersion bump (per-tick update, not a
|
|
799
|
+
// structural transition). No-op if the sub-agent isn't known yet.
|
|
800
|
+
const sa = state.subAgents.get(event.agentId)
|
|
801
|
+
if (!sa) return state
|
|
802
|
+
const next = new Map(state.subAgents)
|
|
803
|
+
next.set(event.agentId, {
|
|
804
|
+
...sa,
|
|
805
|
+
currentNarrative: event.text,
|
|
806
|
+
lastEventAt: now,
|
|
807
|
+
})
|
|
808
|
+
return { ...state, subAgents: next }
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
case 'sub_agent_tool_use': {
|
|
812
|
+
const sa = state.subAgents.get(event.agentId)
|
|
813
|
+
if (!sa) return state
|
|
814
|
+
// Consume pendingPreamble exactly once โ same one-shot semantic as
|
|
815
|
+
// the parent path (3ad8436): the first sub_agent_tool_use after a
|
|
816
|
+
// sub_agent_text pairs with it; sibling tool_uses in the same
|
|
817
|
+
// assistant message fall back to filename/pattern.
|
|
818
|
+
const preamble = sa.pendingPreamble ?? undefined
|
|
819
|
+
// Mirror the parent tool_use TodoWrite handling: a sub-agent's
|
|
820
|
+
// TodoWrite atomically replaces its tasks slice for the per-agent
|
|
821
|
+
// card render.
|
|
822
|
+
let tasks = sa.tasks
|
|
823
|
+
if (event.toolName === 'TodoWrite') {
|
|
824
|
+
const parsed = parseTodoWriteInput(event.input)
|
|
825
|
+
if (parsed != null) tasks = parsed
|
|
826
|
+
}
|
|
827
|
+
const next = new Map(state.subAgents)
|
|
828
|
+
next.set(event.agentId, {
|
|
829
|
+
...sa,
|
|
830
|
+
// toolCount is incremented on sub_agent_tool_result (not here) so
|
|
831
|
+
// the count reflects completed tools โ matching the semantics the
|
|
832
|
+
// renderer surfaces as "N tools total" (Gap 5 fix, #316).
|
|
833
|
+
currentTool: event.toolUseId
|
|
834
|
+
? {
|
|
835
|
+
tool: event.toolName,
|
|
836
|
+
label: toolLabel(event.toolName, event.input, preamble),
|
|
837
|
+
humanAuthored: isHumanDescription(event.toolName, event.input),
|
|
838
|
+
toolUseId: event.toolUseId,
|
|
839
|
+
startedAt: now,
|
|
840
|
+
}
|
|
841
|
+
: sa.currentTool,
|
|
842
|
+
pendingPreamble: null,
|
|
843
|
+
lastEventAt: now,
|
|
844
|
+
tasks,
|
|
845
|
+
})
|
|
846
|
+
return { ...state, subAgents: next }
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
case 'sub_agent_tool_result': {
|
|
850
|
+
const sa = state.subAgents.get(event.agentId)
|
|
851
|
+
if (!sa) return state
|
|
852
|
+
// Per design ยง3.3: per-tool errors don't fail the agent; only the
|
|
853
|
+
// parent's tool_result does. We clear currentTool if it matches,
|
|
854
|
+
// AND stash it as lastCompletedTool so the render fallback chain
|
|
855
|
+
// can surface "just finished X" instead of a bare "(idle)" line
|
|
856
|
+
// while the sub-agent thinks between tools (Gap 6 fix, #316).
|
|
857
|
+
// toolCount increments here (on result, not on use) so the count
|
|
858
|
+
// reflects completed tools โ consistent with render semantics and
|
|
859
|
+
// the spec in the issue (Gap 5 fix, #316).
|
|
860
|
+
if (sa.currentTool && sa.currentTool.toolUseId === event.toolUseId) {
|
|
861
|
+
const justFinished = {
|
|
862
|
+
tool: sa.currentTool.tool,
|
|
863
|
+
label: sa.currentTool.label,
|
|
864
|
+
humanAuthored: sa.currentTool.humanAuthored,
|
|
865
|
+
finishedAt: now,
|
|
866
|
+
}
|
|
867
|
+
// Maintain a ring buffer of the last 2 completed tools for the
|
|
868
|
+
// expandable section (issue #352). Slide the window: drop the oldest
|
|
869
|
+
// when we're already at capacity (2), then append the new entry.
|
|
870
|
+
const prevRecent = sa.recentCompletedTools ?? []
|
|
871
|
+
const nextRecent = [...prevRecent, justFinished].slice(-2)
|
|
872
|
+
const next = new Map(state.subAgents)
|
|
873
|
+
next.set(event.agentId, {
|
|
874
|
+
...sa,
|
|
875
|
+
currentTool: undefined,
|
|
876
|
+
lastCompletedTool: justFinished,
|
|
877
|
+
recentCompletedTools: nextRecent,
|
|
878
|
+
toolCount: sa.toolCount + 1,
|
|
879
|
+
lastEventAt: now,
|
|
880
|
+
})
|
|
881
|
+
return { ...state, subAgents: next }
|
|
882
|
+
}
|
|
883
|
+
return state
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
case 'sub_agent_turn_end': {
|
|
887
|
+
const sa = state.subAgents.get(event.agentId)
|
|
888
|
+
if (!sa) return state
|
|
889
|
+
// Tentative close: parent's tool_result is still authoritative.
|
|
890
|
+
// If it later arrives with isError=true, the tool_result case
|
|
891
|
+
// overrides this 'done' with 'failed'. Clear any lingering
|
|
892
|
+
// pendingPreamble defensively โ mirrors the parent turn_end path.
|
|
893
|
+
// Bump milestoneVersion โ this is a milestone transition.
|
|
894
|
+
const next = new Map(state.subAgents)
|
|
895
|
+
next.set(event.agentId, {
|
|
896
|
+
...sa,
|
|
897
|
+
state: 'done',
|
|
898
|
+
finishedAt: now,
|
|
899
|
+
pendingPreamble: null,
|
|
900
|
+
milestoneVersion: (sa.milestoneVersion ?? 0) + 1,
|
|
901
|
+
})
|
|
902
|
+
// Gate parent narrative steps (#324): remove this agentId from any
|
|
903
|
+
// narrative step's awaitingSubAgentIds. If a step's awaiting list
|
|
904
|
+
// becomes empty (all sub-agents done) and the step is in
|
|
905
|
+
// `awaiting-subagent` state, flip it to `done`.
|
|
906
|
+
const narratives = state.narratives.map(n => {
|
|
907
|
+
if (!n.awaitingSubAgentIds.includes(event.agentId)) return n
|
|
908
|
+
const remaining = n.awaitingSubAgentIds.filter(id => id !== event.agentId)
|
|
909
|
+
// Keep pendingAgentToolUseIds in mind: those migrate to awaitingSubAgentIds
|
|
910
|
+
// when their sub_agent_started fires. Only flip to done when BOTH
|
|
911
|
+
// lists are empty.
|
|
912
|
+
const allDone = remaining.length === 0 && n.pendingAgentToolUseIds.length === 0
|
|
913
|
+
return {
|
|
914
|
+
...n,
|
|
915
|
+
awaitingSubAgentIds: remaining,
|
|
916
|
+
state: (n.state === 'awaiting-subagent' && allDone) ? ('done' as const) : n.state,
|
|
917
|
+
}
|
|
918
|
+
})
|
|
919
|
+
return { ...state, subAgents: next, narratives }
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
case 'sub_agent_nested_spawn': {
|
|
923
|
+
const sa = state.subAgents.get(event.agentId)
|
|
924
|
+
if (!sa) return state
|
|
925
|
+
const next = new Map(state.subAgents)
|
|
926
|
+
next.set(event.agentId, { ...sa, nestedSpawnCount: sa.nestedSpawnCount + 1 })
|
|
927
|
+
return { ...state, subAgents: next }
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
case 'turn_end': {
|
|
931
|
+
if (state.turnStartedAt === 0) return state
|
|
932
|
+
const items = state.items.map((it) =>
|
|
933
|
+
it.state === 'running' ? { ...it, state: 'done' as const, finishedAt: now } : it,
|
|
934
|
+
)
|
|
935
|
+
// Running sub-agents may outlive parent turn_end (common for background
|
|
936
|
+
// `Agent(run_in_background=true)` calls โ parent returns immediately
|
|
937
|
+
// but the sub-agent keeps working). Leave them in `state: 'running'`
|
|
938
|
+
// so their card surface stays informative, and let them close via
|
|
939
|
+
// their own `sub_agent_turn_end` event (or via the driver's
|
|
940
|
+
// abandonment path on maxIdle / enqueue-force-close). For sub-agents
|
|
941
|
+
// already done, clear pendingPreamble defensively.
|
|
942
|
+
const subAgents = new Map<string, SubAgentState>()
|
|
943
|
+
for (const [k, sa] of state.subAgents) {
|
|
944
|
+
if (sa.state === 'running') {
|
|
945
|
+
subAgents.set(k, sa)
|
|
946
|
+
} else {
|
|
947
|
+
subAgents.set(k, { ...sa, pendingPreamble: null })
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
// At turn_end, pass the up-to-date subAgents map (built above) so
|
|
951
|
+
// narrativeTransitionFromActive can see which sub-agents are still
|
|
952
|
+
// running. Active narratives that dispatched background sub-agents
|
|
953
|
+
// become `awaiting-subagent`; the rest become `done` (#324).
|
|
954
|
+
const narratives = state.narratives.map(n =>
|
|
955
|
+
n.state === 'active' ? narrativeTransitionFromActive(n, subAgents) : n,
|
|
956
|
+
)
|
|
957
|
+
return {
|
|
958
|
+
...state,
|
|
959
|
+
items,
|
|
960
|
+
narratives,
|
|
961
|
+
subAgents,
|
|
962
|
+
pendingAgentSpawns: new Map(),
|
|
963
|
+
stage: 'done',
|
|
964
|
+
thinking: false,
|
|
965
|
+
pendingPreamble: null,
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
case 'dequeue':
|
|
970
|
+
// No-op โ we key off enqueue + turn_end for the turn boundary.
|
|
971
|
+
return state
|
|
972
|
+
}
|
|
973
|
+
// Defensive: tsc can't prove exhaustiveness across the SessionEvent
|
|
974
|
+
// discriminated union when new kinds are added incrementally (#623
|
|
975
|
+
// strict-tsc enforcement on plugin source). Fall back to current
|
|
976
|
+
// state on any future unknown kind.
|
|
977
|
+
return state
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// โโโ Renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
981
|
+
|
|
982
|
+
const STEP_DONE = 'โ'
|
|
983
|
+
const STEP_ACTIVE = 'โ'
|
|
984
|
+
const STEP_FAILED = 'โ'
|
|
985
|
+
const STEP_PENDING = 'โ'
|
|
986
|
+
|
|
987
|
+
const TOOL_SYMBOL: Record<ItemState, string> = {
|
|
988
|
+
pending: STEP_PENDING,
|
|
989
|
+
running: STEP_ACTIVE,
|
|
990
|
+
done: STEP_DONE,
|
|
991
|
+
failed: STEP_FAILED,
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Max checklist lines to render inline. Older completed items collapse
|
|
996
|
+
* into a synthetic "(+N more earlier steps)" rollup line so the card
|
|
997
|
+
* stays compact during long turns. Chosen to fit comfortably on a
|
|
998
|
+
* mobile Telegram screen without scroll.
|
|
999
|
+
*/
|
|
1000
|
+
const MAX_VISIBLE_ITEMS = 5
|
|
1001
|
+
|
|
1002
|
+
// Re-export the shared formatters so existing callers (and the test
|
|
1003
|
+
// file `tests/progress-card.test.ts`) keep working. The implementation
|
|
1004
|
+
// lives in `./card-format.js` โ see issue #94.
|
|
1005
|
+
export const formatDuration = sharedFormatDuration
|
|
1006
|
+
const escapeHtml = sharedEscapeHtml
|
|
1007
|
+
const truncate = sharedTruncate
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Strip the `<channel โฆ>` XML wrapper (if present) from the enqueue raw
|
|
1011
|
+
* content, returning the plain user message text.
|
|
1012
|
+
*/
|
|
1013
|
+
function extractUserText(raw: string): string {
|
|
1014
|
+
// The enqueue raw content typically looks like:
|
|
1015
|
+
// <channel source="switchroom-telegram" chat_id="โฆ" โฆ>USER TEXT</channel>
|
|
1016
|
+
const m = raw.match(/<channel[^>]*>([\s\S]*?)<\/channel>/)
|
|
1017
|
+
const body = m ? m[1] : raw
|
|
1018
|
+
return body.trim()
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Render a single checklist line body: tool name + a short label hint.
|
|
1023
|
+
*
|
|
1024
|
+
* Format: `<code>tool</code> <code>label</code>` โ both tool name and
|
|
1025
|
+
* target argument use fixed-width formatting for scanability on mobile
|
|
1026
|
+
* narrow screens. The sub-agent `Agent` tool uses a colon separator
|
|
1027
|
+
* ("Agent: <description>") because the description is a phrase, not a
|
|
1028
|
+
* filename.
|
|
1029
|
+
*
|
|
1030
|
+
* `running` items bold the tool name so the eye jumps to the line that's
|
|
1031
|
+
* currently in flight.
|
|
1032
|
+
*/
|
|
1033
|
+
function renderItemCore(
|
|
1034
|
+
tool: string,
|
|
1035
|
+
label: string,
|
|
1036
|
+
bold = false,
|
|
1037
|
+
humanAuthored = false,
|
|
1038
|
+
): string {
|
|
1039
|
+
// MCP tools: the label from toolLabel() already begins with a
|
|
1040
|
+
// prettified "Server: action" form (from mcpBaseLabel), so echoing
|
|
1041
|
+
// the raw `mcp__server__action` tool name as a prefix just duplicates
|
|
1042
|
+
// the friendly name. Render the label alone. If label is empty
|
|
1043
|
+
// (malformed mcp__ name, no input keys to preview), fall through so
|
|
1044
|
+
// the raw tool name still appears rather than rendering nothing.
|
|
1045
|
+
//
|
|
1046
|
+
// humanAuthored: Bash/BashOutput/Task/Agent tool_use items whose label
|
|
1047
|
+
// came from input.description (a human-written phrase) rather than a
|
|
1048
|
+
// raw command / fallback. Suppress the tool-name prefix for the same
|
|
1049
|
+
// reason as MCP tools โ the description is already self-explanatory.
|
|
1050
|
+
if ((tool.startsWith('mcp__') || humanAuthored) && label) {
|
|
1051
|
+
return bold ? `<b>${escapeHtml(label)}</b>` : escapeHtml(label)
|
|
1052
|
+
}
|
|
1053
|
+
const toolHtml = bold ? `<b><code>${escapeHtml(tool)}</code></b>` : `<code>${escapeHtml(tool)}</code>`
|
|
1054
|
+
if (!label) return toolHtml
|
|
1055
|
+
const separator = tool === 'Agent' || tool === 'Task' ? ': ' : ' '
|
|
1056
|
+
return `${toolHtml}${separator}<code>${escapeHtml(label)}</code>`
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Cap the visible checklist at MAX_VISIBLE_ITEMS. When more items exist,
|
|
1061
|
+
* the OLDEST completed items collapse into a "(+N more earlier steps)"
|
|
1062
|
+
* synthetic line rendered by render(); any still-running item is always
|
|
1063
|
+
* kept visible, even if that means pushing the visible tail beyond the cap.
|
|
1064
|
+
*
|
|
1065
|
+
* Exported for tests.
|
|
1066
|
+
*/
|
|
1067
|
+
export function applyVisibleCap(
|
|
1068
|
+
items: ReadonlyArray<RolledItem>,
|
|
1069
|
+
): { items: RolledItem[]; overflowCount: number } {
|
|
1070
|
+
if (items.length <= MAX_VISIBLE_ITEMS) {
|
|
1071
|
+
return { items: items.slice(), overflowCount: 0 }
|
|
1072
|
+
}
|
|
1073
|
+
// Take the last N; anything before that is collapsed. Running items
|
|
1074
|
+
// tend to be at the tail (new tool_use appends), so this naturally
|
|
1075
|
+
// keeps them visible.
|
|
1076
|
+
const tail = items.slice(items.length - MAX_VISIBLE_ITEMS)
|
|
1077
|
+
const dropped = items.length - tail.length
|
|
1078
|
+
// Count the dropped items by their underlying `count` when rolled up,
|
|
1079
|
+
// so a collapsed "Read ร6" contributes 6 to the overflow count rather
|
|
1080
|
+
// than 1. Gives the user a meaningful "+N" signal.
|
|
1081
|
+
let overflow = 0
|
|
1082
|
+
for (let i = 0; i < dropped; i++) {
|
|
1083
|
+
overflow += items[i].count ?? 1
|
|
1084
|
+
}
|
|
1085
|
+
return { items: tail, overflowCount: overflow }
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Render the current state to Telegram HTML. `now` is the wall-clock time
|
|
1090
|
+
* used for elapsed-time calculations so the render is deterministic in tests.
|
|
1091
|
+
*
|
|
1092
|
+
* Multi-agent: when `PROGRESS_CARD_MULTI_AGENT=1` AND there is any sub-agent
|
|
1093
|
+
* activity (subAgents non-empty OR pendingAgentSpawns non-empty), the card
|
|
1094
|
+
* splits into [Main] / [Sub-agents] sections. Otherwise the layout is
|
|
1095
|
+
* byte-identical to the legacy single-section card.
|
|
1096
|
+
*/
|
|
1097
|
+
/**
|
|
1098
|
+
* Optional task-counter hint passed to render() by the driver when multiple
|
|
1099
|
+
* concurrent tasks are active in the same chat (e.g. parallel forum topics).
|
|
1100
|
+
* When provided, the header shows "(N/M)" so users can see "task 1 of 2".
|
|
1101
|
+
*/
|
|
1102
|
+
export interface TaskNum {
|
|
1103
|
+
/** 1-based position of this task among the active tasks. */
|
|
1104
|
+
index: number
|
|
1105
|
+
/** Total number of currently active tasks in the chat. */
|
|
1106
|
+
total: number
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Extra render hints the driver computes per flush. `stuckMs` is the
|
|
1111
|
+
* gap between the caller's clock and the last real session event that
|
|
1112
|
+
* updated this card. When it crosses STUCK_THRESHOLD_MS (2 min) the
|
|
1113
|
+
* renderer inserts a โ ๏ธ stuck-warning line under the header. Zombie
|
|
1114
|
+
* closure (driver `maxIdleMs`) still fires at its configured ceiling โ
|
|
1115
|
+
* the warning is the earlier, softer signal users see first.
|
|
1116
|
+
*/
|
|
1117
|
+
export interface RenderOptions {
|
|
1118
|
+
stuckMs?: number
|
|
1119
|
+
/**
|
|
1120
|
+
* Issue #132: when a turn ends without the agent ever calling
|
|
1121
|
+
* `reply` / `stream_reply`, the card should NOT render as "โ
Done"
|
|
1122
|
+
* (which the user reads as "agent acknowledged and replied") because
|
|
1123
|
+
* no user-visible text was produced. The driver tracks per-chat
|
|
1124
|
+
* "did a reply tool fire" and forwards the answer here so the
|
|
1125
|
+
* renderer can distinguish the silent-end case.
|
|
1126
|
+
*
|
|
1127
|
+
* When true and the turn is terminal, the header swaps to
|
|
1128
|
+
* "๐ Ended without reply" with a hint line suggesting `/restart` or
|
|
1129
|
+
* a rephrase. Has no effect while the turn is still running.
|
|
1130
|
+
*/
|
|
1131
|
+
silentEnd?: boolean
|
|
1132
|
+
/**
|
|
1133
|
+
* Issue #137: the agent DID call `reply` / `stream_reply` this turn
|
|
1134
|
+
* but no outbound message ever actually landed in the chat
|
|
1135
|
+
* (recordOutboundDelivered was never called for the card). Distinct
|
|
1136
|
+
* from silentEnd because the agent tried โ the failure is in the
|
|
1137
|
+
* delivery layer (MCP bridge instability, dropped streams, etc.),
|
|
1138
|
+
* not the model going mute.
|
|
1139
|
+
*
|
|
1140
|
+
* Mutually exclusive with silentEnd at the driver layer (replyNot-
|
|
1141
|
+
* Delivered requires replyToolCalled=true; silentEnd requires it
|
|
1142
|
+
* false), but the renderer guards with `!silentEnd` to be safe.
|
|
1143
|
+
* When true and the turn is terminal, the header swaps to
|
|
1144
|
+
* "โ ๏ธ Reply attempted but not delivered".
|
|
1145
|
+
*/
|
|
1146
|
+
replyNotDelivered?: boolean
|
|
1147
|
+
/**
|
|
1148
|
+
* Gap 8 (decoupled render and unpin): when true, the parent turn has
|
|
1149
|
+
* ended (turn_end received) but sub-agents are still running. The
|
|
1150
|
+
* renderer shows "โ
Done" in the parent header immediately rather than
|
|
1151
|
+
* "โ๏ธ Workingโฆ", while sub-agent rows still show their running state.
|
|
1152
|
+
* Distinct from `silentEnd` / `replyNotDelivered` โ those apply only
|
|
1153
|
+
* on true terminal state. This flag applies during the deferred-unpin
|
|
1154
|
+
* window.
|
|
1155
|
+
*/
|
|
1156
|
+
parentDone?: boolean
|
|
1157
|
+
/**
|
|
1158
|
+
* Gap 8 (stalled forced close): when true, the deferred-completion
|
|
1159
|
+
* timeout fired (sub-agents never reported done). Render a "stalled"
|
|
1160
|
+
* header rather than "โ
Done" to signal forced closure.
|
|
1161
|
+
*/
|
|
1162
|
+
stalledClose?: boolean
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Below this age the renderer treats the card as "fresh" and hides the
|
|
1167
|
+
* stuck-warning entirely. The 120s cutoff matches the spec in
|
|
1168
|
+
* `docs/pinned-progress-card-reliability.md` ยง5 F10.
|
|
1169
|
+
*/
|
|
1170
|
+
export const STUCK_THRESHOLD_MS = 2 * 60_000
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Cache entry for a sub-agent's `<blockquote expandable>` section. The
|
|
1174
|
+
* driver holds one of these per sub-agent and passes the whole map to
|
|
1175
|
+
* render() on each flush. When the sub-agent's `milestoneVersion` hasn't
|
|
1176
|
+
* changed, render() reuses the cached HTML instead of re-building it โ
|
|
1177
|
+
* this prevents the edit from touching the expandable section, so the
|
|
1178
|
+
* user's expanded view survives per-tool throttle ticks.
|
|
1179
|
+
*/
|
|
1180
|
+
export interface ExpandableCacheEntry {
|
|
1181
|
+
milestoneVersion: number
|
|
1182
|
+
html: string
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/** Keyed by agentId. */
|
|
1186
|
+
export type ExpandableCache = Map<string, ExpandableCacheEntry>
|
|
1187
|
+
|
|
1188
|
+
export function render(
|
|
1189
|
+
state: ProgressCardState,
|
|
1190
|
+
now: number,
|
|
1191
|
+
taskNum?: TaskNum,
|
|
1192
|
+
opts?: RenderOptions,
|
|
1193
|
+
expandableCache?: ExpandableCache,
|
|
1194
|
+
fleet?: ReadonlyMap<string, FleetMember>,
|
|
1195
|
+
): string {
|
|
1196
|
+
// P4 of #662 โ two-zone renderer is the ONLY renderer. The legacy
|
|
1197
|
+
// <blockquote expandable> path was deleted in the same PR (no env
|
|
1198
|
+
// flag remains). expandableCache is retained for caller compatibility
|
|
1199
|
+
// but the two-zone renderer does not consume it.
|
|
1200
|
+
void expandableCache
|
|
1201
|
+
return renderTwoZoneCard({ state, fleet: fleet ?? new Map(), now, taskNum, opts })
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function renderNarrativeChecklist(
|
|
1205
|
+
narratives: ReadonlyArray<NarrativeStep>,
|
|
1206
|
+
now: number,
|
|
1207
|
+
lines: string[],
|
|
1208
|
+
): void {
|
|
1209
|
+
if (narratives.length > MAX_VISIBLE_ITEMS) {
|
|
1210
|
+
const overflow = narratives.length - MAX_VISIBLE_ITEMS
|
|
1211
|
+
lines.push(`<i>(+${overflow} earlier)</i>`)
|
|
1212
|
+
}
|
|
1213
|
+
const visible = narratives.slice(-MAX_VISIBLE_ITEMS)
|
|
1214
|
+
for (const step of visible) {
|
|
1215
|
+
if (step.state === 'active' || step.state === 'awaiting-subagent') {
|
|
1216
|
+
const age = now - step.startedAt
|
|
1217
|
+
const dur = formatDuration(age)
|
|
1218
|
+
// When an active (or awaiting-subagent) narrative is older than the
|
|
1219
|
+
// stuck threshold, the "No events for X" banner will already be rendered
|
|
1220
|
+
// above. A confidently-bolded narrative with a ticking age next to it
|
|
1221
|
+
// sends mixed signals ("stuck" vs "actively working on X"). De-emphasise
|
|
1222
|
+
// the narrative to italic with a `stale` marker so the signals agree:
|
|
1223
|
+
// the last announced step, not necessarily what's running right now.
|
|
1224
|
+
if (age > STUCK_THRESHOLD_MS) {
|
|
1225
|
+
lines.push(`${STEP_ACTIVE} <i>${escapeHtml(step.text)} ยท stale (${dur})</i>`)
|
|
1226
|
+
} else {
|
|
1227
|
+
lines.push(`${STEP_ACTIVE} <b>${escapeHtml(step.text)}</b> <i>(${dur})</i>`)
|
|
1228
|
+
}
|
|
1229
|
+
} else {
|
|
1230
|
+
// #320: drop the <s>...</s> wrap on done items. Telegram desktop
|
|
1231
|
+
// renders strikethrough with a salmon/red strike-line in both
|
|
1232
|
+
// light and dark themes โ users read it as "deleted/failed/error",
|
|
1233
|
+
// not "done". The leading STEP_DONE bullet (โ) + the symbol
|
|
1234
|
+
// distinction (vs โ for active) + bold-vs-plain weight already
|
|
1235
|
+
// signal completion without the alarm. See #320 Option A.
|
|
1236
|
+
lines.push(`${STEP_DONE} ${escapeHtml(step.text)}`)
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Render one [Main]-section line. Encapsulates the existing per-state
|
|
1243
|
+
* branches (running/rollup/done) so the main render() loop reads cleanly.
|
|
1244
|
+
*
|
|
1245
|
+
* Multi-agent twist (Ken locked-in #4): an `Agent`/`Task` item with a
|
|
1246
|
+
* correlated, still-running sub-agent stays in the ๐ค emoji even if its
|
|
1247
|
+
* own state field already happens to be 'running' โ and we DON'T flip it
|
|
1248
|
+
* to โ
on a tentative `sub_agent_turn_end`. Only the parent's own
|
|
1249
|
+
* tool_result (which mutates `item.state` to 'done'/'failed') flips it.
|
|
1250
|
+
*/
|
|
1251
|
+
function renderMainItem(
|
|
1252
|
+
item: RolledItem,
|
|
1253
|
+
now: number,
|
|
1254
|
+
multiAgentActive: boolean,
|
|
1255
|
+
subAgents: ReadonlyMap<string, SubAgentState>,
|
|
1256
|
+
): string {
|
|
1257
|
+
const isAgent = item.tool === 'Agent' || item.tool === 'Task'
|
|
1258
|
+
const indent = multiAgentActive ? ' ' : ''
|
|
1259
|
+
|
|
1260
|
+
const humanAuthored = item.humanAuthored ?? false
|
|
1261
|
+
|
|
1262
|
+
// #378 sub-issue 1: when an Agent/Task item has a correlated, still-
|
|
1263
|
+
// alive sub-agent and the multi-agent renderer is active (i.e. the
|
|
1264
|
+
// sub-agent's expandable WILL be drawn below), the Main row would be
|
|
1265
|
+
// a duplicate (same ๐ค emoji, same description, same elapsed). Return
|
|
1266
|
+
// empty so the outer render loop skips this row. With multiAgentActive
|
|
1267
|
+
// off, the sub-agent expandable does NOT render โ fall through to the
|
|
1268
|
+
// normal Main-row render so the user still sees something.
|
|
1269
|
+
//
|
|
1270
|
+
// The "Main ยท N tools" header count is intentionally NOT decremented โ
|
|
1271
|
+
// it reflects "N tool calls happened this turn" as a lifetime count,
|
|
1272
|
+
// not visible rows.
|
|
1273
|
+
if (
|
|
1274
|
+
multiAgentActive
|
|
1275
|
+
&& isAgent
|
|
1276
|
+
&& item.kind !== 'rollup'
|
|
1277
|
+
&& item.state === 'running'
|
|
1278
|
+
&& item.spawnedAgentId
|
|
1279
|
+
&& subAgents.has(item.spawnedAgentId)
|
|
1280
|
+
) {
|
|
1281
|
+
return ''
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
if (isAgent && item.state === 'running' && multiAgentActive) {
|
|
1285
|
+
// Pre-correlation (or sub-agent already terminal), hold the ๐ค
|
|
1286
|
+
// emoji on the Main row. Show elapsed since the parent's tool_use
|
|
1287
|
+
// fired.
|
|
1288
|
+
const dur = formatDuration(now - item.startedAt)
|
|
1289
|
+
return `${indent}๐ค ${renderItemCore(item.tool, item.label, /*bold*/ true, humanAuthored)} <i>(${dur})</i>`
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const symbol = TOOL_SYMBOL[item.state]
|
|
1293
|
+
if (item.state === 'running') {
|
|
1294
|
+
const dur = formatDuration(now - item.startedAt)
|
|
1295
|
+
return `${indent}${symbol} ${renderItemCore(item.tool, item.label, /*bold*/ true, humanAuthored)} <i>(${dur})</i>`
|
|
1296
|
+
}
|
|
1297
|
+
if ((item.state === 'done' || item.state === 'failed') && item.finishedAt != null) {
|
|
1298
|
+
if (item.kind === 'rollup') {
|
|
1299
|
+
const labelHtml = item.label ? ` ${escapeHtml(item.label)}` : ''
|
|
1300
|
+
return `${indent}${symbol} ${escapeHtml(item.tool)}${labelHtml} <i>ร${item.count}</i>`
|
|
1301
|
+
}
|
|
1302
|
+
const dur = formatDuration(item.finishedAt - item.startedAt)
|
|
1303
|
+
const needsDuration = item.finishedAt - item.startedAt >= 1000
|
|
1304
|
+
// #320: no <s> wrap on done items here either. The symbol
|
|
1305
|
+
// distinction (โ vs โ) + the bold-vs-plain treatment already
|
|
1306
|
+
// differentiate done from active; strikethrough renders red in
|
|
1307
|
+
// Telegram desktop and reads as "deleted/failed". See #320
|
|
1308
|
+
// Option A โ this aligns the rolled-card path with the
|
|
1309
|
+
// narrative-checklist + sub-agent-expandable paths now that all
|
|
1310
|
+
// three drop strikethrough.
|
|
1311
|
+
return `${indent}${symbol} ${renderItemCore(item.tool, item.label, false, humanAuthored)}${needsDuration ? ` <i>(${dur})</i>` : ''}`
|
|
1312
|
+
}
|
|
1313
|
+
void subAgents
|
|
1314
|
+
return `${indent}${symbol} ${renderItemCore(item.tool, item.label, false, humanAuthored)}`
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Collapse runs of consecutive identical tools (e.g. a slurry of Reads)
|
|
1320
|
+
* into a single rollup item "<tool> [label] รN". Two thresholds apply:
|
|
1321
|
+
*
|
|
1322
|
+
* - ROLLUP_THRESHOLD (2): identical tool + identical label โ collapses to
|
|
1323
|
+
* "<tool> <label> รN", preserving the shared label in the rollup so the
|
|
1324
|
+
* user can still see "Read foo.ts ร3" instead of three identical lines.
|
|
1325
|
+
*
|
|
1326
|
+
* - MIXED_ROLLUP_THRESHOLD (3): identical tool, differing labels โ collapses
|
|
1327
|
+
* to "<tool> รN" (no label) when there are 3+ items. The label is dropped
|
|
1328
|
+
* because there is no single representative value, and showing one
|
|
1329
|
+
* arbitrarily would be misleading. Users see "Read ร4" (heuristic summary).
|
|
1330
|
+
*
|
|
1331
|
+
* Partial runs (any item still running) are never collapsed โ the running
|
|
1332
|
+
* item is always shown individually so the user can see live progress.
|
|
1333
|
+
*
|
|
1334
|
+
* Human-authored items are never collapsed into a bare "Tool รN" rollup (#41).
|
|
1335
|
+
* When any item in the run has `humanAuthored=true`, each is rendered
|
|
1336
|
+
* individually so the agent's natural-language descriptions remain visible.
|
|
1337
|
+
*/
|
|
1338
|
+
interface RolledItem extends ChecklistItem {
|
|
1339
|
+
readonly kind?: 'single' | 'rollup'
|
|
1340
|
+
readonly count?: number
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/** Minimum run length to collapse same-tool + same-label items. */
|
|
1344
|
+
const ROLLUP_THRESHOLD = 2
|
|
1345
|
+
/** Minimum run length to collapse same-tool, mixed-label items (C1 heuristic). */
|
|
1346
|
+
const MIXED_ROLLUP_THRESHOLD = 3
|
|
1347
|
+
|
|
1348
|
+
// Exported for tests.
|
|
1349
|
+
export function compactItems(items: ReadonlyArray<ChecklistItem>): RolledItem[] {
|
|
1350
|
+
const out: RolledItem[] = []
|
|
1351
|
+
let run: ChecklistItem[] = []
|
|
1352
|
+
|
|
1353
|
+
const flush = (): void => {
|
|
1354
|
+
if (run.length === 0) return
|
|
1355
|
+
const first = run[0]
|
|
1356
|
+
const last = run[run.length - 1]
|
|
1357
|
+
const allDone = run.every((r) => r.state === 'done')
|
|
1358
|
+
const sameLabel = run.every((r) => r.label === first.label)
|
|
1359
|
+
// Never collapse a run that contains any human-authored item (#41 fix).
|
|
1360
|
+
// Descriptions written by the agent ("Check commit state", "Run tests")
|
|
1361
|
+
// are valuable context โ collapsing them into "Bash รN" discards that
|
|
1362
|
+
// signal. Each human-authored item must appear as its own line.
|
|
1363
|
+
const anyHumanAuthored = run.some((r) => r.humanAuthored)
|
|
1364
|
+
|
|
1365
|
+
if (allDone && !anyHumanAuthored && sameLabel && run.length >= ROLLUP_THRESHOLD) {
|
|
1366
|
+
// B3 + B1: identical tool + identical label โ rollup keeping the label
|
|
1367
|
+
out.push({
|
|
1368
|
+
id: first.id,
|
|
1369
|
+
toolUseId: null,
|
|
1370
|
+
tool: first.tool,
|
|
1371
|
+
label: first.label,
|
|
1372
|
+
humanAuthored: first.humanAuthored,
|
|
1373
|
+
state: 'done',
|
|
1374
|
+
startedAt: first.startedAt,
|
|
1375
|
+
finishedAt: last.finishedAt,
|
|
1376
|
+
kind: 'rollup',
|
|
1377
|
+
count: run.length,
|
|
1378
|
+
})
|
|
1379
|
+
} else if (allDone && !anyHumanAuthored && !sameLabel && run.length >= MIXED_ROLLUP_THRESHOLD) {
|
|
1380
|
+
// C1: same tool, mixed labels, no human-authored โ rollup without label
|
|
1381
|
+
out.push({
|
|
1382
|
+
id: first.id,
|
|
1383
|
+
toolUseId: null,
|
|
1384
|
+
tool: first.tool,
|
|
1385
|
+
label: '',
|
|
1386
|
+
humanAuthored: false,
|
|
1387
|
+
state: 'done',
|
|
1388
|
+
startedAt: first.startedAt,
|
|
1389
|
+
finishedAt: last.finishedAt,
|
|
1390
|
+
kind: 'rollup',
|
|
1391
|
+
count: run.length,
|
|
1392
|
+
})
|
|
1393
|
+
} else {
|
|
1394
|
+
for (const r of run) out.push({ ...r, kind: 'single' })
|
|
1395
|
+
}
|
|
1396
|
+
run = []
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
for (const it of items) {
|
|
1400
|
+
if (run.length > 0 && run[run.length - 1].tool === it.tool) {
|
|
1401
|
+
run.push(it)
|
|
1402
|
+
} else {
|
|
1403
|
+
flush()
|
|
1404
|
+
run = [it]
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
flush()
|
|
1408
|
+
return out
|
|
1409
|
+
}
|