@jr2/orchestrator 0.4.1 → 0.6.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.
- package/README.md +20 -2
- package/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +478 -0
- package/docs/concepts/architecture.md +243 -0
- package/docs/concepts/charts.md +146 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +257 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +658 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +298 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +306 -0
- package/docs/concepts/the-menu.md +381 -0
- package/docs/concepts/workspaces.md +756 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +383 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +993 -0
- package/docs/get-started/install.md +130 -0
- package/docs/guides/add-tools-to-the-sandbox.md +308 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +224 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +230 -0
- package/docs/guides/look-inside-a-sandbox.md +177 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +532 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +587 -0
- package/docs/guides/run-one-worker-per-item.md +322 -0
- package/docs/guides/set-an-agents-access-and-dials.md +420 -0
- package/docs/guides/size-a-workspace.md +217 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +214 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +349 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +237 -0
- package/docs/reference/authoring-surface/agent.md +151 -0
- package/docs/reference/authoring-surface/answer.md +156 -0
- package/docs/reference/authoring-surface/arg.md +82 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +206 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +96 -0
- package/docs/reference/authoring-surface/file.md +142 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +259 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +105 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +143 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +130 -0
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +139 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +15 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +61 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +20 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
- package/docs/reference/authoring-surface/type-customize.md +111 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +37 -0
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +34 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +24 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +115 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +287 -0
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +217 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +765 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +86 -0
- package/docs/reference/glossary.md +687 -0
- package/docs/reference/http-api.md +1386 -0
- package/docs/reference/machines.md +189 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +207 -0
- package/package.json +5 -3
- package/src/actor.ts +694 -106
- package/src/agent.ts +114 -38
- package/src/ambient.ts +3 -4
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +345 -46
- package/src/charts.ts +43 -8
- package/src/config.ts +440 -178
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +325 -99
- package/src/customize.ts +199 -49
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +641 -128
- package/src/effector-wire.ts +165 -13
- package/src/fail.ts +222 -0
- package/src/harness-client.ts +587 -50
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +556 -58
- package/src/http.ts +480 -30
- package/src/index.ts +74 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +85 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +24 -7
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +42 -58
- package/src/names.ts +19 -6
- package/src/notices.ts +483 -41
- package/src/open-body.ts +242 -40
- package/src/open-effect.ts +81 -8
- package/src/open.ts +90 -6
- package/src/parts.ts +213 -30
- package/src/party.ts +101 -2
- package/src/pick.ts +497 -11
- package/src/pool.ts +282 -50
- package/src/registration.ts +133 -12
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +22 -3
- package/src/run-host.ts +665 -45
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +457 -71
- package/src/sandbox-kube.ts +66 -17
- package/src/secret-placement.ts +59 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +325 -62
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/tokens.ts +107 -32
- package/src/vocabulary.ts +88 -14
- package/src/wire.ts +100 -26
- package/src/workspace.ts +771 -238
- package/src/worktree.ts +249 -0
package/src/size.ts
CHANGED
|
@@ -23,15 +23,73 @@
|
|
|
23
23
|
/** A Kubernetes quantity: `"2Gi"`, `"1500m"`, `"2"`, or a plain number (`2` cpus, bytes of memory). */
|
|
24
24
|
export type Quantity = string | number;
|
|
25
25
|
|
|
26
|
+
// A Size (ADR-0060, GLOSSARY.md "Size"): `limits.memory` and `limits.cpu`, nothing else. A field
|
|
27
|
+
// left out resolves down the chain (`resolveSize`) — the Instance's `sandbox.resources`, then the
|
|
28
|
+
// kit default (`KIT_SIZE`). The same shape is the User Container's split (`userSeatOf`,
|
|
29
|
+
// `splitSize`), where a field left out means the seat has no limit of its own for it and shares
|
|
30
|
+
// the pod's budget.
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* kit default of 2Gi and 1 cpu.
|
|
32
|
+
* The Size of a Workspace: the most memory and cpu that its whole Sandbox can use. jr2 reserves
|
|
33
|
+
* all of it on the node. Thus a Sandbox never gets less than its Size and never uses more.
|
|
30
34
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
35
|
+
* Write it as `{ limits: { memory, cpu } }`. You can write it in three places: the `resources`
|
|
36
|
+
* option of `workspace()`, `customize(machine, { resources })`, and `sandbox.resources` in
|
|
37
|
+
* `jr2.config.ts`. The Size is a fact of the Machine. The Instance's `sandbox.resources` applies
|
|
38
|
+
* only to a Workspace that states no Size. A field that neither states comes from the kit default:
|
|
39
|
+
* memory `2Gi`, cpu `1`.
|
|
40
|
+
*
|
|
41
|
+
* The same shape is the User Container's part of the Size, in `user: { image, resources }`. There,
|
|
42
|
+
* a field you leave out gives the User Container no limit of its own: it shares the pod's budget.
|
|
43
|
+
*
|
|
44
|
+
* It refuses any key but `limits.memory` and `limits.cpu`, where you write it. The message names
|
|
45
|
+
* the field: `<where>` is where you wrote the Size, for example `workspace(): resources`.
|
|
46
|
+
*
|
|
47
|
+
* - `<where>.requests is not accepted — jr2 reserves the whole Size, so every request equals its
|
|
48
|
+
* limit — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template
|
|
49
|
+
* size (ADR-0060)`
|
|
50
|
+
* - `<where>.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing else —
|
|
51
|
+
* Agent Substrate's template size (ADR-0060)`
|
|
52
|
+
* - `<where>.limits.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing
|
|
53
|
+
* else — Agent Substrate's template size (ADR-0060)`
|
|
54
|
+
* - `<where>.limits.memory: <value> is not a positive quantity (e.g. "2Gi", "512Mi")`, and for
|
|
55
|
+
* cpu, `<where>.limits.cpu: <value> is not a positive quantity (e.g. "2", "1500m")`
|
|
56
|
+
*
|
|
57
|
+
* `jr2 up` also refuses a Size that leaves the Harness container less than its floor. The message
|
|
58
|
+
* starts with `the Size leaves the Harness container <memory> memory, below its 256Mi floor:` or
|
|
59
|
+
* with `the Size leaves the Harness container no cpu:`. The text that follows names each number
|
|
60
|
+
* and where it came from.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { customize, type Size } from "@jr2/orchestrator";
|
|
65
|
+
* import { task } from "@jr2/machines";
|
|
66
|
+
*
|
|
67
|
+
* const size: Size = { limits: { memory: "4Gi", cpu: "2" } };
|
|
68
|
+
*
|
|
69
|
+
* export const machine = customize(task, {
|
|
70
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
71
|
+
* agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
|
|
72
|
+
* resources: size,
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @see docs/guides/size-a-workspace.md
|
|
77
|
+
* @see docs/concepts/workspaces.md
|
|
78
|
+
* @see docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md
|
|
33
79
|
*/
|
|
34
|
-
export type Size = {
|
|
80
|
+
export type Size = {
|
|
81
|
+
/** The two limits. Write `limits: {}` to state no field: each field then comes from the next
|
|
82
|
+
* source. */
|
|
83
|
+
limits: {
|
|
84
|
+
/** The memory limit, as a Kubernetes quantity: `"4Gi"`, `"512Mi"`, or a number of bytes.
|
|
85
|
+
* It must be more than zero. When you leave it out, it comes from the next source, as above. */
|
|
86
|
+
memory?: Quantity;
|
|
87
|
+
/** The cpu limit, as a Kubernetes quantity: `"2"`, `"1500m"`, or a number of cpus. It must be
|
|
88
|
+
* more than zero. jr2 enforces it. When you leave it out, it comes from the next source, as
|
|
89
|
+
* above. */
|
|
90
|
+
cpu?: Quantity;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
35
93
|
|
|
36
94
|
/** The kit's default Size (ADR-0060). Node gives each process a V8 heap of about 55% of its
|
|
37
95
|
* container limit, and `tsc` on a mid-size repo aborted below about 1.1Gi, so 1Gi fails a common
|
|
@@ -303,11 +361,65 @@ export function splitSize(size: ResolvedSize, user?: Size): SizeSplit {
|
|
|
303
361
|
|
|
304
362
|
// --- the User Container seat -------------------------------------------------------------------
|
|
305
363
|
|
|
364
|
+
// The User Container as a Machine states it (ADR-0005, ADR-0060): an image in ADR-0037's two
|
|
365
|
+
// shapes, and optionally its split of the Size. The object form exists for the split alone.
|
|
306
366
|
/**
|
|
307
|
-
* The User Container
|
|
308
|
-
*
|
|
367
|
+
* The User Container in its object form: its image, and its part of the Workspace's Size. The User
|
|
368
|
+
* Container is an optional container of your own in the Sandbox pod. It runs its own entrypoint
|
|
369
|
+
* with `/work` mounted, and jr2 never builds into it, probes it or sends it commands. Use it for a
|
|
370
|
+
* service that must run without an Agent, such as an sshd.
|
|
371
|
+
*
|
|
372
|
+
* Write it in the `user` option of `workspace()` or of `customize()`. That option also takes the
|
|
373
|
+
* image as a string; use this object form only to give the User Container a part of the Size. A
|
|
374
|
+
* `customize()` that names `user` replaces the whole User Container, its part of the Size
|
|
375
|
+
* included.
|
|
376
|
+
*
|
|
377
|
+
* It refuses, where you write it. `<where>` is `workspace()` or `customize()`:
|
|
378
|
+
*
|
|
379
|
+
* - an image that is not a non-empty string. The message starts with
|
|
380
|
+
* `` <where>: `user` must be a non-empty string (an image), or { image, resources? } ``.
|
|
381
|
+
* - `<where>: user.<key> is not accepted — the User Container is { image, resources? } (ADR-0005,
|
|
382
|
+
* ADR-0060)`
|
|
383
|
+
*
|
|
384
|
+
* `resources` is checked as a {@link Size}, with the same refusals.
|
|
385
|
+
*
|
|
386
|
+
* `jr2 up` refuses a part that leaves the Harness container less than its floor. The message starts
|
|
387
|
+
* with `the Size leaves the Harness container <memory> memory, below its 256Mi floor:` or with
|
|
388
|
+
* `the Size leaves the Harness container no cpu:`. The text that follows names each number and
|
|
389
|
+
* where it came from.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* import { customize, type UserContainer } from "@jr2/orchestrator";
|
|
394
|
+
* import { task } from "@jr2/machines";
|
|
395
|
+
*
|
|
396
|
+
* const sshd: UserContainer = {
|
|
397
|
+
* image: "registry.example.com/sshd:1.0",
|
|
398
|
+
* resources: { limits: { memory: "256Mi", cpu: "250m" } },
|
|
399
|
+
* };
|
|
400
|
+
*
|
|
401
|
+
* export const machine = customize(task, {
|
|
402
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
403
|
+
* agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
|
|
404
|
+
* user: sshd,
|
|
405
|
+
* });
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* @see docs/concepts/workspaces.md
|
|
409
|
+
* @see docs/guides/size-a-workspace.md
|
|
410
|
+
* @see docs/reference/decisions/0005-sandbox-pod-composition.md
|
|
411
|
+
* @see docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md
|
|
309
412
|
*/
|
|
310
|
-
export type UserContainer = {
|
|
413
|
+
export type UserContainer = {
|
|
414
|
+
/** The image: a `file:` URL to a docker context that your module ships
|
|
415
|
+
* (`import.meta.resolve("./sshd")`), which `jr2 up` builds, or a registry ref. It must not be
|
|
416
|
+
* empty. */
|
|
417
|
+
image: string;
|
|
418
|
+
/** This container's part of the Workspace's Size, as a Size. jr2 takes it from the Harness
|
|
419
|
+
* container's part. A field you leave out gives this container no limit of its own for it: it
|
|
420
|
+
* shares the pod's budget. Default: no part. */
|
|
421
|
+
resources?: Size;
|
|
422
|
+
};
|
|
311
423
|
|
|
312
424
|
/** The `user` option in either shape, read as its two halves. */
|
|
313
425
|
export function userSeatOf(user: string | UserContainer | undefined): { image?: string; resources?: Size } {
|
package/src/snapshot-store.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// store is owed — with `:memory:` for tests. A persist survives a process crash, not a power loss
|
|
5
5
|
// (WAL, `synchronous=NORMAL`), and the write stays synchronous so the row is on disk before the
|
|
6
6
|
// Machine's next side effect runs. `markLost` records a run whose live state could not be
|
|
7
|
-
// re-hydrated (e.g. its flue handle is gone); a non-live row
|
|
7
|
+
// re-hydrated (e.g. its flue handle is gone), keeping any snapshot the row had; a non-live row
|
|
8
|
+
// lives a week, then `sweep` deletes it.
|
|
8
9
|
|
|
9
10
|
import { DatabaseSync } from "node:sqlite";
|
|
10
11
|
import type { StoredSnapshot } from "./durability.ts";
|
|
@@ -37,18 +38,31 @@ export interface SnapshotStore {
|
|
|
37
38
|
* silently to the live one, which makes resolution unsound.
|
|
38
39
|
*/
|
|
39
40
|
findIdsByPrefix(prefix: string, limit: number): Promise<string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* The run whose blob carries the idempotency key `effect` (`dispatchedBy.effect`, ADR-0071), in
|
|
43
|
+
* any status, or undefined: what answers a re-sent dispatch with the run it already started. A
|
|
44
|
+
* row `markLost` wrote with no save before it has no snapshot, and so no key; a swept row is gone
|
|
45
|
+
* with its key.
|
|
46
|
+
*/
|
|
47
|
+
findByDispatch(effect: string): Promise<string | undefined>;
|
|
40
48
|
save(runId: string, snapshot: unknown, status?: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Record a run whose live state could not be re-hydrated. An upsert: a row that was saved KEEPS
|
|
51
|
+
* its snapshot and its idempotency key, because a lost dispatched run must keep answering its
|
|
52
|
+
* key — a re-send from its caller would otherwise start a second run (ADR-0071). Only a run with
|
|
53
|
+
* no row before gets a row with no snapshot.
|
|
54
|
+
*/
|
|
41
55
|
markLost(runId: string, reason: string): Promise<void>;
|
|
42
56
|
/**
|
|
43
57
|
* Record a run this Orchestrator can no longer read (ADR-0030, ADR-0069) — refused, not resumed:
|
|
44
58
|
* the Assembly its snapshot names is not on the volume, was published for another Kit version or
|
|
45
59
|
* does not load, or its Machine's shape does not match the snapshot's stamp.
|
|
46
60
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
61
|
+
* An UPDATE of a row that is there, and it KEEPS the snapshot: a drifted run is intact and only
|
|
62
|
+
* unreadable by this Orchestrator, and it is the one a human most needs to inspect. Nulling it
|
|
63
|
+
* would also hide the run entirely: `read()` returns undefined for a null blob, so
|
|
64
|
+
* `jr2 status <id>` would answer `no run` — a refusal indistinguishable from a run that never
|
|
65
|
+
* existed. It keeps the row's idempotency key too (ADR-0071).
|
|
52
66
|
*/
|
|
53
67
|
markDrifted(runId: string, reason: string): Promise<void>;
|
|
54
68
|
close(): Promise<void>;
|
|
@@ -62,6 +76,20 @@ type Row = {
|
|
|
62
76
|
updated_at: string;
|
|
63
77
|
};
|
|
64
78
|
|
|
79
|
+
/** The lookup `findByDispatch` runs, exported so a test can ask the planner about THIS statement,
|
|
80
|
+
* not a copy of it: the column is indexed, so the plan is a seek. */
|
|
81
|
+
export const FIND_BY_DISPATCH_SQL = `SELECT run_id FROM machine_snapshots
|
|
82
|
+
WHERE dispatch_effect = ? ORDER BY rowid LIMIT 1`;
|
|
83
|
+
|
|
84
|
+
/** The idempotency key a blob carries at `dispatchedBy.effect` (ADR-0071), or null. */
|
|
85
|
+
function dispatchEffectOf(snapshot: unknown): string | null {
|
|
86
|
+
if (typeof snapshot !== "object" || snapshot === null) return null;
|
|
87
|
+
const by = (snapshot as { dispatchedBy?: unknown }).dispatchedBy;
|
|
88
|
+
if (typeof by !== "object" || by === null) return null;
|
|
89
|
+
const effect = (by as { effect?: unknown }).effect;
|
|
90
|
+
return typeof effect === "string" ? effect : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
export class SqliteSnapshotStore implements SnapshotStore {
|
|
66
94
|
private readonly db: DatabaseSync;
|
|
67
95
|
|
|
@@ -83,6 +111,16 @@ export class SqliteSnapshotStore implements SnapshotStore {
|
|
|
83
111
|
updated_at TEXT
|
|
84
112
|
)
|
|
85
113
|
`);
|
|
114
|
+
// The idempotency key of a dispatched run (ADR-0071) is copied out of its blob into a column of
|
|
115
|
+
// its own when `save` writes the row, and the column is indexed. Not an expression index over
|
|
116
|
+
// `json_extract(snapshot, …)`: that runs on every row and THROWS on a blob SQLite's JSON parser
|
|
117
|
+
// refuses (nested about a thousand deep), so one such row would fail its own save and every
|
|
118
|
+
// later `init()` over the volume. A column also costs no JSON parse a save.
|
|
119
|
+
const columns = this.db.prepare(`PRAGMA table_info(machine_snapshots)`).all() as Array<{ name: string }>;
|
|
120
|
+
if (!columns.some((c) => c.name === "dispatch_effect")) {
|
|
121
|
+
this.db.exec(`ALTER TABLE machine_snapshots ADD COLUMN dispatch_effect TEXT`);
|
|
122
|
+
}
|
|
123
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS machine_snapshots_dispatch_effect ON machine_snapshots(dispatch_effect)`);
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
async load(runId: string): Promise<StoredSnapshot | null> {
|
|
@@ -151,18 +189,24 @@ export class SqliteSnapshotStore implements SnapshotStore {
|
|
|
151
189
|
return rows.map((row) => row.run_id);
|
|
152
190
|
}
|
|
153
191
|
|
|
192
|
+
async findByDispatch(effect: string): Promise<string | undefined> {
|
|
193
|
+
const row = this.db.prepare(FIND_BY_DISPATCH_SQL).get(effect) as { run_id: string } | undefined;
|
|
194
|
+
return row?.run_id;
|
|
195
|
+
}
|
|
196
|
+
|
|
154
197
|
async save(runId: string, snapshot: unknown, status = "live"): Promise<void> {
|
|
155
198
|
this.db
|
|
156
199
|
.prepare(
|
|
157
|
-
`INSERT INTO machine_snapshots (run_id, status, snapshot, reason, updated_at)
|
|
158
|
-
VALUES (?, ?, ?, NULL, ?)
|
|
200
|
+
`INSERT INTO machine_snapshots (run_id, status, snapshot, reason, updated_at, dispatch_effect)
|
|
201
|
+
VALUES (?, ?, ?, NULL, ?, ?)
|
|
159
202
|
ON CONFLICT(run_id) DO UPDATE SET
|
|
160
203
|
status = excluded.status,
|
|
161
204
|
snapshot = excluded.snapshot,
|
|
162
205
|
reason = NULL,
|
|
163
|
-
updated_at = excluded.updated_at
|
|
206
|
+
updated_at = excluded.updated_at,
|
|
207
|
+
dispatch_effect = excluded.dispatch_effect`,
|
|
164
208
|
)
|
|
165
|
-
.run(runId, status, JSON.stringify(snapshot), new Date().toISOString());
|
|
209
|
+
.run(runId, status, JSON.stringify(snapshot), new Date().toISOString(), dispatchEffectOf(snapshot));
|
|
166
210
|
}
|
|
167
211
|
|
|
168
212
|
async markLost(runId: string, reason: string): Promise<void> {
|
package/src/stub-harness.ts
CHANGED
|
@@ -5,20 +5,26 @@
|
|
|
5
5
|
// the durable stream open, and never act — the Machine parks exactly as it would against a
|
|
6
6
|
// silent real Harness, and e2e drives it by playing the agent against `/mcp/<iid>` instead.
|
|
7
7
|
//
|
|
8
|
-
// Wire (ADR-0027 — the normative model of the
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// Wire (ADR-0027 — the normative model of the conversation endpoints; `@jr2/harness` serves it for
|
|
9
|
+
// real), and the pod's memory kills, which a stub never has (ADR-0062):
|
|
10
|
+
// POST /agents/:name/:id {message, cwd?, definition, model?, thinkingLevel?, notices?, kept?} → 200 { streamUrl, offset, submissionId }
|
|
11
|
+
// (`message` and `cwd` are the Turn's Frame — what it is about and where it works, ADR-0057;
|
|
12
|
+
// `kept` admits as any POST does, because the stub keeps every conversation — the real Harness
|
|
13
|
+
// answers 404 when it keeps none, ADR-0062)
|
|
11
14
|
// GET /agents/:name/:id?offset=…[&view=updates] → 200 `[]` + Stream-Next-Offset/Up-To-Date
|
|
12
15
|
// GET /agents/:name/:id?…&live=long-poll → parked; 204 + same headers on timeout
|
|
13
|
-
// POST /agents/:name/:id/abort
|
|
16
|
+
// POST /agents/:name/:id/abort [{cause?}] → 200 { aborted }
|
|
14
17
|
// GET /agents/:name/:id?view=history → 200 { …, settlements }
|
|
18
|
+
// GET /memory-kills → 200 { kills: [] }
|
|
15
19
|
// (the client's `wait(admission)` long-polls `streamUrl?view=updates` from the admission offset;
|
|
16
20
|
// an empty stream parks it — exactly the "admitted, never settles" semantics the mechanics tier
|
|
17
21
|
// needs.) The client then re-polls calmly at the long-poll cadence. `close()` severs parked polls.
|
|
18
22
|
//
|
|
19
|
-
// A stub submission therefore ends
|
|
20
|
-
//
|
|
21
|
-
//
|
|
23
|
+
// A stub submission therefore ends one way: the sweep, when the state that asked for the turn stops
|
|
24
|
+
// waiting (ADR-0024). Its outcome follows the sweep's cause, as the real Harness's does: `ended` for
|
|
25
|
+
// the running Submission when the Turn's own pick ended it (`cause: "pick"`), `aborted` for every
|
|
26
|
+
// other Submission and every other end. That is what `history`'s `settlements` carries, and it is
|
|
27
|
+
// the only place a black-box test can see a turn end — the run itself is untouched by an abort.
|
|
22
28
|
//
|
|
23
29
|
// This can later grow scriptable behavior or be swapped for a real local Harness without
|
|
24
30
|
// touching actor code — it is only a different URL.
|
|
@@ -55,8 +61,8 @@ export type Admission = {
|
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
/** One settled submission, in the shape `history()` reports it. The stub settles submissions for
|
|
58
|
-
* exactly one reason — an abort (ADR-0024) — so `outcome`
|
|
59
|
-
export type StubSettlement = { submissionId: string; outcome: "aborted" };
|
|
64
|
+
* exactly one reason — an abort (ADR-0024) — so `outcome` is one of the two a sweep gives. */
|
|
65
|
+
export type StubSettlement = { submissionId: string; outcome: "ended" | "aborted" };
|
|
60
66
|
|
|
61
67
|
export type StubHarnessOptions = {
|
|
62
68
|
/** Listen port. Default 0 → ephemeral (read back from `url`). */
|
|
@@ -102,17 +108,39 @@ export async function startStubHarness(opts: StubHarnessOptions = {}): Promise<R
|
|
|
102
108
|
// End every in-flight and queued submission for one instance (`agents.abort` — ADR-0024).
|
|
103
109
|
// Answers `{ aborted }`: whether there was anything to end, exactly as the real Harness
|
|
104
110
|
// does for an idle instance. Settlement is recorded here rather than pushed on the stream, because nothing is
|
|
105
|
-
// listening — the actor that asked for the turn is already stopped.
|
|
111
|
+
// listening — the actor that asked for the turn is already stopped. The body's cause decides the
|
|
112
|
+
// running Submission's outcome, the first unsettled one (`AbortRequest`): `ended` for `pick` with
|
|
113
|
+
// that Submission's id; no body, another id, or any other cause, `aborted`. What is queued
|
|
114
|
+
// behind it never ran: `aborted`.
|
|
106
115
|
const aborting = req.method === "POST" && ABORT_PATH.exec(url.pathname);
|
|
107
116
|
if (aborting) {
|
|
108
117
|
const [, agentName, instanceId] = aborting as unknown as [string, string, string];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
let body = "";
|
|
119
|
+
req.on("data", (chunk: Buffer) => (body += chunk));
|
|
120
|
+
req.on("end", () => {
|
|
121
|
+
const sent = safeParse(body) as { cause?: unknown; submissionId?: unknown } | undefined;
|
|
122
|
+
const k = key(decodeURIComponent(agentName), decodeURIComponent(instanceId));
|
|
123
|
+
const ended = unsettled.get(k)?.splice(0) ?? [];
|
|
124
|
+
const byPick = sent?.cause === "pick" && sent.submissionId === ended[0];
|
|
125
|
+
const settled = settlements.get(k) ?? [];
|
|
126
|
+
settled.push(
|
|
127
|
+
...ended.map((submissionId, i) => ({
|
|
128
|
+
submissionId,
|
|
129
|
+
outcome: byPick && i === 0 ? ("ended" as const) : ("aborted" as const),
|
|
130
|
+
})),
|
|
131
|
+
);
|
|
132
|
+
settlements.set(k, settled);
|
|
133
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
134
|
+
res.end(JSON.stringify({ aborted: ended.length > 0 }));
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The pod's memory kills (ADR-0062): a stub runs no process, so none. Answered, not a 404, so a
|
|
140
|
+
// Turn the stub admits logs no failed read.
|
|
141
|
+
if (req.method === "GET" && url.pathname === "/memory-kills") {
|
|
114
142
|
res.writeHead(200, { "content-type": "application/json" });
|
|
115
|
-
res.end(JSON.stringify({
|
|
143
|
+
res.end(JSON.stringify({ kills: [] }));
|
|
116
144
|
return;
|
|
117
145
|
}
|
|
118
146
|
|
package/src/timers.ts
CHANGED
|
@@ -106,11 +106,15 @@ export function scheduledFor(ref: AnyActorRef): Array<{ id: string; event: Event
|
|
|
106
106
|
* Install it before `start()` (the root's creation event is early enough): a timer armed before the
|
|
107
107
|
* wrap is not seen. The same xstate internals as the rest of this module; the canary is the same.
|
|
108
108
|
*/
|
|
109
|
-
export function watchTimers(
|
|
109
|
+
export function watchTimers(
|
|
110
|
+
system: AnyActorRef["system"],
|
|
111
|
+
hooks: TimerHooks = {},
|
|
112
|
+
): (source: unknown, event: unknown) => boolean {
|
|
110
113
|
const scheduler = system.scheduler;
|
|
111
114
|
const { schedule, cancel } = scheduler;
|
|
112
115
|
const armed = new Map<string, { source: AnyActorRef; event: EventObject }>();
|
|
113
116
|
scheduler.schedule = (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
|
|
117
|
+
hooks.arming?.(source);
|
|
114
118
|
schedule(source, target, event, delay, id);
|
|
115
119
|
armed.set(keyOf(source, id), { source, event });
|
|
116
120
|
};
|
|
@@ -129,6 +133,15 @@ export function watchTimers(system: AnyActorRef["system"]): (source: unknown, ev
|
|
|
129
133
|
};
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
/** What `watchTimers` tells as the scheduler arms a timer: `arming` is called with the timer's
|
|
137
|
+
* source just before xstate schedules it, and xstate then arms it on the system's clock in the same
|
|
138
|
+
* call. xstate schedules in the deferred effects of the event whose actions armed the timer, so the
|
|
139
|
+
* source still takes that event. The run host keeps each timer's cause this way (`cause.ts`,
|
|
140
|
+
* ADR-0024). */
|
|
141
|
+
export type TimerHooks = {
|
|
142
|
+
arming?: (source: AnyActorRef) => void;
|
|
143
|
+
};
|
|
144
|
+
|
|
132
145
|
/**
|
|
133
146
|
* Start a restored run, then schedule each recorded timer again on it. One function, so the order
|
|
134
147
|
* cannot be got wrong: re-armed BEFORE `start()`, a timer is armed twice and fires twice, because
|
package/src/tokens.ts
CHANGED
|
@@ -17,17 +17,20 @@
|
|
|
17
17
|
// (`JR2_PUBLISH_TOKEN`), read by `jr2 up` through the kube API only. Every other
|
|
18
18
|
// route answers it 403: it is a known token, but not one that route accepts.
|
|
19
19
|
//
|
|
20
|
-
// Sandbox token the Custodian's
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// (
|
|
20
|
+
// Sandbox token the Custodian's credentials, two of them, each valid on its own routes alone
|
|
21
|
+
// (ADR-0076). Both are signed names of a POD hosting Turns — a Sandbox, or the
|
|
22
|
+
// Instance Harness's Service name (ADR-0031) — and both live in the Custodian
|
|
23
|
+
// container ALONE, which no Workspace code reaches (ADR-0059):
|
|
24
|
+
// control token `agent:<pod>.<sig>`. Delivers to `kind: "agent"` registrations whose pod is
|
|
25
|
+
// THIS one, and only for the slot the Harness states (`X-Jr2-Slot`), on the
|
|
26
|
+
// four agent routes. The Harness process alone holds its secret Stand-in.
|
|
27
|
+
// fetch token `<pod>.<sig>`. Asks for a fetch for THIS Sandbox (ADR-0053), on
|
|
28
|
+
// `POST /sandboxes/<pod>/fetch` alone. Its Stand-in is public: it can do
|
|
29
|
+
// nothing else.
|
|
30
|
+
// Neither is ever a Gate's (a compromised Agent must not approve its own review),
|
|
31
|
+
// and neither reaches another pod (`coding.ts`'s iids are derivable and feature
|
|
32
|
+
// ids are readable from the Work Source, so a merely run-scoped token would let
|
|
33
|
+
// one feature's coder inject a verdict into another feature's reviewer).
|
|
31
34
|
//
|
|
32
35
|
// And one bearer the other way (ADR-0058), which is no principal here because this server never
|
|
33
36
|
// checks it:
|
|
@@ -53,17 +56,53 @@ export type Principal =
|
|
|
53
56
|
/** `jr2 up`, publishing an Assembly (ADR-0069). Only `PUT /assembly` accepts it: it is not an
|
|
54
57
|
* Instance principal, so it reads no run and starts none. */
|
|
55
58
|
| { kind: "publisher" }
|
|
56
|
-
/** One Sandbox's
|
|
57
|
-
|
|
59
|
+
/** One Sandbox's fetch token (ADR-0053, ADR-0076): asks for a fetch for that Sandbox, and does
|
|
60
|
+
* nothing else. */
|
|
61
|
+
| { kind: "sandbox"; sandbox: string }
|
|
62
|
+
/** One pod's control token (ADR-0076), with the slot the Harness stated for the conversation
|
|
63
|
+
* (`X-Jr2-Slot`): speaks on the agent routes for that slot's registrations in that pod, and for no
|
|
64
|
+
* other. */
|
|
65
|
+
| { kind: "agent"; sandbox: string; slot: string };
|
|
58
66
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
67
|
+
/** The header the Harness states a control request's slot in (ADR-0076): the Agent's actor path
|
|
68
|
+
* and agent name, its continued conversation's Instance ID without the run id. */
|
|
69
|
+
export const SLOT_HEADER = "x-jr2-slot";
|
|
61
70
|
|
|
62
|
-
/**
|
|
71
|
+
/** Resolve a bearer token, and the slot a control request states, to a principal; undefined = not a
|
|
72
|
+
* token we minted, or a control token with no slot (→ 401). */
|
|
73
|
+
export type Authenticator = (bearer: string | undefined, slot?: string) => Principal | undefined;
|
|
74
|
+
|
|
75
|
+
/** The fetch token for one Sandbox: its name, signed. Same name + key → same token, always. The
|
|
76
|
+
* bare form of ADR-0013's Sandbox token; since ADR-0076 it opens the fetch route alone. */
|
|
63
77
|
export function sandboxToken(key: Buffer, sandbox: string): string {
|
|
64
78
|
return `${sandbox}.${sign(key, sandbox)}`;
|
|
65
79
|
}
|
|
66
80
|
|
|
81
|
+
/** The domain of a control token's signed name (ADR-0076). No DNS label holds a `:`, so a control
|
|
82
|
+
* token is never read as a fetch token, nor the reverse. */
|
|
83
|
+
const CONTROL_PREFIX = "agent:";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The control token for one pod (ADR-0076): what the Custodian puts in place of the secret control
|
|
87
|
+
* Stand-in on the agent routes. A signed name like the fetch token — same key, same deterministic
|
|
88
|
+
* re-mint — in its own `agent:` domain. No slot is inside it: the Harness states the slot on each
|
|
89
|
+
* request, and the Orchestrator checks it against the registration's.
|
|
90
|
+
*/
|
|
91
|
+
export function controlToken(key: Buffer, pod: string): string {
|
|
92
|
+
const name = `${CONTROL_PREFIX}${pod}`;
|
|
93
|
+
return `${name}.${sign(key, name)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The secret control Stand-in for one pod (ADR-0076, ADR-0059): the bearer the Harness process
|
|
98
|
+
* alone holds, as a file under `/run/jr2/` that no confined child can read, and that the Custodian
|
|
99
|
+
* swaps for {@link controlToken} on the agent routes. A 43-character base64url HMAC, so no
|
|
100
|
+
* Workspace code can guess it. Its own `control-standin:` domain keeps it apart from every token.
|
|
101
|
+
*/
|
|
102
|
+
export function controlStandIn(key: Buffer, pod: string): string {
|
|
103
|
+
return sign(key, `control-standin:${pod}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
67
106
|
/**
|
|
68
107
|
* The Harness bearer for one placement (ADR-0058): what the Orchestrator presents on every Harness
|
|
69
108
|
* wire call — admit, stream, abort, echo — to the Harness in the pod `placement` names (a
|
|
@@ -138,37 +177,65 @@ export function createAuthenticator(opts: {
|
|
|
138
177
|
/** The publish token (ADR-0069). Absent → no bearer resolves to the publisher. */
|
|
139
178
|
publishToken?: string;
|
|
140
179
|
}): Authenticator {
|
|
141
|
-
return (bearer) => {
|
|
180
|
+
return (bearer, slot) => {
|
|
142
181
|
if (!bearer) return undefined;
|
|
143
182
|
if (constantTimeEqual(bearer, opts.instanceToken)) return { kind: "instance" };
|
|
144
183
|
if (opts.publishToken !== undefined && constantTimeEqual(bearer, opts.publishToken)) return { kind: "publisher" };
|
|
145
184
|
const cut = bearer.lastIndexOf(".");
|
|
146
185
|
if (cut <= 0) return undefined;
|
|
147
|
-
const
|
|
148
|
-
if (!constantTimeEqual(bearer.slice(cut + 1), sign(opts.signingKey,
|
|
149
|
-
|
|
186
|
+
const name = bearer.slice(0, cut);
|
|
187
|
+
if (!constantTimeEqual(bearer.slice(cut + 1), sign(opts.signingKey, name))) return undefined;
|
|
188
|
+
// A control token (ADR-0076), read BEFORE the bare form. A control request states its slot; one
|
|
189
|
+
// that states none is refused like an unknown token, so no route ever sees a control principal
|
|
190
|
+
// without a slot.
|
|
191
|
+
if (name.startsWith(CONTROL_PREFIX)) {
|
|
192
|
+
const pod = name.slice(CONTROL_PREFIX.length);
|
|
193
|
+
if (!isPodName(pod) || !slot) return undefined;
|
|
194
|
+
return { kind: "agent", sandbox: pod, slot };
|
|
195
|
+
}
|
|
196
|
+
// The bare fetch token, in its own domain alone: a name in any other domain (`harness:`,
|
|
197
|
+
// `control-standin:`, `held-standin:` …) is not a Sandbox's, whatever key signed it.
|
|
198
|
+
if (!isPodName(name)) return undefined;
|
|
199
|
+
return { kind: "sandbox", sandbox: name };
|
|
150
200
|
};
|
|
151
201
|
}
|
|
152
202
|
|
|
203
|
+
/** A pod's name as a token signs it: a DNS-1123 name, so it holds no `:` and no `/` — the marks of
|
|
204
|
+
* every other signed domain (ADR-0076). */
|
|
205
|
+
function isPodName(name: string): boolean {
|
|
206
|
+
return /^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$/.test(name);
|
|
207
|
+
}
|
|
208
|
+
|
|
153
209
|
/**
|
|
154
|
-
* May this principal
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
210
|
+
* May this principal speak on this agent registration's routes — its surface, a pick, a delivery's
|
|
211
|
+
* poll, a notice's acknowledgment? The Instance token may (it is the operator). A control token may
|
|
212
|
+
* only when the registration records ITS pod — which is why an Agent registration carries `sandbox`
|
|
213
|
+
* at all (ADR-0013) — AND the slot the Harness stated is the registration's (ADR-0076). The recorded
|
|
214
|
+
* pod is the one hosting the Turn: a Workspace's Sandbox, or `jr2-instance-harness` for a Menu-only
|
|
215
|
+
* registration (ADR-0031). An agent registration with NO pod at all is an explicit-`endpoint` run
|
|
216
|
+
* (the stub Harness on the host, in no pod): no pod's token can claim it. A fetch token never may:
|
|
217
|
+
* it opens the fetch route alone.
|
|
218
|
+
*
|
|
219
|
+
* The slot check finds a routing fault. It is not the boundary: only the Harness process holds the
|
|
220
|
+
* control Stand-in, so only the Harness can state a slot at all (ADR-0076).
|
|
160
221
|
*/
|
|
161
|
-
export function mayDeliverToAgent(principal: Principal,
|
|
222
|
+
export function mayDeliverToAgent(principal: Principal, registration: { sandbox?: string; slot?: string }): boolean {
|
|
162
223
|
if (principal.kind === "instance") return true;
|
|
163
|
-
if (principal.kind !== "
|
|
164
|
-
return
|
|
224
|
+
if (principal.kind !== "agent") return false;
|
|
225
|
+
return (
|
|
226
|
+
registration.sandbox !== undefined &&
|
|
227
|
+
registration.sandbox === principal.sandbox &&
|
|
228
|
+
registration.slot !== undefined &&
|
|
229
|
+
registration.slot === principal.slot
|
|
230
|
+
);
|
|
165
231
|
}
|
|
166
232
|
|
|
167
233
|
/**
|
|
168
|
-
* May this principal ask for a fetch on this Sandbox (ADR-0053)? A
|
|
234
|
+
* May this principal ask for a fetch on this Sandbox (ADR-0053)? A fetch token may ask for its
|
|
169
235
|
* OWN pod and no other — the scope is the caches that pod mounts, and the route refuses a Repo it
|
|
170
236
|
* does not. The Instance token may, on the same grounds it may deliver to any agent surface: it is
|
|
171
|
-
* the operator.
|
|
237
|
+
* the operator. A control token may not: it opens the agent routes alone (ADR-0076). The publish
|
|
238
|
+
* token may not: it opens the publish door and no other (ADR-0069).
|
|
172
239
|
*/
|
|
173
240
|
export function mayAskForSandbox(principal: Principal, sandbox: string): boolean {
|
|
174
241
|
if (principal.kind === "instance") return true;
|
|
@@ -176,7 +243,15 @@ export function mayAskForSandbox(principal: Principal, sandbox: string): boolean
|
|
|
176
243
|
return principal.sandbox === sandbox;
|
|
177
244
|
}
|
|
178
245
|
|
|
179
|
-
|
|
246
|
+
/** Whether a principal is an Agent's — a control token or a fetch token. A Gate refuses every one
|
|
247
|
+
* (ADR-0013, ADR-0076): an Agent never reaches a Gate. */
|
|
248
|
+
export function isAgentPrincipal(principal: Principal): boolean {
|
|
249
|
+
return principal.kind === "agent" || principal.kind === "sandbox";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** An HMAC of `value` under the signing key, base64url (43 characters). Exported so every signed
|
|
253
|
+
* name and Stand-in derives one way. */
|
|
254
|
+
export function sign(key: Buffer, value: string): string {
|
|
180
255
|
return createHmac("sha256", key).update(value).digest("base64url");
|
|
181
256
|
}
|
|
182
257
|
|