@galaxyproject/orbit 0.0.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -0
- package/LICENSE +21 -0
- package/README.md +760 -5
- package/bin/legacy-mcp-config.js +363 -0
- package/bin/orbit.js +810 -0
- package/bin/pi-extension-path.d.ts +4 -0
- package/bin/pi-extension-path.js +44 -0
- package/bin/provider-auth.d.ts +22 -0
- package/bin/provider-auth.js +77 -0
- package/bin/thinking-pref.js +38 -0
- package/bin/update-check.d.ts +15 -0
- package/bin/update-check.js +137 -0
- package/extensions/cli-update/index.ts +32 -0
- package/extensions/orbit/activity-hooks.ts +141 -0
- package/extensions/orbit/activity.ts +116 -0
- package/extensions/orbit/agent-dir.ts +11 -0
- package/extensions/orbit/auto-resume.ts +352 -0
- package/extensions/orbit/config.ts +3 -0
- package/extensions/orbit/confusables.ts +1 -0
- package/extensions/orbit/context.ts +957 -0
- package/extensions/orbit/dashboard-commands.ts +191 -0
- package/extensions/orbit/dashboard-store.ts +473 -0
- package/extensions/orbit/dashboard-tools.ts +1185 -0
- package/extensions/orbit/empty-completion.ts +99 -0
- package/extensions/orbit/evidence-gate.ts +1276 -0
- package/extensions/orbit/evidence-override-command.ts +294 -0
- package/extensions/orbit/exec-guard/bash-risk.ts +1408 -0
- package/extensions/orbit/exec-guard/gate.ts +181 -0
- package/extensions/orbit/exec-guard/guardian-config.ts +54 -0
- package/extensions/orbit/exec-guard/index.ts +1 -0
- package/extensions/orbit/exec-guard/model-tier.ts +36 -0
- package/extensions/orbit/exec-guard/path-jail.ts +54 -0
- package/extensions/orbit/exec-guard/policy.ts +342 -0
- package/extensions/orbit/exec-guard/runtime-state.ts +15 -0
- package/extensions/orbit/exec-guard/sensitive-read.ts +525 -0
- package/extensions/orbit/exec-guard/types.ts +42 -0
- package/extensions/orbit/execution-commands.ts +134 -0
- package/extensions/orbit/feedback-command.ts +81 -0
- package/extensions/orbit/feedback.ts +96 -0
- package/extensions/orbit/galaxy-api.test.ts +122 -0
- package/extensions/orbit/galaxy-api.ts +606 -0
- package/extensions/orbit/galaxy-artifact-link-guidance.ts +5 -0
- package/extensions/orbit/galaxy-destructive-gate.ts +113 -0
- package/extensions/orbit/galaxy-enrich.ts +1108 -0
- package/extensions/orbit/galaxy-failure.ts +101 -0
- package/extensions/orbit/galaxy-fixture.ts +126 -0
- package/extensions/orbit/galaxy-history-guard.ts +233 -0
- package/extensions/orbit/galaxy-job-block.ts +347 -0
- package/extensions/orbit/galaxy-job-ops.ts +288 -0
- package/extensions/orbit/galaxy-live-source.ts +929 -0
- package/extensions/orbit/galaxy-markdown-adapter.ts +248 -0
- package/extensions/orbit/galaxy-op-policy.ts +332 -0
- package/extensions/orbit/galaxy-ops-tools.ts +326 -0
- package/extensions/orbit/galaxy-output-store.ts +80 -0
- package/extensions/orbit/galaxy-output.ts +346 -0
- package/extensions/orbit/galaxy-page-binding.ts +222 -0
- package/extensions/orbit/galaxy-page-markdown-guidance.ts +15 -0
- package/extensions/orbit/galaxy-page-ops.ts +159 -0
- package/extensions/orbit/galaxy-page-sync.test.ts +417 -0
- package/extensions/orbit/galaxy-page-sync.ts +318 -0
- package/extensions/orbit/galaxy-pages-api.ts +203 -0
- package/extensions/orbit/galaxy-pages-sync.ts +409 -0
- package/extensions/orbit/galaxy-poll-guard.ts +189 -0
- package/extensions/orbit/galaxy-poller.ts +706 -0
- package/extensions/orbit/galaxy-provenance.ts +623 -0
- package/extensions/orbit/galaxy-reconcile.ts +956 -0
- package/extensions/orbit/galaxy-recovery.ts +176 -0
- package/extensions/orbit/galaxy-repeat-guard.ts +55 -0
- package/extensions/orbit/galaxy-submission-capture.ts +707 -0
- package/extensions/orbit/galaxy-submission.ts +502 -0
- package/extensions/orbit/galaxy-tool-source.ts +78 -0
- package/extensions/orbit/galaxy-tool-wrap.ts +234 -0
- package/extensions/orbit/galaxy-udt-block.ts +127 -0
- package/extensions/orbit/galaxy-upload-tus.ts +334 -0
- package/extensions/orbit/galaxy-upload.ts +300 -0
- package/extensions/orbit/galaxy-version-capture.ts +65 -0
- package/extensions/orbit/git.ts +173 -0
- package/extensions/orbit/harness-block-fields.ts +597 -0
- package/extensions/orbit/index.ts +593 -0
- package/extensions/orbit/init-gate.ts +284 -0
- package/extensions/orbit/instructions-command.ts +136 -0
- package/extensions/orbit/invocation-failure-hint.ts +57 -0
- package/extensions/orbit/iwc-plan-check.ts +94 -0
- package/extensions/orbit/lesson-command.ts +303 -0
- package/extensions/orbit/lesson-hint.ts +245 -0
- package/extensions/orbit/lesson-nudge.ts +106 -0
- package/extensions/orbit/lessons/compose.ts +177 -0
- package/extensions/orbit/lessons/counters.ts +120 -0
- package/extensions/orbit/lessons/enabled.ts +116 -0
- package/extensions/orbit/lessons/matcher.ts +346 -0
- package/extensions/orbit/lessons/paths.ts +286 -0
- package/extensions/orbit/lessons/pi-event-contract.ts +105 -0
- package/extensions/orbit/lessons/propose-replay.ts +103 -0
- package/extensions/orbit/lessons/propose-tool.ts +222 -0
- package/extensions/orbit/lessons/propose.ts +502 -0
- package/extensions/orbit/lessons/replay.ts +89 -0
- package/extensions/orbit/lessons/reproduction-index.ts +83 -0
- package/extensions/orbit/lessons/search-tool.ts +99 -0
- package/extensions/orbit/lessons/search.ts +136 -0
- package/extensions/orbit/lessons/store.ts +415 -0
- package/extensions/orbit/lessons/text.ts +36 -0
- package/extensions/orbit/lessons/types.ts +69 -0
- package/extensions/orbit/lessons/user-dir.ts +24 -0
- package/extensions/orbit/lessons/user-lesson.ts +88 -0
- package/extensions/orbit/lessons/wrapper.ts +61 -0
- package/extensions/orbit/lessons-command.ts +147 -0
- package/extensions/orbit/local-exec.ts +39 -0
- package/extensions/orbit/mcp-output.ts +179 -0
- package/extensions/orbit/mcp-servers.test.ts +24 -0
- package/extensions/orbit/mcp-servers.ts +25 -0
- package/extensions/orbit/model-script.ts +351 -0
- package/extensions/orbit/notebook-anchors.ts +342 -0
- package/extensions/orbit/notebook-writer.ts +863 -0
- package/extensions/orbit/observation-allowlists.ts +1109 -0
- package/extensions/orbit/observation-replay.ts +148 -0
- package/extensions/orbit/observation-triggers.ts +563 -0
- package/extensions/orbit/observation-ui.ts +175 -0
- package/extensions/orbit/observations-command.ts +421 -0
- package/extensions/orbit/observations-config.ts +200 -0
- package/extensions/orbit/observations.ts +1381 -0
- package/extensions/orbit/profiles.ts +270 -0
- package/extensions/orbit/progress-updates.ts +95 -0
- package/extensions/orbit/proposal-commands.ts +882 -0
- package/extensions/orbit/provenance-fs-file.ts +59 -0
- package/extensions/orbit/provenance-fs.ts +32 -0
- package/extensions/orbit/provenance-memory-fs.ts +59 -0
- package/extensions/orbit/record-store-file.ts +177 -0
- package/extensions/orbit/record-store-page.ts +77 -0
- package/extensions/orbit/record-store-seam.ts +47 -0
- package/extensions/orbit/record-store.ts +149 -0
- package/extensions/orbit/registry-carrier.ts +107 -0
- package/extensions/orbit/registry-eligibility.ts +95 -0
- package/extensions/orbit/registry-evaluation.ts +403 -0
- package/extensions/orbit/registry-evaluator.ts +519 -0
- package/extensions/orbit/registry-import.ts +91 -0
- package/extensions/orbit/registry-lock.ts +221 -0
- package/extensions/orbit/registry-memory-fs.ts +42 -0
- package/extensions/orbit/registry-page-carrier.ts +103 -0
- package/extensions/orbit/registry-proposal.ts +1348 -0
- package/extensions/orbit/registry-runtime.ts +234 -0
- package/extensions/orbit/registry-schema.ts +735 -0
- package/extensions/orbit/registry-submitter.ts +59 -0
- package/extensions/orbit/registry-templates.ts +346 -0
- package/extensions/orbit/registry.ts +476 -0
- package/extensions/orbit/sandbox/index.ts +120 -0
- package/extensions/orbit/sandbox/sandbox-bash.ts +93 -0
- package/extensions/orbit/sandbox/sandbox-config.ts +87 -0
- package/extensions/orbit/sandbox/sandbox-init-message.ts +28 -0
- package/extensions/orbit/secret-redaction.ts +247 -0
- package/extensions/orbit/session-index/cwd.ts +18 -0
- package/extensions/orbit/session-index/db.ts +56 -0
- package/extensions/orbit/session-index/indexer.ts +154 -0
- package/extensions/orbit/session-index/is-enabled.ts +17 -0
- package/extensions/orbit/session-index/parse.ts +254 -0
- package/extensions/orbit/session-index/query.ts +209 -0
- package/extensions/orbit/session-index/schema.ts +72 -0
- package/extensions/orbit/session-index/tools.ts +152 -0
- package/extensions/orbit/session-lifecycle.ts +321 -0
- package/extensions/orbit/skill-triggers.ts +159 -0
- package/extensions/orbit/skills-command.ts +48 -0
- package/extensions/orbit/skills-discovery.ts +435 -0
- package/extensions/orbit/skills.ts +84 -0
- package/extensions/orbit/sra-import-gate.ts +77 -0
- package/extensions/orbit/state.ts +265 -0
- package/extensions/orbit/submission-replay.ts +147 -0
- package/extensions/orbit/sync-command.ts +157 -0
- package/extensions/orbit/teams/critic-parser.ts +93 -0
- package/extensions/orbit/teams/dispatcher.ts +210 -0
- package/extensions/orbit/teams/is-enabled.ts +22 -0
- package/extensions/orbit/teams/tool.ts +187 -0
- package/extensions/orbit/teams/types.ts +57 -0
- package/extensions/orbit/teams/validate.ts +42 -0
- package/extensions/orbit/tester-id-command.ts +133 -0
- package/extensions/orbit/tools-sync.ts +279 -0
- package/extensions/orbit/tools.ts +1300 -0
- package/extensions/orbit/types.ts +41 -0
- package/extensions/orbit/ui-bridge.ts +55 -0
- package/extensions/orbit/ulid.ts +60 -0
- package/extensions/orbit/user-instructions.ts +388 -0
- package/extensions/orbit/vendor/skills/_catalog.json +68 -0
- package/extensions/orbit/vendor/skills/_manifest.json +1106 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/SKILL.md +112 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_provenance.json +218 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_required_tools.json +33 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/add.json +50 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-extract.json +51 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-next-step.json +42 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-validate.json +140 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/list.json +32 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/notes/galaxy-tool-job-failure-reference.md +137 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/schemas/galaxy-tool-summary.schema.json +175 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/schemas/galaxy-workflow-draft.schema.json +2727 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/SKILL.md +82 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_provenance.json +133 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_required_tools.json +16 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/cli/draft-validate.json +140 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/notes/galaxy-workflow-draft-format.md +135 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/schemas/galaxy-workflow-draft.schema.json +2727 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/SKILL.md +84 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/_provenance.json +166 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/_verify.json +29 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/galaxy-workflow-testability-design.md +136 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/iwc-test-data-conventions.md +308 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/galaxy-workflow-test-plan.schema.json +552 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/summary-galaxy-workflow.schema.json +687 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/tests-format.schema.json +7328 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/SKILL.md +127 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_provenance.json +198 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_required_tools.json +16 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/cli/convert.json +123 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/galaxy-data-flow-draft-contract.md +129 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/iwc-test-data-conventions.md +308 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/SKILL.md +73 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/_provenance.json +160 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-collection-semantics.md +43 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-collection-semantics.yml +1250 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-tool-job-failure-reference.md +137 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/nextflow-operators-to-galaxy-collection-recipes.md +98 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/SKILL.md +174 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/_provenance.json +130 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/_required_tools.json +18 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-revisions.json +48 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-search.json +95 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-versions.json +39 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/notes/component-tool-shed-search.md +230 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/schemas/galaxy-tool-discovery.schema.json +231 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/SKILL.md +93 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_provenance.json +201 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_required_tools.json +28 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/cli/planemo.md +44 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/cli/validate-tests.json +40 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/galaxy-workflow-testability-design.md +136 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/iwc-test-data-conventions.md +308 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/schemas/galaxy-workflow-test-plan.schema.json +552 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/schemas/tests-format.schema.json +7328 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/SKILL.md +81 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/_provenance.json +93 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/references/schemas/summary-galaxy-workflow.schema.json +687 -0
- package/extensions/orbit/vendor/skills/pipeline-update-interview-to-galaxy/SKILL.md +81 -0
- package/extensions/orbit/vendor/skills/pipeline-update-interview-to-galaxy/_assembly.json +23 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/SKILL.md +79 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/_provenance.json +125 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/_required_tools.json +28 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/cli/planemo.md +44 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/cli/validate-tests.json +40 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/schemas/tests-format.schema.json +7328 -0
- package/extensions/orbit/vendor/skills/skills/LICENSE +21 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/README.md +39 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/SKILL.md +892 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/api-patterns.md +927 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/apply-rules.md +1482 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/test-patterns.md +996 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/tools.md +609 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-data-libraries/SKILL.md +170 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/README.md +162 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/SKILL.md +99 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/README.md +119 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/tool-checking.md +419 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/workflow-testing.md +617 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/galaxy-integration.md +614 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/SKILL.md +144 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/average_col3.ipynb +68 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/extract_sample_metadata.ipynb +190 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/variant_annotation.ipynb +816 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/vcp_variant_map.ipynb +443 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/scripts/README.md +165 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/scripts/galaxy_tool_checker.py +521 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/SKILL.md +128 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/gotchas.md +135 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/history-access.md +132 -0
- package/extensions/orbit/vendor/skills/skills/reproduciblify/SKILL.md +146 -0
- package/extensions/orbit/vendor/skills/skills/reproduciblify/references/directives.yml +496 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/SKILL.md +245 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/01-minimal-echo.yml +27 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/02-single-data-in-out.yml +27 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/03-multiple-data-js.yml +28 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/04-select-integer-boolean.yml +41 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/05-resource-requirements.yml +25 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/06-biocontainer-tool.yml +38 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/07-inline-python-script.yml +56 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/08-configfile-script.yml +74 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/references/common-mistakes.md +76 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/references/schema-reference.md +155 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/references/templating.md +196 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/scripts/validate.py +109 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/README.md +71 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/SKILL.md +190 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/examples/histology-staining.md +145 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/examples/tissue-microarray-analysis.md +176 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/references/directives.md +415 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/SKILL.md +98 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_provenance.json +93 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_required_tools.json +28 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/convert.json +123 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/foundry.md +25 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/validate.json +142 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/schemas/summary-galaxy-workflow.schema.json +687 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/SKILL.md +74 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_provenance.json +71 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_required_tools.json +16 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/cli/validate.json +142 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/notes/galaxy-workflow-testability-design.md +136 -0
- package/extensions/orbit/vendor-skills.ts +283 -0
- package/extensions/orbit/workspace-state-dir.ts +46 -0
- package/extensions/orbit-handoff/index.ts +73 -0
- package/extensions/orbit-handoff/orbit-launcher.ts +142 -0
- package/extensions/whats-new/index.ts +98 -0
- package/lessons/LICENSE +17 -0
- package/lessons/README.md +117 -0
- package/lessons/SCHEMA.md +187 -0
- package/lessons/build-snapshot.mjs +311 -0
- package/lessons/data/downloaded-file-is-not-what-its-extension-says.md +61 -0
- package/lessons/galaxy-api/403-history-is-a-hard-stop.md +57 -0
- package/lessons/galaxy-api/collection-into-single-dataset-input.md +61 -0
- package/lessons/galaxy-api/connectedvalue-in-command-line.md +62 -0
- package/lessons/galaxy-api/hid-is-not-an-id.md +59 -0
- package/lessons/galaxy-api/invoke-workflow-inputs-not-params.md +60 -0
- package/lessons/galaxy-api/repeat-param-pipe-keys.md +63 -0
- package/lessons/galaxy-api/run-tool-returns-on-submit.md +61 -0
- package/lessons/galaxy-tools/reference-index-not-on-server.md +62 -0
- package/lessons/log.md +42 -0
- package/lessons/reproduction/input-population-mismatch.md +59 -0
- package/lessons/reproduction/methods-text-vs-executed-parameters.md +62 -0
- package/lessons/snapshot.json +1004 -0
- package/lessons/stats/de-contrast-direction-and-sample-labels.md +63 -0
- package/lessons/stats/na-coerced-to-zero-in-filters.md +59 -0
- package/lessons/validate.mjs +1219 -0
- package/package.json +103 -5
- package/shared/agent-prompt.d.ts +8 -0
- package/shared/agent-prompt.js +15 -0
- package/shared/approval-prompt.d.ts +4 -0
- package/shared/approval-prompt.js +61 -0
- package/shared/brain-env.d.ts +14 -0
- package/shared/brain-env.js +108 -0
- package/shared/brain-exit.d.ts +20 -0
- package/shared/brain-exit.js +149 -0
- package/shared/capability-profile.d.ts +16 -0
- package/shared/capability-profile.js +75 -0
- package/shared/config-source.d.ts +13 -0
- package/shared/config-source.js +17 -0
- package/shared/confusables.d.ts +3 -0
- package/shared/confusables.js +93 -0
- package/shared/custom-provider.d.ts +45 -0
- package/shared/custom-provider.js +169 -0
- package/shared/dashboard-contract.d.ts +123 -0
- package/shared/dashboard-contract.js +560 -0
- package/shared/dashboard-layout-store.d.ts +44 -0
- package/shared/dashboard-layout-store.js +281 -0
- package/shared/feedback-contract.d.ts +61 -0
- package/shared/feedback-contract.js +146 -0
- package/shared/galaxy-artifact-links.d.ts +30 -0
- package/shared/galaxy-artifact-links.js +166 -0
- package/shared/galaxy-credentials.d.ts +26 -0
- package/shared/galaxy-credentials.js +71 -0
- package/shared/galaxy-destructive.d.ts +21 -0
- package/shared/galaxy-destructive.js +269 -0
- package/shared/galaxy-guidance.d.ts +2 -0
- package/shared/galaxy-guidance.js +53 -0
- package/shared/galaxy-live-contract.d.ts +92 -0
- package/shared/galaxy-live-contract.js +180 -0
- package/shared/galaxy-page-cells.d.ts +2 -0
- package/shared/galaxy-page-cells.js +22 -0
- package/shared/galaxy-tool-family.d.ts +5 -0
- package/shared/galaxy-tool-family.js +27 -0
- package/shared/galaxy-url.d.ts +1 -0
- package/shared/galaxy-url.js +48 -0
- package/shared/gtn.d.ts +31 -0
- package/shared/gtn.js +253 -0
- package/shared/handled-prompt.d.ts +3 -0
- package/shared/handled-prompt.js +26 -0
- package/shared/known-provider.d.ts +11 -0
- package/shared/known-provider.js +89 -0
- package/shared/lesson-hint-marker.d.ts +7 -0
- package/shared/lesson-hint-marker.js +32 -0
- package/shared/lesson-rules.d.ts +150 -0
- package/shared/lesson-rules.js +203 -0
- package/shared/malformed-object-ids.d.ts +1 -0
- package/shared/malformed-object-ids.js +37 -0
- package/shared/markdown-sections.d.ts +10 -0
- package/shared/markdown-sections.js +118 -0
- package/shared/notebook-fences.d.ts +12 -0
- package/shared/notebook-fences.js +53 -0
- package/shared/observation-contract.d.ts +91 -0
- package/shared/observation-contract.js +544 -0
- package/shared/orbit-config.d.ts +226 -0
- package/shared/orbit-config.js +189 -0
- package/shared/orbit-env.d.ts +13 -0
- package/shared/orbit-env.js +124 -0
- package/shared/orbit-shell-contract.d.ts +74 -0
- package/shared/orbit-shell-contract.js +30 -0
- package/shared/page-content-hash.d.ts +1 -0
- package/shared/page-content-hash.js +13 -0
- package/shared/pi-default-model.d.ts +7 -0
- package/shared/pi-default-model.js +41 -0
- package/shared/prompt-blocks.d.ts +87 -0
- package/shared/prompt-blocks.js +898 -0
- package/shared/provider-auth-caps.d.ts +22 -0
- package/shared/provider-auth-caps.js +57 -0
- package/shared/random.d.ts +2 -0
- package/shared/random.js +15 -0
- package/shared/record-excerpt.d.ts +4 -0
- package/shared/record-excerpt.js +16 -0
- package/shared/redirect-guard.d.ts +36 -0
- package/shared/redirect-guard.js +235 -0
- package/shared/skills-pin.d.ts +8 -0
- package/shared/skills-pin.js +9 -0
- package/shared/skills-router.d.ts +17 -0
- package/shared/skills-router.js +63 -0
- package/shared/sra-import.d.ts +34 -0
- package/shared/sra-import.js +206 -0
- package/shared/state-dir.d.ts +21 -0
- package/shared/state-dir.js +240 -0
- package/shared/team-dispatch-contract.d.ts +28 -0
- package/shared/team-dispatch-contract.js +1 -0
- package/shared/truncate-middle.d.ts +11 -0
- package/shared/truncate-middle.js +68 -0
- package/shared/version-compare.d.ts +10 -0
- package/shared/version-compare.js +74 -0
- package/shared/whats-new.d.ts +31 -0
- package/shared/whats-new.js +83 -0
- package/shared/wsl.d.ts +10 -0
- package/shared/wsl.js +24 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval and attempt registry: the harness-owned record that binds what
|
|
3
|
+
* the user approved to what the harness submitted and to what came back.
|
|
4
|
+
* Registry design v3 is the authority (`docs/architecture.md` will say so once
|
|
5
|
+
* the registry is wired in); this module implements §2, §3 and §10.
|
|
6
|
+
*
|
|
7
|
+
* What it guarantees here, and only here:
|
|
8
|
+
*
|
|
9
|
+
* - Every write is signed with a key generated when the store is constructed
|
|
10
|
+
* and held only in this object's memory. A document whose signature verifies
|
|
11
|
+
* with that key is this session's own state; anything else is an import and
|
|
12
|
+
* goes through the import rule, which strips every approval, exception,
|
|
13
|
+
* check and authority it claims.
|
|
14
|
+
* - `revision` only goes up. An own-signed document older than what the store
|
|
15
|
+
* already holds is a replay and is ignored.
|
|
16
|
+
* - One writer at a time, fenced by the lock token.
|
|
17
|
+
* - `handoff_eligible` is recomputed on every write and never read from input.
|
|
18
|
+
*
|
|
19
|
+
* No harness coupling: the analysis directory, clock and filesystem come in
|
|
20
|
+
* through the constructor, and nothing here imports pi or extension state.
|
|
21
|
+
* `tests/registry-no-pi-imports.test.ts` holds that line.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
25
|
+
import { randomBytes, randomHex } from "../../shared/random.js";
|
|
26
|
+
import * as path from "path";
|
|
27
|
+
import { computeHandoffEligible } from "./registry-eligibility";
|
|
28
|
+
import { applyImportRule } from "./registry-import";
|
|
29
|
+
import { RegistryLock, renameReplacing, type LockRecord, type RegistryFs } from "./registry-lock";
|
|
30
|
+
import {
|
|
31
|
+
CURRENT_REGISTRY_VERSION,
|
|
32
|
+
DIGEST_RE,
|
|
33
|
+
MAX_REGISTRY_REVISION,
|
|
34
|
+
RegistryFormatError,
|
|
35
|
+
assertNoDuplicateKeys,
|
|
36
|
+
canonicalJson,
|
|
37
|
+
migrateToCurrent,
|
|
38
|
+
normalizeServerUrl,
|
|
39
|
+
parseRegistry,
|
|
40
|
+
sha256Hex,
|
|
41
|
+
type AttemptId,
|
|
42
|
+
type Registry,
|
|
43
|
+
} from "./registry-schema";
|
|
44
|
+
import { ulid } from "./ulid";
|
|
45
|
+
|
|
46
|
+
export * from "./registry-schema";
|
|
47
|
+
export { computeHandoffEligible } from "./registry-eligibility";
|
|
48
|
+
export { applyImportRule } from "./registry-import";
|
|
49
|
+
export { STALE_LOCK_MS, type RegistryFs, type LockRecord } from "./registry-lock";
|
|
50
|
+
export type { Submitter, SubmitOutcome, TemplateSnapshot } from "./registry-submitter";
|
|
51
|
+
|
|
52
|
+
/** Writes an imported registry must still have room for. */
|
|
53
|
+
const REVISION_HEADROOM = 2 ** 20;
|
|
54
|
+
|
|
55
|
+
/** Past this, a registry file or carrier is rejected unread. */
|
|
56
|
+
export const MAX_REGISTRY_BYTES = 4 * 1024 * 1024;
|
|
57
|
+
|
|
58
|
+
export const STATE_DIR = path.join(".loom", "state");
|
|
59
|
+
|
|
60
|
+
export interface RegistryStoreOptions {
|
|
61
|
+
analysisDir: string;
|
|
62
|
+
/** The Galaxy server this session talks to; attempts for any other are quarantined. */
|
|
63
|
+
serverUrl: string;
|
|
64
|
+
sessionId: string;
|
|
65
|
+
fs: RegistryFs;
|
|
66
|
+
/** Milliseconds since the epoch. */
|
|
67
|
+
clock: () => number;
|
|
68
|
+
pid?: number;
|
|
69
|
+
/** Used only when there is nothing to load. Defaults to a fresh ULID. */
|
|
70
|
+
analysisId?: string;
|
|
71
|
+
/** Defaults to `process.platform`; decides whether a busy rename is retried. */
|
|
72
|
+
platform?: NodeJS.Platform;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type LoadOutcome =
|
|
76
|
+
| { kind: "empty" }
|
|
77
|
+
| { kind: "own"; revision: number }
|
|
78
|
+
| { kind: "stale"; revision: number; current: number }
|
|
79
|
+
| { kind: "imported"; revision: number; quarantined: AttemptId[] }
|
|
80
|
+
/** A foreign document arrived while this session holds its own signed state. */
|
|
81
|
+
| { kind: "ignored"; reason: string }
|
|
82
|
+
| { kind: "rejected"; reason: string };
|
|
83
|
+
|
|
84
|
+
export class RegistryReadOnlyError extends Error {
|
|
85
|
+
constructor(message: string) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.name = "RegistryReadOnlyError";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class RegistryFencedError extends RegistryReadOnlyError {
|
|
92
|
+
constructor(holder: LockRecord | null) {
|
|
93
|
+
super(
|
|
94
|
+
holder
|
|
95
|
+
? `registry lock was taken over by session ${holder.session_id} (pid ${holder.pid}); reopened read-only`
|
|
96
|
+
: "registry lock is gone; reopened read-only",
|
|
97
|
+
);
|
|
98
|
+
this.name = "RegistryFencedError";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class RegistryStore {
|
|
103
|
+
readonly stateDir: string;
|
|
104
|
+
readonly registryPath: string;
|
|
105
|
+
readonly lockPath: string;
|
|
106
|
+
readonly templatesDir: string;
|
|
107
|
+
readonly serverUrl: string;
|
|
108
|
+
/** Human-readable things the session should be told about. */
|
|
109
|
+
readonly notices: string[] = [];
|
|
110
|
+
|
|
111
|
+
// Private class field: not enumerable, not reachable by JSON.stringify,
|
|
112
|
+
// structuredClone, or anything that walks the object's own keys.
|
|
113
|
+
readonly #sessionKey: Uint8Array = randomBytes(32);
|
|
114
|
+
readonly #fs: RegistryFs;
|
|
115
|
+
readonly #clock: () => number;
|
|
116
|
+
readonly #pid: number;
|
|
117
|
+
readonly #platform: NodeJS.Platform;
|
|
118
|
+
readonly #lock: RegistryLock;
|
|
119
|
+
#mode: "writer" | "read-only" = "read-only";
|
|
120
|
+
#registry: Registry;
|
|
121
|
+
/** Whether what we hold was signed by this session (written or verified). */
|
|
122
|
+
#ownState = false;
|
|
123
|
+
|
|
124
|
+
constructor(opts: RegistryStoreOptions) {
|
|
125
|
+
this.stateDir = path.join(opts.analysisDir, STATE_DIR);
|
|
126
|
+
this.registryPath = path.join(this.stateDir, "registry.json");
|
|
127
|
+
this.lockPath = path.join(this.stateDir, "lock");
|
|
128
|
+
this.templatesDir = path.join(this.stateDir, "templates");
|
|
129
|
+
this.serverUrl = normalizeServerUrl(opts.serverUrl);
|
|
130
|
+
this.#fs = opts.fs;
|
|
131
|
+
this.#clock = opts.clock;
|
|
132
|
+
this.#pid = opts.pid ?? process.pid;
|
|
133
|
+
this.#platform = opts.platform ?? process.platform;
|
|
134
|
+
this.#lock = new RegistryLock(
|
|
135
|
+
this.lockPath,
|
|
136
|
+
opts.fs,
|
|
137
|
+
opts.clock,
|
|
138
|
+
this.#pid,
|
|
139
|
+
opts.sessionId,
|
|
140
|
+
this.#platform,
|
|
141
|
+
);
|
|
142
|
+
this.#registry = this.emptyRegistry(opts.analysisId ?? ulid(opts.clock()));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get mode(): "writer" | "read-only" {
|
|
146
|
+
return this.#mode;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** A deep copy of the current registry. Mutating it changes nothing. */
|
|
150
|
+
snapshot(): Registry {
|
|
151
|
+
return structuredClone(this.#registry);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
newAttemptId(): AttemptId {
|
|
155
|
+
return ulid(this.#clock());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Take the writer lock (or open read-only), then load what is on disk. */
|
|
159
|
+
open(): LoadOutcome {
|
|
160
|
+
this.#fs.mkdirSync(this.stateDir, { recursive: true });
|
|
161
|
+
const lock = this.#lock.acquire();
|
|
162
|
+
if (lock.mode === "writer") {
|
|
163
|
+
this.#mode = "writer";
|
|
164
|
+
if (lock.tookOverFrom) {
|
|
165
|
+
this.notices.push(
|
|
166
|
+
`Took over a stale registry lock from session ${lock.tookOverFrom.session_id} (pid ${lock.tookOverFrom.pid}).`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
this.#mode = "read-only";
|
|
171
|
+
this.notices.push(
|
|
172
|
+
`Another live session (${lock.holder.session_id}, pid ${lock.holder.pid}) holds the registry; opened read-only.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return this.load();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Re-read the registry file and ingest it. */
|
|
179
|
+
load(): LoadOutcome {
|
|
180
|
+
if (!this.#fs.existsSync(this.registryPath)) return { kind: "empty" };
|
|
181
|
+
let text: string;
|
|
182
|
+
try {
|
|
183
|
+
text = this.#fs.readFileSync(this.registryPath, "utf-8");
|
|
184
|
+
} catch (err) {
|
|
185
|
+
return this.reject(`could not read registry.json: ${(err as Error).message}`, true);
|
|
186
|
+
}
|
|
187
|
+
return this.ingestText(text, true);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Ingest a serialised registry from any source: the file, or a decoded Page
|
|
192
|
+
* carrier. `fromFile` only decides whether a rejected document is moved
|
|
193
|
+
* aside on disk.
|
|
194
|
+
*/
|
|
195
|
+
ingestText(text: string, fromFile = false): LoadOutcome {
|
|
196
|
+
if (Buffer.byteLength(text, "utf-8") > MAX_REGISTRY_BYTES) {
|
|
197
|
+
return this.reject(`registry is larger than ${MAX_REGISTRY_BYTES} bytes`, fromFile);
|
|
198
|
+
}
|
|
199
|
+
let raw: unknown;
|
|
200
|
+
try {
|
|
201
|
+
raw = JSON.parse(text);
|
|
202
|
+
assertNoDuplicateKeys(text);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return this.reject(`registry is not valid JSON: ${(err as Error).message}`, fromFile);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Verify against exactly what was signed, before parsing drops anything.
|
|
208
|
+
if (this.verifies(raw)) {
|
|
209
|
+
let own: Registry;
|
|
210
|
+
try {
|
|
211
|
+
own = parseRegistry(raw);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
return this.reject(`own registry failed validation: ${(err as Error).message}`, fromFile);
|
|
214
|
+
}
|
|
215
|
+
// Equal counts as stale when what we hold came from an import: the
|
|
216
|
+
// import carried the revision forward, so an own copy at that number
|
|
217
|
+
// predates it.
|
|
218
|
+
const current = this.#registry.revision;
|
|
219
|
+
if (own.revision < current || (own.revision === current && !this.#ownState && current > 0)) {
|
|
220
|
+
this.notices.push(
|
|
221
|
+
`Ignored an older copy of this session's registry (revision ${own.revision}, current ${this.#registry.revision}).`,
|
|
222
|
+
);
|
|
223
|
+
return { kind: "stale", revision: own.revision, current: this.#registry.revision };
|
|
224
|
+
}
|
|
225
|
+
this.#registry = this.withDerived(own);
|
|
226
|
+
this.#ownState = true;
|
|
227
|
+
return { kind: "own", revision: own.revision };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Replacing this session's own state with a foreign document would let
|
|
231
|
+
// whoever wrote it wipe live approvals (agy's carrier-wipe). How a foreign
|
|
232
|
+
// Page carrier merges into live state is the pull-merge rule's job; until
|
|
233
|
+
// then it is refused. A read-only session has no state of its own, but
|
|
234
|
+
// what it holds is what the writer wrote, and a carrier mustn't replace
|
|
235
|
+
// that either: only the file, which is the writer's, may.
|
|
236
|
+
if ((this.#ownState && this.#mode === "writer") || (!fromFile && this.#mode !== "writer")) {
|
|
237
|
+
const reason =
|
|
238
|
+
this.#mode === "writer"
|
|
239
|
+
? "a registry this session did not write arrived while it holds its own"
|
|
240
|
+
: "a carrier can't replace the registry in a read-only session";
|
|
241
|
+
this.notices.push(`Ignored ${reason}.`);
|
|
242
|
+
return { kind: "ignored", reason };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let imported: Registry;
|
|
246
|
+
try {
|
|
247
|
+
imported = parseRegistry(migrateToCurrent(raw));
|
|
248
|
+
} catch (err) {
|
|
249
|
+
return this.reject((err as Error).message, fromFile);
|
|
250
|
+
}
|
|
251
|
+
// Leave room to keep counting: a document at the ceiling would make every
|
|
252
|
+
// later write fail validation.
|
|
253
|
+
if (imported.revision > MAX_REGISTRY_REVISION - REVISION_HEADROOM) {
|
|
254
|
+
return this.reject("revision too close to the ceiling to keep writing", fromFile);
|
|
255
|
+
}
|
|
256
|
+
const { registry, quarantined } = applyImportRule(imported, this.serverUrl);
|
|
257
|
+
// Keep revisions monotonic across the import, so an older own copy that
|
|
258
|
+
// turns up later still reads as stale.
|
|
259
|
+
registry.revision = Math.max(imported.revision, this.#registry.revision);
|
|
260
|
+
this.#registry = this.withDerived(registry);
|
|
261
|
+
this.notices.push(
|
|
262
|
+
`Imported a registry this session did not write: approvals and exceptions are restored, not live; checks, provenance and evaluations must be re-established.` +
|
|
263
|
+
(quarantined.length > 0
|
|
264
|
+
? ` ${quarantined.length} attempt(s) from another Galaxy server were quarantined.`
|
|
265
|
+
: ""),
|
|
266
|
+
);
|
|
267
|
+
this.#ownState = false;
|
|
268
|
+
if (this.#mode === "writer") {
|
|
269
|
+
try {
|
|
270
|
+
this.persist();
|
|
271
|
+
} catch (err) {
|
|
272
|
+
// Fenced mid-import: `becomeFenced()` already reloaded read-only.
|
|
273
|
+
if (!(err instanceof RegistryFencedError)) throw err;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return { kind: "imported", revision: this.#registry.revision, quarantined };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The one way to change the registry. `mutate` edits a draft; the result is
|
|
281
|
+
* validated, its derived fields recomputed, its revision bumped, signed, and
|
|
282
|
+
* written atomically -- or nothing changes.
|
|
283
|
+
*/
|
|
284
|
+
update(mutate: (draft: Registry) => void): Registry {
|
|
285
|
+
this.assertWriter();
|
|
286
|
+
const draft = structuredClone(this.#registry);
|
|
287
|
+
mutate(draft);
|
|
288
|
+
let next: Registry;
|
|
289
|
+
try {
|
|
290
|
+
next = parseRegistry({ ...draft, version: CURRENT_REGISTRY_VERSION });
|
|
291
|
+
} catch (err) {
|
|
292
|
+
throw new RegistryFormatError(`refusing to write: ${(err as Error).message}`);
|
|
293
|
+
}
|
|
294
|
+
for (const a of Object.values(next.attempts)) {
|
|
295
|
+
if (normalizeServerUrl(a.server_url) !== this.serverUrl) {
|
|
296
|
+
throw new RegistryFormatError(
|
|
297
|
+
`refusing to write: attempt ${a.attempt_id} is for ${a.server_url}, not ${this.serverUrl}`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
next.server_url = this.serverUrl;
|
|
302
|
+
next.analysis_id = this.#registry.analysis_id;
|
|
303
|
+
// Revision, token and signature belong to persist(), not to the caller.
|
|
304
|
+
next.revision = this.#registry.revision;
|
|
305
|
+
const previous = this.#registry;
|
|
306
|
+
this.#registry = this.withDerived(next);
|
|
307
|
+
try {
|
|
308
|
+
this.persist();
|
|
309
|
+
} catch (err) {
|
|
310
|
+
// A fence already reloaded the new writer's state; don't paper over it.
|
|
311
|
+
if (!(err instanceof RegistryFencedError)) this.#registry = previous;
|
|
312
|
+
throw err;
|
|
313
|
+
}
|
|
314
|
+
return this.snapshot();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Freeze a template or definition body under `templates/<digest>.json`.
|
|
319
|
+
* Content-addressed and write-once: the file's bytes are the canonical JSON
|
|
320
|
+
* whose sha256 is its name, and an existing file is never rewritten.
|
|
321
|
+
*/
|
|
322
|
+
putTemplate(body: unknown): string {
|
|
323
|
+
this.assertWriter();
|
|
324
|
+
const text = canonicalJson(body);
|
|
325
|
+
const digest = sha256Hex(text);
|
|
326
|
+
const file = path.join(this.templatesDir, `${digest}.json`);
|
|
327
|
+
if (this.#fs.existsSync(file)) {
|
|
328
|
+
if (sha256Hex(this.#fs.readFileSync(file, "utf-8")) !== digest) {
|
|
329
|
+
throw new RegistryFormatError(`template ${digest} on disk does not match its name`);
|
|
330
|
+
}
|
|
331
|
+
return digest;
|
|
332
|
+
}
|
|
333
|
+
this.#fs.mkdirSync(this.templatesDir, { recursive: true });
|
|
334
|
+
this.writeAtomic(file, text);
|
|
335
|
+
return digest;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** A frozen template by digest, or undefined if missing or altered. */
|
|
339
|
+
getTemplate(digest: string): unknown {
|
|
340
|
+
if (!DIGEST_RE.test(digest)) return undefined;
|
|
341
|
+
const file = path.join(this.templatesDir, `${digest}.json`);
|
|
342
|
+
if (!this.#fs.existsSync(file)) return undefined;
|
|
343
|
+
const text = this.#fs.readFileSync(file, "utf-8");
|
|
344
|
+
if (sha256Hex(text) !== digest) {
|
|
345
|
+
this.notices.push(`Template ${digest} was altered on disk and is being ignored.`);
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
return JSON.parse(text);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Refresh the lock heartbeat; drops to read-only if the lock was taken. */
|
|
352
|
+
heartbeat(): boolean {
|
|
353
|
+
if (this.#mode !== "writer") return false;
|
|
354
|
+
if (this.#lock.heartbeat()) return true;
|
|
355
|
+
this.becomeFenced();
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
close(): void {
|
|
360
|
+
this.#lock.release();
|
|
361
|
+
this.#mode = "read-only";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
private emptyRegistry(analysisId: string): Registry {
|
|
367
|
+
return {
|
|
368
|
+
version: CURRENT_REGISTRY_VERSION,
|
|
369
|
+
revision: 0,
|
|
370
|
+
writer_token: "",
|
|
371
|
+
session_sig: "",
|
|
372
|
+
analysis_id: analysisId,
|
|
373
|
+
server_url: this.serverUrl,
|
|
374
|
+
attempts: {},
|
|
375
|
+
exceptions: [],
|
|
376
|
+
supervision: { active_at_shutdown: [] },
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private sign(doc: Record<string, unknown>): string {
|
|
381
|
+
const { session_sig: _omit, ...rest } = doc;
|
|
382
|
+
return createHmac("sha256", this.#sessionKey)
|
|
383
|
+
.update(canonicalJson(rest), "utf-8")
|
|
384
|
+
.digest("hex");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private verifies(raw: unknown): boolean {
|
|
388
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
|
|
389
|
+
const doc = raw as Record<string, unknown>;
|
|
390
|
+
if (typeof doc.session_sig !== "string" || !/^[0-9a-f]{64}$/.test(doc.session_sig)) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
let expected: string;
|
|
394
|
+
try {
|
|
395
|
+
expected = this.sign(doc);
|
|
396
|
+
} catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(doc.session_sig, "hex"));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private withDerived(reg: Registry): Registry {
|
|
403
|
+
for (const a of Object.values(reg.attempts)) {
|
|
404
|
+
a.handoff_eligible = computeHandoffEligible(a, reg.exceptions);
|
|
405
|
+
}
|
|
406
|
+
for (const a of Object.values(reg.quarantine ?? {})) a.handoff_eligible = false;
|
|
407
|
+
return reg;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private assertWriter(): void {
|
|
411
|
+
if (this.#mode !== "writer") {
|
|
412
|
+
throw new RegistryReadOnlyError("registry is open read-only in this session");
|
|
413
|
+
}
|
|
414
|
+
if (!this.#lock.stillHeld()) throw this.becomeFenced();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Drop to read-only after losing the lock; the caller decides whether to throw. */
|
|
418
|
+
private becomeFenced(): RegistryFencedError {
|
|
419
|
+
const holder = this.#lock.read();
|
|
420
|
+
this.#lock.relinquish();
|
|
421
|
+
this.#mode = "read-only";
|
|
422
|
+
this.#ownState = false;
|
|
423
|
+
const err = new RegistryFencedError(holder);
|
|
424
|
+
this.notices.push(err.message);
|
|
425
|
+
// Reload what the new writer has, read-only. Its signature isn't ours, so
|
|
426
|
+
// this is an import like any other.
|
|
427
|
+
this.load();
|
|
428
|
+
return err;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private persist(): void {
|
|
432
|
+
const token = this.#lock.heldToken;
|
|
433
|
+
if (token === null || !this.#lock.stillHeld()) throw this.becomeFenced();
|
|
434
|
+
const reg = this.#registry;
|
|
435
|
+
reg.revision += 1;
|
|
436
|
+
reg.writer_token = token as string;
|
|
437
|
+
reg.session_sig = "";
|
|
438
|
+
reg.session_sig = this.sign(reg as unknown as Record<string, unknown>);
|
|
439
|
+
this.writeAtomic(this.registryPath, `${canonicalJson(reg)}\n`);
|
|
440
|
+
this.#ownState = true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private writeAtomic(file: string, text: string): void {
|
|
444
|
+
const tmp = `${file}.${this.#pid}.${randomHex(6)}.tmp`;
|
|
445
|
+
this.#fs.writeFileSync(tmp, text, { flag: "wx" });
|
|
446
|
+
renameReplacing(this.#fs, tmp, file, this.#platform);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private reject(reason: string, fromFile: boolean): LoadOutcome {
|
|
450
|
+
this.notices.push(
|
|
451
|
+
(this.#ownState && this.#mode === "writer") || !fromFile
|
|
452
|
+
? `Registry ${fromFile ? "file" : "carrier"} rejected (${reason}); keeping what this session holds.`
|
|
453
|
+
: `Registry rejected (${reason}); starting with an empty registry.`,
|
|
454
|
+
);
|
|
455
|
+
if (fromFile && this.#mode === "writer" && this.#fs.existsSync(this.registryPath)) {
|
|
456
|
+
// Keep the evidence; the next write would otherwise overwrite it.
|
|
457
|
+
const aside = `${this.registryPath}.rejected-${this.#clock()}`;
|
|
458
|
+
try {
|
|
459
|
+
this.#fs.renameSync(this.registryPath, aside);
|
|
460
|
+
} catch {
|
|
461
|
+
// leave it; the next write replaces it
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if ((this.#ownState && this.#mode === "writer") || !fromFile) {
|
|
465
|
+
// The file was damaged under us, or a carrier didn't parse: what we hold
|
|
466
|
+
// stays. A rejected carrier must never be a way to clear the registry.
|
|
467
|
+
return { kind: "rejected", reason };
|
|
468
|
+
}
|
|
469
|
+
const empty = this.emptyRegistry(this.#registry.analysis_id);
|
|
470
|
+
// Never let a rejection reset the revision: that would re-open replay.
|
|
471
|
+
empty.revision = this.#registry.revision;
|
|
472
|
+
this.#registry = empty;
|
|
473
|
+
this.#ownState = false;
|
|
474
|
+
return { kind: "rejected", reason };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as os from "os";
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { createBashTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
5
|
+
import { loadGuardianConfig, resolveSandbox } from "../exec-guard/guardian-config";
|
|
6
|
+
import { isSandboxActive, setSandboxActive } from "../exec-guard/runtime-state";
|
|
7
|
+
import { buildSandboxConfig } from "./sandbox-config";
|
|
8
|
+
import { createSandboxedBashOps } from "./sandbox-bash";
|
|
9
|
+
import { describeSandboxInitFailure } from "./sandbox-init-message";
|
|
10
|
+
import { galaxyCredentials } from "../galaxy-api";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Bash OS sandbox (opt-in). When enabled (`--sandbox` / ORBIT_SANDBOX=1 /
|
|
14
|
+
* guardian.sandbox) and the platform supports it, `bash` runs inside an OS sandbox
|
|
15
|
+
* (sandbox-exec / bubblewrap via ASRT): its writes are confined to the workspace
|
|
16
|
+
* and its network to an allowlist, so an allowed command's blast radius is the
|
|
17
|
+
* analysis dir, not the machine.
|
|
18
|
+
*
|
|
19
|
+
* Layered UNDER the exec-guard: the guard's tool_call gate still decides
|
|
20
|
+
* allow/ask/deny on the original command; this only changes HOW an already-allowed
|
|
21
|
+
* command runs. OFF by default because the sandbox necessarily restricts bash
|
|
22
|
+
* network (ASRT cannot confine writes alone, and rejects a wildcard allowlist) --
|
|
23
|
+
* a tradeoff the user opts into.
|
|
24
|
+
*
|
|
25
|
+
* Default-on confinement of the model's *file* edits lives in the gate itself
|
|
26
|
+
* (the exec-guard write/edit path-jail), independent of this sandbox.
|
|
27
|
+
*
|
|
28
|
+
* Shell-neutral: all of this lives in the brain. Mid-session cwd changes use the
|
|
29
|
+
* cwd captured at session start (Orbit re-spawns the brain on a dir switch).
|
|
30
|
+
*/
|
|
31
|
+
export function registerSandbox(pi: ExtensionAPI): void {
|
|
32
|
+
let sessionCwd = process.cwd();
|
|
33
|
+
|
|
34
|
+
pi.registerTool({
|
|
35
|
+
...createBashTool(sessionCwd),
|
|
36
|
+
async execute(id, params, signal, onUpdate) {
|
|
37
|
+
const tool = isSandboxActive()
|
|
38
|
+
? createBashTool(sessionCwd, { operations: createSandboxedBashOps() })
|
|
39
|
+
: createBashTool(sessionCwd);
|
|
40
|
+
return tool.execute(id, params, signal, onUpdate);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// `!`-prefixed user commands bypass tool_call; sandbox them too when active.
|
|
45
|
+
pi.on("user_bash", () => {
|
|
46
|
+
if (!isSandboxActive()) return;
|
|
47
|
+
return { operations: createSandboxedBashOps() };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
51
|
+
setSandboxActive(false);
|
|
52
|
+
const cfg = loadGuardianConfig();
|
|
53
|
+
if (!resolveSandbox(cfg)) return; // opt-in: off -> plain gate, no sandbox
|
|
54
|
+
|
|
55
|
+
sessionCwd = ctx.cwd;
|
|
56
|
+
|
|
57
|
+
if (!SandboxManager.isSupportedPlatform()) {
|
|
58
|
+
ctx.ui.notify(
|
|
59
|
+
`Bash sandbox requested, but OS sandboxing isn't supported on ${process.platform} -- bash stays gated per action.`,
|
|
60
|
+
"warning",
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await SandboxManager.initialize(
|
|
67
|
+
buildSandboxConfig({
|
|
68
|
+
cwd: sessionCwd,
|
|
69
|
+
tmpDir: os.tmpdir(),
|
|
70
|
+
extraWriteRoots: cfg.extraWorkspaceRoots,
|
|
71
|
+
galaxyUrl: galaxyCredentials().url,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
setSandboxActive(true);
|
|
75
|
+
ctx.ui.setStatus("loom-sandbox", "Bash sandboxed");
|
|
76
|
+
ctx.ui.notify(
|
|
77
|
+
"Bash sandbox on: bash writes are confined to the workspace and its network is limited.",
|
|
78
|
+
"info",
|
|
79
|
+
);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
setSandboxActive(false);
|
|
82
|
+
ctx.ui.notify(
|
|
83
|
+
describeSandboxInitFailure(
|
|
84
|
+
process.platform,
|
|
85
|
+
err instanceof Error ? err.message : String(err),
|
|
86
|
+
),
|
|
87
|
+
"error",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
pi.on("session_shutdown", async () => {
|
|
93
|
+
if (!isSandboxActive()) return;
|
|
94
|
+
try {
|
|
95
|
+
await SandboxManager.reset();
|
|
96
|
+
} catch {
|
|
97
|
+
// best-effort cleanup
|
|
98
|
+
}
|
|
99
|
+
setSandboxActive(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
pi.registerCommand("sandbox", {
|
|
103
|
+
description: "Show bash sandbox status",
|
|
104
|
+
handler: async (_args, ctx) => {
|
|
105
|
+
if (!resolveSandbox(loadGuardianConfig())) {
|
|
106
|
+
ctx.ui.notify(
|
|
107
|
+
"Bash sandbox is off. Enable with --sandbox, ORBIT_SANDBOX=1, or guardian.sandbox in config. (Your file-tool writes are confined to the analysis dir regardless.)",
|
|
108
|
+
"info",
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
ctx.ui.notify(
|
|
113
|
+
isSandboxActive()
|
|
114
|
+
? "Bash sandbox on -- bash runs inside the OS sandbox (writes confined to the workspace; network limited)."
|
|
115
|
+
: `Bash sandbox requested, but inactive on ${process.platform}; bash stays gated per action.`,
|
|
116
|
+
"info",
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
4
|
+
import type { BashOperations } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A `BashOperations` that runs each command inside the OS sandbox.
|
|
8
|
+
* `SandboxManager.wrapWithSandbox` rewraps the command for `sandbox-exec`
|
|
9
|
+
* (macOS) / `bubblewrap` (Linux) using the profile from `SandboxManager.initialize`.
|
|
10
|
+
*
|
|
11
|
+
* Adapted from pi's bundled `examples/extensions/sandbox` reference: detached so
|
|
12
|
+
* the whole process group can be SIGKILL'd on timeout/abort, stdout+stderr piped
|
|
13
|
+
* to onData, and the standard timeout/abort/exit semantics pi expects. We can't use
|
|
14
|
+
* pi's lighter `spawnHook` here -- that hook is synchronous, and `wrapWithSandbox`
|
|
15
|
+
* is async -- so, like the reference, we supply a custom `exec`.
|
|
16
|
+
*
|
|
17
|
+
* Known limitation (inherited from that reference, and bounded by the tool timeout):
|
|
18
|
+
* completion is detected via `"close"`, so a command that backgrounds a child
|
|
19
|
+
* holding stdout/stderr can delay resolution until the timeout fires. pi's internal
|
|
20
|
+
* backend avoids this with an `"exit"`-based wait + detached-PID tracking, but those
|
|
21
|
+
* helpers aren't exported, so matching them downstream would diverge from the
|
|
22
|
+
* maintained reference; the proper fix belongs upstream in pi.
|
|
23
|
+
*/
|
|
24
|
+
export function createSandboxedBashOps(): BashOperations {
|
|
25
|
+
return {
|
|
26
|
+
async exec(command, cwd, { onData, signal, timeout, env }) {
|
|
27
|
+
if (!existsSync(cwd)) {
|
|
28
|
+
throw new Error(`Working directory does not exist: ${cwd}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const wrappedCommand = await SandboxManager.wrapWithSandbox(command);
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
// Abort may have fired before this point (incl. during the async
|
|
35
|
+
// wrapWithSandbox above); don't spawn a command we'd immediately discard.
|
|
36
|
+
if (signal?.aborted) {
|
|
37
|
+
reject(new Error("aborted"));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const child = spawn("bash", ["-c", wrappedCommand], {
|
|
42
|
+
cwd,
|
|
43
|
+
env,
|
|
44
|
+
detached: true,
|
|
45
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
let timedOut = false;
|
|
49
|
+
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
50
|
+
|
|
51
|
+
const killGroup = () => {
|
|
52
|
+
if (child.pid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(-child.pid, "SIGKILL");
|
|
55
|
+
} catch {
|
|
56
|
+
child.kill("SIGKILL");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (timeout !== undefined && timeout > 0) {
|
|
62
|
+
timeoutHandle = setTimeout(() => {
|
|
63
|
+
timedOut = true;
|
|
64
|
+
killGroup();
|
|
65
|
+
}, timeout * 1000);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
child.stdout?.on("data", onData);
|
|
69
|
+
child.stderr?.on("data", onData);
|
|
70
|
+
|
|
71
|
+
child.on("error", (err) => {
|
|
72
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
73
|
+
reject(err);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const onAbort = () => killGroup();
|
|
77
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
78
|
+
|
|
79
|
+
child.on("close", (code) => {
|
|
80
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
81
|
+
signal?.removeEventListener("abort", onAbort);
|
|
82
|
+
if (signal?.aborted) {
|
|
83
|
+
reject(new Error("aborted"));
|
|
84
|
+
} else if (timedOut) {
|
|
85
|
+
reject(new Error(`timeout:${timeout}`));
|
|
86
|
+
} else {
|
|
87
|
+
resolve({ exitCode: code });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|