@jr2/orchestrator 0.6.0 → 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 (301) 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 +86 -9
  14. package/docs/concepts/architecture.md +41 -28
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +7 -5
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +4 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +50 -22
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +5 -4
  23. package/docs/concepts/pools-and-sources.md +187 -11
  24. package/docs/concepts/secrets-and-trust.md +73 -33
  25. package/docs/concepts/the-menu.md +11 -0
  26. package/docs/concepts/workspaces.md +6 -5
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
  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/guides/add-tools-to-the-sandbox.md +55 -11
  32. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  33. package/docs/guides/answer-a-gate.md +74 -37
  34. package/docs/guides/bind-open-parts.md +24 -8
  35. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  36. package/docs/guides/cancel-a-run.md +26 -16
  37. package/docs/guides/fix-a-failed-converge.md +18 -6
  38. package/docs/guides/give-a-caller-a-scope.md +203 -0
  39. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  41. package/docs/guides/let-an-agent-write-a-chart.md +9 -6
  42. package/docs/guides/look-inside-a-sandbox.md +101 -31
  43. package/docs/guides/open-the-console.md +63 -14
  44. package/docs/guides/read-a-conversation.md +225 -0
  45. package/docs/guides/run-a-chart-by-name.md +17 -16
  46. package/docs/guides/run-one-worker-per-item.md +15 -9
  47. package/docs/guides/set-an-agents-access-and-dials.md +2 -0
  48. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  49. package/docs/guides/start-a-run-from-a-run.md +18 -16
  50. package/docs/guides/start-a-run.md +24 -18
  51. package/docs/guides/steer-an-agent.md +332 -0
  52. package/docs/guides/use-a-hosted-provider.md +1 -1
  53. package/docs/guides/watch-a-run.md +127 -121
  54. package/docs/guides/write-a-hook-kind.md +238 -0
  55. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  56. package/docs/guides/write-a-workflow.md +17 -7
  57. package/docs/llms.txt +81 -15
  58. package/docs/reference/authoring-surface/agent.md +1 -1
  59. package/docs/reference/authoring-surface/answer.md +1 -1
  60. package/docs/reference/authoring-surface/arg.md +1 -1
  61. package/docs/reference/authoring-surface/basic.md +1 -1
  62. package/docs/reference/authoring-surface/bearer.md +1 -1
  63. package/docs/reference/authoring-surface/branchname.md +1 -1
  64. package/docs/reference/authoring-surface/carry.md +23 -0
  65. package/docs/reference/authoring-surface/chartschema.md +1 -1
  66. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  67. package/docs/reference/authoring-surface/command.md +1 -1
  68. package/docs/reference/authoring-surface/customize.md +4 -1
  69. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  70. package/docs/reference/authoring-surface/defineevent.md +1 -1
  71. package/docs/reference/authoring-surface/dispatch.md +5 -4
  72. package/docs/reference/authoring-surface/fail.md +1 -1
  73. package/docs/reference/authoring-surface/file.md +1 -1
  74. package/docs/reference/authoring-surface/frompick.md +1 -1
  75. package/docs/reference/authoring-surface/gate.md +118 -0
  76. package/docs/reference/authoring-surface/group-agents.md +15 -0
  77. package/docs/reference/authoring-surface/group-charts.md +16 -0
  78. package/docs/reference/authoring-surface/group-composition.md +25 -0
  79. package/docs/reference/authoring-surface/group-config.md +17 -0
  80. package/docs/reference/authoring-surface/group-effects.md +48 -0
  81. package/docs/reference/authoring-surface/group-failure.md +13 -0
  82. package/docs/reference/authoring-surface/group-gates.md +16 -0
  83. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  84. package/docs/reference/authoring-surface/group-steering.md +12 -0
  85. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  86. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  87. package/docs/reference/authoring-surface/http.md +1 -1
  88. package/docs/reference/authoring-surface/isfailure.md +1 -1
  89. package/docs/reference/authoring-surface/isready.md +1 -1
  90. package/docs/reference/authoring-surface/jr2setup.md +13 -3
  91. package/docs/reference/authoring-surface/mcp.md +107 -0
  92. package/docs/reference/authoring-surface/open.md +5 -3
  93. package/docs/reference/authoring-surface/openbody.md +1 -1
  94. package/docs/reference/authoring-surface/paramsof.md +1 -1
  95. package/docs/reference/authoring-surface/pool.md +77 -9
  96. package/docs/reference/authoring-surface/push.md +1 -1
  97. package/docs/reference/authoring-surface/readyof.md +1 -1
  98. package/docs/reference/authoring-surface/reread.md +19 -0
  99. package/docs/reference/authoring-surface/secret.md +1 -1
  100. package/docs/reference/authoring-surface/sentby.md +1 -1
  101. package/docs/reference/authoring-surface/skip.md +22 -0
  102. package/docs/reference/authoring-surface/source.md +56 -1
  103. package/docs/reference/authoring-surface/steer.md +93 -0
  104. package/docs/reference/authoring-surface/text.md +1 -1
  105. package/docs/reference/authoring-surface/type-agentdefinition.md +14 -1
  106. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  107. package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
  108. package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
  109. package/docs/reference/authoring-surface/type-binding.md +1 -1
  110. package/docs/reference/authoring-surface/type-caller.md +40 -0
  111. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  112. package/docs/reference/authoring-surface/type-carry.md +19 -0
  113. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  114. package/docs/reference/authoring-surface/type-commandoutput.md +1 -1
  115. package/docs/reference/authoring-surface/type-customize.md +16 -1
  116. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  117. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  118. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  119. package/docs/reference/authoring-surface/type-dispatchoutput.md +1 -1
  120. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  121. package/docs/reference/authoring-surface/type-framepath.md +1 -1
  122. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  123. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  124. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  125. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  126. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  127. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  128. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  129. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  130. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  131. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  132. package/docs/reference/authoring-surface/type-hostinjectedinput.md +1 -1
  133. package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
  134. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  135. package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
  136. package/docs/reference/authoring-surface/type-lends.md +5 -1
  137. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  138. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  139. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  140. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  141. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  142. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  143. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  144. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  145. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  146. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  147. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  148. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  149. package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
  150. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  151. package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
  152. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  153. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  154. package/docs/reference/authoring-surface/type-reposlot.md +1 -1
  155. package/docs/reference/authoring-surface/type-reread.md +18 -0
  156. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  157. package/docs/reference/authoring-surface/type-sender.md +26 -1
  158. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  159. package/docs/reference/authoring-surface/type-size.md +1 -1
  160. package/docs/reference/authoring-surface/type-skip.md +19 -0
  161. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  162. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  163. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  164. package/docs/reference/authoring-surface/type-sourcespec.md +16 -7
  165. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  166. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  167. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  168. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  169. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  170. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  171. package/docs/reference/authoring-surface/type-workspaced.md +1 -1
  172. package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
  173. package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
  174. package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
  175. package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
  176. package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
  177. package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
  178. package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
  179. package/docs/reference/authoring-surface/workspace.md +1 -1
  180. package/docs/reference/authoring-surface/worktree.md +1 -1
  181. package/docs/reference/authoring-surface.md +88 -167
  182. package/docs/reference/cli.md +413 -74
  183. package/docs/reference/config.md +235 -2
  184. package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
  185. package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
  186. package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
  187. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  188. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  189. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  190. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  191. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +9 -2
  192. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
  193. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  194. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +18 -14
  195. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
  196. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  197. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +32 -18
  198. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
  199. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  200. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +116 -34
  201. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
  202. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  203. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +7 -6
  204. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
  205. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  206. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  207. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -18
  208. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  209. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +4 -3
  210. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  211. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +5 -4
  212. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
  213. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  214. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  215. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  216. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +7 -0
  217. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
  218. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
  219. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
  220. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
  221. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  222. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  223. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +51 -23
  224. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
  225. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
  226. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  227. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +17 -13
  228. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  229. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  230. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
  231. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
  232. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  233. 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
  234. package/docs/reference/decisions.md +6 -4
  235. package/docs/reference/glossary.md +96 -42
  236. package/docs/reference/hooks.md +1077 -0
  237. package/docs/reference/http-api.md +976 -149
  238. package/docs/why.md +2 -1
  239. package/package.json +10 -3
  240. package/src/actor.ts +36 -10
  241. package/src/agent.ts +50 -0
  242. package/src/assembly-checks.ts +530 -4
  243. package/src/callers.ts +245 -0
  244. package/src/chart.ts +52 -7
  245. package/src/config.ts +550 -1
  246. package/src/conversations.ts +453 -0
  247. package/src/custodian.ts +8 -2
  248. package/src/customize.ts +143 -4
  249. package/src/dispatch-effect.ts +142 -26
  250. package/src/durability.ts +2 -0
  251. package/src/duration.ts +20 -0
  252. package/src/effect-checks.ts +2 -1
  253. package/src/effect.ts +60 -7
  254. package/src/effector-wire.ts +272 -27
  255. package/src/gate-brand.ts +16 -0
  256. package/src/gate.ts +634 -48
  257. package/src/harness-client.ts +10 -4
  258. package/src/held-secrets.ts +72 -23
  259. package/src/holds.ts +2 -2
  260. package/src/hook-kinds/discord.ts +92 -0
  261. package/src/hook-kinds/door.ts +107 -0
  262. package/src/hook-kinds/github.ts +89 -0
  263. package/src/hook-kinds/gitlab.ts +72 -0
  264. package/src/hook-kinds/hook.ts +7 -0
  265. package/src/hook-kinds/index.ts +465 -0
  266. package/src/hook-kinds/slack.ts +151 -0
  267. package/src/hook-kinds/webhook.ts +113 -0
  268. package/src/hooks.ts +506 -0
  269. package/src/http-effect.ts +76 -38
  270. package/src/http.ts +889 -94
  271. package/src/index.ts +16 -5
  272. package/src/instance.ts +196 -26
  273. package/src/internal.ts +22 -3
  274. package/src/machine-doc.ts +26 -1
  275. package/src/mcp-effect.ts +424 -0
  276. package/src/mount-digest.ts +41 -7
  277. package/src/names.ts +64 -2
  278. package/src/notices.ts +138 -10
  279. package/src/open-body.ts +25 -11
  280. package/src/open-effect.ts +17 -4
  281. package/src/open.ts +4 -2
  282. package/src/parts.ts +87 -9
  283. package/src/party.ts +2 -1
  284. package/src/pick.ts +2 -2
  285. package/src/pool.ts +1142 -163
  286. package/src/registration.ts +203 -13
  287. package/src/run-host.ts +1509 -171
  288. package/src/run-list.ts +191 -0
  289. package/src/sandbox-kube.ts +53 -5
  290. package/src/secret-placement.ts +239 -33
  291. package/src/server.ts +53 -4
  292. package/src/setup.ts +72 -32
  293. package/src/snapshot-store.ts +565 -48
  294. package/src/steer.ts +111 -0
  295. package/src/stub-harness.ts +6 -2
  296. package/src/tokens.ts +263 -22
  297. package/src/wire.ts +17 -0
  298. package/src/workspace.ts +50 -6
  299. package/src/worktree.ts +3 -3
  300. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  301. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -0,0 +1,191 @@
1
+ // The run listing (ADR-0009): what `GET /runs` reads of its query, the summary of one run, and the
2
+ // cursor of a page. The live runs come from the host's registry and the ended runs from the store's
3
+ // listing columns (`SnapshotStore.ended`); `RunHost.history` joins the two halves in one order,
4
+ // newest start first, so a page is the same whichever half its runs are in.
5
+
6
+ import type { RunStarter } from "./callers.ts";
7
+ import { periodMs } from "./duration.ts";
8
+ import type { DispatchedBy } from "./registration.ts";
9
+
10
+ /**
11
+ * One run as `GET /runs` lists it (ADR-0009): a summary, not the whole `RunStatus`. `status` is
12
+ * `active` for a live run, else the store's word for how it ended. `reason` says why, where the
13
+ * Machine did not: the host's reason (`drifted`, `lost`, ADR-0030), the Failure's reason
14
+ * (`failed`, ADR-0073) or the fault (`error`). `endedAt` is the terminal write of an ended run.
15
+ * `openGates` counts the Gates open now, so it is 0 for an ended run. `placing` names the child
16
+ * paths of the Workspaces that wait for capacity (ADR-0064), and `dispatchedBy` the run that
17
+ * started this one (ADR-0071).
18
+ */
19
+ export type RunSummary = {
20
+ runId: string;
21
+ workflow: string;
22
+ status: ShownStatus;
23
+ reason?: string;
24
+ startedBy?: RunStarter;
25
+ startedAt: string;
26
+ endedAt?: string;
27
+ assembly?: string;
28
+ openGates: number;
29
+ placing?: string[];
30
+ dispatchedBy?: DispatchedBy;
31
+ /** The space guard deleted the run's kept conversations (ADR-0065); the row stays. */
32
+ swept?: "conversation swept: no space";
33
+ };
34
+
35
+ /** A page of the listing: the runs, and the cursor of the next page when there is one. */
36
+ export type RunPage = { runs: RunSummary[]; next?: string };
37
+
38
+ /** How a run ended, as the store writes it (ADR-0065, ADR-0025, ADR-0030, ADR-0073, ADR-0074). */
39
+ export type EndedStatus = "done" | "failed" | "error" | "cancelled" | "lost" | "drifted";
40
+
41
+ /** The statuses of an ended run, as the store writes them. xstate's `stopped` is not one: it is
42
+ * mechanism, and a cancel ends the run `cancelled` (ADR-0025). */
43
+ export const ENDED_STATUSES: readonly EndedStatus[] = ["done", "failed", "error", "cancelled", "lost", "drifted"];
44
+
45
+ /** The status of a live run: the Machine's own word. */
46
+ export const LIVE_STATUS = "active";
47
+
48
+ /** A run status as jr2 shows it, on every route and feed: {@link LIVE_STATUS} while the run is
49
+ * live, else how it ended. The store's own `live` and xstate's `stopped` are never shown. */
50
+ export type ShownStatus = typeof LIVE_STATUS | EndedStatus;
51
+
52
+ /** A word that the store wrote for an ended run. */
53
+ export function isEndedStatus(word: string): word is EndedStatus {
54
+ return (ENDED_STATUSES as readonly string[]).includes(word);
55
+ }
56
+
57
+ /**
58
+ * The shown status of a store row: `live` shows as {@link LIVE_STATUS}, and an ended word as
59
+ * itself. jr2 writes no other word. A row with another word is a defect in the store, so it shows
60
+ * as `error`: it is not live, and nothing can say how it ended.
61
+ */
62
+ export function shownOfRow(word: string): ShownStatus {
63
+ if (word === "live") return LIVE_STATUS;
64
+ return isEndedStatus(word) ? word : "error";
65
+ }
66
+
67
+ /** How many runs a page holds when the query names no `limit`, and the most it can name. */
68
+ export const RUNS_PAGE_DEFAULT = 50;
69
+ export const RUNS_PAGE_MAX = 500;
70
+
71
+ /** The query of `GET /runs`, read. `ended` is the ended statuses to list, or `undefined` for none. */
72
+ export type RunListQuery = {
73
+ live: boolean;
74
+ ended?: readonly EndedStatus[];
75
+ workflow?: string;
76
+ since?: string;
77
+ after?: { startedAt: string; runId: string };
78
+ limit: number;
79
+ };
80
+
81
+ /** An ISO-8601 date, or a date and time with an optional zone. */
82
+ const ISO_TIME = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
83
+
84
+ /**
85
+ * Read the query of `GET /runs`, or the refusal's text. `status` is `live` (the default), `ended`,
86
+ * `all`, or a comma list of statuses and the two words; `since` is a duration back from `now`
87
+ * (`"2h"`, `"7d"`) or an ISO-8601 time; `cursor` is a page's `next`; `limit` is 1 to
88
+ * {@link RUNS_PAGE_MAX}.
89
+ */
90
+ export function parseRunListQuery(
91
+ q: { status?: string; workflow?: string; since?: string; cursor?: string; limit?: string },
92
+ now: number,
93
+ ): RunListQuery | { error: string } {
94
+ let live = false;
95
+ const ended = new Set<EndedStatus>();
96
+ for (const word of (q.status ?? "live").split(",").map((w) => w.trim())) {
97
+ if (word === "live" || word === LIVE_STATUS) live = true;
98
+ else if (word === "ended") for (const s of ENDED_STATUSES) ended.add(s);
99
+ else if (word === "all") {
100
+ live = true;
101
+ for (const s of ENDED_STATUSES) ended.add(s);
102
+ } else if (isEndedStatus(word)) ended.add(word);
103
+ else {
104
+ return {
105
+ error: `status "${word}" is not one jr2 lists — use live, ended, all, or a comma list of ${[LIVE_STATUS, ...ENDED_STATUSES].join(", ")}`,
106
+ };
107
+ }
108
+ }
109
+ let since: string | undefined;
110
+ if (q.since !== undefined) {
111
+ const back = periodMs(q.since);
112
+ // Only an ISO-8601 shape reaches `Date.parse`: it reads other text leniently, so `7` would be
113
+ // a time in 2001 and list the whole history.
114
+ const at = back !== undefined ? now - back : ISO_TIME.test(q.since) ? Date.parse(q.since) : Number.NaN;
115
+ if (!Number.isFinite(at)) {
116
+ return { error: `since "${q.since}" is not a duration (such as 2h or 7d) or an ISO-8601 time` };
117
+ }
118
+ // A duration longer than the clock goes back lists every run, as the sweep's `Math.max(0, …)`:
119
+ // a time before 1970 is outside what `Date` can print at its far end.
120
+ since = new Date(Math.max(at, 0)).toISOString();
121
+ }
122
+ let after: RunListQuery["after"];
123
+ if (q.cursor !== undefined) {
124
+ after = decodeCursor(q.cursor);
125
+ if (!after) return { error: "cursor is not one that GET /runs gave — use the next of the page before" };
126
+ }
127
+ let limit = RUNS_PAGE_DEFAULT;
128
+ if (q.limit !== undefined) {
129
+ limit = Number(q.limit);
130
+ if (!Number.isInteger(limit) || limit < 1 || limit > RUNS_PAGE_MAX) {
131
+ return { error: `limit is a whole number from 1 to ${RUNS_PAGE_MAX}` };
132
+ }
133
+ }
134
+ return {
135
+ live,
136
+ ...(ended.size ? { ended: [...ended] } : {}),
137
+ ...(q.workflow !== undefined && q.workflow !== "" ? { workflow: q.workflow } : {}),
138
+ ...(since !== undefined ? { since } : {}),
139
+ ...(after ? { after } : {}),
140
+ limit,
141
+ };
142
+ }
143
+
144
+ /** The cursor after `run`: its start and its id, which order the listing. Opaque to a reader. */
145
+ export function encodeCursor(run: { startedAt: string; runId: string }): string {
146
+ return Buffer.from(JSON.stringify([run.startedAt, run.runId])).toString("base64url");
147
+ }
148
+
149
+ /** A cursor read back, or undefined when it is not one that {@link encodeCursor} made. */
150
+ export function decodeCursor(cursor: string): { startedAt: string; runId: string } | undefined {
151
+ try {
152
+ const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")) as unknown;
153
+ if (!Array.isArray(value) || value.length !== 2) return undefined;
154
+ const [startedAt, runId] = value as unknown[];
155
+ if (typeof startedAt !== "string" || typeof runId !== "string") return undefined;
156
+ if (!Number.isFinite(Date.parse(startedAt))) return undefined;
157
+ return { startedAt, runId };
158
+ } catch {
159
+ return undefined;
160
+ }
161
+ }
162
+
163
+ /** The listing's order: the newest start first, then the larger run id. */
164
+ export function newestFirst(a: RunSummary, b: RunSummary): number {
165
+ if (a.startedAt !== b.startedAt) return a.startedAt < b.startedAt ? 1 : -1;
166
+ return a.runId < b.runId ? 1 : a.runId > b.runId ? -1 : 0;
167
+ }
168
+
169
+ /** Whether `run` comes after the cursor `after` in the order. */
170
+ export function isAfter(
171
+ run: { startedAt: string; runId: string },
172
+ after: { startedAt: string; runId: string },
173
+ ): boolean {
174
+ return run.startedAt < after.startedAt || (run.startedAt === after.startedAt && run.runId < after.runId);
175
+ }
176
+
177
+ /** The child paths of every Workspace in `placing` (ADR-0064), at any depth, off the state values
178
+ * alone. The root in `placing` is the empty path. */
179
+ export function placingOf(value: unknown, children: readonly PlacingChild[]): string[] {
180
+ return [...(value === "placing" ? [""] : []), ...placingIn(children, [])];
181
+ }
182
+
183
+ /** The part of a `RunChild` that {@link placingOf} reads. */
184
+ export type PlacingChild = { id: string; value: unknown; children: readonly PlacingChild[] };
185
+
186
+ function placingIn(children: readonly PlacingChild[], path: string[]): string[] {
187
+ return children.flatMap((child) => {
188
+ const here = [...path, child.id];
189
+ return [...(child.value === "placing" ? [here.join("/")] : []), ...placingIn(child.children, here)];
190
+ });
191
+ }
@@ -45,6 +45,8 @@
45
45
  // volume runtime the kit's Harness image, as an `image` volume → /opt/jr2
46
46
  // volume conversations an emptyDir → /run/jr2/conversations, the Harness container's alone
47
47
  // (ADR-0031: a Harness restart inside the pod keeps its conversations)
48
+ // volume shell an emptyDir → /run/jr2/shell, the Harness container's alone: the
49
+ // socket of a person's shell (`jr2 workspace shell`, ADR-0076)
48
50
  // initContainer preflight the USER'S image + that volume → ADR-0037's probe, the thing that
49
51
  // proves a registry ref, whose first appearance is this provision
50
52
  // container harness the Sandbox Image, command overridden, /work + /opt/jr2 mounted
@@ -105,6 +107,8 @@ import {
105
107
  CONVERSATIONS_DIR,
106
108
  CONVERSATIONS_DIR_ENV,
107
109
  CONVERSATIONS_VOLUME,
110
+ SHELL_DIR,
111
+ SHELL_VOLUME,
108
112
  HELD_MOUNT,
109
113
  CA_CONFIGMAP,
110
114
  heldManifestKey,
@@ -115,6 +119,15 @@ import {
115
119
  PRIORITY_CLASS_SANDBOX,
116
120
  REPOS_MOUNT,
117
121
  sandboxCustodianConfigMap,
122
+ ANNOTATION_HOLD,
123
+ ANNOTATION_KEEPALIVE,
124
+ ANNOTATION_WORKSPACE_PATH,
125
+ HOLD_HELD,
126
+ HOLD_RELEASED,
127
+ LABEL_SANDBOX_RUN,
128
+ LABEL_SANDBOX_WORKFLOW,
129
+ LABEL_SANDBOX_WORKSPACE,
130
+ workspaceLabel,
118
131
  } from "./names.ts";
119
132
  import {
120
133
  CONFIGMAPS,
@@ -770,6 +783,7 @@ export function kubeSandbox(opts: KubeSandboxOptions = {}): SandboxPort {
770
783
  name: string;
771
784
  runId: string;
772
785
  workflow: string;
786
+ path: string;
773
787
  image?: string;
774
788
  user?: string;
775
789
  workGroup?: number;
@@ -802,8 +816,16 @@ export function kubeSandbox(opts: KubeSandboxOptions = {}): SandboxPort {
802
816
  metadata: {
803
817
  name: req.name,
804
818
  namespace: ns,
805
- // The run↔workspace link `jr2 ls` groups by (ADR-0009/0012) — readable without the host.
806
- labels: { "jr2.dev/run": req.runId, "jr2.dev/workflow": req.workflow },
819
+ // The run↔workspace link the `jr2 workspace` verbs read (ADR-0009/0012), without the host:
820
+ // the run, the Workflow and a selector form of the path as labels, the exact path as an
821
+ // annotation. The hold mark is `held` from this first write (ADR-0021); the Lease writes it
822
+ // again with each renewal, and `released` when it stops for good.
823
+ labels: {
824
+ [LABEL_SANDBOX_RUN]: req.runId,
825
+ [LABEL_SANDBOX_WORKFLOW]: req.workflow,
826
+ ...(req.path ? { [LABEL_SANDBOX_WORKSPACE]: workspaceLabel(req.path) } : {}),
827
+ },
828
+ annotations: { ...(req.path ? { [ANNOTATION_WORKSPACE_PATH]: req.path } : {}), [ANNOTATION_HOLD]: HOLD_HELD },
807
829
  },
808
830
  spec: {
809
831
  // The Sandbox Image, unmodified (ADR-0037) — the user's tools, its own USER and HOME, and
@@ -888,6 +910,9 @@ export function kubeSandbox(opts: KubeSandboxOptions = {}): SandboxPort {
888
910
  // that Workspace is `Lost` (ADR-0021) — and mounted below into the Harness container
889
911
  // ALONE: the User Container must not read an Agent's history.
890
912
  { name: CONVERSATIONS_VOLUME, emptyDir: {} },
913
+ // The shell socket's folder (ADR-0076): the Harness listens there for `jr2 workspace
914
+ // shell`, in the Harness container ALONE, where no process that it confines makes a file.
915
+ { name: SHELL_VOLUME, emptyDir: {} },
891
916
  // Only for ADR-0037's fallback seat: uid 1000 on a stranger's base has no home at all.
892
917
  ...seat.homeVolume,
893
918
  // The Custodian's (its values, leaves and config), and the trust ConfigMap (ADR-0020).
@@ -902,6 +927,7 @@ export function kubeSandbox(opts: KubeSandboxOptions = {}): SandboxPort {
902
927
  { name: "work", mountPath: WORK_ROOT },
903
928
  RUNTIME_VOLUME_MOUNT,
904
929
  { name: CONVERSATIONS_VOLUME, mountPath: CONVERSATIONS_DIR },
930
+ { name: SHELL_VOLUME, mountPath: SHELL_DIR },
905
931
  ...seat.homeMount,
906
932
  ...custodian.harnessMounts,
907
933
  ],
@@ -1448,13 +1474,35 @@ export function kubeSandbox(opts: KubeSandboxOptions = {}): SandboxPort {
1448
1474
  leaseIntervalMs,
1449
1475
 
1450
1476
  async renew(name) {
1451
- // A write, and only that (ADR-0021): the stamp the operator's idle GC reads. What the
1452
- // workspace IS comes from the watch, never from this answer.
1477
+ // A write, and only that (ADR-0021): the stamp the operator's idle GC reads, and the hold
1478
+ // mark, written again so a restored run's Lease marks the Sandbox `held`. What the workspace
1479
+ // IS comes from the watch, never from this answer.
1453
1480
  await kube().patch(SANDBOXES, ns, name, {
1454
- metadata: { annotations: { "jr2.dev/keepalive": new Date().toISOString() } },
1481
+ metadata: { annotations: { [ANNOTATION_KEEPALIVE]: new Date().toISOString(), [ANNOTATION_HOLD]: HOLD_HELD } },
1455
1482
  });
1456
1483
  },
1457
1484
 
1485
+ async releaseHold(name) {
1486
+ // The Lease stopped for good (ADR-0021): no run holds this Sandbox now, and `jr2 workspace rm`
1487
+ // may remove it. A write, and only that.
1488
+ await kube().patch(SANDBOXES, ns, name, { metadata: { annotations: { [ANNOTATION_HOLD]: HOLD_RELEASED } } });
1489
+ },
1490
+
1491
+ async releaseRun(runId) {
1492
+ // A restore ended this run without hosting it (ADR-0021): no Lease runs, so each of its
1493
+ // Sandboxes gets the mark here. The list is the cluster's, not the watch's cache, which may
1494
+ // not have listed yet at boot.
1495
+ const list = await kube().list<SandboxObject>(SANDBOXES, ns, { labelSelector: `${LABEL_SANDBOX_RUN}=${runId}` });
1496
+ await Promise.all(
1497
+ list.items.flatMap((sb) => {
1498
+ const name = sb.metadata?.name;
1499
+ return name
1500
+ ? [kube().patch(SANDBOXES, ns, name, { metadata: { annotations: { [ANNOTATION_HOLD]: HOLD_RELEASED } } })]
1501
+ : [];
1502
+ }),
1503
+ );
1504
+ },
1505
+
1458
1506
  continuity(name, listener) {
1459
1507
  // The watch's word on this name (ADR-0063): the first list, then every event. A dropped
1460
1508
  // watch says nothing, so this says nothing either — unknown is never loss.