@jr2/orchestrator 0.3.0 → 0.5.1

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 (354) hide show
  1. package/README.md +20 -2
  2. package/bin/server.ts +7 -4
  3. package/console/canvas.ts +27 -14
  4. package/console/components/app.ts +12 -1
  5. package/console/components/fleet.ts +52 -6
  6. package/console/components/machine-pane.ts +128 -2
  7. package/console/components/nav.ts +1 -1
  8. package/console/components/schema-form.ts +12 -4
  9. package/console/json-field.ts +23 -0
  10. package/console/main.ts +122 -39
  11. package/console/store.ts +321 -21
  12. package/console/style.css +111 -0
  13. package/docs/agents-block.md +5 -0
  14. package/docs/concepts/agents-and-turns.md +442 -0
  15. package/docs/concepts/architecture.md +230 -0
  16. package/docs/concepts/charts.md +143 -0
  17. package/docs/concepts/composition.md +357 -0
  18. package/docs/concepts/converge.md +254 -0
  19. package/docs/concepts/durable-runs.md +189 -0
  20. package/docs/concepts/effects.md +603 -0
  21. package/docs/concepts/gates.md +208 -0
  22. package/docs/concepts/machines-and-workflows.md +289 -0
  23. package/docs/concepts/pools-and-sources.md +230 -0
  24. package/docs/concepts/secrets-and-trust.md +222 -0
  25. package/docs/concepts/the-menu.md +378 -0
  26. package/docs/concepts/workspaces.md +678 -0
  27. package/docs/get-started/a-model-decides.md +563 -0
  28. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  29. package/docs/get-started/connect-a-model.md +248 -0
  30. package/docs/get-started/create-an-instance.md +194 -0
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  32. package/docs/get-started/install.md +126 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  34. package/docs/guides/answer-a-gate.md +304 -0
  35. package/docs/guides/bind-open-parts.md +223 -0
  36. package/docs/guides/cancel-a-run.md +146 -0
  37. package/docs/guides/clean-up.md +206 -0
  38. package/docs/guides/fix-a-failed-converge.md +361 -0
  39. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  41. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  42. package/docs/guides/look-inside-a-sandbox.md +174 -0
  43. package/docs/guides/mirror-the-kit-images.md +176 -0
  44. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  45. package/docs/guides/open-the-console.md +91 -0
  46. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  47. package/docs/guides/push-and-sign-a-branch.md +486 -0
  48. package/docs/guides/run-a-chart-by-name.md +270 -0
  49. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  50. package/docs/guides/run-one-worker-per-item.md +321 -0
  51. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  52. package/docs/guides/size-a-workspace.md +204 -0
  53. package/docs/guides/start-a-run-from-a-run.md +237 -0
  54. package/docs/guides/start-a-run.md +207 -0
  55. package/docs/guides/upgrade-the-kit-version.md +240 -0
  56. package/docs/guides/use-a-hosted-provider.md +208 -0
  57. package/docs/guides/use-a-local-model.md +313 -0
  58. package/docs/guides/watch-a-run.md +326 -0
  59. package/docs/guides/write-a-workflow.md +318 -0
  60. package/docs/index.md +44 -0
  61. package/docs/llms.txt +216 -0
  62. package/docs/reference/authoring-surface/agent.md +128 -0
  63. package/docs/reference/authoring-surface/answer.md +140 -0
  64. package/docs/reference/authoring-surface/arg.md +60 -0
  65. package/docs/reference/authoring-surface/basic.md +62 -0
  66. package/docs/reference/authoring-surface/bearer.md +53 -0
  67. package/docs/reference/authoring-surface/chartschema.md +23 -0
  68. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  69. package/docs/reference/authoring-surface/command.md +191 -0
  70. package/docs/reference/authoring-surface/customize.md +64 -0
  71. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  72. package/docs/reference/authoring-surface/defineevent.md +114 -0
  73. package/docs/reference/authoring-surface/dispatch.md +126 -0
  74. package/docs/reference/authoring-surface/fail.md +83 -0
  75. package/docs/reference/authoring-surface/file.md +122 -0
  76. package/docs/reference/authoring-surface/frompick.md +277 -0
  77. package/docs/reference/authoring-surface/http.md +159 -0
  78. package/docs/reference/authoring-surface/isfailure.md +58 -0
  79. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  80. package/docs/reference/authoring-surface/open.md +74 -0
  81. package/docs/reference/authoring-surface/openbody.md +86 -0
  82. package/docs/reference/authoring-surface/paramsof.md +125 -0
  83. package/docs/reference/authoring-surface/pool.md +186 -0
  84. package/docs/reference/authoring-surface/push.md +112 -0
  85. package/docs/reference/authoring-surface/secret.md +118 -0
  86. package/docs/reference/authoring-surface/sentby.md +83 -0
  87. package/docs/reference/authoring-surface/source.md +51 -0
  88. package/docs/reference/authoring-surface/text.md +62 -0
  89. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  90. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  91. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  92. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  93. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  94. package/docs/reference/authoring-surface/type-arg.md +19 -0
  95. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  96. package/docs/reference/authoring-surface/type-binding.md +62 -0
  97. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  98. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  99. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  100. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  101. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  102. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  103. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  104. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  105. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  106. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  107. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  108. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  109. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  110. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  111. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  112. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  113. package/docs/reference/authoring-surface/type-childat.md +12 -0
  114. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  115. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  116. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  117. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  118. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  119. package/docs/reference/authoring-surface/type-customize.md +109 -0
  120. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  121. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  122. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  124. package/docs/reference/authoring-surface/type-duration.md +15 -0
  125. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  126. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  127. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  128. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  129. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  130. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  131. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  132. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  133. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  134. package/docs/reference/authoring-surface/type-failure.md +19 -0
  135. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  136. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  137. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  138. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  139. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  140. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  141. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  142. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  143. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  144. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  145. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  146. package/docs/reference/authoring-surface/type-lends.md +38 -0
  147. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  148. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  149. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  150. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  151. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  152. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  153. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  154. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  155. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  156. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  157. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  158. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  159. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  160. package/docs/reference/authoring-surface/type-openof.md +12 -0
  161. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  162. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  163. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  164. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  165. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  166. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  167. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  168. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  169. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  170. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  171. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  172. package/docs/reference/authoring-surface/type-reached.md +12 -0
  173. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  174. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  175. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  176. package/docs/reference/authoring-surface/type-secret.md +25 -0
  177. package/docs/reference/authoring-surface/type-sender.md +19 -0
  178. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  179. package/docs/reference/authoring-surface/type-size.md +85 -0
  180. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  181. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  182. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  183. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  184. package/docs/reference/authoring-surface/type-text.md +19 -0
  185. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  186. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  187. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  188. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  189. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  190. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  191. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  192. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  193. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  194. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  195. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  196. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  197. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  198. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  199. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  200. package/docs/reference/authoring-surface/workspace.md +259 -0
  201. package/docs/reference/authoring-surface.md +197 -0
  202. package/docs/reference/cli.md +433 -0
  203. package/docs/reference/config.md +756 -0
  204. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  205. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  206. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  207. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  208. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  209. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  210. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  211. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  212. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  213. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  214. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  215. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  216. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  217. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  218. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  219. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  220. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  221. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  222. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  223. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  224. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  225. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  226. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  227. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  228. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  229. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  230. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  231. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  232. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  233. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  234. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  235. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  236. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  237. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  238. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  239. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  240. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  241. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  242. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  243. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  244. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  245. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  246. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  247. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  248. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  249. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  250. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  251. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  252. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  253. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  254. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  255. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  256. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  257. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  258. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  259. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  260. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  261. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  262. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  263. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  264. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  265. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  266. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  267. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  268. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  269. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  270. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  271. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  272. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  273. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  274. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  275. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  276. package/docs/reference/decisions.md +83 -0
  277. package/docs/reference/glossary.md +648 -0
  278. package/docs/reference/http-api.md +1348 -0
  279. package/docs/reference/machines.md +190 -0
  280. package/docs/reference/mechanism-events.md +72 -0
  281. package/docs/why.md +206 -0
  282. package/package.json +6 -4
  283. package/src/actor.ts +976 -61
  284. package/src/agent.ts +114 -38
  285. package/src/ambient.ts +4 -1
  286. package/src/assembly-checks.ts +347 -0
  287. package/src/assembly-tar.ts +494 -0
  288. package/src/assembly.ts +721 -0
  289. package/src/cause.ts +234 -0
  290. package/src/chart.ts +2182 -0
  291. package/src/charts.ts +748 -0
  292. package/src/config.ts +520 -162
  293. package/src/content-hash.ts +105 -0
  294. package/src/custodian.lua +6 -5
  295. package/src/custodian.ts +143 -39
  296. package/src/customize.ts +388 -55
  297. package/src/dispatch-effect.ts +945 -0
  298. package/src/door-compat.ts +217 -0
  299. package/src/durability.ts +11 -4
  300. package/src/effect-checks.ts +557 -0
  301. package/src/effect.ts +1202 -0
  302. package/src/effector-wire.ts +674 -0
  303. package/src/fail.ts +209 -0
  304. package/src/fingerprint.ts +4 -3
  305. package/src/gate.ts +8 -0
  306. package/src/git-clone.ts +65 -0
  307. package/src/git-push.ts +336 -0
  308. package/src/harness-client.ts +764 -48
  309. package/src/held-secrets.ts +82 -26
  310. package/src/holds.ts +156 -0
  311. package/src/http-effect.ts +1423 -0
  312. package/src/http.ts +916 -113
  313. package/src/images.ts +47 -21
  314. package/src/index.ts +62 -43
  315. package/src/instance.ts +505 -59
  316. package/src/internal.ts +84 -0
  317. package/src/kube-client.ts +333 -0
  318. package/src/machine-doc.ts +106 -6
  319. package/src/mechanism-routes.ts +168 -0
  320. package/src/model-keys.generated.ts +53 -0
  321. package/src/model-keys.ts +18 -58
  322. package/src/mount-digest.ts +53 -0
  323. package/src/names.ts +66 -8
  324. package/src/notices.ts +141 -0
  325. package/src/open-body.ts +1580 -0
  326. package/src/open-effect.ts +217 -0
  327. package/src/open.ts +59 -6
  328. package/src/parts.ts +709 -41
  329. package/src/party.ts +534 -0
  330. package/src/pick.ts +761 -0
  331. package/src/pool.ts +248 -37
  332. package/src/registration.ts +382 -19
  333. package/src/repo-fetch.ts +60 -85
  334. package/src/repos.ts +65 -75
  335. package/src/rest.ts +133 -0
  336. package/src/run-host.ts +2383 -115
  337. package/src/run-output.ts +38 -0
  338. package/src/sandbox-effect.ts +1126 -0
  339. package/src/sandbox-effector.ts +111 -0
  340. package/src/sandbox-kube.ts +1721 -0
  341. package/src/sandbox-watch.ts +260 -0
  342. package/src/secret-placement.ts +529 -0
  343. package/src/server.ts +284 -84
  344. package/src/setup.ts +819 -117
  345. package/src/size.ts +454 -0
  346. package/src/snapshot-store.ts +115 -20
  347. package/src/stub-harness.ts +49 -16
  348. package/src/sync-client.ts +54 -0
  349. package/src/timers.ts +191 -0
  350. package/src/tokens.ts +35 -4
  351. package/src/vocabulary.ts +135 -15
  352. package/src/wire.ts +238 -10
  353. package/src/workspace.ts +1195 -338
  354. package/src/sandbox-kubectl.ts +0 -1108
package/README.md CHANGED
@@ -1,8 +1,26 @@
1
1
  # @jr2/orchestrator
2
2
 
3
3
  The jr2 Orchestrator: the runtime that executes Machines — xstate actors driving Agents in Kubernetes Sandboxes — plus
4
- the HTTP API, the Console it serves, and the kit pieces a Workflow imports (`agent()`, `workspace()`, `gate()`,
5
- `customize()`, …).
4
+ the HTTP API, the Console it serves, and the Authoring surface.
5
+
6
+ The Authoring surface is what this package exports from its root: the names that an author writes in a workflow module
7
+ or in `jr2.config.ts`, and no other names.
8
+
9
+ - **Machine and events:** `jr2Setup()`, `defineEvent()`, `sentBy()`, and the type `HostInjectedInput`.
10
+ - **Agents:** `agent()`, and the types `AgentDefinition` and `ThinkingLevel`.
11
+ - **Workspaces and Repos:** `workspace()`, and the types `Workspaced`, `RepoSlot` and `Binding`.
12
+ - **Effects:** `command()`, `arg()`, `http()`, `secret()`, `bearer()`, `basic()`, `text()`, `file()`, `push()`,
13
+ `dispatch()`, `fromPick()`, `paramsOf()`, `answer()`, and the types `CommandOutput`, `HttpOutput`, `PushOutput` and
14
+ `DispatchOutput`.
15
+ - **Composition:** `open`, `customize()`, `pool()`, `source()`, and the types `Customize`, `EffectBinding`, `Size` and
16
+ `UserContainer`.
17
+ - **Charts:** `openBody()`, `chartSchema`, `chartSchemaFor()`, and the types `OpenBodyDeclaration` and `OpenBodyOutput`.
18
+ - **Config:** `defineConfig()`, and the type `JR2Config`.
19
+
20
+ A state that needs outside input invokes the Gate as `src: "gate"`. You do not import it: `jr2Setup()` registers it.
21
+
22
+ The kit's own packages import `@jr2/orchestrator/internal`. It is not for workflow authors. It has no stability promise,
23
+ and any name in it can change in any release.
6
24
 
7
25
  An Instance depends on it and its `workflows/` import from it; `jr2 up` bakes the engine and those workflows into one
8
26
  image, so this package is run in-cluster, never on the host. Its version is the kit's version: the Kit images an
package/bin/server.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // The instance image's entrypoint (ADR-0019): boot the orchestrator described by env + cwd and
3
- // serve until SIGINT/SIGTERM. A zero-build `.ts` shebang like the `jr2` bin; the e2e tier spawns
4
- // this exact process as its per-scenario fixture (ADR-0010). Logic lives in `serverMain` — this
5
- // file owns only what a process owns: env, cwd, stdout, signals, exit.
2
+ // The Orchestrator Kit image's entrypoint (ADR-0019, ADR-0069): boot the orchestrator described
3
+ // by env + cwd and serve until SIGINT/SIGTERM. The cwd holds the state volume, and the Workflows
4
+ // come from the current Assembly on it; the folder itself is never loaded, so a process with no
5
+ // published Assembly serves none until `jr2 up` publishes one. A zero-build `.ts` shebang like
6
+ // the `jr2` bin; the e2e tier spawns this exact process as its per-scenario fixture and publishes
7
+ // through the same door (ADR-0010). Logic lives in `serverMain` — this file owns only what a
8
+ // process owns: env, cwd, stdout, signals, exit.
6
9
 
7
10
  import { serverMain } from "../src/server.ts";
8
11
 
package/console/canvas.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // Working state only, never belief: the fold set, the viewport transform, and what is SHOWN live
9
9
  // here — everything the page believes stays in store.ts and arrives as `updateCanvas` arguments.
10
10
 
11
- import type { GateCard, ObservedRun } from "./store.ts";
11
+ import type { GateCard, ObservedRun, Seat } from "./store.ts";
12
12
 
13
13
  // ---- The machine doc, as `GET /workflows/:name/machine` serves it -------------------------------
14
14
  // Mirrors machine-doc.ts (the server's serializer) the way store.ts mirrors `RunObservation`: the
@@ -57,7 +57,8 @@ export type ChildMachineDoc = {
57
57
  export type MachineEventDoc = {
58
58
  name: string;
59
59
  description?: string;
60
- audience: "agent" | "external" | "any";
60
+ /** Absent: each writing's state decides who may send it (ADR-0015). */
61
+ audience?: "agent" | "external" | "any";
61
62
  /** The def's input schema as JSON Schema. */
62
63
  input: unknown;
63
64
  };
@@ -70,6 +71,10 @@ export type MachineBodyDoc = {
70
71
  /** The events THIS Machine declares, and only this one (ADR-0049): a nested Machine's ride its
71
72
  * own body doc, because event names are scoped to the Machine that declared them. */
72
73
  events: MachineEventDoc[];
74
+ /** Who may send what, per state (ADR-0015) — mirrors `MachineSeat`. */
75
+ seats: Seat[];
76
+ /** What the build found legal and suspect (ADR-0015). */
77
+ warnings: string[];
73
78
  };
74
79
 
75
80
  /** The serialized structure of a workflow's Machine — mirrors `MachineDoc`. */
@@ -77,6 +82,8 @@ export type MachineDoc = MachineBodyDoc & {
77
82
  workflow: string;
78
83
  /** States whose child list may be incomplete (enqueueActions closures) — surfaced as a notice. */
79
84
  opaqueStates?: string[];
85
+ /** A run's doc only: each bound Chart it could not draw, and why (ADR-0070) — surfaced as a notice. */
86
+ chartsNotDrawn?: Array<{ path: string; src: string; reason: string }>;
80
87
  };
81
88
 
82
89
  /** One live child actor as the observation band reports it — `ObservedRun.children`, deepened
@@ -138,9 +145,11 @@ export function initCanvas(els: { canvas: HTMLElement; svg: SVGSVGElement }, can
138
145
  svg = els.svg;
139
146
  hooks = canvasHooks;
140
147
  wireCanvasNavigation();
141
- addEventListener("resize", () => {
142
- if (fitting) fitToWidth();
143
- });
148
+ // The pane changes size with the window AND with the strips above the diagram (a wait, a held
149
+ // pick, the seats opening) — a fit follows either.
150
+ new ResizeObserver(() => {
151
+ if (fitting) fitWhole();
152
+ }).observe(svg);
144
153
  }
145
154
 
146
155
  const SVG_NS = "http://www.w3.org/2000/svg";
@@ -498,8 +507,8 @@ let layoutSize = { width: 0, height: 0 };
498
507
  let scale = 1;
499
508
  let panX = 0;
500
509
  let panY = 0;
501
- /** Fit the Machine's width to the canvas, and keep fitting it as the diagram grows — until the
502
- * reader takes the view into their own hands, after which it is theirs. */
510
+ /** Fit the whole Machine to the canvas, and keep fitting it as the diagram or the pane changes —
511
+ * until the reader takes the view into their own hands, after which it is theirs. */
503
512
  let fitting = true;
504
513
  /** The <g> the current render mounted — the transform target; each re-render swaps it in and
505
514
  * re-applies the view, so a relayout never resets where the reader was looking. */
@@ -513,11 +522,15 @@ function applyView(): void {
513
522
  hooks.onScale(Math.round(scale * 100)); // the nav readout follows every path
514
523
  }
515
524
 
516
- /** Scale so the whole width lands in the pane, centered. Never magnifies — a small Machine stays
517
- * 1:1. */
518
- function fitToWidth(): void {
519
- if (!layoutSize.width || svg.clientWidth <= 0) return applyView();
520
- scale = Math.min(1, Math.max(0.2, (svg.clientWidth - FIT_PAD * 2) / layoutSize.width));
525
+ /** Scale so the whole Machine — its width AND its height — lands in the pane, centered across.
526
+ * Never magnifies (a small Machine stays 1:1), and never below the floor: a Machine larger than the
527
+ * floor allows pans. Fitting the width alone left a tall Machine's lower states — a `workspace()`
528
+ * body under the wrapper's chain — below the pane, where no reader looks for them. */
529
+ function fitWhole(): void {
530
+ if (!layoutSize.width || svg.clientWidth <= 0 || svg.clientHeight <= 0) return applyView();
531
+ const across = (svg.clientWidth - FIT_PAD * 2) / layoutSize.width;
532
+ const down = (svg.clientHeight - FIT_PAD * 2) / layoutSize.height;
533
+ scale = Math.min(1, Math.max(0.2, Math.min(across, down)));
521
534
  panX = Math.max(FIT_PAD, (svg.clientWidth - layoutSize.width * scale) / 2);
522
535
  panY = FIT_PAD;
523
536
  applyView();
@@ -547,7 +560,7 @@ export function zoomReset(): void {
547
560
  }
548
561
  export function zoomFit(): void {
549
562
  fitting = true;
550
- fitToWidth();
563
+ fitWhole();
551
564
  }
552
565
 
553
566
  // ---- Direct navigation: drag pans, wheel zooms --------------------------------------------------
@@ -638,7 +651,7 @@ async function renderMachine(doc: MachineBodyDoc, live: RunChild[]): Promise<voi
638
651
  renderEdges(layout, rootG);
639
652
  // The fresh <g> becomes the viewport, wearing the view the reader already had (or a fit).
640
653
  viewportG = rootG;
641
- fitting ? fitToWidth() : applyView();
654
+ fitting ? fitWhole() : applyView();
642
655
  }
643
656
 
644
657
  // ---- Live runs ----------------------------------------------------------------------------------
@@ -6,7 +6,15 @@
6
6
 
7
7
  import { Fragment, h, type JSX } from "preact";
8
8
  import { useState } from "preact/hooks";
9
- import { selectedRun, selectedRunGates, type Frame, type Store } from "../store.ts";
9
+ import {
10
+ selectedRun,
11
+ selectedRunFailure,
12
+ selectedRunGates,
13
+ selectedRunHeld,
14
+ selectedRunWaiting,
15
+ type Frame,
16
+ type Store,
17
+ } from "../store.ts";
10
18
  import type { MachineDoc } from "../canvas.ts";
11
19
  import { Nav } from "./nav.ts";
12
20
  import { Fleet } from "./fleet.ts";
@@ -71,6 +79,9 @@ export function App(props: {
71
79
  run: selectedRun(store),
72
80
  selectedNodeId: store.selectedNodeId,
73
81
  gates: selectedRunGates(store),
82
+ waiting: selectedRunWaiting(store),
83
+ held: selectedRunHeld(store),
84
+ failure: selectedRunFailure(store),
74
85
  api,
75
86
  }),
76
87
  h(Drawer, { store, chosenTab, onChooseTab: setChosenTab, api }),
@@ -2,14 +2,18 @@
2
2
  // master–detail shell (ADR-0032). Every row derives from store selectors (`fleetRuns`), never a
3
3
  // component-local cache; the vdom's keyed diff is what keeps a half-typed start form alive under
4
4
  // the status frames that used to demand main.js's hand-rolled `memo()`.
5
+ //
6
+ // A named Chart is a Workflow (ADR-0070): it lists under its Chart wrapper, indented, with its origin
7
+ // (`chart file` or `chart`) and, when a publish no longer admits it, `unrunnable` with the reason.
5
8
 
6
9
  import { h, type JSX } from "preact";
7
- import { fleetRuns, type ObservedRun, type Store } from "../store.ts";
10
+ import { dispatchBadge, fleetEntries, fleetRuns, type FleetEntry, type ObservedRun, type Store } from "../store.ts";
8
11
  import { SchemaForm } from "./schema-form.ts";
9
12
  import type { AppApi } from "./app.ts";
10
13
 
11
14
  export function Fleet(props: { store: Store; schemas: ReadonlyMap<string, unknown>; api: AppApi }): JSX.Element {
12
15
  const { store, schemas, api } = props;
16
+ const entries = fleetEntries(store.listing);
13
17
  return h(
14
18
  "aside",
15
19
  { id: "fleet" },
@@ -17,18 +21,38 @@ export function Fleet(props: { store: Store; schemas: ReadonlyMap<string, unknow
17
21
  h(
18
22
  "ul",
19
23
  { id: "workflow-list" },
20
- store.workflows.length
21
- ? store.workflows.map((name) => workflowItem(name, store, schemas, api))
24
+ entries.length
25
+ ? entries.map((entry) => workflowItem(entry, store, schemas, api))
22
26
  : h("li", { class: "empty" }, "no workflows registered"),
23
27
  ),
24
28
  );
25
29
  }
26
30
 
27
- function workflowItem(name: string, store: Store, schemas: ReadonlyMap<string, unknown>, api: AppApi): JSX.Element {
31
+ /** How the rail names an origin (ADR-0070); a Machine that is no Chart wrapper shows none. */
32
+ function originTag(entry: FleetEntry): string | null {
33
+ if (entry.origin === "file") return "chart file";
34
+ if (entry.origin === "runtime") return "chart";
35
+ if (entry.wrapper) return entry.wrapper.oneShot ? "chart wrapper, one-shot" : "chart wrapper";
36
+ return null;
37
+ }
38
+
39
+ function workflowItem(
40
+ entry: FleetEntry,
41
+ store: Store,
42
+ schemas: ReadonlyMap<string, unknown>,
43
+ api: AppApi,
44
+ ): JSX.Element {
45
+ const name = entry.name;
28
46
  const rows = fleetRuns(store, name);
47
+ const tag = originTag(entry);
29
48
  return h(
30
49
  "li",
31
- { key: name, class: name === store.workflow ? "wf wf--selected" : "wf" },
50
+ {
51
+ key: name,
52
+ class: `${name === store.workflow ? "wf wf--selected" : "wf"}${entry.depth ? " wf--chart" : ""}`,
53
+ // A Chart indents under its wrapper; the rail's stylesheet has no rule for depth yet.
54
+ style: entry.depth ? { marginLeft: "14px" } : undefined,
55
+ },
32
56
  h(
33
57
  "div",
34
58
  { class: "wf-head", onClick: () => void api.selectWorkflow(name, { push: true }) },
@@ -45,8 +69,17 @@ function workflowItem(name: string, store: Store, schemas: ReadonlyMap<string, u
45
69
  store.expanded.has(name) ? "▾" : "▸",
46
70
  ),
47
71
  h("span", { class: "wf-name" }, name),
72
+ tag ? h("span", { class: "wf-count wf-origin" }, tag) : null,
73
+ entry.unrunnable ? h("span", { class: "wf-count wf-unrunnable", title: entry.unrunnable }, "unrunnable") : null,
74
+ entry.shadows
75
+ ? h(
76
+ "span",
77
+ { class: "wf-count wf-unrunnable", title: entry.shadows.unrunnable ?? "a Chart of this name is shadowed" },
78
+ entry.shadows.origin === "runtime" ? "shadows a chart" : "shadows a chart file",
79
+ )
80
+ : null,
48
81
  h("span", { class: "wf-count" }, String(rows.filter((r) => !r.settled).length)),
49
- store.token === "live"
82
+ store.token === "live" && !entry.unrunnable
50
83
  ? h(
51
84
  "button",
52
85
  {
@@ -98,6 +131,8 @@ function runRows(
98
131
  function runRow(name: string, run: ObservedRun, settled: boolean, store: Store, api: AppApi): JSX.Element {
99
132
  const gates = store.gates.get(run.runId)?.gates.length ?? 0;
100
133
  const selected = name === store.workflow && run.runId === store.selectedRunId;
134
+ // A dispatched run names its caller on the row (ADR-0071), off the open observation.
135
+ const badge = dispatchBadge(run);
101
136
  return h(
102
137
  "li",
103
138
  {
@@ -109,6 +144,17 @@ function runRow(name: string, run: ObservedRun, settled: boolean, store: Store,
109
144
  },
110
145
  },
111
146
  h("span", { class: "run-id" }, run.runId.slice(0, 8)),
147
+ badge && run.dispatchedBy
148
+ ? h(
149
+ "span",
150
+ {
151
+ class: "dispatch-badge dim",
152
+ title: `dispatched by ${run.dispatchedBy.run} (${run.dispatchedBy.slot}), depth ${run.dispatchedBy.depth}`,
153
+ style: { fontSize: "11px", marginRight: "6px" },
154
+ },
155
+ badge,
156
+ )
157
+ : null,
112
158
  gates
113
159
  ? h("span", { class: "gate-badge", title: `${gates} open gate${gates === 1 ? "" : "s"}` }, `⚑${gates}`)
114
160
  : null,
@@ -5,7 +5,15 @@
5
5
 
6
6
  import { h, type JSX } from "preact";
7
7
  import { useEffect, useRef } from "preact/hooks";
8
- import type { GateCard, ObservedRun } from "../store.ts";
8
+ import {
9
+ dispatchStrip,
10
+ dispatchedStrip as dispatchedStripText,
11
+ docSeats,
12
+ type GateCard,
13
+ type HeldLine,
14
+ type ObservedRun,
15
+ type WaitingLine,
16
+ } from "../store.ts";
9
17
  import {
10
18
  clearCanvas,
11
19
  initCanvas,
@@ -21,9 +29,12 @@ export function MachinePane(props: {
21
29
  run: ObservedRun | null;
22
30
  selectedNodeId: string | null;
23
31
  gates: GateCard[];
32
+ waiting: WaitingLine[];
33
+ held: HeldLine[];
34
+ failure: string | null;
24
35
  api: AppApi;
25
36
  }): JSX.Element {
26
- const { view, run, selectedNodeId, gates, api } = props;
37
+ const { view, run, selectedNodeId, gates, waiting, held, failure, api } = props;
27
38
  const canvasRef = useRef<HTMLElement>(null);
28
39
  const svgRef = useRef<SVGSVGElement>(null);
29
40
 
@@ -56,11 +67,37 @@ export function MachinePane(props: {
56
67
  { id: "canvas", ref: canvasRef },
57
68
  h("div", { id: "placeholder", hidden: !view.placeholder }, view.placeholder ?? ""),
58
69
  h("div", { id: "error", hidden: !view.note, class: view.note?.notice ? "notice" : "" }, view.note?.text ?? ""),
70
+ dispatchedStrip(run),
71
+ startedStrip(run),
59
72
  rootTransitionStrip(view.doc),
73
+ seatsPanel(view.doc),
74
+ failureStrip(failure),
75
+ waitingStrip(waiting),
76
+ heldStrip(held),
60
77
  h("svg", { id: "machine-svg", ref: svgRef }),
61
78
  );
62
79
  }
63
80
 
81
+ /** Who dispatched the selected run (ADR-0071): its caller's run, slot and the chain's depth. Read
82
+ * off the open observation the pane already holds, so it shows for every viewer, with no fetch. */
83
+ function dispatchedStrip(run: ObservedRun | null): JSX.Element | null {
84
+ const text = dispatchStrip(run);
85
+ if (!text) return null;
86
+ return h(
87
+ "div",
88
+ { id: "dispatched-by", class: "dim", style: { margin: "28px 28px 0", fontFamily: "var(--mono)" } },
89
+ text,
90
+ );
91
+ }
92
+
93
+ /** The runs the selected run dispatched (ADR-0071): the chain's other direction, off the same
94
+ * open observation, with no fetch. */
95
+ function startedStrip(run: ObservedRun | null): JSX.Element | null {
96
+ const text = dispatchedStripText(run);
97
+ if (!text) return null;
98
+ return h("div", { id: "dispatched", class: "dim", style: { margin: "8px 28px 0", fontFamily: "var(--mono)" } }, text);
99
+ }
100
+
64
101
  /** Machine-level transitions ("from any state") as the strip above the Machine — an edge from the
65
102
  * root would lie, because the root has no box (it renders as the page). */
66
103
  function rootTransitionStrip(doc: MachineDoc | null): JSX.Element {
@@ -83,3 +120,92 @@ function rootTransitionStrip(doc: MachineDoc | null): JSX.Element {
83
120
  }),
84
121
  );
85
122
  }
123
+
124
+ /** Who may send what, per state, and the load warnings (ADR-0015) — what `jr2 up` prints, for the
125
+ * Machine and every Machine it composes. Structure, so tokenless too. Folded by default: the
126
+ * diagram keeps the pane, and a warning shows in the summary so it is not missed folded. */
127
+ function seatsPanel(doc: MachineDoc | null): JSX.Element | null {
128
+ const { seats, warnings } = docSeats(doc);
129
+ if (!seats.length && !warnings.length) return null;
130
+ return h(
131
+ "details",
132
+ { id: "seats" },
133
+ h(
134
+ "summary",
135
+ null,
136
+ "who may send what",
137
+ h("span", { class: "dim" }, ` · ${seats.length} seat${seats.length === 1 ? "" : "s"}`),
138
+ warnings.length
139
+ ? h("span", { class: "seats-warned" }, ` · ${warnings.length} warning${warnings.length === 1 ? "" : "s"}`)
140
+ : null,
141
+ ),
142
+ h(
143
+ "ul",
144
+ { class: "seat-list" },
145
+ seats.map((seat) =>
146
+ h(
147
+ "li",
148
+ { key: `${seat.state} ${seat.party} ${seat.agent ?? ""}` },
149
+ h("span", { class: "seat-state" }, seat.state),
150
+ h("span", { class: "dim" }, seat.party === "agent" ? ` agent:${seat.agent}` : " gate"),
151
+ h("span", { class: "seat-names" }, ` ${seat.names.length ? seat.names.join(", ") : "—"}`),
152
+ ),
153
+ ),
154
+ ),
155
+ warnings.length
156
+ ? h(
157
+ "ul",
158
+ { class: "seat-warnings" },
159
+ warnings.map((w) => h("li", { key: w }, w)),
160
+ )
161
+ : null,
162
+ );
163
+ }
164
+
165
+ /** Why the selected run failed (ADR-0073): the reason that the Machine gave to `fail`, as `jr2 status`
166
+ * prints it. The rail colors a `failed` run for every viewer; the reason is in the run's output,
167
+ * which is guarded, so it is here only with a live token. */
168
+ function failureStrip(failure: string | null): JSX.Element {
169
+ return h("div", { id: "failure", hidden: failure === null }, failure === null ? "" : `failed: ${failure}`);
170
+ }
171
+
172
+ /** Why the selected run's Workspaces in `placing` wait (ADR-0064), one line each, in the
173
+ * scheduler's or the quota's own words. The diagram lights `placing` for every viewer; this reason
174
+ * is guarded, so it is here only with a live token. */
175
+ function waitingStrip(waiting: WaitingLine[]): JSX.Element {
176
+ return h(
177
+ "ul",
178
+ { id: "waiting" },
179
+ waiting.map((w) =>
180
+ h(
181
+ "li",
182
+ { key: w.child },
183
+ h(
184
+ "span",
185
+ { class: "waiting-head" },
186
+ `${w.child || "Workspace"} waits for ${w.on === "quota" ? "quota" : "a node"}`,
187
+ ),
188
+ h("span", { class: "dim", title: w.since }, ` since ${new Date(w.since).toLocaleTimeString()}`),
189
+ h("div", { class: "waiting-message" }, w.message),
190
+ ),
191
+ ),
192
+ );
193
+ }
194
+
195
+ /** The selected run's outstanding picks (ADR-0024): the Agent, its pick, and since when it is held.
196
+ * A held Turn has no default time bound, so this is where a human sees one that waits too long.
197
+ * Guarded like the waiting strip: here only with a live token. */
198
+ function heldStrip(held: HeldLine[]): JSX.Element {
199
+ return h(
200
+ "ul",
201
+ { id: "held" },
202
+ held.map((line) =>
203
+ h(
204
+ "li",
205
+ { key: `${line.agent} ${line.pick}` },
206
+ h("span", { class: "held-head" }, `${line.agent || "Agent"}'s pick ${line.pick} runs`),
207
+ h("span", { class: "dim", title: line.since }, ` held since ${new Date(line.since).toLocaleTimeString()}`),
208
+ ),
209
+ ),
210
+ );
211
+ }
@@ -50,7 +50,7 @@ export function Nav(props: {
50
50
  h("button", { id: "zoom-out", title: "zoom out", onClick: () => api.zoomOut() }, "−"),
51
51
  h("button", { id: "zoom-reset", title: "reset zoom", onClick: () => api.zoomReset() }, `${zoomPct}%`),
52
52
  h("button", { id: "zoom-in", title: "zoom in", onClick: () => api.zoomIn() }, "+"),
53
- h("button", { id: "zoom-fit", title: "fit to width", onClick: () => api.zoomFit() }, "fit"),
53
+ h("button", { id: "zoom-fit", title: "fit the whole Machine", onClick: () => api.zoomFit() }, "fit"),
54
54
  ),
55
55
  h(
56
56
  "button",
@@ -1,15 +1,14 @@
1
1
  // The shared form (start-run + gate delivery — ADR-0033): one component for both writes the
2
2
  // Console makes. A flat object schema becomes typed inputs (string/number/boolean/enum, required
3
- // marked), no schema becomes a raw JSON textarea. `onSubmit` resolves to an error string to render
3
+ // marked; an object or array property is a JSON textarea, so a Chart wrapper's `chart` field —
4
+ // ADR-0070 — goes as an object, not as a string), no schema becomes a raw JSON textarea. `onSubmit` resolves to an error string to render
4
5
  // inline (the server's 400 names the accepted shape — that text IS the form's error display) or null when the write
5
6
  // landed. The inputs are UNCONTROLLED: what the reader is typing is theirs, not state, and the
6
7
  // vdom's keyed diff keeps the elements — and so the half-typed text — alive across status frames.
7
8
 
8
9
  import { h, type JSX } from "preact";
9
10
  import { useState } from "preact/hooks";
10
-
11
- /** One property of a flat object schema, as far as the form reads it. */
12
- type SchemaProp = { type?: string; enum?: unknown[]; description?: string };
11
+ import { jsonField, type SchemaProp } from "../json-field.ts";
13
12
 
14
13
  /** The schema's `properties`, or null when there is no schema to generate from (→ raw textarea). */
15
14
  function propsOf(schema: unknown): Record<string, SchemaProp> | null {
@@ -89,12 +88,16 @@ function field(key: string, prop: SchemaProp, required: Set<unknown>): JSX.Eleme
89
88
  );
90
89
  } else if (prop.type === "number" || prop.type === "integer") {
91
90
  input = h("input", { name: key, type: "number", step: "any" });
91
+ } else if (isJsonType(prop.type)) {
92
+ input = h("textarea", { name: key, rows: 4, placeholder: `JSON ${prop.type}` });
92
93
  } else {
93
94
  input = h("input", { name: key, type: "text" });
94
95
  }
95
96
  return h("label", { key, title: prop.description }, h("span", null, required.has(key) ? `${key} *` : key), input);
96
97
  }
97
98
 
99
+ const isJsonType = (type: string | undefined): type is "object" | "array" => type === "object" || type === "array";
100
+
98
101
  /** The submit body, read back off the DOM the fields live in. Typed fields: empty optional inputs
99
102
  * are ABSENT, not "" — the server's schema is the authority on required-ness and says so in its
100
103
  * 400. The raw textarea must parse to an object. */
@@ -116,6 +119,11 @@ function collect(form: HTMLFormElement, properties: Record<string, SchemaProp> |
116
119
  const body: Record<string, unknown> = {};
117
120
  for (const [key, prop] of Object.entries(properties)) {
118
121
  const el = form.elements.namedItem(key);
122
+ if (el instanceof HTMLTextAreaElement) {
123
+ const value = jsonField(key, prop, el.value);
124
+ if (value !== undefined) body[key] = value;
125
+ continue;
126
+ }
119
127
  if (!(el instanceof HTMLInputElement || el instanceof HTMLSelectElement)) continue;
120
128
  if (el instanceof HTMLInputElement && el.type === "checkbox") {
121
129
  body[key] = el.checked; // a required boolean: always an answer, by design
@@ -0,0 +1,23 @@
1
+ // An object or array property's textarea, parsed (ADR-0033, ADR-0070): a Chart wrapper's one-shot
2
+ // door takes `chart` as an object, so the Console's start form sends JSON of the property's type,
3
+ // never a string. DOM-free, so the Node test project can read it without the browser libs.
4
+
5
+ /** One property of a flat object schema, as far as the form reads it. */
6
+ export type SchemaProp = { type?: string; enum?: unknown[]; description?: string };
7
+
8
+ /** An object or array property's textarea, parsed: absent when empty, an error that names the
9
+ * field when the text is not JSON of the property's type. */
10
+ export function jsonField(key: string, prop: SchemaProp, text: string): unknown {
11
+ const trimmed = text.trim();
12
+ if (!trimmed) return undefined;
13
+ let parsed: unknown;
14
+ try {
15
+ parsed = JSON.parse(trimmed);
16
+ } catch {
17
+ throw new Error(`${key}: not valid JSON`);
18
+ }
19
+ const isArray = Array.isArray(parsed);
20
+ const ok = prop.type === "array" ? isArray : parsed !== null && typeof parsed === "object" && !isArray;
21
+ if (!ok) throw new Error(`${key}: must be a JSON ${prop.type}`);
22
+ return parsed;
23
+ }