@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.
Files changed (451) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/LICENSE +21 -0
  3. package/README.md +760 -5
  4. package/bin/legacy-mcp-config.js +363 -0
  5. package/bin/orbit.js +810 -0
  6. package/bin/pi-extension-path.d.ts +4 -0
  7. package/bin/pi-extension-path.js +44 -0
  8. package/bin/provider-auth.d.ts +22 -0
  9. package/bin/provider-auth.js +77 -0
  10. package/bin/thinking-pref.js +38 -0
  11. package/bin/update-check.d.ts +15 -0
  12. package/bin/update-check.js +137 -0
  13. package/extensions/cli-update/index.ts +32 -0
  14. package/extensions/orbit/activity-hooks.ts +141 -0
  15. package/extensions/orbit/activity.ts +116 -0
  16. package/extensions/orbit/agent-dir.ts +11 -0
  17. package/extensions/orbit/auto-resume.ts +352 -0
  18. package/extensions/orbit/config.ts +3 -0
  19. package/extensions/orbit/confusables.ts +1 -0
  20. package/extensions/orbit/context.ts +957 -0
  21. package/extensions/orbit/dashboard-commands.ts +191 -0
  22. package/extensions/orbit/dashboard-store.ts +473 -0
  23. package/extensions/orbit/dashboard-tools.ts +1185 -0
  24. package/extensions/orbit/empty-completion.ts +99 -0
  25. package/extensions/orbit/evidence-gate.ts +1276 -0
  26. package/extensions/orbit/evidence-override-command.ts +294 -0
  27. package/extensions/orbit/exec-guard/bash-risk.ts +1408 -0
  28. package/extensions/orbit/exec-guard/gate.ts +181 -0
  29. package/extensions/orbit/exec-guard/guardian-config.ts +54 -0
  30. package/extensions/orbit/exec-guard/index.ts +1 -0
  31. package/extensions/orbit/exec-guard/model-tier.ts +36 -0
  32. package/extensions/orbit/exec-guard/path-jail.ts +54 -0
  33. package/extensions/orbit/exec-guard/policy.ts +342 -0
  34. package/extensions/orbit/exec-guard/runtime-state.ts +15 -0
  35. package/extensions/orbit/exec-guard/sensitive-read.ts +525 -0
  36. package/extensions/orbit/exec-guard/types.ts +42 -0
  37. package/extensions/orbit/execution-commands.ts +134 -0
  38. package/extensions/orbit/feedback-command.ts +81 -0
  39. package/extensions/orbit/feedback.ts +96 -0
  40. package/extensions/orbit/galaxy-api.test.ts +122 -0
  41. package/extensions/orbit/galaxy-api.ts +606 -0
  42. package/extensions/orbit/galaxy-artifact-link-guidance.ts +5 -0
  43. package/extensions/orbit/galaxy-destructive-gate.ts +113 -0
  44. package/extensions/orbit/galaxy-enrich.ts +1108 -0
  45. package/extensions/orbit/galaxy-failure.ts +101 -0
  46. package/extensions/orbit/galaxy-fixture.ts +126 -0
  47. package/extensions/orbit/galaxy-history-guard.ts +233 -0
  48. package/extensions/orbit/galaxy-job-block.ts +347 -0
  49. package/extensions/orbit/galaxy-job-ops.ts +288 -0
  50. package/extensions/orbit/galaxy-live-source.ts +929 -0
  51. package/extensions/orbit/galaxy-markdown-adapter.ts +248 -0
  52. package/extensions/orbit/galaxy-op-policy.ts +332 -0
  53. package/extensions/orbit/galaxy-ops-tools.ts +326 -0
  54. package/extensions/orbit/galaxy-output-store.ts +80 -0
  55. package/extensions/orbit/galaxy-output.ts +346 -0
  56. package/extensions/orbit/galaxy-page-binding.ts +222 -0
  57. package/extensions/orbit/galaxy-page-markdown-guidance.ts +15 -0
  58. package/extensions/orbit/galaxy-page-ops.ts +159 -0
  59. package/extensions/orbit/galaxy-page-sync.test.ts +417 -0
  60. package/extensions/orbit/galaxy-page-sync.ts +318 -0
  61. package/extensions/orbit/galaxy-pages-api.ts +203 -0
  62. package/extensions/orbit/galaxy-pages-sync.ts +409 -0
  63. package/extensions/orbit/galaxy-poll-guard.ts +189 -0
  64. package/extensions/orbit/galaxy-poller.ts +706 -0
  65. package/extensions/orbit/galaxy-provenance.ts +623 -0
  66. package/extensions/orbit/galaxy-reconcile.ts +956 -0
  67. package/extensions/orbit/galaxy-recovery.ts +176 -0
  68. package/extensions/orbit/galaxy-repeat-guard.ts +55 -0
  69. package/extensions/orbit/galaxy-submission-capture.ts +707 -0
  70. package/extensions/orbit/galaxy-submission.ts +502 -0
  71. package/extensions/orbit/galaxy-tool-source.ts +78 -0
  72. package/extensions/orbit/galaxy-tool-wrap.ts +234 -0
  73. package/extensions/orbit/galaxy-udt-block.ts +127 -0
  74. package/extensions/orbit/galaxy-upload-tus.ts +334 -0
  75. package/extensions/orbit/galaxy-upload.ts +300 -0
  76. package/extensions/orbit/galaxy-version-capture.ts +65 -0
  77. package/extensions/orbit/git.ts +173 -0
  78. package/extensions/orbit/harness-block-fields.ts +597 -0
  79. package/extensions/orbit/index.ts +593 -0
  80. package/extensions/orbit/init-gate.ts +284 -0
  81. package/extensions/orbit/instructions-command.ts +136 -0
  82. package/extensions/orbit/invocation-failure-hint.ts +57 -0
  83. package/extensions/orbit/iwc-plan-check.ts +94 -0
  84. package/extensions/orbit/lesson-command.ts +303 -0
  85. package/extensions/orbit/lesson-hint.ts +245 -0
  86. package/extensions/orbit/lesson-nudge.ts +106 -0
  87. package/extensions/orbit/lessons/compose.ts +177 -0
  88. package/extensions/orbit/lessons/counters.ts +120 -0
  89. package/extensions/orbit/lessons/enabled.ts +116 -0
  90. package/extensions/orbit/lessons/matcher.ts +346 -0
  91. package/extensions/orbit/lessons/paths.ts +286 -0
  92. package/extensions/orbit/lessons/pi-event-contract.ts +105 -0
  93. package/extensions/orbit/lessons/propose-replay.ts +103 -0
  94. package/extensions/orbit/lessons/propose-tool.ts +222 -0
  95. package/extensions/orbit/lessons/propose.ts +502 -0
  96. package/extensions/orbit/lessons/replay.ts +89 -0
  97. package/extensions/orbit/lessons/reproduction-index.ts +83 -0
  98. package/extensions/orbit/lessons/search-tool.ts +99 -0
  99. package/extensions/orbit/lessons/search.ts +136 -0
  100. package/extensions/orbit/lessons/store.ts +415 -0
  101. package/extensions/orbit/lessons/text.ts +36 -0
  102. package/extensions/orbit/lessons/types.ts +69 -0
  103. package/extensions/orbit/lessons/user-dir.ts +24 -0
  104. package/extensions/orbit/lessons/user-lesson.ts +88 -0
  105. package/extensions/orbit/lessons/wrapper.ts +61 -0
  106. package/extensions/orbit/lessons-command.ts +147 -0
  107. package/extensions/orbit/local-exec.ts +39 -0
  108. package/extensions/orbit/mcp-output.ts +179 -0
  109. package/extensions/orbit/mcp-servers.test.ts +24 -0
  110. package/extensions/orbit/mcp-servers.ts +25 -0
  111. package/extensions/orbit/model-script.ts +351 -0
  112. package/extensions/orbit/notebook-anchors.ts +342 -0
  113. package/extensions/orbit/notebook-writer.ts +863 -0
  114. package/extensions/orbit/observation-allowlists.ts +1109 -0
  115. package/extensions/orbit/observation-replay.ts +148 -0
  116. package/extensions/orbit/observation-triggers.ts +563 -0
  117. package/extensions/orbit/observation-ui.ts +175 -0
  118. package/extensions/orbit/observations-command.ts +421 -0
  119. package/extensions/orbit/observations-config.ts +200 -0
  120. package/extensions/orbit/observations.ts +1381 -0
  121. package/extensions/orbit/profiles.ts +270 -0
  122. package/extensions/orbit/progress-updates.ts +95 -0
  123. package/extensions/orbit/proposal-commands.ts +882 -0
  124. package/extensions/orbit/provenance-fs-file.ts +59 -0
  125. package/extensions/orbit/provenance-fs.ts +32 -0
  126. package/extensions/orbit/provenance-memory-fs.ts +59 -0
  127. package/extensions/orbit/record-store-file.ts +177 -0
  128. package/extensions/orbit/record-store-page.ts +77 -0
  129. package/extensions/orbit/record-store-seam.ts +47 -0
  130. package/extensions/orbit/record-store.ts +149 -0
  131. package/extensions/orbit/registry-carrier.ts +107 -0
  132. package/extensions/orbit/registry-eligibility.ts +95 -0
  133. package/extensions/orbit/registry-evaluation.ts +403 -0
  134. package/extensions/orbit/registry-evaluator.ts +519 -0
  135. package/extensions/orbit/registry-import.ts +91 -0
  136. package/extensions/orbit/registry-lock.ts +221 -0
  137. package/extensions/orbit/registry-memory-fs.ts +42 -0
  138. package/extensions/orbit/registry-page-carrier.ts +103 -0
  139. package/extensions/orbit/registry-proposal.ts +1348 -0
  140. package/extensions/orbit/registry-runtime.ts +234 -0
  141. package/extensions/orbit/registry-schema.ts +735 -0
  142. package/extensions/orbit/registry-submitter.ts +59 -0
  143. package/extensions/orbit/registry-templates.ts +346 -0
  144. package/extensions/orbit/registry.ts +476 -0
  145. package/extensions/orbit/sandbox/index.ts +120 -0
  146. package/extensions/orbit/sandbox/sandbox-bash.ts +93 -0
  147. package/extensions/orbit/sandbox/sandbox-config.ts +87 -0
  148. package/extensions/orbit/sandbox/sandbox-init-message.ts +28 -0
  149. package/extensions/orbit/secret-redaction.ts +247 -0
  150. package/extensions/orbit/session-index/cwd.ts +18 -0
  151. package/extensions/orbit/session-index/db.ts +56 -0
  152. package/extensions/orbit/session-index/indexer.ts +154 -0
  153. package/extensions/orbit/session-index/is-enabled.ts +17 -0
  154. package/extensions/orbit/session-index/parse.ts +254 -0
  155. package/extensions/orbit/session-index/query.ts +209 -0
  156. package/extensions/orbit/session-index/schema.ts +72 -0
  157. package/extensions/orbit/session-index/tools.ts +152 -0
  158. package/extensions/orbit/session-lifecycle.ts +321 -0
  159. package/extensions/orbit/skill-triggers.ts +159 -0
  160. package/extensions/orbit/skills-command.ts +48 -0
  161. package/extensions/orbit/skills-discovery.ts +435 -0
  162. package/extensions/orbit/skills.ts +84 -0
  163. package/extensions/orbit/sra-import-gate.ts +77 -0
  164. package/extensions/orbit/state.ts +265 -0
  165. package/extensions/orbit/submission-replay.ts +147 -0
  166. package/extensions/orbit/sync-command.ts +157 -0
  167. package/extensions/orbit/teams/critic-parser.ts +93 -0
  168. package/extensions/orbit/teams/dispatcher.ts +210 -0
  169. package/extensions/orbit/teams/is-enabled.ts +22 -0
  170. package/extensions/orbit/teams/tool.ts +187 -0
  171. package/extensions/orbit/teams/types.ts +57 -0
  172. package/extensions/orbit/teams/validate.ts +42 -0
  173. package/extensions/orbit/tester-id-command.ts +133 -0
  174. package/extensions/orbit/tools-sync.ts +279 -0
  175. package/extensions/orbit/tools.ts +1300 -0
  176. package/extensions/orbit/types.ts +41 -0
  177. package/extensions/orbit/ui-bridge.ts +55 -0
  178. package/extensions/orbit/ulid.ts +60 -0
  179. package/extensions/orbit/user-instructions.ts +388 -0
  180. package/extensions/orbit/vendor/skills/_catalog.json +68 -0
  181. package/extensions/orbit/vendor/skills/_manifest.json +1106 -0
  182. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/SKILL.md +112 -0
  183. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_provenance.json +218 -0
  184. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_required_tools.json +33 -0
  185. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_verify.json +4 -0
  186. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/add.json +50 -0
  187. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-extract.json +51 -0
  188. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-next-step.json +42 -0
  189. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-validate.json +140 -0
  190. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/list.json +32 -0
  191. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/notes/galaxy-tool-job-failure-reference.md +137 -0
  192. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/notes/open-requirements-ledger.md +173 -0
  193. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/schemas/galaxy-tool-summary.schema.json +175 -0
  194. package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/schemas/galaxy-workflow-draft.schema.json +2727 -0
  195. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/SKILL.md +82 -0
  196. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_provenance.json +133 -0
  197. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_required_tools.json +16 -0
  198. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_verify.json +4 -0
  199. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/cli/draft-validate.json +140 -0
  200. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/notes/galaxy-workflow-draft-format.md +135 -0
  201. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/notes/open-requirements-ledger.md +173 -0
  202. package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/schemas/galaxy-workflow-draft.schema.json +2727 -0
  203. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/SKILL.md +84 -0
  204. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/_provenance.json +166 -0
  205. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/_verify.json +29 -0
  206. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/galaxy-workflow-testability-design.md +136 -0
  207. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
  208. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/iwc-test-data-conventions.md +308 -0
  209. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/planemo-asserts-idioms.md +246 -0
  210. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/planemo-workflow-test-architecture.md +151 -0
  211. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/galaxy-workflow-test-plan.schema.json +552 -0
  212. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/summary-galaxy-workflow.schema.json +687 -0
  213. package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/tests-format.schema.json +7328 -0
  214. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/SKILL.md +127 -0
  215. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_provenance.json +198 -0
  216. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_required_tools.json +16 -0
  217. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_verify.json +4 -0
  218. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/cli/convert.json +123 -0
  219. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/galaxy-data-flow-draft-contract.md +129 -0
  220. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
  221. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/iwc-test-data-conventions.md +308 -0
  222. package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/open-requirements-ledger.md +173 -0
  223. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/SKILL.md +73 -0
  224. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/_provenance.json +160 -0
  225. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/_verify.json +4 -0
  226. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-collection-semantics.md +43 -0
  227. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-collection-semantics.yml +1250 -0
  228. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-tool-job-failure-reference.md +137 -0
  229. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
  230. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
  231. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/nextflow-operators-to-galaxy-collection-recipes.md +98 -0
  232. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/planemo-asserts-idioms.md +246 -0
  233. package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/planemo-workflow-test-architecture.md +151 -0
  234. package/extensions/orbit/vendor/skills/discover-shed-tool/SKILL.md +174 -0
  235. package/extensions/orbit/vendor/skills/discover-shed-tool/_provenance.json +130 -0
  236. package/extensions/orbit/vendor/skills/discover-shed-tool/_required_tools.json +18 -0
  237. package/extensions/orbit/vendor/skills/discover-shed-tool/_verify.json +17 -0
  238. package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-revisions.json +48 -0
  239. package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-search.json +95 -0
  240. package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-versions.json +39 -0
  241. package/extensions/orbit/vendor/skills/discover-shed-tool/references/notes/component-tool-shed-search.md +230 -0
  242. package/extensions/orbit/vendor/skills/discover-shed-tool/references/schemas/galaxy-tool-discovery.schema.json +231 -0
  243. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/SKILL.md +93 -0
  244. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_provenance.json +201 -0
  245. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_required_tools.json +28 -0
  246. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_verify.json +17 -0
  247. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/cli/planemo.md +44 -0
  248. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/cli/validate-tests.json +40 -0
  249. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/galaxy-workflow-testability-design.md +136 -0
  250. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
  251. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/iwc-test-data-conventions.md +308 -0
  252. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/planemo-asserts-idioms.md +246 -0
  253. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/planemo-workflow-test-architecture.md +151 -0
  254. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/schemas/galaxy-workflow-test-plan.schema.json +552 -0
  255. package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/schemas/tests-format.schema.json +7328 -0
  256. package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/SKILL.md +81 -0
  257. package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/_provenance.json +93 -0
  258. package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/_verify.json +17 -0
  259. package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/references/notes/open-requirements-ledger.md +173 -0
  260. package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/references/schemas/summary-galaxy-workflow.schema.json +687 -0
  261. package/extensions/orbit/vendor/skills/pipeline-update-interview-to-galaxy/SKILL.md +81 -0
  262. package/extensions/orbit/vendor/skills/pipeline-update-interview-to-galaxy/_assembly.json +23 -0
  263. package/extensions/orbit/vendor/skills/run-workflow-test/SKILL.md +79 -0
  264. package/extensions/orbit/vendor/skills/run-workflow-test/_provenance.json +125 -0
  265. package/extensions/orbit/vendor/skills/run-workflow-test/_required_tools.json +28 -0
  266. package/extensions/orbit/vendor/skills/run-workflow-test/_verify.json +4 -0
  267. package/extensions/orbit/vendor/skills/run-workflow-test/references/cli/planemo.md +44 -0
  268. package/extensions/orbit/vendor/skills/run-workflow-test/references/cli/validate-tests.json +40 -0
  269. package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
  270. package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/planemo-asserts-idioms.md +246 -0
  271. package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/planemo-workflow-test-architecture.md +151 -0
  272. package/extensions/orbit/vendor/skills/run-workflow-test/references/schemas/tests-format.schema.json +7328 -0
  273. package/extensions/orbit/vendor/skills/skills/LICENSE +21 -0
  274. package/extensions/orbit/vendor/skills/skills/collection-manipulation/README.md +39 -0
  275. package/extensions/orbit/vendor/skills/skills/collection-manipulation/SKILL.md +892 -0
  276. package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/api-patterns.md +927 -0
  277. package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/apply-rules.md +1482 -0
  278. package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/test-patterns.md +996 -0
  279. package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/tools.md +609 -0
  280. package/extensions/orbit/vendor/skills/skills/galaxy-data-libraries/SKILL.md +170 -0
  281. package/extensions/orbit/vendor/skills/skills/galaxy-integration/README.md +162 -0
  282. package/extensions/orbit/vendor/skills/skills/galaxy-integration/SKILL.md +99 -0
  283. package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/README.md +119 -0
  284. package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/tool-checking.md +419 -0
  285. package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/workflow-testing.md +617 -0
  286. package/extensions/orbit/vendor/skills/skills/galaxy-integration/galaxy-integration.md +614 -0
  287. package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/SKILL.md +144 -0
  288. package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/average_col3.ipynb +68 -0
  289. package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/extract_sample_metadata.ipynb +190 -0
  290. package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/variant_annotation.ipynb +816 -0
  291. package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/vcp_variant_map.ipynb +443 -0
  292. package/extensions/orbit/vendor/skills/skills/galaxy-integration/scripts/README.md +165 -0
  293. package/extensions/orbit/vendor/skills/skills/galaxy-integration/scripts/galaxy_tool_checker.py +521 -0
  294. package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/SKILL.md +128 -0
  295. package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/gotchas.md +135 -0
  296. package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/history-access.md +132 -0
  297. package/extensions/orbit/vendor/skills/skills/reproduciblify/SKILL.md +146 -0
  298. package/extensions/orbit/vendor/skills/skills/reproduciblify/references/directives.yml +496 -0
  299. package/extensions/orbit/vendor/skills/skills/udt-authoring/SKILL.md +245 -0
  300. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/01-minimal-echo.yml +27 -0
  301. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/02-single-data-in-out.yml +27 -0
  302. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/03-multiple-data-js.yml +28 -0
  303. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/04-select-integer-boolean.yml +41 -0
  304. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/05-resource-requirements.yml +25 -0
  305. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/06-biocontainer-tool.yml +38 -0
  306. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/07-inline-python-script.yml +56 -0
  307. package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/08-configfile-script.yml +74 -0
  308. package/extensions/orbit/vendor/skills/skills/udt-authoring/references/common-mistakes.md +76 -0
  309. package/extensions/orbit/vendor/skills/skills/udt-authoring/references/schema-reference.md +155 -0
  310. package/extensions/orbit/vendor/skills/skills/udt-authoring/references/templating.md +196 -0
  311. package/extensions/orbit/vendor/skills/skills/udt-authoring/scripts/validate.py +109 -0
  312. package/extensions/orbit/vendor/skills/skills/workflow-reports/README.md +71 -0
  313. package/extensions/orbit/vendor/skills/skills/workflow-reports/SKILL.md +190 -0
  314. package/extensions/orbit/vendor/skills/skills/workflow-reports/examples/histology-staining.md +145 -0
  315. package/extensions/orbit/vendor/skills/skills/workflow-reports/examples/tissue-microarray-analysis.md +176 -0
  316. package/extensions/orbit/vendor/skills/skills/workflow-reports/references/directives.md +415 -0
  317. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/SKILL.md +98 -0
  318. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_provenance.json +93 -0
  319. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_required_tools.json +28 -0
  320. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_verify.json +17 -0
  321. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/convert.json +123 -0
  322. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/foundry.md +25 -0
  323. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/validate.json +142 -0
  324. package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/schemas/summary-galaxy-workflow.schema.json +687 -0
  325. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/SKILL.md +74 -0
  326. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_provenance.json +71 -0
  327. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_required_tools.json +16 -0
  328. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_verify.json +4 -0
  329. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/cli/validate.json +142 -0
  330. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
  331. package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/notes/galaxy-workflow-testability-design.md +136 -0
  332. package/extensions/orbit/vendor-skills.ts +283 -0
  333. package/extensions/orbit/workspace-state-dir.ts +46 -0
  334. package/extensions/orbit-handoff/index.ts +73 -0
  335. package/extensions/orbit-handoff/orbit-launcher.ts +142 -0
  336. package/extensions/whats-new/index.ts +98 -0
  337. package/lessons/LICENSE +17 -0
  338. package/lessons/README.md +117 -0
  339. package/lessons/SCHEMA.md +187 -0
  340. package/lessons/build-snapshot.mjs +311 -0
  341. package/lessons/data/downloaded-file-is-not-what-its-extension-says.md +61 -0
  342. package/lessons/galaxy-api/403-history-is-a-hard-stop.md +57 -0
  343. package/lessons/galaxy-api/collection-into-single-dataset-input.md +61 -0
  344. package/lessons/galaxy-api/connectedvalue-in-command-line.md +62 -0
  345. package/lessons/galaxy-api/hid-is-not-an-id.md +59 -0
  346. package/lessons/galaxy-api/invoke-workflow-inputs-not-params.md +60 -0
  347. package/lessons/galaxy-api/repeat-param-pipe-keys.md +63 -0
  348. package/lessons/galaxy-api/run-tool-returns-on-submit.md +61 -0
  349. package/lessons/galaxy-tools/reference-index-not-on-server.md +62 -0
  350. package/lessons/log.md +42 -0
  351. package/lessons/reproduction/input-population-mismatch.md +59 -0
  352. package/lessons/reproduction/methods-text-vs-executed-parameters.md +62 -0
  353. package/lessons/snapshot.json +1004 -0
  354. package/lessons/stats/de-contrast-direction-and-sample-labels.md +63 -0
  355. package/lessons/stats/na-coerced-to-zero-in-filters.md +59 -0
  356. package/lessons/validate.mjs +1219 -0
  357. package/package.json +103 -5
  358. package/shared/agent-prompt.d.ts +8 -0
  359. package/shared/agent-prompt.js +15 -0
  360. package/shared/approval-prompt.d.ts +4 -0
  361. package/shared/approval-prompt.js +61 -0
  362. package/shared/brain-env.d.ts +14 -0
  363. package/shared/brain-env.js +108 -0
  364. package/shared/brain-exit.d.ts +20 -0
  365. package/shared/brain-exit.js +149 -0
  366. package/shared/capability-profile.d.ts +16 -0
  367. package/shared/capability-profile.js +75 -0
  368. package/shared/config-source.d.ts +13 -0
  369. package/shared/config-source.js +17 -0
  370. package/shared/confusables.d.ts +3 -0
  371. package/shared/confusables.js +93 -0
  372. package/shared/custom-provider.d.ts +45 -0
  373. package/shared/custom-provider.js +169 -0
  374. package/shared/dashboard-contract.d.ts +123 -0
  375. package/shared/dashboard-contract.js +560 -0
  376. package/shared/dashboard-layout-store.d.ts +44 -0
  377. package/shared/dashboard-layout-store.js +281 -0
  378. package/shared/feedback-contract.d.ts +61 -0
  379. package/shared/feedback-contract.js +146 -0
  380. package/shared/galaxy-artifact-links.d.ts +30 -0
  381. package/shared/galaxy-artifact-links.js +166 -0
  382. package/shared/galaxy-credentials.d.ts +26 -0
  383. package/shared/galaxy-credentials.js +71 -0
  384. package/shared/galaxy-destructive.d.ts +21 -0
  385. package/shared/galaxy-destructive.js +269 -0
  386. package/shared/galaxy-guidance.d.ts +2 -0
  387. package/shared/galaxy-guidance.js +53 -0
  388. package/shared/galaxy-live-contract.d.ts +92 -0
  389. package/shared/galaxy-live-contract.js +180 -0
  390. package/shared/galaxy-page-cells.d.ts +2 -0
  391. package/shared/galaxy-page-cells.js +22 -0
  392. package/shared/galaxy-tool-family.d.ts +5 -0
  393. package/shared/galaxy-tool-family.js +27 -0
  394. package/shared/galaxy-url.d.ts +1 -0
  395. package/shared/galaxy-url.js +48 -0
  396. package/shared/gtn.d.ts +31 -0
  397. package/shared/gtn.js +253 -0
  398. package/shared/handled-prompt.d.ts +3 -0
  399. package/shared/handled-prompt.js +26 -0
  400. package/shared/known-provider.d.ts +11 -0
  401. package/shared/known-provider.js +89 -0
  402. package/shared/lesson-hint-marker.d.ts +7 -0
  403. package/shared/lesson-hint-marker.js +32 -0
  404. package/shared/lesson-rules.d.ts +150 -0
  405. package/shared/lesson-rules.js +203 -0
  406. package/shared/malformed-object-ids.d.ts +1 -0
  407. package/shared/malformed-object-ids.js +37 -0
  408. package/shared/markdown-sections.d.ts +10 -0
  409. package/shared/markdown-sections.js +118 -0
  410. package/shared/notebook-fences.d.ts +12 -0
  411. package/shared/notebook-fences.js +53 -0
  412. package/shared/observation-contract.d.ts +91 -0
  413. package/shared/observation-contract.js +544 -0
  414. package/shared/orbit-config.d.ts +226 -0
  415. package/shared/orbit-config.js +189 -0
  416. package/shared/orbit-env.d.ts +13 -0
  417. package/shared/orbit-env.js +124 -0
  418. package/shared/orbit-shell-contract.d.ts +74 -0
  419. package/shared/orbit-shell-contract.js +30 -0
  420. package/shared/page-content-hash.d.ts +1 -0
  421. package/shared/page-content-hash.js +13 -0
  422. package/shared/pi-default-model.d.ts +7 -0
  423. package/shared/pi-default-model.js +41 -0
  424. package/shared/prompt-blocks.d.ts +87 -0
  425. package/shared/prompt-blocks.js +898 -0
  426. package/shared/provider-auth-caps.d.ts +22 -0
  427. package/shared/provider-auth-caps.js +57 -0
  428. package/shared/random.d.ts +2 -0
  429. package/shared/random.js +15 -0
  430. package/shared/record-excerpt.d.ts +4 -0
  431. package/shared/record-excerpt.js +16 -0
  432. package/shared/redirect-guard.d.ts +36 -0
  433. package/shared/redirect-guard.js +235 -0
  434. package/shared/skills-pin.d.ts +8 -0
  435. package/shared/skills-pin.js +9 -0
  436. package/shared/skills-router.d.ts +17 -0
  437. package/shared/skills-router.js +63 -0
  438. package/shared/sra-import.d.ts +34 -0
  439. package/shared/sra-import.js +206 -0
  440. package/shared/state-dir.d.ts +21 -0
  441. package/shared/state-dir.js +240 -0
  442. package/shared/team-dispatch-contract.d.ts +28 -0
  443. package/shared/team-dispatch-contract.js +1 -0
  444. package/shared/truncate-middle.d.ts +11 -0
  445. package/shared/truncate-middle.js +68 -0
  446. package/shared/version-compare.d.ts +10 -0
  447. package/shared/version-compare.js +74 -0
  448. package/shared/whats-new.d.ts +31 -0
  449. package/shared/whats-new.js +83 -0
  450. package/shared/wsl.d.ts +10 -0
  451. package/shared/wsl.js +24 -0
@@ -0,0 +1,882 @@
1
+ /**
2
+ * Propose and approve (#476, registry design v3 §4-§5): the `loom_propose`
3
+ * tool the model writes proposals with, and the `/approve`, `/revoke` and
4
+ * `/pending` commands the user answers with.
5
+ *
6
+ * Consent has to come from a surface the model cannot drive. These are slash
7
+ * commands, which pi runs only for input a person typed (or Orbit's prompt
8
+ * box sent): a message an extension sends on the model's behalf goes through
9
+ * `sendUserMessage`, which does not expand commands. So `loom_propose` can put
10
+ * a proposal in front of the user, and nothing the model does can approve it.
11
+ *
12
+ * What an approval covers is fixed by the harness, not by the block: `/approve`
13
+ * fetches the template itself, shows the user the table it is about to freeze,
14
+ * re-reads the block afterwards and refuses if it moved, and records the Spec
15
+ * in the signed registry. Any later edit that changes what the proposal
16
+ * amounts to revokes the approval on the next read of the notebook.
17
+ *
18
+ * Submission still goes through the existing tools until `loom_submit` lands.
19
+ */
20
+
21
+ import { randomBytes } from "crypto";
22
+ import * as path from "path";
23
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
24
+ import { Type } from "@sinclair/typebox";
25
+ import { appendActivityEvent } from "./activity";
26
+ import { getGalaxyConfig } from "./galaxy-api";
27
+ import {
28
+ ambiguousAnchorMessage,
29
+ listNotebookAnchors,
30
+ resolveNotebookAnchor,
31
+ unknownAnchorMessage,
32
+ } from "./notebook-anchors";
33
+ import {
34
+ appendProposalBlock,
35
+ findProposalBlocks,
36
+ proposalSightings,
37
+ setProposalSpecRevision,
38
+ withNotebookCas,
39
+ withNotebookLock,
40
+ type ProposalBlock,
41
+ } from "./notebook-writer";
42
+ import { recordStore } from "./record-store";
43
+ import { canonicalJson, normalizeServerUrl, sha256Hex } from "./registry";
44
+ import {
45
+ approveProposal,
46
+ canonicalizeProposal,
47
+ checkInputFormats,
48
+ describePredicate,
49
+ findDrift,
50
+ liveApproval,
51
+ mappedOverSlots,
52
+ newProposalId,
53
+ parseInputs,
54
+ parseOverrides,
55
+ parsePredicate,
56
+ parseTarget,
57
+ pendingProposals,
58
+ renderProposalTable,
59
+ revokeProposal,
60
+ templateShape,
61
+ validateProposal,
62
+ type Drift,
63
+ type Proposal,
64
+ type TemplateShape,
65
+ } from "./registry-proposal";
66
+ import {
67
+ flushHeldRevocations,
68
+ getSessionRegistry,
69
+ heldRevocations,
70
+ sessionView,
71
+ type SessionRegistry,
72
+ } from "./registry-runtime";
73
+
74
+ export { sessionView };
75
+ import {
76
+ inputFormatFetcherFor,
77
+ primeTemplateReplay,
78
+ TemplateUnavailableError,
79
+ templateFetcherFor,
80
+ type InputFormatFetcher,
81
+ type TemplateFetcher,
82
+ } from "./registry-templates";
83
+ import { getCurrentHistoryId, getNotebookPath, onNotebookChange } from "./state";
84
+
85
+ /** Everything the propose/approve flow reads from the session, so tests can supply it. */
86
+ export interface ProposalDeps {
87
+ registry: () => SessionRegistry | null;
88
+ notebookPath: () => string | null;
89
+ galaxyUrl: () => string | null;
90
+ historyId: () => string | null;
91
+ fetcher: (sessionDir: string) => TemplateFetcher;
92
+ /** Reads the inputs' datatypes for the format check; absent or null skips it. */
93
+ formats?: (sessionDir: string) => InputFormatFetcher | null;
94
+ /** ISO timestamp. */
95
+ now: () => string;
96
+ random: (n: number) => Uint8Array;
97
+ }
98
+
99
+ export const defaultDeps: ProposalDeps = {
100
+ registry: getSessionRegistry,
101
+ notebookPath: getNotebookPath,
102
+ galaxyUrl: () => getGalaxyConfig()?.url ?? null,
103
+ historyId: getCurrentHistoryId,
104
+ fetcher: templateFetcherFor,
105
+ formats: inputFormatFetcherFor,
106
+ now: () => new Date().toISOString(),
107
+ random: (n) => randomBytes(n),
108
+ };
109
+
110
+ /** No assertion definitions exist yet (#477), so naming one is refused. */
111
+ function assertionDefinitions(_ids: string[]): Map<string, unknown> {
112
+ return new Map();
113
+ }
114
+
115
+ function activity(
116
+ deps: ProposalDeps,
117
+ kind: string,
118
+ source: string,
119
+ payload: Record<string, unknown>,
120
+ ) {
121
+ const nb = deps.notebookPath();
122
+ if (!nb) return;
123
+ appendActivityEvent(path.dirname(nb), { timestamp: deps.now(), kind, source, payload });
124
+ }
125
+
126
+ async function readRecordOrNull(nb: string): Promise<string | null> {
127
+ try {
128
+ return (await recordStore(nb).read()).content;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /** Why this session can't record approvals, or null when it can. */
135
+ function cannotWrite(session: SessionRegistry | null): string | null {
136
+ if (!session) return "the approval registry isn't open in this session";
137
+ if (session.unavailable)
138
+ return `the approval registry couldn't be opened (${session.unavailable})`;
139
+ if (session.store.mode !== "writer") {
140
+ return "the approval registry is read-only in this session (another session holds it)";
141
+ }
142
+ return null;
143
+ }
144
+
145
+ // ─────────────────────────────────────────────────────────────────────────────
146
+ // Revoke on edit
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+
149
+ /**
150
+ * Revoke every live approval the notebook, as `content` reads now, no longer
151
+ * backs. Runs on every notebook change and before every command, so an edit
152
+ * can't outlive the next look at the notebook.
153
+ */
154
+ export function checkProposalDrift(deps: ProposalDeps, content: string): Drift[] {
155
+ const session = deps.registry();
156
+ if (!session) return [];
157
+ const nb = deps.notebookPath();
158
+ const notebook = content;
159
+ const stepExists = (anchor: string) => {
160
+ const r = resolveNotebookAnchor(notebook, anchor);
161
+ return r.kind === "resolved" && r.anchor === anchor;
162
+ };
163
+
164
+ // Drift is computed whatever the store's mode. A session that approved
165
+ // something and then lost the lock (or whose revocation write failed) still
166
+ // holds that approval as live in memory, and every reader has to stop
167
+ // treating it as live the moment the notebook stops backing it.
168
+ const { store } = session;
169
+ const held = heldRevocations(store);
170
+ const fresh = findDrift(store.snapshot(), proposalSightings(notebook), stepExists).filter(
171
+ (d) => !held.has(d.attemptId),
172
+ );
173
+ for (const d of fresh) held.set(d.attemptId, d);
174
+ const written = flushHeldRevocations(store);
175
+ for (const d of fresh) {
176
+ activity(deps, "proposal.revoked", "harness", {
177
+ proposal_id: d.proposalId,
178
+ attempt_id: d.attemptId,
179
+ reason: d.reason,
180
+ ...(written.has(d.attemptId) ? {} : { recorded: "in memory; registry not writable" }),
181
+ });
182
+ }
183
+ if (fresh.length > 0 && nb)
184
+ void clearEchoes(
185
+ nb,
186
+ fresh.map((d) => d.proposalId),
187
+ );
188
+ return fresh;
189
+ }
190
+
191
+ async function clearEchoes(nb: string, ids: string[]): Promise<void> {
192
+ try {
193
+ await withNotebookLock(nb, () =>
194
+ withNotebookCas(nb, (content) => {
195
+ let next = content;
196
+ for (const id of ids) next = setProposalSpecRevision(next, id, null);
197
+ return { content: next, result: undefined };
198
+ }),
199
+ );
200
+ } catch {
201
+ // Display only; the registry already says revoked.
202
+ }
203
+ }
204
+
205
+ /** The drift check against the record as it reads right now. */
206
+ export async function checkProposalDriftNow(deps: ProposalDeps): Promise<Drift[]> {
207
+ if (!deps.registry()) return [];
208
+ const nb = deps.notebookPath();
209
+ let text = nb ? await readRecordOrNull(nb) : null;
210
+ // A notebook that's gone takes its proposals with it. One that exists but
211
+ // can't be read right now is left for the next look.
212
+ if (text === null && nb && (await recordStore(nb).stat()) === null) text = "";
213
+ if (text === null) return [];
214
+ return checkProposalDrift(deps, text);
215
+ }
216
+
217
+ // ─────────────────────────────────────────────────────────────────────────────
218
+ // loom_propose
219
+ // ─────────────────────────────────────────────────────────────────────────────
220
+
221
+ export interface ProposeParams {
222
+ step_anchor: string;
223
+ label?: string;
224
+ target: { kind: string; id: string; version?: string };
225
+ history_id?: string;
226
+ inputs: unknown;
227
+ overrides?: unknown;
228
+ predicate?: unknown;
229
+ assertions?: string[];
230
+ }
231
+
232
+ export type ProposeResult =
233
+ | {
234
+ ok: true;
235
+ proposal: Proposal;
236
+ table: string;
237
+ /** Slots given a collection that the run maps over, one job per element. */
238
+ mappedOver: string[];
239
+ /** Inputs whose format couldn't be checked, and why. */
240
+ formatsUnchecked: string[];
241
+ }
242
+ | { ok: false; problems: string[] };
243
+
244
+ /**
245
+ * The format check (#618), when this session can ask Galaxy. A dataset the
246
+ * slot can't take is a problem; one it couldn't judge is only noted.
247
+ */
248
+ async function checkFormats(
249
+ deps: ProposalDeps,
250
+ nb: string,
251
+ proposal: Proposal,
252
+ shape: TemplateShape,
253
+ signal?: AbortSignal,
254
+ ): Promise<{ problems: string[]; unchecked: string[] }> {
255
+ const fetch = deps.formats?.(path.dirname(nb));
256
+ if (!fetch) return { problems: [], unchecked: [] };
257
+ return checkInputFormats(proposal, shape, await fetch(proposal.inputs, signal));
258
+ }
259
+
260
+ /**
261
+ * Validate a proposal against the fetched template and, only if nothing is
262
+ * wrong, write it to the notebook. The proposal id, server and timestamps are
263
+ * the harness's; the model supplies what to run.
264
+ */
265
+ export async function propose(
266
+ deps: ProposalDeps,
267
+ params: ProposeParams,
268
+ signal?: AbortSignal,
269
+ ): Promise<ProposeResult> {
270
+ const nb = deps.notebookPath();
271
+ if (!nb) return { ok: false, problems: ["no notebook is open"] };
272
+ const serverUrl = deps.galaxyUrl();
273
+ if (!serverUrl) return { ok: false, problems: ["Galaxy isn't connected in this session"] };
274
+ const content = await readRecordOrNull(nb);
275
+ if (content === null) return { ok: false, problems: [`couldn't read ${nb}`] };
276
+
277
+ const errors: string[] = [];
278
+ const kind = params.target?.kind;
279
+ const field = kind === "workflow" ? "workflow_id" : kind === "udt" ? "tool_uuid" : "tool_id";
280
+ const target = parseTarget(
281
+ { kind, [field]: params.target?.id, version: params.target?.version },
282
+ errors,
283
+ );
284
+ const inputs = parseInputs(params.inputs ?? [], errors);
285
+ const overrides = parseOverrides(params.overrides ?? [], errors);
286
+ const predicate = parsePredicate(params.predicate ?? { kind: "manual" }, errors);
287
+ const assertions = params.assertions ?? [];
288
+ const historyId = params.history_id?.trim() || deps.historyId();
289
+ if (!historyId) errors.push("history_id is required (no history is selected in this session)");
290
+
291
+ const anchor = resolveNotebookAnchor(content, params.step_anchor);
292
+ if (anchor.kind === "unknown") {
293
+ errors.push(unknownAnchorMessage(params.step_anchor, listNotebookAnchors(content)));
294
+ } else if (anchor.kind === "ambiguous") {
295
+ errors.push(ambiguousAnchorMessage(params.step_anchor, anchor.candidates));
296
+ }
297
+ const defined = assertionDefinitions(assertions);
298
+ for (const id of assertions) {
299
+ if (!defined.has(id))
300
+ errors.push(`assertion "${id}" isn't defined; assertions aren't available yet`);
301
+ }
302
+ if (predicate?.kind === "assertions_pass") {
303
+ errors.push("an assertions_pass predicate needs assertions, which aren't available yet");
304
+ }
305
+ if (errors.length > 0 || !target || !inputs || !overrides || !predicate || !historyId) {
306
+ return { ok: false, problems: errors };
307
+ }
308
+ if (anchor.kind !== "resolved") return { ok: false, problems: errors };
309
+
310
+ let snapshot;
311
+ try {
312
+ snapshot = await deps.fetcher(path.dirname(nb))(target, historyId, signal);
313
+ } catch (err) {
314
+ return {
315
+ ok: false,
316
+ problems: [err instanceof TemplateUnavailableError ? err.message : String(err)],
317
+ };
318
+ }
319
+
320
+ const session = deps.registry();
321
+ const taken = new Set(
322
+ findProposalBlocks(content)
323
+ .map((b) => b.proposalId)
324
+ .filter((id): id is string => id !== null),
325
+ );
326
+ for (const a of Object.values(session?.store.snapshot().attempts ?? {})) {
327
+ if (a.approval) taken.add(a.approval.proposal_id);
328
+ }
329
+ const shape = templateShape(target.kind, snapshot);
330
+ const proposal = canonicalizeProposal(
331
+ {
332
+ proposalId: newProposalId(deps.random, taken),
333
+ stepAnchor: anchor.anchor,
334
+ ...(params.label?.trim() ? { label: params.label.trim() } : {}),
335
+ target,
336
+ serverUrl,
337
+ historyId,
338
+ inputs,
339
+ overrides,
340
+ predicate,
341
+ assertions,
342
+ templateDigest: sha256Hex(canonicalJson(snapshot.body)),
343
+ createdAt: deps.now(),
344
+ },
345
+ shape,
346
+ );
347
+ const problems = validateProposal(proposal, shape);
348
+ if (problems.length > 0) return { ok: false, problems };
349
+ const formats = await checkFormats(deps, nb, proposal, shape, signal);
350
+ if (formats.problems.length > 0) return { ok: false, problems: formats.problems };
351
+
352
+ // Freeze the template it was validated against, when this session can.
353
+ if (!cannotWrite(session)) {
354
+ try {
355
+ session?.store.putTemplate(snapshot.body);
356
+ } catch {
357
+ // The approval fetches and freezes its own; this one is a courtesy.
358
+ }
359
+ }
360
+
361
+ try {
362
+ await withNotebookLock(nb, () =>
363
+ withNotebookCas(nb, (fresh) => {
364
+ const again = resolveNotebookAnchor(fresh, proposal.stepAnchor);
365
+ if (again.kind !== "resolved")
366
+ throw new Error(`step ${proposal.stepAnchor} is gone from the notebook`);
367
+ return { content: appendProposalBlock(fresh, proposal), result: undefined };
368
+ }),
369
+ );
370
+ } catch (err) {
371
+ return { ok: false, problems: [`couldn't write the proposal: ${(err as Error).message}`] };
372
+ }
373
+
374
+ activity(deps, "proposal.created", "loom_propose", {
375
+ proposal_id: proposal.proposalId,
376
+ step_anchor: proposal.stepAnchor,
377
+ target: proposal.target,
378
+ history_id: proposal.historyId,
379
+ template_digest: proposal.templateDigest,
380
+ template_version: snapshot.version,
381
+ });
382
+ return {
383
+ ok: true,
384
+ proposal,
385
+ table: renderProposalTable(proposal, { resolvedVersion: snapshot.version, shape }),
386
+ mappedOver: mappedOverSlots(proposal, shape),
387
+ formatsUnchecked: formats.unchecked,
388
+ };
389
+ }
390
+
391
+ // ─────────────────────────────────────────────────────────────────────────────
392
+ // /approve, /revoke, /pending
393
+ // ─────────────────────────────────────────────────────────────────────────────
394
+
395
+ export interface CommandReply {
396
+ level: "info" | "warning" | "error";
397
+ message: string;
398
+ /** Set by /approve when it recorded a new live approval: the proposal to submit now. */
399
+ approved?: string;
400
+ }
401
+
402
+ const PROPOSAL_ARG = /^\s*(\S+)\s*$/;
403
+ const APPROVE_ARGS = /^\s*(?!--)(\S+)(?:\s+(--yes))?\s*$/;
404
+ const APPROVE_USAGE =
405
+ "Usage: /approve <proposal-id> (add --yes where there's no dialog to confirm in, e.g. the terminal's json/print modes)";
406
+
407
+ function locate(content: string, id: string): ProposalBlock | string {
408
+ const blocks = findProposalBlocks(content).filter((b) => b.proposalId === id);
409
+ if (blocks.length === 0)
410
+ return `No proposal ${id} in the notebook. /pending lists the ones there are.`;
411
+ if (blocks.length > 1) {
412
+ return `The notebook has ${blocks.length} blocks claiming ${id}, and there is no telling which one you mean. Remove the extras first.`;
413
+ }
414
+ if (!blocks[0].proposal) {
415
+ return `Proposal ${id} can't be read: ${blocks[0].errors.join("; ")}.`;
416
+ }
417
+ return blocks[0];
418
+ }
419
+
420
+ export interface ApproveOptions {
421
+ /** Shown the harness-rendered table; resolves to the user's answer. Absent = no dialog. */
422
+ confirm?: (title: string, message: string) => Promise<boolean>;
423
+ signal?: AbortSignal;
424
+ }
425
+
426
+ export async function approveCommand(
427
+ deps: ProposalDeps,
428
+ args: string,
429
+ opts: ApproveOptions = {},
430
+ ): Promise<CommandReply> {
431
+ const m = args.match(APPROVE_ARGS);
432
+ if (!m)
433
+ return {
434
+ level: "info",
435
+ message: (await pendingCommand(deps)).message + `\n${APPROVE_USAGE}`,
436
+ };
437
+ const id = m[1];
438
+ const yes = m[2] !== undefined;
439
+ const session = deps.registry();
440
+ const blocked = cannotWrite(session);
441
+ if (blocked || !session) return { level: "warning", message: `Can't approve: ${blocked}.` };
442
+ const serverUrl = deps.galaxyUrl();
443
+ if (!serverUrl || normalizeServerUrl(serverUrl) !== session.store.serverUrl) {
444
+ return {
445
+ level: "warning",
446
+ message:
447
+ `Can't approve: this session's registry is for ${session.store.serverUrl || "no Galaxy server"}` +
448
+ `${serverUrl ? `, but Galaxy is now ${serverUrl}` : ""}. Restart the session once connected.`,
449
+ };
450
+ }
451
+ const nb = deps.notebookPath();
452
+ const content = nb ? await readRecordOrNull(nb) : null;
453
+ if (!nb || content === null) return { level: "warning", message: "Can't approve: no notebook." };
454
+ checkProposalDrift(deps, content);
455
+
456
+ const found = locate(content, id);
457
+ if (typeof found === "string") return { level: "warning", message: found };
458
+ const proposal = found.proposal as Proposal;
459
+
460
+ let snapshot;
461
+ try {
462
+ snapshot = await deps.fetcher(path.dirname(nb))(
463
+ proposal.target,
464
+ proposal.historyId,
465
+ opts.signal,
466
+ );
467
+ } catch (err) {
468
+ return { level: "warning", message: `Can't approve ${id}: ${(err as Error).message}.` };
469
+ }
470
+ const shape = templateShape(proposal.target.kind, snapshot);
471
+ const problems = validateProposal(proposal, shape);
472
+ let formatsUnchecked: string[] = [];
473
+ if (problems.length === 0) {
474
+ const formats = await checkFormats(deps, nb, proposal, shape, opts.signal);
475
+ problems.push(...formats.problems);
476
+ formatsUnchecked = formats.unchecked;
477
+ }
478
+ if (problems.length > 0) {
479
+ return {
480
+ level: "warning",
481
+ message: `Not approved -- ${id} doesn't match what Galaxy says now:\n- ${problems.join("\n- ")}`,
482
+ };
483
+ }
484
+
485
+ const table = renderProposalTable(proposal, {
486
+ resolvedVersion: snapshot.version,
487
+ plain: true,
488
+ shape,
489
+ });
490
+ // Approval re-fetches and freezes what Galaxy says now, by design; say so
491
+ // when that's not what the proposal was checked against.
492
+ const templateNote =
493
+ proposal.templateDigest && proposal.templateDigest !== sha256Hex(canonicalJson(snapshot.body))
494
+ ? "\n\nGalaxy's template for this target changed since it was proposed; the above was checked against the current one."
495
+ : "";
496
+ const formatNote =
497
+ formatsUnchecked.length > 0
498
+ ? `\n\nFormats not checked:\n- ${formatsUnchecked.join("\n- ")}`
499
+ : "";
500
+ if (opts.confirm) {
501
+ const confirmed = await opts.confirm(
502
+ `Approve ${id}?`,
503
+ `${table}${templateNote}${formatNote}\n\nThis freezes exactly the above.`,
504
+ );
505
+ if (!confirmed) return { level: "info", message: `Not approved; ${id} is still pending.` };
506
+ } else if (!yes) {
507
+ // No dialog to show the table in. Approving anyway would record consent to
508
+ // a spec the user never saw, so they have to see it here and say so.
509
+ const message =
510
+ `Not approved: there's no dialog here to confirm in. This is what /approve would freeze:\n\n` +
511
+ `${table}${templateNote}${formatNote}\n\nIf that's what you want, run /approve ${id} --yes.`;
512
+ activity(deps, "proposal.approval_unconfirmed", "user", {
513
+ proposal_id: id,
514
+ table,
515
+ ...(formatsUnchecked.length > 0 ? { formats_unchecked: formatsUnchecked } : {}),
516
+ });
517
+ return { level: "warning", message };
518
+ }
519
+
520
+ // The block must still say what was just shown.
521
+ const latest = await readRecordOrNull(nb);
522
+ const again = latest === null ? null : locate(latest, id);
523
+ if (
524
+ !again ||
525
+ typeof again === "string" ||
526
+ canonicalJson(again.proposal) !== canonicalJson(proposal)
527
+ ) {
528
+ return {
529
+ level: "warning",
530
+ message: `Not approved: ${id} changed in the notebook while it was being approved. Look at it again and re-run /approve.`,
531
+ };
532
+ }
533
+
534
+ let result: ReturnType<typeof approveProposal>;
535
+ try {
536
+ result = approveProposal(session.store, {
537
+ proposal,
538
+ snapshot,
539
+ assertionDefinitions: assertionDefinitions(proposal.assertions),
540
+ now: deps.now(),
541
+ });
542
+ } catch (err) {
543
+ // Typically the lock was taken over mid-approval; the store is read-only now.
544
+ return { level: "warning", message: `Not approved: ${(err as Error).message}.` };
545
+ }
546
+ if (!result.ok) {
547
+ return { level: "warning", message: `Not approved:\n- ${result.problems.join("\n- ")}` };
548
+ }
549
+ if (result.unchanged) {
550
+ return { level: "info", message: `${id} is already approved (attempt ${result.attemptId}).` };
551
+ }
552
+
553
+ activity(deps, "proposal.approved", "user", {
554
+ proposal_id: id,
555
+ attempt_id: result.attemptId,
556
+ spec_revision: result.specRevision,
557
+ step_anchor: proposal.stepAnchor,
558
+ version: result.spec.target.version,
559
+ template_digest: result.spec.template_ref.digest,
560
+ confirmed_by: opts.confirm ? "dialog" : "--yes",
561
+ // On the record even where a notice goes nowhere (json/print modes).
562
+ table: renderProposalTable(proposal, {
563
+ resolvedVersion: result.spec.target.version,
564
+ specRevision: result.specRevision,
565
+ plain: true,
566
+ shape,
567
+ }),
568
+ // So the record tells a format that fit from one nobody could check.
569
+ ...(formatsUnchecked.length > 0 ? { formats_unchecked: formatsUnchecked } : {}),
570
+ ...(result.superseded.length > 0 ? { superseded: result.superseded } : {}),
571
+ });
572
+ try {
573
+ await withNotebookLock(nb, () =>
574
+ withNotebookCas(nb, (fresh) => ({
575
+ content: setProposalSpecRevision(fresh, id, result.specRevision),
576
+ result: undefined,
577
+ })),
578
+ );
579
+ } catch {
580
+ // The echo is for the reader; the registry already holds the approval.
581
+ }
582
+ return {
583
+ level: "info",
584
+ approved: id,
585
+ message:
586
+ `Approved ${id} (attempt ${result.attemptId}, spec_revision ${result.specRevision.slice(0, 12)}).\n\n` +
587
+ renderProposalTable(proposal, {
588
+ resolvedVersion: result.spec.target.version,
589
+ specRevision: result.specRevision,
590
+ plain: true,
591
+ shape,
592
+ }) +
593
+ `\n\nEditing the proposal from here revokes this approval.`,
594
+ };
595
+ }
596
+
597
+ /** The turn /approve starts so the approved run is submitted straight away. */
598
+ export function approvedRunPrompt(id: string): string {
599
+ return (
600
+ `[Orbit approval] The user approved ${id}. Submit exactly that run now with the Galaxy tools, ` +
601
+ `then record it with galaxy_job_record (or galaxy_invocation_record for a workflow), passing ` +
602
+ `proposalId: "${id}".`
603
+ );
604
+ }
605
+
606
+ export async function revokeCommand(deps: ProposalDeps, args: string): Promise<CommandReply> {
607
+ const m = args.match(PROPOSAL_ARG);
608
+ if (!m) return { level: "info", message: "Usage: /revoke <proposal-id>" };
609
+ const id = m[1];
610
+ const session = deps.registry();
611
+ const blocked = cannotWrite(session);
612
+ if (blocked || !session) return { level: "warning", message: `Can't revoke: ${blocked}.` };
613
+ await checkProposalDriftNow(deps);
614
+ let revoked: string[];
615
+ try {
616
+ revoked = revokeProposal(session.store, id);
617
+ } catch (err) {
618
+ return { level: "warning", message: `Can't revoke: ${(err as Error).message}.` };
619
+ }
620
+ if (revoked.length === 0) {
621
+ return { level: "info", message: `${id} has no live approval to revoke.` };
622
+ }
623
+ for (const attemptId of revoked) {
624
+ activity(deps, "proposal.revoked", "user", {
625
+ proposal_id: id,
626
+ attempt_id: attemptId,
627
+ reason: "user",
628
+ });
629
+ }
630
+ const nb = deps.notebookPath();
631
+ if (nb) void clearEchoes(nb, [id]);
632
+ return {
633
+ level: "info",
634
+ message: `Revoked the approval of ${id}. /approve it again to re-approve.`,
635
+ };
636
+ }
637
+
638
+ export async function pendingCommand(deps: ProposalDeps): Promise<CommandReply> {
639
+ const session = deps.registry();
640
+ const nb = deps.notebookPath();
641
+ const content = nb ? await readRecordOrNull(nb) : null;
642
+ if (!content)
643
+ return { level: "info", message: "No proposals: the notebook is empty or missing." };
644
+ checkProposalDrift(deps, content);
645
+ const blocks = findProposalBlocks(content);
646
+ const registry = session ? sessionView(session) : undefined;
647
+ const pending = registry
648
+ ? pendingProposals(registry, proposalSightings(content))
649
+ : proposalSightings(content).map((s) => ({ sighting: s, state: "unapproved" as const }));
650
+ const live = registry
651
+ ? blocks.filter((b) => b.proposalId && liveApproval(registry, b.proposalId)).length
652
+ : 0;
653
+ if (pending.length === 0) {
654
+ return {
655
+ level: "info",
656
+ message:
657
+ blocks.length === 0
658
+ ? "No proposals in the notebook."
659
+ : `Nothing pending; ${live} proposal(s) approved.`,
660
+ };
661
+ }
662
+ const why = {
663
+ unapproved: "not approved",
664
+ revoked: "approval revoked",
665
+ restored: "approved in an earlier session; approvals only count in the session that made them",
666
+ invalid: "can't be read",
667
+ } as const;
668
+ const sections = pending.map(({ sighting, state }) => {
669
+ const head = `${sighting.proposalId} -- ${why[state]}`;
670
+ if (!sighting.proposal) {
671
+ const block = blocks.find((b) => b.proposalId === sighting.proposalId);
672
+ return `${head}: ${block?.errors.join("; ") ?? "unreadable"}`;
673
+ }
674
+ return `${head}\n\n${renderProposalTable(sighting.proposal, { compact: true, plain: true })}`;
675
+ });
676
+ const tail =
677
+ registry && session?.store.mode === "writer"
678
+ ? ""
679
+ : "\n\n(The registry is read-only in this session.)";
680
+ return {
681
+ level: "info",
682
+ message:
683
+ `${pending.length} pending proposal(s)${live > 0 ? `, ${live} approved` : ""}. ` +
684
+ `Approve one with /approve <proposal-id>.\n\n${sections.join("\n\n")}${tail}`,
685
+ };
686
+ }
687
+
688
+ // ─────────────────────────────────────────────────────────────────────────────
689
+ // Binding a recorded run to its proposal
690
+ // ─────────────────────────────────────────────────────────────────────────────
691
+
692
+ export type BindResult = { bound: true; attemptId: string } | { bound: false; reason: string };
693
+
694
+ /**
695
+ * Note that the agent says a run it recorded carries out an approved proposal.
696
+ * Until `loom_submit` exists the agent submits with the ordinary tools, so this
697
+ * is the agent's claim and is recorded as one: a `proposal.bound` activity row
698
+ * naming the approval's attempt id. It changes nothing in the registry and
699
+ * writes no harness field on the block -- a claim the harness didn't witness
700
+ * must never read as a submission it did.
701
+ */
702
+ export async function noteProposalBinding(
703
+ deps: ProposalDeps,
704
+ args: {
705
+ proposalId: string;
706
+ notebookAnchor: string;
707
+ run: { kind: "job" | "invocation"; id: string };
708
+ },
709
+ ): Promise<BindResult> {
710
+ const session = deps.registry();
711
+ if (!session) return { bound: false, reason: "the approval registry isn't open in this session" };
712
+ await checkProposalDriftNow(deps);
713
+ const live = liveApproval(sessionView(session), args.proposalId);
714
+ if (!live) {
715
+ return { bound: false, reason: `${args.proposalId} has no live approval in this session` };
716
+ }
717
+ if (live.binding.step_anchor !== args.notebookAnchor) {
718
+ return {
719
+ bound: false,
720
+ reason: `${args.proposalId} was approved for ${live.binding.step_anchor}, not ${args.notebookAnchor}`,
721
+ };
722
+ }
723
+ activity(deps, "proposal.bound", "record-tool", {
724
+ proposal_id: args.proposalId,
725
+ attempt_id: live.attempt_id,
726
+ step_anchor: args.notebookAnchor,
727
+ run_kind: args.run.kind,
728
+ run_id: args.run.id,
729
+ declared_by: "agent",
730
+ });
731
+ return { bound: true, attemptId: live.attempt_id };
732
+ }
733
+
734
+ // ─────────────────────────────────────────────────────────────────────────────
735
+ // Registration
736
+ // ─────────────────────────────────────────────────────────────────────────────
737
+
738
+ function toolText(text: string, details: Record<string, unknown>) {
739
+ return { content: [{ type: "text" as const, text }], details };
740
+ }
741
+
742
+ export function registerProposalCommands(pi: ExtensionAPI, deps: ProposalDeps = defaultDeps): void {
743
+ onNotebookChange((content) => {
744
+ checkProposalDrift(deps, content);
745
+ });
746
+
747
+ // The eval-only template replay is read once, before the model's first turn.
748
+ pi.on("session_start", async () => {
749
+ const nb = deps.notebookPath();
750
+ primeTemplateReplay(nb ? path.dirname(nb) : process.cwd());
751
+ });
752
+
753
+ pi.registerTool({
754
+ name: "loom_propose",
755
+ label: "Propose Galaxy Run",
756
+ description: `Propose one concrete Galaxy run -- a tool, workflow or user-defined tool with its
757
+ exact inputs and parameters -- for the user to approve. The harness fetches the target's template from
758
+ Galaxy and checks your proposal against it (dataset slots and their formats, required inputs, parameter names) before
759
+ anything is written; if something is wrong you get the list of problems and nothing is written. On
760
+ success it writes a loom-proposal block to the notebook and returns a proposal id and a table.
761
+ Show the user that table and ask them to type /approve <proposal-id>. You cannot approve it yourself.
762
+ Give every parameter you set a rationale. Leave version unset to take what Galaxy resolves.
763
+ A tool's dataset input also takes a collection (src hdca): on a one-dataset input it runs once per
764
+ element, on a multiple input it takes them all in one job.`,
765
+ parameters: Type.Object({
766
+ step_anchor: Type.String({
767
+ description: "The plan step this run is for, e.g. 'plan-a-step-2'.",
768
+ }),
769
+ label: Type.Optional(Type.String({ description: "Short human label, e.g. 'Trim reads'." })),
770
+ target: Type.Object({
771
+ kind: Type.Union([Type.Literal("tool"), Type.Literal("workflow"), Type.Literal("udt")]),
772
+ id: Type.String({
773
+ description: "Tool id, workflow id, or user-defined tool uuid.",
774
+ }),
775
+ version: Type.Optional(
776
+ Type.String({ description: "Pin a version; omit (or 'unpinned') to take Galaxy's." }),
777
+ ),
778
+ }),
779
+ history_id: Type.Optional(
780
+ Type.String({ description: "History to run in; defaults to the session's current one." }),
781
+ ),
782
+ inputs: Type.Array(
783
+ Type.Object({
784
+ slot: Type.String({
785
+ description:
786
+ "Tool: the data parameter name in Galaxy's flat form (e.g. 'reads' or 'cond|in1'). Workflow: the input's step index or label.",
787
+ }),
788
+ src: Type.Union([Type.Literal("hda"), Type.Literal("hdca"), Type.Literal("ldda")]),
789
+ id: Type.String({ description: "Dataset or collection id." }),
790
+ }),
791
+ ),
792
+ overrides: Type.Optional(
793
+ Type.Array(
794
+ Type.Object({
795
+ param: Type.String({
796
+ description: "Parameter name, flat form ('section|param', 'rep_0|x').",
797
+ }),
798
+ value: Type.Unknown(),
799
+ rationale: Type.String({ description: "Why this value." }),
800
+ }),
801
+ ),
802
+ ),
803
+ predicate: Type.Optional(
804
+ Type.Unknown({
805
+ description:
806
+ "When the step counts as done: {kind:'manual'} (default), {kind:'exists_with_ext', ext, min_count?} or {kind:'count_eq', count, ext?}.",
807
+ }),
808
+ ),
809
+ }),
810
+ async execute(_id, params, signal) {
811
+ const result = await propose(deps, params as ProposeParams, signal);
812
+ if (!result.ok) {
813
+ return toolText(
814
+ `Not proposed -- fix these and call loom_propose again:\n- ${result.problems.join("\n- ")}`,
815
+ { error: true, problems: result.problems },
816
+ );
817
+ }
818
+ const id = result.proposal.proposalId;
819
+ const mapped =
820
+ result.mappedOver.length > 0
821
+ ? ` It maps over the collection given to ${result.mappedOver.join(", ")}: ` +
822
+ `Galaxy runs it once per element, and each job it makes carries out this proposal.`
823
+ : "";
824
+ const unchecked =
825
+ result.formatsUnchecked.length > 0
826
+ ? `\n\nFormats not checked (Galaxy will say at submission if they're wrong):\n- ` +
827
+ result.formatsUnchecked.join("\n- ")
828
+ : "";
829
+ return toolText(
830
+ `Proposed ${id}. Show the user this table and ask them to type /approve ${id} ` +
831
+ `(or say what to change). Do not submit this run until they have approved it.` +
832
+ `${mapped}\n\n${result.table}${unchecked}`,
833
+ {
834
+ proposalId: id,
835
+ predicate: describePredicate(result.proposal.predicate),
836
+ ...(result.mappedOver.length > 0 ? { mappedOver: result.mappedOver } : {}),
837
+ },
838
+ );
839
+ },
840
+ });
841
+
842
+ const reply = (ctx: ExtensionContext, r: CommandReply) => {
843
+ try {
844
+ ctx.ui.notify(r.message, r.level);
845
+ } catch {
846
+ // headless or stale context; the activity log has the record
847
+ }
848
+ };
849
+
850
+ pi.registerCommand("approve", {
851
+ description: "Approve a pending Galaxy run proposal: /approve <proposal-id> [--yes]",
852
+ handler: async (args: string | undefined, ctx: ExtensionContext) => {
853
+ const confirm = ctx.hasUI
854
+ ? (title: string, message: string) => ctx.ui.confirm(title, message)
855
+ : undefined;
856
+ const r = await approveCommand(deps, args ?? "", { confirm });
857
+ reply(ctx, r);
858
+ // A slash command starts no turn, so without this the approved run waits
859
+ // for the user's next message. Only the user's /approve reaches here.
860
+ // Print and json modes run their messages back to back and dispose the
861
+ // session after the last one, so a turn started here either collides with
862
+ // the next message or is cut off; there the next message has to say go.
863
+ if (r.approved && ctx.mode !== "print" && ctx.mode !== "json") {
864
+ pi.sendUserMessage(approvedRunPrompt(r.approved), { deliverAs: "followUp" });
865
+ }
866
+ },
867
+ });
868
+
869
+ pi.registerCommand("revoke", {
870
+ description: "Revoke the approval of a Galaxy run proposal: /revoke <proposal-id>",
871
+ handler: async (args: string | undefined, ctx: ExtensionContext) => {
872
+ reply(ctx, await revokeCommand(deps, args ?? ""));
873
+ },
874
+ });
875
+
876
+ pi.registerCommand("pending", {
877
+ description: "List Galaxy run proposals waiting for approval",
878
+ handler: async (_args: string | undefined, ctx: ExtensionContext) => {
879
+ reply(ctx, await pendingCommand(deps));
880
+ },
881
+ });
882
+ }