@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/http-effect.ts
CHANGED
|
@@ -50,6 +50,7 @@ import type {
|
|
|
50
50
|
EffectLogic,
|
|
51
51
|
EffectMarker,
|
|
52
52
|
EffectRow,
|
|
53
|
+
EffectParts,
|
|
53
54
|
EffectSnapshot,
|
|
54
55
|
HttpParts,
|
|
55
56
|
OpenHttpParts,
|
|
@@ -77,6 +78,7 @@ import {
|
|
|
77
78
|
type WireBody,
|
|
78
79
|
} from "./effector-wire.ts";
|
|
79
80
|
import { boundRun, type AnyActorSystem } from "./registration.ts";
|
|
81
|
+
import type { AnyWorktreePath } from "./worktree.ts";
|
|
80
82
|
import { open } from "./open.ts";
|
|
81
83
|
import { effectBindingShape, openEffectLogic, openPartsOf, type OpenOf } from "./open-effect.ts";
|
|
82
84
|
import { httpFileWith, sandboxEffectorPort } from "./sandbox-effect.ts";
|
|
@@ -105,9 +107,9 @@ declare const openSecretMark: unique symbol;
|
|
|
105
107
|
*/
|
|
106
108
|
export type OpenSecretMark = { readonly [openSecretMark]: true };
|
|
107
109
|
|
|
108
|
-
/** A
|
|
109
|
-
* secret — a dispatch's target (ADR-0071) — the secret is bound with it, to a name, so the
|
|
110
|
-
* `secret(open)` carries may not be present. One type for the definition and the binding. */
|
|
110
|
+
/** A secret bound to a name, never `secret(open)`. Where a definition's Open part is a whole that
|
|
111
|
+
* holds a secret — a dispatch's target (ADR-0071) — the secret is bound with it, to a name, so the
|
|
112
|
+
* mark `secret(open)` carries may not be present. One type for the definition and the binding. */
|
|
111
113
|
export type NamedSecret = Secret & { readonly [K in keyof OpenSecretMark]?: never };
|
|
112
114
|
|
|
113
115
|
/** `secret(open)`: a secret a packaged Machine names in its auth, a header or a command's env, and
|
|
@@ -169,13 +171,25 @@ const openSecretOf = new WeakMap<object, object>();
|
|
|
169
171
|
*
|
|
170
172
|
* @example
|
|
171
173
|
* ```ts
|
|
172
|
-
* import {
|
|
174
|
+
* import {
|
|
175
|
+
* command,
|
|
176
|
+
* fail,
|
|
177
|
+
* isReady,
|
|
178
|
+
* jr2Setup,
|
|
179
|
+
* secret,
|
|
180
|
+
* workspace,
|
|
181
|
+
* worktree,
|
|
182
|
+
* type Workspaced,
|
|
183
|
+
* } from "@jr2/orchestrator";
|
|
173
184
|
*
|
|
174
185
|
* type Input = Workspaced<{}, "app">;
|
|
175
186
|
*
|
|
187
|
+
* const deployTree = (context: Input) => `${context.workspace.repos.app}/deploy` as const;
|
|
188
|
+
*
|
|
176
189
|
* const body = jr2Setup({
|
|
177
190
|
* types: {} as { context: Input; input: Input },
|
|
178
191
|
* actors: {
|
|
192
|
+
* cut: worktree(),
|
|
179
193
|
* // The command reads the file at $KUBECONFIG. The Agent never reads the secret.
|
|
180
194
|
* deploy: command(["kubectl", "apply", "-f", "k8s/"], {
|
|
181
195
|
* env: { KUBECONFIG: secret("kube").path },
|
|
@@ -186,17 +200,26 @@ const openSecretOf = new WeakMap<object, object>();
|
|
|
186
200
|
* id: "deploy",
|
|
187
201
|
* context: ({ input }) => input,
|
|
188
202
|
* on: { "workspace.lost": ".lost" },
|
|
189
|
-
* initial: "
|
|
203
|
+
* initial: "cutting",
|
|
190
204
|
* states: {
|
|
191
|
-
*
|
|
205
|
+
* cutting: {
|
|
206
|
+
* invoke: {
|
|
207
|
+
* src: "cut",
|
|
208
|
+
* input: ({ context }) => ({ path: deployTree(context), branch: "deploy" }),
|
|
209
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "deploying" }, { target: "notCut" }],
|
|
210
|
+
* },
|
|
211
|
+
* },
|
|
212
|
+
* deploying: {
|
|
213
|
+
* invoke: { src: "deploy", input: ({ context }) => ({ worktree: deployTree(context) }), onDone: "done" },
|
|
214
|
+
* },
|
|
192
215
|
* done: { type: "final" },
|
|
216
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
193
217
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
194
218
|
* },
|
|
195
219
|
* });
|
|
196
220
|
*
|
|
197
221
|
* export const machine = workspace(body, {
|
|
198
222
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
199
|
-
* spec: () => ({ branch: "deploy" }),
|
|
200
223
|
* });
|
|
201
224
|
* ```
|
|
202
225
|
*
|
|
@@ -501,15 +524,12 @@ function asText(v: string | Secret | Text): Text {
|
|
|
501
524
|
/** A value of a file body's part: as written, or from the Effect's input. */
|
|
502
525
|
export type FromInput<S, T> = T | ((input: S extends z.ZodType ? z.infer<S> : void) => T);
|
|
503
526
|
|
|
504
|
-
/** `file(path, {
|
|
527
|
+
/** `file(path, { segments?, contentType? })`: a Worktree file as an `http` body. */
|
|
505
528
|
export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
506
529
|
readonly file: {
|
|
507
|
-
/** The file, relative to the
|
|
508
|
-
* Slot. */
|
|
530
|
+
/** The file, relative to the Worktree that `worktree` of the Effect's input names, or to `/work`
|
|
531
|
+
* in a Workspace with no Repo Slot. */
|
|
509
532
|
readonly path: FromInput<S, string>;
|
|
510
|
-
/** The Repo Slot whose Worktree holds it. Write it only when the Workspace has more than one.
|
|
511
|
-
*/
|
|
512
|
-
readonly slot?: string;
|
|
513
533
|
/** Path segments that jr2 adds to the end of the url, as `path` of `request` does for a json
|
|
514
534
|
* or text body. */
|
|
515
535
|
readonly segments?: FromInput<S, readonly string[]>;
|
|
@@ -521,14 +541,18 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
521
541
|
|
|
522
542
|
/**
|
|
523
543
|
* Make a Worktree file the body of an `http` Effect: `body: file("dist/report.pdf")`. The Effect
|
|
524
|
-
* then runs in the Sandbox Effector of the Workspace whose body invokes it.
|
|
525
|
-
*
|
|
544
|
+
* then runs in the Sandbox Effector of the Workspace whose body invokes it. Under a Workspace with
|
|
545
|
+
* Repo Slots, the invoke's input names the Worktree: `input: { worktree: "/work/app/fix" }`. It is a
|
|
546
|
+
* Worktree that a cut of this run made. That Effector opens the file in that Worktree and sends it.
|
|
547
|
+
* In a Workspace with no Repo Slot, the input names no `worktree`, and the path is relative to
|
|
526
548
|
* `/work`, the folder that its steps share. Use it with `PUT`, `POST` or `PATCH`. `path` and
|
|
527
549
|
* `segments` can be functions of the input of the Effect.
|
|
528
550
|
*
|
|
529
|
-
* Some paths and files give the output `refused`, and then nothing is sent. The
|
|
530
|
-
*
|
|
531
|
-
*
|
|
551
|
+
* Some inputs, paths and files give the output `refused`, and then nothing is sent. The `worktree`
|
|
552
|
+
* has no cut in this Sandbox: `no cut made <path> in this Sandbox: cut it with worktree(); nothing
|
|
553
|
+
* was sent`. Or the input names a `worktree` in a Workspace with no Repo Slot. Or the path has a
|
|
554
|
+
* symlink in it, has `..`, or is absolute. Or the file is a FIFO, a directory, or a file with more
|
|
555
|
+
* than one link.
|
|
532
556
|
*
|
|
533
557
|
* It refuses, when you call it:
|
|
534
558
|
* - a path that is not a string or a function: `file(path): the path is a string, or a function of
|
|
@@ -548,8 +572,13 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
548
572
|
* there is no Worktree to read; invoke it from a workspace()'s body, or send a json or text body
|
|
549
573
|
* (ADR-0068)`.
|
|
550
574
|
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
575
|
+
* And when it starts, under a Workspace with Repo Slots, an input with no `worktree`: ``http
|
|
576
|
+
* "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its input names no
|
|
577
|
+
* `worktree` ``.
|
|
578
|
+
*
|
|
579
|
+
* @param path - The file, relative to the Worktree that `worktree` of the input names, or to
|
|
580
|
+
* `/work` in a Workspace with no Repo Slot: a string, or a function of the input of the Effect
|
|
581
|
+
* that gives one.
|
|
553
582
|
* @param options - Optional. Each field says what it does.
|
|
554
583
|
* @returns The `body` of an `http` Effect.
|
|
555
584
|
*
|
|
@@ -561,17 +590,22 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
561
590
|
* fail,
|
|
562
591
|
* file,
|
|
563
592
|
* http,
|
|
593
|
+
* isReady,
|
|
564
594
|
* jr2Setup,
|
|
565
595
|
* secret,
|
|
566
596
|
* workspace,
|
|
597
|
+
* worktree,
|
|
567
598
|
* type Workspaced,
|
|
568
599
|
* } from "@jr2/orchestrator";
|
|
569
600
|
*
|
|
570
601
|
* type Input = Workspaced<{}, "app">;
|
|
571
602
|
*
|
|
603
|
+
* const reportTree = (context: Input) => `${context.workspace.repos.app}/report` as const;
|
|
604
|
+
*
|
|
572
605
|
* const body = jr2Setup({
|
|
573
606
|
* types: {} as { context: Input; input: Input },
|
|
574
607
|
* actors: {
|
|
608
|
+
* cut: worktree(),
|
|
575
609
|
* build: command(["npm", "run", "report"]),
|
|
576
610
|
* upload: http({
|
|
577
611
|
* method: "PUT",
|
|
@@ -588,18 +622,29 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
588
622
|
* id: "report",
|
|
589
623
|
* context: ({ input }) => input,
|
|
590
624
|
* on: { "workspace.lost": ".lost" },
|
|
591
|
-
* initial: "
|
|
625
|
+
* initial: "cutting",
|
|
592
626
|
* states: {
|
|
593
|
-
*
|
|
594
|
-
*
|
|
627
|
+
* cutting: {
|
|
628
|
+
* invoke: {
|
|
629
|
+
* src: "cut",
|
|
630
|
+
* input: ({ context }) => ({ path: reportTree(context), branch: "report" }),
|
|
631
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "building" }, { target: "notCut" }],
|
|
632
|
+
* },
|
|
633
|
+
* },
|
|
634
|
+
* building: {
|
|
635
|
+
* invoke: { src: "build", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "uploading" },
|
|
636
|
+
* },
|
|
637
|
+
* uploading: {
|
|
638
|
+
* invoke: { src: "upload", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "done" },
|
|
639
|
+
* },
|
|
595
640
|
* done: { type: "final" },
|
|
641
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
596
642
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
597
643
|
* },
|
|
598
644
|
* });
|
|
599
645
|
*
|
|
600
646
|
* export const machine = workspace(body, {
|
|
601
647
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
602
|
-
* spec: () => ({ branch: "report" }),
|
|
603
648
|
* });
|
|
604
649
|
* ```
|
|
605
650
|
*
|
|
@@ -610,9 +655,6 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
610
655
|
export function file<S extends z.ZodType | undefined = undefined>(
|
|
611
656
|
path: FromInput<S, string>,
|
|
612
657
|
options: {
|
|
613
|
-
/** The Repo Slot whose Worktree holds the file. Write it only when the Workspace has more than
|
|
614
|
-
* one. */
|
|
615
|
-
slot?: string;
|
|
616
658
|
/**
|
|
617
659
|
* Path segments that jr2 adds to the end of the url, as `path` does in `request` for a json or
|
|
618
660
|
* text body. A list of strings, or a function of the input of the Effect that gives one.
|
|
@@ -633,7 +675,6 @@ export function file<S extends z.ZodType | undefined = undefined>(
|
|
|
633
675
|
return {
|
|
634
676
|
file: {
|
|
635
677
|
path,
|
|
636
|
-
...(options.slot !== undefined ? { slot: options.slot } : {}),
|
|
637
678
|
...(options.segments !== undefined ? { segments: options.segments } : {}),
|
|
638
679
|
...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
|
|
639
680
|
},
|
|
@@ -723,10 +764,13 @@ export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
|
|
|
723
764
|
request?: (input: S extends z.ZodType ? z.infer<S> : void) => HttpRequestParts;
|
|
724
765
|
// A file body is an ADR-0067 Effect: the Sandbox Effector of the Workspace sends it.
|
|
725
766
|
/**
|
|
726
|
-
* A Worktree file as the body: `file(path, {
|
|
727
|
-
* runs in the Sandbox Effector of its Workspace.
|
|
767
|
+
* A Worktree file as the body: `file(path, { segments?, contentType? })`. The Effect then
|
|
768
|
+
* runs in the Sandbox Effector of its Workspace. Under a Workspace with Repo Slots, the invoke's
|
|
769
|
+
* input names a Worktree that a cut of this run made, in `worktree`; with none, it refuses when
|
|
770
|
+
* it starts: ``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its
|
|
771
|
+
* input names no `worktree` ``. Do not write `request` beside it: the `segments`
|
|
728
772
|
* of `file` are the path. It refuses another body: ``http({ body }): a body is file(path, {
|
|
729
|
-
*
|
|
773
|
+
* segments?, contentType? }) — json and text come from `request` ``.
|
|
730
774
|
*/
|
|
731
775
|
body?: FileBody<S>;
|
|
732
776
|
/**
|
|
@@ -755,6 +799,11 @@ export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
|
|
|
755
799
|
/** @internal */
|
|
756
800
|
export type HttpInput<S> = S extends z.ZodType ? z.infer<S> : void;
|
|
757
801
|
|
|
802
|
+
/** The input of an `http` Effect with a Worktree file as its body. Under a Workspace with Repo
|
|
803
|
+
* Slots, `worktree` names the Worktree. In a Workspace with no Repo Slot, the input has no
|
|
804
|
+
* `worktree`, and the path is relative to `/work`. */
|
|
805
|
+
export type HttpFileInput<S> = (S extends z.ZodType ? z.infer<S> : unknown) & { worktree?: AnyWorktreePath };
|
|
806
|
+
|
|
758
807
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
759
808
|
const DEFAULT_MAX_BODY = 65_536;
|
|
760
809
|
/** How long a re-asked request waits, at most, after a transport failure. */
|
|
@@ -830,7 +879,7 @@ export function httpParts<S extends z.ZodType | undefined>(def: HttpDefinition<S
|
|
|
830
879
|
const body = def.body?.file;
|
|
831
880
|
if (def.body !== undefined && (typeof body !== "object" || body === null)) {
|
|
832
881
|
throw new Error(
|
|
833
|
-
"http({ body }): a body is file(path, {
|
|
882
|
+
"http({ body }): a body is file(path, { segments?, contentType? }) — json and text come from `request`",
|
|
834
883
|
);
|
|
835
884
|
}
|
|
836
885
|
if (body && !FILE_METHODS.includes(def.method)) {
|
|
@@ -866,7 +915,6 @@ export function httpParts<S extends z.ZodType | undefined>(def: HttpDefinition<S
|
|
|
866
915
|
...(body
|
|
867
916
|
? {
|
|
868
917
|
file: {
|
|
869
|
-
...(body.slot !== undefined ? { slot: body.slot } : {}),
|
|
870
918
|
...(body.contentType !== undefined ? { contentType: body.contentType } : {}),
|
|
871
919
|
},
|
|
872
920
|
}
|
|
@@ -1020,8 +1068,58 @@ export function httpWith<S extends z.ZodType | undefined = undefined>(
|
|
|
1020
1068
|
const unbound = openHttpOf(def, (bound) => httpWith(portFactory, bound));
|
|
1021
1069
|
if (unbound) return unbound as EffectLogic<HttpInput<S>, HttpOutput>;
|
|
1022
1070
|
const parts = httpParts(def);
|
|
1023
|
-
|
|
1024
|
-
|
|
1071
|
+
return instanceEffectLogic<HttpInput<S>, HttpOutput>(
|
|
1072
|
+
parts,
|
|
1073
|
+
{
|
|
1074
|
+
kind: "http",
|
|
1075
|
+
idempotent: parts.idempotent,
|
|
1076
|
+
label: `${parts.method} ${hostText(parts)}`,
|
|
1077
|
+
request: (input, slot) => {
|
|
1078
|
+
if (def.input !== undefined) {
|
|
1079
|
+
const parsed = def.input.safeParse(input);
|
|
1080
|
+
if (!parsed.success) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`http "${slot}": its input does not match the definition's input schema — ` +
|
|
1083
|
+
parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; "),
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
input = parsed.data;
|
|
1087
|
+
}
|
|
1088
|
+
const runParts = def.request ? (def.request as (i: unknown) => HttpRequestParts)(input) : {};
|
|
1089
|
+
return startRequestOf(parts, runParts ?? {});
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
portFactory,
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* What the Instance Effector's driver needs of one kind that runs there and has no stop: `http`
|
|
1098
|
+
* and `mcp` (ADR-0068, ADR-0079). The driver is the same for both: the write-ahead intent row, the
|
|
1099
|
+
* restore rule, a forged answer, and D1.
|
|
1100
|
+
*/
|
|
1101
|
+
export type InstanceKind = {
|
|
1102
|
+
readonly kind: "http" | "mcp";
|
|
1103
|
+
/** The author's mark: a restore that finds no replica that holds it sends it again. */
|
|
1104
|
+
readonly idempotent: boolean;
|
|
1105
|
+
/** What the `started` marker says: `POST https://host`, `mcp slack-acme/post_message`. */
|
|
1106
|
+
readonly label: string;
|
|
1107
|
+
/** The run's start, from the invoke's input. It throws on a defect of the Machine, and then
|
|
1108
|
+
* nothing is sent. */
|
|
1109
|
+
readonly request: (input: unknown, slot: string) => StartRequest;
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* The slot logic of an Effect that the Instance Effector runs, over an injected port factory: the
|
|
1114
|
+
* seam `httpWith` and `mcpWith` share.
|
|
1115
|
+
*
|
|
1116
|
+
* @internal
|
|
1117
|
+
*/
|
|
1118
|
+
export function instanceEffectLogic<I, O>(
|
|
1119
|
+
parts: EffectParts,
|
|
1120
|
+
spec: InstanceKind,
|
|
1121
|
+
portFactory: EffectorPortFactory,
|
|
1122
|
+
): EffectLogic<I, O> {
|
|
1025
1123
|
const logic: ActorLogic<EffectSnapshot<O, I>, EventObject, I> = {
|
|
1026
1124
|
getInitialSnapshot: (_scope, input) => ({
|
|
1027
1125
|
status: "active",
|
|
@@ -1039,7 +1137,7 @@ export function httpWith<S extends z.ZodType | undefined = undefined>(
|
|
|
1039
1137
|
return { ...snapshot, status: "error", error: (event as Fault).error, input: undefined };
|
|
1040
1138
|
}
|
|
1041
1139
|
if (event.type === "xstate.stop") {
|
|
1042
|
-
stopped(self, system, snapshot.effectId);
|
|
1140
|
+
stopped(self, system, snapshot.effectId, spec.kind);
|
|
1043
1141
|
return { ...snapshot, status: "stopped", input: undefined };
|
|
1044
1142
|
}
|
|
1045
1143
|
return snapshot;
|
|
@@ -1048,7 +1146,7 @@ export function httpWith<S extends z.ZodType | undefined = undefined>(
|
|
|
1048
1146
|
if (snapshot.status !== "active") return;
|
|
1049
1147
|
const run: Running = { controller: new AbortController() };
|
|
1050
1148
|
running.set(self, run);
|
|
1051
|
-
void drive(self, system, snapshot,
|
|
1149
|
+
void drive(self, system, snapshot, spec, portFactory, run).catch((error: unknown) => {
|
|
1052
1150
|
if (run.controller.signal.aborted) return;
|
|
1053
1151
|
if (self.getSnapshot().status === "active") self.send({ type: "jr2.effect.error", error } as Fault);
|
|
1054
1152
|
});
|
|
@@ -1064,8 +1162,7 @@ async function drive<I, O>(
|
|
|
1064
1162
|
self: AnyActorRef,
|
|
1065
1163
|
system: AnyActorSystem,
|
|
1066
1164
|
snapshot: EffectSnapshot<O, I>,
|
|
1067
|
-
|
|
1068
|
-
def: HttpDefinition<z.ZodType | undefined>,
|
|
1165
|
+
spec: InstanceKind,
|
|
1069
1166
|
portFactory: EffectorPortFactory,
|
|
1070
1167
|
run: Running,
|
|
1071
1168
|
): Promise<void> {
|
|
@@ -1076,11 +1173,11 @@ async function drive<I, O>(
|
|
|
1076
1173
|
const now = (): string => new Date().toISOString();
|
|
1077
1174
|
const mark = (marker: Omit<EffectMarker, "kind" | "slot" | "effect">): void =>
|
|
1078
1175
|
binding?.marker?.({ kind: "effect", slot, effect: id, ...marker });
|
|
1079
|
-
const settle = (output:
|
|
1176
|
+
const settle = (output: NonNullable<EffectMarker["output"]>): void => {
|
|
1080
1177
|
if (signal.aborted || self.getSnapshot().status !== "active") return;
|
|
1081
1178
|
// The settled row first, saved at the write: a restart before the transition is saved delivers
|
|
1082
1179
|
// this output, and sends nothing.
|
|
1083
|
-
binding?.recordEffect?.(id, { state: "settled", kind:
|
|
1180
|
+
binding?.recordEffect?.(id, { state: "settled", kind: spec.kind, slot, output, at: now() });
|
|
1084
1181
|
mark({ stage: "settled", output });
|
|
1085
1182
|
self.send({ type: "jr2.effect.settle", output } satisfies Settle);
|
|
1086
1183
|
};
|
|
@@ -1102,8 +1199,8 @@ async function drive<I, O>(
|
|
|
1102
1199
|
const target = binding?.effector?.("instance");
|
|
1103
1200
|
if (!target) {
|
|
1104
1201
|
throw new Error(
|
|
1105
|
-
|
|
1106
|
-
|
|
1202
|
+
`${spec.kind} "${slot}" runs in the Instance Effector, and this Orchestrator knows none — \`jr2 up\` ` +
|
|
1203
|
+
`converges it when a registered Machine carries an ${spec.kind} Effect (ADR-0068)`,
|
|
1107
1204
|
);
|
|
1108
1205
|
}
|
|
1109
1206
|
const port = portFactory(target);
|
|
@@ -1133,7 +1230,7 @@ async function drive<I, O>(
|
|
|
1133
1230
|
await follow(found);
|
|
1134
1231
|
return;
|
|
1135
1232
|
}
|
|
1136
|
-
if (!
|
|
1233
|
+
if (!spec.idempotent) {
|
|
1137
1234
|
settle(
|
|
1138
1235
|
unknown(
|
|
1139
1236
|
"it was in flight when the Orchestrator restarted, and no Effector replica holds it; not sent again " +
|
|
@@ -1142,7 +1239,7 @@ async function drive<I, O>(
|
|
|
1142
1239
|
);
|
|
1143
1240
|
return;
|
|
1144
1241
|
}
|
|
1145
|
-
mark({ stage: "started", text: `${
|
|
1242
|
+
mark({ stage: "started", text: `${spec.label} (sent again: it is idempotent)` });
|
|
1146
1243
|
await send(requestFor());
|
|
1147
1244
|
return;
|
|
1148
1245
|
}
|
|
@@ -1160,29 +1257,17 @@ async function drive<I, O>(
|
|
|
1160
1257
|
// request leaves.
|
|
1161
1258
|
binding?.recordEffect?.(id, {
|
|
1162
1259
|
state: "intent",
|
|
1163
|
-
kind:
|
|
1260
|
+
kind: spec.kind,
|
|
1164
1261
|
slot,
|
|
1165
1262
|
placement: "instance",
|
|
1166
|
-
idempotent:
|
|
1263
|
+
idempotent: spec.idempotent,
|
|
1167
1264
|
at: now(),
|
|
1168
1265
|
});
|
|
1169
|
-
mark({ stage: "started", text:
|
|
1266
|
+
mark({ stage: "started", text: spec.label });
|
|
1170
1267
|
await send(req);
|
|
1171
1268
|
|
|
1172
1269
|
function requestFor(): StartRequest {
|
|
1173
|
-
|
|
1174
|
-
if (def.input !== undefined) {
|
|
1175
|
-
const parsed = def.input.safeParse(input);
|
|
1176
|
-
if (!parsed.success) {
|
|
1177
|
-
throw new Error(
|
|
1178
|
-
`http "${slot}": its input does not match the definition's input schema — ` +
|
|
1179
|
-
parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; "),
|
|
1180
|
-
);
|
|
1181
|
-
}
|
|
1182
|
-
input = parsed.data;
|
|
1183
|
-
}
|
|
1184
|
-
const runParts = def.request ? (def.request as (i: unknown) => HttpRequestParts)(input) : {};
|
|
1185
|
-
return startRequestOf(parts, runParts ?? {});
|
|
1270
|
+
return spec.request(snapshot.input, slot);
|
|
1186
1271
|
}
|
|
1187
1272
|
|
|
1188
1273
|
/** Send the start, and follow it. A start that never reached a replica is sent again; one whose
|
|
@@ -1209,7 +1294,7 @@ async function drive<I, O>(
|
|
|
1209
1294
|
await follow(found);
|
|
1210
1295
|
return;
|
|
1211
1296
|
}
|
|
1212
|
-
if (!
|
|
1297
|
+
if (!spec.idempotent) {
|
|
1213
1298
|
settle(unknown("its start may have reached the Effector, and no replica holds it now; not sent again"));
|
|
1214
1299
|
return;
|
|
1215
1300
|
}
|
|
@@ -1230,7 +1315,7 @@ async function drive<I, O>(
|
|
|
1230
1315
|
let replica: Replica = first.replica;
|
|
1231
1316
|
for (;;) {
|
|
1232
1317
|
if (state.state === "settled" && state.output) {
|
|
1233
|
-
settle(state.output as
|
|
1318
|
+
settle(state.output as NonNullable<EffectMarker["output"]>);
|
|
1234
1319
|
return;
|
|
1235
1320
|
}
|
|
1236
1321
|
if (state.state === "unknown") {
|
|
@@ -1238,7 +1323,7 @@ async function drive<I, O>(
|
|
|
1238
1323
|
if (!asked || signal.aborted) return;
|
|
1239
1324
|
const { found } = asked;
|
|
1240
1325
|
if (!found) {
|
|
1241
|
-
if (
|
|
1326
|
+
if (spec.idempotent) {
|
|
1242
1327
|
await send(requestFor());
|
|
1243
1328
|
return;
|
|
1244
1329
|
}
|
|
@@ -1269,15 +1354,15 @@ async function drive<I, O>(
|
|
|
1269
1354
|
* unknown": an http Effect has no stop route, so the Effector finishes the call and drops an
|
|
1270
1355
|
* outcome nobody reads. The Machine never hears it.
|
|
1271
1356
|
*/
|
|
1272
|
-
function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string): void {
|
|
1357
|
+
function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string, kind: "http" | "mcp"): void {
|
|
1273
1358
|
const run = running.get(self);
|
|
1274
1359
|
running.delete(self);
|
|
1275
1360
|
run?.controller.abort();
|
|
1276
1361
|
const binding = boundRun(system);
|
|
1277
1362
|
if (binding?.hostStopping) return;
|
|
1278
1363
|
const row = binding?.effectRow?.(effectId);
|
|
1279
|
-
if (row?.state !== "intent" || row.kind !==
|
|
1280
|
-
const aborted: EffectRow = { state: "aborted", kind
|
|
1364
|
+
if (row?.state !== "intent" || row.kind !== kind) return;
|
|
1365
|
+
const aborted: EffectRow = { state: "aborted", kind, slot: row.slot, at: new Date().toISOString() };
|
|
1281
1366
|
binding?.recordEffect?.(effectId, aborted, "with-transition");
|
|
1282
1367
|
binding?.marker?.({
|
|
1283
1368
|
kind: "effect",
|
|
@@ -1290,7 +1375,7 @@ function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string): v
|
|
|
1290
1375
|
|
|
1291
1376
|
/** A start over the bytes the Effector reads is output `refused` (ADR-0068): the Effector could not
|
|
1292
1377
|
* verify it, and a body built from context or a pick's param can reach the bound. */
|
|
1293
|
-
function tooLarge(req: StartRequest):
|
|
1378
|
+
function tooLarge(req: StartRequest): { outcome: "refused"; reason: string } | undefined {
|
|
1294
1379
|
const bytes = Buffer.byteLength(JSON.stringify(req), "utf8");
|
|
1295
1380
|
if (bytes <= MAX_START_BYTES) return undefined;
|
|
1296
1381
|
return {
|
|
@@ -1347,8 +1432,9 @@ function parseDuration(d: Duration): number {
|
|
|
1347
1432
|
* src: "comment" }`.
|
|
1348
1433
|
*
|
|
1349
1434
|
* It runs in the Instance Effector, which `jr2 up` deploys when a Machine has an `http` Effect.
|
|
1350
|
-
* With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it
|
|
1351
|
-
* The Effector makes the call. The
|
|
1435
|
+
* With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it,
|
|
1436
|
+
* and under Repo Slots its input names the Worktree in `worktree`. The Effector makes the call. The
|
|
1437
|
+
* Orchestrator holds no secret.
|
|
1352
1438
|
*
|
|
1353
1439
|
* Every outcome is output (`HttpOutput`): `response`, `unreachable`, `refused` or `unknown`. A 4xx
|
|
1354
1440
|
* or 5xx status is output too. jr2 sends the call at most one time, unless you mark it
|
|
@@ -1402,6 +1488,14 @@ function parseDuration(d: Duration): number {
|
|
|
1402
1488
|
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
1403
1489
|
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
1404
1490
|
*/
|
|
1491
|
+
export function http<
|
|
1492
|
+
S extends z.ZodType | undefined = undefined,
|
|
1493
|
+
const U extends HttpDefinition<S>["url"] = string,
|
|
1494
|
+
const A extends HttpDefinition<S>["auth"] = undefined,
|
|
1495
|
+
const H extends HttpDefinition<S>["headers"] = undefined,
|
|
1496
|
+
>(
|
|
1497
|
+
def: HttpDefinition<S> & { url: U; auth?: A; headers?: H; body: FileBody<S> },
|
|
1498
|
+
): EffectLogic<HttpFileInput<S>, HttpOutput> & OpenOf<HttpOpenParts<U, A, H>>;
|
|
1405
1499
|
export function http<
|
|
1406
1500
|
S extends z.ZodType | undefined = undefined,
|
|
1407
1501
|
const U extends HttpDefinition<S>["url"] = string,
|