@jr2/orchestrator 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +142 -29
- package/docs/concepts/architecture.md +60 -34
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +13 -8
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +7 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +133 -50
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +14 -4
- package/docs/concepts/pools-and-sources.md +193 -17
- package/docs/concepts/secrets-and-trust.md +186 -62
- package/docs/concepts/the-menu.md +14 -0
- package/docs/concepts/workspaces.md +135 -56
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +72 -15
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +29 -12
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +19 -7
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +14 -7
- package/docs/guides/look-inside-a-sandbox.md +115 -42
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +34 -27
- package/docs/guides/set-an-agents-access-and-dials.md +69 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +7 -1
- package/docs/guides/watch-a-run.md +146 -117
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +18 -8
- package/docs/llms.txt +110 -23
- package/docs/reference/authoring-surface/agent.md +35 -12
- package/docs/reference/authoring-surface/answer.md +20 -4
- package/docs/reference/authoring-surface/arg.md +28 -6
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +37 -22
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +18 -5
- package/docs/reference/authoring-surface/file.md +37 -17
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +109 -9
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +14 -4
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +41 -8
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +23 -5
- package/docs/reference/authoring-surface/pool.md +79 -11
- package/docs/reference/authoring-surface/push.md +31 -13
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +26 -5
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +59 -4
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +6 -7
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
- package/docs/reference/authoring-surface/type-customize.md +20 -3
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +6 -6
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +30 -9
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +66 -38
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +88 -147
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +248 -6
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +9 -4
- package/docs/reference/glossary.md +138 -45
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +988 -123
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +7 -5
- package/package.json +10 -3
- package/src/actor.ts +194 -42
- package/src/agent.ts +56 -6
- package/src/ambient.ts +3 -5
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +238 -26
- package/src/charts.ts +9 -2
- package/src/config.ts +566 -7
- package/src/conversations.ts +453 -0
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +257 -77
- package/src/customize.ts +154 -7
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +52 -4
- package/src/effect.ts +401 -132
- package/src/effector-wire.ts +278 -30
- package/src/fail.ts +17 -4
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +84 -35
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +197 -39
- package/src/holds.ts +5 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +164 -70
- package/src/http.ts +933 -99
- package/src/index.ts +30 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +23 -3
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +29 -5
- package/src/mcp-effect.ts +424 -0
- package/src/model-keys.ts +24 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +82 -7
- package/src/notices.ts +576 -59
- package/src/open-body.ts +110 -30
- package/src/open-effect.ts +17 -4
- package/src/open.ts +40 -7
- package/src/parts.ts +125 -35
- package/src/party.ts +35 -1
- package/src/pick.ts +43 -9
- package/src/pool.ts +1172 -172
- package/src/registration.ts +249 -17
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +1614 -190
- package/src/run-list.ts +191 -0
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +116 -21
- package/src/secret-placement.ts +248 -35
- package/src/server.ts +53 -4
- package/src/setup.ts +158 -38
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +362 -46
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +27 -10
- package/src/workspace.ts +314 -259
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
package/src/parts.ts
CHANGED
|
@@ -48,15 +48,18 @@
|
|
|
48
48
|
import { basename } from "node:path";
|
|
49
49
|
import { fileURLToPath } from "node:url";
|
|
50
50
|
import type { AnyActorRef, AnyStateMachine, StateNode, UnknownActorLogic } from "xstate";
|
|
51
|
-
import { isAgent, isOpenAgent, type AgentDeclaration } from "./agent.ts";
|
|
52
|
-
import { argvText, isEffect, placementOf, type EffectParts, type Placement } from "./effect.ts";
|
|
51
|
+
import { isAgent, isOpenAgent, type AgentDeclaration, type AgentMcpServer, type WorkspaceAccess } from "./agent.ts";
|
|
52
|
+
import { argvText, isEffect, placementOf, type EffectParts, type EffectSlot, type Placement } from "./effect.ts";
|
|
53
|
+
import { isGateSlot } from "./gate.ts";
|
|
53
54
|
import { effectBindingLine, openPartsOf, type OpenEffectPart } from "./open-effect.ts";
|
|
54
55
|
import { isFailSlot } from "./fail.ts";
|
|
55
56
|
import { isImageContext } from "./images.ts";
|
|
56
57
|
import { isOpenBody, openBodyDeclarationOf, type OpenBodyDeclaration } from "./open-body.ts";
|
|
57
58
|
import { open } from "./open.ts";
|
|
58
59
|
import { repoIdentity } from "./repo-identity.ts";
|
|
60
|
+
import { refuseUnroutedOutcome } from "./effect-checks.ts";
|
|
59
61
|
import { refuseUnroutedAgentFault } from "./mechanism-routes.ts";
|
|
62
|
+
import { refuseSlotActiveTwice } from "./party.ts";
|
|
60
63
|
import { refuseLostOutput } from "./run-output.ts";
|
|
61
64
|
import { userSeatOf, type Size, type UserContainer } from "./size.ts";
|
|
62
65
|
|
|
@@ -75,9 +78,9 @@ import { userSeatOf, type Size, type UserContainer } from "./size.ts";
|
|
|
75
78
|
export { open } from "./open.ts";
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
|
-
* The repository of a Repo Slot: `{ url, ref }`. `url` is the Repo's git url. `ref` is the branch
|
|
79
|
-
* or commit that the branch
|
|
80
|
-
* url is needed, a string is enough.
|
|
81
|
+
* The repository of a Repo Slot: `{ url, ref }`. `url` is the Repo's git url. `ref` is the branch,
|
|
82
|
+
* the tag or the commit that the clone stays on, and that a new branch starts from; absent, the
|
|
83
|
+
* Repo's default branch. Where only a url is needed, a string is enough.
|
|
81
84
|
*
|
|
82
85
|
* `workspace()` and `customize()` refuse:
|
|
83
86
|
* - an empty `url`: ``<where>: Repo Slot "<slot>" is bound to an empty url (got <value>) — a slot
|
|
@@ -102,7 +105,6 @@ export { open } from "./open.ts";
|
|
|
102
105
|
*
|
|
103
106
|
* export const machine = workspace(body, {
|
|
104
107
|
* repos: { app },
|
|
105
|
-
* spec: () => ({ branch: "jr2/hotfix" }),
|
|
106
108
|
* });
|
|
107
109
|
* ```
|
|
108
110
|
*
|
|
@@ -113,8 +115,8 @@ export { open } from "./open.ts";
|
|
|
113
115
|
export type Binding = {
|
|
114
116
|
/** The Repo's git url. */
|
|
115
117
|
url: string;
|
|
116
|
-
/** The branch or commit that the branch
|
|
117
|
-
* branch. */
|
|
118
|
+
/** The branch, tag or commit that the clone stays on, and that a new branch starts from. Default:
|
|
119
|
+
* the Repo's default branch. */
|
|
118
120
|
ref?: string;
|
|
119
121
|
};
|
|
120
122
|
|
|
@@ -130,10 +132,11 @@ export type Binding = {
|
|
|
130
132
|
* - a function of the run input, `({ input }) => url` or a {@link Binding}: each run chooses. The
|
|
131
133
|
* url must match an entry of `git.credentials` in `jr2.config.ts`, or the run cannot use it.
|
|
132
134
|
*
|
|
133
|
-
* The key of the slot is a directory name under `/work`. It starts with a letter,
|
|
134
|
-
* `workspace()` and `customize()` refuse a key that does
|
|
135
|
-
* not a directory name — a slot becomes `/work/<slot
|
|
136
|
-
*
|
|
135
|
+
* The key of the slot is a directory name under `/work`. It starts with a letter, so never with
|
|
136
|
+
* `.`: `/work/.jr2/` is reserved for jr2. `workspace()` and `customize()` refuse a key that does
|
|
137
|
+
* not: ``<where>: Repo Slot key "<key>" is not a directory name — a slot becomes `/work/<slot>`,
|
|
138
|
+
* `/work/.jr2/` is reserved for jr2, and its order matters, so it starts with a letter and matches
|
|
139
|
+
* /^[A-Za-z][A-Za-z0-9._-]*$/ (ADR-0051, ADR-0075).`` They refuse a value of another
|
|
137
140
|
* shape: ``<where>: Repo Slot "<slot>" is <what> — a slot is `open`, a url, `{ url, ref? }`, or a
|
|
138
141
|
* mapper `({ input }) => url | { url, ref? }` over the door (ADR-0051).``
|
|
139
142
|
*
|
|
@@ -163,7 +166,6 @@ export type Binding = {
|
|
|
163
166
|
* docs: "https://github.com/you/docs.git",
|
|
164
167
|
* config: open,
|
|
165
168
|
* },
|
|
166
|
-
* spec: ({ input }) => ({ branch: `deploy/${input.service}` }),
|
|
167
169
|
* });
|
|
168
170
|
* ```
|
|
169
171
|
*
|
|
@@ -195,12 +197,13 @@ export function repoSlotState(value: RepoSlot<any>): RepoSlotState {
|
|
|
195
197
|
* carry — and to what a prompt can name without quoting. It starts with a LETTER: the handles
|
|
196
198
|
* keep declaration order, which a Machine may give a meaning to (`task`: the first slot is the
|
|
197
199
|
* one the coder edits), and JS puts an integer-like key such as `"1"` ahead of every other key in
|
|
198
|
-
* `Object.keys`, wherever the author wrote it.
|
|
200
|
+
* `Object.keys`, wherever the author wrote it. So a key never starts with `.`, and `/work/.jr2/`
|
|
201
|
+
* stays jr2's (ADR-0075). */
|
|
199
202
|
export const REPO_SLOT_KEY = /^[A-Za-z][A-Za-z0-9._-]*$/;
|
|
200
203
|
|
|
201
204
|
/**
|
|
202
|
-
* Refuse a malformed slot value BY NAME, at build time —
|
|
203
|
-
*
|
|
205
|
+
* Refuse a malformed slot value BY NAME, at build time — a derives-from-a-typo fault, one build
|
|
206
|
+
* earlier. A bound url is also parsed here: an
|
|
204
207
|
* identity the walk cannot derive (a relative path, an unknown scheme) is refused where the
|
|
205
208
|
* author wrote it rather than at the converge that walks it. `where` names the caller
|
|
206
209
|
* (`workspace()`, `customize()`).
|
|
@@ -209,8 +212,8 @@ export function assertRepoSlot(where: string, slot: string, value: unknown): ass
|
|
|
209
212
|
if (!REPO_SLOT_KEY.test(slot)) {
|
|
210
213
|
throw new Error(
|
|
211
214
|
`${where}: Repo Slot key ${JSON.stringify(slot)} is not a directory name — a slot becomes ` +
|
|
212
|
-
"`/work/<slot
|
|
213
|
-
"/^[A-Za-z][A-Za-z0-9._-]*$/ (ADR-0051).",
|
|
215
|
+
"`/work/<slot>`, `/work/.jr2/` is reserved for jr2, and its order matters, so it starts with a letter " +
|
|
216
|
+
"and matches /^[A-Za-z][A-Za-z0-9._-]*$/ (ADR-0051, ADR-0075).",
|
|
214
217
|
);
|
|
215
218
|
}
|
|
216
219
|
if (value === open || typeof value === "function") return;
|
|
@@ -242,9 +245,8 @@ export function assertRepoSlot(where: string, slot: string, value: unknown): ass
|
|
|
242
245
|
* to a concrete ref on the port's side, which is what keeps the Machine cluster-agnostic. The
|
|
243
246
|
* Repos are Slots, each bound, open, or per-run.
|
|
244
247
|
*
|
|
245
|
-
* They are options
|
|
246
|
-
*
|
|
247
|
-
* evaluate.
|
|
248
|
+
* They are static options because `jr2 up` must find them by walking the Machine, and no walk can
|
|
249
|
+
* evaluate a function of run input.
|
|
248
250
|
*/
|
|
249
251
|
export type SandboxParts = {
|
|
250
252
|
/** The Sandbox Image. Absent → the Instance's `images/default`, then the stock Harness. */
|
|
@@ -258,6 +260,8 @@ export type SandboxParts = {
|
|
|
258
260
|
/** The Size the Machine states (ADR-0060) — the `workspace()`'s, with any `customize()` layered
|
|
259
261
|
* over it. Absent → the Instance's `sandbox.resources`, then the kit default. */
|
|
260
262
|
resources?: Size;
|
|
263
|
+
/** The pod's work group (ADR-0005). Absent: the port's default. */
|
|
264
|
+
workGroup?: number;
|
|
261
265
|
/** The Held secrets this Workspace's pod gets beside its Agents' model keys (ADR-0059): names of
|
|
262
266
|
* the `secrets` list. Naming is placement — no other pod gets a name for being listed here. */
|
|
263
267
|
secrets?: string[];
|
|
@@ -561,14 +565,15 @@ export type CarriedEffect = {
|
|
|
561
565
|
(| {
|
|
562
566
|
kind: "command";
|
|
563
567
|
argv: string[];
|
|
564
|
-
slot?: string;
|
|
565
568
|
secrets?: string[];
|
|
566
569
|
pin?: true;
|
|
567
570
|
open?: Array<"argv" | "secret">;
|
|
571
|
+
/** A `worktree()`: its `expect`. A report prints it in place of the kit program's argv. */
|
|
572
|
+
cut?: { expect?: "new" | "existing" };
|
|
568
573
|
}
|
|
569
574
|
/** An http Effect (ADR-0068): its method, its fixed target, and the secrets it names — what
|
|
570
575
|
* places each secret, and what its Effector's config allows. `file`: its body is a Worktree
|
|
571
|
-
* file (ADR-0067), so it runs in its Workspace's Sandbox Effector
|
|
576
|
+
* file (ADR-0067), so it runs in its Workspace's Sandbox Effector.
|
|
572
577
|
* An Open url has no target, and names only the secrets its headers and auth name. */
|
|
573
578
|
| {
|
|
574
579
|
kind: "http";
|
|
@@ -577,10 +582,9 @@ export type CarriedEffect = {
|
|
|
577
582
|
secrets: string[];
|
|
578
583
|
idempotent: boolean;
|
|
579
584
|
file?: true;
|
|
580
|
-
slot?: string;
|
|
581
585
|
open?: Array<"url" | "secret">;
|
|
582
586
|
}
|
|
583
|
-
/** A push (ADR-0067, ADR-
|
|
587
|
+
/** A push (ADR-0067, ADR-0075): a cut Worktree's branch to its Repo Slot's Binding. It names no
|
|
584
588
|
* secret: `jr2 up` resolves its credential through `git.credentials`. */
|
|
585
589
|
| { kind: "push"; slot?: string; sign: boolean; squash: boolean; force: boolean; pin: boolean; secrets: string[] }
|
|
586
590
|
/** A dispatch (ADR-0071): the Workflow it starts a run of, as `jr2 run` names it, whether it
|
|
@@ -598,6 +602,20 @@ export type CarriedEffect = {
|
|
|
598
602
|
input?: Record<string, unknown>;
|
|
599
603
|
open?: Array<"target">;
|
|
600
604
|
}
|
|
605
|
+
/** An mcp Effect (ADR-0079): what it asks of which server, by the server's name in the `mcp`
|
|
606
|
+
* block of `jr2.config.ts`. It names no secret of its own: `jr2 up` reads the server's url and
|
|
607
|
+
* secret from the config and places them in the Instance Effector. An Open server or tool is
|
|
608
|
+
* absent until bound. `timeoutMs` is the author's; 0 when the author set none, and the server's
|
|
609
|
+
* `timeout` then applies. */
|
|
610
|
+
| {
|
|
611
|
+
kind: "mcp";
|
|
612
|
+
op: "call" | "read" | "prompt";
|
|
613
|
+
server?: string;
|
|
614
|
+
tool?: string;
|
|
615
|
+
secrets: string[];
|
|
616
|
+
idempotent: boolean;
|
|
617
|
+
open?: Array<"server" | "tool">;
|
|
618
|
+
}
|
|
601
619
|
);
|
|
602
620
|
|
|
603
621
|
/** One state that can fail the run (ADR-0073): it invokes a `fail()` slot. `state` is the state's
|
|
@@ -646,9 +664,22 @@ export type CarriedParts = {
|
|
|
646
664
|
/**
|
|
647
665
|
* The Instance Harness is needed (ADR-0031): an Agent declares `workspace: "none"`, or an Open
|
|
648
666
|
* body allows it. `models` are the models its Turns may run: those Agents', and those Open bodies'
|
|
649
|
-
* allowed ones.
|
|
667
|
+
* allowed ones. `servers` are the MCP servers those Agents name (ADR-0079), sorted, deduped.
|
|
668
|
+
* Absent when nothing needs it.
|
|
650
669
|
*/
|
|
651
|
-
instanceHarness?: { models: string[] };
|
|
670
|
+
instanceHarness?: { models: string[]; servers: string[] };
|
|
671
|
+
/** Every Agent slot whose definition names an MCP server (ADR-0079), at each `customize()` route
|
|
672
|
+
* the walk reached it by, in walk order: what `jr2 up` checks against the config's `mcp` block. */
|
|
673
|
+
mcpAgents: CarriedMcpAgent[];
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
/** An Agent that names MCP servers (ADR-0079): its slot, the `customize()` route to its Machine,
|
|
677
|
+
* where its Turns run, and its `mcp` list as written. */
|
|
678
|
+
export type CarriedMcpAgent = {
|
|
679
|
+
slot: string;
|
|
680
|
+
path: OpenSlot["path"];
|
|
681
|
+
workspace: WorkspaceAccess;
|
|
682
|
+
mcp: readonly AgentMcpServer[];
|
|
652
683
|
};
|
|
653
684
|
|
|
654
685
|
/** A machine actor, told apart from a promise/callback/observable one by having a state tree.
|
|
@@ -739,6 +770,7 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
739
770
|
const openEffects: CarriedParts["openEffects"] = [];
|
|
740
771
|
const sizes: CarriedSize[] = [];
|
|
741
772
|
const openBodies: CarriedParts["openBodies"] = [];
|
|
773
|
+
const mcpAgents: CarriedMcpAgent[] = [];
|
|
742
774
|
// The Machine that declares each Open body: where its lent Agents are read (not carried data).
|
|
743
775
|
const bodyWrappers = new Map<CarriedParts["openBodies"][number], AnyStateMachine>();
|
|
744
776
|
let sandboxed = false;
|
|
@@ -808,6 +840,9 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
808
840
|
refuseInlineParts(machine);
|
|
809
841
|
refuseLostOutput(machine);
|
|
810
842
|
refuseUnroutedAgentFault(machine);
|
|
843
|
+
refuseUnroutedOutcome(machine);
|
|
844
|
+
// Here too, for a Machine built with xstate's own setup: the Turns' identity is the runtime's.
|
|
845
|
+
refuseSlotActiveTwice(machine);
|
|
811
846
|
const parts = sandboxPartsOf(machine);
|
|
812
847
|
const user = userSeatOf(parts.user);
|
|
813
848
|
collectImage(parts.image);
|
|
@@ -826,8 +861,11 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
826
861
|
const body = wrapperBodyOf(machine);
|
|
827
862
|
for (const { state, slot } of failingStatesOf(machine)) failures.push({ machine: machine.id, state, slot, path });
|
|
828
863
|
for (const [name, logic] of Object.entries(machine.implementations.actors as Record<string, unknown>)) {
|
|
829
|
-
if (isAgent(logic))
|
|
830
|
-
|
|
864
|
+
if (isAgent(logic)) {
|
|
865
|
+
collectAgent(path, name, logic.definition);
|
|
866
|
+
const { mcp, workspace } = logic.definition;
|
|
867
|
+
if (mcp?.length) mcpAgents.push({ slot: name, path, workspace: workspace ?? "write", mcp });
|
|
868
|
+
} else if (isOpenBody(logic)) {
|
|
831
869
|
const body = { slot: name, declaration: openBodyDeclarationOf(logic), path };
|
|
832
870
|
openBodies.push(body);
|
|
833
871
|
bodyWrappers.set(body, machine);
|
|
@@ -836,6 +874,10 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
836
874
|
effects.push(effect);
|
|
837
875
|
if (effect.kind !== "push" && effect.open !== undefined)
|
|
838
876
|
openEffects.push({ slot: name, path, parts: [...effect.open] });
|
|
877
|
+
} else if (lookReadOf(logic)) {
|
|
878
|
+
// A Gate slot's look reads with an http Effect (ADR-0011): the Instance Effector runs it, so
|
|
879
|
+
// `jr2 up` converges the Effector and places its secrets as for any Effect slot.
|
|
880
|
+
effects.push(carriedEffectOf(`${name}.look`, path, route, lookReadOf(logic)!.effect));
|
|
839
881
|
} else {
|
|
840
882
|
const child = asMachine(logic);
|
|
841
883
|
if (child) walk(child, slotPath(path, name, body), name === body ? route : [...route, name]);
|
|
@@ -852,6 +894,7 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
852
894
|
const noneBodies = openBodies.filter((b) => bodyTurnsRunIn(bodyWrappers.get(b)!, b.declaration, "instance"));
|
|
853
895
|
const harnessModels = new Set<string>(menuOnly.map((a) => a.definition.model).filter(bound));
|
|
854
896
|
for (const body of noneBodies) for (const m of body.declaration.allows.models) harnessModels.add(m);
|
|
897
|
+
const harnessServers = new Set(menuOnly.flatMap((a) => (a.definition.mcp ?? []).map((e) => e.server)));
|
|
855
898
|
return {
|
|
856
899
|
agents,
|
|
857
900
|
failures,
|
|
@@ -865,7 +908,10 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
865
908
|
composesSandbox: sandboxed,
|
|
866
909
|
openBodies,
|
|
867
910
|
models: [...models].sort(),
|
|
868
|
-
...(menuOnly.length || noneBodies.length
|
|
911
|
+
...(menuOnly.length || noneBodies.length
|
|
912
|
+
? { instanceHarness: { models: [...harnessModels].sort(), servers: [...harnessServers].sort() } }
|
|
913
|
+
: {}),
|
|
914
|
+
mcpAgents,
|
|
869
915
|
};
|
|
870
916
|
}
|
|
871
917
|
|
|
@@ -918,6 +964,11 @@ export function placedEffectsOf(root: AnyStateMachine): CarriedEffect[] {
|
|
|
918
964
|
effects.push(carriedEffectOf(name, path, route, logic.effect));
|
|
919
965
|
continue;
|
|
920
966
|
}
|
|
967
|
+
const look = lookReadOf(logic);
|
|
968
|
+
if (look) {
|
|
969
|
+
effects.push(carriedEffectOf(`${name}.look`, path, route, look.effect));
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
921
972
|
const child = asMachine(logic);
|
|
922
973
|
if (child) walk(child, slotPath(path, name, body), name === body ? route : [...route, name], on);
|
|
923
974
|
}
|
|
@@ -968,7 +1019,22 @@ export function carriedEffectOf(
|
|
|
968
1019
|
idempotent: parts.idempotent,
|
|
969
1020
|
timeoutMs: parts.timeoutMs,
|
|
970
1021
|
...(parts.file ? { file: true as const } : {}),
|
|
971
|
-
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
if (parts.kind === "mcp") {
|
|
1025
|
+
return {
|
|
1026
|
+
name,
|
|
1027
|
+
path,
|
|
1028
|
+
route,
|
|
1029
|
+
kind: "mcp",
|
|
1030
|
+
placement,
|
|
1031
|
+
op: parts.op,
|
|
1032
|
+
...(parts.server === open ? {} : { server: parts.server }),
|
|
1033
|
+
...(parts.tool === undefined || parts.tool === open ? {} : { tool: parts.tool }),
|
|
1034
|
+
secrets: [],
|
|
1035
|
+
idempotent: parts.idempotent,
|
|
1036
|
+
timeoutMs: parts.timeoutMs ?? 0,
|
|
1037
|
+
...(openParts.length ? { open: openParts as Array<"server" | "tool"> } : {}),
|
|
972
1038
|
};
|
|
973
1039
|
}
|
|
974
1040
|
if (parts.kind === "push") {
|
|
@@ -988,7 +1054,7 @@ export function carriedEffectOf(
|
|
|
988
1054
|
...(slot !== undefined ? { slot } : {}),
|
|
989
1055
|
};
|
|
990
1056
|
}
|
|
991
|
-
const { argv, timeoutMs,
|
|
1057
|
+
const { argv, timeoutMs, secrets, pin } = parts;
|
|
992
1058
|
return {
|
|
993
1059
|
name,
|
|
994
1060
|
path,
|
|
@@ -997,10 +1063,10 @@ export function carriedEffectOf(
|
|
|
997
1063
|
placement,
|
|
998
1064
|
argv: argvText(argv),
|
|
999
1065
|
timeoutMs,
|
|
1000
|
-
...(slot !== undefined ? { slot } : {}),
|
|
1001
1066
|
...(secrets?.length ? { secrets: [...secrets] } : {}),
|
|
1002
1067
|
...(pin ? { pin } : {}),
|
|
1003
1068
|
...(openParts.length ? { open: openParts as Array<"argv" | "secret"> } : {}),
|
|
1069
|
+
...(parts.cut ? { cut: { ...parts.cut } } : {}),
|
|
1004
1070
|
};
|
|
1005
1071
|
}
|
|
1006
1072
|
|
|
@@ -1035,10 +1101,17 @@ export type WorkspacePod = {
|
|
|
1035
1101
|
* down to the next `workspace()`, which is a pod of its own, and every model an Open body below
|
|
1036
1102
|
* it allows a Chart's own Agents when it allows "write" or "read" (ADR-0070). Sorted, deduped. */
|
|
1037
1103
|
models: string[];
|
|
1104
|
+
/** The MCP servers that the Agents whose Turns run in this pod name (ADR-0079), with the Agent
|
|
1105
|
+
* that names each and its allowlist: what places each server's secret in this pod, and what the
|
|
1106
|
+
* allowlist rule reads. Absent when none names one. */
|
|
1107
|
+
mcp?: Array<{ slot: string; path: OpenSlot["path"]; entry: AgentMcpServer }>;
|
|
1038
1108
|
/** The Effects placed in this pod's Sandbox Effector (ADR-0068): every `push`, file-body `http` and
|
|
1039
1109
|
* credentialed `command` slot below the `workspace()`, down to the next `workspace()` — read off
|
|
1040
1110
|
* their plain data, statically. Empty → the pod gets no Sandbox Effector and no Secret. */
|
|
1041
1111
|
effects: CarriedEffect[];
|
|
1112
|
+
/** The slot of each `worktree()` below the `workspace()`, down to the next `workspace()`. A cut runs
|
|
1113
|
+
* in the Harness container, so `effects` does not hold it. Absent when there is none. */
|
|
1114
|
+
cuts?: string[];
|
|
1042
1115
|
/** The `workspace()`'s `effector: { image }`, as written. Absent → the Sandbox Image. */
|
|
1043
1116
|
effector?: { image: string };
|
|
1044
1117
|
/** The `workspace()`'s own `image`, as written — its Sandbox Image. Absent → `images/default`,
|
|
@@ -1067,6 +1140,8 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1067
1140
|
models: Set<string>,
|
|
1068
1141
|
on: Set<AnyStateMachine>,
|
|
1069
1142
|
effects: Map<string, CarriedEffect>,
|
|
1143
|
+
cuts: Set<string>,
|
|
1144
|
+
mcp: NonNullable<WorkspacePod["mcp"]>,
|
|
1070
1145
|
path: OpenSlot["path"],
|
|
1071
1146
|
route: PlacementRoute,
|
|
1072
1147
|
): void => {
|
|
@@ -1074,12 +1149,14 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1074
1149
|
on.add(machine);
|
|
1075
1150
|
const body = wrapperBodyOf(machine);
|
|
1076
1151
|
const visit = (child: AnyStateMachine, at: OpenSlot["path"], to: PlacementRoute) => {
|
|
1077
|
-
if (!composesSandbox(child)) modelsIn(child, models, on, effects, at, to);
|
|
1152
|
+
if (!composesSandbox(child)) modelsIn(child, models, on, effects, cuts, mcp, at, to);
|
|
1078
1153
|
};
|
|
1079
1154
|
for (const [name, logic] of Object.entries(machine.implementations.actors as Record<string, unknown>)) {
|
|
1080
1155
|
if (isAgent(logic)) {
|
|
1081
1156
|
const { model, workspace } = logic.definition;
|
|
1082
1157
|
if (workspace !== "none" && typeof model === "string") models.add(model);
|
|
1158
|
+
// A "none" Agent's servers are the Instance Harness's (ADR-0079), never this pod's.
|
|
1159
|
+
if (workspace !== "none") for (const entry of logic.definition.mcp ?? []) mcp.push({ slot: name, path, entry });
|
|
1083
1160
|
continue;
|
|
1084
1161
|
}
|
|
1085
1162
|
// A Turn under an Open body may run in this pod with any model the body allows (ADR-0070):
|
|
@@ -1093,6 +1170,7 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1093
1170
|
if (isEffect(logic)) {
|
|
1094
1171
|
const effect = carriedEffectOf(name, path, route, logic.effect);
|
|
1095
1172
|
if (effect.placement === "sandbox") effects.set(JSON.stringify([route, name]), effect);
|
|
1173
|
+
if (logic.effect.kind === "command" && logic.effect.cut) cuts.add(name);
|
|
1096
1174
|
continue;
|
|
1097
1175
|
}
|
|
1098
1176
|
const child = asMachine(logic);
|
|
@@ -1112,7 +1190,9 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1112
1190
|
if (composesSandbox(machine)) {
|
|
1113
1191
|
const models = new Set<string>();
|
|
1114
1192
|
const effects = new Map<string, CarriedEffect>();
|
|
1115
|
-
|
|
1193
|
+
const cuts = new Set<string>();
|
|
1194
|
+
const mcp: NonNullable<WorkspacePod["mcp"]> = [];
|
|
1195
|
+
modelsIn(machine, models, new Set(), effects, cuts, mcp, path, route);
|
|
1116
1196
|
const parts = sandboxPartsOf(machine);
|
|
1117
1197
|
const bindings: Record<string, string | undefined> = {};
|
|
1118
1198
|
if (parts.repos !== open) {
|
|
@@ -1127,6 +1207,8 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1127
1207
|
secrets: [...(parts.secrets ?? [])],
|
|
1128
1208
|
models: [...models].sort(),
|
|
1129
1209
|
effects: [...effects.values()],
|
|
1210
|
+
...(cuts.size ? { cuts: [...cuts] } : {}),
|
|
1211
|
+
...(mcp.length ? { mcp } : {}),
|
|
1130
1212
|
...(parts.effector ? { effector: { ...parts.effector } } : {}),
|
|
1131
1213
|
...(parts.image !== undefined ? { image: parts.image } : {}),
|
|
1132
1214
|
bindings,
|
|
@@ -1145,6 +1227,14 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1145
1227
|
return pods;
|
|
1146
1228
|
}
|
|
1147
1229
|
|
|
1230
|
+
/** The read of a Gate slot's bound look, when it is an Effect (ADR-0011); undefined otherwise. */
|
|
1231
|
+
function lookReadOf(logic: unknown): EffectSlot | undefined {
|
|
1232
|
+
if (!isGateSlot(logic)) return undefined;
|
|
1233
|
+
const look = logic.gateSlot.look;
|
|
1234
|
+
if (typeof look !== "object" || look === null) return undefined;
|
|
1235
|
+
return isEffect(look.read) ? look.read : undefined;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1148
1238
|
/** Every state of `machine`'s own tree that invokes a `fail()` slot (ADR-0073), in document order,
|
|
1149
1239
|
* with the slot key: a state that can fail the run. A child Machine's states are its own walk's. */
|
|
1150
1240
|
export function failingStatesOf(machine: AnyStateMachine): Array<{ state: string; slot: string }> {
|
package/src/party.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import type { AnyActorRef, AnyEventObject, AnyMachineSnapshot, AnyStateMachine } from "xstate";
|
|
20
20
|
import { SENDER_KEY, type Sender } from "@jr2/agent-protocol";
|
|
21
21
|
import { isAgent } from "./agent.ts";
|
|
22
|
+
import { isGateInvoke } from "./gate-brand.ts";
|
|
22
23
|
import { vocabularyOf } from "./vocabulary.ts";
|
|
23
24
|
|
|
24
25
|
/** The guard type `sentBy` names: dotted, so no author guard can take the name. */
|
|
@@ -138,7 +139,7 @@ export function partiesAt(node: Node, machine: AnyStateMachine): Sender[] {
|
|
|
138
139
|
const out: Sender[] = [];
|
|
139
140
|
for (const inv of node.invoke) {
|
|
140
141
|
if (typeof inv.src !== "string") continue;
|
|
141
|
-
if (inv
|
|
142
|
+
if (isGateInvoke(inv, actors)) out.push({ party: "gate", id: inv.id });
|
|
142
143
|
else if (isAgent(actors[inv.src])) out.push({ party: "agent", id: inv.src });
|
|
143
144
|
}
|
|
144
145
|
return out;
|
|
@@ -422,6 +423,39 @@ function orthogonal(a: Node, b: Node): boolean {
|
|
|
422
423
|
/** Can the two states be active at the same time? */
|
|
423
424
|
const together = (a: Node, b: Node): boolean => isAncestorOrSelf(a, b) || isAncestorOrSelf(b, a) || orthogonal(a, b);
|
|
424
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Refuse one Agent slot invoked by two states that can be active together (ADR-0015): in two
|
|
428
|
+
* parallel regions, one state inside the other, or twice by one state. A slot is one Agent of this
|
|
429
|
+
* Machine: its Turns share one sender, so `sentBy` cannot tell them apart and one pick matches both
|
|
430
|
+
* writings, and they share one conversation, so `continue` cannot hold two. Two Agents at once are
|
|
431
|
+
* two slots (one definition can fill both), a child Machine in each region, or a Pool (ADR-0075).
|
|
432
|
+
*/
|
|
433
|
+
export function refuseSlotActiveTwice(machine: AnyStateMachine): void {
|
|
434
|
+
const bySlot = new Map<string, Node[]>();
|
|
435
|
+
for (const node of allNodes(machine))
|
|
436
|
+
for (const p of partiesAt(node, machine))
|
|
437
|
+
if (p.party === "agent") bySlot.set(p.id, [...(bySlot.get(p.id) ?? []), node]);
|
|
438
|
+
for (const [slot, nodes] of bySlot) {
|
|
439
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
440
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
441
|
+
const [a, b] = [nodes[i]!, nodes[j]!];
|
|
442
|
+
if (!together(a, b)) continue;
|
|
443
|
+
const how =
|
|
444
|
+
a === b
|
|
445
|
+
? `state "${a.id}" invokes it twice`
|
|
446
|
+
: `"${a.id}" and "${b.id}" invoke it and can be active together ` +
|
|
447
|
+
(orthogonal(a, b) ? "(parallel regions)" : "(one inside the other)");
|
|
448
|
+
throw new Error(
|
|
449
|
+
`machine "${machine.id}": Agent "${slot}" would run two Turns at once: ${how}. A slot is one Agent, with ` +
|
|
450
|
+
"one sender and one conversation, so `sentBy` cannot tell its two Turns apart and `continue` cannot " +
|
|
451
|
+
"hold both. For two Agents at once, use two slots (one definition can fill both), a child Machine in " +
|
|
452
|
+
"each region, or a Pool (ADR-0075)",
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
425
459
|
/** One writing: one `on` key on one state, with its candidates (an array writing has several). */
|
|
426
460
|
type Writing = { node: Node; name: string; candidates: Transition[] };
|
|
427
461
|
|
package/src/pick.ts
CHANGED
|
@@ -63,9 +63,11 @@ import type { PickAnswer } from "./wire.ts";
|
|
|
63
63
|
* command,
|
|
64
64
|
* defineEvent,
|
|
65
65
|
* fail,
|
|
66
|
+
* isReady,
|
|
66
67
|
* jr2Setup,
|
|
67
68
|
* paramsOf,
|
|
68
69
|
* workspace,
|
|
70
|
+
* worktree,
|
|
69
71
|
* type Workspaced,
|
|
70
72
|
* } from "@jr2/orchestrator";
|
|
71
73
|
*
|
|
@@ -83,6 +85,8 @@ import type { PickAnswer } from "./wire.ts";
|
|
|
83
85
|
*
|
|
84
86
|
* type Input = Workspaced<{}, "app">;
|
|
85
87
|
*
|
|
88
|
+
* const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
89
|
+
*
|
|
86
90
|
* const body = jr2Setup({
|
|
87
91
|
* types: {} as { context: Input; input: Input },
|
|
88
92
|
* events: [runTest, finished],
|
|
@@ -91,6 +95,7 @@ import type { PickAnswer } from "./wire.ts";
|
|
|
91
95
|
* model: "anthropic/claude-haiku-5-5",
|
|
92
96
|
* instructions: "You are a careful engineer.",
|
|
93
97
|
* }),
|
|
98
|
+
* cut: worktree(),
|
|
94
99
|
* test: command(["node", "--test", arg("file")]),
|
|
95
100
|
* fail: fail(),
|
|
96
101
|
* },
|
|
@@ -98,12 +103,22 @@ import type { PickAnswer } from "./wire.ts";
|
|
|
98
103
|
* id: "fix-tests",
|
|
99
104
|
* context: ({ input }) => input,
|
|
100
105
|
* on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
101
|
-
* initial: "
|
|
106
|
+
* initial: "cutting",
|
|
102
107
|
* states: {
|
|
108
|
+
* cutting: {
|
|
109
|
+
* invoke: {
|
|
110
|
+
* src: "cut",
|
|
111
|
+
* input: ({ context }) => ({ path: fixTree(context), branch: "fix-tests" }),
|
|
112
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
113
|
+
* },
|
|
114
|
+
* },
|
|
103
115
|
* coding: {
|
|
104
116
|
* invoke: {
|
|
105
117
|
* src: "coder",
|
|
106
|
-
* input: {
|
|
118
|
+
* input: ({ context }) => ({
|
|
119
|
+
* prompt: "Make the tests pass. Run the tests of one module at a time.",
|
|
120
|
+
* cwd: fixTree(context),
|
|
121
|
+
* }),
|
|
107
122
|
* },
|
|
108
123
|
* on: { finished: "done" },
|
|
109
124
|
* initial: "ready",
|
|
@@ -113,13 +128,17 @@ import type { PickAnswer } from "./wire.ts";
|
|
|
113
128
|
* invoke: {
|
|
114
129
|
* src: "test",
|
|
115
130
|
* // A plain mapper: the Machine makes the file name from the pick's `module`.
|
|
116
|
-
* input: ({ event }) => ({
|
|
131
|
+
* input: ({ context, event }) => ({
|
|
132
|
+
* file: `test/${paramsOf(event, runTest).module}.test.js`,
|
|
133
|
+
* worktree: fixTree(context),
|
|
134
|
+
* }),
|
|
117
135
|
* onDone: "ready",
|
|
118
136
|
* },
|
|
119
137
|
* },
|
|
120
138
|
* },
|
|
121
139
|
* },
|
|
122
140
|
* done: { type: "final" },
|
|
141
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
123
142
|
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
124
143
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
125
144
|
* },
|
|
@@ -127,7 +146,6 @@ import type { PickAnswer } from "./wire.ts";
|
|
|
127
146
|
*
|
|
128
147
|
* export const machine = workspace(body, {
|
|
129
148
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
130
|
-
* spec: () => ({ branch: "fix-tests" }),
|
|
131
149
|
* });
|
|
132
150
|
* ```
|
|
133
151
|
*
|
|
@@ -406,8 +424,8 @@ export function fromPick<N extends string, I extends z.ZodObject, C, E extends E
|
|
|
406
424
|
}
|
|
407
425
|
|
|
408
426
|
/**
|
|
409
|
-
* One reply since the pick, as the author reads it
|
|
410
|
-
* (`src`); or an event that answered a state below the Agent's — a Gate's answer, a nested Agent's
|
|
427
|
+
* One reply since the pick, as the author reads it. It is an invoked actor's output, typed by its
|
|
428
|
+
* slot (`src`); or an event that answered a state below the Agent's — a Gate's answer, a nested Agent's
|
|
411
429
|
* pick — typed by the Machine's events. A reply is from outside the Machine: the events it sends
|
|
412
430
|
* itself are not links.
|
|
413
431
|
*/
|
|
@@ -640,8 +658,10 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
640
658
|
* command,
|
|
641
659
|
* defineEvent,
|
|
642
660
|
* fail,
|
|
661
|
+
* isReady,
|
|
643
662
|
* jr2Setup,
|
|
644
663
|
* workspace,
|
|
664
|
+
* worktree,
|
|
645
665
|
* type Workspaced,
|
|
646
666
|
* } from "@jr2/orchestrator";
|
|
647
667
|
*
|
|
@@ -659,6 +679,8 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
659
679
|
*
|
|
660
680
|
* type Input = Workspaced<{}, "app">;
|
|
661
681
|
*
|
|
682
|
+
* const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
683
|
+
*
|
|
662
684
|
* const body = jr2Setup({
|
|
663
685
|
* types: {} as { context: Input; input: Input },
|
|
664
686
|
* events: [runTests, finished],
|
|
@@ -667,6 +689,7 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
667
689
|
* model: "anthropic/claude-haiku-5-5",
|
|
668
690
|
* instructions: "You are a careful engineer.",
|
|
669
691
|
* }),
|
|
692
|
+
* cut: worktree(),
|
|
670
693
|
* tests: command(["npm", "test"]),
|
|
671
694
|
* fail: fail(),
|
|
672
695
|
* },
|
|
@@ -674,10 +697,20 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
674
697
|
* id: "fix-tests",
|
|
675
698
|
* context: ({ input }) => input,
|
|
676
699
|
* on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
677
|
-
* initial: "
|
|
700
|
+
* initial: "cutting",
|
|
678
701
|
* states: {
|
|
702
|
+
* cutting: {
|
|
703
|
+
* invoke: {
|
|
704
|
+
* src: "cut",
|
|
705
|
+
* input: ({ context }) => ({ path: fixTree(context), branch: "fix-tests" }),
|
|
706
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
707
|
+
* },
|
|
708
|
+
* },
|
|
679
709
|
* coding: {
|
|
680
|
-
* invoke: {
|
|
710
|
+
* invoke: {
|
|
711
|
+
* src: "coder",
|
|
712
|
+
* input: ({ context }) => ({ prompt: "Make the tests pass.", cwd: fixTree(context) }),
|
|
713
|
+
* },
|
|
681
714
|
* on: { finished: "done" },
|
|
682
715
|
* initial: "ready",
|
|
683
716
|
* states: {
|
|
@@ -685,6 +718,7 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
685
718
|
* testing: {
|
|
686
719
|
* invoke: {
|
|
687
720
|
* src: "tests",
|
|
721
|
+
* input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
688
722
|
* onDone: {
|
|
689
723
|
* target: "ready",
|
|
690
724
|
* // The Agent reads this string, not the whole output of the command.
|
|
@@ -699,6 +733,7 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
699
733
|
* },
|
|
700
734
|
* },
|
|
701
735
|
* done: { type: "final" },
|
|
736
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
702
737
|
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
703
738
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
704
739
|
* },
|
|
@@ -706,7 +741,6 @@ export const isAnswer = (action: unknown): boolean =>
|
|
|
706
741
|
*
|
|
707
742
|
* export const machine = workspace(body, {
|
|
708
743
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
709
|
-
* spec: () => ({ branch: "fix-tests" }),
|
|
710
744
|
* });
|
|
711
745
|
* ```
|
|
712
746
|
*
|