@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/setup.ts
CHANGED
|
@@ -42,22 +42,69 @@ import {
|
|
|
42
42
|
type UnknownActorLogic,
|
|
43
43
|
} from "xstate";
|
|
44
44
|
import type { z } from "zod";
|
|
45
|
-
import { eventMap, type EventDef, type EventFrom } from "@jr2/agent-protocol";
|
|
45
|
+
import { eventMap, type EventDef, type EventFrom, type Sender } from "@jr2/agent-protocol";
|
|
46
46
|
import type { AgentRunInput, AgentTurnInput, FaultTelemetry } from "./actor.ts";
|
|
47
47
|
import { isAgent } from "./agent.ts";
|
|
48
|
+
import { isEffect } from "./effect.ts";
|
|
49
|
+
import { effectChecks } from "./effect-checks.ts";
|
|
48
50
|
import { gate } from "./gate.ts";
|
|
51
|
+
import { attachLent, isOpenBody, openBodyDeclarationOf, type LentParts } from "./open-body.ts";
|
|
52
|
+
import { refuseInlineParts } from "./parts.ts";
|
|
53
|
+
import { partyWarnings, refuseUnknownSentBy, SENT_BY, sentByGuard } from "./party.ts";
|
|
54
|
+
import { refuseLostOutput } from "./run-output.ts";
|
|
55
|
+
import { refuseUnroutedAgentFault } from "./mechanism-routes.ts";
|
|
49
56
|
import { boundEpoch, boundRunId, continuedIid } from "./registration.ts";
|
|
50
|
-
import { attachInputSchema, attachVocabulary } from "./vocabulary.ts";
|
|
57
|
+
import { attachInputSchema, attachLoadWarnings, attachSeats, attachVocabulary } from "./vocabulary.ts";
|
|
51
58
|
|
|
52
59
|
/**
|
|
53
|
-
* The
|
|
54
|
-
*
|
|
55
|
-
*
|
|
60
|
+
* The marker that `compileChart` puts on the config of a Chart's Machine (ADR-0070). In a Chart the
|
|
61
|
+
* `agent.fault` route is optional: the Open body sends an `agent.fault` that no transition takes to
|
|
62
|
+
* the Machine that invoked it, so `createMachine` does not refuse a Chart state with no route. Not on the Authoring
|
|
63
|
+
* surface (index.ts). The load walk (`partsOf`) refuses an author's Machine with no route, with or
|
|
64
|
+
* without the marker (ADR-0074).
|
|
56
65
|
*/
|
|
57
|
-
export
|
|
66
|
+
export const COMPILED_CHART: unique symbol = Symbol("jr2.compiledChart");
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
// Injected into every jr2Setup union. Dotted names by construction (`NAME_RE` forbids dots in
|
|
69
|
+
// workflow event names), so they can never collide with a def.
|
|
70
|
+
/** The events that jr2 itself sends into each Machine that `jr2Setup` makes. A Mechanism event's
|
|
71
|
+
* name has a dot, and a workflow event's name cannot, so the two never collide. */
|
|
72
|
+
export type MechanismEvent = FaultTelemetry | WorkspaceLost;
|
|
73
|
+
|
|
74
|
+
// A Workspace's Continuity broke (ADR-0021): its Sandbox is gone, or Lost. `reason` and `message`
|
|
75
|
+
// are for a body whose policy tells the causes apart, or only reports them; no policy needs them.
|
|
76
|
+
/**
|
|
77
|
+
* jr2 sends `workspace.lost` to the body of a `workspace()` when its Sandbox is gone: its pod was
|
|
78
|
+
* evicted or deleted, or its node went. jr2 does not make the Sandbox again, and the commits that
|
|
79
|
+
* were not pushed go with it. The body decides what to do next: route the event with
|
|
80
|
+
* `on: { "workspace.lost": … }`, usually once, on the body's root. `workspace()` refuses a body
|
|
81
|
+
* with a state that is not final and has no route for it on itself or on a state above. When the
|
|
82
|
+
* event comes and no transition takes it (the route's guard is false), the run ends `error`, with
|
|
83
|
+
* the event, the state and the reason in its fault.
|
|
84
|
+
*/
|
|
85
|
+
export type WorkspaceLost = {
|
|
86
|
+
type: "workspace.lost";
|
|
87
|
+
/** Why the Sandbox is gone, for a person or a log: the pod's reason (`Evicted`, `NodeShutdown`),
|
|
88
|
+
* the operator's (`PodDeleted`, `NodeLost`), or `Deleted`. A body can act without it. */
|
|
89
|
+
reason?: string;
|
|
90
|
+
/** More text about the loss, when jr2 has it. */
|
|
91
|
+
message?: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// The defs' derived types plus the mechanism's (ADR-0015).
|
|
95
|
+
/** The type of `event` in a Machine that `jr2Setup` makes: each workflow event in its `events`,
|
|
96
|
+
* and each Mechanism event. A workflow event that an Agent or a Gate sent has `event.sender`. An
|
|
97
|
+
* event that the Machine sent to itself (`raise`, `sendTo`) has no `sender`. */
|
|
98
|
+
export type WorkflowEvent<TDefs extends readonly EventDef[]> =
|
|
99
|
+
| (EventFrom<TDefs[number]> & { readonly sender?: Sender })
|
|
100
|
+
| MechanismEvent;
|
|
101
|
+
|
|
102
|
+
/** The guard jr2Setup registers for every Machine — `sentBy(id)` (ADR-0015) — in the typed guard
|
|
103
|
+
* map, so `sentBy("coder")` and `and([sentBy("coder"), "ready"])` type-check with no cast.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
type SentByGuards = { [SENT_BY]: { id: string } };
|
|
61
108
|
|
|
62
109
|
/** The jr2 actor every workflow can invoke by name without listing it (ADR-0015). `gate` is the
|
|
63
110
|
* whole set: an Agent is not pre-registered, because it is not one logic — it is the slot the
|
|
@@ -65,25 +112,154 @@ export type WorkflowEvent<TDefs extends readonly EventDef[]> = EventFrom<TDefs[n
|
|
|
65
112
|
const jr2Actors = { gate };
|
|
66
113
|
type JR2Actors = typeof jr2Actors;
|
|
67
114
|
|
|
68
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Consumer actors merge OVER the pre-registered set: same name → the consumer's logic wins.
|
|
117
|
+
*
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
69
120
|
type MergedActors<TActors extends Record<string, UnknownActorLogic>> = Omit<JR2Actors, keyof TActors> & TActors;
|
|
70
121
|
|
|
71
122
|
// Local equivalents of xstate's non-exported setup() mapped helpers (setup.d.ts) — same shapes,
|
|
72
123
|
// so the declared signature below composes with the public `ActionFunction`/`GuardPredicate`.
|
|
124
|
+
/** @internal */
|
|
73
125
|
type ToParameterizedObject<T extends Record<string, ParameterizedObject["params"] | undefined>> = {
|
|
74
126
|
[K in keyof T & string]: { type: K; params: T[K] };
|
|
75
127
|
}[keyof T & string];
|
|
128
|
+
/** @internal */
|
|
76
129
|
type ToProvidedActor<TActors extends Record<string, UnknownActorLogic>> = {
|
|
77
130
|
[K in keyof TActors & string]: { src: K; logic: TActors[K]; id: string | undefined };
|
|
78
131
|
}[keyof TActors & string];
|
|
79
132
|
|
|
80
133
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
134
|
+
* Set up a Machine that takes workflow events: the jr2 form of xstate's `setup()`. Write a Machine
|
|
135
|
+
* with it when an Agent or a Gate sends events into the Machine, or when the Machine declares a
|
|
136
|
+
* door. Call `.createMachine(config)` on the result, as with `setup()`. The Machine you get is a
|
|
137
|
+
* plain xstate Machine.
|
|
138
|
+
*
|
|
139
|
+
* What it adds to `setup()`:
|
|
140
|
+
* - `events` replaces `types.events`. The event type of the Machine comes from the
|
|
141
|
+
* `defineEvent` events you list, plus the events jr2 itself sends (`agent.fault`,
|
|
142
|
+
* `workspace.lost`). An event that a party sent has `event.sender`. A Machine that takes no
|
|
143
|
+
* workflow event leaves out `events`.
|
|
144
|
+
* - The `gate` actor is ready to invoke by name: `invoke: { src: "gate" }`.
|
|
145
|
+
* - Each Agent slot (`actors: { coder: agent(…) }`) gets its Menu from the states that invoke it.
|
|
146
|
+
* - The `sentBy(id)` guard is ready to use.
|
|
147
|
+
* - `createMachine` also takes `input`: a zod object for the input that starts a run. jr2 checks
|
|
148
|
+
* each start against it. Without it, a run can start with any input. The `setup()` of xstate
|
|
149
|
+
* does not read this `input`, so a Machine with a door uses `jr2Setup`, also when it takes no
|
|
150
|
+
* workflow event. `jr2 up` and the publish door refuse a registered Machine that has a zod
|
|
151
|
+
* `input` if `jr2Setup` did not make the Machine: ``workflow "<name>": machine "<id>" has a zod `input` on
|
|
152
|
+
* createMachine, but jr2Setup did not make this Machine, so no door reads that input …``
|
|
153
|
+
*
|
|
154
|
+
* The output of a run is the root `output` of `createMachine`, beside `id` and `states`, as in
|
|
155
|
+
* xstate v5. xstate does not read the `output` of a final state as the output of the Machine. It
|
|
156
|
+
* gives that output to the root `output` as `event.output`. Write
|
|
157
|
+
* `output: ({ event }) => event.output as Output` at the root, or make the output from `context`
|
|
158
|
+
* there. `createMachine` refuses an `output` on a final state directly below the root when the root
|
|
159
|
+
* has no `output` (see the list below).
|
|
160
|
+
*
|
|
161
|
+
* `createMachine` refuses a Machine that it cannot run as written. Among the refusals:
|
|
162
|
+
* - an `on` key that no listed event has: ``machine "<id>" handles event "<key>", which no def in
|
|
163
|
+
* jr2Setup({ events }) declares (declared: <names>) — a typo, or a missing defineEvent``
|
|
164
|
+
* - a listed event that no state takes: ``machine "<id>" declares "<name>", which no state handles
|
|
165
|
+
* — a pick no state takes is on no Menu and no Gate: handle it, or drop it from
|
|
166
|
+
* jr2Setup({ events })``
|
|
167
|
+
* - an entry of `events` that `defineEvent` did not make, or one name listed twice:
|
|
168
|
+
* ``machine "<id>": `events` entry is not a defineEvent() def: <entry>``,
|
|
169
|
+
* ``machine "<id>": duplicate event "<name>" in `events` ``
|
|
170
|
+
* - an `agent()`, an Effect or a `workspace()` written on `invoke.src` instead of under `actors`:
|
|
171
|
+
* ``machine "<id>": state "<state>" invokes <part> inline — a Part is a key of actors, so it has
|
|
172
|
+
* a name that the ledger, the report and customize() share: actors: { <name>: <factory>(...) },
|
|
173
|
+
* then invoke: { src: "<name>" }``. `<part>` is `an Agent`, `an Effect` or `a Workspace`.
|
|
174
|
+
* `<factory>` is `agent`, the Effect's kind (such as `command`), or `workspace`.
|
|
175
|
+
* - an `output` on a final state directly below the root, when the root has no `output`:
|
|
176
|
+
* ``machine "<id>": final state "<state>" declares an output, and the root has no output — xstate
|
|
177
|
+
* v5 gives a run only its root output, so this output reaches nothing and the run ends with none:
|
|
178
|
+
* write it at the root, as output: ({ context }) => ..., or keep it on the final state and pass it
|
|
179
|
+
* on with output: ({ event }) => event.output at the root``. With more than one such final state,
|
|
180
|
+
* it names each one and puts the words in the plural: ``final states "<state>", "<state>"
|
|
181
|
+
* declare an output``, ``these outputs reach nothing``, ``write one at the root`` and ``keep
|
|
182
|
+
* them on the final states and pass them on``. An `output` on a
|
|
183
|
+
* final state inside a compound state is legal: the `onDone` of that compound state gets it as
|
|
184
|
+
* `event.output`.
|
|
185
|
+
* - a parallel state at or below a state that invokes an Agent: ``machine "<id>": state "<state>"
|
|
186
|
+
* is parallel, at or below Agent "<slot>"'s state "<state>" — a parallel region below an Agent's
|
|
187
|
+
* state is reserved for picks the Agent does not wait for, which jr2 does not build; background
|
|
188
|
+
* work goes above the Agent's state``
|
|
189
|
+
* - a state below an Agent's state that invokes something and that nothing enters: ``machine
|
|
190
|
+
* "<id>": state "<state>" below Agent "<slot>"'s state "<state>" is entered by nothing, so what
|
|
191
|
+
* it invokes (<src>) never runs — target it from a pick or a route, or delete it``
|
|
192
|
+
* - a state that invokes an Agent when that state and each state above it have no `agent.fault`
|
|
193
|
+
* route: ``machine "<id>": state "<state>" invokes an Agent, and no route for "agent.fault" is
|
|
194
|
+
* on it or on a state above — jr2 sends agent.fault when the Agent's Turn cannot go on, and with
|
|
195
|
+
* no route the run faults: route it on that state or on a state above it, as on: {
|
|
196
|
+
* "agent.fault": "<the state that handles it>" } (ADR-0074)``. A state that invokes an Open body
|
|
197
|
+
* that lends an Agent or allows a model gets the same line, with `invokes an Open body that can
|
|
198
|
+
* run an Agent`: the body sends it a Chart's unrouted `agent.fault`. When both kinds have no route,
|
|
199
|
+
* one line names both, joined by `, and`. One route on the root is a route for each state. A
|
|
200
|
+
* Chart's Machine does not get this refusal: in a Chart the route is optional (ADR-0070).
|
|
201
|
+
*
|
|
202
|
+
* It also refuses a `sentBy` that names no party (see `sentBy`). It refuses a pick param that
|
|
203
|
+
* nothing reads, where the pick enters a state that invokes an Effect (see `fromPick`). Shapes that
|
|
204
|
+
* are legal but probably not what you mean, such as a mode that nothing enters, are warnings that
|
|
205
|
+
* `jr2 up` prints.
|
|
206
|
+
*
|
|
207
|
+
* @param def - What `setup()` takes, with `events` in place of `types.events`. A Machine that
|
|
208
|
+
* needs none of it calls `jr2Setup()`.
|
|
209
|
+
* @returns What `setup()` returns. Call `.createMachine(config)` on it.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { z } from "zod";
|
|
214
|
+
* import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
|
|
215
|
+
*
|
|
216
|
+
* const proposed = defineEvent({
|
|
217
|
+
* name: "proposed",
|
|
218
|
+
* description: "Call this with your plan.",
|
|
219
|
+
* input: z.object({ plan: z.string() }),
|
|
220
|
+
* });
|
|
221
|
+
* const approve = defineEvent({ name: "approve", input: z.object({}) });
|
|
222
|
+
* const reject = defineEvent({ name: "reject", input: z.object({ reason: z.string() }) });
|
|
223
|
+
*
|
|
224
|
+
* export const machine = jr2Setup({
|
|
225
|
+
* types: {} as { input: { goal: string }; context: { goal: string } },
|
|
226
|
+
* events: [proposed, approve, reject],
|
|
227
|
+
* actors: {
|
|
228
|
+
* planner: agent({
|
|
229
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
230
|
+
* instructions: "You plan small releases.",
|
|
231
|
+
* workspace: "none",
|
|
232
|
+
* }),
|
|
233
|
+
* fail: fail(),
|
|
234
|
+
* },
|
|
235
|
+
* }).createMachine({
|
|
236
|
+
* id: "plan",
|
|
237
|
+
* input: z.object({ goal: z.string() }),
|
|
238
|
+
* context: ({ input }) => ({ goal: input.goal }),
|
|
239
|
+
* on: { "agent.fault": ".failed" },
|
|
240
|
+
* initial: "planning",
|
|
241
|
+
* states: {
|
|
242
|
+
* planning: {
|
|
243
|
+
* invoke: { src: "planner", input: ({ context }) => ({ prompt: `Plan this: ${context.goal}` }) },
|
|
244
|
+
* on: { proposed: "review" },
|
|
245
|
+
* },
|
|
246
|
+
* review: {
|
|
247
|
+
* invoke: { src: "gate" },
|
|
248
|
+
* on: { approve: "approved", reject: "planning" },
|
|
249
|
+
* },
|
|
250
|
+
* approved: { type: "final" },
|
|
251
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
252
|
+
* },
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @see docs/guides/write-a-workflow.md
|
|
257
|
+
* @see docs/concepts/machines-and-workflows.md
|
|
258
|
+
* @see docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md
|
|
83
259
|
*/
|
|
84
260
|
export function jr2Setup<
|
|
85
261
|
TContext extends MachineContext = MachineContext,
|
|
86
|
-
const TDefs extends readonly EventDef[] = readonly
|
|
262
|
+
const TDefs extends readonly EventDef[] = readonly [],
|
|
87
263
|
TActors extends Record<string, UnknownActorLogic> = {},
|
|
88
264
|
TActions extends Record<string, ParameterizedObject["params"] | undefined> = {},
|
|
89
265
|
TGuards extends Record<string, ParameterizedObject["params"] | undefined> = {},
|
|
@@ -93,50 +269,62 @@ export function jr2Setup<
|
|
|
93
269
|
TOutput extends NonReducibleUnknown = NonReducibleUnknown,
|
|
94
270
|
TEmitted extends EventObject = EventObject,
|
|
95
271
|
TMeta extends MetaObject = MetaObject,
|
|
96
|
-
>(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
272
|
+
>(
|
|
273
|
+
def: {
|
|
274
|
+
/** The types of the Machine, as in `setup()`: `context`, `input`, `output`, `tags`, `emitted`
|
|
275
|
+
* and `meta`. Not `events`: the event type comes from `events`. */
|
|
276
|
+
types?: {
|
|
277
|
+
context?: TContext;
|
|
278
|
+
input?: TInput;
|
|
279
|
+
output?: TOutput;
|
|
280
|
+
tags?: TTag;
|
|
281
|
+
emitted?: TEmitted;
|
|
282
|
+
meta?: TMeta;
|
|
283
|
+
};
|
|
284
|
+
// The single source for types, validation and delivery.
|
|
285
|
+
/** The workflow events this Machine takes: each one made by `defineEvent`. Leave it out when the
|
|
286
|
+
* Machine takes none. Each listed event must be the name of an `on` key. Each `on` key must be
|
|
287
|
+
* listed, if it is not a jr2 or xstate event (an event with a `.` in its name). */
|
|
288
|
+
events?: TDefs;
|
|
289
|
+
/** The actors the states invoke by name, as in `setup()`: Agent slots (`agent(…)`), Effects
|
|
290
|
+
* (`command(…)`, `http(…)`, `push(…)`), child Machines, `workspace(…)` and plain xstate logic.
|
|
291
|
+
* The key is the slot's name. An actor named `gate` replaces jr2's Gate. */
|
|
292
|
+
actors?: TActors;
|
|
293
|
+
/** Named actions, as in `setup()`. Their `event` has the type of this Machine's events. */
|
|
294
|
+
actions?: {
|
|
295
|
+
[K in keyof TActions]: ActionFunction<
|
|
296
|
+
TContext,
|
|
297
|
+
WorkflowEvent<TDefs>,
|
|
298
|
+
WorkflowEvent<TDefs>,
|
|
299
|
+
TActions[K],
|
|
300
|
+
ToProvidedActor<MergedActors<TActors>>,
|
|
301
|
+
ToParameterizedObject<TActions>,
|
|
302
|
+
ToParameterizedObject<TGuards>,
|
|
303
|
+
TDelay,
|
|
304
|
+
TEmitted
|
|
305
|
+
>;
|
|
306
|
+
};
|
|
307
|
+
/** Named guards, as in `setup()`. `sentBy` is there already; do not name a guard `jr2.sentBy`. */
|
|
308
|
+
guards?: {
|
|
309
|
+
[K in keyof TGuards]: GuardPredicate<TContext, WorkflowEvent<TDefs>, TGuards[K], ToParameterizedObject<TGuards>>;
|
|
310
|
+
};
|
|
311
|
+
/** Named delays, as in `setup()`. */
|
|
312
|
+
delays?: {
|
|
313
|
+
[K in TDelay]: DelayConfig<
|
|
314
|
+
TContext,
|
|
315
|
+
WorkflowEvent<TDefs>,
|
|
316
|
+
ToParameterizedObject<TActions>["params"],
|
|
317
|
+
WorkflowEvent<TDefs>
|
|
318
|
+
>;
|
|
319
|
+
};
|
|
320
|
+
} = {},
|
|
321
|
+
): SetupReturn<
|
|
134
322
|
TContext,
|
|
135
323
|
WorkflowEvent<TDefs>,
|
|
136
324
|
MergedActors<TActors>,
|
|
137
325
|
{},
|
|
138
326
|
TActions,
|
|
139
|
-
TGuards,
|
|
327
|
+
TGuards & SentByGuards,
|
|
140
328
|
TDelay,
|
|
141
329
|
TTag,
|
|
142
330
|
TInput,
|
|
@@ -144,11 +332,14 @@ export function jr2Setup<
|
|
|
144
332
|
TEmitted,
|
|
145
333
|
TMeta
|
|
146
334
|
> {
|
|
335
|
+
// Like `setup()`, but `types.events` is gone (ADR-0015): the event union derives from `events`
|
|
336
|
+
// (defineEvent defs, taken as values) plus the mechanism events.
|
|
147
337
|
const inner = setup({
|
|
148
338
|
types: def.types,
|
|
149
339
|
actors: { ...jr2Actors, ...(def.actors ?? {}) },
|
|
150
340
|
actions: def.actions,
|
|
151
|
-
|
|
341
|
+
// `sentBy(id)` is a named guard with params, so the load walk can READ it (ADR-0015).
|
|
342
|
+
guards: { ...(def.guards ?? {}), [SENT_BY]: sentByGuard },
|
|
152
343
|
delays: def.delays,
|
|
153
344
|
} as never);
|
|
154
345
|
|
|
@@ -157,19 +348,28 @@ export function jr2Setup<
|
|
|
157
348
|
// machine receives at creation — and is pulled OFF before xstate sees it: a zod schema is
|
|
158
349
|
// not machine structure, and it must never reach the fingerprint/serialization paths that
|
|
159
350
|
// read `machine.config`. It is attached beside the vocabulary below.
|
|
160
|
-
|
|
351
|
+
// A compiled Chart (ADR-0070) is marked, and the marker is pulled off as `input` is.
|
|
352
|
+
const {
|
|
353
|
+
input: inputSchema,
|
|
354
|
+
[COMPILED_CHART]: compiledChart,
|
|
355
|
+
...machineConfig
|
|
356
|
+
} = config as { input?: z.ZodObject; [COMPILED_CHART]?: true } & Record<string, unknown>;
|
|
161
357
|
|
|
162
358
|
// Resolve the defs first (duplicates and reserved semantics fail HERE, naming the machine —
|
|
163
359
|
// the same loud failure `eventMap` gave the manifest, moved to machine-build time)…
|
|
164
|
-
const defs = eventMap((machineConfig as { id?: string }).id ?? "(machine)", def.events);
|
|
360
|
+
const defs = eventMap((machineConfig as { id?: string }).id ?? "(machine)", def.events ?? []);
|
|
361
|
+
|
|
362
|
+
// Every Open body slot (ADR-0070), found by its brand: each name it lends resolves against THIS
|
|
363
|
+
// setup's own parts, or the Machine is refused naming it. The resolved set is stamped on the slot,
|
|
364
|
+
// for the door that admits a Chart before a run and for the walk at `jr2 up`.
|
|
365
|
+
stampOpenBodies((machineConfig as { id?: string }).id ?? "(machine)", def);
|
|
165
366
|
|
|
166
367
|
// …then rewrite the config (ADR-0015): every Agent-slot/`gate` invoke's input is wrapped to
|
|
167
368
|
// append its DERIVED menu and finalize the mechanism fields. Static — the walk sees the same
|
|
168
369
|
// config the Console will — and the derived names still ride serializable input, so the
|
|
169
370
|
// ADR-0007 restore path and invoke-time `resolveAccepts` validation are unchanged.
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
);
|
|
371
|
+
const derived = deriveMenus(machineConfig, defs, def.actors ?? {});
|
|
372
|
+
const machine = (inner.createMachine as unknown as (c: never) => AnyStateMachine)(derived.config as never);
|
|
173
373
|
|
|
174
374
|
// Close the nested-`on` typo hole (ADR-0015): with the manifest dead, a typo'd key would
|
|
175
375
|
// silently become vocabulary — so every non-dotted key the machine handles anywhere must map
|
|
@@ -177,6 +377,7 @@ export function jr2Setup<
|
|
|
177
377
|
// targetless/actionless transitions — exactly where a typo'd key would hide). Dotted names
|
|
178
378
|
// (`agent.*`, `workspace.*`, `xstate.*`, delayed transitions) are mechanically jr2's/xstate's;
|
|
179
379
|
// `*` is the wildcard descriptor.
|
|
380
|
+
const handled = new Set<string>();
|
|
180
381
|
const walk = (node: AnyStateMachine["root"]): void => {
|
|
181
382
|
for (const descriptor of node.transitions.keys()) {
|
|
182
383
|
if (descriptor === "*" || descriptor.includes(".")) continue;
|
|
@@ -186,12 +387,51 @@ export function jr2Setup<
|
|
|
186
387
|
`declares (declared: ${[...defs.keys()].join(", ") || "none"}) — a typo, or a missing defineEvent`,
|
|
187
388
|
);
|
|
188
389
|
}
|
|
390
|
+
handled.add(descriptor);
|
|
189
391
|
}
|
|
190
392
|
for (const child of Object.values(node.states)) walk(child);
|
|
191
393
|
};
|
|
192
394
|
walk(machine.root);
|
|
193
395
|
|
|
396
|
+
// The other direction (ADR-0015): a def no state handles is in no derived set, so it is on no
|
|
397
|
+
// Menu and no Gate — a pick nothing can send and nothing would take.
|
|
398
|
+
const dead = [...defs.keys()].filter((name) => !handled.has(name));
|
|
399
|
+
if (dead.length) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`machine "${machine.id}" declares ${dead.map((name) => `"${name}"`).join(", ")}, which no state handles — ` +
|
|
402
|
+
`a pick no state takes is on no Menu and no Gate: handle it, or drop it from jr2Setup({ events })`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// A Part written inline has no name (ADR-0049, ADR-0067): refused, with the slot form.
|
|
407
|
+
refuseInlineParts(machine);
|
|
408
|
+
// A final state's output with no root output (ADR-0015): the run would end with none.
|
|
409
|
+
refuseLostOutput(machine);
|
|
410
|
+
// A state that invokes an Agent, or an Open body that can run one, with no `agent.fault` route
|
|
411
|
+
// (ADR-0074): the run would wait for ever. Not in a compiled Chart: there the route is optional,
|
|
412
|
+
// and the Open body sends the event that no transition takes to the Machine that invoked it (ADR-0070).
|
|
413
|
+
if (compiledChart !== true) refuseUnroutedAgentFault(machine);
|
|
414
|
+
refuseParallelBelowAgents(machine, def.actors ?? {});
|
|
415
|
+
refuseUnknownSentBy(machine);
|
|
416
|
+
// Before the Effect checks: they ask who may send a writing (`mayTake`), which reads its audience.
|
|
194
417
|
attachVocabulary(machine, defs);
|
|
418
|
+
// The Effect checks (ADR-0067): a pick param nothing reads, a `fill` that overwrites one, a
|
|
419
|
+
// `fromPick` of another pick — refused; a mapper the probe cannot run — warned.
|
|
420
|
+
const effects = effectChecks(machine, def.actors ?? {}, defs);
|
|
421
|
+
if (effects.errors.length) throw new Error(`machine "${machine.id}": ${effects.errors.join("; ")}`);
|
|
422
|
+
|
|
423
|
+
const idOf = (path: readonly string[]): string => stateAt(machine, path).id;
|
|
424
|
+
attachSeats(
|
|
425
|
+
machine,
|
|
426
|
+
derived.seats.map(({ path, ...seat }) => ({ state: idOf(path), ...seat })),
|
|
427
|
+
);
|
|
428
|
+
attachLoadWarnings(machine, [
|
|
429
|
+
...unenteredModes(machine, def.actors ?? {}),
|
|
430
|
+
...restWarnings(machine, def.actors ?? {}),
|
|
431
|
+
...effects.warnings,
|
|
432
|
+
...derived.warnings.map((warning) => warning(idOf)),
|
|
433
|
+
...partyWarnings(machine, derived.derivedGateIds),
|
|
434
|
+
]);
|
|
195
435
|
if (inputSchema) attachInputSchema(machine, inputSchema);
|
|
196
436
|
return machine;
|
|
197
437
|
};
|
|
@@ -204,7 +444,7 @@ export function jr2Setup<
|
|
|
204
444
|
MergedActors<TActors>,
|
|
205
445
|
{},
|
|
206
446
|
TActions,
|
|
207
|
-
TGuards,
|
|
447
|
+
TGuards & SentByGuards,
|
|
208
448
|
TDelay,
|
|
209
449
|
TTag,
|
|
210
450
|
TInput,
|
|
@@ -214,12 +454,17 @@ export function jr2Setup<
|
|
|
214
454
|
>;
|
|
215
455
|
}
|
|
216
456
|
|
|
217
|
-
// --- Menu derivation (ADR-0015)
|
|
218
|
-
// A state that invokes an AGENT SLOT
|
|
219
|
-
// transitions
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
457
|
+
// --- Menu derivation (ADR-0015, ADR-0029) --------------------------------------------------------
|
|
458
|
+
// A state that invokes an AGENT SLOT derives the workflow events the Turn may pick: its own
|
|
459
|
+
// transitions, its ancestors' (bubbled, per statechart semantics), and those written on the states
|
|
460
|
+
// below it — its MODES (`modePicks`) — filtered to the names that Agent may send (the audience
|
|
461
|
+
// rule, on `deriveMenus`). That is its derived set, before its guards narrow it to the Allowed
|
|
462
|
+
// picks; the Allowed picks are read off the live snapshot, so they follow the active mode. The
|
|
463
|
+
// Agent's MENU — its tools block — is the union of those sets over every state that invokes it,
|
|
464
|
+
// fixed for the conversation. A `gate` derives its own and its ancestors' transitions, and those
|
|
465
|
+
// written on its plain children (the same descent, stopping at a child that invokes a party),
|
|
466
|
+
// filtered to the names its caller may send. An explicit `accepts:` on a gate's input remains an
|
|
467
|
+
// escape hatch.
|
|
223
468
|
//
|
|
224
469
|
// An agent invoke is identified by its LOGIC, not by a reserved src name: the walk looks the
|
|
225
470
|
// invoke's `src` up in the setup's own `actors` map and asks `isAgent` (ADR-0049). That is also
|
|
@@ -240,18 +485,81 @@ type LooseState = {
|
|
|
240
485
|
};
|
|
241
486
|
type InputArgs = { context: unknown; event: unknown; self: AnyActorRef };
|
|
242
487
|
|
|
243
|
-
/**
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
488
|
+
/**
|
|
489
|
+
* One party's seat on one state: who may send what there (ADR-0015). `path` is the state's key
|
|
490
|
+
* path below the Machine's root; `names` is the derived set — for an Agent, what its Turn may pick
|
|
491
|
+
* before the guards narrow it; for a Gate, its accepted set unless its input names `accepts:`.
|
|
492
|
+
*/
|
|
493
|
+
export type Seat = { path: readonly string[]; party: "agent" | "gate"; agent?: string; names: string[] };
|
|
494
|
+
|
|
495
|
+
/** Each name in reach, with the OWNER mark of every writing of it in reach (see below). */
|
|
496
|
+
type Reach = Map<string, Set<string>>;
|
|
497
|
+
/** Each name written on the path from the root: where, and the owner mark of each writing. */
|
|
498
|
+
type Writings = Map<string, Array<{ path: readonly string[]; owner: string }>>;
|
|
499
|
+
/** A load warning, rendered once the built Machine can turn a key path into a state id. */
|
|
500
|
+
type Warning = (idOf: (path: readonly string[]) => string) => string;
|
|
501
|
+
|
|
502
|
+
/** The owner mark of a writing no party answers for — at or above it, no state invokes an Agent
|
|
503
|
+
* slot or a `gate` — so both kinds may send it. */
|
|
504
|
+
const NOBODY = "";
|
|
505
|
+
/** The owner mark of the state at `path`: the party it invokes answers for the writings below it.
|
|
506
|
+
* `#` keeps the root's mark apart from {@link NOBODY}. */
|
|
507
|
+
const ownerMark = (path: readonly string[]): string => `#${path.join(".")}`;
|
|
508
|
+
|
|
509
|
+
/** Rewrite a machine config, wrapping every Agent-slot/`gate` invoke input (immutably).
|
|
510
|
+
*
|
|
511
|
+
* The same walk derives each Agent name's MENU (ADR-0029): the union of the derived sets of every
|
|
512
|
+
* state that invokes an Agent of that name in this Machine — the scope of its continued
|
|
513
|
+
* conversation's id (ADR-0057) — sorted, so it is byte-stable for every Turn of the conversation
|
|
514
|
+
* and for every fresh one. The wrapped inputs read it at invoke time, when the walk is long done.
|
|
515
|
+
*
|
|
516
|
+
* WHO may send a name (the audience rule, ADR-0015). A writing — an `on` key on a state —
|
|
517
|
+
* belongs to the party that its state, or the nearest state above it, invokes: an Agent slot, a
|
|
518
|
+
* `gate`, or both when one state invokes both. With no such state above, it belongs to both. An
|
|
519
|
+
* event whose def leaves `audience` unset follows that rule; an explicit `audience` overrides it
|
|
520
|
+
* BY KIND over the whole reach (`agent`: every Agent whose reach holds a writing of it; `external`:
|
|
521
|
+
* every Gate's; `any`: both). The REACH never changes — own + ancestors, plus the states below
|
|
522
|
+
* with no party-invoking state between (an Agent's modes, a Gate's plain children) — and the rule
|
|
523
|
+
* only filters inside it, so it cannot widen who may send a name. */
|
|
524
|
+
function deriveMenus(
|
|
525
|
+
config: unknown,
|
|
526
|
+
defs: Map<string, EventDef>,
|
|
527
|
+
actors: Record<string, UnknownActorLogic>,
|
|
528
|
+
): { config: unknown; seats: Seat[]; warnings: Warning[]; derivedGateIds: Set<string> } {
|
|
529
|
+
const pick = (reach: Reach, kind: "agent" | "external", self: string): string[] =>
|
|
530
|
+
[...reach]
|
|
531
|
+
.filter(([name, owners]) => {
|
|
532
|
+
const d = defs.get(name);
|
|
533
|
+
if (!d) return false;
|
|
534
|
+
if (d.audience !== undefined) return d.audience === kind || d.audience === "any";
|
|
535
|
+
return owners.has(self) || owners.has(NOBODY);
|
|
536
|
+
})
|
|
537
|
+
.map(([name]) => name);
|
|
538
|
+
const menus = new Map<string, Set<string>>();
|
|
539
|
+
const menuOf = (agentName: string): string[] => [...(menus.get(agentName) ?? [])].sort();
|
|
540
|
+
const seats: Seat[] = [];
|
|
541
|
+
// The load warnings this walk can see (ADR-0015), each rendered once the states have ids. Keyed,
|
|
542
|
+
// so a writing seen from two paths is one warning.
|
|
543
|
+
const warnings = new Map<string, Warning>();
|
|
544
|
+
// Which parties each owner mark's state invokes — what the Gate-above warning reads.
|
|
545
|
+
const parties = new Map<string, { agents: string[]; gate: boolean }>();
|
|
546
|
+
// The Gate invoke ids this walk names: a party warning says how to name one instead.
|
|
547
|
+
const derivedGateIds = new Set<string>();
|
|
548
|
+
|
|
549
|
+
const walk = (
|
|
550
|
+
node: LooseState,
|
|
551
|
+
inherited: Reach,
|
|
552
|
+
writtenAbove: Writings,
|
|
553
|
+
path: readonly string[],
|
|
554
|
+
ownerAbove: string,
|
|
555
|
+
): LooseState => {
|
|
556
|
+
const owner = invokesParty(node, actors) ? ownerMark(path) : ownerAbove;
|
|
557
|
+
if (owner !== ownerAbove) parties.set(owner, partiesOf(node, actors));
|
|
558
|
+
const names: Reach = new Map([...inherited].map(([name, owners]) => [name, new Set(owners)]));
|
|
559
|
+
const written: Writings = new Map([...writtenAbove].map(([name, at]) => [name, [...at]]));
|
|
560
|
+
for (const name of ownPicks(node)) {
|
|
561
|
+
names.set(name, (names.get(name) ?? new Set<string>()).add(owner));
|
|
562
|
+
written.set(name, [...(written.get(name) ?? []), { path, owner }]);
|
|
255
563
|
}
|
|
256
564
|
|
|
257
565
|
let out = node;
|
|
@@ -264,17 +572,49 @@ function deriveMenus(config: unknown, defs: Map<string, EventDef>, actors: Recor
|
|
|
264
572
|
const wrapOne = (inv: LooseInvoke): LooseInvoke => {
|
|
265
573
|
// The slot key IS the Agent name (ADR-0049) — read off the declaration, never authored.
|
|
266
574
|
if (typeof inv?.src === "string" && isAgent(actors[inv.src])) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
575
|
+
// A mode's writings are this state's by construction: the descent stops at any child
|
|
576
|
+
// that invokes a party, so none answers between them and this one.
|
|
577
|
+
const reach: Reach = new Map([...names].map(([name, owners]) => [name, new Set(owners)]));
|
|
578
|
+
for (const name of modePicks(node, actors))
|
|
579
|
+
reach.set(name, (reach.get(name) ?? new Set<string>()).add(owner));
|
|
580
|
+
const derived = pick(reach, "agent", owner);
|
|
581
|
+
// A Gate above that takes a writing from this Agent: the Gate's caller owns it, so the
|
|
582
|
+
// Agent below — which reaches it — cannot send it. Legal, and the one narrowing of the
|
|
583
|
+
// audience rule an author may not expect.
|
|
584
|
+
const agent = inv.src;
|
|
585
|
+
for (const [name, owners] of reach) {
|
|
586
|
+
if (derived.includes(name) || defs.get(name)?.audience !== undefined) continue;
|
|
587
|
+
for (const at of written.get(name) ?? []) {
|
|
588
|
+
if (!owners.has(at.owner) || at.owner === owner || !parties.get(at.owner)?.gate) continue;
|
|
589
|
+
warnings.set(
|
|
590
|
+
`gate:${agent}:${path.join(".")}:${name}:${at.path.join(".")}`,
|
|
591
|
+
(idOf) =>
|
|
592
|
+
`Agent "${agent}" on "${idOf(path)}" cannot send "${name}": it is written on "${idOf(at.path)}", ` +
|
|
593
|
+
`whose Gate's caller owns it — give the def \`audience: "any"\` for both to send it`,
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const menu = menus.get(inv.src) ?? new Set<string>();
|
|
598
|
+
for (const name of derived) menu.add(name);
|
|
599
|
+
menus.set(inv.src, menu);
|
|
600
|
+
seats.push({ path, party: "agent", agent: inv.src, names: [...derived].sort() });
|
|
601
|
+
return { ...inv, input: wrapAgentInput(inv.input, derived, inv.src, menuOf) };
|
|
270
602
|
}
|
|
271
603
|
if (inv?.src === "gate") {
|
|
272
|
-
|
|
604
|
+
// A Gate's plain children — below it, with no party-invoking state between — are its own
|
|
605
|
+
// by position, as an Agent's modes are its (ADR-0015).
|
|
606
|
+
const reach: Reach = new Map([...names].map(([name, owners]) => [name, new Set(owners)]));
|
|
607
|
+
for (const name of modePicks(node, actors))
|
|
608
|
+
reach.set(name, (reach.get(name) ?? new Set<string>()).add(owner));
|
|
609
|
+
const accepts = pick(reach, "external", owner);
|
|
610
|
+
seats.push({ path, party: "gate", names: [...accepts].sort() });
|
|
611
|
+
const wrapped: LooseInvoke = { ...inv, input: wrapGateInput(inv.input, accepts) };
|
|
273
612
|
// A machine-root gate (empty path) is left to xstate's default id: a `""` id would be
|
|
274
613
|
// worse than a noisy one, and the derived gate id still works.
|
|
275
614
|
if (inv.id == null && path.length) {
|
|
276
615
|
const key = path.join(".");
|
|
277
616
|
wrapped.id = unnamedGates > 1 ? `${key}.${ordinal++}` : key;
|
|
617
|
+
derivedGateIds.add(wrapped.id as string);
|
|
278
618
|
}
|
|
279
619
|
return wrapped;
|
|
280
620
|
}
|
|
@@ -284,53 +624,409 @@ function deriveMenus(config: unknown, defs: Map<string, EventDef>, actors: Recor
|
|
|
284
624
|
}
|
|
285
625
|
if (node.states) {
|
|
286
626
|
const states: Record<string, LooseState> = {};
|
|
287
|
-
for (const [key, child] of Object.entries(node.states))
|
|
627
|
+
for (const [key, child] of Object.entries(node.states))
|
|
628
|
+
states[key] = walk(child, names, written, [...path, key], owner);
|
|
288
629
|
out = { ...(out === node ? node : out), states };
|
|
289
630
|
}
|
|
290
631
|
return out;
|
|
291
632
|
};
|
|
292
633
|
|
|
293
|
-
|
|
634
|
+
const out = walk(config as LooseState, new Map(), new Map(), [], NOBODY);
|
|
635
|
+
return { config: out, seats, warnings: [...warnings.values()], derivedGateIds };
|
|
294
636
|
}
|
|
295
637
|
|
|
296
638
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
639
|
+
* A mode that nothing enters (ADR-0015): a state below an Agent's state with no invoke that the
|
|
640
|
+
* Machine can never enter. Its picks are on the Menu and never allowed. A warning, not a refusal: the
|
|
641
|
+
* Machine is legal, and a mode left unwired is often work in progress. A state below an Agent's state
|
|
642
|
+
* that INVOKES something and that nothing enters is refused (ADR-0067): what it invokes never runs,
|
|
643
|
+
* which is never work in progress. Both look below a state that invokes an Effect or a child Machine;
|
|
644
|
+
* neither looks below one that invokes another Agent or a Gate.
|
|
299
645
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* walks every registered Machine.
|
|
646
|
+
* "Can enter" is reachability from the root, to a fixed point. A state is entered when its parent is
|
|
647
|
+
* entered and it is the parent's initial state or a region of a parallel parent, or when a transition
|
|
648
|
+
* whose SOURCE can be entered targets it or a state inside it (entering a state enters its
|
|
649
|
+
* ancestors). So a compound mode's own `initial` does not enter it, and neither does a transition
|
|
650
|
+
* written on the mode into itself: neither runs unless the mode is already active.
|
|
306
651
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
652
|
+
* A transition into a history state enters what that history state restores. The walk follows its
|
|
653
|
+
* DEFAULT target — shallow or deep, what xstate enters when nothing is recorded yet — or, with none
|
|
654
|
+
* written, its parent's initial state, which entering the parent already covers. A recorded history
|
|
655
|
+
* restores only states that were entered another way. A default target the walk cannot resolve is a
|
|
656
|
+
* warning of its own: the modes behind it may read as unentered when they are not, and a state that
|
|
657
|
+
* invokes below that history state's parent is warned about, never refused — the walk refuses only
|
|
658
|
+
* what it knows nothing enters. A history state is no mode.
|
|
309
659
|
*/
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
660
|
+
function unenteredModes(machine: AnyStateMachine, actors: Record<string, UnknownActorLogic>): string[] {
|
|
661
|
+
type Node = AnyStateMachine["root"];
|
|
662
|
+
const all: Node[] = [];
|
|
663
|
+
const collect = (node: Node): void => {
|
|
664
|
+
all.push(node);
|
|
665
|
+
for (const child of Object.values(node.states)) collect(child);
|
|
666
|
+
};
|
|
667
|
+
collect(machine.root);
|
|
668
|
+
|
|
669
|
+
const out: string[] = [];
|
|
670
|
+
const entered = new Set<Node>([machine.root]);
|
|
671
|
+
const enter = (node: Node): boolean => {
|
|
672
|
+
let added = false;
|
|
673
|
+
for (let n: Node | undefined = node; n !== undefined && !entered.has(n); n = n.parent) {
|
|
674
|
+
entered.add(n);
|
|
675
|
+
added = true;
|
|
676
|
+
}
|
|
677
|
+
if (node.type === "history") for (const target of historyDefault(node)) added = enter(target) || added;
|
|
678
|
+
return added;
|
|
679
|
+
};
|
|
680
|
+
/** A history state's default target, resolved the way xstate resolves it: a path from its parent,
|
|
681
|
+
* or a `#id`. Unresolvable: warned once, and nothing is followed. */
|
|
682
|
+
const unresolved = new Set<Node>();
|
|
683
|
+
const historyDefault = (node: Node): Node[] => {
|
|
684
|
+
const written = (node.config as { target?: unknown }).target;
|
|
685
|
+
if (written === undefined) return [];
|
|
686
|
+
const targets = (Array.isArray(written) ? written : [written]) as unknown[];
|
|
687
|
+
const resolved: Node[] = [];
|
|
688
|
+
for (const target of targets) {
|
|
689
|
+
const found = typeof target === "string" ? resolvePath(node.parent!, target) : undefined;
|
|
690
|
+
if (found !== undefined) {
|
|
691
|
+
resolved.push(found);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (unresolved.has(node)) continue;
|
|
695
|
+
unresolved.add(node);
|
|
696
|
+
out.push(
|
|
697
|
+
`history state "${node.id}" has a default target the load walk cannot follow (${JSON.stringify(target)}) — ` +
|
|
698
|
+
`a mode entered only through it is reported as unentered, and a state that invokes is warned, not refused`,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return resolved;
|
|
702
|
+
};
|
|
703
|
+
const resolvePath = (parent: Node, target: string): Node | undefined => {
|
|
704
|
+
if (target.startsWith("#")) {
|
|
705
|
+
try {
|
|
706
|
+
return machine.getStateNodeById(target.slice(1)) as Node;
|
|
707
|
+
} catch {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
let at: Node | undefined = parent;
|
|
712
|
+
for (const key of target.split(".")) at = at?.states[key];
|
|
713
|
+
return at;
|
|
714
|
+
};
|
|
715
|
+
for (let changed = true; changed; ) {
|
|
716
|
+
changed = false;
|
|
717
|
+
for (const node of all) {
|
|
718
|
+
const parent = node.parent;
|
|
719
|
+
if (parent === undefined || entered.has(node) || !entered.has(parent)) continue;
|
|
720
|
+
if (parent.type === "parallel" || (parent.type === "compound" && parent.initial.target.includes(node)))
|
|
721
|
+
changed = enter(node) || changed;
|
|
722
|
+
}
|
|
723
|
+
for (const node of all) {
|
|
724
|
+
if (!entered.has(node)) continue;
|
|
725
|
+
const transitions = [...node.transitions.values()].flat().concat(node.always ?? []);
|
|
726
|
+
for (const t of transitions) for (const target of t.target ?? []) changed = enter(target) || changed;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const invokedAgent = (node: Node): string | undefined =>
|
|
730
|
+
node.invoke.find((inv) => typeof inv.src === "string" && isAgent(actors[inv.src]))?.src as string | undefined;
|
|
731
|
+
const isBelow = (node: Node, ancestor: Node): boolean => {
|
|
732
|
+
for (let n = node.parent; n !== undefined; n = n.parent) if (n === ancestor) return true;
|
|
733
|
+
return false;
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const refused: string[] = [];
|
|
737
|
+
for (const node of all) {
|
|
738
|
+
const agent = invokedAgent(node);
|
|
739
|
+
// An Agent's state the Machine never enters is a larger defect than its modes, and not this check.
|
|
740
|
+
if (agent === undefined || !entered.has(node)) continue;
|
|
741
|
+
// Every state below the Agent's state belongs to its Turn, down to a child that invokes another
|
|
742
|
+
// party (an Agent or a Gate): that child is checked, and below it is that party's. A state below
|
|
743
|
+
// an unentered one is not reported again.
|
|
744
|
+
const descend = (parent: Node): void => {
|
|
745
|
+
for (const child of Object.values(parent.states)) {
|
|
746
|
+
if (child.type === "history") continue;
|
|
747
|
+
if (child.invoke.length > 0) {
|
|
748
|
+
// A child that invokes something and that nothing enters (ADR-0067): what it invokes never
|
|
749
|
+
// runs — a refusal, where a mode nothing enters is only a warning.
|
|
750
|
+
if (!entered.has(child)) {
|
|
751
|
+
const what = child.invoke.map((inv) => (typeof inv.src === "string" ? inv.src : inv.id)).join(", ");
|
|
752
|
+
// Where a history default the walk cannot follow may enter it, the walk does not know: a
|
|
753
|
+
// warning, never a refusal of a Machine that may be legal.
|
|
754
|
+
const unsure = [...unresolved].find((h) => isBelow(child, h.parent!));
|
|
755
|
+
if (unsure !== undefined) {
|
|
756
|
+
out.push(
|
|
757
|
+
`state "${child.id}" below Agent "${agent}"'s state "${node.id}" may be entered by nothing, so what ` +
|
|
758
|
+
`it invokes (${what}) may never run — the walk cannot follow history state "${unsure.id}"'s ` +
|
|
759
|
+
`default target, which may enter it`,
|
|
760
|
+
);
|
|
761
|
+
} else {
|
|
762
|
+
refused.push(
|
|
763
|
+
`state "${child.id}" below Agent "${agent}"'s state "${node.id}" is entered by nothing, so what it ` +
|
|
764
|
+
`invokes (${what}) never runs — target it from a pick or a route, or delete it`,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const party = child.invoke.some(
|
|
770
|
+
(inv) => inv.src === "gate" || (typeof inv.src === "string" && isAgent(actors[inv.src])),
|
|
771
|
+
);
|
|
772
|
+
if (!party) descend(child);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (!entered.has(child)) {
|
|
776
|
+
const picks = [...child.transitions.keys()].filter((key) => !key.includes(".") && key !== "*");
|
|
777
|
+
out.push(
|
|
778
|
+
`mode "${child.id}" of Agent "${agent}"'s state "${node.id}" — nothing enters it: it is not ` +
|
|
779
|
+
`initial and no state the Machine can enter has a transition into it, so its picks` +
|
|
780
|
+
`${picks.length ? ` (${picks.join(", ")})` : ""} are on the Menu and never allowed`,
|
|
781
|
+
);
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
descend(child);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
descend(node);
|
|
788
|
+
}
|
|
789
|
+
if (refused.length) throw new Error(`machine "${machine.id}": ${refused.join("; ")}`);
|
|
790
|
+
return out;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Does this logic settle on its own — a promise, the one plain xstate actor that ends without being
|
|
795
|
+
* stopped? Asked of its own transition: a promise logic's initial snapshot, sent xstate's resolve
|
|
796
|
+
* event, is `done`. That event name is an xstate internal (5.32.2); a bump that renamed it would
|
|
797
|
+
* make every promise read long-lived, and setup.test.ts pins it against the real constructors.
|
|
798
|
+
*/
|
|
799
|
+
function settlesOnItsOwn(logic: unknown): boolean {
|
|
800
|
+
const l = logic as {
|
|
801
|
+
getInitialSnapshot?: (scope: unknown, input: unknown) => unknown;
|
|
802
|
+
transition?: (snapshot: unknown, event: unknown, scope: unknown) => { status?: string } | undefined;
|
|
803
|
+
};
|
|
804
|
+
if (typeof l?.getInitialSnapshot !== "function" || typeof l.transition !== "function") return false;
|
|
805
|
+
try {
|
|
806
|
+
const scope = { self: {}, defer: () => {}, emit: () => {} };
|
|
807
|
+
const snapshot = l.getInitialSnapshot(scope, undefined);
|
|
808
|
+
return l.transition(snapshot, { type: "xstate.promise.resolve", data: undefined }, scope)?.status === "done";
|
|
809
|
+
} catch {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* The load walk's Rest warnings (ADR-0024): shapes below an Agent's state that keep it from Rest, so
|
|
816
|
+
* every pick of the Agent is refused for longer than the author likely meant. Warnings, never
|
|
817
|
+
* refusals: each can be intended.
|
|
818
|
+
*
|
|
819
|
+
* - **A Gate whose answer has no target.** The Gate stays open after its answer, and the Agent's
|
|
820
|
+
* state is not at Rest again until the Gate's state exits.
|
|
821
|
+
* - **A long-lived invoke** — an actor that runs until its state exits: not a promise or an Effect (each settles),
|
|
822
|
+
* a Gate, an Agent or a child Machine. While it runs, the Agent's state is never at Rest.
|
|
823
|
+
*
|
|
824
|
+
* The descent below an Agent's state stops at a child that invokes another Agent: below it is that
|
|
825
|
+
* Agent's state, read for that Agent.
|
|
826
|
+
*/
|
|
827
|
+
function restWarnings(machine: AnyStateMachine, actors: Record<string, UnknownActorLogic>): string[] {
|
|
828
|
+
type Node = AnyStateMachine["root"];
|
|
829
|
+
const implementations = machine.implementations.actors as Record<string, unknown>;
|
|
830
|
+
const logicOf = (src: unknown): unknown => (typeof src === "string" ? (actors[src] ?? implementations[src]) : src);
|
|
831
|
+
const agentOf = (node: Node): string | undefined =>
|
|
832
|
+
node.invoke.find((inv) => typeof inv.src === "string" && isAgent(actors[inv.src]))?.src as string | undefined;
|
|
833
|
+
const out: string[] = [];
|
|
834
|
+
const visit = (node: Node): void => {
|
|
835
|
+
const agent = agentOf(node);
|
|
836
|
+
if (agent !== undefined) {
|
|
837
|
+
const descend = (parent: Node): void => {
|
|
838
|
+
for (const child of Object.values(parent.states)) {
|
|
839
|
+
if (agentOf(child) !== undefined) continue;
|
|
840
|
+
for (const inv of child.invoke) {
|
|
841
|
+
const logic = logicOf(inv.src);
|
|
842
|
+
const name = typeof inv.src === "string" ? inv.src : inv.id;
|
|
843
|
+
if (inv.src === "gate") {
|
|
844
|
+
for (const [event, candidates] of child.transitions) {
|
|
845
|
+
if (event.includes(".") || event === "*") continue;
|
|
846
|
+
if (candidates.some((t) => (t.target ?? []).length > 0)) continue;
|
|
847
|
+
out.push(
|
|
848
|
+
`Gate "${inv.id}" at "${child.id}", below Agent "${agent}"'s state "${node.id}": its answer "${event}" ` +
|
|
849
|
+
`has no target, so the Gate stays open after it — the Agent's state is not at rest again until ` +
|
|
850
|
+
`"${child.id}" exits, and every pick of the Agent is refused until then; give the answer a target`,
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
// An Open body (ADR-0070) runs a Chart, as a child Machine runs its states.
|
|
856
|
+
if (
|
|
857
|
+
isAgent(logic) ||
|
|
858
|
+
isEffect(logic) ||
|
|
859
|
+
isOpenBody(logic) ||
|
|
860
|
+
(logic as { root?: unknown } | undefined)?.root !== undefined
|
|
861
|
+
)
|
|
862
|
+
continue;
|
|
863
|
+
if (settlesOnItsOwn(logic)) continue;
|
|
864
|
+
out.push(
|
|
865
|
+
`"${child.id}", below Agent "${agent}"'s state "${node.id}", invokes "${name}", which runs until its ` +
|
|
866
|
+
`state exits (not a promise, an Effect, a Gate, an Agent or a child Machine): while it runs the Agent's state ` +
|
|
867
|
+
`is never at rest, so it blocks every pick of the Agent — invoke it above the Agent's state`,
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
descend(child);
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
descend(node);
|
|
874
|
+
}
|
|
875
|
+
for (const child of Object.values(node.states)) visit(child);
|
|
876
|
+
};
|
|
877
|
+
visit(machine.root);
|
|
878
|
+
return out;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Resolve and stamp every Open body slot's lent set (ADR-0070). Each name an Open body lends must be
|
|
883
|
+
* a part of this setup, of the kind it is lent as: an `agents` entry an Agent slot (an Open one too:
|
|
884
|
+
* its composer binds it as today), an `effects` entry an Effect, a `machines` entry a Machine, and a
|
|
885
|
+
* guard, action or delay one this setup implements. An unknown name refuses the createMachine naming
|
|
886
|
+
* it, so an Open body whose names did not resolve never reaches the walk.
|
|
887
|
+
*/
|
|
888
|
+
function stampOpenBodies(
|
|
889
|
+
machineId: string,
|
|
890
|
+
def: {
|
|
891
|
+
actors?: Record<string, unknown>;
|
|
892
|
+
guards?: Record<string, unknown>;
|
|
893
|
+
actions?: Record<string, unknown>;
|
|
894
|
+
delays?: Record<string, unknown>;
|
|
895
|
+
},
|
|
315
896
|
): void {
|
|
316
|
-
const
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
897
|
+
const actors = def.actors ?? {};
|
|
898
|
+
for (const [slot, logic] of Object.entries(actors)) {
|
|
899
|
+
if (!isOpenBody(logic)) continue;
|
|
900
|
+
const decl = openBodyDeclarationOf(logic);
|
|
901
|
+
const faults: string[] = [];
|
|
902
|
+
const lent: LentParts = { actors: {}, guards: {}, actions: {}, delays: {} };
|
|
903
|
+
const actorKinds = [
|
|
904
|
+
["agents", "an Agent slot (agent(...))", isAgent],
|
|
905
|
+
["effects", "an Effect slot", isEffect],
|
|
906
|
+
["machines", "a Machine", (l: unknown) => (l as { root?: unknown } | undefined)?.root !== undefined],
|
|
907
|
+
] as const;
|
|
908
|
+
for (const [kind, what, test] of actorKinds) {
|
|
909
|
+
for (const name of decl.lends[kind] ?? []) {
|
|
910
|
+
const found = actors[name];
|
|
911
|
+
if (name === slot) faults.push(`lends.${kind}: "${name}" is the Open body itself`);
|
|
912
|
+
else if (found === undefined) {
|
|
913
|
+
faults.push(
|
|
914
|
+
`lends.${kind}: "${name}" is no actor slot of this Machine; its actors: ${Object.keys(actors).join(", ")}`,
|
|
915
|
+
);
|
|
916
|
+
} else if (!test(found)) faults.push(`lends.${kind}: "${name}" is not ${what}`);
|
|
917
|
+
else lent.actors[name] = found as UnknownActorLogic;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const implKinds = [
|
|
921
|
+
["guards", def.guards ?? {}, lent.guards],
|
|
922
|
+
["actions", def.actions ?? {}, lent.actions],
|
|
923
|
+
["delays", def.delays ?? {}, lent.delays],
|
|
924
|
+
] as const;
|
|
925
|
+
for (const [kind, impl, into] of implKinds) {
|
|
926
|
+
for (const name of decl.lends[kind] ?? []) {
|
|
927
|
+
if (impl[name] === undefined) {
|
|
928
|
+
faults.push(
|
|
929
|
+
`lends.${kind}: "${name}" is no ${kind.slice(0, -1)} of this Machine; its ${kind}: ${Object.keys(impl).join(", ") || "none"}`,
|
|
930
|
+
);
|
|
931
|
+
} else (into as Record<string, unknown>)[name] = impl[name];
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (faults.length) throw new Error(`machine "${machineId}": Open body "${slot}": ${faults.join("; ")}`);
|
|
935
|
+
attachLent(logic, lent);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* A parallel region below a state that invokes an Agent is refused (ADR-0015). The shape is
|
|
941
|
+
* reserved for picks the Agent does not wait for, which jr2 does not build; a Turn takes one pick
|
|
942
|
+
* at a time. The Agent's state itself being parallel counts: its regions are below it. A
|
|
943
|
+
* Background work goes above the Agent's state.
|
|
944
|
+
*/
|
|
945
|
+
function refuseParallelBelowAgents(machine: AnyStateMachine, actors: Record<string, UnknownActorLogic>): void {
|
|
946
|
+
type Node = AnyStateMachine["root"];
|
|
947
|
+
const firstParallel = (node: Node): Node | undefined => {
|
|
948
|
+
if (node.type === "parallel") return node;
|
|
949
|
+
for (const child of Object.values(node.states)) {
|
|
950
|
+
const found = firstParallel(child);
|
|
951
|
+
if (found !== undefined) return found;
|
|
952
|
+
}
|
|
953
|
+
return undefined;
|
|
320
954
|
};
|
|
321
|
-
|
|
955
|
+
const visit = (node: Node): void => {
|
|
956
|
+
const agent = node.invoke.find((inv) => typeof inv.src === "string" && isAgent(actors[inv.src]))?.src;
|
|
957
|
+
const parallel = agent === undefined ? undefined : firstParallel(node);
|
|
958
|
+
if (parallel !== undefined) {
|
|
959
|
+
throw new Error(
|
|
960
|
+
`machine "${machine.id}": state "${parallel.id}" is parallel, at or below Agent "${String(agent)}"'s state ` +
|
|
961
|
+
`"${node.id}" — a parallel region below an Agent's state is reserved for picks the Agent does not wait ` +
|
|
962
|
+
`for, which jr2 does not build; background work goes above the Agent's state`,
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
for (const child of Object.values(node.states)) visit(child);
|
|
966
|
+
};
|
|
967
|
+
visit(machine.root);
|
|
968
|
+
}
|
|
322
969
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
970
|
+
/** The built state node at a key path below the root — what turns a walk's path into a state id. */
|
|
971
|
+
function stateAt(machine: AnyStateMachine, path: readonly string[]): AnyStateMachine["root"] {
|
|
972
|
+
let node = machine.root;
|
|
973
|
+
for (const key of path) node = node.states[key]!;
|
|
974
|
+
return node;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/** A state's own workflow-event keys: dotted names are jr2's or xstate's, `*` is the wildcard. */
|
|
978
|
+
function ownPicks(node: LooseState): string[] {
|
|
979
|
+
return Object.keys(node.on ?? {}).filter((key) => !key.includes(".") && key !== "*");
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/** The parties a state invokes: its Agent slots by name, and whether it invokes a `gate`. */
|
|
983
|
+
function partiesOf(node: LooseState, actors: Record<string, UnknownActorLogic>): { agents: string[]; gate: boolean } {
|
|
984
|
+
const invokes = node.invoke ? (Array.isArray(node.invoke) ? node.invoke : [node.invoke]) : [];
|
|
985
|
+
return {
|
|
986
|
+
agents: invokes.flatMap((inv) => (typeof inv?.src === "string" && isAgent(actors[inv.src]) ? [inv.src] : [])),
|
|
987
|
+
gate: invokes.some((inv) => inv?.src === "gate"),
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/** Does this state invoke a party that sends this Machine's workflow events — an Agent slot or a
|
|
992
|
+
* `gate`? An Effect, a child Machine, a `workspace()`/`pool()` body: none of them sends them. */
|
|
993
|
+
function invokesParty(node: LooseState, actors: Record<string, UnknownActorLogic>): boolean {
|
|
994
|
+
const { agents, gate } = partiesOf(node, actors);
|
|
995
|
+
return gate || agents.length > 0;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* The picks written on the states BELOW an Agent's state — its modes (ADR-0015). xstate hands an
|
|
1000
|
+
* event to the deepest active state that handles it, and a false guard there falls through to the
|
|
1001
|
+
* parent, so a child's `on` keys are picks the Turn can be allowed while that child is active — and
|
|
1002
|
+
* `can()` on the live snapshot already answers for the active child (ADR-0029).
|
|
1003
|
+
*
|
|
1004
|
+
* The descent STOPS at a child that invokes another party — an Agent slot (a nested Turn: its picks
|
|
1005
|
+
* are its own) or a `gate` (its `on` keys are the Gate's accepted set) — and leaves out everything
|
|
1006
|
+
* below it. A child that runs an Effect, a child Machine or a timer is no stop: its `on` keys are
|
|
1007
|
+
* this Turn's.
|
|
1008
|
+
*/
|
|
1009
|
+
function modePicks(node: LooseState, actors: Record<string, UnknownActorLogic>): Set<string> {
|
|
1010
|
+
return new Set(modeWritings(node, [], actors).map((w) => w.name));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/** Each writing {@link modePicks} reads, with the key path of the mode it is written on. */
|
|
1014
|
+
function modeWritings(
|
|
1015
|
+
node: LooseState,
|
|
1016
|
+
path: readonly string[],
|
|
1017
|
+
actors: Record<string, UnknownActorLogic>,
|
|
1018
|
+
): Array<{ name: string; path: readonly string[] }> {
|
|
1019
|
+
const out: Array<{ name: string; path: readonly string[] }> = [];
|
|
1020
|
+
const descend = (n: LooseState, at: readonly string[]): void => {
|
|
1021
|
+
for (const [key, child] of Object.entries(n.states ?? {})) {
|
|
1022
|
+
if (invokesParty(child, actors)) continue;
|
|
1023
|
+
const childPath = [...at, key];
|
|
1024
|
+
for (const name of ownPicks(child)) out.push({ name, path: childPath });
|
|
1025
|
+
descend(child, childPath);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
descend(node, path);
|
|
1029
|
+
return out;
|
|
334
1030
|
}
|
|
335
1031
|
|
|
336
1032
|
const resolveInput = (orig: unknown, args: InputArgs): Record<string, unknown> =>
|
|
@@ -367,9 +1063,9 @@ function refuseUnknownKeys(consumer: object, agentName: string): void {
|
|
|
367
1063
|
);
|
|
368
1064
|
}
|
|
369
1065
|
|
|
370
|
-
/** Wrap an Agent slot's invoke input: name the Agent (the slot key), append the derived
|
|
371
|
-
* finalize the mechanism fields. */
|
|
372
|
-
function wrapAgentInput(orig: unknown, derived: string[], agentName: string) {
|
|
1066
|
+
/** Wrap an Agent slot's invoke input: name the Agent (the slot key), append the state's derived set
|
|
1067
|
+
* and the Agent's Menu, and finalize the mechanism fields. */
|
|
1068
|
+
function wrapAgentInput(orig: unknown, derived: string[], agentName: string, menuOf: (agentName: string) => string[]) {
|
|
373
1069
|
return (args: InputArgs): AgentRunInput => {
|
|
374
1070
|
const consumer = resolveInput(orig, args) as Partial<AgentTurnInput & AgentRunInput>;
|
|
375
1071
|
refuseUnknownKeys(consumer, agentName);
|
|
@@ -378,6 +1074,9 @@ function wrapAgentInput(orig: unknown, derived: string[], agentName: string) {
|
|
|
378
1074
|
// jr2 mints every id (ADR-0016, ADR-0057): the Turn's input carries no id to honor, so
|
|
379
1075
|
// nothing a Machine writes can address a conversation jr2 did not derive.
|
|
380
1076
|
instanceId: mintIid(consumer, agentName, args.self),
|
|
1077
|
+
// One per invocation (ADR-0016): two Turns that continue one conversation share the iid, the
|
|
1078
|
+
// admission ledger's key, and a restore must not re-attach one Turn to the other's admission.
|
|
1079
|
+
turnId: randomUUID(),
|
|
381
1080
|
endpoint: consumer.endpoint,
|
|
382
1081
|
sandbox: consumer.sandbox,
|
|
383
1082
|
prompt: consumer.prompt,
|
|
@@ -391,8 +1090,11 @@ function wrapAgentInput(orig: unknown, derived: string[], agentName: string) {
|
|
|
391
1090
|
// ADR-0035's reroll gate: closed to `continue: true`, which names an EXISTING conversation
|
|
392
1091
|
// while the runaway's one recovery is a fresh one — exactly what it opted out of.
|
|
393
1092
|
...(consumer.continue === true ? { continuation: true } : {}),
|
|
394
|
-
// The
|
|
1093
|
+
// The state's derived set (ADR-0015): always the derived one, since ADR-0057 retired the
|
|
1094
|
+
// override. What delivery validates against, and what the Allowed picks narrow.
|
|
395
1095
|
tools: derived,
|
|
1096
|
+
// The Menu (ADR-0029): every pick any state invoking this Agent derives — the tools block.
|
|
1097
|
+
menu: menuOf(agentName),
|
|
396
1098
|
};
|
|
397
1099
|
};
|
|
398
1100
|
}
|