@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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/dashboard` -- the user's own handle on the layout.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic and model-free on purpose. Asking the agent to reset a layout
|
|
5
|
+
* costs a turn, can be misread, and is refused outright when the panels in the
|
|
6
|
+
* way are the user's own -- which is exactly right for the agent and exactly
|
|
7
|
+
* wrong for the person whose dashboard it is. So the destructive operations
|
|
8
|
+
* live here, where the user types them, and the provenance guard the tools run
|
|
9
|
+
* under does not apply: the user is allowed to throw away their own panels.
|
|
10
|
+
*
|
|
11
|
+
* `undo` is here for the same reason. The pane has its own undo, but the
|
|
12
|
+
* terminal has no pane at all, and an agent change the user cannot reverse
|
|
13
|
+
* without one is not undoable in any sense that matters.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import {
|
|
18
|
+
DASHBOARD_PRESETS,
|
|
19
|
+
createDefaultDashboardDocument,
|
|
20
|
+
dashboardFromPreset,
|
|
21
|
+
} from "../../shared/dashboard-contract.js";
|
|
22
|
+
import type { DashboardDocument } from "../../shared/dashboard-contract.js";
|
|
23
|
+
import {
|
|
24
|
+
commitDashboardChange,
|
|
25
|
+
countPanels,
|
|
26
|
+
isProtectedPanel,
|
|
27
|
+
landedLine,
|
|
28
|
+
logDashboardChange,
|
|
29
|
+
summarizeDocument,
|
|
30
|
+
} from "./dashboard-tools";
|
|
31
|
+
import {
|
|
32
|
+
readDashboardDocument,
|
|
33
|
+
replaceDashboardDocument,
|
|
34
|
+
undoDashboardChange,
|
|
35
|
+
} from "./dashboard-store";
|
|
36
|
+
|
|
37
|
+
const USAGE =
|
|
38
|
+
"Usage: /dashboard (show the layout) | /dashboard preset <name> | /dashboard reset | /dashboard undo";
|
|
39
|
+
|
|
40
|
+
function presetList(): string {
|
|
41
|
+
return DASHBOARD_PRESETS.map((p) => ` ${p.id} -- ${p.description}`).join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** How many of the panels about to be discarded were the user's own. */
|
|
45
|
+
function userPanelsIn(document: DashboardDocument): number {
|
|
46
|
+
return document.dashboards.reduce(
|
|
47
|
+
(total, d) => total + d.panels.filter(isProtectedPanel).length,
|
|
48
|
+
0,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function discardNote(before: DashboardDocument): string {
|
|
53
|
+
const count = userPanelsIn(before);
|
|
54
|
+
if (count === 0) return "";
|
|
55
|
+
return `\nThis replaced ${count} panel(s) you had placed. /dashboard undo puts them back.`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function showLayout(ctx: ExtensionContext): Promise<void> {
|
|
59
|
+
const read = await readDashboardDocument();
|
|
60
|
+
if (!read.ok) {
|
|
61
|
+
ctx.ui.notify(read.error, "warning");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const lines = [
|
|
65
|
+
read.exists
|
|
66
|
+
? `Dashboard layout (${countPanels(read.document)} panel(s)) -- ${read.path}`
|
|
67
|
+
: "No layout file yet. This is the default you would see:",
|
|
68
|
+
...summarizeDocument(read.document),
|
|
69
|
+
];
|
|
70
|
+
if (read.problems.length > 0) {
|
|
71
|
+
lines.push(
|
|
72
|
+
"",
|
|
73
|
+
"The file on disk had problems and was left alone:",
|
|
74
|
+
...read.problems.map((p) => ` ${p.path || "document"}: ${p.message}`),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
lines.push("", `Presets:\n${presetList()}`, USAGE);
|
|
78
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function applyPreset(ctx: ExtensionContext, presetId: string): Promise<void> {
|
|
82
|
+
if (!presetId) {
|
|
83
|
+
ctx.ui.notify(`Which preset?\n${presetList()}`, "warning");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!dashboardFromPreset(presetId)) {
|
|
87
|
+
ctx.ui.notify(`No preset "${presetId}".\n${presetList()}`, "error");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let discarded = "";
|
|
92
|
+
const outcome = await commitDashboardChange(
|
|
93
|
+
(current) => {
|
|
94
|
+
const preset = dashboardFromPreset(presetId);
|
|
95
|
+
if (!preset) return { ok: false, error: `No preset "${presetId}".` };
|
|
96
|
+
const existing = current.dashboards.findIndex((d) => d.id === preset.id);
|
|
97
|
+
const next: DashboardDocument = {
|
|
98
|
+
...current,
|
|
99
|
+
dashboards: current.dashboards.slice(),
|
|
100
|
+
};
|
|
101
|
+
if (existing >= 0) {
|
|
102
|
+
discarded = discardNote({ ...current, dashboards: [current.dashboards[existing]] });
|
|
103
|
+
next.dashboards[existing] = preset;
|
|
104
|
+
} else {
|
|
105
|
+
next.dashboards.push(preset);
|
|
106
|
+
}
|
|
107
|
+
next.activeId = preset.id;
|
|
108
|
+
return { ok: true, document: next, notes: [`applied the ${preset.id} preset`] };
|
|
109
|
+
},
|
|
110
|
+
{ asUser: true },
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (!outcome.ok) {
|
|
114
|
+
ctx.ui.notify(outcome.error, "error");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
logDashboardChange(outcome.path, outcome.notes, "/dashboard preset");
|
|
118
|
+
ctx.ui.notify(
|
|
119
|
+
[`Dashboard set to the ${presetId} preset.`, ...summarizeDocument(outcome.document)].join(
|
|
120
|
+
"\n",
|
|
121
|
+
) +
|
|
122
|
+
discarded +
|
|
123
|
+
`\n${landedLine()}`,
|
|
124
|
+
"info",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The documented way out of a layout nothing else can cope with, so it must not
|
|
130
|
+
* depend on being able to read that layout: a file past the size cap, or
|
|
131
|
+
* corrupt in a way the validator refuses, is exactly when someone types this.
|
|
132
|
+
* That means no compare-and-swap either -- "whatever is there, give me the
|
|
133
|
+
* default" has nothing to conflict with.
|
|
134
|
+
*/
|
|
135
|
+
async function resetLayout(ctx: ExtensionContext): Promise<void> {
|
|
136
|
+
const before = await readDashboardDocument();
|
|
137
|
+
const discarded = before.ok ? discardNote(before.document) : "";
|
|
138
|
+
|
|
139
|
+
const outcome = await replaceDashboardDocument(createDefaultDashboardDocument());
|
|
140
|
+
if (!outcome.ok) {
|
|
141
|
+
ctx.ui.notify(outcome.error, "error");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
logDashboardChange(outcome.path, ["reset to the default layout"], "/dashboard reset");
|
|
145
|
+
ctx.ui.notify(
|
|
146
|
+
["Dashboard reset to the default layout.", ...summarizeDocument(outcome.document)].join("\n") +
|
|
147
|
+
discarded +
|
|
148
|
+
(outcome.undoable === false
|
|
149
|
+
? "\nThe layout it replaced was too large to hold, so this one cannot be undone."
|
|
150
|
+
: "") +
|
|
151
|
+
`\n${landedLine()}`,
|
|
152
|
+
"info",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function undoLayout(ctx: ExtensionContext): Promise<void> {
|
|
157
|
+
const outcome = await undoDashboardChange();
|
|
158
|
+
if (!outcome.ok) {
|
|
159
|
+
ctx.ui.notify(outcome.error, "warning");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
ctx.ui.notify(
|
|
163
|
+
outcome.restored === "none"
|
|
164
|
+
? `Undone: the dashboard is back to the default layout.\n${landedLine()}`
|
|
165
|
+
: `Undone: the previous dashboard layout is back.\n${landedLine()}`,
|
|
166
|
+
"info",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function registerDashboardCommands(pi: ExtensionAPI): void {
|
|
171
|
+
pi.registerCommand("dashboard", {
|
|
172
|
+
description:
|
|
173
|
+
"Show the dashboard layout. Subcommands: preset <name> | reset | undo (the last change).",
|
|
174
|
+
handler: async (args: string | undefined, ctx: ExtensionContext) => {
|
|
175
|
+
const parts = (args ?? "").trim().split(/\s+/).filter(Boolean);
|
|
176
|
+
const sub = (parts[0] ?? "").toLowerCase();
|
|
177
|
+
try {
|
|
178
|
+
if (!sub) return await showLayout(ctx);
|
|
179
|
+
if (sub === "preset") return await applyPreset(ctx, parts[1] ?? "");
|
|
180
|
+
if (sub === "reset") return await resetLayout(ctx);
|
|
181
|
+
if (sub === "undo") return await undoLayout(ctx);
|
|
182
|
+
ctx.ui.notify(`Unknown /dashboard subcommand: ${sub}.\n${USAGE}`, "warning");
|
|
183
|
+
} catch (err) {
|
|
184
|
+
ctx.ui.notify(
|
|
185
|
+
`/dashboard ${sub}: ${err instanceof Error ? err.message : String(err)}`,
|
|
186
|
+
"error",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brain-side access to the dashboard layout file.
|
|
3
|
+
*
|
|
4
|
+
* The layout is `.loom-dashboard.json` beside `notebook.md` in the analysis
|
|
5
|
+
* directory. The shells already read and write it -- the desktop through its
|
|
6
|
+
* cwd watcher, both shells through a revision poll -- so a write here IS the
|
|
7
|
+
* transport: there is no second channel and no widget push for layout. The
|
|
8
|
+
* brain writes the file; whichever shell is attached picks it up within a few
|
|
9
|
+
* seconds. In the CLI nothing is attached and the file is simply updated.
|
|
10
|
+
*
|
|
11
|
+
* Three writers share this file (the pane's editor, a widget saving its own
|
|
12
|
+
* config, and this module), which is why every write is a compare-and-swap on
|
|
13
|
+
* the shared content revision and lands through a temp file plus rename. Same
|
|
14
|
+
* discipline as `writeNotebook`/`withNotebookCas`, same reasons.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { randomBytes } from "node:crypto";
|
|
18
|
+
import { readLayoutFile, withLayoutLock } from "../../shared/dashboard-layout-store.js";
|
|
19
|
+
import * as fsp from "node:fs/promises";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import {
|
|
22
|
+
DASHBOARD_FILENAME,
|
|
23
|
+
DASHBOARD_MAX_BYTES,
|
|
24
|
+
createDefaultDashboardDocument,
|
|
25
|
+
dashboardRevision,
|
|
26
|
+
parseDashboardDocument,
|
|
27
|
+
serializeDashboardDocument,
|
|
28
|
+
} from "../../shared/dashboard-contract.js";
|
|
29
|
+
import type { DashboardDocument, DashboardProblem } from "../../shared/dashboard-contract.js";
|
|
30
|
+
import { getNotebookPath } from "./state";
|
|
31
|
+
|
|
32
|
+
/** How many read-apply-write attempts before giving up to a concurrent writer. */
|
|
33
|
+
const MAX_CAS_ATTEMPTS = 3;
|
|
34
|
+
|
|
35
|
+
/** Undo depth. Layout documents are small; this costs nothing worth counting. */
|
|
36
|
+
const MAX_UNDO_DEPTH = 10;
|
|
37
|
+
|
|
38
|
+
export type DashboardStoreFailure = { ok: false; error: string };
|
|
39
|
+
|
|
40
|
+
export type DashboardReadResult =
|
|
41
|
+
| {
|
|
42
|
+
ok: true;
|
|
43
|
+
/** The validated document -- the default one when there is no file yet. */
|
|
44
|
+
document: DashboardDocument;
|
|
45
|
+
/** False when nothing is on disk, so the caller can say "default layout". */
|
|
46
|
+
exists: boolean;
|
|
47
|
+
/** Repairs validation made, or why an unreadable file was ignored. */
|
|
48
|
+
problems: DashboardProblem[];
|
|
49
|
+
/** Content fingerprint of what was on disk, null when there was no file. */
|
|
50
|
+
revision: string | null;
|
|
51
|
+
/** Exactly what was on disk, null when there was no file. */
|
|
52
|
+
raw: string | null;
|
|
53
|
+
path: string;
|
|
54
|
+
}
|
|
55
|
+
| DashboardStoreFailure;
|
|
56
|
+
|
|
57
|
+
export type DashboardWriteResult =
|
|
58
|
+
{ ok: true; document: DashboardDocument; path: string; revision: string } | DashboardStoreFailure;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Where the layout lives for this session. Derived from the notebook path, not
|
|
62
|
+
* from a caller argument and not from `process.cwd()`: the notebook is what
|
|
63
|
+
* defines "the analysis directory" brain-side, and a fixed basename under it
|
|
64
|
+
* means no tool input ever reaches a filesystem path.
|
|
65
|
+
*/
|
|
66
|
+
export function getDashboardPath(): string | null {
|
|
67
|
+
const notebook = getNotebookPath();
|
|
68
|
+
if (!notebook) return null;
|
|
69
|
+
return path.join(path.dirname(notebook), DASHBOARD_FILENAME);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const CHANGED_SINCE =
|
|
73
|
+
"The dashboard has changed since that edit, so undoing it would discard the newer layout. Nothing was changed.";
|
|
74
|
+
|
|
75
|
+
const NO_SESSION =
|
|
76
|
+
"No analysis directory yet -- the dashboard file lives beside notebook.md, and this session has no notebook.";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Refuse a symlink at the layout path, on read and on write.
|
|
80
|
+
*
|
|
81
|
+
* The basename is fixed so there is nothing to traverse with, but the agent can
|
|
82
|
+
* write in the analysis directory and `writeFile` follows symlinks, so a link
|
|
83
|
+
* planted at this name would turn a background layout save into a write to
|
|
84
|
+
* whatever it points at.
|
|
85
|
+
*/
|
|
86
|
+
async function refuseSymlink(filePath: string, checkSize = true): Promise<string | null> {
|
|
87
|
+
try {
|
|
88
|
+
const st = await fsp.lstat(filePath);
|
|
89
|
+
if (st.isSymbolicLink()) {
|
|
90
|
+
return `${DASHBOARD_FILENAME} is a symbolic link; refusing to read or write through it.`;
|
|
91
|
+
}
|
|
92
|
+
if (checkSize && st.isFile() && st.size > DASHBOARD_MAX_BYTES) {
|
|
93
|
+
return `${DASHBOARD_FILENAME} is larger than ${DASHBOARD_MAX_BYTES} bytes; refusing to read it. /dashboard reset will replace it.`;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Missing is fine -- that is the no-file-yet case.
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The file's text, or null when there is genuinely no file.
|
|
103
|
+
*
|
|
104
|
+
* Through the shared reader, so the brain reads the layout the way the shells
|
|
105
|
+
* do: by descriptor, with the symlink and size checks on the thing actually
|
|
106
|
+
* opened rather than on the name a moment earlier. Only a missing file is "no
|
|
107
|
+
* file"; every other refusal throws, because turning an unreadable layout into
|
|
108
|
+
* "there is nothing here" would let the compare-and-swap below rename over
|
|
109
|
+
* something it could not read.
|
|
110
|
+
*/
|
|
111
|
+
async function readRaw(filePath: string): Promise<string | null> {
|
|
112
|
+
const res = await readLayoutFile(filePath, DASHBOARD_MAX_BYTES);
|
|
113
|
+
if (!res.ok) throw new Error(res.error);
|
|
114
|
+
return res.raw;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A lock the other process would not give up is a failed write, not a crash. */
|
|
118
|
+
function lockFailure<T extends { ok: false; error: string }>(err: unknown): T {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: `Could not write ${DASHBOARD_FILENAME}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
122
|
+
} as T;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Describe a filesystem failure without the filesystem's own words.
|
|
127
|
+
*
|
|
128
|
+
* An errno message carries the absolute path, and for a failed rename it also
|
|
129
|
+
* carries the scratch filename, which is the one thing about this writer worth
|
|
130
|
+
* keeping to ourselves. Every other message here names only the basename; these
|
|
131
|
+
* should too.
|
|
132
|
+
*/
|
|
133
|
+
function fsFailure(verb: string, err: unknown): string {
|
|
134
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
135
|
+
return `Could not ${verb} ${DASHBOARD_FILENAME}${code ? ` (${code})` : ""}.`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Read and validate the layout, falling back to the default document. */
|
|
139
|
+
export async function readDashboardDocument(): Promise<DashboardReadResult> {
|
|
140
|
+
const filePath = getDashboardPath();
|
|
141
|
+
if (!filePath) return { ok: false, error: NO_SESSION };
|
|
142
|
+
|
|
143
|
+
const refusal = await refuseSymlink(filePath);
|
|
144
|
+
if (refusal) return { ok: false, error: refusal };
|
|
145
|
+
|
|
146
|
+
let raw: string | null;
|
|
147
|
+
try {
|
|
148
|
+
raw = await readRaw(filePath);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return { ok: false, error: fsFailure("read", err) };
|
|
151
|
+
}
|
|
152
|
+
if (raw === null) {
|
|
153
|
+
return {
|
|
154
|
+
ok: true,
|
|
155
|
+
document: createDefaultDashboardDocument(),
|
|
156
|
+
exists: false,
|
|
157
|
+
problems: [],
|
|
158
|
+
revision: null,
|
|
159
|
+
raw: null,
|
|
160
|
+
path: filePath,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parsed = parseDashboardDocument(raw);
|
|
165
|
+
if (!parsed.ok) {
|
|
166
|
+
// Matches the shells: an unreadable layout is left on disk exactly as it is
|
|
167
|
+
// and the default is shown, because losing someone's layout to a parse bug
|
|
168
|
+
// is worse than one session of the default.
|
|
169
|
+
return {
|
|
170
|
+
ok: true,
|
|
171
|
+
document: createDefaultDashboardDocument(),
|
|
172
|
+
exists: true,
|
|
173
|
+
problems: parsed.problems,
|
|
174
|
+
revision: dashboardRevision(raw),
|
|
175
|
+
raw,
|
|
176
|
+
path: filePath,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
document: parsed.document,
|
|
182
|
+
exists: true,
|
|
183
|
+
problems: parsed.problems,
|
|
184
|
+
revision: dashboardRevision(raw),
|
|
185
|
+
raw,
|
|
186
|
+
path: filePath,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* One entry per write this process made, newest last.
|
|
192
|
+
*
|
|
193
|
+
* `previous: null` means "there was no file", so undoing back to that state
|
|
194
|
+
* removes the one we created rather than leaving a layout nobody asked for.
|
|
195
|
+
* `wrote` and `path` are what make the stack safe to keep across a directory
|
|
196
|
+
* switch and across someone else editing the layout: an entry only applies if
|
|
197
|
+
* the file is still exactly where that write left it.
|
|
198
|
+
*/
|
|
199
|
+
type UndoEntry = { path: string; previous: string | null; wrote: string };
|
|
200
|
+
|
|
201
|
+
const undoStack: UndoEntry[] = [];
|
|
202
|
+
|
|
203
|
+
function pushUndo(entry: UndoEntry): void {
|
|
204
|
+
// A new analysis directory makes every older entry unreachable -- undo only
|
|
205
|
+
// ever acts on the file it is looking at -- and keeping them around means the
|
|
206
|
+
// first undo someone types in the new directory pops a stranger's entry and
|
|
207
|
+
// reports a change they never made.
|
|
208
|
+
for (let i = undoStack.length - 1; i >= 0; i--) {
|
|
209
|
+
if (undoStack[i].path !== entry.path) undoStack.splice(i, 1);
|
|
210
|
+
}
|
|
211
|
+
undoStack.push(entry);
|
|
212
|
+
if (undoStack.length > MAX_UNDO_DEPTH) undoStack.shift();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function undoDepth(): number {
|
|
216
|
+
return undoStack.length;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Drop the whole stack. `pushUndo` already discards entries for another
|
|
221
|
+
* directory, so nothing in the product needs this; it is here so a test can
|
|
222
|
+
* start from a known state, and so a future session_start hook has somewhere
|
|
223
|
+
* obvious to call.
|
|
224
|
+
*/
|
|
225
|
+
export function resetDashboardUndo(): void {
|
|
226
|
+
undoStack.length = 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function tmpPathFor(filePath: string): string {
|
|
230
|
+
return `${filePath}.tmp.${randomBytes(8).toString("hex")}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Stage the new text beside the file, re-check that the file still has the
|
|
235
|
+
* revision we based the change on, then rename over it.
|
|
236
|
+
*
|
|
237
|
+
* Like `writeNotebook`, the check sits between the staging write and the
|
|
238
|
+
* rename, which narrows the window to a single syscall without closing it.
|
|
239
|
+
* `wx` means the scratch file is created or the write fails, so a planted
|
|
240
|
+
* symlink at a scratch name cannot redirect it either.
|
|
241
|
+
*/
|
|
242
|
+
async function swapInto(
|
|
243
|
+
filePath: string,
|
|
244
|
+
text: string,
|
|
245
|
+
baseRevision: string | null,
|
|
246
|
+
): Promise<"ok" | "conflict"> {
|
|
247
|
+
const tmp = tmpPathFor(filePath);
|
|
248
|
+
await fsp.writeFile(tmp, text, { encoding: "utf-8", flag: "wx" });
|
|
249
|
+
try {
|
|
250
|
+
const current = dashboardRevision(await readRaw(filePath));
|
|
251
|
+
if (current !== baseRevision) return "conflict";
|
|
252
|
+
await fsp.rename(tmp, filePath);
|
|
253
|
+
return "ok";
|
|
254
|
+
} finally {
|
|
255
|
+
await fsp.rm(tmp, { force: true }).catch(() => {});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Read -> apply -> write as a compare-and-swap against the shared revision.
|
|
261
|
+
*
|
|
262
|
+
* `apply` runs against each attempt's freshly validated document and returns
|
|
263
|
+
* the document to persist, or a refusal that abandons the write with nothing
|
|
264
|
+
* written. Validation of the result is the caller's job -- it has the better
|
|
265
|
+
* error messages -- but the text is capped here because the shells cap it too.
|
|
266
|
+
*/
|
|
267
|
+
export async function updateDashboardDocument(
|
|
268
|
+
apply: (
|
|
269
|
+
current: DashboardDocument,
|
|
270
|
+
exists: boolean,
|
|
271
|
+
) => { ok: true; document: DashboardDocument } | DashboardStoreFailure,
|
|
272
|
+
): Promise<DashboardWriteResult> {
|
|
273
|
+
const filePath = getDashboardPath();
|
|
274
|
+
if (!filePath) return { ok: false, error: NO_SESSION };
|
|
275
|
+
|
|
276
|
+
// Serialized per path, for the same reason the shells are: two tool calls in
|
|
277
|
+
// the same turn could otherwise both read, both apply and both write, and the
|
|
278
|
+
// second would silently replace the first.
|
|
279
|
+
return withLayoutLock(filePath, () => updateLocked(filePath, apply)).catch(lockFailure);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function updateLocked(
|
|
283
|
+
filePath: string,
|
|
284
|
+
apply: (
|
|
285
|
+
current: DashboardDocument,
|
|
286
|
+
exists: boolean,
|
|
287
|
+
) => { ok: true; document: DashboardDocument } | DashboardStoreFailure,
|
|
288
|
+
): Promise<DashboardWriteResult> {
|
|
289
|
+
for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {
|
|
290
|
+
const refusal = await refuseSymlink(filePath);
|
|
291
|
+
if (refusal) return { ok: false, error: refusal };
|
|
292
|
+
|
|
293
|
+
const current = await readDashboardDocument();
|
|
294
|
+
if (!current.ok) return current;
|
|
295
|
+
|
|
296
|
+
const applied = apply(current.document, current.exists);
|
|
297
|
+
if (!applied.ok) return applied;
|
|
298
|
+
|
|
299
|
+
const text = serializeDashboardDocument(applied.document);
|
|
300
|
+
if (Buffer.byteLength(text, "utf-8") > DASHBOARD_MAX_BYTES) {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
error: `That layout is larger than ${DASHBOARD_MAX_BYTES} bytes; nothing was written.`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let outcome: "ok" | "conflict";
|
|
308
|
+
try {
|
|
309
|
+
outcome = await swapInto(filePath, text, current.revision);
|
|
310
|
+
} catch (err) {
|
|
311
|
+
return { ok: false, error: fsFailure("write", err) };
|
|
312
|
+
}
|
|
313
|
+
if (outcome === "conflict") continue;
|
|
314
|
+
|
|
315
|
+
// Snapshot what we just replaced, taken from the read that this write was
|
|
316
|
+
// based on. Re-reading here would capture our own bytes.
|
|
317
|
+
const revision = dashboardRevision(text) as string;
|
|
318
|
+
pushUndo({ path: filePath, previous: current.raw, wrote: revision });
|
|
319
|
+
return { ok: true, document: applied.document, path: filePath, revision };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
error: "The dashboard file kept changing while it was being updated; nothing was written.",
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Replace the layout outright, without reading what is there first.
|
|
330
|
+
*
|
|
331
|
+
* The one path that must survive a file the reader refuses -- over the size
|
|
332
|
+
* cap, or corrupt beyond repair -- because it is the documented way out of
|
|
333
|
+
* exactly that state. There is no compare-and-swap here by design: the caller
|
|
334
|
+
* is a user typing `/dashboard reset`, which means "whatever is there, I want
|
|
335
|
+
* the default", and a conflict check would just refuse them again.
|
|
336
|
+
*
|
|
337
|
+
* Undo still works when the old file can be held in memory. When it cannot, the
|
|
338
|
+
* change is recorded as un-undoable rather than as "there was no file", because
|
|
339
|
+
* the latter would have undo delete a file it never created.
|
|
340
|
+
*/
|
|
341
|
+
export async function replaceDashboardDocument(
|
|
342
|
+
document: DashboardDocument,
|
|
343
|
+
): Promise<DashboardWriteResult & { undoable?: boolean }> {
|
|
344
|
+
const filePath = getDashboardPath();
|
|
345
|
+
if (!filePath) return { ok: false, error: NO_SESSION };
|
|
346
|
+
|
|
347
|
+
// Skipping the compare-and-swap is the point here; skipping the lock is not.
|
|
348
|
+
// Unserialized, this interleaves with an in-flight update and one of the two
|
|
349
|
+
// loses: measured against real directories, both calls reported success and
|
|
350
|
+
// the reset's bytes were not the ones on disk in the large majority of
|
|
351
|
+
// trials. Which one loses depends on where the renames fall relative to the
|
|
352
|
+
// update's check; the lock is what stops there being a question.
|
|
353
|
+
return withLayoutLock(filePath, () => replaceLocked(filePath, document)).catch(lockFailure);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function replaceLocked(
|
|
357
|
+
filePath: string,
|
|
358
|
+
document: DashboardDocument,
|
|
359
|
+
): Promise<DashboardWriteResult & { undoable?: boolean }> {
|
|
360
|
+
// The size cap is skipped -- that is the point -- but a symlink is still
|
|
361
|
+
// refused, because "the user asked for it" does not extend to a file of
|
|
362
|
+
// theirs somewhere else.
|
|
363
|
+
const refusal = await refuseSymlink(filePath, false);
|
|
364
|
+
if (refusal) return { ok: false, error: refusal };
|
|
365
|
+
|
|
366
|
+
const text = serializeDashboardDocument(document);
|
|
367
|
+
if (Buffer.byteLength(text, "utf-8") > DASHBOARD_MAX_BYTES) {
|
|
368
|
+
return {
|
|
369
|
+
ok: false,
|
|
370
|
+
error: `That layout is larger than ${DASHBOARD_MAX_BYTES} bytes; nothing was written.`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let previous: string | null = null;
|
|
375
|
+
let undoable = true;
|
|
376
|
+
try {
|
|
377
|
+
previous = await readRaw(filePath);
|
|
378
|
+
} catch {
|
|
379
|
+
undoable = false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const tmp = tmpPathFor(filePath);
|
|
383
|
+
try {
|
|
384
|
+
await fsp.writeFile(tmp, text, { encoding: "utf-8", flag: "wx" });
|
|
385
|
+
await fsp.rename(tmp, filePath);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
await fsp.rm(tmp, { force: true }).catch(() => {});
|
|
388
|
+
return { ok: false, error: fsFailure("write", err) };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const revision = dashboardRevision(text) as string;
|
|
392
|
+
if (undoable) pushUndo({ path: filePath, previous, wrote: revision });
|
|
393
|
+
return { ok: true, document, path: filePath, revision, undoable };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export type DashboardUndoResult =
|
|
397
|
+
{ ok: true; restored: "previous" | "none"; path: string } | DashboardStoreFailure;
|
|
398
|
+
|
|
399
|
+
/** Put back whatever the last write here replaced. */
|
|
400
|
+
export async function undoDashboardChange(): Promise<DashboardUndoResult> {
|
|
401
|
+
const filePath = getDashboardPath();
|
|
402
|
+
if (!filePath) return { ok: false, error: NO_SESSION };
|
|
403
|
+
// Same lock as every other write here. The restore is a compare-and-swap, but
|
|
404
|
+
// interleaved with an update both can pass their checks against the same
|
|
405
|
+
// revision, and then the one that renames last decides what survives -- the
|
|
406
|
+
// other having already reported success. The `rm` branch has no swap at all
|
|
407
|
+
// underneath it.
|
|
408
|
+
//
|
|
409
|
+
// One consequence worth knowing: an undo typed during a tool call now queues
|
|
410
|
+
// behind that call rather than racing it, so it undoes the tool's write.
|
|
411
|
+
return withLayoutLock(filePath, () => undoLocked(filePath)).catch(lockFailure);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function undoLocked(filePath: string): Promise<DashboardUndoResult> {
|
|
415
|
+
if (undoStack.length === 0) {
|
|
416
|
+
return {
|
|
417
|
+
ok: false,
|
|
418
|
+
error: "Nothing to undo -- no dashboard change has been made this session.",
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const refusal = await refuseSymlink(filePath);
|
|
423
|
+
if (refusal) return { ok: false, error: refusal };
|
|
424
|
+
|
|
425
|
+
// An entry for another directory can only be here if the notebook moved
|
|
426
|
+
// mid-session; it can never apply again, and popping it one at a time would
|
|
427
|
+
// report a stranger's change to whoever types undo next.
|
|
428
|
+
while (undoStack.length > 0 && undoStack[undoStack.length - 1].path !== filePath) {
|
|
429
|
+
undoStack.pop();
|
|
430
|
+
}
|
|
431
|
+
if (undoStack.length === 0) {
|
|
432
|
+
return {
|
|
433
|
+
ok: false,
|
|
434
|
+
error: "Nothing to undo -- no dashboard change has been made in this analysis.",
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const entry = undoStack[undoStack.length - 1];
|
|
439
|
+
// Only undo a change that is still the last thing to have happened here. If
|
|
440
|
+
// the user rearranged the pane afterwards, restoring this would throw away
|
|
441
|
+
// work nobody asked us to touch.
|
|
442
|
+
let currentRevision: string | null;
|
|
443
|
+
try {
|
|
444
|
+
currentRevision = dashboardRevision(await readRaw(filePath));
|
|
445
|
+
} catch (err) {
|
|
446
|
+
return { ok: false, error: fsFailure("read", err) };
|
|
447
|
+
}
|
|
448
|
+
if (currentRevision !== entry.wrote) {
|
|
449
|
+
undoStack.pop();
|
|
450
|
+
return { ok: false, error: CHANGED_SINCE };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const previous = entry.previous;
|
|
454
|
+
try {
|
|
455
|
+
if (previous === null) {
|
|
456
|
+
await fsp.rm(filePath, { force: true });
|
|
457
|
+
} else {
|
|
458
|
+
// Through the same compare-and-swap a normal write uses, so the revision
|
|
459
|
+
// is re-checked after staging rather than before: checking only up front
|
|
460
|
+
// leaves a window the width of a whole writeFile for someone else's save
|
|
461
|
+
// to land and be clobbered by the undo.
|
|
462
|
+
if ((await swapInto(filePath, previous, entry.wrote)) === "conflict") {
|
|
463
|
+
undoStack.pop();
|
|
464
|
+
return { ok: false, error: CHANGED_SINCE };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch (err) {
|
|
468
|
+
return { ok: false, error: fsFailure("restore", err) };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
undoStack.pop();
|
|
472
|
+
return { ok: true, restored: previous === null ? "none" : "previous", path: filePath };
|
|
473
|
+
}
|