@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,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scripted model, for the Tier-1 evals.
|
|
3
|
+
*
|
|
4
|
+
* The replay seams (`submission-replay.ts`, `lessons/replay.ts`) feed recorded
|
|
5
|
+
* results into one hook each, which is enough when the thing under test is the
|
|
6
|
+
* hook. The evidence gate is different: what it judges is a model's edit to
|
|
7
|
+
* the notebook, and the deny that matters is the one pi's own `tool_call` path
|
|
8
|
+
* honours -- every other tool_call handler running first, the exec-guard
|
|
9
|
+
* floor, the write landing or not. A replay can't show that. This seam puts a
|
|
10
|
+
* model in the loop that says exactly what a script tells it to, using pi's
|
|
11
|
+
* faux provider, so pi's agent loop, its tools and every Orbit hook run as they
|
|
12
|
+
* do with a real model.
|
|
13
|
+
*
|
|
14
|
+
* `ORBIT_MODEL_SCRIPT` names a JSONL file, one model turn per line, in order:
|
|
15
|
+
*
|
|
16
|
+
* ```json
|
|
17
|
+
* {"tool": "galaxy_invocation_check_all", "input": {}, "text": "Checking the run first."}
|
|
18
|
+
* {"tool": "edit", "input": {"path": "notebook.md", "edits": [{"oldText": "...", "newText": "..."}]}}
|
|
19
|
+
* {"text": "Step 1 is verified."}
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* A tool line ends its turn with `toolUse`, so pi runs the call and asks for
|
|
23
|
+
* the next turn; a text line ends it with `stop`, which ends the prompt -- so
|
|
24
|
+
* text meant to come before a call goes on the call's line. A script that runs out
|
|
25
|
+
* answers every later turn with an empty `stop`, which ends the prompt cleanly
|
|
26
|
+
* rather than with the faux provider's "no more responses" error.
|
|
27
|
+
*
|
|
28
|
+
* Same constraints as the replay seams: off unless the variable is set (and
|
|
29
|
+
* nothing registers otherwise), the file must resolve inside the session
|
|
30
|
+
* directory, and a `model.script` activity row is written before the first
|
|
31
|
+
* turn, so a log produced this way says so. Two more, because this one stands
|
|
32
|
+
* in for the model:
|
|
33
|
+
*
|
|
34
|
+
* - the file is read once per process, at the first session start, through
|
|
35
|
+
* one descriptor on a regular file, so nothing the scripted session does --
|
|
36
|
+
* a write to the script, a tool result, a notebook edit, `/new` -- can add
|
|
37
|
+
* lines to it;
|
|
38
|
+
* - when the variable is set, the scripted model is selected whether or not
|
|
39
|
+
* the file was usable, moved back to if anything selects another, and any
|
|
40
|
+
* request that reaches a real provider anyway is aborted and logged.
|
|
41
|
+
* `team_dispatch`, which calls a model directly, refuses to run.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
45
|
+
import {
|
|
46
|
+
fauxAssistantMessage,
|
|
47
|
+
fauxProvider,
|
|
48
|
+
fauxText,
|
|
49
|
+
fauxToolCall,
|
|
50
|
+
type AssistantMessage,
|
|
51
|
+
type FauxContentBlock,
|
|
52
|
+
type FauxProviderHandle,
|
|
53
|
+
type ToolCall,
|
|
54
|
+
} from "@earendil-works/pi-ai";
|
|
55
|
+
import * as fs from "fs";
|
|
56
|
+
import * as path from "path";
|
|
57
|
+
import { readEnv } from "../../shared/orbit-env.js";
|
|
58
|
+
import { appendActivityEvent } from "./activity";
|
|
59
|
+
import { getNotebookPath } from "./state";
|
|
60
|
+
|
|
61
|
+
export const MODEL_SCRIPT_PROVIDER = "loom-script";
|
|
62
|
+
export const MODEL_SCRIPT_MODEL_ID = "scripted";
|
|
63
|
+
|
|
64
|
+
export interface ScriptTurn {
|
|
65
|
+
text?: string;
|
|
66
|
+
tool?: string;
|
|
67
|
+
input?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ParsedScript {
|
|
71
|
+
turns: ScriptTurn[];
|
|
72
|
+
/** Lines that weren't a turn: bad JSON, or neither a tool nor text. */
|
|
73
|
+
skipped: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isModelScriptEnabled(): boolean {
|
|
77
|
+
return !!readEnv("MODEL_SCRIPT")?.trim();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
81
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One turn per non-blank line; anything that isn't one is counted, not guessed at. */
|
|
85
|
+
export function parseModelScript(raw: string): ParsedScript {
|
|
86
|
+
const turns: ScriptTurn[] = [];
|
|
87
|
+
let skipped = 0;
|
|
88
|
+
for (const line of raw.split("\n")) {
|
|
89
|
+
if (!line.trim()) continue;
|
|
90
|
+
let parsed: unknown;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(line);
|
|
93
|
+
} catch {
|
|
94
|
+
skipped++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!isPlainObject(parsed)) {
|
|
98
|
+
skipped++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const text = typeof parsed.text === "string" ? parsed.text : undefined;
|
|
102
|
+
const tool = typeof parsed.tool === "string" && parsed.tool.trim() ? parsed.tool : undefined;
|
|
103
|
+
if (parsed.input !== undefined && !isPlainObject(parsed.input)) {
|
|
104
|
+
skipped++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (tool) {
|
|
108
|
+
turns.push({ tool, input: (parsed.input as Record<string, unknown>) ?? {}, text });
|
|
109
|
+
} else if (text !== undefined && parsed.tool === undefined) {
|
|
110
|
+
turns.push({ text });
|
|
111
|
+
} else {
|
|
112
|
+
skipped++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { turns, skipped };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The assistant message a turn becomes. Exported for the test that pins the shape. */
|
|
119
|
+
export function turnToMessage(turn: ScriptTurn | undefined): AssistantMessage {
|
|
120
|
+
if (!turn) return fauxAssistantMessage([], { stopReason: "stop" });
|
|
121
|
+
const content: FauxContentBlock[] = [];
|
|
122
|
+
if (turn.text !== undefined) content.push(fauxText(turn.text));
|
|
123
|
+
if (turn.tool) {
|
|
124
|
+
// Parsed from JSON, so it is JSON.
|
|
125
|
+
const args = structuredClone(turn.input ?? {}) as ToolCall["arguments"];
|
|
126
|
+
content.push(fauxToolCall(turn.tool, args));
|
|
127
|
+
return fauxAssistantMessage(content, { stopReason: "toolUse" });
|
|
128
|
+
}
|
|
129
|
+
return fauxAssistantMessage(content, { stopReason: "stop" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* What the process has read of the script. Process-wide rather than held by
|
|
134
|
+
* one extension instance: pi rebuilds the runtime and reruns extension
|
|
135
|
+
* factories on `/new`, resume and fork, and a fresh instance that re-read the
|
|
136
|
+
* file would run whatever an earlier scripted turn wrote into it. On
|
|
137
|
+
* `globalThis` for the same reason -- a re-imported module would start over.
|
|
138
|
+
*/
|
|
139
|
+
interface ScriptState {
|
|
140
|
+
loaded: boolean;
|
|
141
|
+
turns: ScriptTurn[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const STATE_KEY = Symbol.for("loom.modelScript.state");
|
|
145
|
+
|
|
146
|
+
function processState(): ScriptState {
|
|
147
|
+
const g = globalThis as { [STATE_KEY]?: ScriptState };
|
|
148
|
+
return (g[STATE_KEY] ??= { loaded: false, turns: [] });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Test reset: forget what this process read. */
|
|
152
|
+
export function resetModelScriptState(): void {
|
|
153
|
+
const state = processState();
|
|
154
|
+
state.loaded = false;
|
|
155
|
+
state.turns = [];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The faux provider plus the queue it reads from. Every model call takes the
|
|
160
|
+
* next turn and queues itself again, so the provider never runs dry.
|
|
161
|
+
*/
|
|
162
|
+
export function createScriptedModel(state: ScriptState = { loaded: false, turns: [] }): {
|
|
163
|
+
faux: FauxProviderHandle;
|
|
164
|
+
load: (turns: ScriptTurn[]) => void;
|
|
165
|
+
} {
|
|
166
|
+
const faux = fauxProvider({
|
|
167
|
+
provider: MODEL_SCRIPT_PROVIDER,
|
|
168
|
+
api: MODEL_SCRIPT_PROVIDER,
|
|
169
|
+
models: [
|
|
170
|
+
{
|
|
171
|
+
id: MODEL_SCRIPT_MODEL_ID,
|
|
172
|
+
name: "Scripted model (ORBIT_MODEL_SCRIPT)",
|
|
173
|
+
reasoning: false,
|
|
174
|
+
input: ["text"],
|
|
175
|
+
contextWindow: 200_000,
|
|
176
|
+
maxTokens: 16_384,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
const step = (): AssistantMessage => {
|
|
181
|
+
faux.appendResponses([step]);
|
|
182
|
+
return turnToMessage(state.turns.shift());
|
|
183
|
+
};
|
|
184
|
+
faux.setResponses([step]);
|
|
185
|
+
return {
|
|
186
|
+
faux,
|
|
187
|
+
load: (turns) => {
|
|
188
|
+
state.turns = turns.map((t) => structuredClone(t));
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Big enough for any scenario, small enough that a mistake can't stall startup. */
|
|
194
|
+
const MAX_SCRIPT_BYTES = 1 << 20;
|
|
195
|
+
|
|
196
|
+
export interface ScriptRead {
|
|
197
|
+
/** Relative to the real session directory; null when refused. */
|
|
198
|
+
file: string | null;
|
|
199
|
+
parsed: ParsedScript;
|
|
200
|
+
rejected?: "missing" | "outside-session-dir" | "not-a-file" | "too-large" | "unreadable";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Read the script once, through one file descriptor, from a regular file that
|
|
205
|
+
* really is inside the session directory.
|
|
206
|
+
*
|
|
207
|
+
* Resolved before it is opened, then opened with `O_NOFOLLOW`, so swapping the
|
|
208
|
+
* checked file for a symlink between the containment check and the read fails
|
|
209
|
+
* the open instead of reading elsewhere; the content comes from that same
|
|
210
|
+
* descriptor. `O_NONBLOCK` and the `isFile` check refuse a named pipe, which
|
|
211
|
+
* would otherwise let a writer supply lines after startup, or never close and
|
|
212
|
+
* hang it. (On Windows neither flag exists and both are 0; the check on the
|
|
213
|
+
* descriptor still refuses anything that isn't a file.)
|
|
214
|
+
*/
|
|
215
|
+
export function readModelScript(sessionDir: string, configured: string): ScriptRead {
|
|
216
|
+
const empty: ParsedScript = { turns: [], skipped: 0 };
|
|
217
|
+
const root = fs.realpathSync(sessionDir);
|
|
218
|
+
let real: string;
|
|
219
|
+
try {
|
|
220
|
+
real = fs.realpathSync(path.resolve(sessionDir, configured));
|
|
221
|
+
} catch {
|
|
222
|
+
return { file: null, parsed: empty, rejected: "missing" };
|
|
223
|
+
}
|
|
224
|
+
if (!real.startsWith(root + path.sep)) {
|
|
225
|
+
return { file: null, parsed: empty, rejected: "outside-session-dir" };
|
|
226
|
+
}
|
|
227
|
+
const flags =
|
|
228
|
+
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0);
|
|
229
|
+
let fd: number;
|
|
230
|
+
try {
|
|
231
|
+
fd = fs.openSync(real, flags);
|
|
232
|
+
} catch {
|
|
233
|
+
return { file: null, parsed: empty, rejected: "unreadable" };
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const stat = fs.fstatSync(fd);
|
|
237
|
+
if (!stat.isFile()) return { file: null, parsed: empty, rejected: "not-a-file" };
|
|
238
|
+
// O_NOFOLLOW covers the last component only. A parent directory swapped
|
|
239
|
+
// for a symlink between the realpath and the open would still be
|
|
240
|
+
// followed, so the path has to resolve to the same place after the open,
|
|
241
|
+
// and to the file this descriptor has open.
|
|
242
|
+
let again: fs.Stats | null = null;
|
|
243
|
+
try {
|
|
244
|
+
if (fs.realpathSync(real) === real) again = fs.statSync(real);
|
|
245
|
+
} catch {
|
|
246
|
+
// gone or moved: refused below
|
|
247
|
+
}
|
|
248
|
+
if (!again || again.dev !== stat.dev || again.ino !== stat.ino) {
|
|
249
|
+
return { file: null, parsed: empty, rejected: "outside-session-dir" };
|
|
250
|
+
}
|
|
251
|
+
if (stat.size > MAX_SCRIPT_BYTES) {
|
|
252
|
+
return { file: null, parsed: empty, rejected: "too-large" };
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
file: path.relative(root, real),
|
|
256
|
+
parsed: parseModelScript(fs.readFileSync(fd, "utf-8")),
|
|
257
|
+
};
|
|
258
|
+
} finally {
|
|
259
|
+
fs.closeSync(fd);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const SCRIPTED_REF = `${MODEL_SCRIPT_PROVIDER}/${MODEL_SCRIPT_MODEL_ID}`;
|
|
264
|
+
|
|
265
|
+
function logRow(kind: string, payload: Record<string, unknown>): void {
|
|
266
|
+
const notebookPath = getNotebookPath();
|
|
267
|
+
if (!notebookPath) return;
|
|
268
|
+
appendActivityEvent(path.dirname(notebookPath), {
|
|
269
|
+
timestamp: new Date().toISOString(),
|
|
270
|
+
kind,
|
|
271
|
+
source: "model-script",
|
|
272
|
+
payload,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function registerModelScript(pi: ExtensionAPI): void {
|
|
277
|
+
const state = processState();
|
|
278
|
+
const scripted = createScriptedModel(state);
|
|
279
|
+
pi.registerProvider(scripted.faux.provider);
|
|
280
|
+
const isScripted = (m: { provider?: string; id?: string } | undefined) =>
|
|
281
|
+
m?.provider === MODEL_SCRIPT_PROVIDER && m?.id === MODEL_SCRIPT_MODEL_ID;
|
|
282
|
+
|
|
283
|
+
const select = async (): Promise<boolean> => {
|
|
284
|
+
try {
|
|
285
|
+
if (await pi.setModel(scripted.faux.getModel())) return true;
|
|
286
|
+
// A native provider's auth state can lag its registration by a tick.
|
|
287
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
288
|
+
return await pi.setModel(scripted.faux.getModel());
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
pi.on("session_start", async () => {
|
|
295
|
+
const configured = readEnv("MODEL_SCRIPT")?.trim();
|
|
296
|
+
if (!configured) return;
|
|
297
|
+
// Selected before anything can go wrong with the file, so a bad script
|
|
298
|
+
// never leaves the session on a real model.
|
|
299
|
+
const selected = await select();
|
|
300
|
+
if (state.loaded) {
|
|
301
|
+
if (!selected) logRow("model.script.unselected", { model: SCRIPTED_REF });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
state.loaded = true;
|
|
305
|
+
|
|
306
|
+
const notebookPath = getNotebookPath();
|
|
307
|
+
if (!notebookPath) return;
|
|
308
|
+
const read = readModelScript(path.dirname(notebookPath), configured);
|
|
309
|
+
scripted.load(read.parsed.turns);
|
|
310
|
+
logRow("model.script", {
|
|
311
|
+
// Never the configured spelling of a refused path: it can name
|
|
312
|
+
// somewhere outside the session.
|
|
313
|
+
file: read.file,
|
|
314
|
+
entries: read.parsed.turns.length,
|
|
315
|
+
skipped: read.parsed.skipped,
|
|
316
|
+
...(read.rejected ? { rejected: read.rejected } : {}),
|
|
317
|
+
model: SCRIPTED_REF,
|
|
318
|
+
selected,
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Anything that moves the session off the scripted model -- the picker, an
|
|
323
|
+
// RPC, a restore -- is moved back.
|
|
324
|
+
pi.on("model_select", async (event) => {
|
|
325
|
+
if (isScripted(event.model)) return;
|
|
326
|
+
const selected = await select();
|
|
327
|
+
logRow("model.script.reselected", {
|
|
328
|
+
from: `${event.model?.provider}/${event.model?.id}`,
|
|
329
|
+
source: event.source,
|
|
330
|
+
selected,
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// The backstop. The faux provider never calls `onPayload`, so while the
|
|
335
|
+
// seam is on, a provider request reaching this hook is a real one, however
|
|
336
|
+
// it got selected. Abort it before it is sent.
|
|
337
|
+
pi.on("before_provider_request", async (_event, ctx) => {
|
|
338
|
+
ctx.abort();
|
|
339
|
+
logRow("model.script.blocked", {
|
|
340
|
+
model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null,
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** For tools that call a provider directly, outside the session's model. */
|
|
346
|
+
export function modelScriptRefusal(tool: string): string | null {
|
|
347
|
+
return readEnv("MODEL_SCRIPT")?.trim()
|
|
348
|
+
? `${tool} is unavailable while ORBIT_MODEL_SCRIPT is set: it calls a model directly, ` +
|
|
349
|
+
`and the scripted session must never reach a real one.`
|
|
350
|
+
: null;
|
|
351
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a `notebook_anchor` is allowed to point at.
|
|
3
|
+
*
|
|
4
|
+
* `galaxy_invocation_record` / `galaxy_job_record` take a `notebookAnchor` and
|
|
5
|
+
* write it into the block verbatim. Nothing checked it, so a model that typed
|
|
6
|
+
* `plan-1-step-3` where the notebook says `plan-a-step-1` produced a block that
|
|
7
|
+
* binds to no step at all: the Activity panel still shows the run, the poller
|
|
8
|
+
* still advances it, and the evidence gate -- which looks a step's anchor up
|
|
9
|
+
* among the blocks (`findContradictions`) -- silently has nothing to compare.
|
|
10
|
+
* A binding that quietly points nowhere is worse than a rejected call, because
|
|
11
|
+
* every downstream check reads as "no opinion" rather than "broken".
|
|
12
|
+
*
|
|
13
|
+
* Three things count as an anchor, matching what the plan convention
|
|
14
|
+
* (`context.ts`) teaches and what `init-gate.ts` parses:
|
|
15
|
+
*
|
|
16
|
+
* - an explicit `{#plan-a-step-1}` marker, which is what steps carry;
|
|
17
|
+
* - a plan step's positional address, `plan-<letter>-step-<n>`, derived for
|
|
18
|
+
* steps that carry no explicit marker. That is the shape the convention
|
|
19
|
+
* would have given them, and it is the only address available on the
|
|
20
|
+
* Llama-4 path, where `buildPlanConventionBlock({omitAnchors: true})`
|
|
21
|
+
* deliberately tells the model *not* to write `{#...}` because the litellm
|
|
22
|
+
* proxy in front of it reads a curly brace as a tool-call boundary, and
|
|
23
|
+
* then tells it to say "Plan A step 2" instead;
|
|
24
|
+
* - a markdown heading, via its GitHub-style slug, for a notebook whose
|
|
25
|
+
* sections are the only structure it has.
|
|
26
|
+
*
|
|
27
|
+
* Fenced and indented code is excluded, for the reason `parsePlanSteps`
|
|
28
|
+
* excludes fences: a plan draft pasted inside a ```plan fence is quoted, not
|
|
29
|
+
* asserted, and the `loom-invocation` blocks are themselves fences. Without
|
|
30
|
+
* that, a block's own `notebook_anchor:` line would make every later record
|
|
31
|
+
* call for that anchor validate against a record we wrote ourselves.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** `{#some-id}` -- same shape `evidence-gate.ts` reads off a step line. */
|
|
35
|
+
const ANCHOR = /\{#([^}]+)\}/g;
|
|
36
|
+
/** Non-global twin, for the one place that only asks whether there is one. */
|
|
37
|
+
const HAS_ANCHOR = /\{#[^}]+\}/;
|
|
38
|
+
/** A markdown ATX heading, indentable by up to three spaces like any block. */
|
|
39
|
+
const HEADING = /^ {0,3}(#{1,6})\s+(.*)$/;
|
|
40
|
+
/** `## Plan A: Title [routing]` -- the same shape init-gate.ts parses. */
|
|
41
|
+
const PLAN_HEADING = /^ {0,3}##\s+Plan\s+([^:]+):/i;
|
|
42
|
+
const ANY_H2 = /^ {0,3}##\s+/;
|
|
43
|
+
/** A plan step checkbox line, with its optional leading ordinal. */
|
|
44
|
+
const STEP_LINE = /^\s*-\s+\[[ xX!]\]\s+(.*)$/;
|
|
45
|
+
const STEP_ORDINAL = /^(\d+)\.\s*/;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A fence opener: up to three spaces of indent, optionally behind a list
|
|
49
|
+
* marker, then three or more backticks or tildes.
|
|
50
|
+
*
|
|
51
|
+
* The list-marker branch is not pedantry. `- ```plan` opens a fence inside a
|
|
52
|
+
* list item, and a scanner that only looks for a fence at the start of a line
|
|
53
|
+
* walks straight past it and collects every anchor in the quoted plan that
|
|
54
|
+
* follows.
|
|
55
|
+
*/
|
|
56
|
+
const FENCE_OPEN = /^ {0,3}(?:(?:[-*+]|\d{1,9}[.)])\s+)?(`{3,}|~{3,})/;
|
|
57
|
+
/** Four spaces (or a tab) of indent is an indented code block, not prose. */
|
|
58
|
+
const INDENTED_CODE = /^(?: {4}|\t)/;
|
|
59
|
+
|
|
60
|
+
/** How many anchors a rejection names before it starts summarizing. */
|
|
61
|
+
const MAX_LISTED_ANCHORS = 20;
|
|
62
|
+
|
|
63
|
+
export interface NotebookAnchors {
|
|
64
|
+
/** Explicit `{#id}` markers, in document order, spelled as written. */
|
|
65
|
+
explicit: string[];
|
|
66
|
+
/** Positional addresses for plan steps that carry no explicit marker. */
|
|
67
|
+
steps: string[];
|
|
68
|
+
/** Slugs derived from headings, in document order, GitHub-style. */
|
|
69
|
+
headings: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type AnchorResolution =
|
|
73
|
+
| { kind: "resolved"; anchor: string }
|
|
74
|
+
| { kind: "unknown" }
|
|
75
|
+
/** Two anchors differ only in case and the input matched neither exactly. */
|
|
76
|
+
| { kind: "ambiguous"; candidates: string[] };
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Slugify heading text the way GitHub does: drop the inline markup, lowercase,
|
|
80
|
+
* strip punctuation, spaces to hyphens. `## Plan A: chrM Variant Calling
|
|
81
|
+
* [remote]` becomes `plan-a-chrm-variant-calling-remote`.
|
|
82
|
+
*/
|
|
83
|
+
export function slugifyHeading(text: string): string {
|
|
84
|
+
return text
|
|
85
|
+
.replace(ANCHOR, "")
|
|
86
|
+
.replace(/[*_`~]/g, "")
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, "")
|
|
89
|
+
.trim()
|
|
90
|
+
.replace(/\s+/g, "-");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Walk the notebook a line at a time, handing back only the lines a reader
|
|
95
|
+
* would see as prose -- no fenced blocks, no indented code.
|
|
96
|
+
*
|
|
97
|
+
* Fence tracking remembers the marker it opened with and closes only on the
|
|
98
|
+
* same character, at least as long, with nothing but whitespace after it. A
|
|
99
|
+
* symmetric "any three backticks or tildes toggles" rule gets this wrong in
|
|
100
|
+
* both directions: `~~~` inside a ``` block ends the quote early and exposes
|
|
101
|
+
* whatever follows, and a stray ``` inside an indented code sample swallows the
|
|
102
|
+
* rest of the file.
|
|
103
|
+
*/
|
|
104
|
+
function* proseLines(content: string): Generator<string> {
|
|
105
|
+
let fence: { marker: string; length: number } | null = null;
|
|
106
|
+
for (const line of content.split("\n")) {
|
|
107
|
+
if (fence) {
|
|
108
|
+
const close = line.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
109
|
+
if (close && close[1][0] === fence.marker && close[1].length >= fence.length) {
|
|
110
|
+
fence = null;
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (INDENTED_CODE.test(line)) continue;
|
|
115
|
+
const open = line.match(FENCE_OPEN);
|
|
116
|
+
if (open) {
|
|
117
|
+
fence = { marker: open[1][0], length: open[1].length };
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
yield line;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Every anchor the notebook offers, split by how it was written. */
|
|
125
|
+
export function collectNotebookAnchors(content: string): NotebookAnchors {
|
|
126
|
+
const explicit: string[] = [];
|
|
127
|
+
const steps: string[] = [];
|
|
128
|
+
const headings: string[] = [];
|
|
129
|
+
// GitHub disambiguates repeated heading slugs with -1, -2, ... Mirroring it
|
|
130
|
+
// means the second `## Results` is addressable as `results-1` rather than
|
|
131
|
+
// being silently unreachable behind the first.
|
|
132
|
+
const headingCounts = new Map<string, number>();
|
|
133
|
+
let planKey: string | null = null;
|
|
134
|
+
let stepIndex = 0;
|
|
135
|
+
|
|
136
|
+
for (const line of proseLines(content)) {
|
|
137
|
+
for (const m of line.matchAll(ANCHOR)) {
|
|
138
|
+
const id = m[1].trim();
|
|
139
|
+
if (id) explicit.push(id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const heading = line.match(HEADING);
|
|
143
|
+
if (heading) {
|
|
144
|
+
const plan = line.match(PLAN_HEADING);
|
|
145
|
+
if (plan) {
|
|
146
|
+
planKey = slugifyHeading(plan[1]);
|
|
147
|
+
stepIndex = 0;
|
|
148
|
+
} else if (ANY_H2.test(line)) {
|
|
149
|
+
// Any other h2 ends the plan section, same rule init-gate.ts uses.
|
|
150
|
+
planKey = null;
|
|
151
|
+
}
|
|
152
|
+
const slug = slugifyHeading(heading[2]);
|
|
153
|
+
if (slug) {
|
|
154
|
+
const seen = headingCounts.get(slug) ?? 0;
|
|
155
|
+
headingCounts.set(slug, seen + 1);
|
|
156
|
+
headings.push(seen === 0 ? slug : `${slug}-${seen}`);
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const step = line.match(STEP_LINE);
|
|
162
|
+
if (!step || !planKey) continue;
|
|
163
|
+
const text = step[1];
|
|
164
|
+
stepIndex++;
|
|
165
|
+
// A step that names itself is addressed by that name; deriving a second
|
|
166
|
+
// address for it would let a block record an anchor the evidence gate can
|
|
167
|
+
// never match against the step it belongs to.
|
|
168
|
+
if (HAS_ANCHOR.test(text)) continue;
|
|
169
|
+
// Prefer the ordinal the step writes over its position, since that is the
|
|
170
|
+
// number the convention's anchor would carry.
|
|
171
|
+
const ordinal = text.match(STEP_ORDINAL)?.[1] ?? String(stepIndex);
|
|
172
|
+
steps.push(`plan-${planKey}-step-${ordinal}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { explicit, steps, headings };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A checkbox line, under the anchor a registry attempt would be bound to. */
|
|
179
|
+
export interface CheckboxStep {
|
|
180
|
+
anchor: string;
|
|
181
|
+
state: " " | "x" | "!";
|
|
182
|
+
text: string;
|
|
183
|
+
/** False for a positional address, which follows the line's position, not the line. */
|
|
184
|
+
explicit: boolean;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const CHECKBOX_LINE = /^\s*-\s+\[([ xX!])\]\s+(.*)$/;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Every checkbox line a reader would see, keyed the way `collectNotebookAnchors`
|
|
191
|
+
* addresses steps: each explicit `{#id}` on the line wherever it sits in the
|
|
192
|
+
* file, else the plan step's positional address. Unlike `parsePlanSteps` in
|
|
193
|
+
* the evidence gate, an explicitly anchored checkbox counts outside a
|
|
194
|
+
* `## Plan` section too -- renaming the heading above a step must not make its
|
|
195
|
+
* checkbox stop being that step's.
|
|
196
|
+
*/
|
|
197
|
+
export function collectCheckboxSteps(content: string): CheckboxStep[] {
|
|
198
|
+
const out: CheckboxStep[] = [];
|
|
199
|
+
let planKey: string | null = null;
|
|
200
|
+
let stepIndex = 0;
|
|
201
|
+
for (const line of proseLines(content)) {
|
|
202
|
+
if (HEADING.test(line)) {
|
|
203
|
+
const plan = line.match(PLAN_HEADING);
|
|
204
|
+
if (plan) {
|
|
205
|
+
planKey = slugifyHeading(plan[1]);
|
|
206
|
+
stepIndex = 0;
|
|
207
|
+
} else if (ANY_H2.test(line)) {
|
|
208
|
+
planKey = null;
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const box = line.match(CHECKBOX_LINE);
|
|
213
|
+
if (!box) continue;
|
|
214
|
+
const state = (box[1] === "X" ? "x" : box[1]) as CheckboxStep["state"];
|
|
215
|
+
const text = box[2];
|
|
216
|
+
if (planKey && STEP_LINE.test(line)) stepIndex++;
|
|
217
|
+
const ids = [...text.matchAll(ANCHOR)].map((m) => m[1].trim()).filter(Boolean);
|
|
218
|
+
if (ids.length > 0) {
|
|
219
|
+
for (const anchor of ids) out.push({ anchor, state, text: text.trim(), explicit: true });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!planKey) continue;
|
|
223
|
+
const ordinal = text.match(STEP_ORDINAL)?.[1] ?? String(stepIndex);
|
|
224
|
+
out.push({
|
|
225
|
+
anchor: `plan-${planKey}-step-${ordinal}`,
|
|
226
|
+
state,
|
|
227
|
+
text: text.trim(),
|
|
228
|
+
explicit: false,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The anchors a record call may name, de-duplicated, in the order a rejection
|
|
236
|
+
* should offer them: what the author wrote, then step addresses, then sections.
|
|
237
|
+
*
|
|
238
|
+
* De-duplication is case-sensitive on purpose. Two spellings of one anchor are
|
|
239
|
+
* a real collision the author should see, not noise to fold away.
|
|
240
|
+
*/
|
|
241
|
+
export function listNotebookAnchors(content: string): string[] {
|
|
242
|
+
const { explicit, steps, headings } = collectNotebookAnchors(content);
|
|
243
|
+
const seen = new Set<string>();
|
|
244
|
+
const out: string[] = [];
|
|
245
|
+
for (const anchor of [...explicit, ...steps, ...headings]) {
|
|
246
|
+
if (seen.has(anchor)) continue;
|
|
247
|
+
seen.add(anchor);
|
|
248
|
+
out.push(anchor);
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Strip the spellings a model copies out of the markdown -- `{#step-1}`,
|
|
255
|
+
* `#step-1` -- down to the bare id.
|
|
256
|
+
*/
|
|
257
|
+
function normalizeAnchorInput(raw: string): string {
|
|
258
|
+
let value = raw.trim();
|
|
259
|
+
const braced = value.match(/^\{(.+)\}$/);
|
|
260
|
+
if (braced) value = braced[1];
|
|
261
|
+
return value.replace(/^#+/, "").trim();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Resolve `input` against the notebook, returning the anchor **as the notebook
|
|
266
|
+
* spells it**.
|
|
267
|
+
*
|
|
268
|
+
* Canonicalizing rather than echoing the input back matters: the evidence gate
|
|
269
|
+
* matches a block's `notebook_anchor` against a step's anchor verbatim, so
|
|
270
|
+
* storing a case-drifted copy would record a block bound to nothing while
|
|
271
|
+
* looking fine in the file. For the same reason a case-insensitive match that
|
|
272
|
+
* two different anchors both answer to is refused rather than guessed -- the
|
|
273
|
+
* guess would gate the wrong step.
|
|
274
|
+
*
|
|
275
|
+
* `Plan A step 2` is accepted as well as `plan-a-step-2`, because that is the
|
|
276
|
+
* form `anchorGuidance` asks for when explicit anchors are suppressed.
|
|
277
|
+
*/
|
|
278
|
+
export function resolveNotebookAnchor(content: string, input: string): AnchorResolution {
|
|
279
|
+
const wanted = normalizeAnchorInput(input);
|
|
280
|
+
if (!wanted) return { kind: "unknown" };
|
|
281
|
+
const { explicit, steps, headings } = collectNotebookAnchors(content);
|
|
282
|
+
// The prose form the Llama-4 guidance teaches slugs to the address form.
|
|
283
|
+
const candidates = [wanted, slugifyHeading(wanted)].filter(Boolean);
|
|
284
|
+
|
|
285
|
+
for (const candidate of candidates) {
|
|
286
|
+
for (const anchor of explicit) {
|
|
287
|
+
if (anchor === candidate) return { kind: "resolved", anchor };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (const candidate of candidates) {
|
|
291
|
+
const lowered = candidate.toLowerCase();
|
|
292
|
+
const hits = [...new Set(explicit.filter((a) => a.toLowerCase() === lowered))];
|
|
293
|
+
if (hits.length > 1) return { kind: "ambiguous", candidates: hits };
|
|
294
|
+
if (hits.length === 1) return { kind: "resolved", anchor: hits[0] };
|
|
295
|
+
}
|
|
296
|
+
for (const candidate of candidates) {
|
|
297
|
+
const lowered = candidate.toLowerCase();
|
|
298
|
+
for (const anchor of [...steps, ...headings]) {
|
|
299
|
+
if (anchor === lowered) return { kind: "resolved", anchor };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return { kind: "unknown" };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The refusal a record tool hands back, naming what the notebook does have. */
|
|
306
|
+
export function unknownAnchorMessage(input: string, anchors: string[]): string {
|
|
307
|
+
const wanted = normalizeAnchorInput(input) || input.trim();
|
|
308
|
+
if (anchors.length === 0) {
|
|
309
|
+
return (
|
|
310
|
+
`Unknown notebook anchor "${wanted}": notebook.md has no headings, plan steps or ` +
|
|
311
|
+
`{#anchor} markers to bind to, so this block would point at nothing. Write the step ` +
|
|
312
|
+
`first -- \`- [ ] 1. **Step name** {#${wanted}} -- description\` -- then record against it.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const shown = anchors.slice(0, MAX_LISTED_ANCHORS);
|
|
316
|
+
const extra = anchors.length - shown.length;
|
|
317
|
+
const tail = extra > 0 ? `, and ${extra} more` : "";
|
|
318
|
+
return (
|
|
319
|
+
`Unknown notebook anchor "${wanted}": nothing in notebook.md resolves to it, so this ` +
|
|
320
|
+
`block would point at nothing. Anchors that do exist: ${shown.join(", ")}${tail}. ` +
|
|
321
|
+
`Record against one of those, or add {#${wanted}} to the step you mean first.`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** The refusal when the notebook spells one anchor two ways. */
|
|
326
|
+
export function ambiguousAnchorMessage(input: string, candidates: string[]): string {
|
|
327
|
+
const wanted = normalizeAnchorInput(input) || input.trim();
|
|
328
|
+
return (
|
|
329
|
+
`Ambiguous notebook anchor "${wanted}": notebook.md carries ${candidates.join(" and ")}, ` +
|
|
330
|
+
`which differ only in case, and binding to the wrong one would attach this run to the ` +
|
|
331
|
+
`wrong step. Record against one of them exactly as it is written, or give the two steps ` +
|
|
332
|
+
`distinct anchors.`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Thrown by the record tools' write path when the anchor doesn't resolve. */
|
|
337
|
+
export class UnknownAnchorError extends Error {
|
|
338
|
+
constructor(message: string) {
|
|
339
|
+
super(message);
|
|
340
|
+
this.name = "UnknownAnchorError";
|
|
341
|
+
}
|
|
342
|
+
}
|