@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
package/src/chart.ts CHANGED
@@ -19,11 +19,13 @@
19
19
  // (open-body.ts), so that `jr2Setup`'s own refusals and load warnings are lines of the same report.
20
20
 
21
21
  import { createHash } from "node:crypto";
22
+ import { posix } from "node:path";
22
23
  import { z } from "zod";
23
24
  import { defineEvent, type EventAudience, type EventDef } from "@jr2/agent-protocol";
24
25
  import type { AgentDefinition, ThinkingLevel, WorkspaceAccess } from "./agent.ts";
25
- import { inputKeysOf, isEffect } from "./effect.ts";
26
+ import { inputKeysOf, inputShapeOf, isEffect, type InputShape } from "./effect.ts";
26
27
  import { FAILURE_GUARD } from "./fail.ts";
28
+ import { isGateSlot } from "./gate.ts";
27
29
  import { compileChart, durationMs, type LentParts, type OpenBodyDeclaration } from "./open-body.ts";
28
30
  import { SENT_BY } from "./party.ts";
29
31
  import { loadWarningsOf } from "./vocabulary.ts";
@@ -140,6 +142,10 @@ const MECHANISM_EVENTS: Record<string, { fields: Record<string, boolean> }> = {
140
142
  * placement; see {@link checkAgentInput}). */
141
143
  const AGENT_INPUT_KEYS = ["prompt", "cwd", "continue", "model", "thinkingLevel"] as const;
142
144
 
145
+ /** The kit's test for a cut that settled `ready`, in every Chart beside `jr2.failure`: a cut's own
146
+ * outcomes are output, so a Chart routes on them, and a Chart has no other way to read one. */
147
+ export const READY_GUARD = "jr2.ready";
148
+
143
149
  /**
144
150
  * The Chart briefing (ADR-0070): jr2's text on how a Chart runs, for whoever writes one. It is the
145
151
  * grammar's root description, so it rides `GET /workflows/<wrapper>/chart-schema` and a pick's tool
@@ -164,6 +170,16 @@ export function chartBriefing(narrow?: Narrow): string {
164
170
  ? ' `src: "fail"` with `input: { reason }` ends the run as a Failure: work that did not succeed, with ' +
165
171
  `the reason for a person; an \`onError\` with the guard \`${FAILURE_GUARD}\` catches it. A final state means done.`
166
172
  : "";
173
+ // A declaration is names alone, so this says "a lent Effect that cuts".
174
+ const cut =
175
+ narrow === undefined || narrow.lendsEffects
176
+ ? ' In a Workspace with Repo Slots, work happens in a Worktree. A lent Effect that cuts one takes `input: { path: "/work/<slot>/<name>", ' +
177
+ 'branch: "<branch>" }`: `<name>` is one folder name that you choose, and it is not the branch. The Open body sets the cut\'s `expect`, ' +
178
+ "so its input has no `expect`. Give its `onDone` two edges: the guard " +
179
+ `\`${READY_GUARD}\` on the one that goes on, and one with no guard for a refused cut. Then write that same literal path as \`cwd\` in an ` +
180
+ "Agent's `input` and as `worktree` in an Effect's `input`: each Turn and each Effect there must say where it works. " +
181
+ "The slot's folder `/work/<slot>` is not a Worktree."
182
+ : "";
167
183
  const agents = mints
168
184
  ? `A state runs an Agent with \`invoke: { src, input: { prompt } }\`: \`src\` is a lent Agent's name${lent}. ` +
169
185
  "This Chart mints no Agent of its own: there is no `agents`."
@@ -171,7 +187,7 @@ export function chartBriefing(narrow?: Narrow): string {
171
187
  "a key of `agents`; a lent Agent needs no entry in `agents`, which mints new ones.";
172
188
  return (
173
189
  `A Chart is a workflow written as data: states, the events its Agents pick${mints ? "" : ", and the Agents it mints"}. ` +
174
- `${agents}${gate}${fail} An Agent's Turn ends only when it ` +
190
+ `${agents}${gate}${fail}${cut} An Agent's Turn ends only when it ` +
175
191
  "picks one of the Chart's `events`: declare each pick in `events` with an `input` schema " +
176
192
  '(`{ "type": "object", "properties": { "<field>": { "type": "string" } }, "required": ["<field>"] }`; a field not in ' +
177
193
  "`required` may be absent), and " +
@@ -238,6 +254,7 @@ export function pathSegments(path: string): Array<string | number> {
238
254
  type Narrow = {
239
255
  srcs: string[];
240
256
  lentAgents: string[];
257
+ lendsEffects: boolean;
241
258
  gate: boolean;
242
259
  fail: boolean;
243
260
  guards: string[];
@@ -363,16 +380,16 @@ function grammar(narrow?: Narrow): z.ZodType<ChartText> {
363
380
  // `jr2.sentBy` is the kit's routing guard (ADR-0015), and `jr2.failure` its test for a Failure on
364
381
  // an `onError` (ADR-0073): every Chart has both, and neither is lent.
365
382
  const sentByArm = z.strictObject({ type: z.literal(SENT_BY), params: z.strictObject({ id: z.string() }) });
366
- const failureArm = z.strictObject({ type: z.literal(FAILURE_GUARD) });
383
+ const failureArm = z.strictObject({ type: z.union([z.literal(FAILURE_GUARD), z.literal(READY_GUARD)]) });
367
384
  const lentGuardName = narrow
368
- ? enumOf([...narrow.guards, FAILURE_GUARD])
385
+ ? enumOf([...narrow.guards, FAILURE_GUARD, READY_GUARD])
369
386
  : z.string().refine((t) => t !== SENT_BY, { message: `"${SENT_BY}" takes params { id }` });
370
387
  const paramGuardName = narrow
371
388
  ? enumOf(narrow.guards)
372
389
  : z
373
390
  .string()
374
391
  .refine((t) => t !== SENT_BY, { message: `"${SENT_BY}" takes params { id }` })
375
- .refine((t) => t !== FAILURE_GUARD, { message: `"${FAILURE_GUARD}" takes no params` });
392
+ .refine((t) => t !== FAILURE_GUARD && t !== READY_GUARD, { message: "that guard takes no params" });
376
393
  const guard: z.ZodType<ChartGuard> = z
377
394
  .union([
378
395
  lentGuardName,
@@ -397,7 +414,7 @@ function grammar(narrow?: Narrow): z.ZodType<ChartText> {
397
414
  ])
398
415
  .meta({
399
416
  id: `${ID_PREFIX}ChartGuard`,
400
- description: `A lent guard by name, \`jr2.sentBy\`, \`${FAILURE_GUARD}\`, or not/and/or of guards.`,
417
+ description: `A lent guard by name, \`jr2.sentBy\`, \`${FAILURE_GUARD}\`, \`${READY_GUARD}\`, or not/and/or of guards.`,
401
418
  }) as never;
402
419
 
403
420
  const lentActionName = narrow
@@ -657,6 +674,7 @@ function lendsOf(decl: OpenBodyDeclaration) {
657
674
  agents: decl.lends.agents ?? [],
658
675
  effects: decl.lends.effects ?? [],
659
676
  machines: decl.lends.machines ?? [],
677
+ gates: decl.lends.gates ?? [],
660
678
  guards: decl.lends.guards ?? [],
661
679
  actions: decl.lends.actions ?? [],
662
680
  delays: decl.lends.delays ?? [],
@@ -679,8 +697,16 @@ export function narrowedChartSchema(decl: OpenBodyDeclaration): Record<string, u
679
697
  function narrowOf(decl: OpenBodyDeclaration): Narrow {
680
698
  const l = lendsOf(decl);
681
699
  return {
682
- srcs: [...l.agents, ...l.effects, ...l.machines, ...(l.gate ? ["gate"] : []), ...(l.fail ? ["fail"] : [])],
700
+ srcs: [
701
+ ...l.agents,
702
+ ...l.effects,
703
+ ...l.machines,
704
+ ...l.gates,
705
+ ...(l.gate ? ["gate"] : []),
706
+ ...(l.fail ? ["fail"] : []),
707
+ ],
683
708
  lentAgents: l.agents,
709
+ lendsEffects: l.effects.length > 0,
684
710
  gate: l.gate,
685
711
  fail: l.fail,
686
712
  guards: l.guards,
@@ -836,8 +862,22 @@ export type AdmittedChart = {
836
862
  agents: Record<string, AgentDefinition>;
837
863
  /** The bytes of the text's canonical JSON. */
838
864
  size: number;
865
+ /** The invoke ids of the Chart's Gates (ADR-0011), as `jr2Setup` names them: the names that the
866
+ * `answer` scope can name them by (ADR-0078). */
867
+ gates: string[];
839
868
  };
840
869
 
870
+ /**
871
+ * Why a Chart's `cwd` is not a place to work, or undefined when it can be: a Repo Slot's folder is no
872
+ * Worktree. There, an Agent's search hits `default/`, and a relative write lands outside each
873
+ * Worktree (ADR-0057, ADR-0075).
874
+ */
875
+ export function slotFolderFault(cwd: string, slots: readonly string[]): string | undefined {
876
+ const slot = posix.normalize(cwd).match(/^\/work\/([^/]+)\/?$/)?.[1];
877
+ if (slot === undefined || !slots.includes(slot)) return undefined;
878
+ return `"${cwd}" is a Repo Slot's folder, not a Worktree; \`cwd\` is a Worktree that a cut made, "/work/${slot}/<name>", or "/work/${slot}/default" to read, or "/work"`;
879
+ }
880
+
841
881
  /** What the door knows beyond the declaration and the lent set. */
842
882
  export type AdmitOptions = {
843
883
  /**
@@ -846,6 +886,11 @@ export type AdmitOptions = {
846
886
  * `input.<key>` reference is checked against it; absent, `input` references are not checked.
847
887
  */
848
888
  inputKeys?: readonly string[];
889
+ /** The Repo Slots of the Workspace around the Open body, where the door can read them. Given and
890
+ * not empty, a Turn's `cwd` and an Effect's `worktree` are required, a literal path's slot is
891
+ * checked, and a literal `cwd` cannot be a slot's folder. Absent: the start of a Turn or an Effect
892
+ * says the same of a missing `cwd`, of a Worktree path, and of a `cwd` that is a slot's folder. */
893
+ repoSlots?: readonly string[];
849
894
  };
850
895
 
851
896
  const MAX_STATES = 200;
@@ -991,6 +1036,13 @@ export function admitChart(
991
1036
  return new Door(chart, decl, lent, options).admit(size);
992
1037
  }
993
1038
 
1039
+ /** What an Effect's input takes, for a line of the door. */
1040
+ function shapeText(shape: InputShape): string {
1041
+ const need = [...shape.required, ...(shape.oneOf ? [`exactly one of ${shape.oneOf.join(", ")}`] : [])].join(", ");
1042
+ const may = shape.optional.length ? `${need ? ", and " : ""}optionally ${shape.optional.join(", ")}` : "";
1043
+ return need || may ? `${need}${may}` : "nothing";
1044
+ }
1045
+
994
1046
  /** The checks from rule 3 on, over a Chart the grammar admitted. */
995
1047
  class Door {
996
1048
  private readonly lines: Line[] = [];
@@ -1024,6 +1076,8 @@ class Door {
1024
1076
  private readonly invokeKinds = new Map<ChartInvoke, NonNullable<ReturnType<Door["srcKind"]>>>();
1025
1077
  /** Every `jr2.sentBy` id, for the party check. */
1026
1078
  private readonly sentBys: Array<{ id: string; path: string }> = [];
1079
+ /** The Gates' invoke ids, which the party check finds. */
1080
+ private readonly gateIds = new Set<string>();
1027
1081
 
1028
1082
  private readonly chart: ChartText;
1029
1083
  private readonly decl: OpenBodyDeclaration;
@@ -1086,7 +1140,14 @@ class Door {
1086
1140
  const events = Object.entries(this.chart.events ?? {}).map(([name, ev]) => this.eventDef(name, ev)!);
1087
1141
  const agents: Record<string, AgentDefinition> = {};
1088
1142
  for (const [slot, a] of Object.entries(this.chart.agents ?? {})) agents[slot] = { ...a };
1089
- const admitted: AdmittedChart = { text: this.chart, hash: chartHash(this.chart), events, agents, size };
1143
+ const admitted: AdmittedChart = {
1144
+ text: this.chart,
1145
+ hash: chartHash(this.chart),
1146
+ events,
1147
+ agents,
1148
+ size,
1149
+ gates: [...this.gateIds],
1150
+ };
1090
1151
  this.trialCompile(admitted);
1091
1152
  return admitted;
1092
1153
  }
@@ -1112,11 +1173,12 @@ class Door {
1112
1173
  // --- rule 3: names --------------------------------------------------------------------------------
1113
1174
 
1114
1175
  /** The lent actors by kind, as the lent set has them now. */
1115
- private lentActors(): Array<{ name: string; kind: "Agent" | "Effect" | "Machine" }> {
1116
- const out: Array<{ name: string; kind: "Agent" | "Effect" | "Machine" }> = [];
1176
+ private lentActors(): Array<{ name: string; kind: "Agent" | "Effect" | "Machine" | "Gate slot" }> {
1177
+ const out: Array<{ name: string; kind: "Agent" | "Effect" | "Machine" | "Gate slot" }> = [];
1117
1178
  for (const name of this.lends.agents) if (name in this.lent.actors) out.push({ name, kind: "Agent" });
1118
1179
  for (const name of this.lends.effects) if (name in this.lent.actors) out.push({ name, kind: "Effect" });
1119
1180
  for (const name of this.lends.machines) if (name in this.lent.actors) out.push({ name, kind: "Machine" });
1181
+ for (const name of this.lends.gates) if (name in this.lent.actors) out.push({ name, kind: "Gate slot" });
1120
1182
  return out;
1121
1183
  }
1122
1184
 
@@ -1130,7 +1192,7 @@ class Door {
1130
1192
  /** What an invoke's `src` names, or undefined after a refusal line. */
1131
1193
  private srcKind(src: string, path: string): InvokeKind | undefined {
1132
1194
  const lentNow = (name: string) => name in this.lent.actors;
1133
- const declared = [...this.lends.agents, ...this.lends.effects, ...this.lends.machines];
1195
+ const declared = [...this.lends.agents, ...this.lends.effects, ...this.lends.machines, ...this.lends.gates];
1134
1196
  if (declared.includes(src) && !lentNow(src)) {
1135
1197
  this.fail(
1136
1198
  3,
@@ -1141,6 +1203,22 @@ class Door {
1141
1203
  if (this.lends.agents.includes(src)) return "agent";
1142
1204
  if (this.lends.effects.includes(src)) return "effect";
1143
1205
  if (this.lends.machines.includes(src)) return "machine";
1206
+ if (this.lends.gates.includes(src)) {
1207
+ // A lent Gate slot (ADR-0011, ADR-0070): its handlers and its look give its `delivers`, which
1208
+ // the Chart's Machine must take. One that is not in the Chart's Vocabulary could never arrive.
1209
+ const slot = this.lent.actors[src];
1210
+ const outside = isGateSlot(slot)
1211
+ ? slot.gateSlot.delivers.map((d) => d.name).filter((name) => !this.eventSchemas.has(name))
1212
+ : [];
1213
+ if (outside.length) {
1214
+ this.fail(
1215
+ 3,
1216
+ `${path}: the Gate slot "${src}" delivers ${outside.map((n) => `"${n}"`).join(", ")}, which ${outside.length > 1 ? "are" : "is"} not in the Chart's \`events\` (events: ${names([...this.eventSchemas.keys()])}); declare ${outside.length > 1 ? "them" : "it"} in \`events\` and handle ${outside.length > 1 ? "them" : "it"} on this state`,
1217
+ );
1218
+ return undefined;
1219
+ }
1220
+ return "gate";
1221
+ }
1144
1222
  if (src === "gate") {
1145
1223
  if (this.lends.gate) return "gate";
1146
1224
  this.fail(
@@ -1171,8 +1249,8 @@ class Door {
1171
1249
  this.sentBys.push({ id: (guard.params as { id: string }).id, path: `${path}.params.id` });
1172
1250
  return;
1173
1251
  }
1174
- if (guard.type === FAILURE_GUARD) {
1175
- if (guard.params !== undefined) this.fail(3, `${path}.params: "${FAILURE_GUARD}" takes no params`);
1252
+ if (guard.type === FAILURE_GUARD || guard.type === READY_GUARD) {
1253
+ if (guard.params !== undefined) this.fail(3, `${path}.params: "${guard.type}" takes no params`);
1176
1254
  return;
1177
1255
  }
1178
1256
  this.checkGuardName(guard.type, `${path}.type`);
@@ -1180,7 +1258,7 @@ class Door {
1180
1258
  }
1181
1259
 
1182
1260
  private checkGuardName(name: string, path: string): void {
1183
- if (name === FAILURE_GUARD) return;
1261
+ if (name === FAILURE_GUARD || name === READY_GUARD) return;
1184
1262
  const lent = this.lends.guards.filter((g) => g in this.lent.guards);
1185
1263
  if (this.lends.guards.includes(name) && !(name in this.lent.guards)) {
1186
1264
  this.fail(
@@ -1190,7 +1268,7 @@ class Door {
1190
1268
  } else if (!this.lends.guards.includes(name)) {
1191
1269
  this.fail(
1192
1270
  3,
1193
- `${path}: "${name}" is not a lent guard; lent guards: ${names(lent)} (and ${SENT_BY} with params { id }, and ${FAILURE_GUARD})`,
1271
+ `${path}: "${name}" is not a lent guard; lent guards: ${names(lent)} (and ${SENT_BY} with params { id }, ${FAILURE_GUARD} and ${READY_GUARD})`,
1194
1272
  );
1195
1273
  }
1196
1274
  }
@@ -1335,7 +1413,14 @@ class Door {
1335
1413
  // --- rule 5: minted Agents ------------------------------------------------------------------------
1336
1414
 
1337
1415
  private checkAgents(): void {
1338
- const lentNames = new Set([...this.lends.agents, ...this.lends.effects, ...this.lends.machines, "gate", "fail"]);
1416
+ const lentNames = new Set([
1417
+ ...this.lends.agents,
1418
+ ...this.lends.effects,
1419
+ ...this.lends.machines,
1420
+ ...this.lends.gates,
1421
+ "gate",
1422
+ "fail",
1423
+ ]);
1339
1424
  for (const [slot, a] of Object.entries(this.chart.agents ?? {})) {
1340
1425
  const path = `agents.${slot}`;
1341
1426
  if (lentNames.has(slot)) {
@@ -1679,7 +1764,8 @@ class Door {
1679
1764
  /** The parties' rules, as `jr2Setup` applies them (ADR-0015), each with its path in the Chart. */
1680
1765
  private checkParties(): void {
1681
1766
  const agents = new Set<string>();
1682
- const gates = new Set<string>();
1767
+ const gates = this.gateIds;
1768
+ const slotStates = new Map<string, Node[]>();
1683
1769
  for (const node of this.nodes) {
1684
1770
  const invokes = listAt(node.state.invoke, `${node.doc}.invoke`);
1685
1771
  // A Gate's invoke id, as jr2Setup names an unnamed one: the state's key path.
@@ -1694,6 +1780,7 @@ class Door {
1694
1780
  if (kind === "gate") gates.add(inv.id ?? (unnamed > 1 ? `${key}.${ordinal++}` : key));
1695
1781
  if (kind !== "agent" && kind !== "minted") continue;
1696
1782
  agents.add(inv.src);
1783
+ slotStates.set(inv.src, [...(slotStates.get(inv.src) ?? []), node]);
1697
1784
  if (inv.onDone !== undefined) {
1698
1785
  this.fail(
1699
1786
  6,
@@ -1715,6 +1802,25 @@ class Door {
1715
1802
  }
1716
1803
  }
1717
1804
  }
1805
+ // One Agent, two Turns at once: one sender and one conversation cannot hold both.
1806
+ // One line for each Agent: every state of it that can be active with another of them.
1807
+ for (const [slot, states] of slotStates) {
1808
+ const clash = new Set<Node>();
1809
+ for (let i = 0; i < states.length; i++) {
1810
+ for (let j = i + 1; j < states.length; j++) {
1811
+ const [a, b] = [states[i]!, states[j]!];
1812
+ const together =
1813
+ a === b || isAncestorOrSelf(a, b) || isAncestorOrSelf(b, a) || nearestCommon(a, b)?.kind === "parallel";
1814
+ if (together) clash.add(a).add(b);
1815
+ }
1816
+ }
1817
+ if (clash.size === 0) continue;
1818
+ const docs = [...clash].map((n) => n.doc).join(", ");
1819
+ this.fail(
1820
+ 6,
1821
+ `${docs}: Agent "${slot}" would run two Turns at once: these states invoke it and can be active together (in parallel regions, one inside the other, or one state twice); an Agent runs one Turn at a time: invoke another Agent in one of them`,
1822
+ );
1823
+ }
1718
1824
  for (const { id, path } of this.sentBys) {
1719
1825
  if (id.includes("{{")) {
1720
1826
  this.fail(3, `${path}: a sentBy id is a literal party name, never a reference`);
@@ -1933,8 +2039,9 @@ class Door {
1933
2039
  }
1934
2040
  if (kind === "effect") {
1935
2041
  const logic = this.lent.actors[inv.src];
1936
- const keys = isEffect(logic) ? inputKeysOf(logic.effect) : undefined;
1937
- if (keys === undefined && isEffect(logic) && logic.effect.kind === "dispatch") {
2042
+ const parts = isEffect(logic) ? logic.effect : undefined;
2043
+ const keys = parts ? inputKeysOf(parts) : undefined;
2044
+ if (keys === undefined && parts?.kind === "dispatch") {
1938
2045
  // A dispatch with no input schema has no keys to hold the Chart to: its input is the run's
1939
2046
  // body, and the callee's door is the check (ADR-0071). Its values are what any Effect's
1940
2047
  // input slot takes.
@@ -1957,31 +2064,114 @@ class Door {
1957
2064
  `${ipath}: the lent Effect "${inv.src}" has no input keys the door can read (an Open argv nobody bound)`,
1958
2065
  );
1959
2066
  }
1960
- if (input === undefined && keys.length === 0) return;
1961
- if (!isObject) {
1962
- return this.fail(8, `${ipath}: the lent Effect "${inv.src}" takes an object of exactly ${names(keys)}`);
2067
+ const shape = this.shapeHere(parts!);
2068
+ // A cut's `expect` is static, and a Chart cannot state it: an unstated one would let a planner's
2069
+ // name land on someone else's branch.
2070
+ if (parts?.kind === "command" && parts.cut && parts.cut.expect === undefined) {
2071
+ const fresh = this.lentNewCuts();
2072
+ this.fail(
2073
+ 8,
2074
+ `${ipath}: the lent cut "${inv.src}" does not state \`expect\`, so a Chart cannot use it; ` +
2075
+ (fresh.length
2076
+ ? `use a lent cut with \`expect: "new"\`: ${names(fresh)}`
2077
+ : `the Open body must state it (\`worktree({ expect: "new" })\` would); ${this.lentText()}`),
2078
+ );
2079
+ }
2080
+ // A push publishes the branch of a cut's row, which stays in the run's ledger, so a Chart that
2081
+ // cuts and a later Chart that pushes are the same pair: the test is what is lent, not what this
2082
+ // Chart invokes.
2083
+ const force = this.lentForcePush();
2084
+ if (parts?.kind === "command" && parts.cut?.expect === "existing" && force !== undefined) {
2085
+ const fresh = this.lentNewCuts();
2086
+ this.fail(
2087
+ 8,
2088
+ `${ipath}: the lent cut "${inv.src}" states \`expect: "existing"\` beside the lent force push "${force}", and together they can force-push any existing branch; ` +
2089
+ (fresh.length
2090
+ ? `use a lent cut with \`expect: "new"\`: ${names(fresh)}`
2091
+ : `this Open body lends no cut with \`expect: "new"\`, so a Chart cannot cut here (\`worktree({ expect: "new" })\` would); ${this.lentText()}`),
2092
+ );
1963
2093
  }
2094
+ if (input === undefined && shape.required.length === 0 && !shape.oneOf) return;
2095
+ const takes = shapeText(shape);
2096
+ if (!isObject) return this.fail(8, `${ipath}: the lent Effect "${inv.src}" takes an object: ${takes}`);
1964
2097
  const given = Object.keys(input);
1965
- const missing = keys.filter((k) => !given.includes(k));
2098
+ const missing = shape.required.filter((k) => !given.includes(k));
1966
2099
  const extra = given.filter((k) => !keys.includes(k));
1967
- if (missing.length || extra.length) {
2100
+ const ofOne = shape.oneOf?.filter((k) => given.includes(k)).length ?? 1;
2101
+ if (missing.length || extra.length || ofOne !== 1) {
1968
2102
  this.fail(
1969
2103
  8,
1970
- `${ipath}: the lent Effect "${inv.src}" takes exactly ${names(keys)}` +
2104
+ `${ipath}: the lent Effect "${inv.src}" takes ${takes}` +
1971
2105
  (missing.length ? `; missing: ${missing.join(", ")}` : "") +
1972
- (extra.length ? `; not its input: ${extra.join(", ")}` : ""),
2106
+ (extra.length ? `; not its input: ${extra.join(", ")}` : "") +
2107
+ (ofOne !== 1 ? `; give one of ${shape.oneOf!.join(", ")}` : ""),
1973
2108
  );
1974
2109
  }
2110
+ if (parts?.kind === "command" && parts.cut && given.includes("detach") && given.includes("from")) {
2111
+ this.fail(8, `${ipath}: a detached cut takes no \`from\``);
2112
+ }
1975
2113
  for (const k of given.filter((g) => keys.includes(g))) {
1976
2114
  const v = (input as Record<string, unknown>)[k];
1977
2115
  if (typeof v !== "string" && !isRef(v))
1978
2116
  this.fail(8, `${ipath}.${k}: an Effect's input slot takes a string or a reference`);
2117
+ else if ((k === "worktree" || k === "path") && typeof v === "string") this.checkLiteralPath(v, `${ipath}.${k}`);
1979
2118
  }
1980
2119
  return;
1981
2120
  }
1982
2121
  this.checkAgentInput(inv.src, kind, input, isObject, ipath);
1983
2122
  }
1984
2123
 
2124
+ /** An Effect's shape here: where the door knows the Workspace has Repo Slots, `worktree` is
2125
+ * required on what can name one. Elsewhere the start says so (`drive`). */
2126
+ private shapeHere(parts: Parameters<typeof inputShapeOf>[0]): InputShape {
2127
+ const shape = inputShapeOf(parts)!;
2128
+ if (!this.options.repoSlots?.length || !shape.optional.includes("worktree")) return shape;
2129
+ return {
2130
+ ...shape,
2131
+ required: [...shape.required, "worktree"],
2132
+ optional: shape.optional.filter((k) => k !== "worktree"),
2133
+ };
2134
+ }
2135
+
2136
+ /** The lent Effects, each with its parts. */
2137
+ private lentEffects(): Array<{ name: string; parts: Parameters<typeof inputShapeOf>[0] }> {
2138
+ return this.lends.effects.flatMap((name) => {
2139
+ const logic = this.lent.actors[name];
2140
+ return isEffect(logic) ? [{ name, parts: logic.effect }] : [];
2141
+ });
2142
+ }
2143
+
2144
+ /** The name of a lent push that forces, if one is lent. */
2145
+ private lentForcePush(): string | undefined {
2146
+ return this.lentEffects().find(({ parts }) => parts.kind === "push" && parts.force)?.name;
2147
+ }
2148
+
2149
+ /** The names of the lent cuts that make only a new branch. */
2150
+ private lentNewCuts(): string[] {
2151
+ return this.lentEffects()
2152
+ .filter(({ parts }) => parts.kind === "command" && parts.cut?.expect === "new")
2153
+ .map(({ name }) => name);
2154
+ }
2155
+
2156
+ /** A literal Worktree path: a wrong slot costs no pod. A path with a reference waits for its start. */
2157
+ private checkLiteralPath(value: string, path: string): void {
2158
+ if (referencesOf(value).length) return;
2159
+ const match = value.match(/^\/work\/([^/]+)\/([^/.-][^/]*)$/);
2160
+ const slots = this.options.repoSlots;
2161
+ if (match === null) this.fail(8, `${path}: "${value}" is not a Worktree path: /work/<slot>/<name>`);
2162
+ else if (slots !== undefined && !slots.includes(match[1]!)) {
2163
+ this.fail(8, `${path}: "${value}" names no Repo Slot of this Workspace; its Repo Slots: ${names([...slots])}`);
2164
+ }
2165
+ }
2166
+
2167
+ /** A literal `cwd`, see {@link slotFolderFault}. A `cwd` with a reference waits for its value: the
2168
+ * Open body asks the same question when the Turn starts. */
2169
+ private checkCwd(value: string, path: string): void {
2170
+ if (referencesOf(value).length) return;
2171
+ const fault = slotFolderFault(value, this.options.repoSlots ?? []);
2172
+ if (fault !== undefined) this.fail(8, `${path}: ${fault}`);
2173
+ }
2174
+
1985
2175
  /** The input of `fail` (ADR-0073): `{ reason, detail? }`, `reason` a text for a person (a string
1986
2176
  * or a reference), `detail` any value. */
1987
2177
  private checkFailInput(input: unknown, isObject: boolean, ipath: string): void {
@@ -2000,6 +2190,15 @@ class Door {
2000
2190
  }
2001
2191
  }
2002
2192
 
2193
+ /** Does this Agent read `/work`? A `workspace: "none"` one frames nothing. */
2194
+ private worksInWorkspace(src: string, kind: "agent" | "minted"): boolean {
2195
+ if (kind === "agent") {
2196
+ const logic = this.lent.actors[src] as { definition?: { workspace?: unknown } } | undefined;
2197
+ return logic?.definition?.workspace !== "none";
2198
+ }
2199
+ return (this.chart.agents?.[src] as { workspace?: unknown } | undefined)?.workspace !== "none";
2200
+ }
2201
+
2003
2202
  /**
2004
2203
  * An Agent invoke's input is a Turn's Frame and Dials (ADR-0057): `prompt` (a string), and
2005
2204
  * optionally `cwd`, `continue: true`, a `model` the Open body allows, and a `thinkingLevel`.
@@ -2033,6 +2232,13 @@ class Door {
2033
2232
  else if (typeof obj.prompt !== "string")
2034
2233
  this.fail(8, `${ipath}.prompt: a prompt is a string (placeholders inside it are fine)`);
2035
2234
  if (obj.cwd !== undefined && typeof obj.cwd !== "string") this.fail(8, `${ipath}.cwd: a cwd is a string`);
2235
+ if (obj.cwd === undefined && this.options.repoSlots?.length && this.worksInWorkspace(src, kind)) {
2236
+ this.fail(
2237
+ 8,
2238
+ `${ipath}: ${who} works under a Workspace with Repo Slots, and a Turn there says where: \`cwd\` is a Worktree, or "/work"`,
2239
+ );
2240
+ }
2241
+ if (typeof obj.cwd === "string" && this.worksInWorkspace(src, kind)) this.checkCwd(obj.cwd, `${ipath}.cwd`);
2036
2242
  if (obj.continue !== undefined && obj.continue !== true)
2037
2243
  this.fail(8, `${ipath}.continue: \`continue\` is true or absent`);
2038
2244
  if (obj.model !== undefined) {
@@ -2164,6 +2370,12 @@ function wholeRefOf(v: unknown): string | undefined {
2164
2370
  return isRef(v) ? (v as { $ref: string }).$ref : undefined;
2165
2371
  }
2166
2372
 
2373
+ /** The nearest state at or above both `a` and `b`. */
2374
+ function nearestCommon(a: Node, b: Node): Node | undefined {
2375
+ for (let n: Node | undefined = a; n; n = n.parent) if (isAncestorOrSelf(n, b)) return n;
2376
+ return undefined;
2377
+ }
2378
+
2167
2379
  /** Is `a` the state `b` or a state above it? */
2168
2380
  function isAncestorOrSelf(a: Node, b: Node): boolean {
2169
2381
  for (let n: Node | undefined = b; n; n = n.parent) if (n === a) return true;
package/src/charts.ts CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  isOpenBody,
26
26
  lentIn,
27
27
  openBodyDeclarationOf,
28
+ repoSlotsAround,
28
29
  type LentParts,
29
30
  type OpenBodyDeclaration,
30
31
  type OpenBodyLogic,
@@ -305,6 +306,8 @@ export type ChartWrapper = {
305
306
  chartFields: string[];
306
307
  /** The door's other fields: the Chart's `input` keys the door can see (ADR-0070 rule 7). */
307
308
  inputKeys: string[];
309
+ /** The Repo Slots of the `workspace()` around the wrapper, where the door can read them. */
310
+ repoSlots?: readonly string[];
308
311
  };
309
312
 
310
313
  /**
@@ -333,7 +336,8 @@ export function chartWrapperOf(machine: AnyStateMachine): ChartWrapper | undefin
333
336
  const chartFields = Object.keys(shape).filter((k) => isChartSchema(shape[k]));
334
337
  // The door's other fields, less the body's placement: a Chart reads no url (open-body.ts).
335
338
  const inputKeys = Object.keys(chartInputOf(shape)).filter((k) => !chartFields.includes(k));
336
- return { body: bodies[0]!.slot, bodies, chartFields, inputKeys };
339
+ const repoSlots = repoSlotsAround(machine);
340
+ return { body: bodies[0]!.slot, bodies, chartFields, inputKeys, ...(repoSlots ? { repoSlots } : {}) };
337
341
  }
338
342
 
339
343
  /**
@@ -347,7 +351,10 @@ export function admitUnder(wrapper: ChartWrapper, text: unknown): AdmittedChart
347
351
  const report: string[] = [];
348
352
  for (const body of wrapper.bodies) {
349
353
  try {
350
- return admitBodyChart(text, body.declaration, body.lent, { inputKeys: wrapper.inputKeys });
354
+ return admitBodyChart(text, body.declaration, body.lent, {
355
+ inputKeys: wrapper.inputKeys,
356
+ ...(wrapper.repoSlots ? { repoSlots: wrapper.repoSlots } : {}),
357
+ });
351
358
  } catch (err) {
352
359
  if (!(err instanceof ChartRefusal)) throw err;
353
360
  const prefix = wrapper.bodies.length > 1 ? `body "${body.slot}": ` : "";