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