@jr2/orchestrator 0.4.1 → 0.6.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/README.md +20 -2
- package/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +478 -0
- package/docs/concepts/architecture.md +243 -0
- package/docs/concepts/charts.md +146 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +257 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +658 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +298 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +306 -0
- package/docs/concepts/the-menu.md +381 -0
- package/docs/concepts/workspaces.md +756 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +383 -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 +993 -0
- package/docs/get-started/install.md +130 -0
- package/docs/guides/add-tools-to-the-sandbox.md +308 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +224 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/cut-a-worktree.md +560 -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 +230 -0
- package/docs/guides/look-inside-a-sandbox.md +177 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -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 +532 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +587 -0
- package/docs/guides/run-one-worker-per-item.md +322 -0
- package/docs/guides/set-an-agents-access-and-dials.md +420 -0
- package/docs/guides/size-a-workspace.md +217 -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 +214 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +349 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +237 -0
- package/docs/reference/authoring-surface/agent.md +151 -0
- package/docs/reference/authoring-surface/answer.md +156 -0
- package/docs/reference/authoring-surface/arg.md +82 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/branchname.md +15 -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 +206 -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 +96 -0
- package/docs/reference/authoring-surface/file.md +142 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +259 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +105 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +143 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +130 -0
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +139 -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 +15 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- 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-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +61 -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 +20 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
- package/docs/reference/authoring-surface/type-customize.md +111 -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 +72 -0
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -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 +37 -0
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -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 +95 -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-lateroutcome.md +21 -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-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -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 +22 -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 +26 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +34 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +149 -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 +84 -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 +24 -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 +115 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +22 -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 +27 -0
- 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 +287 -0
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +217 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +765 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +50 -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 +175 -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 +271 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -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 +287 -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 +101 -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 +175 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -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 +245 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -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 +193 -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 +126 -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 +238 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -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 +108 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -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 +239 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -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 +552 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -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 +334 -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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +86 -0
- package/docs/reference/glossary.md +687 -0
- package/docs/reference/http-api.md +1386 -0
- package/docs/reference/machines.md +189 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +207 -0
- package/package.json +5 -3
- package/src/actor.ts +694 -106
- package/src/agent.ts +114 -38
- package/src/ambient.ts +3 -4
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +345 -46
- package/src/charts.ts +43 -8
- package/src/config.ts +440 -178
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +325 -99
- package/src/customize.ts +199 -49
- 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 +50 -3
- package/src/effect.ts +641 -128
- package/src/effector-wire.ts +165 -13
- package/src/fail.ts +222 -0
- package/src/harness-client.ts +587 -50
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +556 -58
- package/src/http.ts +480 -30
- package/src/index.ts +74 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +85 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +24 -7
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +42 -58
- package/src/names.ts +19 -6
- package/src/notices.ts +483 -41
- package/src/open-body.ts +242 -40
- package/src/open-effect.ts +81 -8
- package/src/open.ts +90 -6
- package/src/parts.ts +213 -30
- package/src/party.ts +101 -2
- package/src/pick.ts +497 -11
- package/src/pool.ts +282 -50
- package/src/registration.ts +133 -12
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +22 -3
- package/src/run-host.ts +665 -45
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +457 -71
- package/src/sandbox-kube.ts +66 -17
- package/src/secret-placement.ts +59 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +325 -62
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/tokens.ts +107 -32
- package/src/vocabulary.ts +88 -14
- package/src/wire.ts +100 -26
- package/src/workspace.ts +771 -238
- package/src/worktree.ts +249 -0
package/src/harness-client.ts
CHANGED
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
// Sandbox's Service, and a CR at `phase: Ready` is not yet routable).
|
|
33
33
|
//
|
|
34
34
|
// `abort` is the third verb, and ADR-0024 wires it to the END OF THE INVOCATION: a turn ends with
|
|
35
|
-
// the state that asked for it. It sweeps the running Submission and everything queued behind it
|
|
36
|
-
//
|
|
37
|
-
//
|
|
35
|
+
// the state that asked for it. It sweeps the running Submission and everything queued behind it,
|
|
36
|
+
// and says its cause: `pick` when the Turn's own pick ended it, which settles the running
|
|
37
|
+
// Submission `ended`; no cause otherwise, which settles it `aborted`. The outcome is worth having for
|
|
38
|
+
// observability, though jr2 never reads it (the actor is stopped by then; see actor.ts).
|
|
38
39
|
|
|
39
40
|
import { agentActorWith } from "./actor.ts";
|
|
40
41
|
import type {
|
|
@@ -44,6 +45,7 @@ import type {
|
|
|
44
45
|
AgentRunInput,
|
|
45
46
|
AgentRunPort,
|
|
46
47
|
AgentSettleOptions,
|
|
48
|
+
ListedKill,
|
|
47
49
|
} from "./actor.ts";
|
|
48
50
|
import type { AgentDeclaration, AgentDefinition, ThinkingLevel } from "./agent.ts";
|
|
49
51
|
import {
|
|
@@ -66,12 +68,20 @@ import {
|
|
|
66
68
|
STREAM_NEXT_OFFSET_HEADER,
|
|
67
69
|
VIEW_HISTORY,
|
|
68
70
|
VIEW_UPDATES,
|
|
71
|
+
type AbortCause,
|
|
72
|
+
type AbortRequest,
|
|
69
73
|
type EchoEvent,
|
|
70
74
|
type ExecRead,
|
|
71
75
|
type ExecRequest,
|
|
72
76
|
type LiveConversation,
|
|
73
77
|
type LiveSetRequest,
|
|
74
78
|
type LiveSetResponse,
|
|
79
|
+
type MemoryKillsResponse,
|
|
80
|
+
type NotKeptResponse,
|
|
81
|
+
MAX_KILL_NAME,
|
|
82
|
+
MAX_KILL_STAMP,
|
|
83
|
+
MAX_KILLS_BODY,
|
|
84
|
+
MAX_TOLD,
|
|
75
85
|
type Notice,
|
|
76
86
|
type Settlement,
|
|
77
87
|
type StreamEvent,
|
|
@@ -81,8 +91,8 @@ import {
|
|
|
81
91
|
/** The three-verb wire client (the injectable seam — structurally what `@flue/sdk`'s
|
|
82
92
|
* `agents.{send,wait,abort}` was, minus the SDK). */
|
|
83
93
|
export type HarnessClient = {
|
|
84
|
-
/** `POST /agents/:name/:id {message, cwd?, definition, model?, thinkingLevel?}`
|
|
85
|
-
* with `streamUrl` resolved absolute. The FRAME is `message` and `cwd` — what this Turn is about
|
|
94
|
+
/** `POST /agents/:name/:id {message, cwd?, definition, model?, thinkingLevel?, notices?, kept?}`
|
|
95
|
+
* → the Admission, with `streamUrl` resolved absolute. The FRAME is `message` and `cwd` — what this Turn is about
|
|
86
96
|
* and where it works (ADR-0057), flat siblings on the body. The DEFINITION is the slot's,
|
|
87
97
|
* carried by the Machine and sent with every admission (ADR-0049) — the Harness keeps no roster,
|
|
88
98
|
* so `:name` alone would name nothing there — and it travels as IDENTITY alone, because this
|
|
@@ -97,23 +107,50 @@ export type HarnessClient = {
|
|
|
97
107
|
* `/work` — the stub-Harness run with no Workspace. */
|
|
98
108
|
cwd?: string;
|
|
99
109
|
definition: AgentDefinition;
|
|
110
|
+
/** The Agent's slot (ADR-0076), which the Harness states on each control request for this
|
|
111
|
+
* conversation. Omitted only by a caller that drives no Machine (a test of the wire). */
|
|
112
|
+
slot?: string;
|
|
100
113
|
model?: string;
|
|
101
114
|
thinkingLevel?: ThinkingLevel;
|
|
102
115
|
/** The pending notices in this admission's scope (ADR-0062). Omitted when there are none. */
|
|
103
116
|
notices?: Notice[];
|
|
117
|
+
/** The admission is for a conversation this process was told the Harness keeps (ADR-0062).
|
|
118
|
+
* The Harness that keeps none answers 404 and seats none: this verb throws
|
|
119
|
+
* {@link ConversationNotKept}. */
|
|
120
|
+
kept?: boolean;
|
|
104
121
|
signal?: AbortSignal;
|
|
105
122
|
},
|
|
106
123
|
): Promise<AgentAdmission>;
|
|
107
|
-
/** Follow the stream to this Submission's Settlement: resolve on `completed
|
|
108
|
-
*
|
|
109
|
-
* `
|
|
124
|
+
/** Follow the stream to this Submission's Settlement: resolve on `completed` alone — the run
|
|
125
|
+
* returned with the Turn still open, which the Agent actor answers with the nudge or the wake —
|
|
126
|
+
* and reject with `SettlementFault` on `failed`/`ended`/`aborted`/404. `ended` is no fault, and no
|
|
127
|
+
* live actor reads it: a Submission settles `ended` only after the Turn's own pick took the
|
|
128
|
+
* Machine out of the Agent's state. That stopped the actor that waits on it (ADR-0024), and a
|
|
129
|
+
* stopped actor drops every rejection. A guard kill read on the way is handed to `onMemoryLimit` (ADR-0061). */
|
|
110
130
|
wait(admission: AgentAdmission, opts?: AgentSettleOptions): Promise<void>;
|
|
111
131
|
/** `GET /agents/:name/:id?view=history`: does the Harness still hold this conversation? Only a
|
|
112
132
|
* 404 says no (ADR-0027: a GET on an unknown conversation is 404); anything else — another
|
|
113
133
|
* status, a transport failure — says yes, because unknown is never loss (ADR-0062). */
|
|
114
134
|
holds(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<boolean>;
|
|
115
|
-
/** `
|
|
116
|
-
|
|
135
|
+
/** `GET /memory-kills`: the pod's guard kills (ADR-0061, ADR-0062). A request that never left
|
|
136
|
+
* this host, and a draining Harness's 503, are sent again until `signal` ends the read — the
|
|
137
|
+
* caller owns the read's limit (the Agent actor's 5 s); with no signal, it is sent once. Any other
|
|
138
|
+
* answer but a 200 that reads — a Harness that keeps none, a transport failure, the caller's
|
|
139
|
+
* timeout — is `undefined`, "could not tell": the Harness still holds what it kept, and the next
|
|
140
|
+
* admission reads again. A kill with no id or time, with an id or a time outside the wire's bounds
|
|
141
|
+
* (`wire.ts`), or with a time that does not parse, is skipped; one whose `peak` or `limit` is not
|
|
142
|
+
* a size is kept without sizes, and one whose `told` holds an entry that is not a conversation, or
|
|
143
|
+
* a name outside the bounds, is kept without that entry — each fault said. Absent sizes are no fault: the pod left them out.
|
|
144
|
+
* Each fault is one line, said when it starts. A read that works says nothing, and the next fault
|
|
145
|
+
* after it is said again. */
|
|
146
|
+
memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined>;
|
|
147
|
+
/** `POST /agents/:name/:id/abort`, with the body `{ cause, submissionId }` when a cause is given
|
|
148
|
+
* (`AbortRequest`). The `{ aborted }` answer is dropped (ADR-0024). */
|
|
149
|
+
abort(
|
|
150
|
+
agentName: string,
|
|
151
|
+
instanceId: string,
|
|
152
|
+
opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
|
|
153
|
+
): Promise<void>;
|
|
117
154
|
};
|
|
118
155
|
|
|
119
156
|
export type HarnessClientOptions = {
|
|
@@ -148,8 +185,9 @@ export type HarnessClientOptions = {
|
|
|
148
185
|
log?: (line: string) => void;
|
|
149
186
|
};
|
|
150
187
|
|
|
151
|
-
/** A Submission that settled `failed`/`aborted` — or whose conversation the Harness no
|
|
152
|
-
* has (404) — surfaced to the actor as an `agent.fault
|
|
188
|
+
/** A Submission that settled `failed`/`ended`/`aborted` — or whose conversation the Harness no
|
|
189
|
+
* longer has (404) — surfaced to the actor as an `agent.fault` while it still waits on the Turn.
|
|
190
|
+
* Replaces the SDK's execution error. */
|
|
153
191
|
export class SettlementFault extends Error {
|
|
154
192
|
/** The Settlement as the stream carried it; absent when the conversation itself was lost. */
|
|
155
193
|
readonly settlement?: Settlement;
|
|
@@ -164,6 +202,83 @@ export class SettlementFault extends Error {
|
|
|
164
202
|
}
|
|
165
203
|
}
|
|
166
204
|
|
|
205
|
+
/** An admission for a kept conversation (`kept`) that the Harness refused with 404: it keeps no
|
|
206
|
+
* such conversation, and it seated none (ADR-0062). `notKept` is what the Agent actor reads, as it
|
|
207
|
+
* reads `lost`: that module is wire-free. */
|
|
208
|
+
export class ConversationNotKept extends Error {
|
|
209
|
+
readonly notKept = true;
|
|
210
|
+
constructor(message: string) {
|
|
211
|
+
super(message);
|
|
212
|
+
this.name = "ConversationNotKept";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The Harnesses whose memory kills this process could not read, each said once (`memoryKills`).
|
|
217
|
+
* Per process, not per client: an Agent actor builds a client for each Turn. */
|
|
218
|
+
const killReadFaults = new Set<string>();
|
|
219
|
+
|
|
220
|
+
/** The Harnesses whose kills — on the list or on a stream — held a field this process could not
|
|
221
|
+
* use, each said once until a clean read. Per process, like `killReadFaults`. Neither set is cleaned
|
|
222
|
+
* when a Workspace goes: no hook reaches this module from the Sandbox's destroy, and an entry is
|
|
223
|
+
* one URL per Sandbox this process ever read. */
|
|
224
|
+
const badKillFaults = new Set<string>();
|
|
225
|
+
|
|
226
|
+
/** Say once, until a clean read, that a Harness's kills held a field this process could not use. */
|
|
227
|
+
function badKill(baseUrl: string, log: (line: string) => void, what: string): void {
|
|
228
|
+
if (badKillFaults.has(baseUrl)) return;
|
|
229
|
+
badKillFaults.add(baseUrl);
|
|
230
|
+
log(`harness ${baseUrl}: ${what} (ADR-0062)`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** A size as the guard writes one (`1.9Gi`, `512Mi`), short. The Agent's uid can write the file
|
|
234
|
+
* the list is read from, and these words go into the next Agent's Briefing as they are, so
|
|
235
|
+
* nothing else passes (ADR-0062). */
|
|
236
|
+
function isSize(value: unknown): value is string {
|
|
237
|
+
return typeof value === "string" && value.length <= 16 && /^\d{1,6}(\.\d{1,3})?(Ki|Mi|Gi|Ti)$/.test(value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** A string no longer than `max` characters. The bounds are the wire's (`wire.ts`), the same as
|
|
241
|
+
* the Harness's: the Harness counts the bytes of the JSON text, which are never fewer, so a kill it
|
|
242
|
+
* keeps is one this process keeps. An answer larger than `MAX_KILLS_BODY` is no list to act on:
|
|
243
|
+
* the Harness answers no list that large. */
|
|
244
|
+
const fits = (v: unknown, max: number): v is string => typeof v === "string" && v.length <= max;
|
|
245
|
+
|
|
246
|
+
/** One kill of the list as this process can use it, or undefined when it has no id or time, when
|
|
247
|
+
* its id or time is outside the wire's bounds, or when its time does not parse — the only kills
|
|
248
|
+
* that are dropped. Sizes that are not sizes are left out, so the kill is kept with no sizes; a
|
|
249
|
+
* `told` entry that is not a conversation, or whose name is outside the bounds, is left out of
|
|
250
|
+
* `told`. */
|
|
251
|
+
function listedKill(value: unknown): ListedKill | undefined {
|
|
252
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
253
|
+
const { id, at, peak, limit, told } = value as Record<string, unknown>;
|
|
254
|
+
if (!fits(id, MAX_KILL_STAMP) || !fits(at, MAX_KILL_STAMP) || Number.isNaN(Date.parse(at))) return undefined;
|
|
255
|
+
const conversations = (Array.isArray(told) ? told.slice(0, MAX_TOLD) : []).filter(
|
|
256
|
+
(t): t is { agent: string; instanceId: string } =>
|
|
257
|
+
typeof t === "object" && t !== null && fits(t.agent, MAX_KILL_NAME) && fits(t.instanceId, MAX_KILL_NAME),
|
|
258
|
+
);
|
|
259
|
+
const sized = isSize(peak) && isSize(limit);
|
|
260
|
+
return {
|
|
261
|
+
id,
|
|
262
|
+
at,
|
|
263
|
+
told: conversations.map(({ agent, instanceId }) => ({ agent, instanceId })),
|
|
264
|
+
...(sized ? { peak, limit } : {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Whether a listed kill came through as the wire states it. */
|
|
269
|
+
function whole(raw: unknown, kill: ListedKill | undefined): boolean {
|
|
270
|
+
if (kill === undefined) return false;
|
|
271
|
+
const { peak, limit, told } = raw as { peak?: unknown; limit?: unknown; told?: unknown };
|
|
272
|
+
// Sizes the pod left out are no fault; sizes it sent that are not sizes are.
|
|
273
|
+
if (kill.limit === undefined && (peak !== undefined || limit !== undefined)) return false;
|
|
274
|
+
return Array.isArray(told) && Math.min(told.length, MAX_TOLD) === kill.told.length && told.length <= MAX_TOLD;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A timeout's abort reason (`AbortSignal.timeout`): a read that ran out of time, not an abandon. */
|
|
278
|
+
function isTimeout(reason: unknown): boolean {
|
|
279
|
+
return reason instanceof DOMException && reason.name === "TimeoutError";
|
|
280
|
+
}
|
|
281
|
+
|
|
167
282
|
/** Build the real wire client from connection options. */
|
|
168
283
|
export function createHarnessClient(options: HarnessClientOptions): HarnessClient {
|
|
169
284
|
const { baseUrl } = options;
|
|
@@ -270,17 +385,34 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
270
385
|
// `machine-doc.ts` and the surface read it — and the Harness neither validates nor
|
|
271
386
|
// uses it, so it has no business on the Turn's wire.
|
|
272
387
|
definition: admittedDefinition(sendOptions.definition),
|
|
388
|
+
// Who the conversation is (ADR-0076): the Harness states it on each control request.
|
|
389
|
+
...(sendOptions.slot === undefined ? {} : { slot: sendOptions.slot }),
|
|
273
390
|
// Omitted when unset, so an admission with no dials is byte-identical to before.
|
|
274
391
|
...(sendOptions.model ? { model: sendOptions.model } : {}),
|
|
275
392
|
...(sendOptions.thinkingLevel ? { thinkingLevel: sendOptions.thinkingLevel } : {}),
|
|
276
393
|
// The Briefing's news (ADR-0062): typed data, the Harness writes the words. Omitted
|
|
277
394
|
// when there is none, so an admission without notices is byte-identical to before.
|
|
278
395
|
...(sendOptions.notices?.length ? { notices: sendOptions.notices } : {}),
|
|
396
|
+
// Omitted when unset, so an admission that is not for a kept conversation is
|
|
397
|
+
// byte-identical to before.
|
|
398
|
+
...(sendOptions.kept ? { kept: true } : {}),
|
|
279
399
|
}),
|
|
280
400
|
signal: sendOptions.signal,
|
|
281
401
|
},
|
|
282
402
|
sendOptions.signal,
|
|
283
403
|
);
|
|
404
|
+
if (sendOptions.kept && res.status === 404) {
|
|
405
|
+
// "Not kept" only with the Harness's marker: a 404 from anything else — a proxy, a route
|
|
406
|
+
// that moved — is a failed admission, as before.
|
|
407
|
+
const body = (await res.json().catch(() => undefined)) as Partial<NotKeptResponse> | undefined;
|
|
408
|
+
const detail = typeof body?.error === "string" ? body.error : res.statusText || "no detail";
|
|
409
|
+
if (body?.kept === false) {
|
|
410
|
+
throw new ConversationNotKept(
|
|
411
|
+
`harness admission refused (404): ${detail} — the Harness does not keep this conversation (ADR-0062)`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
throw new Error(`harness admission failed (404): ${detail}`);
|
|
415
|
+
}
|
|
284
416
|
if (!res.ok) {
|
|
285
417
|
throw new Error(`harness admission failed (${res.status}): ${await errorDetail(res)}`);
|
|
286
418
|
}
|
|
@@ -340,8 +472,19 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
340
472
|
|
|
341
473
|
for (const event of events) {
|
|
342
474
|
if (event.type === "memory-limit") {
|
|
343
|
-
const { peak, limit, conversationId, position } = event;
|
|
344
|
-
|
|
475
|
+
const { id, peak, limit, conversationId, position } = event;
|
|
476
|
+
// A size that is not one is not put in a Briefing (the Agent's uid can write the record):
|
|
477
|
+
// the kill is kept, with no sizes, and the fault is said.
|
|
478
|
+
const sized = isSize(peak) && isSize(limit);
|
|
479
|
+
if (!sized)
|
|
480
|
+
badKill(baseUrl, log, "a memory kill on a stream has sizes that are not sizes — kept without them");
|
|
481
|
+
// An older Harness's event has no id: keyed by its place on the stream, exactly as the
|
|
482
|
+
// Orchestrator before raised it, so a replayed read is the same kill and it can never
|
|
483
|
+
// mark a later kill seen. An id too long to be one is no id.
|
|
484
|
+
const kill = fits(id, MAX_KILL_STAMP)
|
|
485
|
+
? { id }
|
|
486
|
+
: { id: `${conversationId}@${position.batch}.${position.index}`, legacy: true as const };
|
|
487
|
+
opts?.onMemoryLimit?.({ ...kill, ...(sized ? { peak, limit } : {}) });
|
|
345
488
|
continue;
|
|
346
489
|
}
|
|
347
490
|
if (event.type !== "submission-settled" || event.submissionId !== admission.submissionId) continue;
|
|
@@ -355,21 +498,108 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
|
|
|
355
498
|
async holds(agentName, instanceId, opts) {
|
|
356
499
|
const url = new URL(conversationUrl(agentName, instanceId));
|
|
357
500
|
url.searchParams.set("view", VIEW_HISTORY);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
501
|
+
// Asked as a restart is seen, when the new container may not listen yet: a request that
|
|
502
|
+
// never left this host, and a draining Harness's 503, are sent again until the caller's
|
|
503
|
+
// signal ends the wait — then, and on any other failure, unknown is never loss.
|
|
504
|
+
let backoffMs = backoffInitialMs;
|
|
505
|
+
for (;;) {
|
|
506
|
+
let again = false;
|
|
507
|
+
try {
|
|
508
|
+
const res = await fetchImpl(url.toString(), { headers: authorization, signal: opts?.signal });
|
|
509
|
+
// Drain the body so the socket is released; the snapshot itself is not the question.
|
|
510
|
+
await res.body?.cancel().catch(() => undefined);
|
|
511
|
+
if (res.status !== DRAINING) return res.status !== 404;
|
|
512
|
+
again = true;
|
|
513
|
+
} catch (err) {
|
|
514
|
+
if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
|
|
515
|
+
again = !opts?.signal?.aborted && neverDelivered(err);
|
|
516
|
+
}
|
|
517
|
+
if (!again || opts?.signal === undefined) return true;
|
|
518
|
+
try {
|
|
519
|
+
await sleep(jittered(backoffMs), opts.signal);
|
|
520
|
+
} catch (err) {
|
|
521
|
+
if (opts.signal.aborted && !isTimeout(opts.signal.reason)) throw err;
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
backoffMs = Math.min(backoffMs * 2, backoffMaxMs);
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
async memoryKills(opts) {
|
|
529
|
+
const url = new URL("/memory-kills", baseUrl).toString();
|
|
530
|
+
// Idempotent, so re-sent on what `postAdmission` re-sends — a request that never left this
|
|
531
|
+
// host, a draining Harness's 503 — on the same ladder, until the caller's signal ends it. The
|
|
532
|
+
// limit is the caller's alone, so there is one clock for it.
|
|
533
|
+
let backoffMs = backoffInitialMs;
|
|
534
|
+
let why: string;
|
|
535
|
+
for (;;) {
|
|
536
|
+
let again = false;
|
|
537
|
+
try {
|
|
538
|
+
const res = await fetchImpl(url, { headers: authorization, signal: opts?.signal });
|
|
539
|
+
if (res.status === 200 && Number(res.headers.get("content-length") ?? 0) > MAX_KILLS_BODY) {
|
|
540
|
+
await res.body?.cancel().catch(() => undefined);
|
|
541
|
+
why = `the answer is larger than ${MAX_KILLS_BODY} bytes`;
|
|
542
|
+
} else if (res.status === 200) {
|
|
543
|
+
const text = await res.text();
|
|
544
|
+
const kills =
|
|
545
|
+
text.length > MAX_KILLS_BODY ? undefined : (JSON.parse(text) as Partial<MemoryKillsResponse>).kills;
|
|
546
|
+
if (Array.isArray(kills)) {
|
|
547
|
+
// A read that works ends the fault: the next fault is said again.
|
|
548
|
+
killReadFaults.delete(baseUrl);
|
|
549
|
+
const read = kills.map(listedKill);
|
|
550
|
+
const usable = read.filter((kill): kill is ListedKill => kill !== undefined);
|
|
551
|
+
const flawed = kills.filter((raw, i) => !whole(raw, read[i])).length;
|
|
552
|
+
if (flawed === 0) badKillFaults.delete(baseUrl);
|
|
553
|
+
else {
|
|
554
|
+
badKill(
|
|
555
|
+
baseUrl,
|
|
556
|
+
log,
|
|
557
|
+
`${flawed} of its memory kills are not the wire's shape — those with no id or time, or with one outside the wire's bounds or a time that does not parse, are skipped, the others are kept without the fields that were wrong`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
return usable;
|
|
561
|
+
}
|
|
562
|
+
why = "the answer is not a list of kills";
|
|
563
|
+
} else {
|
|
564
|
+
why = `HTTP ${res.status}: ${await errorDetail(res)}`;
|
|
565
|
+
again = res.status === DRAINING;
|
|
566
|
+
}
|
|
567
|
+
} catch (err) {
|
|
568
|
+
// A local abandon propagates, as in every other verb; a timeout the caller set is none.
|
|
569
|
+
if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
|
|
570
|
+
why = transportDetail(err);
|
|
571
|
+
again = !opts?.signal?.aborted && neverDelivered(err);
|
|
572
|
+
}
|
|
573
|
+
if (!again || opts?.signal === undefined) break;
|
|
574
|
+
try {
|
|
575
|
+
await sleep(jittered(backoffMs), opts?.signal);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
|
|
578
|
+
why = "the read's time ran out";
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
backoffMs = Math.min(backoffMs * 2, backoffMaxMs);
|
|
582
|
+
}
|
|
583
|
+
// One line for each fault: said when it starts. A read that works says nothing, and the next
|
|
584
|
+
// fault after it is said again.
|
|
585
|
+
if (!killReadFaults.has(baseUrl)) {
|
|
586
|
+
killReadFaults.add(baseUrl);
|
|
587
|
+
log(
|
|
588
|
+
`harness ${baseUrl}: cannot read its memory kills (${why}) — no notice from them now; the Harness keeps them, and the next admission reads again (ADR-0062)`,
|
|
589
|
+
);
|
|
366
590
|
}
|
|
591
|
+
return undefined;
|
|
367
592
|
},
|
|
368
593
|
|
|
369
594
|
async abort(agentName, instanceId, opts) {
|
|
595
|
+
// No cause, no body: the request an Orchestrator from before `cause` sent, byte for byte.
|
|
596
|
+
const body: AbortRequest | undefined = opts?.cause
|
|
597
|
+
? { cause: opts.cause, ...(opts.submissionId ? { submissionId: opts.submissionId } : {}) }
|
|
598
|
+
: undefined;
|
|
370
599
|
const res = await fetchImpl(`${conversationUrl(agentName, instanceId)}/abort`, {
|
|
371
600
|
method: "POST",
|
|
372
|
-
headers: authorization,
|
|
601
|
+
headers: body ? { ...authorization, "content-type": "application/json" } : authorization,
|
|
602
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
373
603
|
signal: opts?.signal,
|
|
374
604
|
});
|
|
375
605
|
if (!res.ok) {
|
|
@@ -404,9 +634,11 @@ export function harnessAgentRunPort(client: HarnessClient): AgentRunPort {
|
|
|
404
634
|
// to the Harness's refusal if it is not a path.
|
|
405
635
|
...(input.cwd === undefined ? {} : { cwd: input.cwd }),
|
|
406
636
|
definition: opts.definition,
|
|
637
|
+
slot: opts.slot,
|
|
407
638
|
model: input.model,
|
|
408
639
|
thinkingLevel: input.thinkingLevel,
|
|
409
640
|
...(opts.notices?.length ? { notices: opts.notices } : {}),
|
|
641
|
+
...(opts.kept ? { kept: true } : {}),
|
|
410
642
|
signal: opts.signal,
|
|
411
643
|
});
|
|
412
644
|
},
|
|
@@ -419,11 +651,23 @@ export function harnessAgentRunPort(client: HarnessClient): AgentRunPort {
|
|
|
419
651
|
return await client.holds(agentName, instanceId, opts);
|
|
420
652
|
},
|
|
421
653
|
|
|
422
|
-
async
|
|
654
|
+
async memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined> {
|
|
655
|
+
return await client.memoryKills(opts);
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
async abort(
|
|
659
|
+
agentName: string,
|
|
660
|
+
instanceId: string,
|
|
661
|
+
opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
|
|
662
|
+
): Promise<void> {
|
|
423
663
|
// The result (`{ aborted }` — whether there was work to end) is dropped: by the time this
|
|
424
664
|
// runs the state has already moved on, and an idle instance is exactly as fine as a
|
|
425
665
|
// stopped one. Settlement is asynchronous and nothing here is listening (ADR-0024).
|
|
426
|
-
await client.abort(agentName, instanceId, {
|
|
666
|
+
await client.abort(agentName, instanceId, {
|
|
667
|
+
signal: opts?.signal,
|
|
668
|
+
...(opts?.cause ? { cause: opts.cause } : {}),
|
|
669
|
+
...(opts?.submissionId ? { submissionId: opts.submissionId } : {}),
|
|
670
|
+
});
|
|
427
671
|
},
|
|
428
672
|
};
|
|
429
673
|
}
|
|
@@ -508,22 +752,152 @@ export function createLiveSetPush(options: {
|
|
|
508
752
|
}
|
|
509
753
|
|
|
510
754
|
/**
|
|
511
|
-
*
|
|
512
|
-
* `jr2Setup
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
* `
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
755
|
+
* Make an Agent: an actor slot that runs one Turn of a model each time a state invokes it. Put it
|
|
756
|
+
* under `actors` in `jr2Setup`, and invoke it by its key: `actors: { coder: agent(…) }`, then
|
|
757
|
+
* `invoke: { src: "coder", input: { prompt } }`. The key is the Agent's name.
|
|
758
|
+
*
|
|
759
|
+
* The invoke's `input` is the Turn: `prompt` (required), `cwd`, `model`, `thinkingLevel` and
|
|
760
|
+
* `continue`. `cwd` is the absolute directory where the Working tools work. Under Repo Slots it
|
|
761
|
+
* is required: a Worktree that a cut made, `<slot>/default` to read, or `/work`. An Agent with
|
|
762
|
+
* `workspace: "none"`, and a Workspace with no Repo Slot, need no `cwd`. `continue: true` continues the Agent's conversation
|
|
763
|
+
* in this Machine. Absent, each Turn starts a new conversation. `continue` is the literal `true`,
|
|
764
|
+
* not a `boolean`. A Turn can join only a conversation that an earlier Turn started with
|
|
765
|
+
* `continue: true`. Thus in a retry loop, put `continue: true` on each Turn, also on the first, so
|
|
766
|
+
* that attempt 2 sees attempt 1. Then a Runaway on the first Turn gets no second start, because a
|
|
767
|
+
* continued Turn gets none (ADR-0035). Spread it only when a state must start a clean conversation
|
|
768
|
+
* on some Turns only: `...(context.clean ? {} : { continue: true as const })`. A clean Turn does not
|
|
769
|
+
* start the continued conversation again: the next Turn with `continue: true` joins the earlier
|
|
770
|
+
* one. The Turn ends when the state that invoked the Agent exits, usually on a pick from the
|
|
771
|
+
* Agent's Menu.
|
|
772
|
+
*
|
|
773
|
+
* The actor of an Agent never finishes, so an `onDone` on its invoke never runs. Give the Agent a
|
|
774
|
+
* pick, such as `finish`, and route on it. When a Turn fails with no pick, jr2 sends the event
|
|
775
|
+
* `agent.fault` to the Machine. It is an event, not an error of the invoke, so an `onError` does
|
|
776
|
+
* not get it. Route it on each state that invokes an Agent, or once on a state above them:
|
|
777
|
+
* `on: { "agent.fault": "failed" }`. `jr2 check` and `jr2 up` refuse a state that invokes an
|
|
778
|
+
* Agent with no route for it, and an `agent.fault` that no transition takes ends the run `error`.
|
|
779
|
+
*
|
|
780
|
+
* An Agent with `workspace: "write"` or `"read"` works in a Sandbox, so invoke it inside a
|
|
781
|
+
* `workspace()` body. An Agent with `workspace: "none"` can run anywhere. A model can lack the
|
|
782
|
+
* definition's thinking level: see {@link ThinkingLevel}.
|
|
783
|
+
*
|
|
784
|
+
* When a Turn starts, it refuses:
|
|
785
|
+
* - a key in `input` that a Turn does not have: ``agent "<slot>": the Turn's input carries
|
|
786
|
+
* `<key>`, which a Turn has no use for — its input is its Frame (`prompt`, `cwd`), its Dials
|
|
787
|
+
* (`model`, `thinkingLevel`), and `continue` (ADR-0057)``
|
|
788
|
+
* - no `cwd` under Repo Slots: ``turn <id>: agent "<slot>" works under a Workspace with Repo Slots
|
|
789
|
+
* (<slots>) and its Turn framed no `cwd`: say where it works, a Worktree, or `cwd: "/work"` above
|
|
790
|
+
* them (ADR-0057)``
|
|
791
|
+
* - a `cwd` that is not an absolute path: ``agent "<slot>": the Turn's Frame carries a `cwd` that
|
|
792
|
+
* is not an absolute path — the Frame says WHERE this Turn works (ADR-0057), and a Turn rooted
|
|
793
|
+
* anywhere else would work in the wrong directory silently``. The Harness makes this refusal
|
|
794
|
+
* when it admits the Turn, and the error starts with `harness admission failed (400): `.
|
|
795
|
+
* - an Agent with Working tools outside a `workspace()`: ``turn <id>: agent "<slot>" has
|
|
796
|
+
* workspace: "<access>" — invoke it inside a workspace() (ambient resolution), or pass an
|
|
797
|
+
* explicit `endpoint` (workspace-less stub path)``
|
|
798
|
+
* - a `model` that is still `open`: ``agent "<slot>" has an Open model — nobody bound it; bind it
|
|
799
|
+
* where the Machine is registered (`customize(<machine>, { agents: { <slot>: { model:
|
|
800
|
+
* "<provider>/<model>" } } })`), because a packaged Machine cannot pick a model on your behalf
|
|
801
|
+
* (ADR-0054)``. `jr2 up` refuses this before it builds anything.
|
|
802
|
+
*
|
|
803
|
+
* @param declaration - The Agent's definition (see {@link AgentDefinition}). `model` may be `open`
|
|
804
|
+
* in a Machine that you publish as a package.
|
|
805
|
+
* @returns The actor logic of the Agent's slot.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```ts
|
|
809
|
+
* import { z } from "zod";
|
|
810
|
+
* import {
|
|
811
|
+
* agent,
|
|
812
|
+
* defineEvent,
|
|
813
|
+
* fail,
|
|
814
|
+
* isReady,
|
|
815
|
+
* jr2Setup,
|
|
816
|
+
* workspace,
|
|
817
|
+
* worktree,
|
|
818
|
+
* type Workspaced,
|
|
819
|
+
* } from "@jr2/orchestrator";
|
|
820
|
+
*
|
|
821
|
+
* const finish = defineEvent({
|
|
822
|
+
* name: "finish",
|
|
823
|
+
* description: "Call this when `node --test` passes.",
|
|
824
|
+
* input: z.object({}),
|
|
825
|
+
* });
|
|
826
|
+
* const runInput = z.object({ branch: z.string() });
|
|
827
|
+
* type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
828
|
+
*
|
|
829
|
+
* const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
|
|
830
|
+
*
|
|
831
|
+
* const body = jr2Setup({
|
|
832
|
+
* types: {} as { input: BodyInput; context: BodyInput },
|
|
833
|
+
* events: [finish],
|
|
834
|
+
* actors: {
|
|
835
|
+
* coder: agent({
|
|
836
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
837
|
+
* instructions: "You fix failing tests. You change no test.",
|
|
838
|
+
* }),
|
|
839
|
+
* cut: worktree(),
|
|
840
|
+
* fail: fail(),
|
|
841
|
+
* },
|
|
842
|
+
* }).createMachine({
|
|
843
|
+
* id: "fix",
|
|
844
|
+
* context: ({ input }) => input,
|
|
845
|
+
* on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
846
|
+
* initial: "cutting",
|
|
847
|
+
* states: {
|
|
848
|
+
* cutting: {
|
|
849
|
+
* invoke: {
|
|
850
|
+
* src: "cut",
|
|
851
|
+
* input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
|
|
852
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
853
|
+
* },
|
|
854
|
+
* },
|
|
855
|
+
* coding: {
|
|
856
|
+
* invoke: {
|
|
857
|
+
* src: "coder",
|
|
858
|
+
* input: ({ context }) => ({
|
|
859
|
+
* prompt: "Make `node --test` pass, then commit.",
|
|
860
|
+
* cwd: fixTree(context),
|
|
861
|
+
* thinkingLevel: "high",
|
|
862
|
+
* }),
|
|
863
|
+
* },
|
|
864
|
+
* on: { finish: "done" },
|
|
865
|
+
* },
|
|
866
|
+
* done: { type: "final" },
|
|
867
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
868
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
869
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
870
|
+
* },
|
|
871
|
+
* });
|
|
872
|
+
*
|
|
873
|
+
* export const machine = workspace(body, {
|
|
874
|
+
* input: runInput,
|
|
875
|
+
* repos: { app: "https://github.com/you/app.git" },
|
|
876
|
+
* });
|
|
877
|
+
* ```
|
|
878
|
+
*
|
|
879
|
+
* @see docs/concepts/agents-and-turns.md
|
|
880
|
+
* @see docs/guides/write-a-workflow.md
|
|
881
|
+
* @see docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md
|
|
882
|
+
* @see docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md
|
|
525
883
|
*/
|
|
884
|
+
export function agent<const D extends AgentDeclaration>(
|
|
885
|
+
declaration: D,
|
|
886
|
+
): AgentLogic & (D extends { workspace: "none" } ? { readonly definition: { readonly workspace: "none" } } : unknown);
|
|
526
887
|
export function agent(declaration: AgentDeclaration): AgentLogic {
|
|
888
|
+
// THE authoring surface for an Agent (ADR-0049): one definition in, one actor slot out. The
|
|
889
|
+
// slot key is the Agent's name (the Harness route, the minted iid, the markers), so a name the
|
|
890
|
+
// Machine does not carry is a compile error on `src`, and two Machines in one run may each carry
|
|
891
|
+
// their own `coder`.
|
|
892
|
+
//
|
|
893
|
+
// Everything live is constructed per-invocation from serializable input: the wire client from
|
|
894
|
+
// `input.endpoint` (which Sandbox's Harness — or the wire-compatible dev stub; either way it's
|
|
895
|
+
// only a URL, one code path). Lives here, not in actor.ts, so the actor logic and its unit
|
|
896
|
+
// tests never touch the wire.
|
|
897
|
+
//
|
|
898
|
+
// It takes a DECLARATION (ADR-0054), so a packaged Machine may write `agent({ model: open, … })`
|
|
899
|
+
// and leave the one part it cannot honestly fill to whoever registers it. What rides the Turn
|
|
900
|
+
// is still an `AgentDefinition`: the actor narrows on start and refuses an Open one there.
|
|
527
901
|
return agentActorWith(
|
|
528
902
|
(endpoint, bearer) => createHarnessAgentRunClient({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),
|
|
529
903
|
declaration,
|
|
@@ -600,26 +974,189 @@ export function createExecPort(options: {
|
|
|
600
974
|
}
|
|
601
975
|
|
|
602
976
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
977
|
+
* Make a `command` Effect: an actor slot that runs one program in the Workspace's Sandbox when a
|
|
978
|
+
* state invokes it. Put it under `actors` and invoke it by its key: `actors: { tests:
|
|
979
|
+
* command(["node", "--test"]) }`, then `invoke: { src: "tests", onDone: … }`.
|
|
980
|
+
*
|
|
981
|
+
* `argv` is a list, never shell text: each entry is one argument. An `arg("name")` entry is filled
|
|
982
|
+
* from the invoke's `input` (`input: { name: "…" }`), as one argument whatever its text. To use a
|
|
983
|
+
* shell, write it: `["sh", "-c", "…"]`.
|
|
984
|
+
*
|
|
985
|
+
* Where it runs:
|
|
986
|
+
* - With no Effect secret in `env`, in the Harness container of the enclosing Workspace's Sandbox.
|
|
987
|
+
* It runs in the Worktree that `worktree` of the invoke's input names, or in `/work` in a
|
|
988
|
+
* Workspace with no Repo Slot. The Agent's Working tools also run in this container.
|
|
989
|
+
* - With an Effect secret in `env`, in the Sandbox Effector, against a commit of the branch. This
|
|
990
|
+
* is the branch tip when it starts, or with `pin: true` the commit that the invoke's `input.sha`
|
|
991
|
+
* names. The output then says `dirty: true` when the Worktree held edits that the command did
|
|
992
|
+
* not see.
|
|
993
|
+
*
|
|
994
|
+
* Every outcome is output for `onDone`, never an error: `{ outcome: "exited", code, tail }`,
|
|
995
|
+
* `{ outcome: "timeout", tail }`, `{ outcome: "killed", signal, tail }`, or `{ outcome: "unknown",
|
|
996
|
+
* reason }` when jr2 cannot know what happened. `tail` is the end of the output stream.
|
|
997
|
+
*
|
|
998
|
+
* After an Orchestrator restart:
|
|
999
|
+
* - A command with no Effect secret in `env` never runs twice. jr2 attaches to it again, or settles
|
|
1000
|
+
* `unknown`.
|
|
1001
|
+
* - A command with an Effect secret in `env` runs again when the Sandbox Effector no longer holds
|
|
1002
|
+
* it. It runs against the same commit. If jr2 has no commit for it, it settles `unknown`.
|
|
1003
|
+
*
|
|
1004
|
+
* When the state exits, the command stops: jr2 kills its process group. `onError` is only for a
|
|
1005
|
+
* Machine or config defect.
|
|
1006
|
+
*
|
|
1007
|
+
* It refuses, when you call it:
|
|
1008
|
+
* - an empty `argv`: `command(argv): argv is a non-empty list — each entry one argument, never
|
|
1009
|
+
* shell text`
|
|
1010
|
+
* - an entry that is not a string or `arg()`: `command(argv): an argv entry is a string or
|
|
1011
|
+
* arg("name"), got <entry>`
|
|
1012
|
+
* - a `timeout` that is not a positive duration: `command: timeout "<timeout>" is not a positive
|
|
1013
|
+
* duration (like "90s", "10m", "1h")`
|
|
1014
|
+
* - an `env` name that is not a variable name, or `PATH`, `HOME` or `TMPDIR`: ``command(argv, {
|
|
1015
|
+
* env }): "<name>" is not a variable the author sets — PATH, HOME and TMPDIR are the Effector's
|
|
1016
|
+
* (ADR-0067)``
|
|
1017
|
+
* - `pin: true` with no Effect secret in `env`: ``command(argv, { pin: true }) names no secret in
|
|
1018
|
+
* its env: `pin` chooses the commit a credentialed command runs against in the Sandbox
|
|
1019
|
+
* Effector, and a command that names no secret runs in the live Worktree (ADR-0067) — name its
|
|
1020
|
+
* secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``
|
|
1021
|
+
*
|
|
1022
|
+
* And when it starts, with no Effect secret in `env`:
|
|
1023
|
+
* - outside a `workspace()`: `command "<slot>" runs in the Harness container of a Workspace, and
|
|
1024
|
+
* its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
|
|
1025
|
+
* - no `worktree` under Repo Slots: `` command "<slot>" runs under a Workspace with Repo Slots
|
|
1026
|
+
* (<slots>) and its input names no `worktree`: say which, a Worktree that a cut gave or
|
|
1027
|
+
* `<slot>/default` ``
|
|
1028
|
+
* - an `arg()` with no string in `input`: `command "<slot>": argv slot <<name>> has no string
|
|
1029
|
+
* value in its input (got <type>) — map it in the invoke's input`. The angle brackets around the
|
|
1030
|
+
* arg's name are in the text: `argv slot <file>`. `<type>` is `nothing` when `input` has no such
|
|
1031
|
+
* field, or the type of its value, such as `number`.
|
|
1032
|
+
*
|
|
1033
|
+
* And when it starts, with an Effect secret in `env`:
|
|
1034
|
+
* - outside a `workspace()`: `command "<slot>" runs in the Sandbox Effector of a Workspace, and its
|
|
1035
|
+
* state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
|
|
1036
|
+
* - a Workspace with no Repo Slot: `command "<slot>" runs under a Workspace with no Repo Slot: a
|
|
1037
|
+
* command with an Effect secret runs against one commit of a Repo Slot's branch, and there is
|
|
1038
|
+
* none — declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent
|
|
1039
|
+
* (ADR-0051)`. `jr2 up` refuses it first, with the same reason
|
|
1040
|
+
* - a `worktree` that no cut made in this Sandbox: the output is `exited` 127, with `no cut made
|
|
1041
|
+
* <path> in this Sandbox: cut it with worktree()` in its tail. A detached Worktree gets the same
|
|
1042
|
+
* form
|
|
1043
|
+
* - `pin: true` and an `input.sha` that is not a full commit id: `command "<slot>" (pin): its
|
|
1044
|
+
* input's sha is not a full commit id`
|
|
1045
|
+
* - an `arg()` with no string in `input`: the same text as above.
|
|
1046
|
+
*
|
|
1047
|
+
* @param argv - The program and its arguments: strings and `arg("name")` entries. Or `open`, in a
|
|
1048
|
+
* Machine that you publish as a package, for the Instance that registers it to bind with
|
|
1049
|
+
* `customize()`.
|
|
1050
|
+
* @param options - Optional. `timeout`: `"90s"`, `"10m"` or `"1h"`, default
|
|
1051
|
+
* `"10m"`; when it expires, the output is `timeout`. `tail`: how many characters of the end of
|
|
1052
|
+
* the output stream to keep, default 4096. `env`: environment variables. Each value is
|
|
1053
|
+
* `secret("name")` for the value of an Effect secret, or `secret("name").path` for the path of a
|
|
1054
|
+
* file that holds it. `pin`: `true` to run against the commit in `input.sha`. Use `pin` only
|
|
1055
|
+
* with an Effect secret in `env`.
|
|
1056
|
+
* @returns The actor logic of the Effect's slot. Its output is a `CommandOutput`.
|
|
1057
|
+
*
|
|
1058
|
+
* @example
|
|
1059
|
+
* ```ts
|
|
1060
|
+
* import { z } from "zod";
|
|
1061
|
+
* import {
|
|
1062
|
+
* arg,
|
|
1063
|
+
* command,
|
|
1064
|
+
* fail,
|
|
1065
|
+
* isReady,
|
|
1066
|
+
* jr2Setup,
|
|
1067
|
+
* workspace,
|
|
1068
|
+
* worktree,
|
|
1069
|
+
* type Workspaced,
|
|
1070
|
+
* } from "@jr2/orchestrator";
|
|
1071
|
+
*
|
|
1072
|
+
* const runInput = z.object({ branch: z.string() });
|
|
1073
|
+
* type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
1074
|
+
*
|
|
1075
|
+
* const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
|
|
1076
|
+
*
|
|
1077
|
+
* const body = jr2Setup({
|
|
1078
|
+
* types: {} as { input: BodyInput; context: BodyInput },
|
|
1079
|
+
* actors: {
|
|
1080
|
+
* cut: worktree(),
|
|
1081
|
+
* tests: command(["node", "--test", arg("file")], { timeout: "5m" }),
|
|
1082
|
+
* fail: fail(),
|
|
1083
|
+
* },
|
|
1084
|
+
* }).createMachine({
|
|
1085
|
+
* id: "check",
|
|
1086
|
+
* context: ({ input }) => input,
|
|
1087
|
+
* on: { "workspace.lost": ".lost" },
|
|
1088
|
+
* initial: "cutting",
|
|
1089
|
+
* states: {
|
|
1090
|
+
* cutting: {
|
|
1091
|
+
* invoke: {
|
|
1092
|
+
* src: "cut",
|
|
1093
|
+
* input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
|
|
1094
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
|
|
1095
|
+
* },
|
|
1096
|
+
* },
|
|
1097
|
+
* testing: {
|
|
1098
|
+
* invoke: {
|
|
1099
|
+
* src: "tests",
|
|
1100
|
+
* input: ({ context }) => ({ file: "test/greet.test.js", worktree: testTree(context) }),
|
|
1101
|
+
* onDone: [
|
|
1102
|
+
* {
|
|
1103
|
+
* guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
|
|
1104
|
+
* target: "passed",
|
|
1105
|
+
* },
|
|
1106
|
+
* { target: "failed" },
|
|
1107
|
+
* ],
|
|
1108
|
+
* },
|
|
1109
|
+
* },
|
|
1110
|
+
* passed: { type: "final" },
|
|
1111
|
+
* // Not final states: each one fails the run, and the run ends `failed`.
|
|
1112
|
+
* failed: { invoke: { src: "fail", input: { reason: "the test did not pass" } } },
|
|
1113
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
1114
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
1115
|
+
* },
|
|
1116
|
+
* });
|
|
1117
|
+
*
|
|
1118
|
+
* export const machine = workspace(body, {
|
|
1119
|
+
* input: runInput,
|
|
1120
|
+
* repos: { app: "https://github.com/you/app.git" },
|
|
1121
|
+
* });
|
|
1122
|
+
* ```
|
|
1123
|
+
*
|
|
1124
|
+
* @see docs/concepts/effects.md
|
|
1125
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
1126
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
1127
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
609
1128
|
*/
|
|
610
|
-
export function command<const Opt extends CommandOptions = CommandOptions>(
|
|
611
|
-
argv: typeof open,
|
|
612
|
-
options?: Opt,
|
|
613
|
-
): EffectLogic<OpenCommandInput<Opt["pin"]>, CommandOutput> & OpenEffect<"argv" | EnvOpenParts<Opt>>;
|
|
614
1129
|
export function command<const A extends readonly (string | Arg)[], const Opt extends CommandOptions = CommandOptions>(
|
|
615
1130
|
argv: A,
|
|
616
1131
|
options?: Opt,
|
|
617
|
-
): EffectLogic<CommandInput<A, Opt["pin"]>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>;
|
|
1132
|
+
): EffectLogic<CommandInput<A, Opt["pin"], Opt>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>;
|
|
1133
|
+
/**
|
|
1134
|
+
* Make a `command` Effect whose argv is Open: `command(open, options)`, in a Machine that you
|
|
1135
|
+
* publish as a package. The Instance that registers the Machine binds the argv one time:
|
|
1136
|
+
* `customize(machine, { effects: { <slot>: { argv } } })`. {@link command} tells the rest, and
|
|
1137
|
+
* what it refuses.
|
|
1138
|
+
*
|
|
1139
|
+
* @param argv - `open`.
|
|
1140
|
+
* @param options - Optional. The same options as for a written argv.
|
|
1141
|
+
* @returns The actor logic of the Effect's slot. Its output is a `CommandOutput`.
|
|
1142
|
+
*
|
|
1143
|
+
* @see docs/guides/bind-open-parts.md
|
|
1144
|
+
* @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
|
|
1145
|
+
*/
|
|
1146
|
+
export function command<const Opt extends CommandOptions = CommandOptions>(
|
|
1147
|
+
argv: typeof open,
|
|
1148
|
+
options?: Opt,
|
|
1149
|
+
): EffectLogic<OpenCommandInput<Opt["pin"], Opt>, CommandOutput> & OpenEffect<"argv" | EnvOpenParts<Opt>>;
|
|
618
1150
|
export function command(
|
|
619
1151
|
argv: readonly (string | Arg)[] | typeof open,
|
|
620
1152
|
options: CommandOptions = {},
|
|
621
1153
|
// The implementation of the overloads above: each one states the input type.
|
|
622
1154
|
): EffectLogic<any, CommandOutput> {
|
|
1155
|
+
// THE authoring surface for a `command` Effect (ADR-0067). `argv` is never shell text — an
|
|
1156
|
+
// `arg("x")` entry is ONE argument, filled from the invoke's input — and `sh -c` is the author's
|
|
1157
|
+
// explicit choice. Bound to the real exec port here; `commandWith` in effect.ts is the seam the
|
|
1158
|
+
// unit tests share.
|
|
1159
|
+
//
|
|
623
1160
|
// An Open argv (ADR-0054) is the composer's to bind with `customize()`; see open-effect.ts.
|
|
624
1161
|
return commandWith(
|
|
625
1162
|
(endpoint, bearer) => createExecPort({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),
|