@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/sandbox-effect.ts
CHANGED
|
@@ -53,7 +53,7 @@ import type {
|
|
|
53
53
|
PushParts,
|
|
54
54
|
Unknown,
|
|
55
55
|
} from "./effect.ts";
|
|
56
|
-
import { lastPushOf, pushPairsOf } from "./effect.ts";
|
|
56
|
+
import { cutRowOf, lastPushOf, needsASlot, pushPairsOf, worktreePathFault, type LaterOutcome } from "./effect.ts";
|
|
57
57
|
import {
|
|
58
58
|
createEffectorClient,
|
|
59
59
|
EffectorForged,
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
import { open } from "./open.ts";
|
|
90
90
|
import type { OpenOf } from "./open-effect.ts";
|
|
91
91
|
import { boundRun, type AnyActorSystem } from "./registration.ts";
|
|
92
|
+
import type { AnyWorktreePath } from "./worktree.ts";
|
|
92
93
|
import { repoKey } from "./repo-identity.ts";
|
|
93
94
|
import { harnessSyncPort, type SyncPortFactory } from "./sync-client.ts";
|
|
94
95
|
import type { SyncAnswer } from "./wire.ts";
|
|
@@ -126,23 +127,51 @@ function slotOf(kind: string, slot: string, named: string | undefined, ambient:
|
|
|
126
127
|
return named;
|
|
127
128
|
}
|
|
128
129
|
if (slots.length === 1) return slots[0]!;
|
|
130
|
+
const why = slots.length === 0 ? needsASlot(kind) : undefined;
|
|
131
|
+
if (why !== undefined)
|
|
132
|
+
throw new Error(`${kind} "${slot}" runs under a Workspace with no Repo Slot: ${why} (ADR-0051)`);
|
|
129
133
|
throw new Error(
|
|
130
134
|
`${kind} "${slot}" runs under a Workspace carrying ${slots.length === 0 ? "no" : "more than one"} Repo Slot ` +
|
|
131
135
|
`(${slots.join(", ") || "none"}) — say which: { slot: "<slot>" } (ADR-0051)`,
|
|
132
136
|
);
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The cut behind an Effect's `worktree`, or why there is none. A push, a credentialed command and a
|
|
141
|
+
* file body act with a credential, so a path alone does not name their target: the path must be a
|
|
142
|
+
* Worktree a cut made in this Sandbox, and the branch is the cut's record. Nothing reads HEAD, and
|
|
143
|
+
* no input names a branch, so a Chart cannot fill one, and `git switch` in the Worktree moves nothing.
|
|
144
|
+
*/
|
|
145
|
+
function cutTargetOf(
|
|
146
|
+
worktree: unknown,
|
|
147
|
+
ctx: Pick<KindContext, "ambient" | "binding">,
|
|
148
|
+
opts: { repo?: string },
|
|
149
|
+
): { target: { repo: string; path: string; branch: string | null } } | { fault: string } {
|
|
150
|
+
const bad = worktreePathFault(worktree, ctx.ambient);
|
|
151
|
+
if (bad !== undefined) return { fault: bad };
|
|
152
|
+
const row = cutRowOf(ctx.binding?.effectRows?.() ?? {}, ctx.ambient.sandbox, worktree);
|
|
153
|
+
if (row === undefined) return { fault: `no cut made ${String(worktree)} in this Sandbox: cut it with worktree()` };
|
|
154
|
+
if (opts.repo !== undefined && row.repo !== opts.repo) {
|
|
155
|
+
return { fault: `${String(worktree)} is a Worktree of Repo Slot "${row.repo}", not of "${opts.repo}"` };
|
|
156
|
+
}
|
|
157
|
+
return { target: row };
|
|
158
|
+
}
|
|
159
|
+
|
|
135
160
|
/** The slot's repositories, relative to the work root: the Agent's common git dir is the pod-local
|
|
136
161
|
* clone's, beside the branch Worktree (ADR-0004); the cache is the Binding's key (ADR-0051). */
|
|
137
|
-
export function repoRefOf(
|
|
138
|
-
|
|
162
|
+
export function repoRefOf(
|
|
163
|
+
ambient: AmbientHandles,
|
|
164
|
+
repoSlot: string,
|
|
165
|
+
worktree: string = ambient.repos[repoSlot]!,
|
|
166
|
+
): RepoRef & { url: string } {
|
|
139
167
|
const url = ambient.bindings?.[repoSlot];
|
|
140
168
|
if (url === undefined) {
|
|
141
169
|
throw new Error(`Repo Slot "${repoSlot}" has no Binding url in its Workspace's handles — a jr2 defect`);
|
|
142
170
|
}
|
|
143
171
|
const rel = posix.relative(WORK_ROOT, worktree);
|
|
144
172
|
return {
|
|
145
|
-
|
|
173
|
+
// The clone of the slot, read from the slot and not from the Worktree's folder.
|
|
174
|
+
gitDir: posix.join(repoSlot, "default", ".git"),
|
|
146
175
|
worktree: rel,
|
|
147
176
|
cache: repoKey(url),
|
|
148
177
|
url,
|
|
@@ -151,42 +180,197 @@ export function repoRefOf(ambient: AmbientHandles, repoSlot: string): RepoRef &
|
|
|
151
180
|
|
|
152
181
|
// --- The definitions -------------------------------------------------------------------------------
|
|
153
182
|
|
|
154
|
-
/**
|
|
183
|
+
/** The options of `push()`. */
|
|
155
184
|
export type PushOptions = {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
*
|
|
185
|
+
// A pinned commit the Agent pruned is refused by the remote, never substituted (ADR-0067).
|
|
186
|
+
/**
|
|
187
|
+
* `true`: the input of the Effect names the commit to push (`{ sha }`, a full commit id, for
|
|
188
|
+
* example one that a Gate approved). Default: the tip of the Agent's branch when the Effect
|
|
189
|
+
* starts. When the Agent removed that commit from its branch, the remote refuses the push. jr2
|
|
190
|
+
* never pushes another commit in its place.
|
|
191
|
+
*
|
|
192
|
+
* When it starts, it refuses a `sha` that is not a full commit id: `push({ pin: true }): its
|
|
193
|
+
* input's sha is not a full commit id (got <sha>)`.
|
|
194
|
+
*/
|
|
159
195
|
pin?: boolean;
|
|
160
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* `true`: push signed copies of the commits. The `sign` field of the `git.credentials` entry that
|
|
198
|
+
* the Binding matches names the key. Default `false`.
|
|
199
|
+
*
|
|
200
|
+
* At `jr2 up`, it refuses an entry with no `sign` field: `<push> signed, and git.credentials
|
|
201
|
+
* entry "<match>" holds no sign field — add sign: { sshKey: "<a Secret with identity>", name:
|
|
202
|
+
* "<committer name>", email: "<committer email>" } to it; the email must belong to the forge
|
|
203
|
+
* account that holds the key (ADR-0051, ADR-0067)`. `<push>` names the push: `workflow
|
|
204
|
+
* "<workflow>": push "<slot>" pushes Repo Slot "<name>" (<url>)`.
|
|
205
|
+
*/
|
|
161
206
|
sign?: boolean;
|
|
162
|
-
/**
|
|
163
|
-
*
|
|
164
|
-
* `
|
|
207
|
+
/**
|
|
208
|
+
* `true`: push one commit that holds all the changes. The input of the Effect gives its message
|
|
209
|
+
* (`{ message }`). Signed, its committer is the signer. Unsigned, it is the `committer` of the
|
|
210
|
+
* `git.credentials` entry that the Binding matches. Default `false`.
|
|
211
|
+
*
|
|
212
|
+
* When it starts, it refuses an input with no message: `push({ squash: true }): its input
|
|
213
|
+
* carries no message`.
|
|
214
|
+
*
|
|
215
|
+
* At `jr2 up`, it refuses an unsigned squash whose entry has no `sign` and no `committer`:
|
|
216
|
+
* `<push> squashed without sign, and git.credentials entry "<match>" holds neither sign nor
|
|
217
|
+
* committer — the squashed commit is new and needs a committer; add committer: { name:
|
|
218
|
+
* "<committer name>", email: "<committer email>" } to it, or sign the push — push({ squash:
|
|
219
|
+
* true, sign: true }) — and add sign: { sshKey: "<a Secret with identity>", name: "<committer
|
|
220
|
+
* name>", email: "<committer email>" } to it (ADR-0051, ADR-0067)`.
|
|
221
|
+
*/
|
|
165
222
|
squash?: boolean;
|
|
166
|
-
/**
|
|
223
|
+
/** `true`: replace what the remote branch holds. Default `false`. */
|
|
167
224
|
force?: boolean;
|
|
168
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* The Repo Slot to push. Write it only when the Workspace has more than one.
|
|
227
|
+
*
|
|
228
|
+
* When it starts, it refuses:
|
|
229
|
+
* - no `slot` under a Workspace with more than one Repo Slot: `push "<slot>" runs under a
|
|
230
|
+
* Workspace carrying more than one Repo Slot (<slots>) — say which: { slot: "<slot>" }
|
|
231
|
+
* (ADR-0051)`
|
|
232
|
+
* - a `slot` that the Workspace does not have: `push "<slot>" names Repo Slot "<name>", which
|
|
233
|
+
* its Workspace does not carry (<slots>)`
|
|
234
|
+
* - a Workspace with no Repo Slot: `push "<slot>" runs under a Workspace with no Repo Slot: a
|
|
235
|
+
* push publishes a Repo Slot's branch, and there is none — declare one: repos: { app: "<url>" }
|
|
236
|
+
* (ADR-0051)`. `jr2 up` refuses it first, with the same reason.
|
|
237
|
+
*/
|
|
169
238
|
slot?: string;
|
|
170
|
-
|
|
239
|
+
// Static, so that `jr2 up` places the credential per slot.
|
|
240
|
+
/**
|
|
241
|
+
* How long jr2 waits for the push: a number and a unit, such as `"90s"` or `"5m"`. Default
|
|
242
|
+
* `"5m"`. With no answer in that time, the output is `unknown`. It refuses a value that is not a
|
|
243
|
+
* positive duration: `timeout "<timeout>" is not a positive duration (like "30s", "5m")`.
|
|
244
|
+
*/
|
|
171
245
|
timeout?: Duration;
|
|
172
246
|
};
|
|
173
247
|
|
|
174
|
-
/**
|
|
175
|
-
|
|
248
|
+
/** The input of a `push` Effect: the Worktree that a cut made, `{ sha }` with `pin`, and
|
|
249
|
+
* `{ message }` with `squash`. It names no branch: jr2 reads it from the cut's record, so no word of
|
|
250
|
+
* a Chart reaches a remote branch. The other keys are refused by name, because each is a key of a
|
|
251
|
+
* cut or a Turn, and a mapper's excess key is no compile error otherwise. */
|
|
252
|
+
export type PushInput<O extends PushOptions> = {
|
|
253
|
+
/** The Worktree to push: `event.output.path` of its cut, of the push's Repo Slot. */
|
|
254
|
+
worktree: O extends { slot: infer S extends string } ? `/work/${S}/${string}` : AnyWorktreePath;
|
|
255
|
+
branch?: never;
|
|
256
|
+
to?: never;
|
|
257
|
+
cwd?: never;
|
|
258
|
+
path?: never;
|
|
259
|
+
} & (O["pin"] extends true ? { sha: string } : unknown) &
|
|
176
260
|
(O["squash"] extends true ? { message: string } : unknown);
|
|
177
261
|
|
|
178
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* The sync's answer as a push's output carries it: a field beside the outcome, never the outcome.
|
|
264
|
+
*
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
179
267
|
export type SyncState = SyncAnswer["sync"];
|
|
180
268
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*
|
|
269
|
+
// The sync's bundle never reaches the Machine: `deliver` drops it from the output (ADR-0067).
|
|
270
|
+
/**
|
|
271
|
+
* The output of a `push` Effect: what the `onDone` of its state gets as `event.output`. Every
|
|
272
|
+
* outcome is output, so route on `outcome`:
|
|
273
|
+
*
|
|
274
|
+
* - `pushed`: the remote has the commit. `published` is the commit on the remote, and `source` is
|
|
275
|
+
* the Agent's commit that it comes from. They are the same commit, unless the push was signed or
|
|
276
|
+
* squashed. `previous` is what the remote had before, and `unchanged` is `true` when the remote
|
|
277
|
+
* had the commit already. `squashed` is `signed` or `unsigned` when the commit on the remote is
|
|
278
|
+
* one squashed commit. `via` tells how jr2 found the new commits after a signed push:
|
|
279
|
+
* `ancestry`, `pair`, `cherry` or `force`.
|
|
280
|
+
* - `rejected`: git or the remote refused the push. `stage` tells where, `reason` tells why, and
|
|
281
|
+
* `detail` has the words of git.
|
|
282
|
+
* - `diverged`: the commit on the remote is not in the history of the Agent's branch, and jr2
|
|
283
|
+
* could not pair it with a commit there. Nothing was pushed. `reason` tells why, and `published`
|
|
284
|
+
* and `source` name the two commits.
|
|
285
|
+
* - `unknown`: jr2 cannot tell what the push did. `reason` tells why.
|
|
286
|
+
*
|
|
287
|
+
* Beside `pushed`, `sync` tells what jr2 did to the Agent's branch after the push:
|
|
288
|
+
*
|
|
289
|
+
* - `synced`: the commit on the remote is not the Agent's own, and jr2 moved the branch to the
|
|
290
|
+
* published commits.
|
|
291
|
+
* - `already`: the branch has the published commit already.
|
|
292
|
+
* - `skipped`: the remote has the Agent's own commit, so the branch does not move.
|
|
293
|
+
* - `busy`: a rebase, merge, cherry-pick or revert of the branch is in progress. Nothing changed.
|
|
294
|
+
* - `not-synced`: the branch did not move.
|
|
295
|
+
*
|
|
296
|
+
* With `busy` or `not-synced`, `syncReason` tells why, and jr2 tries again before the next push.
|
|
297
|
+
*
|
|
298
|
+
* The union is open (`LaterOutcome`): give the rest a last arm with no guard.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* import {
|
|
303
|
+
* fail,
|
|
304
|
+
* isReady,
|
|
305
|
+
* jr2Setup,
|
|
306
|
+
* push,
|
|
307
|
+
* type PushOutput,
|
|
308
|
+
* workspace,
|
|
309
|
+
* worktree,
|
|
310
|
+
* type Workspaced,
|
|
311
|
+
* } from "@jr2/orchestrator";
|
|
312
|
+
*
|
|
313
|
+
* type Input = Workspaced<{ branch: string; sha: string }, "app">;
|
|
314
|
+
*
|
|
315
|
+
* const releaseTree = (context: Input) => `${context.workspace.repos.app}/release` as const;
|
|
316
|
+
*
|
|
317
|
+
* // A push that did not happen (`rejected` or `diverged`) is a different problem from one jr2
|
|
318
|
+
* // cannot see (`unknown`).
|
|
319
|
+
* const notPushed = (output: PushOutput): boolean =>
|
|
320
|
+
* output.outcome === "rejected" || output.outcome === "diverged";
|
|
321
|
+
*
|
|
322
|
+
* const body = jr2Setup({
|
|
323
|
+
* types: {} as { context: Input; input: Input },
|
|
324
|
+
* actors: { cut: worktree({ expect: "existing" }), publish: push({ pin: true }), fail: fail() },
|
|
325
|
+
* }).createMachine({
|
|
326
|
+
* id: "release",
|
|
327
|
+
* context: ({ input }) => input,
|
|
328
|
+
* on: { "workspace.lost": ".lost" },
|
|
329
|
+
* initial: "cutting",
|
|
330
|
+
* states: {
|
|
331
|
+
* cutting: {
|
|
332
|
+
* invoke: {
|
|
333
|
+
* src: "cut",
|
|
334
|
+
* input: ({ context }) => ({ path: releaseTree(context), branch: context.branch }),
|
|
335
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "publishing" }, { target: "notCut" }],
|
|
336
|
+
* },
|
|
337
|
+
* },
|
|
338
|
+
* publishing: {
|
|
339
|
+
* invoke: {
|
|
340
|
+
* src: "publish",
|
|
341
|
+
* input: ({ context }) => ({ worktree: releaseTree(context), sha: context.sha }),
|
|
342
|
+
* onDone: [
|
|
343
|
+
* { guard: ({ event }) => event.output.outcome === "pushed", target: "published" },
|
|
344
|
+
* { guard: ({ event }) => notPushed(event.output), target: "notPushed" },
|
|
345
|
+
* { target: "unknown" },
|
|
346
|
+
* ],
|
|
347
|
+
* },
|
|
348
|
+
* },
|
|
349
|
+
* published: { type: "final" },
|
|
350
|
+
* // Not final states: each one fails the run, with its own reason.
|
|
351
|
+
* notPushed: { invoke: { src: "fail", input: { reason: "the remote refused the push" } } },
|
|
352
|
+
* unknown: { invoke: { src: "fail", input: { reason: "jr2 cannot tell if the push happened" } } },
|
|
353
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
354
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
355
|
+
* },
|
|
356
|
+
* });
|
|
357
|
+
*
|
|
358
|
+
* export const machine = workspace(body, {
|
|
359
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
360
|
+
* });
|
|
361
|
+
* ```
|
|
362
|
+
*
|
|
363
|
+
* @see docs/guides/push-and-sign-a-branch.md
|
|
364
|
+
* @see docs/concepts/effects.md
|
|
365
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
366
|
+
*/
|
|
184
367
|
export type PushOutput =
|
|
185
368
|
| Exclude<PushWireOutput, { outcome: "pushed" }>
|
|
186
|
-
| (Omit<Extract<PushWireOutput, { outcome: "pushed" }>, "bundle"> & { sync: SyncState; syncReason?: string })
|
|
369
|
+
| (Omit<Extract<PushWireOutput, { outcome: "pushed" }>, "bundle"> & { sync: SyncState; syncReason?: string })
|
|
370
|
+
| LaterOutcome;
|
|
187
371
|
|
|
188
372
|
export function pushParts(options: PushOptions = {}): PushParts {
|
|
189
|
-
const inputKeys = [...(options.pin ? ["sha"] : []), ...(options.squash ? ["message"] : [])];
|
|
373
|
+
const inputKeys = ["worktree", ...(options.pin ? ["sha"] : []), ...(options.squash ? ["message"] : [])];
|
|
190
374
|
return {
|
|
191
375
|
kind: "push",
|
|
192
376
|
pin: options.pin === true,
|
|
@@ -232,8 +416,8 @@ export function pushStartOf(
|
|
|
232
416
|
};
|
|
233
417
|
}
|
|
234
418
|
|
|
235
|
-
/** A file-body http's start for one run (ADR-0067): the definition's parts, the url's segments, and
|
|
236
|
-
* Worktree file as the body. */
|
|
419
|
+
/** A file-body http's start for one run (ADR-0067): the definition's parts, the url's segments, and
|
|
420
|
+
* the Worktree file as the body. */
|
|
237
421
|
export function fileStartOf(
|
|
238
422
|
parts: HttpParts,
|
|
239
423
|
worktree: string,
|
|
@@ -248,7 +432,8 @@ export function fileStartOf(
|
|
|
248
432
|
return { ...http, body: { file: { worktree, path: run.path, ...(contentType ? { contentType } : {}) } } };
|
|
249
433
|
}
|
|
250
434
|
|
|
251
|
-
/**
|
|
435
|
+
/** The `env` of a `command`: for each variable, `secret("name")` for the value of an Effect
|
|
436
|
+
* secret, or `secret("name").path` for the path of a file that holds it. */
|
|
252
437
|
export type CommandEnv = Readonly<Record<string, { readonly secret: string; readonly path: unknown }>>;
|
|
253
438
|
|
|
254
439
|
export function commandEnvParts(env: CommandEnv): Pick<CommandParts, "env" | "secrets" | "openSecret"> {
|
|
@@ -309,6 +494,7 @@ type KindContext = {
|
|
|
309
494
|
slot: string;
|
|
310
495
|
signal: AbortSignal;
|
|
311
496
|
mark: (marker: Omit<EffectMarker, "kind" | "slot" | "effect">) => void;
|
|
497
|
+
input: unknown;
|
|
312
498
|
};
|
|
313
499
|
|
|
314
500
|
/** How one Sandbox kind builds its start and what a restore may send again. */
|
|
@@ -321,13 +507,25 @@ type KindSpec = {
|
|
|
321
507
|
/** The Repo Slot the Effect runs on, written to its settled row. */
|
|
322
508
|
repoOf: (ambient: AmbientHandles, slot: string) => string;
|
|
323
509
|
/** The start for this run; `pinned` is the commit the intent row recorded. */
|
|
324
|
-
build: (
|
|
510
|
+
build: (
|
|
511
|
+
input: unknown,
|
|
512
|
+
ambient: AmbientHandles,
|
|
513
|
+
slot: string,
|
|
514
|
+
pinned?: string,
|
|
515
|
+
extra?: StartExtra,
|
|
516
|
+
binding?: ReturnType<typeof boundRun>,
|
|
517
|
+
) => StartRequest;
|
|
325
518
|
/** Run before a start is sent (a push: the last sync again, and the ledger's pair). */
|
|
326
519
|
before?: (ctx: KindContext) => Promise<StartExtra>;
|
|
327
520
|
/** Run on the Effector's settled output before it is ledgered (a push: the sync). */
|
|
328
521
|
finish?: (output: unknown, ctx: KindContext) => Promise<unknown>;
|
|
329
522
|
/** What a settled output becomes for the Machine. */
|
|
330
523
|
deliver?: (output: unknown) => unknown;
|
|
524
|
+
/** Why this start cannot be sent, as the kind's own output: a word an Agent or a Chart can say.
|
|
525
|
+
* It settles before any intent row: nothing went out. */
|
|
526
|
+
refuse?: (ctx: KindContext) => unknown;
|
|
527
|
+
/** The fields of the settled row that a later Effect reads. */
|
|
528
|
+
rowFields?: (ctx: KindContext) => { branch?: string; worktree?: string; repo?: string };
|
|
331
529
|
};
|
|
332
530
|
|
|
333
531
|
type StartExtra = { pairs?: readonly Pair[] };
|
|
@@ -406,6 +604,9 @@ async function drive<I, O>(
|
|
|
406
604
|
kind: spec.kind,
|
|
407
605
|
slot,
|
|
408
606
|
...(ambient ? { sandbox: ambient.sandbox, ...repoField(spec, ambient, slot) } : {}),
|
|
607
|
+
...(ambient && spec.rowFields
|
|
608
|
+
? spec.rowFields({ ambient, binding, id, slot, signal, mark, input: snapshot.input })
|
|
609
|
+
: {}),
|
|
409
610
|
output,
|
|
410
611
|
at: now(),
|
|
411
612
|
});
|
|
@@ -447,7 +648,7 @@ async function drive<I, O>(
|
|
|
447
648
|
// A credentialed command's stop goes through this port when its state exits (ADR-0067).
|
|
448
649
|
const run = running.get(self);
|
|
449
650
|
if (spec.kind === "command" && run) run.port = port;
|
|
450
|
-
const ctx: KindContext = { ambient, binding, id, slot, signal, mark };
|
|
651
|
+
const ctx: KindContext = { ambient, binding, id, slot, signal, mark, input: snapshot.input };
|
|
451
652
|
|
|
452
653
|
const find = async (): Promise<{ found: EffectState | undefined } | undefined> => {
|
|
453
654
|
try {
|
|
@@ -477,12 +678,12 @@ async function drive<I, O>(
|
|
|
477
678
|
});
|
|
478
679
|
const extra = spec.before ? await spec.before(ctx) : {};
|
|
479
680
|
if (signal.aborted) return;
|
|
480
|
-
await send(spec.build(snapshot.input, ambient, slot, pinned, extra), pinned);
|
|
681
|
+
await send(spec.build(snapshot.input, ambient, slot, pinned, extra, binding), pinned);
|
|
481
682
|
return;
|
|
482
683
|
}
|
|
483
684
|
if (spec.kind === "http" && spec.idempotent) {
|
|
484
685
|
mark({ stage: "started", text: `${spec.label(ambient)} (sent again: it is idempotent)` });
|
|
485
|
-
await send(spec.build(snapshot.input, ambient, slot), undefined);
|
|
686
|
+
await send(spec.build(snapshot.input, ambient, slot, undefined, undefined, binding), undefined);
|
|
486
687
|
return;
|
|
487
688
|
}
|
|
488
689
|
settle(unknown(`${why}; not run again`));
|
|
@@ -503,9 +704,14 @@ async function drive<I, O>(
|
|
|
503
704
|
return;
|
|
504
705
|
}
|
|
505
706
|
|
|
707
|
+
const refusal = spec.refuse?.(ctx);
|
|
708
|
+
if (refusal !== undefined) {
|
|
709
|
+
settle(refusal);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
506
712
|
const extra = spec.before ? await spec.before(ctx) : {};
|
|
507
713
|
if (signal.aborted) return;
|
|
508
|
-
const req = spec.build(snapshot.input, ambient, slot, undefined, extra);
|
|
714
|
+
const req = spec.build(snapshot.input, ambient, slot, undefined, extra, binding);
|
|
509
715
|
const big = tooLarge(req);
|
|
510
716
|
if (big) {
|
|
511
717
|
settle(big);
|
|
@@ -579,7 +785,8 @@ async function drive<I, O>(
|
|
|
579
785
|
}
|
|
580
786
|
}
|
|
581
787
|
|
|
582
|
-
/** The settled row's Repo Slot, or none when the Workspace does not name one the kind can run on.
|
|
788
|
+
/** The settled row's Repo Slot, or none when the Workspace does not name one the kind can run on.
|
|
789
|
+
*/
|
|
583
790
|
function repoField(spec: KindSpec, ambient: AmbientHandles, slot: string): { repo?: string } {
|
|
584
791
|
try {
|
|
585
792
|
return { repo: spec.repoOf(ambient, slot) };
|
|
@@ -660,9 +867,10 @@ function stoppedText(state: EffectState | undefined): string {
|
|
|
660
867
|
}
|
|
661
868
|
|
|
662
869
|
/** A start over the bytes an Effector reads is sent nowhere, and settles as its kind's own outcome
|
|
663
|
-
* (ADR-0067): an http `refused`; a push `rejected` at stage `start` — nothing pushed; a
|
|
664
|
-
* command `exited` 127 with the reason in its tail, the Effector's own form for a
|
|
665
|
-
* could not start. An Agent's pick parameter — a squash message, an argv part — can
|
|
870
|
+
* (ADR-0067): an http `refused`; a push `rejected` at stage `start` — nothing pushed; a
|
|
871
|
+
* credentialed command `exited` 127 with the reason in its tail, the Effector's own form for a
|
|
872
|
+
* command that could not start. An Agent's pick parameter — a squash message, an argv part — can
|
|
873
|
+
* reach the bound. */
|
|
666
874
|
function tooLarge(req: StartRequest): HttpOutput | PushWireOutput | CommandWireOutput | undefined {
|
|
667
875
|
const bytes = Buffer.byteLength(JSON.stringify(req), "utf8");
|
|
668
876
|
if (bytes <= MAX_START_BYTES) return undefined;
|
|
@@ -713,40 +921,65 @@ export function pushWith<const O extends PushOptions>(
|
|
|
713
921
|
portFactory: EffectorPortFactory,
|
|
714
922
|
options?: O,
|
|
715
923
|
syncPorts: SyncPortFactory = harnessSyncPort,
|
|
716
|
-
): EffectLogic<PushInput<O
|
|
924
|
+
): EffectLogic<PushInput<O>, PushOutput> {
|
|
717
925
|
const parts = pushParts(options);
|
|
718
926
|
const repoOf = (ambient: AmbientHandles, slot: string) => slotOf("push", slot, parts.slot, ambient);
|
|
719
|
-
const worktreeOf = (ctx: KindContext) => ctx.ambient.repos[repoOf(ctx.ambient, ctx.slot)]!;
|
|
720
927
|
const syncOf = (ctx: KindContext) =>
|
|
721
928
|
syncPorts(ctx.ambient.endpoint, ctx.binding?.harnessBearer?.(ctx.ambient.sandbox));
|
|
929
|
+
const worktreeOf = (input: unknown) => (input as { worktree?: unknown } | undefined)?.worktree;
|
|
930
|
+
/** The target of this push: the cut's row. `refuse` has run, so a build finds one; a restored push
|
|
931
|
+
* finds it too, because a row outlives the restart. */
|
|
932
|
+
const targetOf = (ctx: Pick<KindContext, "ambient" | "binding" | "input" | "slot">) => {
|
|
933
|
+
const found = cutTargetOf(worktreeOf(ctx.input), ctx, { repo: repoOf(ctx.ambient, ctx.slot) });
|
|
934
|
+
if ("fault" in found) throw new Error(`push "${ctx.slot}": ${found.fault}`);
|
|
935
|
+
return found.target as { repo: string; path: string; branch: string };
|
|
936
|
+
};
|
|
722
937
|
return sandboxLogic(portFactory, parts, {
|
|
723
938
|
kind: "push",
|
|
724
939
|
idempotent: false,
|
|
725
940
|
repeatsPinned: true,
|
|
726
|
-
label: (
|
|
941
|
+
label: () => `push${parts.sign ? " (signed)" : ""}`,
|
|
727
942
|
repoOf,
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
const
|
|
943
|
+
// Anything a Chart or a pick can say, so it is output (ADR-0067): `rejected` at stage start.
|
|
944
|
+
refuse: (ctx) => {
|
|
945
|
+
const found = cutTargetOf(worktreeOf(ctx.input), ctx, { repo: repoOf(ctx.ambient, ctx.slot) });
|
|
946
|
+
const fault =
|
|
947
|
+
"fault" in found
|
|
948
|
+
? found.fault
|
|
949
|
+
: found.target.branch === null
|
|
950
|
+
? `${found.target.path} is detached: a push publishes the branch of a Worktree, and it holds none`
|
|
951
|
+
: undefined;
|
|
952
|
+
if (fault === undefined) return undefined;
|
|
953
|
+
return { outcome: "rejected", stage: "start", reason: `${fault}; nothing was pushed` };
|
|
954
|
+
},
|
|
955
|
+
rowFields: (ctx) => {
|
|
956
|
+
const found = cutTargetOf(worktreeOf(ctx.input), ctx, { repo: repoOf(ctx.ambient, ctx.slot) });
|
|
957
|
+
if ("fault" in found || found.target.branch === null) return {};
|
|
958
|
+
return { branch: found.target.branch };
|
|
959
|
+
},
|
|
960
|
+
build: (input, ambient, slot, pinned, extra, binding) => {
|
|
961
|
+
const target = targetOf({ ambient, binding, input, slot });
|
|
962
|
+
const ref = repoRefOf(ambient, target.repo, target.path);
|
|
963
|
+
const req = pushStartOf(parts, ref, target.branch, input as { sha?: string; message?: string }, extra?.pairs);
|
|
732
964
|
if (pinned) req.push.pin = pinned;
|
|
733
965
|
return req;
|
|
734
966
|
},
|
|
735
|
-
// Every push of this
|
|
736
|
-
//
|
|
737
|
-
//
|
|
967
|
+
// Every push of this Repo Slot's branch that published gives the Effector a pair, oldest first
|
|
968
|
+
// (ADR-0067). The newest one's sync, when it did not finish, runs again first, in this push's
|
|
969
|
+
// Worktree and from the bundle the row keeps while it is the newest
|
|
738
970
|
// push and its sync did not finish (`dropSavedTails`); the push does not depend on it.
|
|
739
971
|
before: async (ctx) => {
|
|
740
972
|
const rows = ctx.binding?.effectRows?.() ?? {};
|
|
741
973
|
const repo = repoOf(ctx.ambient, ctx.slot);
|
|
742
|
-
const
|
|
974
|
+
const target = targetOf(ctx);
|
|
975
|
+
const last = lastPushOf(rows, ctx.ambient.sandbox, repo, target.branch, ctx.id);
|
|
743
976
|
if (last === undefined) return {};
|
|
744
977
|
const { row, rowId, output } = last;
|
|
745
978
|
if (output.sync === "busy" || output.sync === "not-synced") {
|
|
746
979
|
const answer = await syncOf(ctx).sync(
|
|
747
980
|
{
|
|
748
|
-
worktree:
|
|
749
|
-
branch:
|
|
981
|
+
worktree: target.path,
|
|
982
|
+
branch: target.branch,
|
|
750
983
|
published: output.published,
|
|
751
984
|
source: output.source,
|
|
752
985
|
...(typeof output.bundle === "string" ? { bundle: output.bundle } : {}),
|
|
@@ -758,7 +991,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
758
991
|
ctx.binding?.recordEffect?.(rowId, { ...row, output: { ...output, ...syncFields(answer) } });
|
|
759
992
|
}
|
|
760
993
|
// Each row's `squashed` rides beside its pair: a push that maps the same commit names what it is.
|
|
761
|
-
return { pairs: pushPairsOf(rows, ctx.ambient.sandbox, repo, ctx.id) };
|
|
994
|
+
return { pairs: pushPairsOf(rows, ctx.ambient.sandbox, repo, target.branch, ctx.id) };
|
|
762
995
|
},
|
|
763
996
|
// After every pushed outcome whose published commit is not the Agent's own — signed copies, or
|
|
764
997
|
// one squashed commit, signed or not (ADR-0067): the sync, in the Workspace's Harness, from the
|
|
@@ -769,10 +1002,11 @@ export function pushWith<const O extends PushOptions>(
|
|
|
769
1002
|
const o = output as PushWireOutput;
|
|
770
1003
|
if (o.outcome !== "pushed") return o;
|
|
771
1004
|
if (o.published === o.source) return { ...o, sync: "skipped" satisfies SyncState };
|
|
1005
|
+
const target = targetOf(ctx);
|
|
772
1006
|
const answer = await syncOf(ctx).sync(
|
|
773
1007
|
{
|
|
774
|
-
worktree:
|
|
775
|
-
branch:
|
|
1008
|
+
worktree: target.path,
|
|
1009
|
+
branch: target.branch,
|
|
776
1010
|
published: o.published,
|
|
777
1011
|
source: o.source,
|
|
778
1012
|
...(o.bundle !== undefined ? { bundle: o.bundle } : {}),
|
|
@@ -787,7 +1021,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
787
1021
|
const { bundle: _bundle, ...rest } = o;
|
|
788
1022
|
return rest;
|
|
789
1023
|
},
|
|
790
|
-
}) as EffectLogic<PushInput<O
|
|
1024
|
+
}) as EffectLogic<PushInput<O>, PushOutput>;
|
|
791
1025
|
}
|
|
792
1026
|
|
|
793
1027
|
/** The sync's answer as the output's fields: its state, and its reason when it moved nothing. */
|
|
@@ -800,8 +1034,9 @@ function syncFields(answer: SyncAnswer): { sync: SyncState; syncReason?: string
|
|
|
800
1034
|
|
|
801
1035
|
export { lastPushOf };
|
|
802
1036
|
|
|
803
|
-
/** An http whose body is a Worktree file (ADR-0067), over an injected port factory — the seam
|
|
804
|
-
* and the unit tests share. It runs in the Sandbox Effector of the Workspace whose body
|
|
1037
|
+
/** An http whose body is a Worktree file (ADR-0067), over an injected port factory — the seam
|
|
1038
|
+
* `http()` and the unit tests share. It runs in the Sandbox Effector of the Workspace whose body
|
|
1039
|
+
* invokes it. */
|
|
805
1040
|
export function httpFileWith<
|
|
806
1041
|
S extends z.ZodType | undefined = undefined,
|
|
807
1042
|
const U extends HttpDefinition<S>["url"] = string,
|
|
@@ -827,17 +1062,38 @@ export function httpFileWith<S extends z.ZodType | undefined = undefined>(
|
|
|
827
1062
|
kind: "http",
|
|
828
1063
|
idempotent: parts.idempotent,
|
|
829
1064
|
repeatsPinned: false,
|
|
830
|
-
|
|
831
|
-
|
|
1065
|
+
// With no Repo Slot the file is in `/work`, which is no Worktree (ADR-0051).
|
|
1066
|
+
label: (ambient) =>
|
|
1067
|
+
`http ${parts.method} ${parts.target.scheme}://${parts.target.host} ` +
|
|
1068
|
+
(Object.keys(ambient.repos).length === 0 ? "(a file of /work)" : "(a Worktree file)"),
|
|
1069
|
+
repoOf: (ambient, slot) => slotOf("http", slot, undefined, ambient),
|
|
1070
|
+
// The same row check as a push: a word of a Chart must not choose what leaves the Sandbox with a
|
|
1071
|
+
// credential. `refused`, the kind's own form.
|
|
1072
|
+
refuse: (ctx) => {
|
|
1073
|
+
const named = (ctx.input as { worktree?: unknown } | undefined)?.worktree;
|
|
1074
|
+
if (Object.keys(ctx.ambient.repos).length === 0) {
|
|
1075
|
+
return named === undefined
|
|
1076
|
+
? undefined
|
|
1077
|
+
: { outcome: "refused", reason: `no Worktree in a Workspace with no Repo Slot; nothing was sent` };
|
|
1078
|
+
}
|
|
1079
|
+
if (named === undefined) {
|
|
1080
|
+
throw new Error(
|
|
1081
|
+
`http "${ctx.slot}" reads a Worktree file, under a Workspace with Repo Slots, and its input names no \`worktree\``,
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
const found = cutTargetOf(named, ctx, {});
|
|
1085
|
+
return "fault" in found ? { outcome: "refused", reason: `${found.fault}; nothing was sent` } : undefined;
|
|
1086
|
+
},
|
|
832
1087
|
build: (input, ambient, slot) => {
|
|
833
|
-
|
|
1088
|
+
const { worktree: named, ...rest } = (input ?? {}) as { worktree?: string };
|
|
1089
|
+
let value: unknown = rest;
|
|
834
1090
|
if (def.input !== undefined) {
|
|
835
|
-
const parsed = def.input.safeParse(
|
|
1091
|
+
const parsed = def.input.safeParse(rest);
|
|
836
1092
|
if (!parsed.success) throw new Error(`http "${slot}": its input does not match the definition's input schema`);
|
|
837
1093
|
value = parsed.data;
|
|
838
1094
|
}
|
|
839
|
-
|
|
840
|
-
const worktree = posix.relative(WORK_ROOT,
|
|
1095
|
+
// With no Repo Slot the file is in `/work`, which its steps share (ADR-0051).
|
|
1096
|
+
const worktree = named === undefined ? "." : posix.relative(WORK_ROOT, named);
|
|
841
1097
|
const segments = fromInput<readonly string[]>(body.segments, value);
|
|
842
1098
|
return fileStartOf(parts, worktree, {
|
|
843
1099
|
path: fromInput<string>(body.path, value)!,
|
|
@@ -847,7 +1103,8 @@ export function httpFileWith<S extends z.ZodType | undefined = undefined>(
|
|
|
847
1103
|
});
|
|
848
1104
|
}
|
|
849
1105
|
|
|
850
|
-
/** A command whose env names a secret: it runs in the Sandbox Effector against the pinned commit.
|
|
1106
|
+
/** A command whose env names a secret: it runs in the Sandbox Effector against the pinned commit.
|
|
1107
|
+
*/
|
|
851
1108
|
export function credentialedCommandWith(
|
|
852
1109
|
portFactory: EffectorPortFactory,
|
|
853
1110
|
parts: CommandParts,
|
|
@@ -859,10 +1116,30 @@ export function credentialedCommandWith(
|
|
|
859
1116
|
idempotent: false,
|
|
860
1117
|
repeatsPinned: true,
|
|
861
1118
|
label: () => parts.argv.map((a) => (typeof a === "string" ? a : `<${(a as Arg).arg}>`)).join(" "),
|
|
862
|
-
repoOf: (ambient, slot) => slotOf("command", slot,
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1119
|
+
repoOf: (ambient, slot) => slotOf("command", slot, undefined, ambient),
|
|
1120
|
+
// The same row check as a push, in the form a command that could not start has (ADR-0067). No
|
|
1121
|
+
// Repo Slot at all is a defect of the Machine, as for a push: a throw, and `onError` (ADR-0051).
|
|
1122
|
+
refuse: (ctx) => {
|
|
1123
|
+
if (Object.keys(ctx.ambient.repos).length === 0) {
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
`command "${ctx.slot}" runs under a Workspace with no Repo Slot: ${needsASlot("command")} (ADR-0051)`,
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
const found = cutTargetOf((ctx.input as { worktree?: unknown } | undefined)?.worktree, ctx, {});
|
|
1129
|
+
const fault =
|
|
1130
|
+
"fault" in found
|
|
1131
|
+
? found.fault
|
|
1132
|
+
: found.target.branch === null
|
|
1133
|
+
? `${found.target.path} is detached, and a credentialed command runs on a commit of a branch`
|
|
1134
|
+
: undefined;
|
|
1135
|
+
return fault === undefined
|
|
1136
|
+
? undefined
|
|
1137
|
+
: { outcome: "exited", code: 127, tail: `jr2: ${fault}; nothing was run\n` };
|
|
1138
|
+
},
|
|
1139
|
+
build: (input, ambient, slot, pinned, _extra, binding) => {
|
|
1140
|
+
const found = cutTargetOf((input as { worktree?: unknown } | undefined)?.worktree, { ambient, binding }, {});
|
|
1141
|
+
if ("fault" in found) throw new Error(`command "${slot}": ${found.fault}`);
|
|
1142
|
+
const ref = repoRefOf(ambient, found.target.repo, found.target.path);
|
|
866
1143
|
const approved = pin ? (input as { sha?: unknown } | undefined)?.sha : undefined;
|
|
867
1144
|
if (pin && (typeof approved !== "string" || !/^[0-9a-f]{40}$/.test(approved))) {
|
|
868
1145
|
throw new Error(`command "${slot}" (pin): its input's sha is not a full commit id`);
|
|
@@ -871,7 +1148,7 @@ export function credentialedCommandWith(
|
|
|
871
1148
|
parts,
|
|
872
1149
|
argvOf(input, slot),
|
|
873
1150
|
ref,
|
|
874
|
-
|
|
1151
|
+
found.target.branch!,
|
|
875
1152
|
pinned ?? (approved as string | undefined),
|
|
876
1153
|
);
|
|
877
1154
|
},
|
|
@@ -879,12 +1156,121 @@ export function credentialedCommandWith(
|
|
|
879
1156
|
}
|
|
880
1157
|
|
|
881
1158
|
/**
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
1159
|
+
* Make a `push` Effect: it pushes the branch of a Worktree to the Binding of its Repo Slot.
|
|
1160
|
+
* Put it in `actors`, and invoke it from the body of a `workspace()`: `actors: { publish: push({
|
|
1161
|
+
* sign: true }) }`.
|
|
1162
|
+
*
|
|
1163
|
+
* Its input names the Worktree: `{ worktree }`. It has no branch. jr2 reads the branch from the
|
|
1164
|
+
* `worktree()` cut of that path, so an Agent that moves `HEAD` does not change what jr2 pushes.
|
|
1165
|
+
*
|
|
1166
|
+
* It runs in the Sandbox Effector of the Workspace, with the push credential of the
|
|
1167
|
+
* `git.credentials` entry that the Binding matches. The Agent never reads that credential. It
|
|
1168
|
+
* pushes one commit: the tip of the Agent's branch when the Effect starts, or, with `pin`, the
|
|
1169
|
+
* commit that the input names. After an Orchestrator restart, jr2 pushes only that same commit
|
|
1170
|
+
* again. With `sign` or `squash`, the commit on the remote is not the Agent's own. Then jr2 moves
|
|
1171
|
+
* the Agent's branch to it.
|
|
1172
|
+
*
|
|
1173
|
+
* Every outcome is output (`PushOutput`): `pushed`, `rejected`, `diverged` or `unknown`. A
|
|
1174
|
+
* `worktree` that no cut made in this Sandbox, a Worktree of another Repo Slot, and a detached
|
|
1175
|
+
* Worktree are `rejected` at `stage` `start`, and nothing is pushed. Each field of
|
|
1176
|
+
* {@link PushOptions} tells what it refuses. When it starts, it also refuses a push
|
|
1177
|
+
* outside a Workspace: `push "<slot>" runs in the Sandbox Effector of a Workspace, and its state is
|
|
1178
|
+
* not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`.
|
|
1179
|
+
*
|
|
1180
|
+
* @param options - Optional. Each field tells what it does.
|
|
1181
|
+
* @returns An Effect, for `actors`.
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* import { z } from "zod";
|
|
1186
|
+
* import {
|
|
1187
|
+
* agent,
|
|
1188
|
+
* defineEvent,
|
|
1189
|
+
* fail,
|
|
1190
|
+
* isReady,
|
|
1191
|
+
* jr2Setup,
|
|
1192
|
+
* push,
|
|
1193
|
+
* workspace,
|
|
1194
|
+
* worktree,
|
|
1195
|
+
* type Workspaced,
|
|
1196
|
+
* } from "@jr2/orchestrator";
|
|
1197
|
+
*
|
|
1198
|
+
* const committed = defineEvent({
|
|
1199
|
+
* name: "committed",
|
|
1200
|
+
* description: "Say that the fix is committed.",
|
|
1201
|
+
* input: z.object({}),
|
|
1202
|
+
* });
|
|
1203
|
+
*
|
|
1204
|
+
* type Input = Workspaced<{}, "app">;
|
|
1205
|
+
*
|
|
1206
|
+
* const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
1207
|
+
*
|
|
1208
|
+
* const body = jr2Setup({
|
|
1209
|
+
* types: {} as { context: Input; input: Input },
|
|
1210
|
+
* events: [committed],
|
|
1211
|
+
* actors: {
|
|
1212
|
+
* coder: agent({
|
|
1213
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
1214
|
+
* instructions: "You are a careful engineer.",
|
|
1215
|
+
* }),
|
|
1216
|
+
* cut: worktree(),
|
|
1217
|
+
* publish: push({ sign: true }),
|
|
1218
|
+
* fail: fail(),
|
|
1219
|
+
* },
|
|
1220
|
+
* }).createMachine({
|
|
1221
|
+
* id: "fix-test",
|
|
1222
|
+
* context: ({ input }) => input,
|
|
1223
|
+
* on: { "workspace.lost": ".failed", "agent.fault": ".failed" },
|
|
1224
|
+
* initial: "cutting",
|
|
1225
|
+
* states: {
|
|
1226
|
+
* cutting: {
|
|
1227
|
+
* invoke: {
|
|
1228
|
+
* src: "cut",
|
|
1229
|
+
* input: ({ context }) => ({ path: fixTree(context), branch: "fix-test" }),
|
|
1230
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
1231
|
+
* },
|
|
1232
|
+
* },
|
|
1233
|
+
* coding: {
|
|
1234
|
+
* invoke: {
|
|
1235
|
+
* src: "coder",
|
|
1236
|
+
* input: ({ context }) => ({ prompt: "Fix the failing test, and commit the fix.", cwd: fixTree(context) }),
|
|
1237
|
+
* },
|
|
1238
|
+
* // The Agent moves the Machine on with a pick. An Agent's actor never ends, so `onDone`
|
|
1239
|
+
* // never fires.
|
|
1240
|
+
* on: { committed: "publishing" },
|
|
1241
|
+
* },
|
|
1242
|
+
* publishing: {
|
|
1243
|
+
* invoke: {
|
|
1244
|
+
* src: "publish",
|
|
1245
|
+
* // jr2 reads the branch from the cut of this Worktree.
|
|
1246
|
+
* input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
1247
|
+
* onDone: [
|
|
1248
|
+
* { guard: ({ event }) => event.output.outcome === "pushed", target: "done" },
|
|
1249
|
+
* { target: "failed" },
|
|
1250
|
+
* ],
|
|
1251
|
+
* },
|
|
1252
|
+
* },
|
|
1253
|
+
* done: { type: "final" },
|
|
1254
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
1255
|
+
* // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
|
|
1256
|
+
* failed: { invoke: { src: "fail", input: { reason: "the fix was not pushed" } } },
|
|
1257
|
+
* },
|
|
1258
|
+
* });
|
|
1259
|
+
*
|
|
1260
|
+
* export const machine = workspace(body, {
|
|
1261
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
1262
|
+
* });
|
|
1263
|
+
* ```
|
|
1264
|
+
*
|
|
1265
|
+
* @see docs/guides/push-and-sign-a-branch.md
|
|
1266
|
+
* @see docs/concepts/effects.md
|
|
1267
|
+
* @see docs/guides/give-the-cluster-git-credentials.md
|
|
1268
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
1269
|
+
* @see docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md
|
|
886
1270
|
*/
|
|
887
|
-
export function push<const O extends PushOptions>(options?: O): EffectLogic<PushInput<O
|
|
1271
|
+
export function push<const O extends PushOptions>(options?: O): EffectLogic<PushInput<O>, PushOutput> {
|
|
1272
|
+
// After an Orchestrator restart, a push is sent again with only the commit its intent row holds
|
|
1273
|
+
// (ADR-0067): a run is a function of the commit, never of the Agent's tip now.
|
|
888
1274
|
return pushWith(sandboxEffectorPort, options);
|
|
889
1275
|
}
|
|
890
1276
|
|