@jr2/orchestrator 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/bin/server.ts +7 -4
- package/console/canvas.ts +27 -14
- package/console/components/app.ts +12 -1
- package/console/components/fleet.ts +52 -6
- package/console/components/machine-pane.ts +128 -2
- package/console/components/nav.ts +1 -1
- package/console/components/schema-form.ts +12 -4
- package/console/json-field.ts +23 -0
- package/console/main.ts +122 -39
- package/console/store.ts +321 -21
- package/console/style.css +111 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +442 -0
- package/docs/concepts/architecture.md +230 -0
- package/docs/concepts/charts.md +143 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +254 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +603 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +289 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +222 -0
- package/docs/concepts/the-menu.md +378 -0
- package/docs/concepts/workspaces.md +678 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
- package/docs/get-started/install.md +126 -0
- package/docs/guides/add-tools-to-the-sandbox.md +295 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +223 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +226 -0
- package/docs/guides/look-inside-a-sandbox.md +174 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +486 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +544 -0
- package/docs/guides/run-one-worker-per-item.md +321 -0
- package/docs/guides/set-an-agents-access-and-dials.md +375 -0
- package/docs/guides/size-a-workspace.md +204 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +208 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +326 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +216 -0
- package/docs/reference/authoring-surface/agent.md +128 -0
- package/docs/reference/authoring-surface/answer.md +140 -0
- package/docs/reference/authoring-surface/arg.md +60 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +191 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +83 -0
- package/docs/reference/authoring-surface/file.md +122 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +159 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +74 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +125 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +112 -0
- package/docs/reference/authoring-surface/secret.md +118 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +12 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-binding.md +62 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +19 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
- package/docs/reference/authoring-surface/type-customize.md +109 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +41 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +15 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +32 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +94 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
- package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
- package/docs/reference/authoring-surface/workspace.md +259 -0
- package/docs/reference/authoring-surface.md +197 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +756 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions.md +83 -0
- package/docs/reference/glossary.md +648 -0
- package/docs/reference/http-api.md +1348 -0
- package/docs/reference/machines.md +190 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +206 -0
- package/package.json +6 -4
- package/src/actor.ts +976 -61
- package/src/agent.ts +114 -38
- package/src/ambient.ts +4 -1
- package/src/assembly-checks.ts +347 -0
- package/src/assembly-tar.ts +494 -0
- package/src/assembly.ts +721 -0
- package/src/cause.ts +234 -0
- package/src/chart.ts +2182 -0
- package/src/charts.ts +748 -0
- package/src/config.ts +520 -162
- package/src/content-hash.ts +105 -0
- package/src/custodian.lua +6 -5
- package/src/custodian.ts +143 -39
- package/src/customize.ts +388 -55
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +557 -0
- package/src/effect.ts +1202 -0
- package/src/effector-wire.ts +674 -0
- package/src/fail.ts +209 -0
- package/src/fingerprint.ts +4 -3
- package/src/gate.ts +8 -0
- package/src/git-clone.ts +65 -0
- package/src/git-push.ts +336 -0
- package/src/harness-client.ts +764 -48
- package/src/held-secrets.ts +82 -26
- package/src/holds.ts +156 -0
- package/src/http-effect.ts +1423 -0
- package/src/http.ts +916 -113
- package/src/images.ts +47 -21
- package/src/index.ts +62 -43
- package/src/instance.ts +505 -59
- package/src/internal.ts +84 -0
- package/src/kube-client.ts +333 -0
- package/src/machine-doc.ts +106 -6
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/mount-digest.ts +53 -0
- package/src/names.ts +66 -8
- package/src/notices.ts +141 -0
- package/src/open-body.ts +1580 -0
- package/src/open-effect.ts +217 -0
- package/src/open.ts +59 -6
- package/src/parts.ts +709 -41
- package/src/party.ts +534 -0
- package/src/pick.ts +761 -0
- package/src/pool.ts +248 -37
- package/src/registration.ts +382 -19
- package/src/repo-fetch.ts +60 -85
- package/src/repos.ts +65 -75
- package/src/rest.ts +133 -0
- package/src/run-host.ts +2383 -115
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +1126 -0
- package/src/sandbox-effector.ts +111 -0
- package/src/sandbox-kube.ts +1721 -0
- package/src/sandbox-watch.ts +260 -0
- package/src/secret-placement.ts +529 -0
- package/src/server.ts +284 -84
- package/src/setup.ts +819 -117
- package/src/size.ts +454 -0
- package/src/snapshot-store.ts +115 -20
- package/src/stub-harness.ts +49 -16
- package/src/sync-client.ts +54 -0
- package/src/timers.ts +191 -0
- package/src/tokens.ts +35 -4
- package/src/vocabulary.ts +135 -15
- package/src/wire.ts +238 -10
- package/src/workspace.ts +1195 -338
- package/src/sandbox-kubectl.ts +0 -1108
package/src/pick.ts
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
// What an author writes between a pick and the work it starts (ADR-0067). All three are optional:
|
|
2
|
+
// plain xstate — an input mapper, no answer — is legal.
|
|
3
|
+
//
|
|
4
|
+
// - `fromPick(def, fill)` — the `input` of a state a pick enters. The pick's params flow into the
|
|
5
|
+
// invoked actor's input BY NAME; `fill` returns only what the MACHINE adds. So the security
|
|
6
|
+
// boundary reads in one expression: the def is what the Agent supplies, `fill` is what the
|
|
7
|
+
// Machine adds, and the sum is checked against the invoked actor's input type — for a missing or
|
|
8
|
+
// mistyped key only: TypeScript accepts an extra key on a spread. The load walk refuses the rest
|
|
9
|
+
// (effect-checks.ts): a param that is no key of an Effect's input and that `fill` does not read,
|
|
10
|
+
// and a param `fill` overwrites.
|
|
11
|
+
// - `paramsOf(event, def)` — the same params, for a plain mapper that wants them.
|
|
12
|
+
// - `answer(fn)` — an xstate action that sets what the Agent reads when its pick is answered
|
|
13
|
+
// (ADR-0024). Absent, it reads the last reply since the pick. `fn` gets `{ context, event, chain }`:
|
|
14
|
+
// `chain` is every reply since the pick, oldest first, each typed by its slot and its kind. Its
|
|
15
|
+
// order and length are run-time facts, not typed. Last call wins. It runs where xstate runs it —
|
|
16
|
+
// a `rest`-entry `answer` runs only when a route enters `rest` again.
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
ActionFunction,
|
|
20
|
+
AnyActorRef,
|
|
21
|
+
AnyMachineSnapshot,
|
|
22
|
+
AnyStateMachine,
|
|
23
|
+
EventObject,
|
|
24
|
+
MachineContext,
|
|
25
|
+
OutputFrom,
|
|
26
|
+
ParameterizedObject,
|
|
27
|
+
ProvidedActor,
|
|
28
|
+
} from "xstate";
|
|
29
|
+
import { enqueueActions } from "xstate";
|
|
30
|
+
import type { z } from "zod";
|
|
31
|
+
import { SENDER_KEY, type EventDef, type Sender } from "@jr2/agent-protocol";
|
|
32
|
+
import { isAgent } from "./agent.ts";
|
|
33
|
+
import { boundRun } from "./registration.ts";
|
|
34
|
+
import type { PickAnswer } from "./wire.ts";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read the params of a pick from the event that entered the state. Use it in a plain `input`
|
|
38
|
+
* mapper when the Machine must change a param before the invoked actor gets it. When the params go
|
|
39
|
+
* to the input as they are, use `fromPick`.
|
|
40
|
+
*
|
|
41
|
+
* It parses the params with the `input` schema of the pick. It refuses an event that is not this
|
|
42
|
+
* pick: `state entered by "<event>", but its input reads the params of pick "<pick>"`.
|
|
43
|
+
*
|
|
44
|
+
* `createMachine` calls each mapper one time to see which params it reads. When the mapper throws
|
|
45
|
+
* there, `jr2 up` prints a warning before a run starts: `pick "<pick>" enters state "<state>"
|
|
46
|
+
* (Effect "<slot>"): could not probe the input mapper (<error>); its params are unchecked`.
|
|
47
|
+
*
|
|
48
|
+
* `createMachine` refuses a param of a pick that enters a state that invokes an Effect, when
|
|
49
|
+
* nothing reads the param: `machine "<id>": pick "<pick>" enters state "<state>" (Effect
|
|
50
|
+
* "<slot>"), which never reads "<param>": the Agent supplies it and nothing uses it — map it in an
|
|
51
|
+
* invoke's input, store it with assign, or drop it from the def`.
|
|
52
|
+
*
|
|
53
|
+
* @param event - The event that entered the state: the `event` that the mapper gets.
|
|
54
|
+
* @param def - The pick, as `defineEvent` made it.
|
|
55
|
+
* @returns The params of the pick, typed by its `input` schema.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { z } from "zod";
|
|
60
|
+
* import {
|
|
61
|
+
* agent,
|
|
62
|
+
* arg,
|
|
63
|
+
* command,
|
|
64
|
+
* defineEvent,
|
|
65
|
+
* fail,
|
|
66
|
+
* jr2Setup,
|
|
67
|
+
* paramsOf,
|
|
68
|
+
* workspace,
|
|
69
|
+
* type Workspaced,
|
|
70
|
+
* } from "@jr2/orchestrator";
|
|
71
|
+
*
|
|
72
|
+
* const runTest = defineEvent({
|
|
73
|
+
* name: "run_test",
|
|
74
|
+
* description: "Run the tests of one module.",
|
|
75
|
+
* input: z.object({ module: z.string() }),
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* const finished = defineEvent({
|
|
79
|
+
* name: "finished",
|
|
80
|
+
* description: "Say that all the tests pass.",
|
|
81
|
+
* input: z.object({}),
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* type Input = Workspaced<{}, "app">;
|
|
85
|
+
*
|
|
86
|
+
* const body = jr2Setup({
|
|
87
|
+
* types: {} as { context: Input; input: Input },
|
|
88
|
+
* events: [runTest, finished],
|
|
89
|
+
* actors: {
|
|
90
|
+
* coder: agent({
|
|
91
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
92
|
+
* instructions: "You are a careful engineer.",
|
|
93
|
+
* }),
|
|
94
|
+
* test: command(["node", "--test", arg("file")]),
|
|
95
|
+
* fail: fail(),
|
|
96
|
+
* },
|
|
97
|
+
* }).createMachine({
|
|
98
|
+
* id: "fix-tests",
|
|
99
|
+
* context: ({ input }) => input,
|
|
100
|
+
* on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
101
|
+
* initial: "coding",
|
|
102
|
+
* states: {
|
|
103
|
+
* coding: {
|
|
104
|
+
* invoke: {
|
|
105
|
+
* src: "coder",
|
|
106
|
+
* input: { prompt: "Make the tests pass. Run the tests of one module at a time." },
|
|
107
|
+
* },
|
|
108
|
+
* on: { finished: "done" },
|
|
109
|
+
* initial: "ready",
|
|
110
|
+
* states: {
|
|
111
|
+
* ready: { on: { run_test: "testing" } },
|
|
112
|
+
* testing: {
|
|
113
|
+
* invoke: {
|
|
114
|
+
* src: "test",
|
|
115
|
+
* // A plain mapper: the Machine makes the file name from the pick's `module`.
|
|
116
|
+
* input: ({ event }) => ({ file: `test/${paramsOf(event, runTest).module}.test.js` }),
|
|
117
|
+
* onDone: "ready",
|
|
118
|
+
* },
|
|
119
|
+
* },
|
|
120
|
+
* },
|
|
121
|
+
* },
|
|
122
|
+
* done: { type: "final" },
|
|
123
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
124
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
125
|
+
* },
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* export const machine = workspace(body, {
|
|
129
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
130
|
+
* spec: () => ({ branch: "fix-tests" }),
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @see docs/concepts/effects.md
|
|
135
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
136
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
137
|
+
*/
|
|
138
|
+
export function paramsOf<N extends string, I extends z.ZodObject>(event: EventObject, def: EventDef<N, I>): z.infer<I> {
|
|
139
|
+
// Throws on a jr2 or author defect, which the load walk (effect-checks.ts) finds first: it probes
|
|
140
|
+
// the mapper before the Machine runs.
|
|
141
|
+
if (event.type !== def.name) {
|
|
142
|
+
throw new Error(`state entered by "${event.type}", but its input reads the params of pick "${def.name}"`);
|
|
143
|
+
}
|
|
144
|
+
const { type: _type, [SENDER_KEY]: _sender, ...payload } = event as EventObject & Record<string, unknown>;
|
|
145
|
+
return def.input.parse(payload);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The brand `fromPick` puts on the mapper it returns: the pick whose params it reads, and the
|
|
149
|
+
* `fill` the load walk probes. */
|
|
150
|
+
export type PickMapper = {
|
|
151
|
+
readonly pick: EventDef;
|
|
152
|
+
readonly fill?: (args: { context: unknown; params: unknown }) => unknown;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Is this invoke input a `fromPick` mapper? */
|
|
156
|
+
export function isPickMapper(value: unknown): value is PickMapper & ((args: unknown) => unknown) {
|
|
157
|
+
return typeof value === "function" && "pick" in value;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Make the `input` of the invoke of a state that a pick enters: the params of the pick go to the
|
|
162
|
+
* input of the invoked actor by name. With `fill`, the Machine adds the keys that the Agent must
|
|
163
|
+
* not choose. So one expression shows the security boundary: the pick is what the Agent gives, and
|
|
164
|
+
* `fill` is what the Machine adds.
|
|
165
|
+
*
|
|
166
|
+
* TypeScript checks the params and the keys of `fill` together against the input type of the
|
|
167
|
+
* invoked actor. It finds a missing key or a key of the wrong type, but it accepts an extra key.
|
|
168
|
+
*
|
|
169
|
+
* So `createMachine` refuses:
|
|
170
|
+
* - a param that is no key of the input of the Effect, and that nothing else reads: ``machine
|
|
171
|
+
* "<id>": pick "<pick>" enters state "<state>" (Effect "<slot>") with fromPick: "<param>" is no
|
|
172
|
+
* key of the Effect's input (<keys>), and nothing else reads it — the Agent supplies it and it
|
|
173
|
+
* reaches nothing; name an argv slot for it, read it in `fill`, store it with assign, or drop it
|
|
174
|
+
* from the def``
|
|
175
|
+
* - a key that `fill` sets and that is also a param: ``machine "<id>": pick "<pick>" enters state
|
|
176
|
+
* "<state>" (Effect "<slot>"): `fill` sets "<param>", which is also a param of the pick — the
|
|
177
|
+
* Machine's value wins and the Agent's is dropped; rename one, or drop it from the def``
|
|
178
|
+
* - a `fromPick` in a state that another event enters: `machine "<id>": state "<state>" (Effect
|
|
179
|
+
* "<slot>") reads the params of pick "<pick>" but is entered on "<event>" — that event does not
|
|
180
|
+
* carry them`
|
|
181
|
+
*
|
|
182
|
+
* The input of an Open argv is one string for each key, so TypeScript checks no key for it. A key
|
|
183
|
+
* that the bound argv names and the input does not give stops the Effect when it starts.
|
|
184
|
+
*
|
|
185
|
+
* The state that the pick enters has one of two places:
|
|
186
|
+
* - A child state of the Agent's state, as in the first example. The Turn stays open, and the
|
|
187
|
+
* pick is outstanding until the Effect ends. Use this shape when the Agent must see the result.
|
|
188
|
+
* The Agent reads the Effect's output as the answer only when the route after the Effect stays
|
|
189
|
+
* in the Agent's state, as `onDone: "ready"` does. Then the Agent can pick again. A route that
|
|
190
|
+
* leaves the Agent's state ends the Turn when the Effect ends, and no Agent reads the output.
|
|
191
|
+
* - A sibling state of the Agent's state, as in the second example. The pick leaves the Agent's
|
|
192
|
+
* state, so the Turn ends before the Effect runs, and the Agent does not see the output. Use
|
|
193
|
+
* this shape for a pick that ends the work of the Agent.
|
|
194
|
+
*
|
|
195
|
+
* @param def - The pick, as `defineEvent` made it.
|
|
196
|
+
* @returns An `input` mapper for the `invoke` of the state.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* import { z } from "zod";
|
|
201
|
+
* import { agent, defineEvent, fail, fromPick, http, jr2Setup } from "@jr2/orchestrator";
|
|
202
|
+
*
|
|
203
|
+
* const postNote = defineEvent({
|
|
204
|
+
* name: "post_note",
|
|
205
|
+
* description: "Post a note to the team channel.",
|
|
206
|
+
* input: z.object({ text: z.string() }),
|
|
207
|
+
* });
|
|
208
|
+
*
|
|
209
|
+
* const finish = defineEvent({
|
|
210
|
+
* name: "finish",
|
|
211
|
+
* description: "The note is posted. This ends your work.",
|
|
212
|
+
* input: z.object({}),
|
|
213
|
+
* });
|
|
214
|
+
*
|
|
215
|
+
* export const machine = jr2Setup({
|
|
216
|
+
* types: {} as { context: {}; input: {} },
|
|
217
|
+
* events: [postNote, finish],
|
|
218
|
+
* actors: {
|
|
219
|
+
* writer: agent({
|
|
220
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
221
|
+
* instructions: "You write one short note for the team.",
|
|
222
|
+
* workspace: "none",
|
|
223
|
+
* }),
|
|
224
|
+
* note: http({
|
|
225
|
+
* method: "POST",
|
|
226
|
+
* url: "https://hooks.example.com/notes",
|
|
227
|
+
* input: z.object({ text: z.string() }),
|
|
228
|
+
* request: ({ text }) => ({ json: { text } }),
|
|
229
|
+
* }),
|
|
230
|
+
* fail: fail(),
|
|
231
|
+
* },
|
|
232
|
+
* }).createMachine({
|
|
233
|
+
* id: "note",
|
|
234
|
+
* on: { "agent.fault": ".failed" },
|
|
235
|
+
* initial: "writing",
|
|
236
|
+
* states: {
|
|
237
|
+
* writing: {
|
|
238
|
+
* invoke: {
|
|
239
|
+
* src: "writer",
|
|
240
|
+
* input: {
|
|
241
|
+
* prompt: "Write a note that says the release is out, post it, and check the reply.",
|
|
242
|
+
* },
|
|
243
|
+
* },
|
|
244
|
+
* initial: "ready",
|
|
245
|
+
* states: {
|
|
246
|
+
* ready: { on: { post_note: "posting", finish: "#done" } },
|
|
247
|
+
* posting: {
|
|
248
|
+
* // The `text` of the pick goes to the input of `note` as it is. The route goes back
|
|
249
|
+
* // to `ready`, in `writing`: the writer reads the output and picks again.
|
|
250
|
+
* invoke: { src: "note", input: fromPick(postNote), onDone: "ready" },
|
|
251
|
+
* },
|
|
252
|
+
* },
|
|
253
|
+
* },
|
|
254
|
+
* done: { id: "done", type: "final" },
|
|
255
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
256
|
+
* },
|
|
257
|
+
* });
|
|
258
|
+
* ```
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { z } from "zod";
|
|
263
|
+
* import { agent, defineEvent, fail, fromPick, http, jr2Setup } from "@jr2/orchestrator";
|
|
264
|
+
*
|
|
265
|
+
* const postNote = defineEvent({
|
|
266
|
+
* name: "post_note",
|
|
267
|
+
* description: "Post the note to the team channel. This ends your work.",
|
|
268
|
+
* input: z.object({ text: z.string() }),
|
|
269
|
+
* });
|
|
270
|
+
*
|
|
271
|
+
* export const machine = jr2Setup({
|
|
272
|
+
* types: {} as { context: {}; input: {} },
|
|
273
|
+
* events: [postNote],
|
|
274
|
+
* actors: {
|
|
275
|
+
* writer: agent({
|
|
276
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
277
|
+
* instructions: "You write one short note for the team.",
|
|
278
|
+
* workspace: "none",
|
|
279
|
+
* }),
|
|
280
|
+
* note: http({
|
|
281
|
+
* method: "POST",
|
|
282
|
+
* url: "https://hooks.example.com/notes",
|
|
283
|
+
* input: z.object({ text: z.string() }),
|
|
284
|
+
* request: ({ text }) => ({ json: { text } }),
|
|
285
|
+
* }),
|
|
286
|
+
* fail: fail(),
|
|
287
|
+
* },
|
|
288
|
+
* }).createMachine({
|
|
289
|
+
* id: "note",
|
|
290
|
+
* on: { "agent.fault": ".failed" },
|
|
291
|
+
* initial: "writing",
|
|
292
|
+
* states: {
|
|
293
|
+
* // The pick leaves this state, so it ends the Turn of the writer.
|
|
294
|
+
* writing: {
|
|
295
|
+
* invoke: {
|
|
296
|
+
* src: "writer",
|
|
297
|
+
* input: { prompt: "Write a note that says the release is out, and post it." },
|
|
298
|
+
* },
|
|
299
|
+
* on: { post_note: "posting" },
|
|
300
|
+
* },
|
|
301
|
+
* // A sibling of `writing`: the Effect runs after the Turn, and no Agent reads its output.
|
|
302
|
+
* posting: {
|
|
303
|
+
* invoke: { src: "note", input: fromPick(postNote), onDone: "done" },
|
|
304
|
+
* },
|
|
305
|
+
* done: { type: "final" },
|
|
306
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
307
|
+
* },
|
|
308
|
+
* });
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* @see docs/concepts/effects.md
|
|
312
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
313
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
314
|
+
*/
|
|
315
|
+
export function fromPick<N extends string, I extends z.ZodObject, C, E extends EventObject>(
|
|
316
|
+
def: EventDef<N, I>,
|
|
317
|
+
): (args: { context: C; event: E; self: AnyActorRef }) => z.infer<I>;
|
|
318
|
+
/**
|
|
319
|
+
* Make the `input` from the params of the pick and the keys that `fill` adds. {@link fromPick}
|
|
320
|
+
* tells the rest, and what `createMachine` refuses.
|
|
321
|
+
*
|
|
322
|
+
* @param def - The pick, as `defineEvent` made it.
|
|
323
|
+
* @param fill - Gets `{ context, params }` and gives back only the keys that the Machine adds. Do
|
|
324
|
+
* not set a key that is also a param.
|
|
325
|
+
* @returns An `input` mapper for the `invoke` of the state.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* import { z } from "zod";
|
|
330
|
+
* import { agent, bearer, defineEvent, fail, fromPick, http, jr2Setup, secret } from "@jr2/orchestrator";
|
|
331
|
+
*
|
|
332
|
+
* const postComment = defineEvent({
|
|
333
|
+
* name: "post_comment",
|
|
334
|
+
* description: "Post a comment on the ticket.",
|
|
335
|
+
* input: z.object({ body: z.string() }),
|
|
336
|
+
* });
|
|
337
|
+
*
|
|
338
|
+
* export const machine = jr2Setup({
|
|
339
|
+
* types: {} as { context: { ticket: string }; input: { ticket: string } },
|
|
340
|
+
* events: [postComment],
|
|
341
|
+
* actors: {
|
|
342
|
+
* triager: agent({
|
|
343
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
344
|
+
* instructions: "You triage tickets.",
|
|
345
|
+
* workspace: "none",
|
|
346
|
+
* }),
|
|
347
|
+
* comment: http({
|
|
348
|
+
* method: "POST",
|
|
349
|
+
* url: "https://jira.example.com/rest/api/2/issue",
|
|
350
|
+
* auth: bearer(secret("jira")),
|
|
351
|
+
* input: z.object({ ticket: z.string(), body: z.string() }),
|
|
352
|
+
* request: ({ ticket, body }) => ({ path: [ticket, "comment"], json: { body } }),
|
|
353
|
+
* }),
|
|
354
|
+
* fail: fail(),
|
|
355
|
+
* },
|
|
356
|
+
* }).createMachine({
|
|
357
|
+
* id: "triage",
|
|
358
|
+
* context: ({ input }) => input,
|
|
359
|
+
* on: { "agent.fault": ".failed" },
|
|
360
|
+
* initial: "triaging",
|
|
361
|
+
* states: {
|
|
362
|
+
* triaging: {
|
|
363
|
+
* invoke: {
|
|
364
|
+
* src: "triager",
|
|
365
|
+
* input: ({ context }) => ({
|
|
366
|
+
* prompt: `Read ticket ${context.ticket}, then post one comment on it.`,
|
|
367
|
+
* }),
|
|
368
|
+
* },
|
|
369
|
+
* initial: "ready",
|
|
370
|
+
* states: {
|
|
371
|
+
* ready: { on: { post_comment: "posting" } },
|
|
372
|
+
* posting: {
|
|
373
|
+
* invoke: {
|
|
374
|
+
* src: "comment",
|
|
375
|
+
* // The Agent gives `body`. The Machine gives `ticket`, so the Agent cannot post on
|
|
376
|
+
* // another ticket.
|
|
377
|
+
* input: fromPick(postComment, ({ context }) => ({ ticket: context.ticket })),
|
|
378
|
+
* onDone: "#done",
|
|
379
|
+
* },
|
|
380
|
+
* },
|
|
381
|
+
* },
|
|
382
|
+
* },
|
|
383
|
+
* done: { id: "done", type: "final" },
|
|
384
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
385
|
+
* },
|
|
386
|
+
* });
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @see docs/concepts/effects.md
|
|
390
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
391
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
392
|
+
*/
|
|
393
|
+
export function fromPick<N extends string, I extends z.ZodObject, C, E extends EventObject, R extends object>(
|
|
394
|
+
def: EventDef<N, I>,
|
|
395
|
+
fill: (args: { context: C; params: z.infer<I> }) => R,
|
|
396
|
+
): (args: { context: C; event: E; self: AnyActorRef }) => z.infer<I> & R;
|
|
397
|
+
export function fromPick<N extends string, I extends z.ZodObject, C, E extends EventObject, R extends object>(
|
|
398
|
+
def: EventDef<N, I>,
|
|
399
|
+
fill?: (args: { context: C; params: z.infer<I> }) => R,
|
|
400
|
+
) {
|
|
401
|
+
const mapper = ({ context, event }: { context: C; event: E; self: AnyActorRef }) => {
|
|
402
|
+
const params = paramsOf(event, def);
|
|
403
|
+
return fill ? { ...params, ...fill({ context, params }) } : params;
|
|
404
|
+
};
|
|
405
|
+
return Object.assign(mapper, { pick: def, ...(fill ? { fill } : {}) });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* One reply since the pick, as the author reads it: an invoked actor's output, typed by its slot
|
|
410
|
+
* (`src`); or an event that answered a state below the Agent's — a Gate's answer, a nested Agent's
|
|
411
|
+
* pick — typed by the Machine's events. A reply is from outside the Machine: the events it sends
|
|
412
|
+
* itself are not links.
|
|
413
|
+
*/
|
|
414
|
+
export type ChainLink<TActor extends ProvidedActor, TEvent extends EventObject> =
|
|
415
|
+
| (TActor extends unknown ? { kind: "output"; src: TActor["src"]; output: OutputFrom<TActor["logic"]> } : never)
|
|
416
|
+
| { kind: "event"; event: Exclude<TEvent, { type: `${string}.${string}` }> };
|
|
417
|
+
|
|
418
|
+
/** The chain as the author reads it, from the replies the run host recorded. */
|
|
419
|
+
function linksOf(chain: readonly PickAnswer[]): unknown[] {
|
|
420
|
+
return chain.flatMap((link): unknown[] => {
|
|
421
|
+
if (link.kind === "output") return [{ kind: "output", src: link.actor, output: link.output }];
|
|
422
|
+
if (link.kind === "event") {
|
|
423
|
+
const sender: Sender | undefined = link.sender;
|
|
424
|
+
return [
|
|
425
|
+
{ kind: "event", event: { type: link.event, ...link.payload, ...(sender ? { [SENDER_KEY]: sender } : {}) } },
|
|
426
|
+
];
|
|
427
|
+
}
|
|
428
|
+
return [];
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
type Node = AnyStateMachine["root"];
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Where an `answer` is written, as the Agents' states it is below: for each place the action is
|
|
436
|
+
* written (a state's entry or exit, a transition on a state, a state's initial transition), the
|
|
437
|
+
* nearest state at or above it that invokes an Agent — `null` for a transition where none does; an
|
|
438
|
+
* entry or exit below no Agent's state seats nowhere. Read once per Machine, by the action's
|
|
439
|
+
* identity: one `answer` object written in two places carries both. At run time each place runs as
|
|
440
|
+
* its own seat (`placeAnswers`): an `answer` cannot tell where it was written, so the place is
|
|
441
|
+
* decided before the Machine runs.
|
|
442
|
+
*/
|
|
443
|
+
export type AnswerSeats = ReadonlySet<Node | null>;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* The one place an `answer` runs from (ADR-0067): the Agent's state it is written below, or `null`
|
|
447
|
+
* on a transition below no Agent's state. At a state, it shapes only a pick of the Agent seated
|
|
448
|
+
* there. At `null`, the event that took the transition names the Agent whose pick it shapes (the
|
|
449
|
+
* run host reads it off the event).
|
|
450
|
+
*/
|
|
451
|
+
export type AnswerSeat = Node | null;
|
|
452
|
+
|
|
453
|
+
const seatsCache = new WeakMap<AnyStateMachine, Map<unknown, Set<Node | null>>>();
|
|
454
|
+
|
|
455
|
+
/** How an `answer` makes the copy of itself that runs from one seat. */
|
|
456
|
+
const PLACE = Symbol("jr2.answer.place");
|
|
457
|
+
type Placeable = { [PLACE]: (seat: AnswerSeat) => unknown; readonly jr2Answer: true };
|
|
458
|
+
|
|
459
|
+
/** An action of `machine` as it is written — inline, or by name — resolved to its implementation.
|
|
460
|
+
*/
|
|
461
|
+
function resolverOf(machine: AnyStateMachine): (a: unknown) => unknown {
|
|
462
|
+
const named = (machine.implementations?.actions ?? {}) as Record<string, unknown>;
|
|
463
|
+
return (a) => {
|
|
464
|
+
const type = typeof a === "string" ? a : typeof a === "object" && a !== null ? (a as { type?: unknown }).type : a;
|
|
465
|
+
return typeof type === "string" ? named[type] : a;
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** The nearest state at or above `n` that invokes one of `machine`'s Agents, or `null`. */
|
|
470
|
+
function seatFinderOf(machine: AnyStateMachine): (n: Node) => Node | null {
|
|
471
|
+
const actors = (machine.implementations?.actors ?? {}) as Record<string, unknown>;
|
|
472
|
+
return (n) => {
|
|
473
|
+
for (let p: Node | undefined = n; p; p = p.parent)
|
|
474
|
+
if (p.invoke.some((inv) => typeof inv.src === "string" && isAgent(actors[inv.src]))) return p;
|
|
475
|
+
return null;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** The `answer` a placed copy was made from: the identity `seatsOfAnswer` groups places by. */
|
|
480
|
+
const originals = new WeakMap<object, unknown>();
|
|
481
|
+
const originalOf = (fn: unknown): unknown => (typeof fn === "function" ? (originals.get(fn) ?? fn) : fn);
|
|
482
|
+
|
|
483
|
+
/** What stands where `original` was written on an entry or an exit below no Agent's state: an
|
|
484
|
+
* `answer` that runs nothing. Still an `answer` to the load walk, which warns on it. */
|
|
485
|
+
function unseatedAnswer(original: unknown): AnswerAction & (() => void) {
|
|
486
|
+
const copy = Object.assign(function unseatedAnswer(): void {}, { jr2Answer: true as const });
|
|
487
|
+
originals.set(copy, original);
|
|
488
|
+
return copy;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const placedMachines = new WeakSet<AnyStateMachine>();
|
|
492
|
+
|
|
493
|
+
/** xstate's `enqueueActions` as it is written: its `collect` runs at the place the action stands,
|
|
494
|
+
* and the actions it enqueues run there too. */
|
|
495
|
+
type Enqueuing = { type: "xstate.enqueueActions"; collect: (args: EnqueueArgs, params: unknown) => void };
|
|
496
|
+
type EnqueueArgs = { context: unknown; event: unknown; enqueue: ((action: unknown) => void) & object };
|
|
497
|
+
const isEnqueuing = (action: unknown): action is Enqueuing =>
|
|
498
|
+
typeof action === "function" &&
|
|
499
|
+
(action as Partial<Enqueuing>).type === "xstate.enqueueActions" &&
|
|
500
|
+
typeof (action as Partial<Enqueuing>).collect === "function";
|
|
501
|
+
|
|
502
|
+
/** The params an action written as `{ type, params }` is run with, as xstate reads them. */
|
|
503
|
+
function paramsAt(written: unknown, args: EnqueueArgs): unknown {
|
|
504
|
+
if (typeof written !== "object" || written === null || !("params" in written)) return undefined;
|
|
505
|
+
const params = (written as { params?: unknown }).params;
|
|
506
|
+
return typeof params === "function" ? params({ context: args.context, event: args.event }) : params;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Give every place an `answer` is written in `machine` a copy of its own, bound to that place's
|
|
511
|
+
* seat (ADR-0067): one action object written in two places then shapes, from each, by that place's
|
|
512
|
+
* rule alone — below an Agent's state, only that Agent's pick; on a transition below none, the pick
|
|
513
|
+
* of the Agent the event names. An entry or an exit below no Agent's state is given an `answer`
|
|
514
|
+
* that sets nothing. At run time an action cannot tell where it was written, so the place is
|
|
515
|
+
* decided here. Called for each Machine of a run's tree as its actor is created, before its first
|
|
516
|
+
* state is entered. Each list is replaced, never written in place: a list the author shared between
|
|
517
|
+
* two places stays the author's.
|
|
518
|
+
*/
|
|
519
|
+
export function placeAnswers(machine: AnyStateMachine): void {
|
|
520
|
+
if (placedMachines.has(machine) || !machine?.root) return;
|
|
521
|
+
placedMachines.add(machine);
|
|
522
|
+
const resolve = resolverOf(machine);
|
|
523
|
+
const seatAbove = seatFinderOf(machine);
|
|
524
|
+
const placeOne = (a: unknown, seat: AnswerSeat | "nowhere"): unknown => {
|
|
525
|
+
const fn = resolve(a);
|
|
526
|
+
// An `enqueueActions` runs what it enqueues at its own place: each `answer` it enqueues, at any
|
|
527
|
+
// depth, runs from the seat the enqueuing action is written at (ADR-0067). The load walk cannot
|
|
528
|
+
// see inside one, so the copy places what it enqueues as it is enqueued.
|
|
529
|
+
if (isEnqueuing(fn)) {
|
|
530
|
+
const collect = fn.collect;
|
|
531
|
+
return enqueueActions(((args: EnqueueArgs) => {
|
|
532
|
+
const enqueue = Object.assign((action: unknown) => args.enqueue(placeOne(action, seat)), args.enqueue);
|
|
533
|
+
collect({ ...args, enqueue }, paramsAt(a, args));
|
|
534
|
+
}) as never);
|
|
535
|
+
}
|
|
536
|
+
if (!isAnswer(fn)) return a;
|
|
537
|
+
if (seat === "nowhere") return unseatedAnswer(originalOf(fn));
|
|
538
|
+
const original = originalOf(fn) as Partial<Placeable>;
|
|
539
|
+
return typeof original[PLACE] === "function" ? original[PLACE](seat) : fn;
|
|
540
|
+
};
|
|
541
|
+
const placed = (list: readonly unknown[], seat: AnswerSeat | "nowhere"): unknown[] =>
|
|
542
|
+
list.map((a) => placeOne(a, seat));
|
|
543
|
+
const visit = (n: Node): void => {
|
|
544
|
+
const seat = seatAbove(n);
|
|
545
|
+
const writable = n as unknown as { entry: unknown[]; exit: unknown[] };
|
|
546
|
+
writable.entry = placed(n.entry, seat ?? "nowhere");
|
|
547
|
+
writable.exit = placed(n.exit, seat ?? "nowhere");
|
|
548
|
+
const transitions = [
|
|
549
|
+
...[...n.transitions.values()].flat(),
|
|
550
|
+
...(n.always ?? []),
|
|
551
|
+
...(n.initial ? [n.initial] : []),
|
|
552
|
+
] as unknown as { actions: readonly unknown[] }[];
|
|
553
|
+
for (const t of new Set(transitions)) t.actions = placed(t.actions, seat);
|
|
554
|
+
for (const child of Object.values(n.states)) visit(child);
|
|
555
|
+
};
|
|
556
|
+
visit(machine.root);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export function seatsOfAnswer(machine: AnyStateMachine, action: unknown): AnswerSeats {
|
|
560
|
+
let byAction = seatsCache.get(machine);
|
|
561
|
+
if (byAction === undefined) {
|
|
562
|
+
byAction = new Map();
|
|
563
|
+
seatsCache.set(machine, byAction);
|
|
564
|
+
const resolve = resolverOf(machine);
|
|
565
|
+
const seatAbove = seatFinderOf(machine);
|
|
566
|
+
const visit = (n: Node): void => {
|
|
567
|
+
const seat = seatAbove(n);
|
|
568
|
+
const onEntryOrExit = [...n.entry, ...n.exit];
|
|
569
|
+
const onTransition = [
|
|
570
|
+
...[...n.transitions.values()].flat().flatMap((t) => t.actions),
|
|
571
|
+
...(n.always ?? []).flatMap((t) => t.actions),
|
|
572
|
+
...(n.initial?.actions ?? []),
|
|
573
|
+
];
|
|
574
|
+
const note = (a: unknown, seated: boolean): void => {
|
|
575
|
+
const fn = resolve(a);
|
|
576
|
+
if (!isAnswer(fn)) return;
|
|
577
|
+
const key = originalOf(fn);
|
|
578
|
+
const seats = byAction!.get(key) ?? new Set<Node | null>();
|
|
579
|
+
if (seated) seats.add(seat);
|
|
580
|
+
byAction!.set(key, seats);
|
|
581
|
+
};
|
|
582
|
+
// An entry or exit below no Agent's state is no transition an Agent's event takes: it seats
|
|
583
|
+
// nowhere, and runs nothing (`placeAnswers`, ADR-0067).
|
|
584
|
+
for (const a of onEntryOrExit) note(a, seat !== null);
|
|
585
|
+
for (const a of onTransition) note(a, true);
|
|
586
|
+
for (const child of Object.values(n.states)) visit(child);
|
|
587
|
+
};
|
|
588
|
+
if (machine?.root) visit(machine.root);
|
|
589
|
+
}
|
|
590
|
+
return byAction.get(originalOf(action)) ?? new Set<Node | null>([null]);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The brand the load walk reads: this action is an `answer`.
|
|
595
|
+
*
|
|
596
|
+
* @internal
|
|
597
|
+
*/
|
|
598
|
+
export type AnswerAction = { readonly jr2Answer: true };
|
|
599
|
+
|
|
600
|
+
/** Is this action an `answer`? */
|
|
601
|
+
export const isAnswer = (action: unknown): boolean =>
|
|
602
|
+
typeof action === "function" && (action as Partial<AnswerAction>).jr2Answer === true;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Set what the Agent reads when jr2 answers its pick. It is an xstate action: write it in
|
|
606
|
+
* `entry`, `exit` or the `actions` of a transition. Without it, the Agent reads the last reply
|
|
607
|
+
* since the pick.
|
|
608
|
+
*
|
|
609
|
+
* `fn` gets `{ context, event, chain }`. `chain` is every reply since the pick, oldest first. Each
|
|
610
|
+
* link is `{ kind: "output", src, output }`, the output of an invoked actor, or `{ kind: "event",
|
|
611
|
+
* event }`, an event that a Gate or a nested Agent sent. TypeScript types each link, but not the
|
|
612
|
+
* order or the number of links. What `fn` gives back goes to the Agent as JSON. A value with no
|
|
613
|
+
* JSON form clears the answer: the Agent then reads the last reply. When more than one `answer`
|
|
614
|
+
* runs, the last one wins.
|
|
615
|
+
*
|
|
616
|
+
* Where you write it decides which pick it answers:
|
|
617
|
+
*
|
|
618
|
+
* - At or below the state of an Agent, it answers only a pick of that Agent.
|
|
619
|
+
* - On a transition below no Agent's state, it answers a pick of the Agent whose event took the
|
|
620
|
+
* transition.
|
|
621
|
+
* - On an `entry` or an `exit` below no Agent's state, it sets nothing, and `jr2 up` prints a
|
|
622
|
+
* warning: `an answer on the <entry|exit> of "<state>" is below no Agent's state, so it shapes
|
|
623
|
+
* no pick — an <entry|exit> there is no transition an Agent's event takes; move it under
|
|
624
|
+
* "<agent state>", or onto a transition (ADR-0067)`.
|
|
625
|
+
*
|
|
626
|
+
* The error of an invoked actor is no link, and the Agent does not see it. To tell the Agent,
|
|
627
|
+
* write `answer` on the `onError` route. An Effect never takes that route for an outcome: every
|
|
628
|
+
* outcome is output.
|
|
629
|
+
*
|
|
630
|
+
* @param fn - Gets `{ context, event, chain }` and gives back what the Agent reads, as a value
|
|
631
|
+
* with a JSON form.
|
|
632
|
+
* @returns An xstate action.
|
|
633
|
+
*
|
|
634
|
+
* @example
|
|
635
|
+
* ```ts
|
|
636
|
+
* import { z } from "zod";
|
|
637
|
+
* import {
|
|
638
|
+
* agent,
|
|
639
|
+
* answer,
|
|
640
|
+
* command,
|
|
641
|
+
* defineEvent,
|
|
642
|
+
* fail,
|
|
643
|
+
* jr2Setup,
|
|
644
|
+
* workspace,
|
|
645
|
+
* type Workspaced,
|
|
646
|
+
* } from "@jr2/orchestrator";
|
|
647
|
+
*
|
|
648
|
+
* const runTests = defineEvent({
|
|
649
|
+
* name: "run_tests",
|
|
650
|
+
* description: "Run the test suite. Its result is this call's answer.",
|
|
651
|
+
* input: z.object({}),
|
|
652
|
+
* });
|
|
653
|
+
*
|
|
654
|
+
* const finished = defineEvent({
|
|
655
|
+
* name: "finished",
|
|
656
|
+
* description: "Say that the tests pass.",
|
|
657
|
+
* input: z.object({}),
|
|
658
|
+
* });
|
|
659
|
+
*
|
|
660
|
+
* type Input = Workspaced<{}, "app">;
|
|
661
|
+
*
|
|
662
|
+
* const body = jr2Setup({
|
|
663
|
+
* types: {} as { context: Input; input: Input },
|
|
664
|
+
* events: [runTests, finished],
|
|
665
|
+
* actors: {
|
|
666
|
+
* coder: agent({
|
|
667
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
668
|
+
* instructions: "You are a careful engineer.",
|
|
669
|
+
* }),
|
|
670
|
+
* tests: command(["npm", "test"]),
|
|
671
|
+
* fail: fail(),
|
|
672
|
+
* },
|
|
673
|
+
* }).createMachine({
|
|
674
|
+
* id: "fix-tests",
|
|
675
|
+
* context: ({ input }) => input,
|
|
676
|
+
* on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
677
|
+
* initial: "coding",
|
|
678
|
+
* states: {
|
|
679
|
+
* coding: {
|
|
680
|
+
* invoke: { src: "coder", input: { prompt: "Make the tests pass." } },
|
|
681
|
+
* on: { finished: "done" },
|
|
682
|
+
* initial: "ready",
|
|
683
|
+
* states: {
|
|
684
|
+
* ready: { on: { run_tests: "testing" } },
|
|
685
|
+
* testing: {
|
|
686
|
+
* invoke: {
|
|
687
|
+
* src: "tests",
|
|
688
|
+
* onDone: {
|
|
689
|
+
* target: "ready",
|
|
690
|
+
* // The Agent reads this string, not the whole output of the command.
|
|
691
|
+
* actions: answer(({ event }) =>
|
|
692
|
+
* event.output.outcome === "exited" && event.output.code === 0
|
|
693
|
+
* ? "The tests pass."
|
|
694
|
+
* : `The tests fail:\n${"tail" in event.output ? event.output.tail : event.output.reason}`,
|
|
695
|
+
* ),
|
|
696
|
+
* },
|
|
697
|
+
* },
|
|
698
|
+
* },
|
|
699
|
+
* },
|
|
700
|
+
* },
|
|
701
|
+
* done: { type: "final" },
|
|
702
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
703
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
704
|
+
* },
|
|
705
|
+
* });
|
|
706
|
+
*
|
|
707
|
+
* export const machine = workspace(body, {
|
|
708
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
709
|
+
* spec: () => ({ branch: "fix-tests" }),
|
|
710
|
+
* });
|
|
711
|
+
* ```
|
|
712
|
+
*
|
|
713
|
+
* @see docs/concepts/the-menu.md
|
|
714
|
+
* @see docs/concepts/effects.md
|
|
715
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
716
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
717
|
+
* @see docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md
|
|
718
|
+
*/
|
|
719
|
+
export function answer<
|
|
720
|
+
TContext extends MachineContext,
|
|
721
|
+
TExpressionEvent extends EventObject,
|
|
722
|
+
TParams extends ParameterizedObject["params"] | undefined,
|
|
723
|
+
TEvent extends EventObject,
|
|
724
|
+
TActor extends ProvidedActor,
|
|
725
|
+
>(
|
|
726
|
+
fn: (args: { context: TContext; event: TExpressionEvent; chain: readonly ChainLink<TActor, TEvent>[] }) => unknown,
|
|
727
|
+
): ActionFunction<TContext, TExpressionEvent, TEvent, TParams, TActor, never, never, never, never> & AnswerAction {
|
|
728
|
+
// The action as it runs from one seat (`placeAnswers`): the Agent's state it is written below, or
|
|
729
|
+
// `null` on a transition below none. The object the author holds runs as `null`: it runs only from
|
|
730
|
+
// a Machine `placeAnswers` never placed.
|
|
731
|
+
const at =
|
|
732
|
+
(seat: AnswerSeat) =>
|
|
733
|
+
({ context, event, self }: { context: TContext; event: TExpressionEvent; self: AnyActorRef }) => {
|
|
734
|
+
const binding = boundRun(self.system);
|
|
735
|
+
// No pick of an Agent this seat reads is being answered (the state's first entry, a later
|
|
736
|
+
// Turn's, another Agent's): there is nothing to shape, and nothing runs.
|
|
737
|
+
const chain = binding?.chainOf?.(self, seat, event);
|
|
738
|
+
if (chain === undefined) return;
|
|
739
|
+
// Kit-internal: the host records links by slot name, which is what ChainLink states.
|
|
740
|
+
const links = linksOf(chain) as unknown as readonly ChainLink<TActor, TEvent>[];
|
|
741
|
+
binding?.setAnswer?.(self, seat, fn({ context, event, chain: links }), event);
|
|
742
|
+
};
|
|
743
|
+
const action = at(null);
|
|
744
|
+
const place = (seat: AnswerSeat): unknown => {
|
|
745
|
+
const copy = Object.assign(at(seat), { jr2Answer: true as const });
|
|
746
|
+
originals.set(copy, action);
|
|
747
|
+
return copy;
|
|
748
|
+
};
|
|
749
|
+
return Object.assign(action, { jr2Answer: true as const, [PLACE]: place }) as unknown as ActionFunction<
|
|
750
|
+
TContext,
|
|
751
|
+
TExpressionEvent,
|
|
752
|
+
TEvent,
|
|
753
|
+
TParams,
|
|
754
|
+
TActor,
|
|
755
|
+
never,
|
|
756
|
+
never,
|
|
757
|
+
never,
|
|
758
|
+
never
|
|
759
|
+
> &
|
|
760
|
+
AnswerAction;
|
|
761
|
+
}
|