@jr2/orchestrator 0.5.1 → 0.7.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 (350) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +142 -29
  14. package/docs/concepts/architecture.md +60 -34
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +13 -8
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +7 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +133 -50
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +14 -4
  23. package/docs/concepts/pools-and-sources.md +193 -17
  24. package/docs/concepts/secrets-and-trust.md +186 -62
  25. package/docs/concepts/the-menu.md +14 -0
  26. package/docs/concepts/workspaces.md +135 -56
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  32. package/docs/get-started/install.md +4 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +72 -15
  34. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  35. package/docs/guides/answer-a-gate.md +74 -37
  36. package/docs/guides/bind-open-parts.md +29 -12
  37. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  38. package/docs/guides/cancel-a-run.md +26 -16
  39. package/docs/guides/cut-a-worktree.md +560 -0
  40. package/docs/guides/fix-a-failed-converge.md +19 -7
  41. package/docs/guides/give-a-caller-a-scope.md +203 -0
  42. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  43. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  44. package/docs/guides/let-an-agent-write-a-chart.md +14 -7
  45. package/docs/guides/look-inside-a-sandbox.md +115 -42
  46. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  47. package/docs/guides/open-the-console.md +63 -14
  48. package/docs/guides/push-and-sign-a-branch.md +67 -21
  49. package/docs/guides/read-a-conversation.md +225 -0
  50. package/docs/guides/run-a-chart-by-name.md +17 -16
  51. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  52. package/docs/guides/run-one-worker-per-item.md +34 -27
  53. package/docs/guides/set-an-agents-access-and-dials.md +69 -22
  54. package/docs/guides/size-a-workspace.md +19 -6
  55. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  56. package/docs/guides/start-a-run-from-a-run.md +18 -16
  57. package/docs/guides/start-a-run.md +24 -18
  58. package/docs/guides/steer-an-agent.md +332 -0
  59. package/docs/guides/use-a-hosted-provider.md +7 -1
  60. package/docs/guides/watch-a-run.md +146 -117
  61. package/docs/guides/write-a-hook-kind.md +238 -0
  62. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  63. package/docs/guides/write-a-workflow.md +18 -8
  64. package/docs/llms.txt +110 -23
  65. package/docs/reference/authoring-surface/agent.md +35 -12
  66. package/docs/reference/authoring-surface/answer.md +20 -4
  67. package/docs/reference/authoring-surface/arg.md +28 -6
  68. package/docs/reference/authoring-surface/basic.md +1 -1
  69. package/docs/reference/authoring-surface/bearer.md +1 -1
  70. package/docs/reference/authoring-surface/branchname.md +15 -0
  71. package/docs/reference/authoring-surface/carry.md +23 -0
  72. package/docs/reference/authoring-surface/chartschema.md +1 -1
  73. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  74. package/docs/reference/authoring-surface/command.md +37 -22
  75. package/docs/reference/authoring-surface/customize.md +4 -1
  76. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  77. package/docs/reference/authoring-surface/defineevent.md +1 -1
  78. package/docs/reference/authoring-surface/dispatch.md +5 -4
  79. package/docs/reference/authoring-surface/fail.md +18 -5
  80. package/docs/reference/authoring-surface/file.md +37 -17
  81. package/docs/reference/authoring-surface/frompick.md +1 -1
  82. package/docs/reference/authoring-surface/gate.md +118 -0
  83. package/docs/reference/authoring-surface/group-agents.md +15 -0
  84. package/docs/reference/authoring-surface/group-charts.md +16 -0
  85. package/docs/reference/authoring-surface/group-composition.md +25 -0
  86. package/docs/reference/authoring-surface/group-config.md +17 -0
  87. package/docs/reference/authoring-surface/group-effects.md +48 -0
  88. package/docs/reference/authoring-surface/group-failure.md +13 -0
  89. package/docs/reference/authoring-surface/group-gates.md +16 -0
  90. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  91. package/docs/reference/authoring-surface/group-steering.md +12 -0
  92. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  93. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  94. package/docs/reference/authoring-surface/http.md +109 -9
  95. package/docs/reference/authoring-surface/isfailure.md +1 -1
  96. package/docs/reference/authoring-surface/isready.md +14 -0
  97. package/docs/reference/authoring-surface/jr2setup.md +14 -4
  98. package/docs/reference/authoring-surface/mcp.md +107 -0
  99. package/docs/reference/authoring-surface/open.md +41 -8
  100. package/docs/reference/authoring-surface/openbody.md +1 -1
  101. package/docs/reference/authoring-surface/paramsof.md +23 -5
  102. package/docs/reference/authoring-surface/pool.md +79 -11
  103. package/docs/reference/authoring-surface/push.md +31 -13
  104. package/docs/reference/authoring-surface/readyof.md +21 -0
  105. package/docs/reference/authoring-surface/reread.md +19 -0
  106. package/docs/reference/authoring-surface/secret.md +26 -5
  107. package/docs/reference/authoring-surface/sentby.md +1 -1
  108. package/docs/reference/authoring-surface/skip.md +22 -0
  109. package/docs/reference/authoring-surface/source.md +59 -4
  110. package/docs/reference/authoring-surface/steer.md +93 -0
  111. package/docs/reference/authoring-surface/text.md +1 -1
  112. package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
  113. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  114. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  115. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  116. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  117. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  118. package/docs/reference/authoring-surface/type-binding.md +6 -7
  119. package/docs/reference/authoring-surface/type-caller.md +40 -0
  120. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  121. package/docs/reference/authoring-surface/type-carry.md +19 -0
  122. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  123. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  124. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  125. package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
  126. package/docs/reference/authoring-surface/type-customize.md +20 -3
  127. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  128. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  129. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  130. package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
  131. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  132. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  133. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  134. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  135. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  136. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  137. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  138. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  139. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  140. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  141. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  142. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  143. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  144. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  145. package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
  146. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  147. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
  149. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  150. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  151. package/docs/reference/authoring-surface/type-lends.md +5 -1
  152. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  153. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  154. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  155. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  156. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  157. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  158. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  159. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  160. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  161. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  162. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  163. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  164. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  165. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  166. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  167. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  168. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  169. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  170. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  171. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  172. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  173. package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
  174. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  175. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  176. package/docs/reference/authoring-surface/type-reposlot.md +6 -6
  177. package/docs/reference/authoring-surface/type-reread.md +18 -0
  178. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  179. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  180. package/docs/reference/authoring-surface/type-sender.md +26 -1
  181. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  182. package/docs/reference/authoring-surface/type-size.md +1 -1
  183. package/docs/reference/authoring-surface/type-skip.md +19 -0
  184. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  185. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  186. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  187. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  188. package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
  189. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  190. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  191. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  192. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  193. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  194. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  195. package/docs/reference/authoring-surface/type-workspaced.md +30 -9
  196. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  197. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  198. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  199. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  200. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  201. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  202. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  203. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  204. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  205. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  206. package/docs/reference/authoring-surface/workspace.md +66 -38
  207. package/docs/reference/authoring-surface/worktree.md +71 -0
  208. package/docs/reference/authoring-surface.md +88 -147
  209. package/docs/reference/cli.md +413 -74
  210. package/docs/reference/config.md +248 -6
  211. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  212. package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
  213. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  214. package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
  215. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  216. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  217. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  218. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  219. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  220. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
  221. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
  222. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  223. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
  224. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  225. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
  226. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  227. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
  228. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
  229. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  230. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
  231. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
  232. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  233. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
  234. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
  235. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  236. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  237. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
  238. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  239. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
  240. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  241. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
  242. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
  243. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  244. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  245. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  246. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  247. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  248. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  249. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
  250. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
  251. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
  252. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
  253. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
  254. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  255. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  256. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
  257. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
  258. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
  259. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  260. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
  261. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  262. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  263. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  264. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
  265. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
  266. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  267. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  268. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  269. package/docs/reference/decisions.md +9 -4
  270. package/docs/reference/glossary.md +138 -45
  271. package/docs/reference/hooks.md +1077 -0
  272. package/docs/reference/http-api.md +988 -123
  273. package/docs/reference/machines.md +0 -1
  274. package/docs/why.md +7 -5
  275. package/package.json +10 -3
  276. package/src/actor.ts +194 -42
  277. package/src/agent.ts +56 -6
  278. package/src/ambient.ts +3 -5
  279. package/src/assembly-checks.ts +530 -4
  280. package/src/callers.ts +245 -0
  281. package/src/chart.ts +238 -26
  282. package/src/charts.ts +9 -2
  283. package/src/config.ts +566 -7
  284. package/src/conversations.ts +453 -0
  285. package/src/custodian.lua +17 -5
  286. package/src/custodian.ts +257 -77
  287. package/src/customize.ts +154 -7
  288. package/src/dispatch-effect.ts +142 -26
  289. package/src/durability.ts +2 -0
  290. package/src/duration.ts +20 -0
  291. package/src/effect-checks.ts +52 -4
  292. package/src/effect.ts +401 -132
  293. package/src/effector-wire.ts +278 -30
  294. package/src/fail.ts +17 -4
  295. package/src/gate-brand.ts +16 -0
  296. package/src/gate.ts +634 -48
  297. package/src/harness-client.ts +84 -35
  298. package/src/harness-env.ts +33 -0
  299. package/src/held-secrets.ts +197 -39
  300. package/src/holds.ts +5 -2
  301. package/src/hook-kinds/discord.ts +92 -0
  302. package/src/hook-kinds/door.ts +107 -0
  303. package/src/hook-kinds/github.ts +89 -0
  304. package/src/hook-kinds/gitlab.ts +72 -0
  305. package/src/hook-kinds/hook.ts +7 -0
  306. package/src/hook-kinds/index.ts +465 -0
  307. package/src/hook-kinds/slack.ts +151 -0
  308. package/src/hook-kinds/webhook.ts +113 -0
  309. package/src/hooks.ts +506 -0
  310. package/src/http-effect.ts +164 -70
  311. package/src/http.ts +933 -99
  312. package/src/index.ts +30 -5
  313. package/src/instance.ts +196 -26
  314. package/src/internal.ts +23 -3
  315. package/src/kube-client.ts +6 -3
  316. package/src/machine-doc.ts +29 -5
  317. package/src/mcp-effect.ts +424 -0
  318. package/src/model-keys.ts +24 -0
  319. package/src/mount-digest.ts +41 -7
  320. package/src/names.ts +82 -7
  321. package/src/notices.ts +576 -59
  322. package/src/open-body.ts +110 -30
  323. package/src/open-effect.ts +17 -4
  324. package/src/open.ts +40 -7
  325. package/src/parts.ts +125 -35
  326. package/src/party.ts +35 -1
  327. package/src/pick.ts +43 -9
  328. package/src/pool.ts +1172 -172
  329. package/src/registration.ts +249 -17
  330. package/src/repo-identity.ts +7 -3
  331. package/src/repos.ts +20 -2
  332. package/src/run-host.ts +1614 -190
  333. package/src/run-list.ts +191 -0
  334. package/src/sandbox-effect.ts +217 -66
  335. package/src/sandbox-kube.ts +116 -21
  336. package/src/secret-placement.ts +248 -35
  337. package/src/server.ts +53 -4
  338. package/src/setup.ts +158 -38
  339. package/src/snapshot-store.ts +565 -48
  340. package/src/steer.ts +111 -0
  341. package/src/stub-harness.ts +6 -2
  342. package/src/tokens.ts +362 -46
  343. package/src/vocabulary.ts +23 -4
  344. package/src/wire.ts +27 -10
  345. package/src/workspace.ts +314 -259
  346. package/src/worktree.ts +249 -0
  347. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  348. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
  349. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  350. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -50,6 +50,7 @@ import type {
50
50
  EffectLogic,
51
51
  EffectMarker,
52
52
  EffectRow,
53
+ EffectParts,
53
54
  EffectSnapshot,
54
55
  HttpParts,
55
56
  OpenHttpParts,
@@ -77,6 +78,7 @@ import {
77
78
  type WireBody,
78
79
  } from "./effector-wire.ts";
79
80
  import { boundRun, type AnyActorSystem } from "./registration.ts";
81
+ import type { AnyWorktreePath } from "./worktree.ts";
80
82
  import { open } from "./open.ts";
81
83
  import { effectBindingShape, openEffectLogic, openPartsOf, type OpenOf } from "./open-effect.ts";
82
84
  import { httpFileWith, sandboxEffectorPort } from "./sandbox-effect.ts";
@@ -105,9 +107,9 @@ declare const openSecretMark: unique symbol;
105
107
  */
106
108
  export type OpenSecretMark = { readonly [openSecretMark]: true };
107
109
 
108
- /** A named secret, never `secret(open)`: where a definition's Open part is a whole that holds a
109
- * secret — a dispatch's target (ADR-0071) — the secret is bound with it, to a name, so the mark
110
- * `secret(open)` carries may not be present. One type for the definition and the binding. */
110
+ /** A secret bound to a name, never `secret(open)`. Where a definition's Open part is a whole that
111
+ * holds a secret — a dispatch's target (ADR-0071) — the secret is bound with it, to a name, so the
112
+ * mark `secret(open)` carries may not be present. One type for the definition and the binding. */
111
113
  export type NamedSecret = Secret & { readonly [K in keyof OpenSecretMark]?: never };
112
114
 
113
115
  /** `secret(open)`: a secret a packaged Machine names in its auth, a header or a command's env, and
@@ -169,13 +171,25 @@ const openSecretOf = new WeakMap<object, object>();
169
171
  *
170
172
  * @example
171
173
  * ```ts
172
- * import { command, fail, jr2Setup, secret, workspace, type Workspaced } from "@jr2/orchestrator";
174
+ * import {
175
+ * command,
176
+ * fail,
177
+ * isReady,
178
+ * jr2Setup,
179
+ * secret,
180
+ * workspace,
181
+ * worktree,
182
+ * type Workspaced,
183
+ * } from "@jr2/orchestrator";
173
184
  *
174
185
  * type Input = Workspaced<{}, "app">;
175
186
  *
187
+ * const deployTree = (context: Input) => `${context.workspace.repos.app}/deploy` as const;
188
+ *
176
189
  * const body = jr2Setup({
177
190
  * types: {} as { context: Input; input: Input },
178
191
  * actors: {
192
+ * cut: worktree(),
179
193
  * // The command reads the file at $KUBECONFIG. The Agent never reads the secret.
180
194
  * deploy: command(["kubectl", "apply", "-f", "k8s/"], {
181
195
  * env: { KUBECONFIG: secret("kube").path },
@@ -186,17 +200,26 @@ const openSecretOf = new WeakMap<object, object>();
186
200
  * id: "deploy",
187
201
  * context: ({ input }) => input,
188
202
  * on: { "workspace.lost": ".lost" },
189
- * initial: "deploying",
203
+ * initial: "cutting",
190
204
  * states: {
191
- * deploying: { invoke: { src: "deploy", onDone: "done" } },
205
+ * cutting: {
206
+ * invoke: {
207
+ * src: "cut",
208
+ * input: ({ context }) => ({ path: deployTree(context), branch: "deploy" }),
209
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "deploying" }, { target: "notCut" }],
210
+ * },
211
+ * },
212
+ * deploying: {
213
+ * invoke: { src: "deploy", input: ({ context }) => ({ worktree: deployTree(context) }), onDone: "done" },
214
+ * },
192
215
  * done: { type: "final" },
216
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
193
217
  * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
194
218
  * },
195
219
  * });
196
220
  *
197
221
  * export const machine = workspace(body, {
198
222
  * repos: { app: { url: "https://github.com/you/app.git" } },
199
- * spec: () => ({ branch: "deploy" }),
200
223
  * });
201
224
  * ```
202
225
  *
@@ -501,15 +524,12 @@ function asText(v: string | Secret | Text): Text {
501
524
  /** A value of a file body's part: as written, or from the Effect's input. */
502
525
  export type FromInput<S, T> = T | ((input: S extends z.ZodType ? z.infer<S> : void) => T);
503
526
 
504
- /** `file(path, { slot?, segments?, contentType? })`: a Worktree file as an `http` body. */
527
+ /** `file(path, { segments?, contentType? })`: a Worktree file as an `http` body. */
505
528
  export type FileBody<S extends z.ZodType | undefined = undefined> = {
506
529
  readonly file: {
507
- /** The file, relative to the Repo Slot's Worktree, or to `/work` in a Workspace with no Repo
508
- * Slot. */
530
+ /** The file, relative to the Worktree that `worktree` of the Effect's input names, or to `/work`
531
+ * in a Workspace with no Repo Slot. */
509
532
  readonly path: FromInput<S, string>;
510
- /** The Repo Slot whose Worktree holds it. Write it only when the Workspace has more than one.
511
- */
512
- readonly slot?: string;
513
533
  /** Path segments that jr2 adds to the end of the url, as `path` of `request` does for a json
514
534
  * or text body. */
515
535
  readonly segments?: FromInput<S, readonly string[]>;
@@ -521,14 +541,18 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
521
541
 
522
542
  /**
523
543
  * Make a Worktree file the body of an `http` Effect: `body: file("dist/report.pdf")`. The Effect
524
- * then runs in the Sandbox Effector of the Workspace whose body invokes it. That Effector opens the
525
- * file in the Worktree and sends it. In a Workspace with no Repo Slot, the path is relative to
544
+ * then runs in the Sandbox Effector of the Workspace whose body invokes it. Under a Workspace with
545
+ * Repo Slots, the invoke's input names the Worktree: `input: { worktree: "/work/app/fix" }`. It is a
546
+ * Worktree that a cut of this run made. That Effector opens the file in that Worktree and sends it.
547
+ * In a Workspace with no Repo Slot, the input names no `worktree`, and the path is relative to
526
548
  * `/work`, the folder that its steps share. Use it with `PUT`, `POST` or `PATCH`. `path` and
527
549
  * `segments` can be functions of the input of the Effect.
528
550
  *
529
- * Some paths and files give the output `refused`, and then nothing is sent. The path has a symlink
530
- * in it, has `..`, or is absolute. Or the file is a FIFO, a directory, or a file with more than one
531
- * link.
551
+ * Some inputs, paths and files give the output `refused`, and then nothing is sent. The `worktree`
552
+ * has no cut in this Sandbox: `no cut made <path> in this Sandbox: cut it with worktree(); nothing
553
+ * was sent`. Or the input names a `worktree` in a Workspace with no Repo Slot. Or the path has a
554
+ * symlink in it, has `..`, or is absolute. Or the file is a FIFO, a directory, or a file with more
555
+ * than one link.
532
556
  *
533
557
  * It refuses, when you call it:
534
558
  * - a path that is not a string or a function: `file(path): the path is a string, or a function of
@@ -548,8 +572,13 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
548
572
  * there is no Worktree to read; invoke it from a workspace()'s body, or send a json or text body
549
573
  * (ADR-0068)`.
550
574
  *
551
- * @param path - The file, relative to the Worktree of the Repo Slot, or to `/work` in a Workspace
552
- * with no Repo Slot: a string, or a function of the input of the Effect that gives one.
575
+ * And when it starts, under a Workspace with Repo Slots, an input with no `worktree`: ``http
576
+ * "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its input names no
577
+ * `worktree` ``.
578
+ *
579
+ * @param path - The file, relative to the Worktree that `worktree` of the input names, or to
580
+ * `/work` in a Workspace with no Repo Slot: a string, or a function of the input of the Effect
581
+ * that gives one.
553
582
  * @param options - Optional. Each field says what it does.
554
583
  * @returns The `body` of an `http` Effect.
555
584
  *
@@ -561,17 +590,22 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
561
590
  * fail,
562
591
  * file,
563
592
  * http,
593
+ * isReady,
564
594
  * jr2Setup,
565
595
  * secret,
566
596
  * workspace,
597
+ * worktree,
567
598
  * type Workspaced,
568
599
  * } from "@jr2/orchestrator";
569
600
  *
570
601
  * type Input = Workspaced<{}, "app">;
571
602
  *
603
+ * const reportTree = (context: Input) => `${context.workspace.repos.app}/report` as const;
604
+ *
572
605
  * const body = jr2Setup({
573
606
  * types: {} as { context: Input; input: Input },
574
607
  * actors: {
608
+ * cut: worktree(),
575
609
  * build: command(["npm", "run", "report"]),
576
610
  * upload: http({
577
611
  * method: "PUT",
@@ -588,18 +622,29 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
588
622
  * id: "report",
589
623
  * context: ({ input }) => input,
590
624
  * on: { "workspace.lost": ".lost" },
591
- * initial: "building",
625
+ * initial: "cutting",
592
626
  * states: {
593
- * building: { invoke: { src: "build", onDone: "uploading" } },
594
- * uploading: { invoke: { src: "upload", onDone: "done" } },
627
+ * cutting: {
628
+ * invoke: {
629
+ * src: "cut",
630
+ * input: ({ context }) => ({ path: reportTree(context), branch: "report" }),
631
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "building" }, { target: "notCut" }],
632
+ * },
633
+ * },
634
+ * building: {
635
+ * invoke: { src: "build", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "uploading" },
636
+ * },
637
+ * uploading: {
638
+ * invoke: { src: "upload", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "done" },
639
+ * },
595
640
  * done: { type: "final" },
641
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
596
642
  * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
597
643
  * },
598
644
  * });
599
645
  *
600
646
  * export const machine = workspace(body, {
601
647
  * repos: { app: { url: "https://github.com/you/app.git" } },
602
- * spec: () => ({ branch: "report" }),
603
648
  * });
604
649
  * ```
605
650
  *
@@ -610,9 +655,6 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
610
655
  export function file<S extends z.ZodType | undefined = undefined>(
611
656
  path: FromInput<S, string>,
612
657
  options: {
613
- /** The Repo Slot whose Worktree holds the file. Write it only when the Workspace has more than
614
- * one. */
615
- slot?: string;
616
658
  /**
617
659
  * Path segments that jr2 adds to the end of the url, as `path` does in `request` for a json or
618
660
  * text body. A list of strings, or a function of the input of the Effect that gives one.
@@ -633,7 +675,6 @@ export function file<S extends z.ZodType | undefined = undefined>(
633
675
  return {
634
676
  file: {
635
677
  path,
636
- ...(options.slot !== undefined ? { slot: options.slot } : {}),
637
678
  ...(options.segments !== undefined ? { segments: options.segments } : {}),
638
679
  ...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
639
680
  },
@@ -723,10 +764,13 @@ export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
723
764
  request?: (input: S extends z.ZodType ? z.infer<S> : void) => HttpRequestParts;
724
765
  // A file body is an ADR-0067 Effect: the Sandbox Effector of the Workspace sends it.
725
766
  /**
726
- * A Worktree file as the body: `file(path, { slot?, segments?, contentType? })`. The Effect then
727
- * runs in the Sandbox Effector of its Workspace. Do not write `request` beside it: the `segments`
767
+ * A Worktree file as the body: `file(path, { segments?, contentType? })`. The Effect then
768
+ * runs in the Sandbox Effector of its Workspace. Under a Workspace with Repo Slots, the invoke's
769
+ * input names a Worktree that a cut of this run made, in `worktree`; with none, it refuses when
770
+ * it starts: ``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its
771
+ * input names no `worktree` ``. Do not write `request` beside it: the `segments`
728
772
  * of `file` are the path. It refuses another body: ``http({ body }): a body is file(path, {
729
- * slot?, segments?, contentType? }) — json and text come from `request` ``.
773
+ * segments?, contentType? }) — json and text come from `request` ``.
730
774
  */
731
775
  body?: FileBody<S>;
732
776
  /**
@@ -755,6 +799,11 @@ export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
755
799
  /** @internal */
756
800
  export type HttpInput<S> = S extends z.ZodType ? z.infer<S> : void;
757
801
 
802
+ /** The input of an `http` Effect with a Worktree file as its body. Under a Workspace with Repo
803
+ * Slots, `worktree` names the Worktree. In a Workspace with no Repo Slot, the input has no
804
+ * `worktree`, and the path is relative to `/work`. */
805
+ export type HttpFileInput<S> = (S extends z.ZodType ? z.infer<S> : unknown) & { worktree?: AnyWorktreePath };
806
+
758
807
  const DEFAULT_TIMEOUT_MS = 30_000;
759
808
  const DEFAULT_MAX_BODY = 65_536;
760
809
  /** How long a re-asked request waits, at most, after a transport failure. */
@@ -830,7 +879,7 @@ export function httpParts<S extends z.ZodType | undefined>(def: HttpDefinition<S
830
879
  const body = def.body?.file;
831
880
  if (def.body !== undefined && (typeof body !== "object" || body === null)) {
832
881
  throw new Error(
833
- "http({ body }): a body is file(path, { slot?, segments?, contentType? }) — json and text come from `request`",
882
+ "http({ body }): a body is file(path, { segments?, contentType? }) — json and text come from `request`",
834
883
  );
835
884
  }
836
885
  if (body && !FILE_METHODS.includes(def.method)) {
@@ -866,7 +915,6 @@ export function httpParts<S extends z.ZodType | undefined>(def: HttpDefinition<S
866
915
  ...(body
867
916
  ? {
868
917
  file: {
869
- ...(body.slot !== undefined ? { slot: body.slot } : {}),
870
918
  ...(body.contentType !== undefined ? { contentType: body.contentType } : {}),
871
919
  },
872
920
  }
@@ -1020,8 +1068,58 @@ export function httpWith<S extends z.ZodType | undefined = undefined>(
1020
1068
  const unbound = openHttpOf(def, (bound) => httpWith(portFactory, bound));
1021
1069
  if (unbound) return unbound as EffectLogic<HttpInput<S>, HttpOutput>;
1022
1070
  const parts = httpParts(def);
1023
- type I = HttpInput<S>;
1024
- type O = HttpOutput;
1071
+ return instanceEffectLogic<HttpInput<S>, HttpOutput>(
1072
+ parts,
1073
+ {
1074
+ kind: "http",
1075
+ idempotent: parts.idempotent,
1076
+ label: `${parts.method} ${hostText(parts)}`,
1077
+ request: (input, slot) => {
1078
+ if (def.input !== undefined) {
1079
+ const parsed = def.input.safeParse(input);
1080
+ if (!parsed.success) {
1081
+ throw new Error(
1082
+ `http "${slot}": its input does not match the definition's input schema — ` +
1083
+ parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; "),
1084
+ );
1085
+ }
1086
+ input = parsed.data;
1087
+ }
1088
+ const runParts = def.request ? (def.request as (i: unknown) => HttpRequestParts)(input) : {};
1089
+ return startRequestOf(parts, runParts ?? {});
1090
+ },
1091
+ },
1092
+ portFactory,
1093
+ );
1094
+ }
1095
+
1096
+ /**
1097
+ * What the Instance Effector's driver needs of one kind that runs there and has no stop: `http`
1098
+ * and `mcp` (ADR-0068, ADR-0079). The driver is the same for both: the write-ahead intent row, the
1099
+ * restore rule, a forged answer, and D1.
1100
+ */
1101
+ export type InstanceKind = {
1102
+ readonly kind: "http" | "mcp";
1103
+ /** The author's mark: a restore that finds no replica that holds it sends it again. */
1104
+ readonly idempotent: boolean;
1105
+ /** What the `started` marker says: `POST https://host`, `mcp slack-acme/post_message`. */
1106
+ readonly label: string;
1107
+ /** The run's start, from the invoke's input. It throws on a defect of the Machine, and then
1108
+ * nothing is sent. */
1109
+ readonly request: (input: unknown, slot: string) => StartRequest;
1110
+ };
1111
+
1112
+ /**
1113
+ * The slot logic of an Effect that the Instance Effector runs, over an injected port factory: the
1114
+ * seam `httpWith` and `mcpWith` share.
1115
+ *
1116
+ * @internal
1117
+ */
1118
+ export function instanceEffectLogic<I, O>(
1119
+ parts: EffectParts,
1120
+ spec: InstanceKind,
1121
+ portFactory: EffectorPortFactory,
1122
+ ): EffectLogic<I, O> {
1025
1123
  const logic: ActorLogic<EffectSnapshot<O, I>, EventObject, I> = {
1026
1124
  getInitialSnapshot: (_scope, input) => ({
1027
1125
  status: "active",
@@ -1039,7 +1137,7 @@ export function httpWith<S extends z.ZodType | undefined = undefined>(
1039
1137
  return { ...snapshot, status: "error", error: (event as Fault).error, input: undefined };
1040
1138
  }
1041
1139
  if (event.type === "xstate.stop") {
1042
- stopped(self, system, snapshot.effectId);
1140
+ stopped(self, system, snapshot.effectId, spec.kind);
1043
1141
  return { ...snapshot, status: "stopped", input: undefined };
1044
1142
  }
1045
1143
  return snapshot;
@@ -1048,7 +1146,7 @@ export function httpWith<S extends z.ZodType | undefined = undefined>(
1048
1146
  if (snapshot.status !== "active") return;
1049
1147
  const run: Running = { controller: new AbortController() };
1050
1148
  running.set(self, run);
1051
- void drive(self, system, snapshot, parts, def, portFactory, run).catch((error: unknown) => {
1149
+ void drive(self, system, snapshot, spec, portFactory, run).catch((error: unknown) => {
1052
1150
  if (run.controller.signal.aborted) return;
1053
1151
  if (self.getSnapshot().status === "active") self.send({ type: "jr2.effect.error", error } as Fault);
1054
1152
  });
@@ -1064,8 +1162,7 @@ async function drive<I, O>(
1064
1162
  self: AnyActorRef,
1065
1163
  system: AnyActorSystem,
1066
1164
  snapshot: EffectSnapshot<O, I>,
1067
- parts: HttpParts,
1068
- def: HttpDefinition<z.ZodType | undefined>,
1165
+ spec: InstanceKind,
1069
1166
  portFactory: EffectorPortFactory,
1070
1167
  run: Running,
1071
1168
  ): Promise<void> {
@@ -1076,11 +1173,11 @@ async function drive<I, O>(
1076
1173
  const now = (): string => new Date().toISOString();
1077
1174
  const mark = (marker: Omit<EffectMarker, "kind" | "slot" | "effect">): void =>
1078
1175
  binding?.marker?.({ kind: "effect", slot, effect: id, ...marker });
1079
- const settle = (output: HttpOutput): void => {
1176
+ const settle = (output: NonNullable<EffectMarker["output"]>): void => {
1080
1177
  if (signal.aborted || self.getSnapshot().status !== "active") return;
1081
1178
  // The settled row first, saved at the write: a restart before the transition is saved delivers
1082
1179
  // this output, and sends nothing.
1083
- binding?.recordEffect?.(id, { state: "settled", kind: "http", slot, output, at: now() });
1180
+ binding?.recordEffect?.(id, { state: "settled", kind: spec.kind, slot, output, at: now() });
1084
1181
  mark({ stage: "settled", output });
1085
1182
  self.send({ type: "jr2.effect.settle", output } satisfies Settle);
1086
1183
  };
@@ -1102,8 +1199,8 @@ async function drive<I, O>(
1102
1199
  const target = binding?.effector?.("instance");
1103
1200
  if (!target) {
1104
1201
  throw new Error(
1105
- `http "${slot}" runs in the Instance Effector, and this Orchestrator knows none — \`jr2 up\` converges it ` +
1106
- "when a registered Machine carries an http Effect (ADR-0068)",
1202
+ `${spec.kind} "${slot}" runs in the Instance Effector, and this Orchestrator knows none — \`jr2 up\` ` +
1203
+ `converges it when a registered Machine carries an ${spec.kind} Effect (ADR-0068)`,
1107
1204
  );
1108
1205
  }
1109
1206
  const port = portFactory(target);
@@ -1133,7 +1230,7 @@ async function drive<I, O>(
1133
1230
  await follow(found);
1134
1231
  return;
1135
1232
  }
1136
- if (!parts.idempotent) {
1233
+ if (!spec.idempotent) {
1137
1234
  settle(
1138
1235
  unknown(
1139
1236
  "it was in flight when the Orchestrator restarted, and no Effector replica holds it; not sent again " +
@@ -1142,7 +1239,7 @@ async function drive<I, O>(
1142
1239
  );
1143
1240
  return;
1144
1241
  }
1145
- mark({ stage: "started", text: `${parts.method} ${hostText(parts)} (sent again: it is idempotent)` });
1242
+ mark({ stage: "started", text: `${spec.label} (sent again: it is idempotent)` });
1146
1243
  await send(requestFor());
1147
1244
  return;
1148
1245
  }
@@ -1160,29 +1257,17 @@ async function drive<I, O>(
1160
1257
  // request leaves.
1161
1258
  binding?.recordEffect?.(id, {
1162
1259
  state: "intent",
1163
- kind: "http",
1260
+ kind: spec.kind,
1164
1261
  slot,
1165
1262
  placement: "instance",
1166
- idempotent: parts.idempotent,
1263
+ idempotent: spec.idempotent,
1167
1264
  at: now(),
1168
1265
  });
1169
- mark({ stage: "started", text: `${parts.method} ${hostText(parts)}` });
1266
+ mark({ stage: "started", text: spec.label });
1170
1267
  await send(req);
1171
1268
 
1172
1269
  function requestFor(): StartRequest {
1173
- let input: unknown = snapshot.input;
1174
- if (def.input !== undefined) {
1175
- const parsed = def.input.safeParse(input);
1176
- if (!parsed.success) {
1177
- throw new Error(
1178
- `http "${slot}": its input does not match the definition's input schema — ` +
1179
- parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; "),
1180
- );
1181
- }
1182
- input = parsed.data;
1183
- }
1184
- const runParts = def.request ? (def.request as (i: unknown) => HttpRequestParts)(input) : {};
1185
- return startRequestOf(parts, runParts ?? {});
1270
+ return spec.request(snapshot.input, slot);
1186
1271
  }
1187
1272
 
1188
1273
  /** Send the start, and follow it. A start that never reached a replica is sent again; one whose
@@ -1209,7 +1294,7 @@ async function drive<I, O>(
1209
1294
  await follow(found);
1210
1295
  return;
1211
1296
  }
1212
- if (!parts.idempotent) {
1297
+ if (!spec.idempotent) {
1213
1298
  settle(unknown("its start may have reached the Effector, and no replica holds it now; not sent again"));
1214
1299
  return;
1215
1300
  }
@@ -1230,7 +1315,7 @@ async function drive<I, O>(
1230
1315
  let replica: Replica = first.replica;
1231
1316
  for (;;) {
1232
1317
  if (state.state === "settled" && state.output) {
1233
- settle(state.output as HttpOutput);
1318
+ settle(state.output as NonNullable<EffectMarker["output"]>);
1234
1319
  return;
1235
1320
  }
1236
1321
  if (state.state === "unknown") {
@@ -1238,7 +1323,7 @@ async function drive<I, O>(
1238
1323
  if (!asked || signal.aborted) return;
1239
1324
  const { found } = asked;
1240
1325
  if (!found) {
1241
- if (parts.idempotent) {
1326
+ if (spec.idempotent) {
1242
1327
  await send(requestFor());
1243
1328
  return;
1244
1329
  }
@@ -1269,15 +1354,15 @@ async function drive<I, O>(
1269
1354
  * unknown": an http Effect has no stop route, so the Effector finishes the call and drops an
1270
1355
  * outcome nobody reads. The Machine never hears it.
1271
1356
  */
1272
- function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string): void {
1357
+ function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string, kind: "http" | "mcp"): void {
1273
1358
  const run = running.get(self);
1274
1359
  running.delete(self);
1275
1360
  run?.controller.abort();
1276
1361
  const binding = boundRun(system);
1277
1362
  if (binding?.hostStopping) return;
1278
1363
  const row = binding?.effectRow?.(effectId);
1279
- if (row?.state !== "intent" || row.kind !== "http") return;
1280
- const aborted: EffectRow = { state: "aborted", kind: "http", slot: row.slot, at: new Date().toISOString() };
1364
+ if (row?.state !== "intent" || row.kind !== kind) return;
1365
+ const aborted: EffectRow = { state: "aborted", kind, slot: row.slot, at: new Date().toISOString() };
1281
1366
  binding?.recordEffect?.(effectId, aborted, "with-transition");
1282
1367
  binding?.marker?.({
1283
1368
  kind: "effect",
@@ -1290,7 +1375,7 @@ function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string): v
1290
1375
 
1291
1376
  /** A start over the bytes the Effector reads is output `refused` (ADR-0068): the Effector could not
1292
1377
  * verify it, and a body built from context or a pick's param can reach the bound. */
1293
- function tooLarge(req: StartRequest): HttpOutput | undefined {
1378
+ function tooLarge(req: StartRequest): { outcome: "refused"; reason: string } | undefined {
1294
1379
  const bytes = Buffer.byteLength(JSON.stringify(req), "utf8");
1295
1380
  if (bytes <= MAX_START_BYTES) return undefined;
1296
1381
  return {
@@ -1347,8 +1432,9 @@ function parseDuration(d: Duration): number {
1347
1432
  * src: "comment" }`.
1348
1433
  *
1349
1434
  * It runs in the Instance Effector, which `jr2 up` deploys when a Machine has an `http` Effect.
1350
- * With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it.
1351
- * The Effector makes the call. The Orchestrator holds no secret.
1435
+ * With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it,
1436
+ * and under Repo Slots its input names the Worktree in `worktree`. The Effector makes the call. The
1437
+ * Orchestrator holds no secret.
1352
1438
  *
1353
1439
  * Every outcome is output (`HttpOutput`): `response`, `unreachable`, `refused` or `unknown`. A 4xx
1354
1440
  * or 5xx status is output too. jr2 sends the call at most one time, unless you mark it
@@ -1402,6 +1488,14 @@ function parseDuration(d: Duration): number {
1402
1488
  * @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
1403
1489
  * @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
1404
1490
  */
1491
+ export function http<
1492
+ S extends z.ZodType | undefined = undefined,
1493
+ const U extends HttpDefinition<S>["url"] = string,
1494
+ const A extends HttpDefinition<S>["auth"] = undefined,
1495
+ const H extends HttpDefinition<S>["headers"] = undefined,
1496
+ >(
1497
+ def: HttpDefinition<S> & { url: U; auth?: A; headers?: H; body: FileBody<S> },
1498
+ ): EffectLogic<HttpFileInput<S>, HttpOutput> & OpenOf<HttpOpenParts<U, A, H>>;
1405
1499
  export function http<
1406
1500
  S extends z.ZodType | undefined = undefined,
1407
1501
  const U extends HttpDefinition<S>["url"] = string,