@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,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What pi's MCP client gave every Galaxy tool for free, rebuilt for calls that run in-process:
|
|
3
|
+
* a deadline, a spill for oversized output, and a result the guards can classify without reading
|
|
4
|
+
* its prose.
|
|
5
|
+
*
|
|
6
|
+
* Every result is a galaxy-ops envelope (`{data, success, message, count, pagination}`, the
|
|
7
|
+
* Python server's shape key for key), including the ones built here -- no connection, a refused
|
|
8
|
+
* argument, the deadline -- so everything downstream parses one shape. The model reads the
|
|
9
|
+
* envelope as JSON text; `structuredContent` carries the same object for programmatic callers;
|
|
10
|
+
* `details.galaxy` says which tool ran, how it failed, and where a spilled output went.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import type { GalaxyContext, GalaxyResult } from "@galaxyproject/galaxy-ops";
|
|
14
|
+
import { utf8ByteLength } from "../../shared/truncate-middle.js";
|
|
15
|
+
import { galaxyOpsContext } from "./galaxy-api";
|
|
16
|
+
import { GALAXY_DEADLINE_TEXT, kindOfEnvelope, type GalaxyCallKind } from "./galaxy-failure";
|
|
17
|
+
import { record, spillPreview } from "./galaxy-output";
|
|
18
|
+
import { galaxyOutputStore } from "./galaxy-output-store";
|
|
19
|
+
import type { GalaxyTool } from "./galaxy-tool-source";
|
|
20
|
+
|
|
21
|
+
/** pi's MCP client gave every request five minutes; so does every in-process op. */
|
|
22
|
+
export const GALAXY_DEADLINE_MS = 300_000;
|
|
23
|
+
/** Above this many UTF-8 bytes the model gets a bounded preview and the full text is saved. */
|
|
24
|
+
export const GALAXY_SPILL_BYTES = 20 * 1024;
|
|
25
|
+
|
|
26
|
+
export type GalaxyEnvelope = GalaxyResult<unknown>;
|
|
27
|
+
|
|
28
|
+
export interface GalaxyCallDetails {
|
|
29
|
+
/** `kind` is set on every failure, null when the failure has no recovery advice. */
|
|
30
|
+
galaxy: { tool: string; kind?: GalaxyCallKind | null; outputId?: string; elapsedMs: number };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GalaxyCallResult {
|
|
34
|
+
content: { type: "text"; text: string }[];
|
|
35
|
+
structuredContent: GalaxyEnvelope;
|
|
36
|
+
details: GalaxyCallDetails;
|
|
37
|
+
isError: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type GalaxyContextFactory = (signal: AbortSignal) => GalaxyContext | null;
|
|
41
|
+
|
|
42
|
+
export interface GalaxyCallOptions {
|
|
43
|
+
deadlineMs?: number;
|
|
44
|
+
/** Where the call's context comes from; tests hand in their own. */
|
|
45
|
+
context?: GalaxyContextFactory;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A failure envelope in runWithEnvelope's own shape, for a refusal made before Galaxy was asked. */
|
|
49
|
+
export function galaxyFailure(message: string, errorKind?: string): GalaxyEnvelope {
|
|
50
|
+
return {
|
|
51
|
+
data: undefined,
|
|
52
|
+
success: false,
|
|
53
|
+
message,
|
|
54
|
+
...(errorKind === undefined ? {} : { errorKind: errorKind as GalaxyEnvelope["errorKind"] }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const NOT_CONNECTED =
|
|
59
|
+
"Galaxy is not connected: no Galaxy URL and API key are configured. Ask the user to run /connect.";
|
|
60
|
+
|
|
61
|
+
/** The envelope as the model and the guards see it, spilled when it is too large to show whole. */
|
|
62
|
+
export function renderGalaxyResult(
|
|
63
|
+
tool: string,
|
|
64
|
+
env: GalaxyEnvelope,
|
|
65
|
+
elapsedMs: number,
|
|
66
|
+
kind?: GalaxyCallKind,
|
|
67
|
+
): GalaxyCallResult {
|
|
68
|
+
let text = JSON.stringify(env);
|
|
69
|
+
let outputId: string | undefined;
|
|
70
|
+
if (utf8ByteLength(text) > GALAXY_SPILL_BYTES) {
|
|
71
|
+
outputId = galaxyOutputStore().put(text, tool);
|
|
72
|
+
text = spillPreview(text, outputId, env.success === false);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text }],
|
|
76
|
+
structuredContent: env,
|
|
77
|
+
details: {
|
|
78
|
+
galaxy: {
|
|
79
|
+
tool,
|
|
80
|
+
...(env.success === false ? { kind: kind ?? null } : {}),
|
|
81
|
+
...(outputId === undefined ? {} : { outputId }),
|
|
82
|
+
elapsedMs,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
isError: env.success === false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type Deadlined<T> = { timedOut: false; value: T; late: boolean } | { timedOut: true };
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Run `work` against a deadline, and return when the deadline fires even if `work` never does.
|
|
93
|
+
*
|
|
94
|
+
* Joining the timer into the signal cancels well-behaved requests and poll sleeps, but a
|
|
95
|
+
* transport is free to ignore its signal, and openapi-fetch then waits forever; only racing the
|
|
96
|
+
* timer guarantees the call comes back. Our own timer rather than `AbortSignal.timeout` so fake
|
|
97
|
+
* timers can drive it. A caller's abort is rethrown: that is pi stopping the turn, not a failure.
|
|
98
|
+
*/
|
|
99
|
+
async function withDeadline<T>(
|
|
100
|
+
signal: AbortSignal | undefined,
|
|
101
|
+
deadlineMs: number,
|
|
102
|
+
work: (joined: AbortSignal) => Promise<T>,
|
|
103
|
+
): Promise<Deadlined<T>> {
|
|
104
|
+
const timer = new AbortController();
|
|
105
|
+
const handle = setTimeout(() => timer.abort(), deadlineMs);
|
|
106
|
+
const joined = signal ? AbortSignal.any([signal, timer.signal]) : timer.signal;
|
|
107
|
+
let unlisten = () => {};
|
|
108
|
+
const aborted = new Promise<{ kind: "aborted" }>((resolve) => {
|
|
109
|
+
const on = () => resolve({ kind: "aborted" });
|
|
110
|
+
if (joined.aborted) return on();
|
|
111
|
+
joined.addEventListener("abort", on, { once: true });
|
|
112
|
+
unlisten = () => joined.removeEventListener("abort", on);
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
let running: Promise<T>;
|
|
116
|
+
try {
|
|
117
|
+
running = work(joined);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
running = Promise.reject(error);
|
|
120
|
+
}
|
|
121
|
+
// Both outcomes are handled here, so the losing call's late rejection is never unhandled.
|
|
122
|
+
const settled = await Promise.race([
|
|
123
|
+
running.then(
|
|
124
|
+
(value) => ({ kind: "value" as const, value }),
|
|
125
|
+
(error: unknown) => ({ kind: "error" as const, error }),
|
|
126
|
+
),
|
|
127
|
+
aborted,
|
|
128
|
+
]);
|
|
129
|
+
if (signal?.aborted) throw signal.reason;
|
|
130
|
+
if (settled.kind === "value") {
|
|
131
|
+
return { timedOut: false, value: settled.value, late: timer.signal.aborted };
|
|
132
|
+
}
|
|
133
|
+
// A call that throws once the deadline fired (a poll sleep's plain Error("aborted"), an
|
|
134
|
+
// aborted fetch) failed because of the deadline, whatever it threw.
|
|
135
|
+
if (settled.kind === "aborted" || timer.signal.aborted) return { timedOut: true };
|
|
136
|
+
throw settled.error;
|
|
137
|
+
} finally {
|
|
138
|
+
clearTimeout(handle);
|
|
139
|
+
unlisten();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Run one Galaxy call under the deadline, with a context bound to the joined signal, and render
|
|
145
|
+
* what comes back. `call` returns an envelope; anything it throws that is not the deadline's
|
|
146
|
+
* doing is rethrown, and pi turns it into an error result.
|
|
147
|
+
*/
|
|
148
|
+
export async function runGalaxyCall(
|
|
149
|
+
tool: string,
|
|
150
|
+
signal: AbortSignal | undefined,
|
|
151
|
+
call: (ctx: GalaxyContext, signal: AbortSignal) => Promise<GalaxyEnvelope>,
|
|
152
|
+
opts: GalaxyCallOptions = {},
|
|
153
|
+
): Promise<GalaxyCallResult> {
|
|
154
|
+
const started = Date.now();
|
|
155
|
+
const contextFor = opts.context ?? galaxyOpsContext;
|
|
156
|
+
const outcome = await withDeadline(
|
|
157
|
+
signal,
|
|
158
|
+
opts.deadlineMs ?? GALAXY_DEADLINE_MS,
|
|
159
|
+
async (joined): Promise<{ env: GalaxyEnvelope; kind?: GalaxyCallKind }> => {
|
|
160
|
+
const ctx = contextFor(joined);
|
|
161
|
+
if (!ctx) return { env: galaxyFailure(NOT_CONNECTED, "auth"), kind: "auth" };
|
|
162
|
+
return { env: await call(ctx, joined) };
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
const elapsedMs = Date.now() - started;
|
|
166
|
+
if (outcome.timedOut) {
|
|
167
|
+
return renderGalaxyResult(tool, galaxyFailure(GALAXY_DEADLINE_TEXT), elapsedMs, "timeout");
|
|
168
|
+
}
|
|
169
|
+
const { env, kind } = outcome.value;
|
|
170
|
+
if (env.success !== false) return renderGalaxyResult(tool, env, elapsedMs);
|
|
171
|
+
// A failure that lands after the timer fired is the aborted request's, not Galaxy's answer.
|
|
172
|
+
if (outcome.late) {
|
|
173
|
+
return renderGalaxyResult(tool, galaxyFailure(GALAXY_DEADLINE_TEXT), elapsedMs, "timeout");
|
|
174
|
+
}
|
|
175
|
+
return renderGalaxyResult(tool, env, elapsedMs, kind ?? kindOfEnvelope(env.errorKind));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The same deadline and spill for a tool a shell supplies itself (an extra), so its large output
|
|
180
|
+
* is read back through `galaxy_read_output` and the guards know it as a Galaxy call. A shell opts
|
|
181
|
+
* an extra in; the source never wraps one on its own.
|
|
182
|
+
*/
|
|
183
|
+
export function wrapGalaxyTool(def: GalaxyTool, opts: { deadlineMs?: number } = {}): GalaxyTool {
|
|
184
|
+
return {
|
|
185
|
+
...def,
|
|
186
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
187
|
+
const started = Date.now();
|
|
188
|
+
const outcome = await withDeadline(signal, opts.deadlineMs ?? GALAXY_DEADLINE_MS, (joined) =>
|
|
189
|
+
Promise.resolve(def.execute(toolCallId, params, joined, onUpdate, ctx)),
|
|
190
|
+
);
|
|
191
|
+
const elapsedMs = Date.now() - started;
|
|
192
|
+
if (outcome.timedOut) {
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: "text", text: GALAXY_DEADLINE_TEXT }],
|
|
195
|
+
details: { galaxy: { tool: def.name, kind: "timeout", elapsedMs } },
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const result = outcome.value as AgentToolResult<unknown>;
|
|
200
|
+
const text = result.content.flatMap((c) => (c.type === "text" ? [c.text] : [])).join("\n");
|
|
201
|
+
let outputId: string | undefined;
|
|
202
|
+
let content = result.content;
|
|
203
|
+
if (utf8ByteLength(text) > GALAXY_SPILL_BYTES) {
|
|
204
|
+
outputId = galaxyOutputStore().put(text, def.name);
|
|
205
|
+
content = [
|
|
206
|
+
...result.content.filter((c) => c.type !== "text"),
|
|
207
|
+
{ type: "text", text: spillPreview(text, outputId, result.isError === true) },
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
...result,
|
|
212
|
+
content,
|
|
213
|
+
details: {
|
|
214
|
+
...record(result.details),
|
|
215
|
+
galaxy: { tool: def.name, ...(outputId === undefined ? {} : { outputId }), elapsedMs },
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** `outputSchema` for every in-process Galaxy tool: the envelope, as runWithEnvelope builds it. */
|
|
223
|
+
export const GALAXY_ENVELOPE_SCHEMA = Object.freeze({
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
data: {},
|
|
227
|
+
success: { type: "boolean" },
|
|
228
|
+
message: { type: "string" },
|
|
229
|
+
count: { type: ["integer", "null"] },
|
|
230
|
+
pagination: { type: ["object", "null"] },
|
|
231
|
+
errorKind: { type: "string" },
|
|
232
|
+
},
|
|
233
|
+
required: ["success"],
|
|
234
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loom-udt` notebook blocks: a pointer to a user-defined tool's definition.
|
|
3
|
+
*
|
|
4
|
+
* A UDT lives in Galaxy's database, scoped to the user who made it. That is
|
|
5
|
+
* fine until the analysis outlives the account, the server, or the tool --
|
|
6
|
+
* at which point the notebook says a tool ran and nothing says what it was.
|
|
7
|
+
* So when the harness sees a UDT created, it writes the definition Galaxy
|
|
8
|
+
* stored to `<analysis>/.loom/provenance/udt/<tool_id>.yaml` and drops this
|
|
9
|
+
* block in the notebook pointing at it.
|
|
10
|
+
*
|
|
11
|
+
* Keyed on `tool_uuid`, which is what `run_user_tool` takes, so a run can be
|
|
12
|
+
* traced back to the exact definition it used. Re-creating the same tool id
|
|
13
|
+
* yields a new uuid and therefore a new block, which is correct: a redefined
|
|
14
|
+
* tool is a different tool.
|
|
15
|
+
*
|
|
16
|
+
* ```loom-udt
|
|
17
|
+
* tool_id: clean_table
|
|
18
|
+
* tool_uuid: 8d5f1c2e-...
|
|
19
|
+
* definition: .loom/provenance/udt/clean_table.yaml
|
|
20
|
+
* created_at: 2026-09-16T15:30:00Z
|
|
21
|
+
* notebook_anchor: plan-a-step-2
|
|
22
|
+
* attempt_id: 01K5CJ6XWQ8QK4S2M7E9V0TZ3B
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface UdtYaml {
|
|
27
|
+
toolId: string;
|
|
28
|
+
toolUuid: string;
|
|
29
|
+
/** Repo-relative path to the stored definition. */
|
|
30
|
+
definition: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
notebookAnchor: string;
|
|
33
|
+
attemptId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
import { notebookFenceOpen } from "../../shared/notebook-fences.js";
|
|
37
|
+
import { appendBlock, isUnambiguousRange } from "./notebook-writer";
|
|
38
|
+
import { scanFencedBlocks } from "./harness-block-fields";
|
|
39
|
+
|
|
40
|
+
const UDT_FENCE_OPEN = notebookFenceOpen("udt");
|
|
41
|
+
const UDT_FENCE_CLOSE = "```";
|
|
42
|
+
|
|
43
|
+
function escapeYaml(value: string): string {
|
|
44
|
+
if (value === "") return '""';
|
|
45
|
+
if (/^[\w .\-/]+$/.test(value)) return value;
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function unescapeYaml(value: string): string {
|
|
50
|
+
const trimmed = value.trim();
|
|
51
|
+
if (trimmed.startsWith('"')) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(trimmed) as string;
|
|
54
|
+
} catch {
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return trimmed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function renderUdtYaml(udt: UdtYaml): string {
|
|
62
|
+
const lines: string[] = [
|
|
63
|
+
UDT_FENCE_OPEN,
|
|
64
|
+
`tool_id: ${escapeYaml(udt.toolId)}`,
|
|
65
|
+
`tool_uuid: ${escapeYaml(udt.toolUuid)}`,
|
|
66
|
+
`definition: ${escapeYaml(udt.definition)}`,
|
|
67
|
+
`created_at: ${udt.createdAt}`,
|
|
68
|
+
`notebook_anchor: ${udt.notebookAnchor}`,
|
|
69
|
+
];
|
|
70
|
+
if (udt.attemptId) lines.push(`attempt_id: ${udt.attemptId}`);
|
|
71
|
+
lines.push(UDT_FENCE_CLOSE);
|
|
72
|
+
return lines.join("\n") + "\n";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseUdtBlock(blockLines: string[]): UdtYaml | null {
|
|
76
|
+
const map = new Map<string, string>();
|
|
77
|
+
for (const line of blockLines) {
|
|
78
|
+
const idx = line.indexOf(":");
|
|
79
|
+
if (idx === -1) continue;
|
|
80
|
+
map.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());
|
|
81
|
+
}
|
|
82
|
+
const toolUuid = map.get("tool_uuid");
|
|
83
|
+
const toolId = map.get("tool_id");
|
|
84
|
+
const definition = map.get("definition");
|
|
85
|
+
if (!toolUuid || !toolId || !definition) return null;
|
|
86
|
+
return {
|
|
87
|
+
toolId: unescapeYaml(toolId),
|
|
88
|
+
toolUuid: unescapeYaml(toolUuid),
|
|
89
|
+
definition: unescapeYaml(definition),
|
|
90
|
+
createdAt: map.get("created_at") ?? "",
|
|
91
|
+
notebookAnchor: map.get("notebook_anchor") ?? "",
|
|
92
|
+
attemptId: map.get("attempt_id") || undefined,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function findUdtBlocks(content: string): UdtYaml[] {
|
|
97
|
+
const lines = content.split("\n");
|
|
98
|
+
const out: UdtYaml[] = [];
|
|
99
|
+
for (const range of scanFencedBlocks(lines, "udt")) {
|
|
100
|
+
const parsed = parseUdtBlock(lines.slice(range.start + 1, range.end));
|
|
101
|
+
if (parsed) out.push(parsed);
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** True when a `loom-udt` block for this uuid is already in the notebook. */
|
|
107
|
+
export function hasUdtBlock(content: string, toolUuid: string): boolean {
|
|
108
|
+
return findUdtBlocks(content).some((b) => b.toolUuid === toolUuid);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Upsert a `loom-udt` block keyed by `tool_uuid`. */
|
|
112
|
+
export function upsertUdtBlock(content: string, udt: UdtYaml): string {
|
|
113
|
+
const lines = content.split("\n");
|
|
114
|
+
const newBlock = renderUdtYaml(udt).trimEnd().split("\n");
|
|
115
|
+
|
|
116
|
+
for (const range of scanFencedBlocks(lines, "udt")) {
|
|
117
|
+
if (!isUnambiguousRange(lines, range.start)) continue;
|
|
118
|
+
const parsed = parseUdtBlock(lines.slice(range.start + 1, range.end));
|
|
119
|
+
if (parsed && parsed.toolUuid === udt.toolUuid) {
|
|
120
|
+
return [...lines.slice(0, range.start), ...newBlock, ...lines.slice(range.end + 1)].join(
|
|
121
|
+
"\n",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return appendBlock(content, newBlock);
|
|
127
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Native TUS upload core for Galaxy.
|
|
3
|
+
* This will migrate to galaxy-ops when that lands -- temporary home here.
|
|
4
|
+
*
|
|
5
|
+
* Intentionally free of any loom/pi imports so it can move cleanly into
|
|
6
|
+
* @galaxyproject/galaxy-ops later. Plain Node + tus-js-client only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createReadStream } from "fs";
|
|
10
|
+
import { parse as parseLegacyUrl } from "url";
|
|
11
|
+
// FileUrlStorage is exported by the Node build but missing from the type defs --
|
|
12
|
+
// import via namespace and cast to avoid the spurious TS2724 error.
|
|
13
|
+
import * as tusClient from "tus-js-client";
|
|
14
|
+
const { Upload, FileUrlStorage } = tusClient as typeof tusClient & {
|
|
15
|
+
FileUrlStorage: new (path: string) => tusClient.UrlStorage;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// buildFetchPayload
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface FetchPayloadOpts {
|
|
23
|
+
historyId: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
fileName: string;
|
|
26
|
+
fileType?: string; // Galaxy ext; default "auto"
|
|
27
|
+
dbkey?: string; // genome build; default "?"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Body for POST /api/tools/fetch after a TUS upload completes. Mirrors bioblend's
|
|
32
|
+
* _fetch_payload. auto_decompress is OFF so an uploaded .gz stays compressed.
|
|
33
|
+
*/
|
|
34
|
+
export function buildFetchPayload(o: FetchPayloadOpts): Record<string, unknown> {
|
|
35
|
+
return {
|
|
36
|
+
history_id: o.historyId,
|
|
37
|
+
targets: [
|
|
38
|
+
{
|
|
39
|
+
destination: { type: "hdas" },
|
|
40
|
+
elements: [
|
|
41
|
+
{
|
|
42
|
+
src: "files",
|
|
43
|
+
ext: o.fileType ?? "auto",
|
|
44
|
+
dbkey: o.dbkey ?? "?",
|
|
45
|
+
to_posix_lines: true,
|
|
46
|
+
space_to_tab: false,
|
|
47
|
+
name: o.fileName,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
"files_0|file_data": { session_id: o.sessionId, name: o.fileName },
|
|
53
|
+
auto_decompress: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// tusUpload
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
export interface TusUploadOpts {
|
|
62
|
+
baseUrl: string; // origin, no trailing slash, no /api (e.g. https://galaxy.test)
|
|
63
|
+
apiKey: string;
|
|
64
|
+
filePath: string; // already validated + realpath-resolved by the caller
|
|
65
|
+
storagePath: string; // file-backed resume store, e.g. ~/.loom/upload-resume.json
|
|
66
|
+
chunkSize?: number;
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
onProgress?: (bytesSent: number, bytesTotal: number) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TusUploadResult {
|
|
72
|
+
sessionId: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Streams require a finite chunk size -- 10 MiB is a safe default that keeps
|
|
76
|
+
// memory bounded while still being large enough to avoid excessive round-trips.
|
|
77
|
+
const DEFAULT_CHUNK = 10 * 1024 * 1024;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scheme + host + port, or null when the URL will not parse. Duplicated rather
|
|
81
|
+
* than imported from `shared/` on purpose: this module's header keeps it free
|
|
82
|
+
* of loom imports so it can move into @galaxyproject/galaxy-ops unchanged.
|
|
83
|
+
*/
|
|
84
|
+
function originOf(url: string | null | undefined): string | null {
|
|
85
|
+
try {
|
|
86
|
+
return new URL(String(url)).origin;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const DEFAULT_PORTS: Record<string, string> = { "http:": "80", "https:": "443" };
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The origin tus-js-client will actually connect to. Its Node transport builds
|
|
96
|
+
* request options with the legacy `url.parse`, which disagrees with WHATWG
|
|
97
|
+
* `URL` on some hosts: `https://usegalaxy.org%2eau/x` is `usegalaxy.org.au`
|
|
98
|
+
* to `URL` but `usegalaxy.org` to `url.parse`. Checking only the WHATWG origin
|
|
99
|
+
* would pass that URL and then send the key to the other host.
|
|
100
|
+
*/
|
|
101
|
+
function transportOriginOf(url: string | null | undefined): string | null {
|
|
102
|
+
try {
|
|
103
|
+
const { protocol, hostname, port } = parseLegacyUrl(String(url));
|
|
104
|
+
if (!protocol || !hostname) return null;
|
|
105
|
+
const effectivePort = port && port !== DEFAULT_PORTS[protocol] ? `:${port}` : "";
|
|
106
|
+
// url.parse drops the brackets from an IPv6 literal; the WHATWG origin keeps them.
|
|
107
|
+
const host = hostname.includes(":") ? `[${hostname}]` : hostname.toLowerCase();
|
|
108
|
+
return `${protocol}//${host}${effectivePort}`;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A stored resume entry. `uploadUrl` is real at runtime; the type defs omit it. */
|
|
115
|
+
type StoredUpload = tusClient.PreviousUpload & {
|
|
116
|
+
uploadUrl?: string | null;
|
|
117
|
+
urlStorageKey?: string;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The resume store with anything off-origin filtered out, in both directions.
|
|
122
|
+
*
|
|
123
|
+
* tus writes the upload URL to the store right after it hands it to us, and
|
|
124
|
+
* abort() does not cancel that write; FileUrlStorage.addUpload keys every entry
|
|
125
|
+
* on a fresh random id, so it appends rather than overwrites and nothing ever
|
|
126
|
+
* removes it. A refused foreign URL would therefore be persisted to
|
|
127
|
+
* ~/.loom/upload-resume.json once per attempt, unbounded, and the first entry
|
|
128
|
+
* back out would be the poisoned one -- killing resume for that file for good.
|
|
129
|
+
*
|
|
130
|
+
* Filtering on the way in keeps it out of the file; filtering on the way out
|
|
131
|
+
* (and deleting what it finds) heals a file an earlier build already wrote.
|
|
132
|
+
*/
|
|
133
|
+
function originScopedUrlStorage(
|
|
134
|
+
inner: tusClient.UrlStorage,
|
|
135
|
+
belongsHere: (url: string | null | undefined) => boolean,
|
|
136
|
+
): tusClient.UrlStorage {
|
|
137
|
+
const keep = async (found: tusClient.PreviousUpload[]): Promise<tusClient.PreviousUpload[]> => {
|
|
138
|
+
const kept: tusClient.PreviousUpload[] = [];
|
|
139
|
+
for (const entry of found as StoredUpload[]) {
|
|
140
|
+
if (belongsHere(entry.uploadUrl)) {
|
|
141
|
+
kept.push(entry);
|
|
142
|
+
} else if (entry.urlStorageKey) {
|
|
143
|
+
// Best effort: a store we cannot prune still yields the right result,
|
|
144
|
+
// it just stays dirty.
|
|
145
|
+
await inner.removeUpload(entry.urlStorageKey).catch(() => {});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return kept;
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
findAllUploads: async () => keep(await inner.findAllUploads()),
|
|
152
|
+
findUploadsByFingerprint: async (fingerprint: string) =>
|
|
153
|
+
keep(await inner.findUploadsByFingerprint(fingerprint)),
|
|
154
|
+
removeUpload: (urlStorageKey: string) => inner.removeUpload(urlStorageKey),
|
|
155
|
+
// Returning "" instead of a key is what tells tus there is nothing to
|
|
156
|
+
// remove later (_removeFromUrlStorage bails on a falsy key).
|
|
157
|
+
addUpload: async (fingerprint: string, upload: tusClient.PreviousUpload) =>
|
|
158
|
+
belongsHere((upload as StoredUpload).uploadUrl) ? inner.addUpload(fingerprint, upload) : "",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function tusUpload(opts: TusUploadOpts): Promise<TusUploadResult> {
|
|
163
|
+
return new Promise<TusUploadResult>((resolve, reject) => {
|
|
164
|
+
// TUS hands back the upload URL in a Location header, and every later PATCH
|
|
165
|
+
// carries `x-api-key`. A Location pointing at another host would therefore
|
|
166
|
+
// send the key there, which is the same exposure an HTTP redirect would
|
|
167
|
+
// give us -- so the upload URL has to stay on the configured origin.
|
|
168
|
+
const expectedOrigin = originOf(opts.baseUrl);
|
|
169
|
+
// Stream and finish() are created unconditionally so cleanup is always
|
|
170
|
+
// the same code path regardless of when abort is detected.
|
|
171
|
+
const stream = createReadStream(opts.filePath);
|
|
172
|
+
let settled = false;
|
|
173
|
+
|
|
174
|
+
const finish = (fn: () => void) => {
|
|
175
|
+
if (settled) return;
|
|
176
|
+
settled = true;
|
|
177
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
178
|
+
stream.destroy();
|
|
179
|
+
fn();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Pre-flight abort: signal was already aborted before we even started.
|
|
183
|
+
// Route through finish() so stream cleanup is invariant.
|
|
184
|
+
if (opts.signal?.aborted) {
|
|
185
|
+
finish(() => reject(abortError()));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const upload = new Upload(stream as unknown as File, {
|
|
190
|
+
endpoint: `${opts.baseUrl}/api/upload/resumable_upload`,
|
|
191
|
+
headers: { "x-api-key": opts.apiKey },
|
|
192
|
+
chunkSize: opts.chunkSize ?? DEFAULT_CHUNK,
|
|
193
|
+
retryDelays: [0, 1000, 3000, 5000],
|
|
194
|
+
urlStorage: originScopedUrlStorage(new FileUrlStorage(opts.storagePath), (url) =>
|
|
195
|
+
sameOriginAsGalaxy(url),
|
|
196
|
+
),
|
|
197
|
+
storeFingerprintForResuming: true,
|
|
198
|
+
removeFingerprintOnSuccess: true,
|
|
199
|
+
onProgress: opts.onProgress,
|
|
200
|
+
onUploadUrlAvailable: () => {
|
|
201
|
+
if (sameOriginAsGalaxy(upload.url)) return;
|
|
202
|
+
// abort() without terminate: a DELETE would send the key to the very
|
|
203
|
+
// origin we are refusing to talk to. tus checks the aborted flag before
|
|
204
|
+
// the next PATCH, so nothing further goes out.
|
|
205
|
+
void upload.abort();
|
|
206
|
+
finish(() => reject(new Error(foreignUploadUrlMessage(upload.url))));
|
|
207
|
+
},
|
|
208
|
+
onError: (err: Error | tusClient.DetailedError) => finish(() => reject(err)),
|
|
209
|
+
onSuccess: () => {
|
|
210
|
+
// Use the URL constructor to parse the upload URL so trailing slashes
|
|
211
|
+
// and query strings don't yield the wrong segment.
|
|
212
|
+
let sessionId: string | undefined;
|
|
213
|
+
try {
|
|
214
|
+
const parsed = new URL(upload.url ?? "");
|
|
215
|
+
sessionId = parsed.pathname.split("/").filter(Boolean).pop();
|
|
216
|
+
} catch {
|
|
217
|
+
// malformed or empty url -- sessionId stays undefined
|
|
218
|
+
}
|
|
219
|
+
finish(() =>
|
|
220
|
+
sessionId
|
|
221
|
+
? resolve({ sessionId })
|
|
222
|
+
: reject(new Error("TUS upload completed but Galaxy returned no session id")),
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
function sameOriginAsGalaxy(url: string | null | undefined): boolean {
|
|
228
|
+
const actual = originOf(url);
|
|
229
|
+
return (
|
|
230
|
+
actual !== null && actual === expectedOrigin && transportOriginOf(url) === expectedOrigin
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function foreignUploadUrlMessage(url: string | null | undefined): string {
|
|
235
|
+
// The origin only: an upload URL's path is a session token.
|
|
236
|
+
const target = originOf(url) ?? "an address that could not be read";
|
|
237
|
+
return (
|
|
238
|
+
`Galaxy directed the upload to ${target}, which is a different origin than the ` +
|
|
239
|
+
`configured ${expectedOrigin ?? opts.baseUrl}. The upload was refused and the API key ` +
|
|
240
|
+
`was not sent to it. Check that GALAXY_URL points at the Galaxy server itself rather ` +
|
|
241
|
+
`than a proxy or a sign-in page.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function onAbort() {
|
|
246
|
+
// No shouldTerminate: true -- leaving the partial TUS session on the server
|
|
247
|
+
// so it can be resumed if the user retries the same file (tus-js-client
|
|
248
|
+
// matches by fingerprint). Terminating here would force a full re-upload.
|
|
249
|
+
// If abort fires during the findPreviousUploads() window (before start()),
|
|
250
|
+
// abort() is a safe no-op; the settled guard below is what skips start().
|
|
251
|
+
void upload.abort();
|
|
252
|
+
finish(() => reject(abortError()));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
256
|
+
|
|
257
|
+
// tus-js-client only STORES resume state on its own -- it does not resume on
|
|
258
|
+
// start(). Look up a stored partial for this same file and continue it;
|
|
259
|
+
// otherwise start fresh. If the stored session is gone server-side (404),
|
|
260
|
+
// tus-js-client drops the stale entry and creates a new upload itself (we
|
|
261
|
+
// always set `endpoint`), so a stale resume transparently restarts.
|
|
262
|
+
upload
|
|
263
|
+
.findPreviousUploads()
|
|
264
|
+
.then((previous) => {
|
|
265
|
+
if (settled) return; // aborted while the resume lookup was in flight
|
|
266
|
+
// Everything here is already on the configured origin: the store
|
|
267
|
+
// wrapper drops (and deletes) anything else before we see it.
|
|
268
|
+
if (previous.length > 0) upload.resumeFromPreviousUpload(previous[0]);
|
|
269
|
+
upload.start();
|
|
270
|
+
})
|
|
271
|
+
.catch(() => {
|
|
272
|
+
// A failed resume lookup (e.g. an unreadable store) must not strand the
|
|
273
|
+
// upload -- fall back to a fresh start.
|
|
274
|
+
if (!settled) upload.start();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function abortError(): Error {
|
|
280
|
+
return new DOMException("Upload aborted", "AbortError");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// waitForDataset
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
export interface DatasetState {
|
|
288
|
+
id: string;
|
|
289
|
+
state: string;
|
|
290
|
+
hid?: number;
|
|
291
|
+
name?: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Galaxy's terminal dataset states -- those from which no further transitions
|
|
295
|
+
// occur, so polling stops (successfully or otherwise) once one is seen. Mirrors
|
|
296
|
+
// Dataset.terminal_states in galaxy's lib/galaxy/model/__init__.py. "empty"
|
|
297
|
+
// (a successfully-uploaded but zero-byte dataset) belongs here too -- omitting
|
|
298
|
+
// it left an empty-file upload polling all the way to the ingest timeout.
|
|
299
|
+
export const TERMINAL_DATASET_STATES = new Set([
|
|
300
|
+
"ok",
|
|
301
|
+
"empty",
|
|
302
|
+
"error",
|
|
303
|
+
"discarded",
|
|
304
|
+
"failed_metadata",
|
|
305
|
+
"deferred",
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
export interface WaitOpts {
|
|
309
|
+
get: (datasetId: string) => Promise<DatasetState>;
|
|
310
|
+
intervalMs?: number;
|
|
311
|
+
timeoutMs?: number;
|
|
312
|
+
now?: () => number; // injectable for tests
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export async function waitForDataset(datasetId: string, opts: WaitOpts): Promise<DatasetState> {
|
|
316
|
+
const interval = opts.intervalMs ?? 1500;
|
|
317
|
+
const timeout = opts.timeoutMs ?? 600_000;
|
|
318
|
+
const now = opts.now ?? (() => Date.now());
|
|
319
|
+
const start = now();
|
|
320
|
+
|
|
321
|
+
// Timeout bounds time-between-terminal-states, not absolute wall time including
|
|
322
|
+
// individual get() calls. A hung get() is the caller's responsibility -- the
|
|
323
|
+
// caller passes an abort-aware getter, so an aborted get() rejects and breaks
|
|
324
|
+
// the loop.
|
|
325
|
+
while (true) {
|
|
326
|
+
const ds = await opts.get(datasetId);
|
|
327
|
+
if (TERMINAL_DATASET_STATES.has(ds.state)) return ds;
|
|
328
|
+
if (now() - start > timeout)
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Upload ingest timed out after ${Math.round(timeout / 1000)}s (last state: ${ds.state})`,
|
|
331
|
+
);
|
|
332
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
333
|
+
}
|
|
334
|
+
}
|