@bastani/atomic 0.6.8 → 0.7.0-2
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/bin/atomic +65 -0
- package/package.json +17 -82
- package/postinstall.mjs +47 -0
- package/.agents/skills/ado-commit/SKILL.md +0 -94
- package/.agents/skills/ado-create-pr/SKILL.md +0 -211
- package/.agents/skills/advanced-evaluation/SKILL.md +0 -404
- package/.agents/skills/advanced-evaluation/references/bias-mitigation.md +0 -288
- package/.agents/skills/advanced-evaluation/references/evaluation-pipeline.md +0 -43
- package/.agents/skills/advanced-evaluation/references/implementation-patterns.md +0 -315
- package/.agents/skills/advanced-evaluation/references/metrics-guide.md +0 -331
- package/.agents/skills/advanced-evaluation/scripts/evaluation_example.py +0 -392
- package/.agents/skills/ast-grep/SKILL.md +0 -325
- package/.agents/skills/ast-grep/references/rule_reference.md +0 -297
- package/.agents/skills/bdi-mental-states/SKILL.md +0 -313
- package/.agents/skills/bdi-mental-states/references/bdi-ontology-core.md +0 -207
- package/.agents/skills/bdi-mental-states/references/framework-integration.md +0 -582
- package/.agents/skills/bdi-mental-states/references/rdf-examples.md +0 -315
- package/.agents/skills/bdi-mental-states/references/sparql-competency.md +0 -420
- package/.agents/skills/bun/SKILL.md +0 -233
- package/.agents/skills/context-compression/SKILL.md +0 -274
- package/.agents/skills/context-compression/references/evaluation-framework.md +0 -213
- package/.agents/skills/context-compression/scripts/compression_evaluator.py +0 -862
- package/.agents/skills/context-compression/tests/test_compression_evaluator.py +0 -56
- package/.agents/skills/context-degradation/SKILL.md +0 -208
- package/.agents/skills/context-degradation/references/patterns.md +0 -314
- package/.agents/skills/context-degradation/scripts/degradation_detector.py +0 -614
- package/.agents/skills/context-fundamentals/SKILL.md +0 -203
- package/.agents/skills/context-fundamentals/references/context-components.md +0 -283
- package/.agents/skills/context-fundamentals/scripts/context_manager.py +0 -533
- package/.agents/skills/context-optimization/SKILL.md +0 -197
- package/.agents/skills/context-optimization/references/optimization_techniques.md +0 -272
- package/.agents/skills/context-optimization/scripts/compaction.py +0 -562
- package/.agents/skills/create-spec/SKILL.md +0 -249
- package/.agents/skills/docx/LICENSE.txt +0 -30
- package/.agents/skills/docx/SKILL.md +0 -592
- package/.agents/skills/docx/scripts/__init__.py +0 -1
- package/.agents/skills/docx/scripts/accept_changes.py +0 -135
- package/.agents/skills/docx/scripts/comment.py +0 -318
- package/.agents/skills/docx/scripts/office/helpers/__init__.py +0 -0
- package/.agents/skills/docx/scripts/office/helpers/merge_runs.py +0 -199
- package/.agents/skills/docx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/.agents/skills/docx/scripts/office/pack.py +0 -159
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/.agents/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/.agents/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/.agents/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/.agents/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/.agents/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/.agents/skills/docx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/.agents/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/.agents/skills/docx/scripts/office/soffice.py +0 -183
- package/.agents/skills/docx/scripts/office/unpack.py +0 -132
- package/.agents/skills/docx/scripts/office/validate.py +0 -111
- package/.agents/skills/docx/scripts/office/validators/__init__.py +0 -15
- package/.agents/skills/docx/scripts/office/validators/base.py +0 -847
- package/.agents/skills/docx/scripts/office/validators/docx.py +0 -446
- package/.agents/skills/docx/scripts/office/validators/pptx.py +0 -275
- package/.agents/skills/docx/scripts/office/validators/redlining.py +0 -247
- package/.agents/skills/docx/scripts/templates/comments.xml +0 -3
- package/.agents/skills/docx/scripts/templates/commentsExtended.xml +0 -3
- package/.agents/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
- package/.agents/skills/docx/scripts/templates/commentsIds.xml +0 -3
- package/.agents/skills/docx/scripts/templates/people.xml +0 -3
- package/.agents/skills/evaluation/SKILL.md +0 -253
- package/.agents/skills/evaluation/references/metrics.md +0 -339
- package/.agents/skills/evaluation/scripts/evaluator.py +0 -627
- package/.agents/skills/explain-code/SKILL.md +0 -232
- package/.agents/skills/filesystem-context/SKILL.md +0 -289
- package/.agents/skills/filesystem-context/references/implementation-patterns.md +0 -549
- package/.agents/skills/filesystem-context/scripts/filesystem_context.py +0 -425
- package/.agents/skills/find-skills/SKILL.md +0 -144
- package/.agents/skills/gh-commit/SKILL.md +0 -245
- package/.agents/skills/gh-create-pr/SKILL.md +0 -95
- package/.agents/skills/hosted-agents/SKILL.md +0 -262
- package/.agents/skills/hosted-agents/references/infrastructure-patterns.md +0 -700
- package/.agents/skills/hosted-agents/scripts/sandbox_manager.py +0 -590
- package/.agents/skills/impeccable/SKILL.md +0 -178
- package/.agents/skills/impeccable/agents/openai.yaml +0 -4
- package/.agents/skills/impeccable/reference/adapt.md +0 -190
- package/.agents/skills/impeccable/reference/animate.md +0 -175
- package/.agents/skills/impeccable/reference/audit.md +0 -134
- package/.agents/skills/impeccable/reference/bolder.md +0 -113
- package/.agents/skills/impeccable/reference/brand.md +0 -114
- package/.agents/skills/impeccable/reference/clarify.md +0 -174
- package/.agents/skills/impeccable/reference/cognitive-load.md +0 -106
- package/.agents/skills/impeccable/reference/color-and-contrast.md +0 -105
- package/.agents/skills/impeccable/reference/colorize.md +0 -154
- package/.agents/skills/impeccable/reference/craft.md +0 -193
- package/.agents/skills/impeccable/reference/critique.md +0 -213
- package/.agents/skills/impeccable/reference/delight.md +0 -302
- package/.agents/skills/impeccable/reference/distill.md +0 -111
- package/.agents/skills/impeccable/reference/document.md +0 -427
- package/.agents/skills/impeccable/reference/extract.md +0 -70
- package/.agents/skills/impeccable/reference/harden.md +0 -347
- package/.agents/skills/impeccable/reference/heuristics-scoring.md +0 -234
- package/.agents/skills/impeccable/reference/interaction-design.md +0 -195
- package/.agents/skills/impeccable/reference/layout.md +0 -141
- package/.agents/skills/impeccable/reference/live.md +0 -594
- package/.agents/skills/impeccable/reference/motion-design.md +0 -109
- package/.agents/skills/impeccable/reference/onboard.md +0 -234
- package/.agents/skills/impeccable/reference/optimize.md +0 -258
- package/.agents/skills/impeccable/reference/overdrive.md +0 -130
- package/.agents/skills/impeccable/reference/personas.md +0 -178
- package/.agents/skills/impeccable/reference/polish.md +0 -232
- package/.agents/skills/impeccable/reference/product.md +0 -62
- package/.agents/skills/impeccable/reference/quieter.md +0 -99
- package/.agents/skills/impeccable/reference/responsive-design.md +0 -114
- package/.agents/skills/impeccable/reference/shape.md +0 -151
- package/.agents/skills/impeccable/reference/spatial-design.md +0 -100
- package/.agents/skills/impeccable/reference/teach.md +0 -156
- package/.agents/skills/impeccable/reference/typeset.md +0 -124
- package/.agents/skills/impeccable/reference/typography.md +0 -159
- package/.agents/skills/impeccable/reference/ux-writing.md +0 -107
- package/.agents/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/.agents/skills/impeccable/scripts/command-metadata.json +0 -94
- package/.agents/skills/impeccable/scripts/design-parser.mjs +0 -820
- package/.agents/skills/impeccable/scripts/detect-csp.mjs +0 -198
- package/.agents/skills/impeccable/scripts/is-generated.mjs +0 -69
- package/.agents/skills/impeccable/scripts/live-accept.mjs +0 -595
- package/.agents/skills/impeccable/scripts/live-browser.js +0 -4781
- package/.agents/skills/impeccable/scripts/live-inject.mjs +0 -445
- package/.agents/skills/impeccable/scripts/live-poll.mjs +0 -186
- package/.agents/skills/impeccable/scripts/live-server.mjs +0 -694
- package/.agents/skills/impeccable/scripts/live-wrap.mjs +0 -571
- package/.agents/skills/impeccable/scripts/live.mjs +0 -247
- package/.agents/skills/impeccable/scripts/load-context.mjs +0 -141
- package/.agents/skills/impeccable/scripts/modern-screenshot.umd.js +0 -14
- package/.agents/skills/impeccable/scripts/pin.mjs +0 -214
- package/.agents/skills/init/SKILL.md +0 -140
- package/.agents/skills/liteparse/SKILL.md +0 -223
- package/.agents/skills/memory-systems/SKILL.md +0 -221
- package/.agents/skills/memory-systems/references/implementation.md +0 -551
- package/.agents/skills/memory-systems/scripts/memory_store.py +0 -616
- package/.agents/skills/multi-agent-patterns/SKILL.md +0 -259
- package/.agents/skills/multi-agent-patterns/references/frameworks.md +0 -433
- package/.agents/skills/multi-agent-patterns/scripts/coordination.py +0 -613
- package/.agents/skills/opentui/SKILL.md +0 -202
- package/.agents/skills/opentui/references/animation/REFERENCE.md +0 -431
- package/.agents/skills/opentui/references/components/REFERENCE.md +0 -144
- package/.agents/skills/opentui/references/components/code-diff.md +0 -672
- package/.agents/skills/opentui/references/components/containers.md +0 -417
- package/.agents/skills/opentui/references/components/inputs.md +0 -531
- package/.agents/skills/opentui/references/components/text-display.md +0 -386
- package/.agents/skills/opentui/references/core/REFERENCE.md +0 -145
- package/.agents/skills/opentui/references/core/api.md +0 -543
- package/.agents/skills/opentui/references/core/configuration.md +0 -168
- package/.agents/skills/opentui/references/core/gotchas.md +0 -393
- package/.agents/skills/opentui/references/core/patterns.md +0 -449
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +0 -617
- package/.agents/skills/opentui/references/layout/REFERENCE.md +0 -337
- package/.agents/skills/opentui/references/layout/patterns.md +0 -444
- package/.agents/skills/opentui/references/react/REFERENCE.md +0 -174
- package/.agents/skills/opentui/references/react/api.md +0 -436
- package/.agents/skills/opentui/references/react/configuration.md +0 -302
- package/.agents/skills/opentui/references/react/gotchas.md +0 -443
- package/.agents/skills/opentui/references/react/patterns.md +0 -501
- package/.agents/skills/opentui/references/solid/REFERENCE.md +0 -201
- package/.agents/skills/opentui/references/solid/api.md +0 -564
- package/.agents/skills/opentui/references/solid/configuration.md +0 -316
- package/.agents/skills/opentui/references/solid/gotchas.md +0 -427
- package/.agents/skills/opentui/references/solid/patterns.md +0 -560
- package/.agents/skills/opentui/references/testing/REFERENCE.md +0 -614
- package/.agents/skills/pdf/LICENSE.txt +0 -30
- package/.agents/skills/pdf/SKILL.md +0 -316
- package/.agents/skills/pdf/forms.md +0 -294
- package/.agents/skills/pdf/reference.md +0 -612
- package/.agents/skills/pdf/scripts/check_bounding_boxes.py +0 -65
- package/.agents/skills/pdf/scripts/check_fillable_fields.py +0 -11
- package/.agents/skills/pdf/scripts/convert_pdf_to_images.py +0 -33
- package/.agents/skills/pdf/scripts/create_validation_image.py +0 -37
- package/.agents/skills/pdf/scripts/extract_form_field_info.py +0 -122
- package/.agents/skills/pdf/scripts/extract_form_structure.py +0 -115
- package/.agents/skills/pdf/scripts/fill_fillable_fields.py +0 -98
- package/.agents/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -107
- package/.agents/skills/playwright-cli/SKILL.md +0 -390
- package/.agents/skills/playwright-cli/references/element-attributes.md +0 -23
- package/.agents/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/.agents/skills/playwright-cli/references/request-mocking.md +0 -87
- package/.agents/skills/playwright-cli/references/running-code.md +0 -241
- package/.agents/skills/playwright-cli/references/session-management.md +0 -225
- package/.agents/skills/playwright-cli/references/spec-driven-testing.md +0 -305
- package/.agents/skills/playwright-cli/references/storage-state.md +0 -275
- package/.agents/skills/playwright-cli/references/test-generation.md +0 -134
- package/.agents/skills/playwright-cli/references/tracing.md +0 -139
- package/.agents/skills/playwright-cli/references/video-recording.md +0 -143
- package/.agents/skills/pptx/LICENSE.txt +0 -30
- package/.agents/skills/pptx/SKILL.md +0 -234
- package/.agents/skills/pptx/editing.md +0 -205
- package/.agents/skills/pptx/pptxgenjs.md +0 -420
- package/.agents/skills/pptx/scripts/__init__.py +0 -0
- package/.agents/skills/pptx/scripts/add_slide.py +0 -195
- package/.agents/skills/pptx/scripts/clean.py +0 -286
- package/.agents/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/.agents/skills/pptx/scripts/office/helpers/merge_runs.py +0 -199
- package/.agents/skills/pptx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/.agents/skills/pptx/scripts/office/pack.py +0 -159
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/.agents/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/.agents/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/.agents/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/.agents/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/.agents/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/.agents/skills/pptx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/.agents/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/.agents/skills/pptx/scripts/office/soffice.py +0 -183
- package/.agents/skills/pptx/scripts/office/unpack.py +0 -132
- package/.agents/skills/pptx/scripts/office/validate.py +0 -111
- package/.agents/skills/pptx/scripts/office/validators/__init__.py +0 -15
- package/.agents/skills/pptx/scripts/office/validators/base.py +0 -847
- package/.agents/skills/pptx/scripts/office/validators/docx.py +0 -446
- package/.agents/skills/pptx/scripts/office/validators/pptx.py +0 -275
- package/.agents/skills/pptx/scripts/office/validators/redlining.py +0 -247
- package/.agents/skills/pptx/scripts/thumbnail.py +0 -289
- package/.agents/skills/project-development/SKILL.md +0 -293
- package/.agents/skills/project-development/references/case-studies.md +0 -388
- package/.agents/skills/project-development/references/pipeline-patterns.md +0 -610
- package/.agents/skills/project-development/scripts/pipeline_template.py +0 -796
- package/.agents/skills/prompt-engineer/SKILL.md +0 -265
- package/.agents/skills/prompt-engineer/references/advanced_patterns.md +0 -271
- package/.agents/skills/prompt-engineer/references/core_prompting.md +0 -137
- package/.agents/skills/prompt-engineer/references/quality_improvement.md +0 -193
- package/.agents/skills/research-codebase/SKILL.md +0 -229
- package/.agents/skills/ripgrep/SKILL.md +0 -384
- package/.agents/skills/skill-creator/LICENSE.txt +0 -202
- package/.agents/skills/skill-creator/SKILL.md +0 -487
- package/.agents/skills/skill-creator/agents/analyzer.md +0 -274
- package/.agents/skills/skill-creator/agents/comparator.md +0 -202
- package/.agents/skills/skill-creator/agents/grader.md +0 -223
- package/.agents/skills/skill-creator/assets/eval_review.html +0 -146
- package/.agents/skills/skill-creator/eval-viewer/generate_review.py +0 -471
- package/.agents/skills/skill-creator/eval-viewer/viewer.html +0 -1325
- package/.agents/skills/skill-creator/references/schemas.md +0 -430
- package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
- package/.agents/skills/skill-creator/scripts/generate_report.py +0 -326
- package/.agents/skills/skill-creator/scripts/improve_description.py +0 -247
- package/.agents/skills/skill-creator/scripts/package_skill.py +0 -136
- package/.agents/skills/skill-creator/scripts/quick_validate.py +0 -103
- package/.agents/skills/skill-creator/scripts/run_eval.py +0 -310
- package/.agents/skills/skill-creator/scripts/run_loop.py +0 -328
- package/.agents/skills/skill-creator/scripts/utils.py +0 -47
- package/.agents/skills/sl-commit/SKILL.md +0 -53
- package/.agents/skills/sl-submit-diff/SKILL.md +0 -57
- package/.agents/skills/tdd/SKILL.md +0 -111
- package/.agents/skills/tdd/deep-modules.md +0 -33
- package/.agents/skills/tdd/interface-design.md +0 -31
- package/.agents/skills/tdd/mocking.md +0 -59
- package/.agents/skills/tdd/refactoring.md +0 -10
- package/.agents/skills/tdd/tests.md +0 -61
- package/.agents/skills/tool-design/SKILL.md +0 -273
- package/.agents/skills/tool-design/references/architectural_reduction.md +0 -210
- package/.agents/skills/tool-design/references/best_practices.md +0 -176
- package/.agents/skills/tool-design/scripts/description_generator.py +0 -528
- package/.agents/skills/typescript-advanced-types/SKILL.md +0 -720
- package/.agents/skills/typescript-expert/SKILL.md +0 -434
- package/.agents/skills/typescript-expert/references/tsconfig-strict.json +0 -92
- package/.agents/skills/typescript-expert/references/typescript-cheatsheet.md +0 -383
- package/.agents/skills/typescript-expert/references/utility-types.ts +0 -335
- package/.agents/skills/typescript-expert/scripts/ts_diagnostic.py +0 -203
- package/.agents/skills/typescript-react-reviewer/SKILL.md +0 -201
- package/.agents/skills/typescript-react-reviewer/references/antipatterns.md +0 -510
- package/.agents/skills/typescript-react-reviewer/references/checklist.md +0 -267
- package/.agents/skills/typescript-react-reviewer/references/react19-patterns.md +0 -305
- package/.agents/skills/workflow-creator/SKILL.md +0 -553
- package/.agents/skills/workflow-creator/references/agent-sessions.md +0 -891
- package/.agents/skills/workflow-creator/references/agent-setup-recipe.md +0 -266
- package/.agents/skills/workflow-creator/references/computation-and-validation.md +0 -201
- package/.agents/skills/workflow-creator/references/control-flow.md +0 -470
- package/.agents/skills/workflow-creator/references/failure-modes.md +0 -1014
- package/.agents/skills/workflow-creator/references/getting-started.md +0 -392
- package/.agents/skills/workflow-creator/references/registry-and-validation.md +0 -141
- package/.agents/skills/workflow-creator/references/running-workflows.md +0 -418
- package/.agents/skills/workflow-creator/references/session-config.md +0 -431
- package/.agents/skills/workflow-creator/references/state-and-data-flow.md +0 -356
- package/.agents/skills/workflow-creator/references/user-input.md +0 -234
- package/.agents/skills/workflow-creator/references/workflow-inputs.md +0 -392
- package/.agents/skills/xlsx/LICENSE.txt +0 -30
- package/.agents/skills/xlsx/SKILL.md +0 -294
- package/.agents/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/.agents/skills/xlsx/scripts/office/helpers/merge_runs.py +0 -199
- package/.agents/skills/xlsx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/.agents/skills/xlsx/scripts/office/pack.py +0 -159
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/.agents/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/.agents/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/.agents/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/.agents/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/.agents/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/.agents/skills/xlsx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/.agents/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/.agents/skills/xlsx/scripts/office/soffice.py +0 -183
- package/.agents/skills/xlsx/scripts/office/unpack.py +0 -132
- package/.agents/skills/xlsx/scripts/office/validate.py +0 -111
- package/.agents/skills/xlsx/scripts/office/validators/__init__.py +0 -15
- package/.agents/skills/xlsx/scripts/office/validators/base.py +0 -847
- package/.agents/skills/xlsx/scripts/office/validators/docx.py +0 -446
- package/.agents/skills/xlsx/scripts/office/validators/pptx.py +0 -275
- package/.agents/skills/xlsx/scripts/office/validators/redlining.py +0 -247
- package/.agents/skills/xlsx/scripts/recalc.py +0 -184
- package/.claude/agents/code-simplifier.md +0 -52
- package/.claude/agents/codebase-analyzer.md +0 -166
- package/.claude/agents/codebase-locator.md +0 -122
- package/.claude/agents/codebase-online-researcher.md +0 -148
- package/.claude/agents/codebase-pattern-finder.md +0 -247
- package/.claude/agents/codebase-research-analyzer.md +0 -179
- package/.claude/agents/codebase-research-locator.md +0 -145
- package/.claude/agents/debugger.md +0 -91
- package/.claude/agents/orchestrator.md +0 -19
- package/.claude/agents/planner.md +0 -295
- package/.claude/agents/reviewer.md +0 -98
- package/.claude/agents/worker.md +0 -165
- package/.claude/settings.json +0 -27
- package/.github/agents/code-simplifier.md +0 -52
- package/.github/agents/codebase-analyzer.md +0 -166
- package/.github/agents/codebase-locator.md +0 -122
- package/.github/agents/codebase-online-researcher.md +0 -146
- package/.github/agents/codebase-pattern-finder.md +0 -247
- package/.github/agents/codebase-research-analyzer.md +0 -179
- package/.github/agents/codebase-research-locator.md +0 -145
- package/.github/agents/debugger.md +0 -98
- package/.github/agents/orchestrator.md +0 -27
- package/.github/agents/planner.md +0 -305
- package/.github/agents/reviewer.md +0 -95
- package/.github/agents/worker.md +0 -237
- package/.github/lsp.json +0 -93
- package/.mcp.json +0 -20
- package/.opencode/agents/code-simplifier.md +0 -62
- package/.opencode/agents/codebase-analyzer.md +0 -171
- package/.opencode/agents/codebase-locator.md +0 -127
- package/.opencode/agents/codebase-online-researcher.md +0 -152
- package/.opencode/agents/codebase-pattern-finder.md +0 -252
- package/.opencode/agents/codebase-research-analyzer.md +0 -183
- package/.opencode/agents/codebase-research-locator.md +0 -149
- package/.opencode/agents/debugger.md +0 -99
- package/.opencode/agents/orchestrator.md +0 -27
- package/.opencode/agents/planner.md +0 -309
- package/.opencode/agents/reviewer.md +0 -103
- package/.opencode/agents/worker.md +0 -165
- package/.opencode/opencode.json +0 -25
- package/README.md +0 -1624
- package/assets/settings.schema.json +0 -51
- package/dist/commands/cli/claude-inflight-hook.d.ts +0 -100
- package/dist/commands/cli/claude-inflight-hook.d.ts.map +0 -1
- package/dist/commands/cli/claude-stop-hook.d.ts +0 -80
- package/dist/commands/cli/claude-stop-hook.d.ts.map +0 -1
- package/dist/lib/atomic-temp.d.ts +0 -8
- package/dist/lib/atomic-temp.d.ts.map +0 -1
- package/dist/lib/path-root-guard.d.ts +0 -4
- package/dist/lib/path-root-guard.d.ts.map +0 -1
- package/dist/lib/spawn.d.ts +0 -102
- package/dist/lib/spawn.d.ts.map +0 -1
- package/dist/lib/terminal-env.d.ts +0 -9
- package/dist/lib/terminal-env.d.ts.map +0 -1
- package/dist/sdk/components/attached-statusline.d.ts +0 -26
- package/dist/sdk/components/attached-statusline.d.ts.map +0 -1
- package/dist/sdk/components/color-utils.d.ts +0 -4
- package/dist/sdk/components/color-utils.d.ts.map +0 -1
- package/dist/sdk/components/compact-switcher.d.ts +0 -10
- package/dist/sdk/components/compact-switcher.d.ts.map +0 -1
- package/dist/sdk/components/connectors.d.ts +0 -16
- package/dist/sdk/components/connectors.d.ts.map +0 -1
- package/dist/sdk/components/edge.d.ts +0 -4
- package/dist/sdk/components/edge.d.ts.map +0 -1
- package/dist/sdk/components/error-boundary.d.ts +0 -23
- package/dist/sdk/components/error-boundary.d.ts.map +0 -1
- package/dist/sdk/components/graph-theme.d.ts +0 -18
- package/dist/sdk/components/graph-theme.d.ts.map +0 -1
- package/dist/sdk/components/header.d.ts +0 -3
- package/dist/sdk/components/header.d.ts.map +0 -1
- package/dist/sdk/components/hooks.d.ts +0 -15
- package/dist/sdk/components/hooks.d.ts.map +0 -1
- package/dist/sdk/components/layout.d.ts +0 -27
- package/dist/sdk/components/layout.d.ts.map +0 -1
- package/dist/sdk/components/node-card.d.ts +0 -10
- package/dist/sdk/components/node-card.d.ts.map +0 -1
- package/dist/sdk/components/orchestrator-panel-contexts.d.ts +0 -16
- package/dist/sdk/components/orchestrator-panel-contexts.d.ts.map +0 -1
- package/dist/sdk/components/orchestrator-panel-store.d.ts +0 -52
- package/dist/sdk/components/orchestrator-panel-store.d.ts.map +0 -1
- package/dist/sdk/components/orchestrator-panel-types.d.ts +0 -18
- package/dist/sdk/components/orchestrator-panel-types.d.ts.map +0 -1
- package/dist/sdk/components/orchestrator-panel.d.ts +0 -86
- package/dist/sdk/components/orchestrator-panel.d.ts.map +0 -1
- package/dist/sdk/components/renderer-background.d.ts +0 -9
- package/dist/sdk/components/renderer-background.d.ts.map +0 -1
- package/dist/sdk/components/session-graph-panel.d.ts +0 -7
- package/dist/sdk/components/session-graph-panel.d.ts.map +0 -1
- package/dist/sdk/components/status-helpers.d.ts +0 -6
- package/dist/sdk/components/status-helpers.d.ts.map +0 -1
- package/dist/sdk/components/statusline.d.ts +0 -5
- package/dist/sdk/components/statusline.d.ts.map +0 -1
- package/dist/sdk/components/tui-diagnostics.d.ts +0 -56
- package/dist/sdk/components/tui-diagnostics.d.ts.map +0 -1
- package/dist/sdk/components/workflow-picker-panel.d.ts +0 -126
- package/dist/sdk/components/workflow-picker-panel.d.ts.map +0 -1
- package/dist/sdk/define-workflow.d.ts +0 -107
- package/dist/sdk/define-workflow.d.ts.map +0 -1
- package/dist/sdk/errors.d.ts +0 -46
- package/dist/sdk/errors.d.ts.map +0 -1
- package/dist/sdk/index.d.ts +0 -26
- package/dist/sdk/index.d.ts.map +0 -1
- package/dist/sdk/primitives/inputs.d.ts +0 -36
- package/dist/sdk/primitives/inputs.d.ts.map +0 -1
- package/dist/sdk/primitives/metadata.d.ts +0 -40
- package/dist/sdk/primitives/metadata.d.ts.map +0 -1
- package/dist/sdk/primitives/run.d.ts +0 -57
- package/dist/sdk/primitives/run.d.ts.map +0 -1
- package/dist/sdk/primitives/sessions.d.ts +0 -128
- package/dist/sdk/primitives/sessions.d.ts.map +0 -1
- package/dist/sdk/providers/claude.d.ts +0 -392
- package/dist/sdk/providers/claude.d.ts.map +0 -1
- package/dist/sdk/providers/copilot.d.ts +0 -55
- package/dist/sdk/providers/copilot.d.ts.map +0 -1
- package/dist/sdk/providers/opencode.d.ts +0 -27
- package/dist/sdk/providers/opencode.d.ts.map +0 -1
- package/dist/sdk/registry.d.ts +0 -27
- package/dist/sdk/registry.d.ts.map +0 -1
- package/dist/sdk/runtime/attached-footer.d.ts +0 -31
- package/dist/sdk/runtime/attached-footer.d.ts.map +0 -1
- package/dist/sdk/runtime/cc-debounce.d.ts +0 -29
- package/dist/sdk/runtime/cc-debounce.d.ts.map +0 -1
- package/dist/sdk/runtime/executor-env.d.ts +0 -20
- package/dist/sdk/runtime/executor-env.d.ts.map +0 -1
- package/dist/sdk/runtime/executor.d.ts +0 -265
- package/dist/sdk/runtime/executor.d.ts.map +0 -1
- package/dist/sdk/runtime/graph-inference.d.ts +0 -35
- package/dist/sdk/runtime/graph-inference.d.ts.map +0 -1
- package/dist/sdk/runtime/orchestrator-entry.d.ts +0 -26
- package/dist/sdk/runtime/orchestrator-entry.d.ts.map +0 -1
- package/dist/sdk/runtime/panel.d.ts +0 -9
- package/dist/sdk/runtime/panel.d.ts.map +0 -1
- package/dist/sdk/runtime/port-discovery.d.ts +0 -71
- package/dist/sdk/runtime/port-discovery.d.ts.map +0 -1
- package/dist/sdk/runtime/status-writer.d.ts +0 -101
- package/dist/sdk/runtime/status-writer.d.ts.map +0 -1
- package/dist/sdk/runtime/theme.d.ts +0 -33
- package/dist/sdk/runtime/theme.d.ts.map +0 -1
- package/dist/sdk/runtime/tmux.d.ts +0 -307
- package/dist/sdk/runtime/tmux.d.ts.map +0 -1
- package/dist/sdk/runtime/version-compat.d.ts +0 -28
- package/dist/sdk/runtime/version-compat.d.ts.map +0 -1
- package/dist/sdk/types.d.ts +0 -435
- package/dist/sdk/types.d.ts.map +0 -1
- package/dist/sdk/worker-shared.d.ts +0 -42
- package/dist/sdk/worker-shared.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +0 -81
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +0 -37
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/batching.d.ts +0 -43
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/batching.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +0 -14
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +0 -136
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +0 -58
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.d.ts +0 -43
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +0 -37
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts +0 -68
- package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts +0 -56
- package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/constants.d.ts +0 -72
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/constants.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/design-system.d.ts +0 -46
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/design-system.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/export.d.ts +0 -32
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/export.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/import.d.ts +0 -33
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/import.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/prompts.d.ts +0 -106
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/prompts.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/scan.d.ts +0 -50
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/scan.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/validation.d.ts +0 -12
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/validation.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts +0 -58
- package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts +0 -37
- package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts +0 -34
- package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/copilot-reviewer.d.ts +0 -25
- package/dist/sdk/workflows/builtin/ralph/helpers/copilot-reviewer.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/git.d.ts +0 -69
- package/dist/sdk/workflows/builtin/ralph/helpers/git.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts +0 -266
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/review.d.ts +0 -24
- package/dist/sdk/workflows/builtin/ralph/helpers/review.d.ts.map +0 -1
- package/dist/sdk/workflows/builtin/ralph/opencode/index.d.ts +0 -33
- package/dist/sdk/workflows/builtin/ralph/opencode/index.d.ts.map +0 -1
- package/dist/sdk/workflows/index.d.ts +0 -32
- package/dist/sdk/workflows/index.d.ts.map +0 -1
- package/dist/services/config/additional-instructions.d.ts +0 -67
- package/dist/services/config/additional-instructions.d.ts.map +0 -1
- package/dist/services/config/atomic-config.d.ts +0 -42
- package/dist/services/config/atomic-config.d.ts.map +0 -1
- package/dist/services/config/definitions.d.ts +0 -52
- package/dist/services/config/definitions.d.ts.map +0 -1
- package/dist/services/config/index.d.ts +0 -7
- package/dist/services/config/index.d.ts.map +0 -1
- package/dist/services/config/scm-sync.d.ts +0 -37
- package/dist/services/config/scm-sync.d.ts.map +0 -1
- package/dist/services/config/settings-schema.d.ts +0 -2
- package/dist/services/config/settings-schema.d.ts.map +0 -1
- package/dist/services/system/copy.d.ts +0 -84
- package/dist/services/system/copy.d.ts.map +0 -1
- package/dist/services/system/detect.d.ts +0 -75
- package/dist/services/system/detect.d.ts.map +0 -1
- package/dist/theme/colors.d.ts +0 -35
- package/dist/theme/colors.d.ts.map +0 -1
- package/src/cli.ts +0 -397
- package/src/commands/builtin-registry.ts +0 -37
- package/src/commands/cli/chat/index.test.ts +0 -252
- package/src/commands/cli/chat/index.ts +0 -430
- package/src/commands/cli/chat.ts +0 -8
- package/src/commands/cli/claude-ask-hook.test.ts +0 -128
- package/src/commands/cli/claude-ask-hook.ts +0 -84
- package/src/commands/cli/claude-inflight-hook.test.ts +0 -598
- package/src/commands/cli/claude-inflight-hook.ts +0 -359
- package/src/commands/cli/claude-session-start-hook.ts +0 -61
- package/src/commands/cli/claude-stop-hook.test.ts +0 -317
- package/src/commands/cli/claude-stop-hook.ts +0 -441
- package/src/commands/cli/completions.ts +0 -24
- package/src/commands/cli/config.ts +0 -80
- package/src/commands/cli/footer.tsx +0 -248
- package/src/commands/cli/init/index.ts +0 -41
- package/src/commands/cli/init/onboarding.ts +0 -61
- package/src/commands/cli/init.ts +0 -8
- package/src/commands/cli/management-commands.ts +0 -112
- package/src/commands/cli/session.test.ts +0 -830
- package/src/commands/cli/session.ts +0 -447
- package/src/commands/cli/workflow-command.test.ts +0 -618
- package/src/commands/cli/workflow-inputs.test.ts +0 -353
- package/src/commands/cli/workflow-inputs.ts +0 -266
- package/src/commands/cli/workflow-list.test.ts +0 -235
- package/src/commands/cli/workflow-list.ts +0 -0
- package/src/commands/cli/workflow-status.test.ts +0 -451
- package/src/commands/cli/workflow-status.ts +0 -330
- package/src/commands/cli/workflow.ts +0 -196
- package/src/completions/bash.ts +0 -102
- package/src/completions/fish.ts +0 -136
- package/src/completions/index.ts +0 -7
- package/src/completions/powershell.ts +0 -195
- package/src/completions/zsh.ts +0 -150
- package/src/lib/atomic-temp.test.ts +0 -86
- package/src/lib/atomic-temp.ts +0 -62
- package/src/lib/common-ignore.ts +0 -46
- package/src/lib/merge.ts +0 -103
- package/src/lib/path-root-guard.ts +0 -38
- package/src/lib/spawn.test.ts +0 -109
- package/src/lib/spawn.ts +0 -678
- package/src/lib/terminal-env.test.ts +0 -343
- package/src/lib/terminal-env.ts +0 -100
- package/src/scripts/bump-version.ts +0 -94
- package/src/scripts/bundle-configs.ts +0 -116
- package/src/scripts/clean-dist.test.ts +0 -53
- package/src/scripts/clean-dist.ts +0 -37
- package/src/scripts/constants-base.ts +0 -14
- package/src/scripts/constants.ts +0 -35
- package/src/sdk/components/attached-statusline.tsx +0 -86
- package/src/sdk/components/color-utils.ts +0 -20
- package/src/sdk/components/compact-switcher.tsx +0 -78
- package/src/sdk/components/connectors.test.ts +0 -707
- package/src/sdk/components/connectors.ts +0 -160
- package/src/sdk/components/edge.tsx +0 -13
- package/src/sdk/components/error-boundary.tsx +0 -38
- package/src/sdk/components/graph-theme.ts +0 -37
- package/src/sdk/components/header.tsx +0 -85
- package/src/sdk/components/hooks.ts +0 -21
- package/src/sdk/components/layout.test.ts +0 -1245
- package/src/sdk/components/layout.ts +0 -223
- package/src/sdk/components/node-card.tsx +0 -91
- package/src/sdk/components/orchestrator-panel-contexts.ts +0 -35
- package/src/sdk/components/orchestrator-panel-store.test.ts +0 -847
- package/src/sdk/components/orchestrator-panel-store.ts +0 -187
- package/src/sdk/components/orchestrator-panel-types.ts +0 -23
- package/src/sdk/components/orchestrator-panel.tsx +0 -262
- package/src/sdk/components/renderer-background.ts +0 -49
- package/src/sdk/components/session-graph-panel.tsx +0 -471
- package/src/sdk/components/status-helpers.ts +0 -33
- package/src/sdk/components/statusline.tsx +0 -68
- package/src/sdk/components/tui-diagnostics.ts +0 -273
- package/src/sdk/components/workflow-picker-panel.tsx +0 -1613
- package/src/sdk/define-workflow.test.ts +0 -354
- package/src/sdk/define-workflow.ts +0 -275
- package/src/sdk/errors.test.ts +0 -83
- package/src/sdk/errors.ts +0 -77
- package/src/sdk/index.test.ts +0 -92
- package/src/sdk/index.ts +0 -101
- package/src/sdk/primitives/inputs.ts +0 -48
- package/src/sdk/primitives/metadata.ts +0 -63
- package/src/sdk/primitives/run.ts +0 -81
- package/src/sdk/primitives/sessions.test.ts +0 -594
- package/src/sdk/primitives/sessions.ts +0 -328
- package/src/sdk/providers/claude.ts +0 -1450
- package/src/sdk/providers/copilot.test.ts +0 -365
- package/src/sdk/providers/copilot.ts +0 -185
- package/src/sdk/providers/headless-hil-policy.test.ts +0 -211
- package/src/sdk/providers/opencode.ts +0 -88
- package/src/sdk/registry.ts +0 -132
- package/src/sdk/runtime/attached-footer.ts +0 -155
- package/src/sdk/runtime/cc-debounce.ts +0 -104
- package/src/sdk/runtime/executor-env.ts +0 -45
- package/src/sdk/runtime/executor.test.ts +0 -1321
- package/src/sdk/runtime/executor.ts +0 -2136
- package/src/sdk/runtime/graph-inference.ts +0 -50
- package/src/sdk/runtime/orchestrator-entry.ts +0 -110
- package/src/sdk/runtime/panel.tsx +0 -9
- package/src/sdk/runtime/port-discovery.test.ts +0 -573
- package/src/sdk/runtime/port-discovery.ts +0 -496
- package/src/sdk/runtime/status-writer.test.ts +0 -245
- package/src/sdk/runtime/status-writer.ts +0 -201
- package/src/sdk/runtime/theme.ts +0 -71
- package/src/sdk/runtime/tmux.conf +0 -112
- package/src/sdk/runtime/tmux.ts +0 -785
- package/src/sdk/runtime/version-compat.ts +0 -68
- package/src/sdk/types.ts +0 -548
- package/src/sdk/worker-shared.test.ts +0 -163
- package/src/sdk/worker-shared.ts +0 -155
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +0 -569
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +0 -481
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/batching.ts +0 -65
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +0 -24
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/ignore-by-default.d.ts +0 -8
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +0 -958
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +0 -505
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.ts +0 -115
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +0 -530
- package/src/sdk/workflows/builtin/open-claude-design/claude/index.ts +0 -500
- package/src/sdk/workflows/builtin/open-claude-design/copilot/index.ts +0 -508
- package/src/sdk/workflows/builtin/open-claude-design/helpers/constants.ts +0 -159
- package/src/sdk/workflows/builtin/open-claude-design/helpers/design-system.ts +0 -88
- package/src/sdk/workflows/builtin/open-claude-design/helpers/export.ts +0 -193
- package/src/sdk/workflows/builtin/open-claude-design/helpers/import.ts +0 -52
- package/src/sdk/workflows/builtin/open-claude-design/helpers/prompts.ts +0 -1110
- package/src/sdk/workflows/builtin/open-claude-design/helpers/scan.ts +0 -117
- package/src/sdk/workflows/builtin/open-claude-design/helpers/validation.ts +0 -38
- package/src/sdk/workflows/builtin/open-claude-design/opencode/index.ts +0 -610
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +0 -272
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +0 -298
- package/src/sdk/workflows/builtin/ralph/helpers/copilot-reviewer.ts +0 -105
- package/src/sdk/workflows/builtin/ralph/helpers/git.ts +0 -201
- package/src/sdk/workflows/builtin/ralph/helpers/prompts.ts +0 -1108
- package/src/sdk/workflows/builtin/ralph/helpers/review.ts +0 -33
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +0 -290
- package/src/sdk/workflows/index.ts +0 -116
- package/src/services/config/additional-instructions.ts +0 -273
- package/src/services/config/atomic-config.ts +0 -210
- package/src/services/config/atomic-global-config.ts +0 -348
- package/src/services/config/config-path.ts +0 -19
- package/src/services/config/definitions.ts +0 -125
- package/src/services/config/index.ts +0 -7
- package/src/services/config/scm-sync.ts +0 -185
- package/src/services/config/settings-schema.ts +0 -2
- package/src/services/config/settings.ts +0 -144
- package/src/services/system/agents.ts +0 -95
- package/src/services/system/auth.test.ts +0 -343
- package/src/services/system/auth.ts +0 -140
- package/src/services/system/auto-sync.ts +0 -128
- package/src/services/system/copy.ts +0 -392
- package/src/services/system/detect.ts +0 -161
- package/src/services/system/file-lock.ts +0 -289
- package/src/services/system/install-ui.ts +0 -296
- package/src/services/system/skills.ts +0 -58
- package/src/theme/colors.ts +0 -96
- package/src/theme/logo.ts +0 -123
- package/src/version.ts +0 -7
|
@@ -1,2136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflow runtime executor.
|
|
3
|
-
*
|
|
4
|
-
* Architecture:
|
|
5
|
-
* 1. `executeWorkflow()` is called by the CLI command (e.g. atomic) or by
|
|
6
|
-
* the SDK's `runWorkflow()` primitive
|
|
7
|
-
* 2. It creates a tmux session with an orchestrator pane that runs the
|
|
8
|
-
* SDK-owned `orchestrator-entry.ts` with three positional args:
|
|
9
|
-
* `<workflowSource> <agent> <inputsB64>`
|
|
10
|
-
* 3. The CLI then attaches to the tmux session (user sees it live)
|
|
11
|
-
* 4. The orchestrator pane imports the workflow module by `source`,
|
|
12
|
-
* calls `runOrchestrator(definition, inputs)`, which then calls
|
|
13
|
-
* `definition.run(workflowCtx)` — the user's callback uses
|
|
14
|
-
* `ctx.stage()` to spawn agent sessions
|
|
15
|
-
*
|
|
16
|
-
* The dev's CLI is never re-imported. The SDK orchestrator entry script
|
|
17
|
-
* is the only re-exec target, so there is no orchestrator-mode env var
|
|
18
|
-
* re-entry signal and no boilerplate in user code.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
-
import { homedir } from "node:os";
|
|
23
|
-
import { writeFile } from "node:fs/promises";
|
|
24
|
-
import { statSync, accessSync, constants as fsConstants } from "node:fs";
|
|
25
|
-
import type {
|
|
26
|
-
WorkflowDefinition,
|
|
27
|
-
WorkflowContext,
|
|
28
|
-
WorkflowInput,
|
|
29
|
-
SessionContext,
|
|
30
|
-
SessionRunOptions,
|
|
31
|
-
SessionHandle,
|
|
32
|
-
SessionRef,
|
|
33
|
-
AgentType,
|
|
34
|
-
Transcript,
|
|
35
|
-
SavedMessage,
|
|
36
|
-
SaveTranscript,
|
|
37
|
-
StageClientOptions,
|
|
38
|
-
StageSessionOptions,
|
|
39
|
-
ProviderClient,
|
|
40
|
-
ProviderSession,
|
|
41
|
-
} from "../types.ts";
|
|
42
|
-
import { type ProviderOverrides } from "../../services/config/definitions.ts";
|
|
43
|
-
import { getProviderOverrides } from "../../services/config/atomic-config.ts";
|
|
44
|
-
import { getCopilotScmDisableFlags } from "../../services/config/scm-sync.ts";
|
|
45
|
-
import { reconcileOpencodeInstructions } from "../../services/config/additional-instructions.ts";
|
|
46
|
-
import { ensureDir } from "../../services/system/copy.ts";
|
|
47
|
-
import type { SessionEvent } from "@github/copilot-sdk";
|
|
48
|
-
import type { SessionPromptResponse } from "@opencode-ai/sdk/v2";
|
|
49
|
-
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
50
|
-
import * as tmux from "./tmux.ts";
|
|
51
|
-
import { spawnMuxAttach } from "./tmux.ts";
|
|
52
|
-
import {
|
|
53
|
-
getListeningPortForPid,
|
|
54
|
-
PORT_DISCOVERY_TIMEOUT_MS,
|
|
55
|
-
} from "./port-discovery.ts";
|
|
56
|
-
import { spawnAttachedFooter } from "./attached-footer.ts";
|
|
57
|
-
import {
|
|
58
|
-
clearClaudeSession,
|
|
59
|
-
ClaudeClientWrapper,
|
|
60
|
-
ClaudeSessionWrapper,
|
|
61
|
-
HeadlessClaudeClientWrapper,
|
|
62
|
-
HeadlessClaudeSessionWrapper,
|
|
63
|
-
} from "../providers/claude.ts";
|
|
64
|
-
import { withHeadlessOpencodeEnv } from "../providers/opencode.ts";
|
|
65
|
-
import { resolveCopilotCliPath } from "../providers/copilot.ts";
|
|
66
|
-
import { OrchestratorPanel } from "./panel.tsx";
|
|
67
|
-
import { GraphFrontierTracker } from "./graph-inference.ts";
|
|
68
|
-
import { buildSnapshot, writeSnapshot } from "./status-writer.ts";
|
|
69
|
-
import { errorMessage } from "../errors.ts";
|
|
70
|
-
import { createPainter } from "../../theme/colors.ts";
|
|
71
|
-
import { atomicTempEnv } from "../../lib/atomic-temp.ts";
|
|
72
|
-
|
|
73
|
-
/** Maximum time (ms) for the SDK probe to succeed after port is discovered. */
|
|
74
|
-
export const SERVER_PROBE_TIMEOUT_MS = 60_000;
|
|
75
|
-
|
|
76
|
-
/** Agent CLI configuration for spawning in tmux panes. */
|
|
77
|
-
const AGENT_CLI: Record<
|
|
78
|
-
AgentType,
|
|
79
|
-
{ cmd: string; chatFlags: string[]; envVars: Record<string, string> }
|
|
80
|
-
> = {
|
|
81
|
-
copilot: {
|
|
82
|
-
cmd: "copilot",
|
|
83
|
-
chatFlags: ["--add-dir", ".", "--yolo", "--experimental"],
|
|
84
|
-
envVars: {
|
|
85
|
-
COPILOT_ALLOW_ALL: "true",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
opencode: { cmd: "opencode", chatFlags: [], envVars: {} },
|
|
89
|
-
claude: {
|
|
90
|
-
cmd: "claude",
|
|
91
|
-
chatFlags: [
|
|
92
|
-
"--allow-dangerously-skip-permissions",
|
|
93
|
-
"--dangerously-skip-permissions",
|
|
94
|
-
],
|
|
95
|
-
envVars: {
|
|
96
|
-
// Enables session_state_changed events in the session JSONL transcript,
|
|
97
|
-
// which the idle detection in claude.ts watches for to know when the
|
|
98
|
-
// agent has finished processing a prompt.
|
|
99
|
-
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1",
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/** Thrown when the user aborts a running workflow via `q` or `Ctrl+C`. */
|
|
105
|
-
class WorkflowAbortError extends Error {
|
|
106
|
-
constructor() {
|
|
107
|
-
super("Workflow aborted by user");
|
|
108
|
-
this.name = "WorkflowAbortError";
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Compile-time exhaustiveness guard for discriminated unions. */
|
|
113
|
-
function assertNever(value: never): never {
|
|
114
|
-
throw new Error(`Unhandled agent type: ${String(value)}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Re-export for backward compatibility (tests import from here)
|
|
118
|
-
export { errorMessage } from "../errors.ts";
|
|
119
|
-
|
|
120
|
-
/** Runtime guard for deserialized SavedMessage objects. */
|
|
121
|
-
function isValidSavedMessage(msg: unknown): msg is SavedMessage {
|
|
122
|
-
if (!msg || typeof msg !== "object") return false;
|
|
123
|
-
const m = msg as Record<string, unknown>;
|
|
124
|
-
return (
|
|
125
|
-
m.provider === "copilot" ||
|
|
126
|
-
m.provider === "opencode" ||
|
|
127
|
-
m.provider === "claude"
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface WorkflowRunOptions {
|
|
132
|
-
/** The compiled workflow definition */
|
|
133
|
-
definition: WorkflowDefinition;
|
|
134
|
-
/** Agent type */
|
|
135
|
-
agent: AgentType;
|
|
136
|
-
/**
|
|
137
|
-
* Structured inputs for this run. Free-form workflows model their
|
|
138
|
-
* single positional prompt as `{ prompt: "..." }` so workflow
|
|
139
|
-
* authors can read `ctx.inputs.prompt` uniformly regardless of
|
|
140
|
-
* whether the workflow declares a schema. Empty record is valid.
|
|
141
|
-
*/
|
|
142
|
-
inputs?: Record<string, string>;
|
|
143
|
-
/** Project root (defaults to cwd) */
|
|
144
|
-
projectRoot?: string;
|
|
145
|
-
/**
|
|
146
|
-
* When true, create the tmux session and return immediately instead
|
|
147
|
-
* of attaching. The orchestrator keeps running in the background on
|
|
148
|
-
* the atomic tmux socket; users can attach later with
|
|
149
|
-
* `atomic workflow session connect <name>`.
|
|
150
|
-
*/
|
|
151
|
-
detach?: boolean;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
interface SessionResult {
|
|
155
|
-
name: string;
|
|
156
|
-
sessionId: string;
|
|
157
|
-
sessionDir: string;
|
|
158
|
-
paneId: string;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** A session that has been spawned but may not have completed yet. */
|
|
162
|
-
interface ActiveSession {
|
|
163
|
-
name: string;
|
|
164
|
-
paneId: string;
|
|
165
|
-
/** Settles when the session finishes. Resolves on success, rejects on failure. */
|
|
166
|
-
done: Promise<void>;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function generateId(): string {
|
|
170
|
-
return crypto.randomUUID().slice(0, 8);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function getSessionsBaseDir(): string {
|
|
174
|
-
return join(homedir(), ".atomic", "sessions");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Resolve a non-JS Copilot CLI binary on PATH.
|
|
179
|
-
*
|
|
180
|
-
* Under Bun, `@github/copilot-sdk` spawns its bundled JS entry via `node`
|
|
181
|
-
* (see `getNodeExecPath` in the SDK). If `node` isn't installed — common in
|
|
182
|
-
* minimal containers — the spawn fails silently with ENOENT and the SDK's
|
|
183
|
-
* write to the child's stdin surfaces as "Cannot call write after a stream
|
|
184
|
-
* was destroyed" from vscode-jsonrpc. Pointing the SDK at a standalone
|
|
185
|
-
* `copilot` binary (the npm-installed ELF executable) sidesteps the
|
|
186
|
-
* node-vs-bun problem because the SDK execs it directly when the path does
|
|
187
|
-
* not end in `.js`.
|
|
188
|
-
*
|
|
189
|
-
* Returns undefined if no suitable binary is found.
|
|
190
|
-
*/
|
|
191
|
-
export function discoverCopilotBinary(): string | undefined {
|
|
192
|
-
const pathVar = process.env.PATH;
|
|
193
|
-
if (!pathVar) return undefined;
|
|
194
|
-
// Windows: only `copilot.exe` is probed. Bun's global install writes a
|
|
195
|
-
// real `.exe` shim, so this covers the Bun-container scenario this guard
|
|
196
|
-
// exists for. Pre-existing npm-installed shims (`copilot.cmd`/`.ps1`)
|
|
197
|
-
// aren't handled — the entire override is gated on `process.versions.bun`.
|
|
198
|
-
const exe = process.platform === "win32" ? "copilot.exe" : "copilot";
|
|
199
|
-
const sep = process.platform === "win32" ? ";" : ":";
|
|
200
|
-
for (const dir of pathVar.split(sep)) {
|
|
201
|
-
if (!dir) continue;
|
|
202
|
-
const candidate = join(dir, exe);
|
|
203
|
-
if (!isExecutableFile(candidate)) continue;
|
|
204
|
-
return candidate;
|
|
205
|
-
}
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* True when we need to override the SDK's default CLI path — i.e. running
|
|
211
|
-
* under Bun, the user hasn't set COPILOT_CLI_PATH, and `node` is not
|
|
212
|
-
* available to execute the SDK's bundled JS entry.
|
|
213
|
-
*
|
|
214
|
-
* Pure predicate on the current env; safe to call repeatedly.
|
|
215
|
-
*/
|
|
216
|
-
export function shouldOverrideCopilotCliPath(): boolean {
|
|
217
|
-
if (!process.versions.bun) return false;
|
|
218
|
-
if (process.env.COPILOT_CLI_PATH) return false;
|
|
219
|
-
if (isNodeOnPath()) return false;
|
|
220
|
-
return discoverCopilotBinary() !== undefined;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function isExecutableFile(path: string): boolean {
|
|
224
|
-
try {
|
|
225
|
-
if (!statSync(path).isFile()) return false;
|
|
226
|
-
if (process.platform === "win32") return true;
|
|
227
|
-
accessSync(path, fsConstants.X_OK);
|
|
228
|
-
return true;
|
|
229
|
-
} catch {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function isNodeOnPath(): boolean {
|
|
235
|
-
const pathVar = process.env.PATH;
|
|
236
|
-
if (!pathVar) return false;
|
|
237
|
-
const exe = process.platform === "win32" ? "node.exe" : "node";
|
|
238
|
-
const sep = process.platform === "win32" ? ";" : ":";
|
|
239
|
-
for (const dir of pathVar.split(sep)) {
|
|
240
|
-
if (!dir) continue;
|
|
241
|
-
if (isExecutableFile(join(dir, exe))) return true;
|
|
242
|
-
}
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Set safe env defaults for the orchestrator process before any SDK is
|
|
248
|
-
* loaded. Idempotent — subsequent calls no-op once `COPILOT_CLI_PATH`
|
|
249
|
-
* is set. Call as early as possible so headless Copilot subprocesses
|
|
250
|
-
* inherit the resolved env.
|
|
251
|
-
*/
|
|
252
|
-
export function applyContainerEnvDefaults(): void {
|
|
253
|
-
if (!process.versions.bun) return;
|
|
254
|
-
if (process.env.COPILOT_CLI_PATH) return;
|
|
255
|
-
if (isNodeOnPath()) return;
|
|
256
|
-
const bin = discoverCopilotBinary();
|
|
257
|
-
if (bin) process.env.COPILOT_CLI_PATH = bin;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Resolve a CLI binary name to its absolute path using the parent atomic
|
|
262
|
-
* process's PATH. tmux's child shell can have a stripped or differently
|
|
263
|
-
* ordered PATH from the user's interactive shell — most visibly when atomic
|
|
264
|
-
* is launched from a globally-installed bin wrapper rather than `bun run dev`.
|
|
265
|
-
* Resolving here, where we still have the full interactive PATH, mirrors
|
|
266
|
-
* how `attached-footer.ts` injects `process.execPath` + an absolute cli.ts
|
|
267
|
-
* path so the footer always spawns regardless of the child shell's PATH.
|
|
268
|
-
*
|
|
269
|
-
* Falls back to the bare name when the binary isn't found on PATH so behavior
|
|
270
|
-
* stays unchanged for callers running entirely inside a normal interactive shell.
|
|
271
|
-
*/
|
|
272
|
-
function resolveCliBinary(cmd: string): string {
|
|
273
|
-
return Bun.which(cmd, { PATH: process.env.PATH ?? "" }) ?? cmd;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/** Wrap a path in bash double quotes only when it contains shell-significant characters. */
|
|
277
|
-
function quotePathIfNeeded(path: string): string {
|
|
278
|
-
return /[\s'"$`!\\]/.test(path) ? `"${escBash(path)}"` : path;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function buildPaneCommand(
|
|
282
|
-
agent: AgentType,
|
|
283
|
-
overrides: ProviderOverrides = {},
|
|
284
|
-
extraChatFlags: string[] = [],
|
|
285
|
-
): { command: string; envVars: Record<string, string> } {
|
|
286
|
-
const {
|
|
287
|
-
cmd,
|
|
288
|
-
chatFlags: defaultFlags,
|
|
289
|
-
envVars: defaultEnvVars,
|
|
290
|
-
} = AGENT_CLI[agent];
|
|
291
|
-
const chatFlags = overrides.chatFlags ?? defaultFlags;
|
|
292
|
-
const claudeTempEnv = agent === "claude" ? atomicTempEnv() : {};
|
|
293
|
-
const envVars = overrides.envVars
|
|
294
|
-
? { ...defaultEnvVars, ...overrides.envVars }
|
|
295
|
-
: defaultEnvVars;
|
|
296
|
-
const mergedEnvVars = { ...envVars, ...claudeTempEnv, ...overrides.envVars };
|
|
297
|
-
|
|
298
|
-
const resolvedCmd = quotePathIfNeeded(resolveCliBinary(cmd));
|
|
299
|
-
|
|
300
|
-
switch (agent) {
|
|
301
|
-
case "copilot": {
|
|
302
|
-
// Prefer the copilot binary resolved via resolveCopilotCliPath so that
|
|
303
|
-
// COPILOT_CLI_PATH (set by applyContainerEnvDefaults in Bun-without-node
|
|
304
|
-
// environments) is honoured in the tmux pane command, keeping the pane
|
|
305
|
-
// binary consistent with the SDK subprocess binary.
|
|
306
|
-
const copilotBin = resolveCopilotCliPath() ?? resolveCliBinary(cmd);
|
|
307
|
-
return {
|
|
308
|
-
command: [
|
|
309
|
-
quotePathIfNeeded(copilotBin),
|
|
310
|
-
"--ui-server",
|
|
311
|
-
"--port",
|
|
312
|
-
"0",
|
|
313
|
-
...chatFlags,
|
|
314
|
-
...extraChatFlags,
|
|
315
|
-
].join(" "),
|
|
316
|
-
envVars: mergedEnvVars,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
case "opencode":
|
|
320
|
-
return {
|
|
321
|
-
command: [resolvedCmd, "--port", "0", ...chatFlags].join(" "),
|
|
322
|
-
envVars: mergedEnvVars,
|
|
323
|
-
};
|
|
324
|
-
case "claude": {
|
|
325
|
-
// Claude is started via createClaudeSession() in the workflow's run().
|
|
326
|
-
// Resolve $SHELL (or the platform default) to an absolute path for the
|
|
327
|
-
// same reason the agent CLIs are resolved above.
|
|
328
|
-
const fallback = process.platform === "win32" ? "pwsh" : "sh";
|
|
329
|
-
const shellCandidate = process.env.SHELL || fallback;
|
|
330
|
-
const resolvedShell =
|
|
331
|
-
shellCandidate.includes("/") || shellCandidate.includes("\\")
|
|
332
|
-
? shellCandidate
|
|
333
|
-
: resolveCliBinary(shellCandidate);
|
|
334
|
-
return {
|
|
335
|
-
command: quotePathIfNeeded(resolvedShell),
|
|
336
|
-
envVars: mergedEnvVars,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
default:
|
|
340
|
-
return assertNever(agent);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export async function waitForServer(
|
|
345
|
-
agent: AgentType,
|
|
346
|
-
paneId: string,
|
|
347
|
-
): Promise<string> {
|
|
348
|
-
if (agent === "claude") return "";
|
|
349
|
-
|
|
350
|
-
const portDeadline = Date.now() + PORT_DISCOVERY_TIMEOUT_MS;
|
|
351
|
-
|
|
352
|
-
// 1. Wait for the agent process to start and the TUI to render.
|
|
353
|
-
while (Date.now() < portDeadline) {
|
|
354
|
-
const content = tmux.capturePane(paneId);
|
|
355
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
356
|
-
if (lines.length >= 3) break;
|
|
357
|
-
await Bun.sleep(1_000);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// 2. Discover the listening port via the agent's PID.
|
|
361
|
-
const panePid = tmux.getPanePid(paneId);
|
|
362
|
-
if (!panePid) {
|
|
363
|
-
throw new Error(`failed to resolve agent PID for pane ${paneId}`);
|
|
364
|
-
}
|
|
365
|
-
const remainingMs = Math.max(0, portDeadline - Date.now());
|
|
366
|
-
const port = await getListeningPortForPid(panePid, {
|
|
367
|
-
timeoutMs: remainingMs,
|
|
368
|
-
});
|
|
369
|
-
if (port === null) {
|
|
370
|
-
throw new Error(
|
|
371
|
-
`agent (${agent}) did not bind a TCP port within ${PORT_DISCOVERY_TIMEOUT_MS}ms ` +
|
|
372
|
-
`(pane ${paneId}, pid ${panePid})`,
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
const serverUrl = `localhost:${port}`;
|
|
376
|
-
|
|
377
|
-
// 3. Verify the SDK can actually connect.
|
|
378
|
-
if (agent === "copilot") {
|
|
379
|
-
const probeDeadline = Date.now() + SERVER_PROBE_TIMEOUT_MS;
|
|
380
|
-
const { CopilotClient } = await import("@github/copilot-sdk");
|
|
381
|
-
while (Date.now() < probeDeadline) {
|
|
382
|
-
try {
|
|
383
|
-
const probe = new CopilotClient({ cliUrl: serverUrl });
|
|
384
|
-
await probe.start();
|
|
385
|
-
await probe.listSessions();
|
|
386
|
-
await probe.stop();
|
|
387
|
-
return serverUrl;
|
|
388
|
-
} catch {
|
|
389
|
-
await Bun.sleep(1_000);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
throw new Error(
|
|
393
|
-
`copilot SDK probe did not respond at ${serverUrl} within ${SERVER_PROBE_TIMEOUT_MS}ms`,
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// OpenCode: short settle delay, then return.
|
|
398
|
-
await Bun.sleep(1_000);
|
|
399
|
-
return serverUrl;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Escape a string for safe interpolation inside a bash double-quoted string.
|
|
404
|
-
*
|
|
405
|
-
* In bash `"..."` strings only `$`, `` ` ``, `\`, `"`, and `!` are special.
|
|
406
|
-
* Single quotes are literal inside double quotes and need no escaping.
|
|
407
|
-
* Null bytes are stripped because bash strings cannot contain them.
|
|
408
|
-
*/
|
|
409
|
-
export function escBash(s: string): string {
|
|
410
|
-
return s
|
|
411
|
-
.replace(/\x00/g, "")
|
|
412
|
-
.replace(/[\n\r]+/g, " ")
|
|
413
|
-
.replace(/[\\"$`!]/g, "\\$&");
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Escape a string for safe interpolation inside a PowerShell double-quoted string.
|
|
418
|
-
*
|
|
419
|
-
* In PowerShell `"..."` strings, backtick is the escape character and `$` triggers
|
|
420
|
-
* variable expansion. Null bytes are stripped for safety.
|
|
421
|
-
*/
|
|
422
|
-
export function escPwsh(s: string): string {
|
|
423
|
-
return s
|
|
424
|
-
.replace(/\x00/g, "")
|
|
425
|
-
.replace(/[`"$]/g, "`$&")
|
|
426
|
-
.replace(/\n/g, "`n")
|
|
427
|
-
.replace(/\r/g, "`r");
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Coerce raw string inputs to their declared runtime types. Integer inputs
|
|
432
|
-
* become `number`; every other declared type passes through as `string`.
|
|
433
|
-
* Unknown keys (not in the schema) are preserved as strings.
|
|
434
|
-
*
|
|
435
|
-
* Invalid integer strings fall back to the key being dropped — validation
|
|
436
|
-
* already runs upstream (in `validateInputsAgainstSchema`), so reaching
|
|
437
|
-
* this path with garbage means the executor was invoked out-of-band.
|
|
438
|
-
*/
|
|
439
|
-
export function coerceInputsBySchema(
|
|
440
|
-
inputs: Record<string, string>,
|
|
441
|
-
schema: readonly WorkflowInput[],
|
|
442
|
-
): Record<string, string | number> {
|
|
443
|
-
const byName = new Map(schema.map((f) => [f.name, f]));
|
|
444
|
-
const out: Record<string, string | number> = {};
|
|
445
|
-
for (const [k, v] of Object.entries(inputs)) {
|
|
446
|
-
const field = byName.get(k);
|
|
447
|
-
if (field?.type === "integer") {
|
|
448
|
-
const parsed = Number.parseInt(v, 10);
|
|
449
|
-
if (Number.isFinite(parsed) && Number.isInteger(parsed)) {
|
|
450
|
-
out[k] = parsed;
|
|
451
|
-
}
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
out[k] = v;
|
|
455
|
-
}
|
|
456
|
-
return out;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// ============================================================================
|
|
460
|
-
// Entry point called by the CLI command
|
|
461
|
-
// ============================================================================
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Called by `atomic workflow -n <name> -a <agent> <prompt>`.
|
|
465
|
-
*
|
|
466
|
-
* Always creates a tmux session in the atomic socket with the
|
|
467
|
-
* orchestrator as the initial pane, then attaches so the user sees
|
|
468
|
-
* everything live — even when invoked from inside another tmux session.
|
|
469
|
-
*/
|
|
470
|
-
export async function executeWorkflow(
|
|
471
|
-
options: WorkflowRunOptions,
|
|
472
|
-
): Promise<{ id: string; tmuxSessionName: string }> {
|
|
473
|
-
const {
|
|
474
|
-
definition,
|
|
475
|
-
agent,
|
|
476
|
-
inputs = {},
|
|
477
|
-
projectRoot = process.cwd(),
|
|
478
|
-
detach = false,
|
|
479
|
-
} = options;
|
|
480
|
-
|
|
481
|
-
// OpenCode reads its `instructions` array from `.opencode/opencode.json`
|
|
482
|
-
// at server-start time — both for the interactive tmux-pane path and the
|
|
483
|
-
// headless `createOpencode({ port: 0 })` path. Reconcile here, before
|
|
484
|
-
// either spawn, so the resolved AGENTS.md is the source of truth on
|
|
485
|
-
// every workflow run. Best-effort: a malformed config shouldn't block.
|
|
486
|
-
if (agent === "opencode") {
|
|
487
|
-
try {
|
|
488
|
-
await reconcileOpencodeInstructions(projectRoot);
|
|
489
|
-
} catch {
|
|
490
|
-
/* swallow */
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const workflowRunId = generateId();
|
|
495
|
-
const tmuxSessionName = `atomic-wf-${agent}-${definition.name}-${workflowRunId}`;
|
|
496
|
-
const sessionsBaseDir = join(getSessionsBaseDir(), workflowRunId);
|
|
497
|
-
await ensureDir(sessionsBaseDir);
|
|
498
|
-
|
|
499
|
-
// Write a launcher script for the orchestrator pane.
|
|
500
|
-
// Runs the SDK-owned orchestrator entry script with positional args:
|
|
501
|
-
// bun <orchestrator-entry.ts> <workflowSource> <agent> <inputsB64>
|
|
502
|
-
// The dev's own CLI is never re-execed.
|
|
503
|
-
const isWin = process.platform === "win32";
|
|
504
|
-
const launcherExt = isWin ? "ps1" : "sh";
|
|
505
|
-
const launcherPath = join(sessionsBaseDir, `orchestrator.${launcherExt}`);
|
|
506
|
-
const logPath = join(sessionsBaseDir, "orchestrator.log");
|
|
507
|
-
const launcherEnvVars = {
|
|
508
|
-
...(agent === "claude" ? atomicTempEnv() : {}),
|
|
509
|
-
...terminalCapabilityEnv(),
|
|
510
|
-
...workflowDiagnosticsEnv(),
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
// Inputs are passed through as base64-encoded JSON so long multiline
|
|
514
|
-
// text values survive shell quoting without any further escaping.
|
|
515
|
-
// Free-form workflows ride the same pipe — their single positional
|
|
516
|
-
// prompt is stored under the `prompt` key so workflow authors always
|
|
517
|
-
// read the user's prompt via `ctx.inputs.prompt`.
|
|
518
|
-
const inputsB64 = Buffer.from(JSON.stringify(inputs)).toString("base64");
|
|
519
|
-
|
|
520
|
-
// Resolve the SDK's orchestrator entry script (sibling of this file).
|
|
521
|
-
const orchestratorEntry = join(import.meta.dir, "orchestrator-entry.ts");
|
|
522
|
-
const workflowSource = definition.source;
|
|
523
|
-
|
|
524
|
-
// Resolve the bun binary to an absolute path here — `process.execPath` is
|
|
525
|
-
// the exact bun interpreter currently running atomic, so we don't depend on
|
|
526
|
-
// bare `bun` being on the tmux child shell's PATH (the same reason
|
|
527
|
-
// `attached-footer.ts` uses it).
|
|
528
|
-
const bunBinary = process.execPath;
|
|
529
|
-
|
|
530
|
-
const launcherScript = isWin
|
|
531
|
-
? [
|
|
532
|
-
`Set-Location "${escPwsh(projectRoot)}"`,
|
|
533
|
-
...Object.entries(launcherEnvVars).map(
|
|
534
|
-
([key, value]) => `$env:${key} = "${escPwsh(value)}"`,
|
|
535
|
-
),
|
|
536
|
-
`$env:ATOMIC_WF_ID = "${escPwsh(workflowRunId)}"`,
|
|
537
|
-
`$env:ATOMIC_WF_TMUX = "${escPwsh(tmuxSessionName)}"`,
|
|
538
|
-
`$env:ATOMIC_WF_AGENT = "${escPwsh(agent)}"`,
|
|
539
|
-
`$env:ATOMIC_WF_CWD = "${escPwsh(projectRoot)}"`,
|
|
540
|
-
`& "${escPwsh(bunBinary)}" run "${escPwsh(orchestratorEntry)}" "${escPwsh(workflowSource)}" "${escPwsh(agent)}" "${escPwsh(inputsB64)}" 2>"${escPwsh(logPath)}"`,
|
|
541
|
-
].join("\n")
|
|
542
|
-
: [
|
|
543
|
-
"#!/bin/bash",
|
|
544
|
-
`cd "${escBash(projectRoot)}"`,
|
|
545
|
-
...Object.entries(launcherEnvVars).map(
|
|
546
|
-
([key, value]) => `export ${key}="${escBash(value)}"`,
|
|
547
|
-
),
|
|
548
|
-
`export ATOMIC_WF_ID="${escBash(workflowRunId)}"`,
|
|
549
|
-
`export ATOMIC_WF_TMUX="${escBash(tmuxSessionName)}"`,
|
|
550
|
-
`export ATOMIC_WF_AGENT="${escBash(agent)}"`,
|
|
551
|
-
`export ATOMIC_WF_CWD="${escBash(projectRoot)}"`,
|
|
552
|
-
`"${escBash(bunBinary)}" run "${escBash(orchestratorEntry)}" "${escBash(workflowSource)}" "${escBash(agent)}" "${escBash(inputsB64)}" 2>"${escBash(logPath)}"`,
|
|
553
|
-
].join("\n");
|
|
554
|
-
|
|
555
|
-
await writeFile(launcherPath, launcherScript, { mode: 0o755 });
|
|
556
|
-
|
|
557
|
-
const shellCmd = isWin
|
|
558
|
-
? `pwsh -NoProfile -File "${escPwsh(launcherPath)}"`
|
|
559
|
-
: `bash "${escBash(launcherPath)}"`;
|
|
560
|
-
tmux.createSession(tmuxSessionName, shellCmd, "orchestrator", undefined, launcherEnvVars);
|
|
561
|
-
tmux.setSessionEnv(tmuxSessionName, "ATOMIC_AGENT", agent);
|
|
562
|
-
|
|
563
|
-
if (detach) {
|
|
564
|
-
// Session is already running detached on the atomic socket (tmux
|
|
565
|
-
// new-session -d). Print connection hints and return so the caller
|
|
566
|
-
// can exit cleanly without blocking on the orchestrator.
|
|
567
|
-
printDetachedBanner(tmuxSessionName);
|
|
568
|
-
return { id: workflowRunId, tmuxSessionName };
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (tmux.isInsideAtomicSocket()) {
|
|
572
|
-
// Already on the atomic server — just switch to the new session.
|
|
573
|
-
tmux.switchClient(tmuxSessionName);
|
|
574
|
-
} else if (tmux.isInsideTmux()) {
|
|
575
|
-
// Inside a different tmux server — detach and replace the client
|
|
576
|
-
// with an attach to the atomic socket (no nesting).
|
|
577
|
-
tmux.detachAndAttachAtomic(tmuxSessionName);
|
|
578
|
-
} else {
|
|
579
|
-
const attachProc = spawnMuxAttach(tmuxSessionName);
|
|
580
|
-
await attachProc.exited;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
return { id: workflowRunId, tmuxSessionName };
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function workflowDiagnosticsEnv(): Record<string, string> {
|
|
587
|
-
const keys = [
|
|
588
|
-
"ATOMIC_TUI_DIAGNOSTICS",
|
|
589
|
-
"ATOMIC_TUI_DIAGNOSTICS_DIR",
|
|
590
|
-
"ATOMIC_TUI_DIAGNOSTICS_INTERVAL_MS",
|
|
591
|
-
"ATOMIC_TUI_DIAGNOSTICS_MAX",
|
|
592
|
-
"ATOMIC_TUI_DIAGNOSTICS_OPENTUI_DUMP",
|
|
593
|
-
];
|
|
594
|
-
const env: Record<string, string> = {};
|
|
595
|
-
for (const key of keys) {
|
|
596
|
-
const value = process.env[key];
|
|
597
|
-
if (value !== undefined) env[key] = value;
|
|
598
|
-
}
|
|
599
|
-
return env;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
function terminalCapabilityEnv(): Record<string, string> {
|
|
603
|
-
const env: Record<string, string> = {};
|
|
604
|
-
const colorterm = process.env.COLORTERM;
|
|
605
|
-
if (colorterm !== undefined) env.COLORTERM = colorterm;
|
|
606
|
-
return env;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Print a short banner telling the user the workflow is running in the
|
|
611
|
-
* background and how to attach to it. Written to stdout so scripts can
|
|
612
|
-
* capture the session name with a simple redirect.
|
|
613
|
-
*/
|
|
614
|
-
function printDetachedBanner(tmuxSessionName: string): void {
|
|
615
|
-
const paint = createPainter();
|
|
616
|
-
process.stdout.write(
|
|
617
|
-
"\n" +
|
|
618
|
-
" " +
|
|
619
|
-
paint("success", "✓") +
|
|
620
|
-
" " +
|
|
621
|
-
paint("text", "workflow started in background", { bold: true }) +
|
|
622
|
-
"\n" +
|
|
623
|
-
" " +
|
|
624
|
-
paint("dim", "session: ") +
|
|
625
|
-
paint("accent", tmuxSessionName) +
|
|
626
|
-
"\n" +
|
|
627
|
-
"\n" +
|
|
628
|
-
" " +
|
|
629
|
-
paint("dim", "attach: ") +
|
|
630
|
-
paint("accent", `atomic workflow session connect ${tmuxSessionName}`) +
|
|
631
|
-
"\n" +
|
|
632
|
-
" " +
|
|
633
|
-
paint("dim", "list: ") +
|
|
634
|
-
paint("accent", "atomic workflow session list") +
|
|
635
|
-
"\n" +
|
|
636
|
-
" " +
|
|
637
|
-
paint("dim", "kill: ") +
|
|
638
|
-
paint("accent", `atomic workflow session kill ${tmuxSessionName}`) +
|
|
639
|
-
"\n" +
|
|
640
|
-
"\n",
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// ============================================================================
|
|
645
|
-
// Session execution helpers
|
|
646
|
-
// ============================================================================
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Resolve the provider-specific session identifier for use as
|
|
650
|
-
* `SessionContext.sessionId`:
|
|
651
|
-
* - Claude interactive: `ClaudeSessionWrapper.sessionId` — the Claude UUID
|
|
652
|
-
* set when `createClaudeSession` ran.
|
|
653
|
-
* - Claude headless: `HeadlessClaudeSessionWrapper.sessionId` — the SDK
|
|
654
|
-
* `session_id` from the most recently completed `query()` (empty string
|
|
655
|
-
* until the first query returns).
|
|
656
|
-
* - Copilot: `CopilotSession.sessionId`.
|
|
657
|
-
* - OpenCode: `Session.id`.
|
|
658
|
-
*
|
|
659
|
-
* Returns an empty string for unknown shapes rather than throwing so
|
|
660
|
-
* early-init readers of `s.sessionId` (e.g. logging) don't crash.
|
|
661
|
-
*/
|
|
662
|
-
function resolveProviderSessionId(
|
|
663
|
-
agent: AgentType,
|
|
664
|
-
providerSession: unknown,
|
|
665
|
-
): string {
|
|
666
|
-
if (!providerSession || typeof providerSession !== "object") return "";
|
|
667
|
-
const obj = providerSession as Record<string, unknown>;
|
|
668
|
-
if (agent === "opencode") {
|
|
669
|
-
return typeof obj["id"] === "string" ? (obj["id"] as string) : "";
|
|
670
|
-
}
|
|
671
|
-
// claude and copilot both expose `sessionId` as a string.
|
|
672
|
-
return typeof obj["sessionId"] === "string"
|
|
673
|
-
? (obj["sessionId"] as string)
|
|
674
|
-
: "";
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/** Type guard for objects with a string `content` property (Copilot assistant.message data). */
|
|
678
|
-
export function hasContent(value: unknown): value is { content: string } {
|
|
679
|
-
return (
|
|
680
|
-
typeof value === "object" &&
|
|
681
|
-
value !== null &&
|
|
682
|
-
"content" in value &&
|
|
683
|
-
typeof (value as { content: unknown }).content === "string"
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* Character budget cap for tool-call `input` payloads embedded in the
|
|
689
|
-
* transcript. Tool call arguments can grow (diffs, large SQL strings, whole
|
|
690
|
-
* files passed inline), and the transcript's primary consumer is a
|
|
691
|
-
* downstream LLM that must `Read` this file as context for its own turn —
|
|
692
|
-
* so we cap the per-call JSON at a predictable size. The suffix
|
|
693
|
-
* `[+N chars]` preserves the dropped length for humans reviewing the file.
|
|
694
|
-
*
|
|
695
|
-
* Tool _results_ are intentionally NOT included in the transcript. File
|
|
696
|
-
* contents, shell output, and search results inflate the transcript
|
|
697
|
-
* dramatically and lead to context rot on the next stage. A reader (human
|
|
698
|
-
* or model) can still reconstruct what the tool returned by looking at
|
|
699
|
-
* the assistant's subsequent text — which is the whole point of the
|
|
700
|
-
* assistant summarising its own work.
|
|
701
|
-
*/
|
|
702
|
-
const TRANSCRIPT_TOOL_INPUT_BUDGET = 800;
|
|
703
|
-
|
|
704
|
-
function truncateForTranscript(text: string, max: number): string {
|
|
705
|
-
if (text.length <= max) return text;
|
|
706
|
-
return text.slice(0, max) + ` … [+${text.length - max} chars]`;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/** Render a tool_use `input` object as a JSON-ish block, capped to budget. */
|
|
710
|
-
function renderToolInput(input: unknown): string {
|
|
711
|
-
let json: string;
|
|
712
|
-
try {
|
|
713
|
-
json = JSON.stringify(input, null, 2);
|
|
714
|
-
} catch {
|
|
715
|
-
json = String(input);
|
|
716
|
-
}
|
|
717
|
-
return truncateForTranscript(json, TRANSCRIPT_TOOL_INPUT_BUDGET);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Render a Claude transcript as readable Markdown.
|
|
722
|
-
*
|
|
723
|
-
* Captures the user/agent interaction chronologically:
|
|
724
|
-
* - User messages (string content) → `### User`
|
|
725
|
-
* - Assistant text blocks → `### Assistant`
|
|
726
|
-
* - Assistant `tool_use` blocks → `**→ \`Name\`**` + JSON input
|
|
727
|
-
*
|
|
728
|
-
* Intentionally omitted:
|
|
729
|
-
* - `tool_result` blocks — their payloads (file contents, shell output,
|
|
730
|
-
* stringified diffs) dominate the transcript and lead to context rot on
|
|
731
|
-
* the next stage. The assistant's subsequent text response already
|
|
732
|
-
* summarises what the tool returned; re-including the raw output
|
|
733
|
-
* duplicates that information at high token cost.
|
|
734
|
-
* - `thinking` blocks — verbose internal reasoning rarely useful when the
|
|
735
|
-
* transcript is re-ingested as context elsewhere.
|
|
736
|
-
* - `system` / `summary` / other non-message types.
|
|
737
|
-
*/
|
|
738
|
-
function renderClaudeTranscript(
|
|
739
|
-
messages: ReadonlyArray<{ type: string; message: unknown }>,
|
|
740
|
-
): string {
|
|
741
|
-
const sections: string[] = [];
|
|
742
|
-
|
|
743
|
-
for (const msg of messages) {
|
|
744
|
-
if (msg.type !== "user" && msg.type !== "assistant") continue;
|
|
745
|
-
|
|
746
|
-
// `message` shape is one of:
|
|
747
|
-
// - a plain string (legacy path),
|
|
748
|
-
// - `{ role, content: string }` (API-style plain text turn),
|
|
749
|
-
// - `{ role, content: Block[] }` (tool-use / tool-result turns).
|
|
750
|
-
// Normalise the first two into a single string; handle the third below.
|
|
751
|
-
const rawMessage = msg.message;
|
|
752
|
-
let plainText: string | null = null;
|
|
753
|
-
let arrayContent: unknown[] | null = null;
|
|
754
|
-
|
|
755
|
-
if (typeof rawMessage === "string") {
|
|
756
|
-
plainText = rawMessage;
|
|
757
|
-
} else if (rawMessage && typeof rawMessage === "object") {
|
|
758
|
-
const content = (rawMessage as { content?: unknown }).content;
|
|
759
|
-
if (typeof content === "string") {
|
|
760
|
-
plainText = content;
|
|
761
|
-
} else if (Array.isArray(content)) {
|
|
762
|
-
arrayContent = content;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (plainText !== null) {
|
|
767
|
-
const trimmed = plainText.trim();
|
|
768
|
-
if (trimmed) {
|
|
769
|
-
const header = msg.type === "user" ? "### User" : "### Assistant";
|
|
770
|
-
sections.push(`${header}\n\n${trimmed}`);
|
|
771
|
-
}
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
if (arrayContent === null) continue;
|
|
776
|
-
const content = arrayContent;
|
|
777
|
-
|
|
778
|
-
if (msg.type === "assistant") {
|
|
779
|
-
// Group all blocks from a single assistant message under one header
|
|
780
|
-
// so text and tool calls read as one coherent turn.
|
|
781
|
-
const parts: string[] = [];
|
|
782
|
-
for (const block of content) {
|
|
783
|
-
if (!block || typeof block !== "object") continue;
|
|
784
|
-
const b = block as Record<string, unknown>;
|
|
785
|
-
if (b["type"] === "text" && typeof b["text"] === "string") {
|
|
786
|
-
const txt = (b["text"] as string).trim();
|
|
787
|
-
if (txt) parts.push(txt);
|
|
788
|
-
} else if (b["type"] === "tool_use") {
|
|
789
|
-
const name =
|
|
790
|
-
typeof b["name"] === "string" ? (b["name"] as string) : "tool";
|
|
791
|
-
const input = renderToolInput(b["input"]);
|
|
792
|
-
parts.push(`**→ \`${name}\`**\n\n\`\`\`json\n${input}\n\`\`\``);
|
|
793
|
-
}
|
|
794
|
-
// Skip "thinking" blocks.
|
|
795
|
-
}
|
|
796
|
-
if (parts.length > 0) {
|
|
797
|
-
sections.push(`### Assistant\n\n${parts.join("\n\n")}`);
|
|
798
|
-
}
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// msg.type === "user" with array content — usually a batch of tool_results
|
|
803
|
-
// responding to the previous assistant turn's tool_use blocks. We skip
|
|
804
|
-
// the tool_result payloads entirely (see function docstring for why) and
|
|
805
|
-
// only surface any inline `text` blocks, which is where a real follow-up
|
|
806
|
-
// user turn would land.
|
|
807
|
-
for (const block of content) {
|
|
808
|
-
if (!block || typeof block !== "object") continue;
|
|
809
|
-
const b = block as Record<string, unknown>;
|
|
810
|
-
if (b["type"] === "text" && typeof b["text"] === "string") {
|
|
811
|
-
const txt = (b["text"] as string).trim();
|
|
812
|
-
if (txt) sections.push(`### User\n\n${txt}`);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
return sections.join("\n\n");
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Render a Copilot transcript as readable Markdown.
|
|
822
|
-
*
|
|
823
|
-
* Preserves the existing `assistant.message → content` extraction and adds
|
|
824
|
-
* `user.message` rendering plus any `toolCalls` attached to an assistant
|
|
825
|
-
* message. All other event types (`session.start`, `session.idle`, plain
|
|
826
|
-
* telemetry, etc.) are skipped.
|
|
827
|
-
*/
|
|
828
|
-
function renderCopilotTranscript(
|
|
829
|
-
events: ReadonlyArray<{ type?: unknown; data?: unknown }>,
|
|
830
|
-
): string {
|
|
831
|
-
const sections: string[] = [];
|
|
832
|
-
|
|
833
|
-
for (const evt of events) {
|
|
834
|
-
if (evt.type === "assistant.message") {
|
|
835
|
-
const data = evt.data;
|
|
836
|
-
if (!hasContent(data)) continue;
|
|
837
|
-
const parts: string[] = [];
|
|
838
|
-
const text = data.content.trim();
|
|
839
|
-
if (text) parts.push(text);
|
|
840
|
-
|
|
841
|
-
// toolCalls is an array on `assistant.message` data when present.
|
|
842
|
-
const toolCalls = (data as Record<string, unknown>)["toolCalls"];
|
|
843
|
-
if (Array.isArray(toolCalls)) {
|
|
844
|
-
for (const call of toolCalls) {
|
|
845
|
-
if (!call || typeof call !== "object") continue;
|
|
846
|
-
const c = call as Record<string, unknown>;
|
|
847
|
-
const name =
|
|
848
|
-
typeof c["name"] === "string"
|
|
849
|
-
? (c["name"] as string)
|
|
850
|
-
: typeof c["toolName"] === "string"
|
|
851
|
-
? (c["toolName"] as string)
|
|
852
|
-
: "tool";
|
|
853
|
-
const args = c["arguments"] ?? c["input"] ?? c["parameters"];
|
|
854
|
-
parts.push(
|
|
855
|
-
`**→ \`${name}\`**\n\n\`\`\`json\n${renderToolInput(args)}\n\`\`\``,
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
if (parts.length > 0) {
|
|
861
|
-
sections.push(`### Assistant\n\n${parts.join("\n\n")}`);
|
|
862
|
-
}
|
|
863
|
-
continue;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
if (evt.type === "user.message") {
|
|
867
|
-
const data = evt.data;
|
|
868
|
-
if (hasContent(data)) {
|
|
869
|
-
const text = data.content.trim();
|
|
870
|
-
if (text) sections.push(`### User\n\n${text}`);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
// All other event types are intentionally skipped.
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return sections.join("\n\n");
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
* Render an OpenCode prompt response as readable Markdown.
|
|
881
|
-
*
|
|
882
|
-
* OpenCode hands us `{ info, parts }`; `parts` is a discriminated union where
|
|
883
|
-
* `text` parts carry the assistant reply and `tool` parts carry tool
|
|
884
|
-
* invocations. `reasoning` and `subtask` parts are internal and omitted.
|
|
885
|
-
*/
|
|
886
|
-
function renderOpencodeTranscript(response: {
|
|
887
|
-
parts?: ReadonlyArray<
|
|
888
|
-
{ type?: unknown; text?: unknown } & Record<string, unknown>
|
|
889
|
-
>;
|
|
890
|
-
}): string {
|
|
891
|
-
if (!response.parts) return "";
|
|
892
|
-
const parts: string[] = [];
|
|
893
|
-
for (const part of response.parts) {
|
|
894
|
-
if (!part || typeof part !== "object") continue;
|
|
895
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
896
|
-
const txt = part.text.trim();
|
|
897
|
-
if (txt) parts.push(txt);
|
|
898
|
-
} else if (part.type === "tool") {
|
|
899
|
-
const name =
|
|
900
|
-
typeof part["tool"] === "string"
|
|
901
|
-
? (part["tool"] as string)
|
|
902
|
-
: typeof part["name"] === "string"
|
|
903
|
-
? (part["name"] as string)
|
|
904
|
-
: "tool";
|
|
905
|
-
const state = part["state"];
|
|
906
|
-
const args =
|
|
907
|
-
state && typeof state === "object"
|
|
908
|
-
? ((state as Record<string, unknown>)["input"] ??
|
|
909
|
-
(state as Record<string, unknown>)["args"])
|
|
910
|
-
: undefined;
|
|
911
|
-
parts.push(
|
|
912
|
-
`**→ \`${name}\`**\n\n\`\`\`json\n${renderToolInput(args)}\n\`\`\``,
|
|
913
|
-
);
|
|
914
|
-
// Tool outputs are intentionally omitted — see the comment on
|
|
915
|
-
// `TRANSCRIPT_TOOL_INPUT_BUDGET` for the context-rot rationale.
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
if (parts.length === 0) return "";
|
|
919
|
-
return `### Assistant\n\n${parts.join("\n\n")}`;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
export function renderMessagesToText(messages: SavedMessage[]): string {
|
|
923
|
-
// Claude messages already come in as a flat chronological list — render
|
|
924
|
-
// the whole slice at once so the helper can cross-reference tool_use_ids
|
|
925
|
-
// against tool_result blocks. Copilot and OpenCode keep their existing
|
|
926
|
-
// per-message rendering.
|
|
927
|
-
const sections: string[] = [];
|
|
928
|
-
const claudeBatch: Array<{ type: string; message: unknown }> = [];
|
|
929
|
-
|
|
930
|
-
const flushClaude = (): void => {
|
|
931
|
-
if (claudeBatch.length === 0) return;
|
|
932
|
-
const rendered = renderClaudeTranscript(claudeBatch);
|
|
933
|
-
if (rendered) sections.push(rendered);
|
|
934
|
-
claudeBatch.length = 0;
|
|
935
|
-
};
|
|
936
|
-
|
|
937
|
-
for (const m of messages) {
|
|
938
|
-
if (m.provider === "claude") {
|
|
939
|
-
claudeBatch.push(m.data as unknown as { type: string; message: unknown });
|
|
940
|
-
continue;
|
|
941
|
-
}
|
|
942
|
-
flushClaude();
|
|
943
|
-
if (m.provider === "copilot") {
|
|
944
|
-
const rendered = renderCopilotTranscript([
|
|
945
|
-
m.data as unknown as { type?: unknown; data?: unknown },
|
|
946
|
-
]);
|
|
947
|
-
if (rendered) sections.push(rendered);
|
|
948
|
-
} else if (m.provider === "opencode") {
|
|
949
|
-
const rendered = renderOpencodeTranscript(
|
|
950
|
-
m.data as unknown as {
|
|
951
|
-
parts?: ReadonlyArray<
|
|
952
|
-
{ type?: unknown; text?: unknown } & Record<string, unknown>
|
|
953
|
-
>;
|
|
954
|
-
},
|
|
955
|
-
);
|
|
956
|
-
if (rendered) sections.push(rendered);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
flushClaude();
|
|
960
|
-
|
|
961
|
-
return sections.join("\n\n");
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
/** Resolve a SessionRef (string or SessionHandle) to the session name. */
|
|
965
|
-
function resolveRef(ref: SessionRef): string {
|
|
966
|
-
return typeof ref === "string" ? ref : ref.name;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
/**
|
|
970
|
-
* Minimal Copilot session surface required by `wrapCopilotSend()`.
|
|
971
|
-
* Uses a generic `on` signature to remain compatible with both the real
|
|
972
|
-
* CopilotSession and lightweight test mocks.
|
|
973
|
-
*/
|
|
974
|
-
export interface CopilotSendSessionSurface {
|
|
975
|
-
on(
|
|
976
|
-
eventType: string,
|
|
977
|
-
handler: (event: { data?: unknown }) => void,
|
|
978
|
-
): () => void;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
/**
|
|
982
|
-
* Wraps a Copilot session's `send()` to block until `session.idle` fires.
|
|
983
|
-
*
|
|
984
|
-
* Copilot's `send()` is fire-and-forget — it returns immediately after
|
|
985
|
-
* queuing the message. This wrapper blocks the returned promise until the
|
|
986
|
-
* session emits `session.idle` (turn complete) or `session.error`.
|
|
987
|
-
*
|
|
988
|
-
* HIL detection for Copilot is handled separately by
|
|
989
|
-
* `watchCopilotSessionForHIL()`, which subscribes to the session's
|
|
990
|
-
* `tool.execution_start` / `tool.execution_complete` events for the
|
|
991
|
-
* `ask_user` built-in tool. Those events fire regardless of whether
|
|
992
|
-
* an `onUserInputRequest` handler is registered, so we can detect HIL
|
|
993
|
-
* via native SDK events while the CLI continues to handle user input
|
|
994
|
-
* locally in the tmux pane.
|
|
995
|
-
*
|
|
996
|
-
* Exported for unit testing.
|
|
997
|
-
*/
|
|
998
|
-
export function wrapCopilotSend<O, R>(
|
|
999
|
-
session: CopilotSendSessionSurface,
|
|
1000
|
-
nativeSend: (options: O) => Promise<R>,
|
|
1001
|
-
): (options: O) => Promise<R> {
|
|
1002
|
-
return async (options: O): Promise<R> => {
|
|
1003
|
-
const idle = new Promise<void>((resolve, reject) => {
|
|
1004
|
-
let unsubIdle: (() => void) | undefined;
|
|
1005
|
-
let unsubError: (() => void) | undefined;
|
|
1006
|
-
const cleanup = () => {
|
|
1007
|
-
unsubIdle?.();
|
|
1008
|
-
unsubError?.();
|
|
1009
|
-
};
|
|
1010
|
-
unsubIdle = session.on("session.idle", () => {
|
|
1011
|
-
cleanup();
|
|
1012
|
-
resolve();
|
|
1013
|
-
});
|
|
1014
|
-
unsubError = session.on("session.error", (event) => {
|
|
1015
|
-
cleanup();
|
|
1016
|
-
const data = event.data as { message?: string } | undefined;
|
|
1017
|
-
reject(new Error(data?.message ?? "Copilot session error"));
|
|
1018
|
-
});
|
|
1019
|
-
});
|
|
1020
|
-
const result = await nativeSend(options);
|
|
1021
|
-
await idle;
|
|
1022
|
-
return result;
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* Minimal shape of an event as produced by the OpenCode v2 SDK event stream.
|
|
1028
|
-
* Using a structural interface rather than the SDK's generated union type keeps
|
|
1029
|
-
* this helper independently unit-testable with plain objects.
|
|
1030
|
-
*
|
|
1031
|
-
* `sessionID` is optional because many OpenCode event types (e.g.
|
|
1032
|
-
* `file.edited`, `session.compacted`) carry properties without that field.
|
|
1033
|
-
* The `watchOpencodeStreamForHIL` implementation guards with a runtime check.
|
|
1034
|
-
*/
|
|
1035
|
-
export interface OpenCodeHILEvent {
|
|
1036
|
-
type: string;
|
|
1037
|
-
properties: { sessionID?: string; [key: string]: unknown };
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Consume an OpenCode SSE event stream and call `onHIL` whenever the session
|
|
1042
|
-
* with `sessionId` enters or exits a human-in-the-loop (HIL) state:
|
|
1043
|
-
*
|
|
1044
|
-
* - `question.asked` → `onHIL(true)` (agent awaiting user input)
|
|
1045
|
-
* - `question.replied` → `onHIL(false)` (user answered, agent resumes)
|
|
1046
|
-
* - `question.rejected` → `onHIL(false)` (user dismissed, agent resumes)
|
|
1047
|
-
*
|
|
1048
|
-
* Events for other sessions are silently ignored. The function returns when
|
|
1049
|
-
* the stream is exhausted (i.e. the server closes the connection).
|
|
1050
|
-
*
|
|
1051
|
-
* NOTE: OpenCode does not emit any bus event for MCP-server-initiated
|
|
1052
|
-
* elicitation requests — its MCP client never registers an
|
|
1053
|
-
* `ElicitRequestSchema` handler, so such requests are auto-rejected by the
|
|
1054
|
-
* MCP SDK at the protocol layer before reaching any OpenCode-level code.
|
|
1055
|
-
* As a result, the workflow UI **cannot** mark an OpenCode session as
|
|
1056
|
-
* "awaiting input" for MCP elicitation; this is an upstream limitation that
|
|
1057
|
-
* Atomic cannot work around. If a future OpenCode release surfaces MCP
|
|
1058
|
-
* elicitation as a bus event, extend the switch below (or add a sibling
|
|
1059
|
-
* watcher) to map it onto `onHIL`.
|
|
1060
|
-
*
|
|
1061
|
-
* Exported for unit testing.
|
|
1062
|
-
*/
|
|
1063
|
-
export async function watchOpencodeStreamForHIL(
|
|
1064
|
-
stream: AsyncIterable<OpenCodeHILEvent>,
|
|
1065
|
-
sessionId: string,
|
|
1066
|
-
onHIL: (waiting: boolean) => void,
|
|
1067
|
-
): Promise<void> {
|
|
1068
|
-
for await (const event of stream) {
|
|
1069
|
-
if (
|
|
1070
|
-
event.type === "question.asked" &&
|
|
1071
|
-
event.properties.sessionID === sessionId
|
|
1072
|
-
) {
|
|
1073
|
-
onHIL(true);
|
|
1074
|
-
} else if (
|
|
1075
|
-
(event.type === "question.replied" ||
|
|
1076
|
-
event.type === "question.rejected") &&
|
|
1077
|
-
event.properties.sessionID === sessionId
|
|
1078
|
-
) {
|
|
1079
|
-
onHIL(false);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Minimal Copilot session surface required by `watchCopilotSessionForHIL()`.
|
|
1086
|
-
* A structural `on()` signature keeps this helper independently unit-testable
|
|
1087
|
-
* with plain objects and compatible with both the real CopilotSession and
|
|
1088
|
-
* test mocks.
|
|
1089
|
-
*/
|
|
1090
|
-
export interface CopilotHILSessionSurface {
|
|
1091
|
-
on(
|
|
1092
|
-
eventType: string,
|
|
1093
|
-
handler: (event: { data?: unknown }) => void,
|
|
1094
|
-
): () => void;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Subscribe to a Copilot session's tool-execution events to track HIL state
|
|
1099
|
-
* for the `ask_user` built-in tool:
|
|
1100
|
-
*
|
|
1101
|
-
* - `tool.execution_start` with `toolName === "ask_user"` → `onHIL(true)`
|
|
1102
|
-
* - `tool.execution_complete` with matching `toolCallId` → `onHIL(false)`
|
|
1103
|
-
*
|
|
1104
|
-
* These events fire regardless of whether an `onUserInputRequest` handler is
|
|
1105
|
-
* registered, so we can detect HIL without providing one — letting the CLI
|
|
1106
|
-
* keep its native tmux-pane dialog.
|
|
1107
|
-
*
|
|
1108
|
-
* Overlapping `ask_user` invocations are tracked by `toolCallId` so
|
|
1109
|
-
* `onHIL(false)` only fires after the last active request resolves.
|
|
1110
|
-
*
|
|
1111
|
-
* Returns an unsubscribe function that removes both listeners.
|
|
1112
|
-
*
|
|
1113
|
-
* Exported for unit testing.
|
|
1114
|
-
*/
|
|
1115
|
-
export function watchCopilotSessionForHIL(
|
|
1116
|
-
session: CopilotHILSessionSurface,
|
|
1117
|
-
onHIL: (waiting: boolean) => void,
|
|
1118
|
-
): () => void {
|
|
1119
|
-
const active = new Set<string>();
|
|
1120
|
-
const unsubStart = session.on("tool.execution_start", (event) => {
|
|
1121
|
-
const data = event.data as
|
|
1122
|
-
| { toolName?: string; toolCallId?: string }
|
|
1123
|
-
| undefined;
|
|
1124
|
-
if (data?.toolName === "ask_user" && data.toolCallId) {
|
|
1125
|
-
const wasEmpty = active.size === 0;
|
|
1126
|
-
active.add(data.toolCallId);
|
|
1127
|
-
if (wasEmpty) onHIL(true);
|
|
1128
|
-
}
|
|
1129
|
-
});
|
|
1130
|
-
const unsubComplete = session.on("tool.execution_complete", (event) => {
|
|
1131
|
-
const data = event.data as { toolCallId?: string } | undefined;
|
|
1132
|
-
if (
|
|
1133
|
-
data?.toolCallId &&
|
|
1134
|
-
active.delete(data.toolCallId) &&
|
|
1135
|
-
active.size === 0
|
|
1136
|
-
) {
|
|
1137
|
-
onHIL(false);
|
|
1138
|
-
}
|
|
1139
|
-
});
|
|
1140
|
-
return () => {
|
|
1141
|
-
unsubStart();
|
|
1142
|
-
unsubComplete();
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
/**
|
|
1147
|
-
* Subscribe to a Copilot session's elicitation events to track HIL state for
|
|
1148
|
-
* `session.ui.elicitation()`, `session.ui.select()`, `session.ui.input()`, and
|
|
1149
|
-
* MCP-server-initiated elicitation requests:
|
|
1150
|
-
*
|
|
1151
|
-
* - `elicitation.requested` → `onHIL(true)` (set transitions empty→non-empty)
|
|
1152
|
-
* - `elicitation.completed` → `onHIL(false)` (set transitions non-empty→empty)
|
|
1153
|
-
*
|
|
1154
|
-
* Overlapping elicitation requests are tracked by `requestId` so
|
|
1155
|
-
* `onHIL(false)` only fires after the last in-flight request completes.
|
|
1156
|
-
*
|
|
1157
|
-
* Returns an unsubscribe function that removes both listeners.
|
|
1158
|
-
*
|
|
1159
|
-
* Exported for unit testing.
|
|
1160
|
-
*/
|
|
1161
|
-
export function watchCopilotSessionForElicitation(
|
|
1162
|
-
session: CopilotHILSessionSurface,
|
|
1163
|
-
onHIL: (waiting: boolean) => void,
|
|
1164
|
-
): () => void {
|
|
1165
|
-
const active = new Set<string>();
|
|
1166
|
-
const unsubRequested = session.on("elicitation.requested", (event) => {
|
|
1167
|
-
const data = event.data as { requestId?: string } | undefined;
|
|
1168
|
-
if (data?.requestId) {
|
|
1169
|
-
const wasEmpty = active.size === 0;
|
|
1170
|
-
active.add(data.requestId);
|
|
1171
|
-
if (wasEmpty) onHIL(true);
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
const unsubCompleted = session.on("elicitation.completed", (event) => {
|
|
1175
|
-
const data = event.data as { requestId?: string } | undefined;
|
|
1176
|
-
if (data?.requestId && active.delete(data.requestId) && active.size === 0) {
|
|
1177
|
-
onHIL(false);
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
1180
|
-
return () => {
|
|
1181
|
-
unsubRequested();
|
|
1182
|
-
unsubCompleted();
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
// ============================================================================
|
|
1187
|
-
// Shared transcript / message readers
|
|
1188
|
-
// ============================================================================
|
|
1189
|
-
|
|
1190
|
-
/**
|
|
1191
|
-
* Create a `transcript(ref)` function bound to a completed-session registry.
|
|
1192
|
-
* Used by both the top-level WorkflowContext and per-session SessionContext
|
|
1193
|
-
* so the implementation is defined once.
|
|
1194
|
-
*/
|
|
1195
|
-
function createTranscriptReader(
|
|
1196
|
-
completedRegistry: Map<string, SessionResult>,
|
|
1197
|
-
): (ref: SessionRef) => Promise<Transcript> {
|
|
1198
|
-
return async (ref) => {
|
|
1199
|
-
const refName = resolveRef(ref);
|
|
1200
|
-
const prev = completedRegistry.get(refName);
|
|
1201
|
-
if (!prev) {
|
|
1202
|
-
const available = [...completedRegistry.keys()].join(", ") || "(none)";
|
|
1203
|
-
throw new Error(
|
|
1204
|
-
`No transcript for "${refName}". Available: ${available}`,
|
|
1205
|
-
);
|
|
1206
|
-
}
|
|
1207
|
-
const filePath = join(prev.sessionDir, "inbox.md");
|
|
1208
|
-
const content = await Bun.file(filePath).text();
|
|
1209
|
-
return { path: filePath, content };
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
/**
|
|
1214
|
-
* Create a `getMessages(ref)` function bound to a completed-session registry.
|
|
1215
|
-
* Used by both the top-level WorkflowContext and per-session SessionContext.
|
|
1216
|
-
*/
|
|
1217
|
-
function createMessagesReader(
|
|
1218
|
-
completedRegistry: Map<string, SessionResult>,
|
|
1219
|
-
): (ref: SessionRef) => Promise<SavedMessage[]> {
|
|
1220
|
-
return async (ref) => {
|
|
1221
|
-
const refName = resolveRef(ref);
|
|
1222
|
-
const prev = completedRegistry.get(refName);
|
|
1223
|
-
if (!prev) {
|
|
1224
|
-
const available = [...completedRegistry.keys()].join(", ") || "(none)";
|
|
1225
|
-
throw new Error(`No messages for "${refName}". Available: ${available}`);
|
|
1226
|
-
}
|
|
1227
|
-
const filePath = join(prev.sessionDir, "messages.json");
|
|
1228
|
-
const raw = await Bun.file(filePath).text();
|
|
1229
|
-
const parsed: unknown = JSON.parse(raw);
|
|
1230
|
-
if (!Array.isArray(parsed)) {
|
|
1231
|
-
throw new Error(`Invalid messages file for "${refName}": expected array`);
|
|
1232
|
-
}
|
|
1233
|
-
return parsed.filter(isValidSavedMessage);
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// ============================================================================
|
|
1238
|
-
// Session runner — implements ctx.stage() lifecycle
|
|
1239
|
-
// ============================================================================
|
|
1240
|
-
|
|
1241
|
-
/** Shared state passed to session runners by the orchestrator. */
|
|
1242
|
-
interface SharedRunnerState {
|
|
1243
|
-
tmuxSessionName: string;
|
|
1244
|
-
sessionsBaseDir: string;
|
|
1245
|
-
/**
|
|
1246
|
-
* The project root the workflow is operating against. Threaded through to
|
|
1247
|
-
* provider initialization so headless paths resolve project-scoped config
|
|
1248
|
-
* (e.g. `additional-instructions`) from the workflow's actual root rather
|
|
1249
|
-
* than `process.cwd()`, which can drift when workflows are invoked
|
|
1250
|
-
* programmatically or from a subdirectory.
|
|
1251
|
-
*/
|
|
1252
|
-
projectRoot: string;
|
|
1253
|
-
agent: AgentType;
|
|
1254
|
-
/**
|
|
1255
|
-
* Structured inputs for this workflow run. Free-form workflows use
|
|
1256
|
-
* `{ prompt: "..." }`; structured workflows use their declared field
|
|
1257
|
-
* names. Workflow authors read both shapes via `ctx.inputs` — integer
|
|
1258
|
-
* inputs are parsed to `number`, everything else stays a `string`.
|
|
1259
|
-
*/
|
|
1260
|
-
inputs: Record<string, string | number>;
|
|
1261
|
-
/** User-configured provider overrides (global + local merged). */
|
|
1262
|
-
providerOverrides: ProviderOverrides;
|
|
1263
|
-
/**
|
|
1264
|
-
* Extra CLI flags appended to the agent's chat flags, derived from
|
|
1265
|
-
* the project's scm selection. Currently only populated for Copilot
|
|
1266
|
-
* (which has no on-disk MCP toggle — see `getCopilotScmDisableFlags`).
|
|
1267
|
-
*/
|
|
1268
|
-
extraChatFlags: string[];
|
|
1269
|
-
panel: OrchestratorPanel;
|
|
1270
|
-
/** Sessions that have been spawned (for name uniqueness + cleanup). */
|
|
1271
|
-
activeRegistry: Map<string, ActiveSession>;
|
|
1272
|
-
/** Sessions that completed successfully (for transcript reads). */
|
|
1273
|
-
completedRegistry: Map<string, SessionResult>;
|
|
1274
|
-
/** Sessions that already failed before completing successfully. */
|
|
1275
|
-
failedRegistry: Set<string>;
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* Append tool names to a Copilot `excludedTools` list without duplicating
|
|
1280
|
-
* entries the caller already supplied. Exported for unit testing.
|
|
1281
|
-
*/
|
|
1282
|
-
export function mergeExcludedTools(
|
|
1283
|
-
existing: string[] | undefined,
|
|
1284
|
-
extras: string[],
|
|
1285
|
-
): string[] {
|
|
1286
|
-
const merged = [...(existing ?? [])];
|
|
1287
|
-
for (const tool of extras) {
|
|
1288
|
-
if (!merged.includes(tool)) merged.push(tool);
|
|
1289
|
-
}
|
|
1290
|
-
return merged;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
type ExternalCopilotClientOptions = Omit<
|
|
1294
|
-
StageClientOptions<"copilot">,
|
|
1295
|
-
"gitHubToken" | "useLoggedInUser"
|
|
1296
|
-
>;
|
|
1297
|
-
|
|
1298
|
-
interface ExternalCopilotOptions {
|
|
1299
|
-
clientOptions: ExternalCopilotClientOptions;
|
|
1300
|
-
sessionGitHubToken?: string;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
/**
|
|
1304
|
-
* Copilot SDK 0.3.0 rejects client-level auth options when connecting to an
|
|
1305
|
-
* existing `cliUrl`. Visible stages use an already-running TUI server, so move
|
|
1306
|
-
* token auth to the session-level option that 0.3.0 introduced for this case.
|
|
1307
|
-
*/
|
|
1308
|
-
export function normalizeExternalCopilotOptions(
|
|
1309
|
-
clientOptions: StageClientOptions<"copilot">,
|
|
1310
|
-
sessionGitHubToken?: string,
|
|
1311
|
-
): ExternalCopilotOptions {
|
|
1312
|
-
const {
|
|
1313
|
-
gitHubToken: clientGitHubToken,
|
|
1314
|
-
useLoggedInUser,
|
|
1315
|
-
...externalClientOptions
|
|
1316
|
-
} = clientOptions;
|
|
1317
|
-
|
|
1318
|
-
if (useLoggedInUser !== undefined) {
|
|
1319
|
-
throw new Error(
|
|
1320
|
-
"Copilot client option `useLoggedInUser` cannot be used for visible stages because they connect to an existing Copilot CLI server. Configure authentication on the server process instead.",
|
|
1321
|
-
);
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
const normalized: ExternalCopilotOptions = {
|
|
1325
|
-
clientOptions: externalClientOptions,
|
|
1326
|
-
};
|
|
1327
|
-
if (sessionGitHubToken !== undefined) {
|
|
1328
|
-
normalized.sessionGitHubToken = sessionGitHubToken;
|
|
1329
|
-
} else if (clientGitHubToken !== undefined) {
|
|
1330
|
-
normalized.sessionGitHubToken = clientGitHubToken;
|
|
1331
|
-
}
|
|
1332
|
-
return normalized;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
/**
|
|
1336
|
-
* Create the provider-specific client and session for a stage.
|
|
1337
|
-
* Called by the session runner after server readiness is confirmed.
|
|
1338
|
-
*
|
|
1339
|
-
* Generic over `A` so callers receive typed `ProviderClient<A>` /
|
|
1340
|
-
* `ProviderSession<A>` without unsafe casts. The internal `switch`
|
|
1341
|
-
* branches know the concrete types being constructed, so the `as`
|
|
1342
|
-
* assertions here are producer-side (correct by construction) rather
|
|
1343
|
-
* than consumer-side (trusting the caller to guess right).
|
|
1344
|
-
*/
|
|
1345
|
-
async function initProviderClientAndSession<A extends AgentType>(
|
|
1346
|
-
agent: A,
|
|
1347
|
-
serverUrl: string,
|
|
1348
|
-
paneId: string,
|
|
1349
|
-
projectRoot: string,
|
|
1350
|
-
clientOpts: StageClientOptions<A>,
|
|
1351
|
-
sessionOpts: StageSessionOptions<A>,
|
|
1352
|
-
headless = false,
|
|
1353
|
-
onHIL?: (waiting: boolean) => void,
|
|
1354
|
-
): Promise<{
|
|
1355
|
-
client: ProviderClient<A>;
|
|
1356
|
-
session: ProviderSession<A>;
|
|
1357
|
-
/** Optional cleanup for SDK-managed resources (e.g. headless OpenCode server). */
|
|
1358
|
-
cleanup?: () => void;
|
|
1359
|
-
}> {
|
|
1360
|
-
type Result = {
|
|
1361
|
-
client: ProviderClient<A>;
|
|
1362
|
-
session: ProviderSession<A>;
|
|
1363
|
-
cleanup?: () => void;
|
|
1364
|
-
};
|
|
1365
|
-
|
|
1366
|
-
switch (agent) {
|
|
1367
|
-
case "copilot": {
|
|
1368
|
-
const { CopilotClient, approveAll } = await import("@github/copilot-sdk");
|
|
1369
|
-
const { copilotSdkLaunchOptions, mergeCopilotSystemMessage } =
|
|
1370
|
-
await import("../providers/copilot.ts");
|
|
1371
|
-
const { resolveAdditionalInstructionsContent } =
|
|
1372
|
-
await import("../../services/config/additional-instructions.ts");
|
|
1373
|
-
const copilotClientOpts = clientOpts as StageClientOptions<"copilot">;
|
|
1374
|
-
const copilotSessionOpts = sessionOpts as StageSessionOptions<"copilot">;
|
|
1375
|
-
// Headless: let the SDK spawn its own CLI process (no cliUrl).
|
|
1376
|
-
// Non-headless: connect to the CLI server running in a tmux pane.
|
|
1377
|
-
// `env` is only meaningful in the headless path — the SDK ignores
|
|
1378
|
-
// it when `cliUrl` is set — but layering in `copilotSdkLaunchOptions`
|
|
1379
|
-
// when the caller didn't supply their own env keeps the
|
|
1380
|
-
// SQLite `ExperimentalWarning` from leaking through the SDK's
|
|
1381
|
-
// `[CLI subprocess]` stderr forwarder.
|
|
1382
|
-
let externalCopilotOptions: ExternalCopilotOptions | undefined;
|
|
1383
|
-
let client: InstanceType<typeof CopilotClient>;
|
|
1384
|
-
if (headless) {
|
|
1385
|
-
client = new CopilotClient({
|
|
1386
|
-
...copilotSdkLaunchOptions(),
|
|
1387
|
-
...copilotClientOpts,
|
|
1388
|
-
});
|
|
1389
|
-
} else {
|
|
1390
|
-
externalCopilotOptions = normalizeExternalCopilotOptions(
|
|
1391
|
-
copilotClientOpts,
|
|
1392
|
-
copilotSessionOpts.gitHubToken,
|
|
1393
|
-
);
|
|
1394
|
-
client = new CopilotClient({
|
|
1395
|
-
...externalCopilotOptions.clientOptions,
|
|
1396
|
-
cliUrl: serverUrl,
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
await client.start();
|
|
1400
|
-
// In headless stages, add `ask_user` to the session's excludedTools so
|
|
1401
|
-
// the agent cannot call the interactive question tool — there is no
|
|
1402
|
-
// human attached to answer and the SDK would otherwise sit blocked.
|
|
1403
|
-
const additionalInstructions =
|
|
1404
|
-
await resolveAdditionalInstructionsContent(projectRoot);
|
|
1405
|
-
const sessionConfig = {
|
|
1406
|
-
onPermissionRequest: approveAll,
|
|
1407
|
-
...copilotSessionOpts,
|
|
1408
|
-
...(externalCopilotOptions?.sessionGitHubToken !== undefined
|
|
1409
|
-
? { gitHubToken: externalCopilotOptions.sessionGitHubToken }
|
|
1410
|
-
: {}),
|
|
1411
|
-
...(headless
|
|
1412
|
-
? {
|
|
1413
|
-
excludedTools: mergeExcludedTools(
|
|
1414
|
-
copilotSessionOpts.excludedTools,
|
|
1415
|
-
["ask_user"],
|
|
1416
|
-
),
|
|
1417
|
-
}
|
|
1418
|
-
: {}),
|
|
1419
|
-
...(additionalInstructions
|
|
1420
|
-
? {
|
|
1421
|
-
systemMessage: mergeCopilotSystemMessage(
|
|
1422
|
-
copilotSessionOpts.systemMessage,
|
|
1423
|
-
additionalInstructions,
|
|
1424
|
-
),
|
|
1425
|
-
}
|
|
1426
|
-
: {}),
|
|
1427
|
-
};
|
|
1428
|
-
const session = await client.createSession(sessionConfig);
|
|
1429
|
-
if (!headless) {
|
|
1430
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
1431
|
-
}
|
|
1432
|
-
return { client, session } as Result;
|
|
1433
|
-
}
|
|
1434
|
-
case "opencode": {
|
|
1435
|
-
const ocSessionOpts = sessionOpts as StageSessionOptions<"opencode">;
|
|
1436
|
-
if (headless) {
|
|
1437
|
-
const { createOpencode } = await import("@opencode-ai/sdk/v2");
|
|
1438
|
-
// Scope OPENCODE_CLIENT=sdk around the SDK spawn so the subprocess
|
|
1439
|
-
// inherits it at fork time. OpenCode only registers its interactive
|
|
1440
|
-
// `question` tool when OPENCODE_CLIENT is "app"/"cli"/"desktop", so
|
|
1441
|
-
// identifying as "sdk" keeps the tool out of the registry entirely
|
|
1442
|
-
// — otherwise an unattended stage can hang forever on question.asked
|
|
1443
|
-
// (the tool's execute calls Question.ask directly and never consults
|
|
1444
|
-
// the session permission ruleset).
|
|
1445
|
-
return await withHeadlessOpencodeEnv(async () => {
|
|
1446
|
-
const oc = await createOpencode({ port: 0 });
|
|
1447
|
-
const sessionResult = await oc.client.session.create({
|
|
1448
|
-
permission: [{ permission: "*", pattern: "*", action: "allow" }],
|
|
1449
|
-
...ocSessionOpts,
|
|
1450
|
-
});
|
|
1451
|
-
return {
|
|
1452
|
-
client: oc.client,
|
|
1453
|
-
session: sessionResult.data!,
|
|
1454
|
-
cleanup: () => oc.server.close(),
|
|
1455
|
-
} as Result;
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
const { createOpencodeClient } = await import("@opencode-ai/sdk/v2");
|
|
1459
|
-
const ocClientOpts = clientOpts as StageClientOptions<"opencode">;
|
|
1460
|
-
const client = createOpencodeClient({
|
|
1461
|
-
...ocClientOpts,
|
|
1462
|
-
baseUrl: serverUrl,
|
|
1463
|
-
});
|
|
1464
|
-
const sessionResult = await client.session.create(ocSessionOpts);
|
|
1465
|
-
await client.tui.selectSession({ sessionID: sessionResult.data!.id });
|
|
1466
|
-
return { client, session: sessionResult.data! } as Result;
|
|
1467
|
-
}
|
|
1468
|
-
case "claude": {
|
|
1469
|
-
if (headless) {
|
|
1470
|
-
// Headless Claude stages use the Agent SDK directly — no tmux pane.
|
|
1471
|
-
// Each query gets its own SDK-assigned session_id; the wrapper
|
|
1472
|
-
// tracks the latest one and exposes it as `sessionId`.
|
|
1473
|
-
const client = new HeadlessClaudeClientWrapper();
|
|
1474
|
-
await client.start();
|
|
1475
|
-
const session = new HeadlessClaudeSessionWrapper(projectRoot);
|
|
1476
|
-
// Cast through `unknown` — `HeadlessClaudeClientWrapper` intentionally
|
|
1477
|
-
// omits the interactive-only fields (`paneId`, `sessionDir`, etc.)
|
|
1478
|
-
// that `ClaudeClientWrapper` has; both satisfy the same runtime
|
|
1479
|
-
// contract used by workflow code.
|
|
1480
|
-
return { client, session } as unknown as Result;
|
|
1481
|
-
}
|
|
1482
|
-
const claudeClientOpts = clientOpts as StageClientOptions<"claude">;
|
|
1483
|
-
const client = new ClaudeClientWrapper(paneId, claudeClientOpts);
|
|
1484
|
-
// `start()` now returns the Claude session UUID, which we pass through
|
|
1485
|
-
// to the session wrapper so `s.sessionId` is the Claude UUID (not the
|
|
1486
|
-
// atomic short ID). This fixes the parallel-workflow bug where save
|
|
1487
|
-
// used to look up "the newest Claude session globally" and could
|
|
1488
|
-
// attribute one branch's transcript to another.
|
|
1489
|
-
const claudeSessionId = await client.start();
|
|
1490
|
-
const session = new ClaudeSessionWrapper(paneId, claudeSessionId, onHIL);
|
|
1491
|
-
return { client, session } as Result;
|
|
1492
|
-
}
|
|
1493
|
-
default:
|
|
1494
|
-
return assertNever(agent);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
/**
|
|
1499
|
-
* Clean up provider-specific resources after a stage callback completes.
|
|
1500
|
-
* Errors are silently caught — cleanup must not mask callback errors.
|
|
1501
|
-
*
|
|
1502
|
-
* The `switch (agent)` already narrows the type, so we call
|
|
1503
|
-
* disconnect/stop directly without redundant `instanceof` checks or
|
|
1504
|
-
* dynamic imports.
|
|
1505
|
-
*/
|
|
1506
|
-
async function cleanupProvider<A extends AgentType>(
|
|
1507
|
-
agent: A,
|
|
1508
|
-
providerClient: ProviderClient<A>,
|
|
1509
|
-
providerSession: ProviderSession<A>,
|
|
1510
|
-
paneId: string,
|
|
1511
|
-
): Promise<void> {
|
|
1512
|
-
switch (agent) {
|
|
1513
|
-
case "copilot": {
|
|
1514
|
-
const session = providerSession as ProviderSession<"copilot">;
|
|
1515
|
-
const client = providerClient as ProviderClient<"copilot">;
|
|
1516
|
-
try {
|
|
1517
|
-
await session.disconnect();
|
|
1518
|
-
} catch (e) {
|
|
1519
|
-
console.warn(
|
|
1520
|
-
`[cleanup] copilot session disconnect failed: ${errorMessage(e)}`,
|
|
1521
|
-
);
|
|
1522
|
-
}
|
|
1523
|
-
try {
|
|
1524
|
-
await client.stop();
|
|
1525
|
-
} catch (e) {
|
|
1526
|
-
console.warn(
|
|
1527
|
-
`[cleanup] copilot client stop failed: ${errorMessage(e)}`,
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
break;
|
|
1531
|
-
}
|
|
1532
|
-
case "opencode":
|
|
1533
|
-
// Stateless HTTP client — no cleanup needed
|
|
1534
|
-
break;
|
|
1535
|
-
case "claude":
|
|
1536
|
-
// Headless Claude stages have no tmux pane to clear.
|
|
1537
|
-
if (!paneId.startsWith("headless-")) {
|
|
1538
|
-
try {
|
|
1539
|
-
await clearClaudeSession(paneId);
|
|
1540
|
-
} catch (e) {
|
|
1541
|
-
console.warn(
|
|
1542
|
-
`[cleanup] claude session clear failed: ${errorMessage(e)}`,
|
|
1543
|
-
);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
break;
|
|
1547
|
-
default:
|
|
1548
|
-
assertNever(agent);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
/**
|
|
1553
|
-
* Create a `ctx.stage()` function bound to a parent name for graph edges.
|
|
1554
|
-
*
|
|
1555
|
-
* Graph topology is auto-inferred from JavaScript's execution order:
|
|
1556
|
-
* - **Sequential** (`await`): the completed stage is in the frontier when the
|
|
1557
|
-
* next stage spawns → parent-child edge.
|
|
1558
|
-
* - **Parallel** (`Promise.all`): both calls fire in the same synchronous
|
|
1559
|
-
* frame → frontier is empty for the second call → sibling edges.
|
|
1560
|
-
* - **Fan-in**: after `Promise.all` resolves, all parallel stages are in the
|
|
1561
|
-
* frontier → the next stage depends on all of them.
|
|
1562
|
-
*
|
|
1563
|
-
* The returned function manages the full session lifecycle:
|
|
1564
|
-
* spawn → init client/session → run callback → flush saves → cleanup → complete/error.
|
|
1565
|
-
*/
|
|
1566
|
-
function createSessionRunner(
|
|
1567
|
-
shared: SharedRunnerState,
|
|
1568
|
-
parentName: string,
|
|
1569
|
-
): <T = void>(
|
|
1570
|
-
options: SessionRunOptions,
|
|
1571
|
-
clientOpts: StageClientOptions<AgentType>,
|
|
1572
|
-
sessionOpts: StageSessionOptions<AgentType>,
|
|
1573
|
-
run: (ctx: SessionContext) => Promise<T>,
|
|
1574
|
-
) => Promise<SessionHandle<T>> {
|
|
1575
|
-
const graphTracker = new GraphFrontierTracker(parentName);
|
|
1576
|
-
|
|
1577
|
-
return async <T = void>(
|
|
1578
|
-
options: SessionRunOptions,
|
|
1579
|
-
clientOpts: StageClientOptions<AgentType>,
|
|
1580
|
-
sessionOpts: StageSessionOptions<AgentType>,
|
|
1581
|
-
run: (ctx: SessionContext) => Promise<T>,
|
|
1582
|
-
): Promise<SessionHandle<T>> => {
|
|
1583
|
-
const { name } = options;
|
|
1584
|
-
|
|
1585
|
-
// ── 1. Validate name uniqueness (synchronous, before any await) ──
|
|
1586
|
-
if (!name || name.trim() === "") {
|
|
1587
|
-
throw new Error("Session name is required.");
|
|
1588
|
-
}
|
|
1589
|
-
if (
|
|
1590
|
-
shared.activeRegistry.has(name) ||
|
|
1591
|
-
shared.completedRegistry.has(name) ||
|
|
1592
|
-
shared.failedRegistry.has(name)
|
|
1593
|
-
) {
|
|
1594
|
-
throw new Error(`Duplicate session name: "${name}"`);
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
const isHeadless = options.headless === true;
|
|
1598
|
-
|
|
1599
|
-
// ── 2. Auto-infer graph parents from frontier (synchronous) ──
|
|
1600
|
-
// Headless stages are invisible in the graph — they must not consume or
|
|
1601
|
-
// update the frontier, otherwise the next visible stage gets orphaned
|
|
1602
|
-
// parent refs that don't exist in the panel.
|
|
1603
|
-
const graphParents = isHeadless ? [] : graphTracker.onSpawn();
|
|
1604
|
-
|
|
1605
|
-
// ── 3. Create done promise so dependent sessions can await this one ──
|
|
1606
|
-
let resolveDone!: () => void;
|
|
1607
|
-
let rejectDone!: (err: unknown) => void;
|
|
1608
|
-
const donePromise = new Promise<void>((resolve, reject) => {
|
|
1609
|
-
resolveDone = resolve;
|
|
1610
|
-
rejectDone = reject;
|
|
1611
|
-
});
|
|
1612
|
-
// Prevent "unhandled rejection" noise when no dependent awaits us.
|
|
1613
|
-
donePromise.catch(() => {});
|
|
1614
|
-
|
|
1615
|
-
// ── 4. Register in active registry (synchronous) ──
|
|
1616
|
-
// Placeholder paneId — filled in after tmux window creation.
|
|
1617
|
-
shared.activeRegistry.set(name, { name, paneId: "", done: donePromise });
|
|
1618
|
-
|
|
1619
|
-
const sessionId = generateId();
|
|
1620
|
-
let paneId = "";
|
|
1621
|
-
let panelSessionAdded = false;
|
|
1622
|
-
|
|
1623
|
-
try {
|
|
1624
|
-
// ── 6. Build pane command (OS allocates port via --port 0) ──
|
|
1625
|
-
const { command: paneCmd, envVars: paneEnvVars } = buildPaneCommand(
|
|
1626
|
-
shared.agent,
|
|
1627
|
-
shared.providerOverrides,
|
|
1628
|
-
shared.extraChatFlags,
|
|
1629
|
-
);
|
|
1630
|
-
|
|
1631
|
-
// ── 7. Create tmux window or headless execution ──
|
|
1632
|
-
let serverUrl: string;
|
|
1633
|
-
if (isHeadless) {
|
|
1634
|
-
// Headless stages use their SDKs directly — no tmux window.
|
|
1635
|
-
// Claude Agent SDK runs in-process; Copilot SDK spawns its own CLI;
|
|
1636
|
-
// OpenCode SDK starts both server and client via createOpencode().
|
|
1637
|
-
paneId = `headless-${name}-${sessionId}`;
|
|
1638
|
-
shared.activeRegistry.set(name, { name, paneId, done: donePromise });
|
|
1639
|
-
serverUrl = "";
|
|
1640
|
-
|
|
1641
|
-
shared.panel.backgroundTaskStarted();
|
|
1642
|
-
panelSessionAdded = true;
|
|
1643
|
-
} else {
|
|
1644
|
-
// Standard tmux window for visible stages.
|
|
1645
|
-
paneId = tmux.createWindow(
|
|
1646
|
-
shared.tmuxSessionName,
|
|
1647
|
-
name,
|
|
1648
|
-
paneCmd,
|
|
1649
|
-
undefined,
|
|
1650
|
-
paneEnvVars,
|
|
1651
|
-
);
|
|
1652
|
-
shared.activeRegistry.set(name, { name, paneId, done: donePromise });
|
|
1653
|
-
|
|
1654
|
-
spawnAttachedFooter(name, paneId);
|
|
1655
|
-
|
|
1656
|
-
serverUrl = await waitForServer(shared.agent, paneId);
|
|
1657
|
-
|
|
1658
|
-
shared.panel.addSession(name, graphParents);
|
|
1659
|
-
panelSessionAdded = true;
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
// ── 9. Create session directory ──
|
|
1663
|
-
const sessionDirName = `${name}-${sessionId}`;
|
|
1664
|
-
const sessionDir = join(shared.sessionsBaseDir, sessionDirName);
|
|
1665
|
-
await ensureDir(sessionDir);
|
|
1666
|
-
|
|
1667
|
-
const messagesPath = join(sessionDir, "messages.json");
|
|
1668
|
-
const inboxPath = join(sessionDir, "inbox.md");
|
|
1669
|
-
|
|
1670
|
-
// ── Message wrapping (Claude/Copilot/OpenCode) ──
|
|
1671
|
-
async function wrapMessages(
|
|
1672
|
-
arg: SessionEvent[] | SessionPromptResponse | string,
|
|
1673
|
-
): Promise<SavedMessage[]> {
|
|
1674
|
-
if (typeof arg === "string") {
|
|
1675
|
-
// `arg` is the Claude session UUID — either `s.sessionId` from an
|
|
1676
|
-
// interactive `ClaudeSessionWrapper` (set at `createClaudeSession`
|
|
1677
|
-
// time) or the SDK-emitted `session_id` tracked inside
|
|
1678
|
-
// `HeadlessClaudeSessionWrapper.query`. Using it directly removes
|
|
1679
|
-
// the "pick the globally newest Claude session" heuristic that
|
|
1680
|
-
// misattributed transcripts across parallel branches.
|
|
1681
|
-
if (!arg) {
|
|
1682
|
-
throw new Error(
|
|
1683
|
-
"wrapMessages: empty Claude session id. Call s.save(s.sessionId) " +
|
|
1684
|
-
"only after a successful s.session.query() (headless wrappers " +
|
|
1685
|
-
"only know their session_id once a query completes).",
|
|
1686
|
-
);
|
|
1687
|
-
}
|
|
1688
|
-
const { getSessionMessages } =
|
|
1689
|
-
await import("@anthropic-ai/claude-agent-sdk");
|
|
1690
|
-
const msgs: SessionMessage[] = await getSessionMessages(arg, {
|
|
1691
|
-
dir: process.cwd(),
|
|
1692
|
-
});
|
|
1693
|
-
return msgs.map((m) => ({ provider: "claude" as const, data: m }));
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
if (!Array.isArray(arg) && "info" in arg && "parts" in arg) {
|
|
1697
|
-
return [
|
|
1698
|
-
{
|
|
1699
|
-
provider: "opencode" as const,
|
|
1700
|
-
data: arg as SessionPromptResponse,
|
|
1701
|
-
},
|
|
1702
|
-
];
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
if (Array.isArray(arg)) {
|
|
1706
|
-
return (arg as SessionEvent[]).map((m) => ({
|
|
1707
|
-
provider: "copilot" as const,
|
|
1708
|
-
data: m,
|
|
1709
|
-
}));
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
return [];
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
// ── Save function ──
|
|
1716
|
-
const pendingSaves: Promise<void>[] = [];
|
|
1717
|
-
|
|
1718
|
-
const save: SaveTranscript = ((
|
|
1719
|
-
arg: SessionEvent[] | SessionPromptResponse | string,
|
|
1720
|
-
) => {
|
|
1721
|
-
const p = (async () => {
|
|
1722
|
-
const wrapped = await wrapMessages(arg);
|
|
1723
|
-
await Bun.write(messagesPath, JSON.stringify(wrapped, null, 2));
|
|
1724
|
-
const text = renderMessagesToText(wrapped);
|
|
1725
|
-
await Bun.write(inboxPath, text);
|
|
1726
|
-
})();
|
|
1727
|
-
pendingSaves.push(p);
|
|
1728
|
-
return p;
|
|
1729
|
-
}) as SaveTranscript;
|
|
1730
|
-
|
|
1731
|
-
// ── Transcript/messages access (reads only from completedRegistry) ──
|
|
1732
|
-
const transcriptFn = createTranscriptReader(shared.completedRegistry);
|
|
1733
|
-
const getMessagesFn = createMessagesReader(shared.completedRegistry);
|
|
1734
|
-
|
|
1735
|
-
// ── HIL (human-in-the-loop) callback ──
|
|
1736
|
-
// Unified callback passed to provider-specific HIL detection so that any
|
|
1737
|
-
// provider can signal when the agent is waiting for user input or has
|
|
1738
|
-
// resumed processing. Both `name` and `shared.panel` are guaranteed to
|
|
1739
|
-
// be in scope here: `name` is validated above and `shared.panel` is
|
|
1740
|
-
// always present on the shared runner state.
|
|
1741
|
-
const onHIL = (waiting: boolean) => {
|
|
1742
|
-
if (waiting) shared.panel.sessionAwaitingInput(name);
|
|
1743
|
-
else shared.panel.sessionResumed(name);
|
|
1744
|
-
};
|
|
1745
|
-
|
|
1746
|
-
// ── 12. Auto-create provider client and session ──
|
|
1747
|
-
const {
|
|
1748
|
-
client: providerClient,
|
|
1749
|
-
session: providerSession,
|
|
1750
|
-
cleanup: providerCleanup,
|
|
1751
|
-
} = await initProviderClientAndSession(
|
|
1752
|
-
shared.agent,
|
|
1753
|
-
serverUrl,
|
|
1754
|
-
paneId,
|
|
1755
|
-
shared.projectRoot,
|
|
1756
|
-
clientOpts,
|
|
1757
|
-
sessionOpts,
|
|
1758
|
-
isHeadless,
|
|
1759
|
-
onHIL,
|
|
1760
|
-
);
|
|
1761
|
-
|
|
1762
|
-
// ── 12a. Copilot: wrap send() to await session.idle ──
|
|
1763
|
-
// Copilot's send() is fire-and-forget — it returns immediately after
|
|
1764
|
-
// queuing the message. Without this wrapper, stage callbacks complete
|
|
1765
|
-
// before the agent finishes processing, causing getMessages() to
|
|
1766
|
-
// return incomplete data and the stage to be marked done prematurely.
|
|
1767
|
-
// We intercept send() to block until the session emits "session.idle",
|
|
1768
|
-
// matching the blocking behavior of Claude's query() and OpenCode's
|
|
1769
|
-
// session.prompt().
|
|
1770
|
-
//
|
|
1771
|
-
// Compatible with sendAndWait(): the SDK's _dispatchEvent broadcasts
|
|
1772
|
-
// to all handlers (typed + wildcard), so both this wrapper's listener
|
|
1773
|
-
// and sendAndWait's internal wildcard handler observe the same event.
|
|
1774
|
-
// Unsubscribe fn for the Copilot HIL event listeners; invoked in the
|
|
1775
|
-
// `finally` block so the handlers are removed when the stage ends.
|
|
1776
|
-
let hilUnsubscribe: (() => void) | undefined;
|
|
1777
|
-
let copilotElicitationUnsubscribe: (() => void) | undefined;
|
|
1778
|
-
|
|
1779
|
-
if (shared.agent === "copilot") {
|
|
1780
|
-
const copilotSession = providerSession as ProviderSession<"copilot">;
|
|
1781
|
-
const nativeSend = copilotSession.send.bind(copilotSession);
|
|
1782
|
-
copilotSession.send = wrapCopilotSend(copilotSession, nativeSend);
|
|
1783
|
-
|
|
1784
|
-
// Copilot HIL detection via native SDK events.
|
|
1785
|
-
//
|
|
1786
|
-
// `tool.execution_start` / `tool.execution_complete` fire for the
|
|
1787
|
-
// `ask_user` built-in tool regardless of whether `onUserInputRequest`
|
|
1788
|
-
// is registered, so we can detect HIL via the SDK's event stream and
|
|
1789
|
-
// still let the CLI render its native tmux-pane dialog.
|
|
1790
|
-
hilUnsubscribe = watchCopilotSessionForHIL(copilotSession, onHIL);
|
|
1791
|
-
|
|
1792
|
-
// Copilot elicitation HIL detection via native SDK events.
|
|
1793
|
-
//
|
|
1794
|
-
// `elicitation.requested` / `elicitation.completed` fire when the
|
|
1795
|
-
// agent calls `session.ui.elicitation()`, `session.ui.select()`,
|
|
1796
|
-
// `session.ui.input()`, or an MCP server issues an elicitation
|
|
1797
|
-
// request. These events are distinct from the `ask_user` tool and
|
|
1798
|
-
// require a separate watcher so the UI can show the "waiting for
|
|
1799
|
-
// response" indicator in all HIL scenarios.
|
|
1800
|
-
copilotElicitationUnsubscribe = watchCopilotSessionForElicitation(
|
|
1801
|
-
copilotSession,
|
|
1802
|
-
onHIL,
|
|
1803
|
-
);
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
// ── 12b. OpenCode: SSE event stream for HIL detection ──
|
|
1807
|
-
//
|
|
1808
|
-
// `client.event.subscribe()` yields `question.asked`, `question.replied`,
|
|
1809
|
-
// and `question.rejected` events in real time. The subscription is
|
|
1810
|
-
// **awaited** before the stage callback runs so the stream is guaranteed
|
|
1811
|
-
// to be open when the first prompt fires.
|
|
1812
|
-
if (shared.agent === "opencode") {
|
|
1813
|
-
const ocClient = providerClient as ProviderClient<"opencode">;
|
|
1814
|
-
const ocSession = providerSession as ProviderSession<"opencode">;
|
|
1815
|
-
const ocSessionId = ocSession.id;
|
|
1816
|
-
|
|
1817
|
-
try {
|
|
1818
|
-
const { stream } = await ocClient.event.subscribe();
|
|
1819
|
-
watchOpencodeStreamForHIL(stream, ocSessionId, onHIL).catch((err) => {
|
|
1820
|
-
console.warn(
|
|
1821
|
-
`[opencode] HIL event stream disconnected for session ${ocSessionId}: ${errorMessage(err)}`,
|
|
1822
|
-
);
|
|
1823
|
-
});
|
|
1824
|
-
} catch (err) {
|
|
1825
|
-
console.warn(
|
|
1826
|
-
`[opencode] HIL event stream failed to subscribe for session ${ocSessionId}: ${errorMessage(err)}`,
|
|
1827
|
-
);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
// ── 13. Construct SessionContext ──
|
|
1832
|
-
// Free-form workflows read their prompt via `s.inputs.prompt`;
|
|
1833
|
-
// structured workflows read their declared fields the same way.
|
|
1834
|
-
// A single uniform access pattern means workflow code never has
|
|
1835
|
-
// to branch on "is this workflow structured or free-form".
|
|
1836
|
-
//
|
|
1837
|
-
// `s.sessionId` is the provider-specific session identifier — the
|
|
1838
|
-
// Claude session UUID, the Copilot session id, or the OpenCode
|
|
1839
|
-
// session id. This is what workflows pass to `s.save(s.sessionId)`
|
|
1840
|
-
// to disambiguate their own transcript when several sessions run
|
|
1841
|
-
// in parallel under the same workflow.
|
|
1842
|
-
//
|
|
1843
|
-
// Exposed as a getter (not a snapshot) because headless Claude stages
|
|
1844
|
-
// don't know their SDK-assigned `session_id` until the first `query()`
|
|
1845
|
-
// completes — `HeadlessClaudeSessionWrapper._lastSessionId` starts empty
|
|
1846
|
-
// and is populated when the SDK emits a `result` event. A snapshot
|
|
1847
|
-
// captured at stage creation would leave `s.sessionId === ""` forever,
|
|
1848
|
-
// so `s.save(s.sessionId)` would always throw "empty Claude session id"
|
|
1849
|
-
// even though the query completed successfully.
|
|
1850
|
-
const ctx: SessionContext = {
|
|
1851
|
-
client: providerClient,
|
|
1852
|
-
session: providerSession,
|
|
1853
|
-
inputs: shared.inputs as SessionContext["inputs"],
|
|
1854
|
-
agent: shared.agent,
|
|
1855
|
-
sessionDir,
|
|
1856
|
-
paneId,
|
|
1857
|
-
get sessionId() {
|
|
1858
|
-
return resolveProviderSessionId(shared.agent, providerSession);
|
|
1859
|
-
},
|
|
1860
|
-
save,
|
|
1861
|
-
transcript: transcriptFn,
|
|
1862
|
-
getMessages: getMessagesFn,
|
|
1863
|
-
stage: createSessionRunner(shared, name) as SessionContext["stage"],
|
|
1864
|
-
};
|
|
1865
|
-
|
|
1866
|
-
// ── Write session metadata ──
|
|
1867
|
-
await Bun.write(
|
|
1868
|
-
join(sessionDir, "metadata.json"),
|
|
1869
|
-
JSON.stringify(
|
|
1870
|
-
{
|
|
1871
|
-
name,
|
|
1872
|
-
description: options.description ?? "",
|
|
1873
|
-
agent: shared.agent,
|
|
1874
|
-
paneId,
|
|
1875
|
-
serverUrl,
|
|
1876
|
-
port: serverUrl ? Number(serverUrl.split(":").pop()) : 0,
|
|
1877
|
-
startedAt: new Date().toISOString(),
|
|
1878
|
-
},
|
|
1879
|
-
null,
|
|
1880
|
-
2,
|
|
1881
|
-
),
|
|
1882
|
-
);
|
|
1883
|
-
|
|
1884
|
-
// ── 14. Run user callback ──
|
|
1885
|
-
let callbackResult: T;
|
|
1886
|
-
try {
|
|
1887
|
-
callbackResult = await run(ctx);
|
|
1888
|
-
if (pendingSaves.length > 0) await Promise.all(pendingSaves);
|
|
1889
|
-
} catch (error) {
|
|
1890
|
-
const message = errorMessage(error);
|
|
1891
|
-
await Bun.write(join(sessionDir, "error.txt"), message).catch(() => {});
|
|
1892
|
-
if (!isHeadless) shared.panel.sessionError(name, message);
|
|
1893
|
-
throw error;
|
|
1894
|
-
} finally {
|
|
1895
|
-
// ── 14a. Stop background HIL watcher (if any) ──
|
|
1896
|
-
hilUnsubscribe?.();
|
|
1897
|
-
copilotElicitationUnsubscribe?.();
|
|
1898
|
-
|
|
1899
|
-
// ── 14b. Auto-cleanup provider resources ──
|
|
1900
|
-
await cleanupProvider(
|
|
1901
|
-
shared.agent,
|
|
1902
|
-
providerClient,
|
|
1903
|
-
providerSession,
|
|
1904
|
-
paneId,
|
|
1905
|
-
);
|
|
1906
|
-
if (providerCleanup) {
|
|
1907
|
-
try {
|
|
1908
|
-
providerCleanup();
|
|
1909
|
-
} catch {}
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
// ── 15. Mark session complete ──
|
|
1914
|
-
if (isHeadless) {
|
|
1915
|
-
shared.panel.backgroundTaskFinished();
|
|
1916
|
-
} else {
|
|
1917
|
-
shared.panel.sessionSuccess(name);
|
|
1918
|
-
}
|
|
1919
|
-
const result: SessionResult = { name, sessionId, sessionDir, paneId };
|
|
1920
|
-
shared.completedRegistry.set(name, result);
|
|
1921
|
-
shared.activeRegistry.delete(name);
|
|
1922
|
-
resolveDone();
|
|
1923
|
-
|
|
1924
|
-
// Update frontier so the next stage in this scope chains from us.
|
|
1925
|
-
// Headless stages are transparent — they don't touch the frontier.
|
|
1926
|
-
if (!isHeadless) graphTracker.onSettle(name);
|
|
1927
|
-
return { name, id: sessionId, result: callbackResult! };
|
|
1928
|
-
} catch (error) {
|
|
1929
|
-
const message = errorMessage(error);
|
|
1930
|
-
if (panelSessionAdded) {
|
|
1931
|
-
if (isHeadless) {
|
|
1932
|
-
shared.panel.backgroundTaskFinished();
|
|
1933
|
-
} else {
|
|
1934
|
-
shared.panel.sessionError(name, message);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
// Kill the tmux window if one was created (visible stages and headless OpenCode).
|
|
1938
|
-
// Headless Claude/Copilot have virtual paneIds ("headless-...") — no window to kill.
|
|
1939
|
-
if (paneId && !paneId.startsWith("headless-")) {
|
|
1940
|
-
try {
|
|
1941
|
-
tmux.killWindow(shared.tmuxSessionName, name);
|
|
1942
|
-
} catch {}
|
|
1943
|
-
}
|
|
1944
|
-
// Ensure the done promise settles and the active entry is cleared.
|
|
1945
|
-
shared.activeRegistry.delete(name);
|
|
1946
|
-
shared.failedRegistry.add(name);
|
|
1947
|
-
rejectDone(error);
|
|
1948
|
-
// Update frontier even on failure — if the caller catches and
|
|
1949
|
-
// continues, the next stage should still chain from this one.
|
|
1950
|
-
// Headless stages are transparent — they don't touch the frontier.
|
|
1951
|
-
if (!isHeadless) graphTracker.onSettle(name);
|
|
1952
|
-
throw error;
|
|
1953
|
-
}
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
// ============================================================================
|
|
1958
|
-
// Orchestrator logic — runs inside a tmux pane
|
|
1959
|
-
// ============================================================================
|
|
1960
|
-
|
|
1961
|
-
export { validateOrchestratorEnv } from "./executor-env.ts";
|
|
1962
|
-
import { validateOrchestratorEnv } from "./executor-env.ts";
|
|
1963
|
-
|
|
1964
|
-
/**
|
|
1965
|
-
* Run the orchestrator for a compiled workflow definition.
|
|
1966
|
-
*
|
|
1967
|
-
* Called by the SDK's `orchestrator-entry.ts` after it imports the
|
|
1968
|
-
* workflow module by `source` and decodes the inputs payload from argv.
|
|
1969
|
-
* The runtime environment (`ATOMIC_WF_ID`, `ATOMIC_WF_TMUX`,
|
|
1970
|
-
* `ATOMIC_WF_AGENT`, `ATOMIC_WF_CWD`) is set by the launcher script that
|
|
1971
|
-
* `executeWorkflow()` writes — those vars describe *where* this
|
|
1972
|
-
* orchestrator is running, not what to do.
|
|
1973
|
-
*/
|
|
1974
|
-
export async function runOrchestrator(
|
|
1975
|
-
definition: WorkflowDefinition,
|
|
1976
|
-
inputs: Record<string, string> = {},
|
|
1977
|
-
): Promise<void> {
|
|
1978
|
-
const { workflowRunId, tmuxSessionName, agent, cwd } =
|
|
1979
|
-
validateOrchestratorEnv();
|
|
1980
|
-
// A bare prompt string is still useful for the panel header and the
|
|
1981
|
-
// session-dir metadata.json — both just want something displayable.
|
|
1982
|
-
// Free-form workflows store their single positional prompt under the
|
|
1983
|
-
// `prompt` key so workflow authors always read it via
|
|
1984
|
-
// `ctx.inputs.prompt`.
|
|
1985
|
-
const prompt = inputs.prompt ?? "";
|
|
1986
|
-
|
|
1987
|
-
process.chdir(cwd);
|
|
1988
|
-
|
|
1989
|
-
const providerOverrides = await getProviderOverrides(agent, cwd);
|
|
1990
|
-
const extraChatFlags =
|
|
1991
|
-
agent === "copilot" ? await getCopilotScmDisableFlags(cwd) : [];
|
|
1992
|
-
const sessionsBaseDir = join(getSessionsBaseDir(), workflowRunId);
|
|
1993
|
-
await ensureDir(sessionsBaseDir);
|
|
1994
|
-
|
|
1995
|
-
const panel = await OrchestratorPanel.create({
|
|
1996
|
-
tmuxSession: tmuxSessionName,
|
|
1997
|
-
});
|
|
1998
|
-
|
|
1999
|
-
// Mirror panel-store mutations to <sessionDir>/status.json so
|
|
2000
|
-
// out-of-process consumers (e.g. `atomic workflow status`) can read
|
|
2001
|
-
// the live workflow state without IPC into the orchestrator.
|
|
2002
|
-
// Writes are debounced via a "pending" flag so a burst of mutations
|
|
2003
|
-
// collapses into a single file write.
|
|
2004
|
-
let snapshotPending = false;
|
|
2005
|
-
const persistSnapshot = (): void => {
|
|
2006
|
-
if (snapshotPending) return;
|
|
2007
|
-
snapshotPending = true;
|
|
2008
|
-
queueMicrotask(() => {
|
|
2009
|
-
snapshotPending = false;
|
|
2010
|
-
const snap = panel.getSnapshot();
|
|
2011
|
-
void writeSnapshot(
|
|
2012
|
-
sessionsBaseDir,
|
|
2013
|
-
buildSnapshot({
|
|
2014
|
-
workflowRunId,
|
|
2015
|
-
tmuxSession: tmuxSessionName,
|
|
2016
|
-
...snap,
|
|
2017
|
-
}),
|
|
2018
|
-
);
|
|
2019
|
-
});
|
|
2020
|
-
};
|
|
2021
|
-
const unsubscribePanel = panel.subscribe(persistSnapshot);
|
|
2022
|
-
// Seed an initial snapshot so the file exists before any session starts.
|
|
2023
|
-
persistSnapshot();
|
|
2024
|
-
|
|
2025
|
-
// Idempotent shutdown guard
|
|
2026
|
-
let shutdownCalled = false;
|
|
2027
|
-
const shutdown = (exitCode = 0) => {
|
|
2028
|
-
if (shutdownCalled) return;
|
|
2029
|
-
shutdownCalled = true;
|
|
2030
|
-
unsubscribePanel();
|
|
2031
|
-
// Final snapshot reflecting terminal state (completed/error/aborted).
|
|
2032
|
-
void writeSnapshot(
|
|
2033
|
-
sessionsBaseDir,
|
|
2034
|
-
buildSnapshot({
|
|
2035
|
-
workflowRunId,
|
|
2036
|
-
tmuxSession: tmuxSessionName,
|
|
2037
|
-
...panel.getSnapshot(),
|
|
2038
|
-
}),
|
|
2039
|
-
);
|
|
2040
|
-
panel.destroy();
|
|
2041
|
-
try {
|
|
2042
|
-
tmux.killSession(tmuxSessionName);
|
|
2043
|
-
} catch {}
|
|
2044
|
-
process.exitCode = exitCode;
|
|
2045
|
-
};
|
|
2046
|
-
|
|
2047
|
-
// Wire SIGINT so the terminal is always restored.
|
|
2048
|
-
// SIGTERM and other signals are handled by OpenTUI's exitSignals.
|
|
2049
|
-
const signalHandler = () => shutdown(1);
|
|
2050
|
-
process.on("SIGINT", signalHandler);
|
|
2051
|
-
|
|
2052
|
-
// Shared state for all session runners
|
|
2053
|
-
const shared: SharedRunnerState = {
|
|
2054
|
-
tmuxSessionName,
|
|
2055
|
-
sessionsBaseDir,
|
|
2056
|
-
projectRoot: cwd,
|
|
2057
|
-
agent,
|
|
2058
|
-
inputs,
|
|
2059
|
-
providerOverrides,
|
|
2060
|
-
extraChatFlags,
|
|
2061
|
-
panel,
|
|
2062
|
-
activeRegistry: new Map(),
|
|
2063
|
-
completedRegistry: new Map(),
|
|
2064
|
-
failedRegistry: new Set(),
|
|
2065
|
-
};
|
|
2066
|
-
|
|
2067
|
-
try {
|
|
2068
|
-
// Parse integer inputs to numbers so `ctx.inputs.<name>` matches the
|
|
2069
|
-
// declared type. Mutate shared.inputs so per-stage SessionContexts see
|
|
2070
|
-
// the same shape.
|
|
2071
|
-
shared.inputs = coerceInputsBySchema(inputs, definition.inputs);
|
|
2072
|
-
|
|
2073
|
-
await Bun.write(
|
|
2074
|
-
join(sessionsBaseDir, "metadata.json"),
|
|
2075
|
-
JSON.stringify(
|
|
2076
|
-
{
|
|
2077
|
-
workflowName: definition.name,
|
|
2078
|
-
agent,
|
|
2079
|
-
prompt,
|
|
2080
|
-
projectRoot: cwd,
|
|
2081
|
-
startedAt: new Date().toISOString(),
|
|
2082
|
-
},
|
|
2083
|
-
null,
|
|
2084
|
-
2,
|
|
2085
|
-
),
|
|
2086
|
-
);
|
|
2087
|
-
|
|
2088
|
-
// Initialize panel with just the orchestrator node (sessions added dynamically)
|
|
2089
|
-
panel.showWorkflowInfo(definition.name, agent, [], prompt);
|
|
2090
|
-
|
|
2091
|
-
// Build the WorkflowContext — top-level context for the .run() callback
|
|
2092
|
-
const sessionRunner = createSessionRunner(shared, "orchestrator");
|
|
2093
|
-
|
|
2094
|
-
const workflowCtx: WorkflowContext = {
|
|
2095
|
-
inputs: shared.inputs as WorkflowContext["inputs"],
|
|
2096
|
-
agent,
|
|
2097
|
-
stage: sessionRunner as WorkflowContext["stage"],
|
|
2098
|
-
transcript: createTranscriptReader(shared.completedRegistry),
|
|
2099
|
-
getMessages: createMessagesReader(shared.completedRegistry),
|
|
2100
|
-
};
|
|
2101
|
-
|
|
2102
|
-
// Run the workflow, racing against user abort (q / Ctrl+C)
|
|
2103
|
-
const abortPromise = panel.waitForAbort().then(() => {
|
|
2104
|
-
throw new WorkflowAbortError();
|
|
2105
|
-
});
|
|
2106
|
-
await Promise.race([definition.run(workflowCtx), abortPromise]);
|
|
2107
|
-
|
|
2108
|
-
panel.showCompletion(definition.name, sessionsBaseDir);
|
|
2109
|
-
await panel.waitForExit();
|
|
2110
|
-
shutdown(0);
|
|
2111
|
-
} catch (error) {
|
|
2112
|
-
// Kill any active tmux windows that didn't complete.
|
|
2113
|
-
// Headless Claude/Copilot have virtual paneIds ("headless-...") — their
|
|
2114
|
-
// SDK-managed processes are cleaned up by cleanupProvider().
|
|
2115
|
-
for (const [, active] of shared.activeRegistry) {
|
|
2116
|
-
try {
|
|
2117
|
-
if (active.paneId && !active.paneId.startsWith("headless-")) {
|
|
2118
|
-
tmux.killWindow(tmuxSessionName, active.name);
|
|
2119
|
-
}
|
|
2120
|
-
} catch {}
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
if (error instanceof WorkflowAbortError) {
|
|
2124
|
-
shutdown(0);
|
|
2125
|
-
} else {
|
|
2126
|
-
const message = errorMessage(error);
|
|
2127
|
-
try {
|
|
2128
|
-
panel.showFatalError(message);
|
|
2129
|
-
await panel.waitForExit();
|
|
2130
|
-
} catch {}
|
|
2131
|
-
shutdown(1);
|
|
2132
|
-
}
|
|
2133
|
-
} finally {
|
|
2134
|
-
process.off("SIGINT", signalHandler);
|
|
2135
|
-
}
|
|
2136
|
-
}
|