@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/effector-wire.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
15
15
|
import { lookup } from "node:dns/promises";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
+
import type { LaterOutcome } from "./effect.ts";
|
|
17
18
|
|
|
18
19
|
// --- The request (POST /effects/<id>) ------------------------------------------------------------
|
|
19
20
|
//
|
|
@@ -104,10 +105,10 @@ export const PairShape = z.strictObject({
|
|
|
104
105
|
});
|
|
105
106
|
export type Pair = z.infer<typeof PairShape>;
|
|
106
107
|
|
|
107
|
-
/** A `push` start:
|
|
108
|
+
/** A `push` start: a cut Worktree's branch to the Repo Slot's Binding, with the push credential of
|
|
108
109
|
* the `git.credentials` entry the url falls under — the Effector's config holds the entries, and
|
|
109
110
|
* matches the url as the fence does (`credential` names one by its `match` instead). `pin` is the
|
|
110
|
-
* commit; absent, the Effector reads the
|
|
111
|
+
* commit; absent, the Effector reads the branch's tip and its answer carries it (`pinned`).
|
|
111
112
|
*/
|
|
112
113
|
export const PushStartShape = z.strictObject({
|
|
113
114
|
kind: z.literal("push"),
|
|
@@ -181,37 +182,90 @@ export const DispatchedByShape = z.strictObject({
|
|
|
181
182
|
* A `dispatch` start (ADR-0071), which only the Instance Effector serves: a run of `workflow` on the
|
|
182
183
|
* Orchestrator at `url` (its origin, one text part), the body `input` whole, the Instance token as a
|
|
183
184
|
* bearer by name, `dispatchedBy` as the `jr2-dispatched-by` header — `instance` set, since the
|
|
184
|
-
* caller is foreign by construction —
|
|
185
|
+
* caller is foreign by construction — `onBehalfOf` as the `jr2-on-behalf-of` header, and, with
|
|
186
|
+
* `wait`, a poll of `GET /runs/<id>` until the run
|
|
185
187
|
* ends or `timeoutMs` passes.
|
|
188
|
+
*
|
|
189
|
+
* `keyOnly` in place of `input`: a key-only ask, sent when the slot's schema refuses the body. The
|
|
190
|
+
* Effector sends the `jr2-key-only` header and no body; the door answers the run under the key, or
|
|
191
|
+
* `{ existing: false }`, which the Effector settles as `absent`. One of the two is set, never both.
|
|
186
192
|
*/
|
|
187
|
-
export const DispatchStartShape = z
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
193
|
+
export const DispatchStartShape = z
|
|
194
|
+
.strictObject({
|
|
195
|
+
kind: z.literal("dispatch"),
|
|
196
|
+
url: TextShape.refine(
|
|
197
|
+
(parts) => parts.length === 1 && "text" in parts[0]! && ORIGIN.test(parts[0].text),
|
|
198
|
+
"a dispatch url is one text part, an origin: http(s)://host[:port]",
|
|
199
|
+
),
|
|
200
|
+
workflow: z.string().regex(WORKFLOW_NAME),
|
|
201
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
202
|
+
keyOnly: z.literal(true).optional(),
|
|
203
|
+
auth: z.strictObject({ bearer: SecretRefShape }),
|
|
204
|
+
// Every id non-empty, as the door's header shape requires (http.ts): an empty one would be the
|
|
205
|
+
// door's 400, which reads as a defect of the input. The effect is a ledger row id, the key. The Go
|
|
206
|
+
// side's `validateDispatch` holds the same rule.
|
|
207
|
+
dispatchedBy: DispatchedByShape.extend({
|
|
208
|
+
run: z.string().min(1),
|
|
209
|
+
slot: z.string().min(1),
|
|
210
|
+
effect: z
|
|
211
|
+
.string()
|
|
212
|
+
.regex(/^[A-Za-z0-9._-]{1,128}$/)
|
|
213
|
+
.refine((id) => id !== "." && id !== ".."),
|
|
214
|
+
instance: z.string().min(1),
|
|
215
|
+
}),
|
|
216
|
+
wait: z.boolean(),
|
|
217
|
+
timeoutMs: z.number().int().positive(),
|
|
218
|
+
// The first Caller of the calling run's chain (ADR-0071, ADR-0078), sent as the
|
|
219
|
+
// `jr2-on-behalf-of` header: printable ASCII, as a header value is, and at most what the door
|
|
220
|
+
// takes. The other Instance records it and does not check it.
|
|
221
|
+
onBehalfOf: z
|
|
203
222
|
.string()
|
|
204
|
-
.regex(/^[
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
223
|
+
.regex(/^[\x20-\x7e]{1,256}$/)
|
|
224
|
+
.optional(),
|
|
225
|
+
})
|
|
226
|
+
.refine(
|
|
227
|
+
(req) => (req.input === undefined) !== (req.keyOnly === undefined),
|
|
228
|
+
"a dispatch start carries its input, or keyOnly and no input",
|
|
229
|
+
);
|
|
211
230
|
export type DispatchStartRequest = z.infer<typeof DispatchStartShape>;
|
|
212
231
|
|
|
213
|
-
/**
|
|
214
|
-
|
|
232
|
+
/** An MCP server's name (ADR-0079), as the `mcp` block of `jr2.config.ts` keys it. The Go side's
|
|
233
|
+
* `mcpServer` is the same rule. */
|
|
234
|
+
export const MCP_SERVER = /^[a-z][a-z0-9-]*$/;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* An `mcp` start (ADR-0067, ADR-0079), which only the Instance Effector serves: the server by its
|
|
238
|
+
* name — the Effector reads its url and the name of its secret from `effector.json`, so neither is
|
|
239
|
+
* on the wire — and one request: `call` a tool with its arguments, `read` a resource, or get a
|
|
240
|
+
* `prompt` with its arguments. `timeoutMs` is the author's; absent, the server's `timeout` from
|
|
241
|
+
* `effector.json` applies. `maxBody` bounds the answer's `text` and its `structured` value.
|
|
242
|
+
*/
|
|
243
|
+
export const McpStartShape = z.strictObject({
|
|
244
|
+
kind: z.literal("mcp"),
|
|
245
|
+
mcp: z
|
|
246
|
+
.strictObject({
|
|
247
|
+
server: z.string().regex(MCP_SERVER),
|
|
248
|
+
call: z.strictObject({ tool: z.string().min(1), arguments: z.record(z.string(), z.unknown()) }).optional(),
|
|
249
|
+
read: z.strictObject({ uri: z.string().min(1) }).optional(),
|
|
250
|
+
prompt: z.strictObject({ name: z.string().min(1), arguments: z.record(z.string(), z.string()) }).optional(),
|
|
251
|
+
})
|
|
252
|
+
.refine(
|
|
253
|
+
(m) => [m.call, m.read, m.prompt].filter((x) => x !== undefined).length === 1,
|
|
254
|
+
"an mcp start carries exactly one of call, read, prompt",
|
|
255
|
+
),
|
|
256
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
257
|
+
maxBody: z.number().int().positive(),
|
|
258
|
+
});
|
|
259
|
+
export type McpStartRequest = z.infer<typeof McpStartShape>;
|
|
260
|
+
|
|
261
|
+
/** Every start: the http kinds, a push, a credentialed command, a dispatch, an mcp request. */
|
|
262
|
+
export const StartRequestShape = z.union([
|
|
263
|
+
HttpStartShape,
|
|
264
|
+
PushStartShape,
|
|
265
|
+
CommandStartShape,
|
|
266
|
+
DispatchStartShape,
|
|
267
|
+
McpStartShape,
|
|
268
|
+
]);
|
|
215
269
|
export type StartRequest = z.infer<typeof StartRequestShape>;
|
|
216
270
|
|
|
217
271
|
/** The most bytes of a start's body the Effector reads (its `maxRequestBody`). A longer body
|
|
@@ -259,6 +313,8 @@ export const HttpOutputShape = z.union([
|
|
|
259
313
|
* - `unknown`: the call can have gone out, but no answer came, or jr2 cannot tell after a restart.
|
|
260
314
|
* `reason` tells why. jr2 does not send it again unless it is marked `idempotent`.
|
|
261
315
|
*
|
|
316
|
+
* The union is open (`LaterOutcome`): give the rest a last arm with no guard.
|
|
317
|
+
*
|
|
262
318
|
* @example
|
|
263
319
|
* ```ts
|
|
264
320
|
* import { fail, http, jr2Setup, type HttpOutput } from "@jr2/orchestrator";
|
|
@@ -291,7 +347,7 @@ export const HttpOutputShape = z.union([
|
|
|
291
347
|
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
292
348
|
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
293
349
|
*/
|
|
294
|
-
export type HttpOutput = z.infer<typeof HttpOutputShape
|
|
350
|
+
export type HttpOutput = z.infer<typeof HttpOutputShape> | LaterOutcome;
|
|
295
351
|
|
|
296
352
|
/**
|
|
297
353
|
* A `push` Effect's output (ADR-0067). `pushed`: the published commit, its source (the Agent's
|
|
@@ -376,9 +432,92 @@ export const DispatchOutputShape = z.discriminatedUnion("outcome", [
|
|
|
376
432
|
z.strictObject({ outcome: z.literal("refused"), status: z.number().int(), report: z.array(z.string()) }),
|
|
377
433
|
z.strictObject({ outcome: z.literal("unreachable"), reason: z.string() }),
|
|
378
434
|
z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
|
|
435
|
+
// The answer to a key-only ask when no run has the key (ADR-0071): nothing was started. Wire
|
|
436
|
+
// only: the driver makes it the invoke's error, the slot's schema refusal, and the Machine never
|
|
437
|
+
// reads it.
|
|
438
|
+
z.strictObject({ outcome: z.literal("absent") }),
|
|
379
439
|
]);
|
|
380
440
|
export type DispatchWireOutput = z.infer<typeof DispatchOutputShape>;
|
|
381
441
|
|
|
442
|
+
/**
|
|
443
|
+
* An `mcp` Effect's output on the wire (ADR-0067, ADR-0079): `returned`, with `isError` for a call
|
|
444
|
+
* alone; or a reason for `refused`, `unreachable`, `input-required` and `unknown`.
|
|
445
|
+
*/
|
|
446
|
+
export const McpOutputShape = z.union([
|
|
447
|
+
z.strictObject({
|
|
448
|
+
outcome: z.literal("returned"),
|
|
449
|
+
isError: z.boolean().optional(),
|
|
450
|
+
text: z.string(),
|
|
451
|
+
structured: z.unknown().optional(),
|
|
452
|
+
truncated: z.boolean().optional(),
|
|
453
|
+
}),
|
|
454
|
+
z.strictObject({
|
|
455
|
+
outcome: z.enum(["refused", "unreachable", "input-required", "unknown"]),
|
|
456
|
+
reason: z.string(),
|
|
457
|
+
}),
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
/** The outcomes that an `mcp` Effect gives besides `returned`. Each one has a `reason` in words. */
|
|
461
|
+
type McpNotReturned =
|
|
462
|
+
| { outcome: "refused"; reason: string }
|
|
463
|
+
| { outcome: "unreachable"; reason: string }
|
|
464
|
+
| { outcome: "input-required"; reason: string }
|
|
465
|
+
| { outcome: "unknown"; reason: string }
|
|
466
|
+
| LaterOutcome;
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The output of an `mcp.call` Effect: what the `onDone` of its state gets as `event.output`. Every
|
|
470
|
+
* outcome is output. Route on `outcome`:
|
|
471
|
+
*
|
|
472
|
+
* - `returned`: the server answered the call. `isError` is the tool's own `isError`: the tool ran
|
|
473
|
+
* and says that it failed. `text` joins the text of the result's content; a part that is not text
|
|
474
|
+
* is one line that names its type. `structured` is the result's `structuredContent`, when it has
|
|
475
|
+
* one. `text` and `structured` each have the byte bound of the Effect's `maxBody`: past it, jr2
|
|
476
|
+
* cuts `text`, drops `structured`, and sets `truncated`.
|
|
477
|
+
* - `refused`: the request did not run. The server answered an HTTP 3xx or 4xx status (a 401 or a
|
|
478
|
+
* 403 included) at any step, the handshake too; it refused the handshake; or it answered the
|
|
479
|
+
* request with the JSON-RPC error `-32700`, `-32600`, `-32601` or `-32602`: a request that it
|
|
480
|
+
* could not read, an unknown method, an unknown tool, or bad arguments; or it answered a read with
|
|
481
|
+
* `-32002`: the resource does not exist. It is also the Effector's
|
|
482
|
+
* own refusal, with nothing sent: the server or its secret is not in its `effector.json`.
|
|
483
|
+
* - `unreachable`: nothing of the request was sent: the name did not resolve, or the connection,
|
|
484
|
+
* TLS or the handshake failed otherwise (a 5xx, the timeout, an answer that is not MCP's, a
|
|
485
|
+
* revision of MCP that jr2 does not speak).
|
|
486
|
+
* - `input-required`: the server asked for input, which jr2 does not give. After the error
|
|
487
|
+
* `-32042`, the tool did not run. After an `elicitation/create` request, which comes while the
|
|
488
|
+
* tool runs, the tool may have begun. jr2 does not send the request again: a Machine that
|
|
489
|
+
* invokes the Effect again after a Gate can run a part of the tool two times.
|
|
490
|
+
* - `unknown`: the request was sent and no answer came (the timeout, a reset, a stream that ended
|
|
491
|
+
* first), or the server answered with a 5xx or another JSON-RPC error. The tool may have acted.
|
|
492
|
+
* jr2 does not send it again unless it is marked `idempotent`.
|
|
493
|
+
*
|
|
494
|
+
* jr2 replaces each value of an Effect secret that the Effector holds with `[secret:<name>]`, in
|
|
495
|
+
* every string of the output. The union is open ({@link LaterOutcome}): give the rest a last arm
|
|
496
|
+
* with no guard.
|
|
497
|
+
*
|
|
498
|
+
* @see docs/concepts/effects.md
|
|
499
|
+
* @see docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md
|
|
500
|
+
*/
|
|
501
|
+
export type McpCallOutput =
|
|
502
|
+
| { outcome: "returned"; isError: boolean; text: string; structured?: unknown; truncated?: boolean }
|
|
503
|
+
| McpNotReturned;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* The output of an `mcp.read` or an `mcp.prompt` Effect. Its outcomes are those of
|
|
507
|
+
* {@link McpCallOutput}, with no `isError`. For a read, `text` joins the text of the resource's
|
|
508
|
+
* contents, and `structured` is the `contents` list: a blob is given only as its `mimeType` and its
|
|
509
|
+
* size. For a prompt, `text` joins the text of the prompt's messages, and `structured` is the
|
|
510
|
+
* `messages` list. Past the bound, `structured` is dropped and `truncated` is set.
|
|
511
|
+
*
|
|
512
|
+
* @see docs/concepts/effects.md
|
|
513
|
+
*/
|
|
514
|
+
export type McpReadOutput =
|
|
515
|
+
| { outcome: "returned"; text: string; structured?: unknown; truncated?: boolean }
|
|
516
|
+
| McpNotReturned;
|
|
517
|
+
|
|
518
|
+
/** The output of any `mcp` Effect: a call's, a read's or a prompt's. */
|
|
519
|
+
export type McpOutput = McpCallOutput | McpReadOutput;
|
|
520
|
+
|
|
382
521
|
/** The replica that holds an Effect, and where to poll it. */
|
|
383
522
|
export const ReplicaShape = z.strictObject({ name: z.string(), address: z.string() });
|
|
384
523
|
export type Replica = z.infer<typeof ReplicaShape>;
|
|
@@ -394,13 +533,122 @@ export const EffectStateShape = z.strictObject({
|
|
|
394
533
|
/** The run a dispatch started, once the door answered and while its wait runs: the Orchestrator
|
|
395
534
|
* writes it to the ledger's intent row, so the caller names its callee during the wait (ADR-0071). */
|
|
396
535
|
run: z.string().optional(),
|
|
397
|
-
output: z
|
|
536
|
+
output: z
|
|
537
|
+
.union([HttpOutputShape, PushOutputShape, CommandOutputShape, DispatchOutputShape, McpOutputShape])
|
|
538
|
+
.optional(),
|
|
398
539
|
});
|
|
399
540
|
export type EffectState = z.infer<typeof EffectStateShape>;
|
|
400
541
|
|
|
401
542
|
/** A refusal's body: the reason, and the replica when it names one. */
|
|
402
543
|
export const RefusalShape = z.strictObject({ error: z.string(), replica: ReplicaShape.optional() });
|
|
403
544
|
|
|
545
|
+
// --- The `mac` check (POST /mac, ADR-0068, ADR-0078) ---------------------------------------------
|
|
546
|
+
//
|
|
547
|
+
// A Hook's `mac` verifier: the Orchestrator builds the bytes that the service signed, and the
|
|
548
|
+
// Instance Effector, which holds the secret, computes the HMAC over them and compares it with the
|
|
549
|
+
// signature in constant time. The answer is "match" or "no match", and nothing more: no HMAC and no
|
|
550
|
+
// secret ever comes back. The Orchestrator makes every check that needs no secret (the window)
|
|
551
|
+
// before it asks.
|
|
552
|
+
|
|
553
|
+
/** The HMAC algorithms of a `mac` check. */
|
|
554
|
+
export const MAC_ALGS = ["sha256", "sha1", "sha512"] as const;
|
|
555
|
+
|
|
556
|
+
/** A `mac` check: the NAME of the secret, the algorithm, the signed bytes and the signature, each
|
|
557
|
+
* in base64. */
|
|
558
|
+
export const MacRequestShape = z.strictObject({
|
|
559
|
+
// A secret's name, as the Effector reads it: a file under its secrets mount.
|
|
560
|
+
secret: z
|
|
561
|
+
.string()
|
|
562
|
+
.regex(/^[A-Za-z0-9._-]{1,253}$/)
|
|
563
|
+
.refine((name) => !name.startsWith("."), "a secret's name does not start with ."),
|
|
564
|
+
alg: z.enum(MAC_ALGS),
|
|
565
|
+
bytes: z.base64(),
|
|
566
|
+
signature: z.base64(),
|
|
567
|
+
});
|
|
568
|
+
export type MacRequest = z.infer<typeof MacRequestShape>;
|
|
569
|
+
|
|
570
|
+
/** The answer to a `mac` check: whether the HMAC of the bytes with the secret is the signature. */
|
|
571
|
+
export const MacAnswerShape = z.strictObject({ match: z.boolean() });
|
|
572
|
+
export type MacAnswer = z.infer<typeof MacAnswerShape>;
|
|
573
|
+
|
|
574
|
+
/** The path of a `mac` check on the Instance Effector. */
|
|
575
|
+
export const MAC_PATH = "/mac";
|
|
576
|
+
|
|
577
|
+
/** The `mac` check, as the Hook's door asks it: `true` for a match. It rejects when no Effector can
|
|
578
|
+
* answer, and the door then answers 503. */
|
|
579
|
+
export type HookMac = (req: {
|
|
580
|
+
secret: string;
|
|
581
|
+
alg: (typeof MAC_ALGS)[number];
|
|
582
|
+
bytes: Uint8Array;
|
|
583
|
+
signature: Uint8Array;
|
|
584
|
+
}) => Promise<boolean>;
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* The `mac` check over the Instance Effector's signed wire (ADR-0068): one replica, any one, since
|
|
588
|
+
* each holds the same secrets. A replica that was never reached is passed over for the next. An
|
|
589
|
+
* answer is trusted only with the Effector's signature, as an Effect's is.
|
|
590
|
+
*/
|
|
591
|
+
export function createMacClient(opts: {
|
|
592
|
+
key: Buffer | string;
|
|
593
|
+
transport: EffectorTransport;
|
|
594
|
+
now?: () => number;
|
|
595
|
+
}): HookMac {
|
|
596
|
+
const now = opts.now ?? Date.now;
|
|
597
|
+
return async ({ secret, alg, bytes, signature }) => {
|
|
598
|
+
const body = JSON.stringify({
|
|
599
|
+
secret,
|
|
600
|
+
alg,
|
|
601
|
+
bytes: Buffer.from(bytes).toString("base64"),
|
|
602
|
+
signature: Buffer.from(signature).toString("base64"),
|
|
603
|
+
} satisfies MacRequest);
|
|
604
|
+
const addresses = shuffle(await opts.transport.replicas().catch(notSent));
|
|
605
|
+
if (addresses.length === 0) throw new EffectorNotSent("no Effector replica is serving");
|
|
606
|
+
let last: unknown;
|
|
607
|
+
for (const address of addresses) {
|
|
608
|
+
const time = String(now());
|
|
609
|
+
const nonce = randomBytes(16).toString("hex");
|
|
610
|
+
const headers = {
|
|
611
|
+
"content-type": "application/json",
|
|
612
|
+
[HEADER_TIME]: time,
|
|
613
|
+
[HEADER_NONCE]: nonce,
|
|
614
|
+
[HEADER_SIGNATURE]: requestMac(opts.key, "POST", address, MAC_PATH, time, nonce, body),
|
|
615
|
+
};
|
|
616
|
+
let answer: RawAnswer;
|
|
617
|
+
try {
|
|
618
|
+
answer = await opts.transport.send(address, "POST", MAC_PATH, headers, body);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
if (neverReached(err)) {
|
|
621
|
+
last = err;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
const want = responseMac(opts.key, answer.status, "POST", MAC_PATH, nonce, answer.body);
|
|
627
|
+
if (!sameString(answer.headers[HEADER_SIGNATURE] ?? "", want)) {
|
|
628
|
+
throw new EffectorForged(
|
|
629
|
+
`the answer to POST ${MAC_PATH} from ${address} does not carry the Effector's signature`,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (answer.status === 503) {
|
|
633
|
+
last = new EffectorNotSent(`the Effector replica at ${address} is stopping`);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (answer.status !== 200) {
|
|
637
|
+
const refusal = RefusalShape.safeParse(parseJson(answer.body));
|
|
638
|
+
throw new Error(
|
|
639
|
+
`the Effector answered a mac check with ${answer.status}: ${refusal.success ? refusal.data.error : "no reason"}`,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const parsed = MacAnswerShape.safeParse(parseJson(answer.body));
|
|
643
|
+
if (!parsed.success) throw new EffectorMalformed("the Effector's answer to a mac check is not the wire's shape");
|
|
644
|
+
return parsed.data.match;
|
|
645
|
+
}
|
|
646
|
+
throw last instanceof EffectorNotSent
|
|
647
|
+
? last
|
|
648
|
+
: new EffectorNotSent(`no Effector replica answered: ${(last as Error | undefined)?.message ?? "none"}`);
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
404
652
|
// --- Signatures ----------------------------------------------------------------------------------
|
|
405
653
|
|
|
406
654
|
export const HEADER_TIME = "x-jr2-time";
|
package/src/fail.ts
CHANGED
|
@@ -87,24 +87,37 @@ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL
|
|
|
87
87
|
* @example
|
|
88
88
|
* ```ts
|
|
89
89
|
* import { z } from "zod";
|
|
90
|
-
* import { command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
90
|
+
* import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
|
|
91
91
|
*
|
|
92
92
|
* const runInput = z.object({ branch: z.string() });
|
|
93
|
+
* type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
94
|
+
*
|
|
95
|
+
* const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
|
|
93
96
|
*
|
|
94
97
|
* const body = jr2Setup({
|
|
95
|
-
* types: {} as { input:
|
|
98
|
+
* types: {} as { input: BodyInput; context: BodyInput },
|
|
96
99
|
* actors: {
|
|
100
|
+
* cut: worktree(),
|
|
97
101
|
* tests: command(["npm", "test"], { timeout: "15m" }),
|
|
98
102
|
* fail: fail(),
|
|
99
103
|
* },
|
|
100
104
|
* }).createMachine({
|
|
101
105
|
* id: "check",
|
|
106
|
+
* context: ({ input }) => input,
|
|
102
107
|
* on: { "workspace.lost": ".lost" },
|
|
103
|
-
* initial: "
|
|
108
|
+
* initial: "cutting",
|
|
104
109
|
* states: {
|
|
110
|
+
* cutting: {
|
|
111
|
+
* invoke: {
|
|
112
|
+
* src: "cut",
|
|
113
|
+
* input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
|
|
114
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
|
|
115
|
+
* },
|
|
116
|
+
* },
|
|
105
117
|
* testing: {
|
|
106
118
|
* invoke: {
|
|
107
119
|
* src: "tests",
|
|
120
|
+
* input: ({ context }) => ({ worktree: testTree(context) }),
|
|
108
121
|
* onDone: [
|
|
109
122
|
* {
|
|
110
123
|
* guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
|
|
@@ -117,6 +130,7 @@ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL
|
|
|
117
130
|
* // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
|
|
118
131
|
* failing: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
|
|
119
132
|
* passed: { type: "final" },
|
|
133
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
120
134
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
121
135
|
* },
|
|
122
136
|
* });
|
|
@@ -124,7 +138,6 @@ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL
|
|
|
124
138
|
* export const machine = workspace(body, {
|
|
125
139
|
* input: runInput,
|
|
126
140
|
* repos: { app: "https://github.com/you/app.git" },
|
|
127
|
-
* spec: ({ input }) => ({ branch: input.branch }),
|
|
128
141
|
* });
|
|
129
142
|
* ```
|
|
130
143
|
*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The brand of a Gate slot (ADR-0011), alone in its module: the walk (setup.ts, party.ts,
|
|
2
|
+
// effect-checks.ts) and the Chart door read it, and gate.ts imports the registration table, which
|
|
3
|
+
// imports party.ts. A leaf with no imports lets each of them ask without a cycle.
|
|
4
|
+
|
|
5
|
+
/** Is this actor logic a Gate slot (`gate({ … })`)? A duck test on the brand, as `isAgent` is. */
|
|
6
|
+
export function isGateSlotLogic(logic: unknown): boolean {
|
|
7
|
+
if (typeof logic !== "object" || logic === null) return false;
|
|
8
|
+
const parts = (logic as { gateSlot?: { delivers?: unknown } }).gateSlot;
|
|
9
|
+
return typeof parts === "object" && parts !== null && Array.isArray(parts.delivers);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Does this invoke run a Gate: the plain `gate`, or a Gate slot? */
|
|
13
|
+
export function isGateInvoke(inv: { src?: unknown } | undefined, actors: Record<string, unknown>): boolean {
|
|
14
|
+
if (inv?.src === "gate") return true;
|
|
15
|
+
return typeof inv?.src === "string" && isGateSlotLogic(actors[inv.src]);
|
|
16
|
+
}
|