@jr2/orchestrator 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +142 -29
- package/docs/concepts/architecture.md +60 -34
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +13 -8
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +7 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +133 -50
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +14 -4
- package/docs/concepts/pools-and-sources.md +193 -17
- package/docs/concepts/secrets-and-trust.md +186 -62
- package/docs/concepts/the-menu.md +14 -0
- package/docs/concepts/workspaces.md +135 -56
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +72 -15
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +29 -12
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +19 -7
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +14 -7
- package/docs/guides/look-inside-a-sandbox.md +115 -42
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +34 -27
- package/docs/guides/set-an-agents-access-and-dials.md +69 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +7 -1
- package/docs/guides/watch-a-run.md +146 -117
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +18 -8
- package/docs/llms.txt +110 -23
- package/docs/reference/authoring-surface/agent.md +35 -12
- package/docs/reference/authoring-surface/answer.md +20 -4
- package/docs/reference/authoring-surface/arg.md +28 -6
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +37 -22
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +18 -5
- package/docs/reference/authoring-surface/file.md +37 -17
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +109 -9
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +14 -4
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +41 -8
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +23 -5
- package/docs/reference/authoring-surface/pool.md +79 -11
- package/docs/reference/authoring-surface/push.md +31 -13
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +26 -5
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +59 -4
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- 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-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +6 -7
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
- package/docs/reference/authoring-surface/type-customize.md +20 -3
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +6 -6
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +30 -9
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- 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 +66 -38
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +88 -147
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +248 -6
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +9 -4
- package/docs/reference/glossary.md +138 -45
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +988 -123
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +7 -5
- package/package.json +10 -3
- package/src/actor.ts +194 -42
- package/src/agent.ts +56 -6
- package/src/ambient.ts +3 -5
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +238 -26
- package/src/charts.ts +9 -2
- package/src/config.ts +566 -7
- package/src/conversations.ts +453 -0
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +257 -77
- package/src/customize.ts +154 -7
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +52 -4
- package/src/effect.ts +401 -132
- package/src/effector-wire.ts +278 -30
- package/src/fail.ts +17 -4
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +84 -35
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +197 -39
- package/src/holds.ts +5 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +164 -70
- package/src/http.ts +933 -99
- package/src/index.ts +30 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +23 -3
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +29 -5
- package/src/mcp-effect.ts +424 -0
- package/src/model-keys.ts +24 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +82 -7
- package/src/notices.ts +576 -59
- package/src/open-body.ts +110 -30
- package/src/open-effect.ts +17 -4
- package/src/open.ts +40 -7
- package/src/parts.ts +125 -35
- package/src/party.ts +35 -1
- package/src/pick.ts +43 -9
- package/src/pool.ts +1172 -172
- package/src/registration.ts +249 -17
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +1614 -190
- package/src/run-list.ts +191 -0
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +116 -21
- package/src/secret-placement.ts +248 -35
- package/src/server.ts +53 -4
- package/src/setup.ts +158 -38
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +362 -46
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +27 -10
- package/src/workspace.ts +314 -259
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
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, needsASlot, 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";
|
|
@@ -135,32 +136,42 @@ function slotOf(kind: string, slot: string, named: string | undefined, ambient:
|
|
|
135
136
|
);
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return
|
|
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 };
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
/** The slot's repositories, relative to the work root: the Agent's common git dir is the pod-local
|
|
154
161
|
* clone's, beside the branch Worktree (ADR-0004); the cache is the Binding's key (ADR-0051). */
|
|
155
|
-
export function repoRefOf(
|
|
156
|
-
|
|
162
|
+
export function repoRefOf(
|
|
163
|
+
ambient: AmbientHandles,
|
|
164
|
+
repoSlot: string,
|
|
165
|
+
worktree: string = ambient.repos[repoSlot]!,
|
|
166
|
+
): RepoRef & { url: string } {
|
|
157
167
|
const url = ambient.bindings?.[repoSlot];
|
|
158
168
|
if (url === undefined) {
|
|
159
169
|
throw new Error(`Repo Slot "${repoSlot}" has no Binding url in its Workspace's handles — a jr2 defect`);
|
|
160
170
|
}
|
|
161
171
|
const rel = posix.relative(WORK_ROOT, worktree);
|
|
162
172
|
return {
|
|
163
|
-
|
|
173
|
+
// The clone of the slot, read from the slot and not from the Worktree's folder.
|
|
174
|
+
gitDir: posix.join(repoSlot, "default", ".git"),
|
|
164
175
|
worktree: rel,
|
|
165
176
|
cache: repoKey(url),
|
|
166
177
|
url,
|
|
@@ -225,6 +236,7 @@ export type PushOptions = {
|
|
|
225
236
|
* (ADR-0051)`. `jr2 up` refuses it first, with the same reason.
|
|
226
237
|
*/
|
|
227
238
|
slot?: string;
|
|
239
|
+
// Static, so that `jr2 up` places the credential per slot.
|
|
228
240
|
/**
|
|
229
241
|
* How long jr2 waits for the push: a number and a unit, such as `"90s"` or `"5m"`. Default
|
|
230
242
|
* `"5m"`. With no answer in that time, the output is `unknown`. It refuses a value that is not a
|
|
@@ -233,8 +245,18 @@ export type PushOptions = {
|
|
|
233
245
|
timeout?: Duration;
|
|
234
246
|
};
|
|
235
247
|
|
|
236
|
-
/** The input of a `push` Effect:
|
|
237
|
-
|
|
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) &
|
|
238
260
|
(O["squash"] extends true ? { message: string } : unknown);
|
|
239
261
|
|
|
240
262
|
/**
|
|
@@ -273,11 +295,24 @@ export type SyncState = SyncAnswer["sync"];
|
|
|
273
295
|
*
|
|
274
296
|
* With `busy` or `not-synced`, `syncReason` tells why, and jr2 tries again before the next push.
|
|
275
297
|
*
|
|
298
|
+
* The union is open (`LaterOutcome`): give the rest a last arm with no guard.
|
|
299
|
+
*
|
|
276
300
|
* @example
|
|
277
301
|
* ```ts
|
|
278
|
-
* import {
|
|
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">;
|
|
279
314
|
*
|
|
280
|
-
*
|
|
315
|
+
* const releaseTree = (context: Input) => `${context.workspace.repos.app}/release` as const;
|
|
281
316
|
*
|
|
282
317
|
* // A push that did not happen (`rejected` or `diverged`) is a different problem from one jr2
|
|
283
318
|
* // cannot see (`unknown`).
|
|
@@ -286,17 +321,24 @@ export type SyncState = SyncAnswer["sync"];
|
|
|
286
321
|
*
|
|
287
322
|
* const body = jr2Setup({
|
|
288
323
|
* types: {} as { context: Input; input: Input },
|
|
289
|
-
* actors: { publish: push({ pin: true }), fail: fail() },
|
|
324
|
+
* actors: { cut: worktree({ expect: "existing" }), publish: push({ pin: true }), fail: fail() },
|
|
290
325
|
* }).createMachine({
|
|
291
326
|
* id: "release",
|
|
292
327
|
* context: ({ input }) => input,
|
|
293
328
|
* on: { "workspace.lost": ".lost" },
|
|
294
|
-
* initial: "
|
|
329
|
+
* initial: "cutting",
|
|
295
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
|
+
* },
|
|
296
338
|
* publishing: {
|
|
297
339
|
* invoke: {
|
|
298
340
|
* src: "publish",
|
|
299
|
-
* input: ({ context }) => ({ sha: context.sha }),
|
|
341
|
+
* input: ({ context }) => ({ worktree: releaseTree(context), sha: context.sha }),
|
|
300
342
|
* onDone: [
|
|
301
343
|
* { guard: ({ event }) => event.output.outcome === "pushed", target: "published" },
|
|
302
344
|
* { guard: ({ event }) => notPushed(event.output), target: "notPushed" },
|
|
@@ -308,13 +350,13 @@ export type SyncState = SyncAnswer["sync"];
|
|
|
308
350
|
* // Not final states: each one fails the run, with its own reason.
|
|
309
351
|
* notPushed: { invoke: { src: "fail", input: { reason: "the remote refused the push" } } },
|
|
310
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" } } },
|
|
311
354
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
312
355
|
* },
|
|
313
356
|
* });
|
|
314
357
|
*
|
|
315
358
|
* export const machine = workspace(body, {
|
|
316
359
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
317
|
-
* spec: () => ({ branch: "release" }),
|
|
318
360
|
* });
|
|
319
361
|
* ```
|
|
320
362
|
*
|
|
@@ -324,10 +366,11 @@ export type SyncState = SyncAnswer["sync"];
|
|
|
324
366
|
*/
|
|
325
367
|
export type PushOutput =
|
|
326
368
|
| Exclude<PushWireOutput, { outcome: "pushed" }>
|
|
327
|
-
| (Omit<Extract<PushWireOutput, { outcome: "pushed" }>, "bundle"> & { sync: SyncState; syncReason?: string })
|
|
369
|
+
| (Omit<Extract<PushWireOutput, { outcome: "pushed" }>, "bundle"> & { sync: SyncState; syncReason?: string })
|
|
370
|
+
| LaterOutcome;
|
|
328
371
|
|
|
329
372
|
export function pushParts(options: PushOptions = {}): PushParts {
|
|
330
|
-
const inputKeys = [...(options.pin ? ["sha"] : []), ...(options.squash ? ["message"] : [])];
|
|
373
|
+
const inputKeys = ["worktree", ...(options.pin ? ["sha"] : []), ...(options.squash ? ["message"] : [])];
|
|
331
374
|
return {
|
|
332
375
|
kind: "push",
|
|
333
376
|
pin: options.pin === true,
|
|
@@ -451,6 +494,7 @@ type KindContext = {
|
|
|
451
494
|
slot: string;
|
|
452
495
|
signal: AbortSignal;
|
|
453
496
|
mark: (marker: Omit<EffectMarker, "kind" | "slot" | "effect">) => void;
|
|
497
|
+
input: unknown;
|
|
454
498
|
};
|
|
455
499
|
|
|
456
500
|
/** How one Sandbox kind builds its start and what a restore may send again. */
|
|
@@ -463,13 +507,25 @@ type KindSpec = {
|
|
|
463
507
|
/** The Repo Slot the Effect runs on, written to its settled row. */
|
|
464
508
|
repoOf: (ambient: AmbientHandles, slot: string) => string;
|
|
465
509
|
/** The start for this run; `pinned` is the commit the intent row recorded. */
|
|
466
|
-
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;
|
|
467
518
|
/** Run before a start is sent (a push: the last sync again, and the ledger's pair). */
|
|
468
519
|
before?: (ctx: KindContext) => Promise<StartExtra>;
|
|
469
520
|
/** Run on the Effector's settled output before it is ledgered (a push: the sync). */
|
|
470
521
|
finish?: (output: unknown, ctx: KindContext) => Promise<unknown>;
|
|
471
522
|
/** What a settled output becomes for the Machine. */
|
|
472
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 };
|
|
473
529
|
};
|
|
474
530
|
|
|
475
531
|
type StartExtra = { pairs?: readonly Pair[] };
|
|
@@ -548,6 +604,9 @@ async function drive<I, O>(
|
|
|
548
604
|
kind: spec.kind,
|
|
549
605
|
slot,
|
|
550
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
|
+
: {}),
|
|
551
610
|
output,
|
|
552
611
|
at: now(),
|
|
553
612
|
});
|
|
@@ -589,7 +648,7 @@ async function drive<I, O>(
|
|
|
589
648
|
// A credentialed command's stop goes through this port when its state exits (ADR-0067).
|
|
590
649
|
const run = running.get(self);
|
|
591
650
|
if (spec.kind === "command" && run) run.port = port;
|
|
592
|
-
const ctx: KindContext = { ambient, binding, id, slot, signal, mark };
|
|
651
|
+
const ctx: KindContext = { ambient, binding, id, slot, signal, mark, input: snapshot.input };
|
|
593
652
|
|
|
594
653
|
const find = async (): Promise<{ found: EffectState | undefined } | undefined> => {
|
|
595
654
|
try {
|
|
@@ -619,12 +678,12 @@ async function drive<I, O>(
|
|
|
619
678
|
});
|
|
620
679
|
const extra = spec.before ? await spec.before(ctx) : {};
|
|
621
680
|
if (signal.aborted) return;
|
|
622
|
-
await send(spec.build(snapshot.input, ambient, slot, pinned, extra), pinned);
|
|
681
|
+
await send(spec.build(snapshot.input, ambient, slot, pinned, extra, binding), pinned);
|
|
623
682
|
return;
|
|
624
683
|
}
|
|
625
684
|
if (spec.kind === "http" && spec.idempotent) {
|
|
626
685
|
mark({ stage: "started", text: `${spec.label(ambient)} (sent again: it is idempotent)` });
|
|
627
|
-
await send(spec.build(snapshot.input, ambient, slot), undefined);
|
|
686
|
+
await send(spec.build(snapshot.input, ambient, slot, undefined, undefined, binding), undefined);
|
|
628
687
|
return;
|
|
629
688
|
}
|
|
630
689
|
settle(unknown(`${why}; not run again`));
|
|
@@ -645,9 +704,14 @@ async function drive<I, O>(
|
|
|
645
704
|
return;
|
|
646
705
|
}
|
|
647
706
|
|
|
707
|
+
const refusal = spec.refuse?.(ctx);
|
|
708
|
+
if (refusal !== undefined) {
|
|
709
|
+
settle(refusal);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
648
712
|
const extra = spec.before ? await spec.before(ctx) : {};
|
|
649
713
|
if (signal.aborted) return;
|
|
650
|
-
const req = spec.build(snapshot.input, ambient, slot, undefined, extra);
|
|
714
|
+
const req = spec.build(snapshot.input, ambient, slot, undefined, extra, binding);
|
|
651
715
|
const big = tooLarge(req);
|
|
652
716
|
if (big) {
|
|
653
717
|
settle(big);
|
|
@@ -857,40 +921,65 @@ export function pushWith<const O extends PushOptions>(
|
|
|
857
921
|
portFactory: EffectorPortFactory,
|
|
858
922
|
options?: O,
|
|
859
923
|
syncPorts: SyncPortFactory = harnessSyncPort,
|
|
860
|
-
): EffectLogic<PushInput<O
|
|
924
|
+
): EffectLogic<PushInput<O>, PushOutput> {
|
|
861
925
|
const parts = pushParts(options);
|
|
862
926
|
const repoOf = (ambient: AmbientHandles, slot: string) => slotOf("push", slot, parts.slot, ambient);
|
|
863
|
-
const worktreeOf = (ctx: KindContext) => ctx.ambient.repos[repoOf(ctx.ambient, ctx.slot)]!;
|
|
864
927
|
const syncOf = (ctx: KindContext) =>
|
|
865
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
|
+
};
|
|
866
937
|
return sandboxLogic(portFactory, parts, {
|
|
867
938
|
kind: "push",
|
|
868
939
|
idempotent: false,
|
|
869
940
|
repeatsPinned: true,
|
|
870
|
-
label: (
|
|
941
|
+
label: () => `push${parts.sign ? " (signed)" : ""}`,
|
|
871
942
|
repoOf,
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
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);
|
|
876
964
|
if (pinned) req.push.pin = pinned;
|
|
877
965
|
return req;
|
|
878
966
|
},
|
|
879
|
-
// Every push of this
|
|
880
|
-
//
|
|
881
|
-
//
|
|
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
|
|
882
970
|
// push and its sync did not finish (`dropSavedTails`); the push does not depend on it.
|
|
883
971
|
before: async (ctx) => {
|
|
884
972
|
const rows = ctx.binding?.effectRows?.() ?? {};
|
|
885
973
|
const repo = repoOf(ctx.ambient, ctx.slot);
|
|
886
|
-
const
|
|
974
|
+
const target = targetOf(ctx);
|
|
975
|
+
const last = lastPushOf(rows, ctx.ambient.sandbox, repo, target.branch, ctx.id);
|
|
887
976
|
if (last === undefined) return {};
|
|
888
977
|
const { row, rowId, output } = last;
|
|
889
978
|
if (output.sync === "busy" || output.sync === "not-synced") {
|
|
890
979
|
const answer = await syncOf(ctx).sync(
|
|
891
980
|
{
|
|
892
|
-
worktree:
|
|
893
|
-
branch:
|
|
981
|
+
worktree: target.path,
|
|
982
|
+
branch: target.branch,
|
|
894
983
|
published: output.published,
|
|
895
984
|
source: output.source,
|
|
896
985
|
...(typeof output.bundle === "string" ? { bundle: output.bundle } : {}),
|
|
@@ -902,7 +991,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
902
991
|
ctx.binding?.recordEffect?.(rowId, { ...row, output: { ...output, ...syncFields(answer) } });
|
|
903
992
|
}
|
|
904
993
|
// Each row's `squashed` rides beside its pair: a push that maps the same commit names what it is.
|
|
905
|
-
return { pairs: pushPairsOf(rows, ctx.ambient.sandbox, repo, ctx.id) };
|
|
994
|
+
return { pairs: pushPairsOf(rows, ctx.ambient.sandbox, repo, target.branch, ctx.id) };
|
|
906
995
|
},
|
|
907
996
|
// After every pushed outcome whose published commit is not the Agent's own — signed copies, or
|
|
908
997
|
// one squashed commit, signed or not (ADR-0067): the sync, in the Workspace's Harness, from the
|
|
@@ -913,10 +1002,11 @@ export function pushWith<const O extends PushOptions>(
|
|
|
913
1002
|
const o = output as PushWireOutput;
|
|
914
1003
|
if (o.outcome !== "pushed") return o;
|
|
915
1004
|
if (o.published === o.source) return { ...o, sync: "skipped" satisfies SyncState };
|
|
1005
|
+
const target = targetOf(ctx);
|
|
916
1006
|
const answer = await syncOf(ctx).sync(
|
|
917
1007
|
{
|
|
918
|
-
worktree:
|
|
919
|
-
branch:
|
|
1008
|
+
worktree: target.path,
|
|
1009
|
+
branch: target.branch,
|
|
920
1010
|
published: o.published,
|
|
921
1011
|
source: o.source,
|
|
922
1012
|
...(o.bundle !== undefined ? { bundle: o.bundle } : {}),
|
|
@@ -931,7 +1021,7 @@ export function pushWith<const O extends PushOptions>(
|
|
|
931
1021
|
const { bundle: _bundle, ...rest } = o;
|
|
932
1022
|
return rest;
|
|
933
1023
|
},
|
|
934
|
-
}) as EffectLogic<PushInput<O
|
|
1024
|
+
}) as EffectLogic<PushInput<O>, PushOutput>;
|
|
935
1025
|
}
|
|
936
1026
|
|
|
937
1027
|
/** The sync's answer as the output's fields: its state, and its reason when it moved nothing. */
|
|
@@ -975,16 +1065,35 @@ export function httpFileWith<S extends z.ZodType | undefined = undefined>(
|
|
|
975
1065
|
// With no Repo Slot the file is in `/work`, which is no Worktree (ADR-0051).
|
|
976
1066
|
label: (ambient) =>
|
|
977
1067
|
`http ${parts.method} ${parts.target.scheme}://${parts.target.host} ` +
|
|
978
|
-
(Object.keys(ambient.repos).length === 0
|
|
979
|
-
repoOf: (ambient, slot) => slotOf("http", slot,
|
|
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
|
+
},
|
|
980
1087
|
build: (input, ambient, slot) => {
|
|
981
|
-
|
|
1088
|
+
const { worktree: named, ...rest } = (input ?? {}) as { worktree?: string };
|
|
1089
|
+
let value: unknown = rest;
|
|
982
1090
|
if (def.input !== undefined) {
|
|
983
|
-
const parsed = def.input.safeParse(
|
|
1091
|
+
const parsed = def.input.safeParse(rest);
|
|
984
1092
|
if (!parsed.success) throw new Error(`http "${slot}": its input does not match the definition's input schema`);
|
|
985
1093
|
value = parsed.data;
|
|
986
1094
|
}
|
|
987
|
-
|
|
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);
|
|
988
1097
|
const segments = fromInput<readonly string[]>(body.segments, value);
|
|
989
1098
|
return fileStartOf(parts, worktree, {
|
|
990
1099
|
path: fromInput<string>(body.path, value)!,
|
|
@@ -1007,10 +1116,30 @@ export function credentialedCommandWith(
|
|
|
1007
1116
|
idempotent: false,
|
|
1008
1117
|
repeatsPinned: true,
|
|
1009
1118
|
label: () => parts.argv.map((a) => (typeof a === "string" ? a : `<${(a as Arg).arg}>`)).join(" "),
|
|
1010
|
-
repoOf: (ambient, slot) => slotOf("command", slot,
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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);
|
|
1014
1143
|
const approved = pin ? (input as { sha?: unknown } | undefined)?.sha : undefined;
|
|
1015
1144
|
if (pin && (typeof approved !== "string" || !/^[0-9a-f]{40}$/.test(approved))) {
|
|
1016
1145
|
throw new Error(`command "${slot}" (pin): its input's sha is not a full commit id`);
|
|
@@ -1019,7 +1148,7 @@ export function credentialedCommandWith(
|
|
|
1019
1148
|
parts,
|
|
1020
1149
|
argvOf(input, slot),
|
|
1021
1150
|
ref,
|
|
1022
|
-
|
|
1151
|
+
found.target.branch!,
|
|
1023
1152
|
pinned ?? (approved as string | undefined),
|
|
1024
1153
|
);
|
|
1025
1154
|
},
|
|
@@ -1027,10 +1156,13 @@ export function credentialedCommandWith(
|
|
|
1027
1156
|
}
|
|
1028
1157
|
|
|
1029
1158
|
/**
|
|
1030
|
-
* Make a `push` Effect: it pushes the branch of
|
|
1159
|
+
* Make a `push` Effect: it pushes the branch of a Worktree to the Binding of its Repo Slot.
|
|
1031
1160
|
* Put it in `actors`, and invoke it from the body of a `workspace()`: `actors: { publish: push({
|
|
1032
1161
|
* sign: true }) }`.
|
|
1033
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
|
+
*
|
|
1034
1166
|
* It runs in the Sandbox Effector of the Workspace, with the push credential of the
|
|
1035
1167
|
* `git.credentials` entry that the Binding matches. The Agent never reads that credential. It
|
|
1036
1168
|
* pushes one commit: the tip of the Agent's branch when the Effect starts, or, with `pin`, the
|
|
@@ -1038,8 +1170,10 @@ export function credentialedCommandWith(
|
|
|
1038
1170
|
* again. With `sign` or `squash`, the commit on the remote is not the Agent's own. Then jr2 moves
|
|
1039
1171
|
* the Agent's branch to it.
|
|
1040
1172
|
*
|
|
1041
|
-
* Every outcome is output (`PushOutput`): `pushed`, `rejected`, `diverged` or `unknown`.
|
|
1042
|
-
*
|
|
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
|
|
1043
1177
|
* outside a Workspace: `push "<slot>" runs in the Sandbox Effector of a Workspace, and its state is
|
|
1044
1178
|
* not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`.
|
|
1045
1179
|
*
|
|
@@ -1053,9 +1187,11 @@ export function credentialedCommandWith(
|
|
|
1053
1187
|
* agent,
|
|
1054
1188
|
* defineEvent,
|
|
1055
1189
|
* fail,
|
|
1190
|
+
* isReady,
|
|
1056
1191
|
* jr2Setup,
|
|
1057
1192
|
* push,
|
|
1058
1193
|
* workspace,
|
|
1194
|
+
* worktree,
|
|
1059
1195
|
* type Workspaced,
|
|
1060
1196
|
* } from "@jr2/orchestrator";
|
|
1061
1197
|
*
|
|
@@ -1067,6 +1203,8 @@ export function credentialedCommandWith(
|
|
|
1067
1203
|
*
|
|
1068
1204
|
* type Input = Workspaced<{}, "app">;
|
|
1069
1205
|
*
|
|
1206
|
+
* const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
1207
|
+
*
|
|
1070
1208
|
* const body = jr2Setup({
|
|
1071
1209
|
* types: {} as { context: Input; input: Input },
|
|
1072
1210
|
* events: [committed],
|
|
@@ -1075,6 +1213,7 @@ export function credentialedCommandWith(
|
|
|
1075
1213
|
* model: "anthropic/claude-haiku-5-5",
|
|
1076
1214
|
* instructions: "You are a careful engineer.",
|
|
1077
1215
|
* }),
|
|
1216
|
+
* cut: worktree(),
|
|
1078
1217
|
* publish: push({ sign: true }),
|
|
1079
1218
|
* fail: fail(),
|
|
1080
1219
|
* },
|
|
@@ -1082,10 +1221,20 @@ export function credentialedCommandWith(
|
|
|
1082
1221
|
* id: "fix-test",
|
|
1083
1222
|
* context: ({ input }) => input,
|
|
1084
1223
|
* on: { "workspace.lost": ".failed", "agent.fault": ".failed" },
|
|
1085
|
-
* initial: "
|
|
1224
|
+
* initial: "cutting",
|
|
1086
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
|
+
* },
|
|
1087
1233
|
* coding: {
|
|
1088
|
-
* invoke: {
|
|
1234
|
+
* invoke: {
|
|
1235
|
+
* src: "coder",
|
|
1236
|
+
* input: ({ context }) => ({ prompt: "Fix the failing test, and commit the fix.", cwd: fixTree(context) }),
|
|
1237
|
+
* },
|
|
1089
1238
|
* // The Agent moves the Machine on with a pick. An Agent's actor never ends, so `onDone`
|
|
1090
1239
|
* // never fires.
|
|
1091
1240
|
* on: { committed: "publishing" },
|
|
@@ -1093,6 +1242,8 @@ export function credentialedCommandWith(
|
|
|
1093
1242
|
* publishing: {
|
|
1094
1243
|
* invoke: {
|
|
1095
1244
|
* src: "publish",
|
|
1245
|
+
* // jr2 reads the branch from the cut of this Worktree.
|
|
1246
|
+
* input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
1096
1247
|
* onDone: [
|
|
1097
1248
|
* { guard: ({ event }) => event.output.outcome === "pushed", target: "done" },
|
|
1098
1249
|
* { target: "failed" },
|
|
@@ -1100,6 +1251,7 @@ export function credentialedCommandWith(
|
|
|
1100
1251
|
* },
|
|
1101
1252
|
* },
|
|
1102
1253
|
* done: { type: "final" },
|
|
1254
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
1103
1255
|
* // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
|
|
1104
1256
|
* failed: { invoke: { src: "fail", input: { reason: "the fix was not pushed" } } },
|
|
1105
1257
|
* },
|
|
@@ -1107,7 +1259,6 @@ export function credentialedCommandWith(
|
|
|
1107
1259
|
*
|
|
1108
1260
|
* export const machine = workspace(body, {
|
|
1109
1261
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
1110
|
-
* spec: () => ({ branch: "fix-test" }),
|
|
1111
1262
|
* });
|
|
1112
1263
|
* ```
|
|
1113
1264
|
*
|
|
@@ -1117,7 +1268,7 @@ export function credentialedCommandWith(
|
|
|
1117
1268
|
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
1118
1269
|
* @see docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md
|
|
1119
1270
|
*/
|
|
1120
|
-
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> {
|
|
1121
1272
|
// After an Orchestrator restart, a push is sent again with only the commit its intent row holds
|
|
1122
1273
|
// (ADR-0067): a run is a function of the commit, never of the Agent's tip now.
|
|
1123
1274
|
return pushWith(sandboxEffectorPort, options);
|