@jr2/orchestrator 0.6.0 → 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 +86 -9
- package/docs/concepts/architecture.md +41 -28
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +7 -5
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +4 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +50 -22
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +5 -4
- package/docs/concepts/pools-and-sources.md +187 -11
- package/docs/concepts/secrets-and-trust.md +73 -33
- package/docs/concepts/the-menu.md +11 -0
- package/docs/concepts/workspaces.md +6 -5
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/guides/add-tools-to-the-sandbox.md +55 -11
- 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 +24 -8
- 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/fix-a-failed-converge.md +18 -6
- 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 +9 -6
- package/docs/guides/look-inside-a-sandbox.md +101 -31
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-one-worker-per-item.md +15 -9
- package/docs/guides/set-an-agents-access-and-dials.md +2 -0
- 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 +1 -1
- package/docs/guides/watch-a-run.md +127 -121
- 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 +17 -7
- package/docs/llms.txt +81 -15
- package/docs/reference/authoring-surface/agent.md +1 -1
- package/docs/reference/authoring-surface/answer.md +1 -1
- package/docs/reference/authoring-surface/arg.md +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/file.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +1 -1
- package/docs/reference/authoring-surface/jr2setup.md +13 -3
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +5 -3
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +1 -1
- package/docs/reference/authoring-surface/pool.md +77 -9
- package/docs/reference/authoring-surface/push.md +1 -1
- package/docs/reference/authoring-surface/readyof.md +1 -1
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +1 -1
- 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 +56 -1
- 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 +14 -1
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
- package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-binding.md +1 -1
- 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-commandoutput.md +1 -1
- package/docs/reference/authoring-surface/type-customize.md +16 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-framepath.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
- 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-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
- 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 +1 -1
- 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-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-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 +16 -7
- 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 +1 -1
- package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
- package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
- package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
- package/docs/reference/authoring-surface/workspace.md +1 -1
- package/docs/reference/authoring-surface/worktree.md +1 -1
- package/docs/reference/authoring-surface.md +88 -167
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +235 -2
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
- package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
- 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 +9 -2
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
- 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 +18 -14
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
- 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 +32 -18
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
- 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 +116 -34
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
- 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 +7 -6
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
- 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 -18
- 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 +4 -3
- 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 +5 -4
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- 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 +7 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
- 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 +51 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
- 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 +17 -13
- 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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
- 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 +6 -4
- package/docs/reference/glossary.md +96 -42
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +976 -149
- package/docs/why.md +2 -1
- package/package.json +10 -3
- package/src/actor.ts +36 -10
- package/src/agent.ts +50 -0
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +52 -7
- package/src/config.ts +550 -1
- package/src/conversations.ts +453 -0
- package/src/custodian.ts +8 -2
- package/src/customize.ts +143 -4
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +2 -1
- package/src/effect.ts +60 -7
- package/src/effector-wire.ts +272 -27
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +10 -4
- package/src/held-secrets.ts +72 -23
- package/src/holds.ts +2 -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 +76 -38
- package/src/http.ts +889 -94
- package/src/index.ts +16 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +22 -3
- package/src/machine-doc.ts +26 -1
- package/src/mcp-effect.ts +424 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +64 -2
- package/src/notices.ts +138 -10
- package/src/open-body.ts +25 -11
- package/src/open-effect.ts +17 -4
- package/src/open.ts +4 -2
- package/src/parts.ts +87 -9
- package/src/party.ts +2 -1
- package/src/pick.ts +2 -2
- package/src/pool.ts +1142 -163
- package/src/registration.ts +203 -13
- package/src/run-host.ts +1509 -171
- package/src/run-list.ts +191 -0
- package/src/sandbox-kube.ts +53 -5
- package/src/secret-placement.ts +239 -33
- package/src/server.ts +53 -4
- package/src/setup.ts +72 -32
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +263 -22
- package/src/wire.ts +17 -0
- package/src/workspace.ts +50 -6
- package/src/worktree.ts +3 -3
- 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/notices.ts
CHANGED
|
@@ -27,8 +27,51 @@
|
|
|
27
27
|
// a second read in the same conversation cannot carry it too — and a restart forgets reservations
|
|
28
28
|
// along with everything else in memory. jr2 keeps no "handled" state: whether an Agent acted on a
|
|
29
29
|
// notice is not the ledger's concern.
|
|
30
|
+
//
|
|
31
|
+
// The same ledger keeps Steering messages (ADR-0002): one transport, two kinds of body. Each entry is
|
|
32
|
+
// an ENVELOPE — the address, the source, the audience, what is reserved and heard — and a BODY: a
|
|
33
|
+
// Notice, or a Steering message with its author. The address is a tagged value: the body's `scope`
|
|
34
|
+
// names its kind and `to` its value — `conversation` (a continued conversation's base id),
|
|
35
|
+
// `workspace` (a Sandbox name), or `slot` (an Agent slot of one Machine instance, ADR-0076). A
|
|
36
|
+
// message to a slot has one taker, like a conversation notice: the slot's open Turn at a step
|
|
37
|
+
// boundary, or else its next Turn's admission, whose conversation does not exist yet when it is
|
|
38
|
+
// sent. It RETIRES, recorded "not delivered", when its Machine instance ends (`retire`). The ledger
|
|
39
|
+
// also keeps the record of each message (`steering`): its author and its outcome.
|
|
40
|
+
|
|
41
|
+
import type { Notice, SteeringAuthor, SteeringMessage } from "./wire.ts";
|
|
42
|
+
|
|
43
|
+
/** One body of the ledger: a Notice (a fact with no author) or a Steering message (an author's
|
|
44
|
+
* text). The body's `scope` is the kind of its address. */
|
|
45
|
+
export type LedgerBody = Notice | SteeringMessage;
|
|
46
|
+
|
|
47
|
+
/** What became of one Steering message (ADR-0002): `held` until a Turn of its slot hears it,
|
|
48
|
+
* `heard` once one did, `not-delivered` when its Machine instance ended first. */
|
|
49
|
+
export type SteeringOutcome = "held" | "heard" | "not-delivered";
|
|
50
|
+
|
|
51
|
+
/** The run's record of one Steering message: the slot it went to, its author, its text, when it was
|
|
52
|
+
* sent, and what became of it. */
|
|
53
|
+
export type SteeringRecord = {
|
|
54
|
+
id: string;
|
|
55
|
+
agent: string;
|
|
56
|
+
author: SteeringAuthor;
|
|
57
|
+
text: string;
|
|
58
|
+
at: string;
|
|
59
|
+
outcome: SteeringOutcome;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** How many Steering messages a run's record keeps: the newest, and every held one. A message is at
|
|
63
|
+
* most 16 KiB, so the record stays under about 1.6 MiB of a run's blob. */
|
|
64
|
+
export const STEERING_RECORD_MAX = 100;
|
|
30
65
|
|
|
31
|
-
|
|
66
|
+
/** The Machine path of a slot (`<machine path>/<agent>`): what ends with its Machine instance. */
|
|
67
|
+
export const slotMachine = (slot: string): string => slot.slice(0, Math.max(0, slot.lastIndexOf("/")));
|
|
68
|
+
|
|
69
|
+
const isSteering = (body: LedgerBody): body is SteeringMessage => body.kind === "steering";
|
|
70
|
+
|
|
71
|
+
/** Whether a record is the record of this message to this slot: a message that an unheard
|
|
72
|
+
* admission carried is put back as a new entry, and its record stays the same. */
|
|
73
|
+
const recordOf = (r: SteeringRecord, body: SteeringMessage, slot: string): boolean =>
|
|
74
|
+
r.agent === slot && r.at === body.at && r.text === body.text;
|
|
32
75
|
|
|
33
76
|
/** Where an admission, or a step boundary, may hear notices: its continued conversation's base
|
|
34
77
|
* Instance ID (ADR-0057) and its Workspace's Sandbox name. Either may be absent — a fresh Turn has no
|
|
@@ -70,7 +113,8 @@ export const AUDIENCE_SKEW_MS = 2_000;
|
|
|
70
113
|
* own `bash` answer told them. */
|
|
71
114
|
export type PendingNotice = {
|
|
72
115
|
id: string;
|
|
73
|
-
|
|
116
|
+
/** The body: a Notice or a Steering message. */
|
|
117
|
+
notice: LedgerBody;
|
|
74
118
|
to: string;
|
|
75
119
|
source?: string;
|
|
76
120
|
audience?: string[];
|
|
@@ -93,15 +137,17 @@ export type NoticeLedgerState = {
|
|
|
93
137
|
/** Each retry ever recorded, by source and slot: a replayed raise records it once. */
|
|
94
138
|
retriesSeen?: string[];
|
|
95
139
|
members?: Record<string, NoticeMember[]>;
|
|
140
|
+
/** The record of the run's Steering messages, oldest first (ADR-0002). */
|
|
141
|
+
steering?: SteeringRecord[];
|
|
96
142
|
};
|
|
97
143
|
|
|
98
144
|
/** What one admission takes: the notices it carries, and their ids for the ledger write. */
|
|
99
145
|
export type TakenNotices = {
|
|
100
146
|
ids: string[];
|
|
101
|
-
notices:
|
|
147
|
+
notices: LedgerBody[];
|
|
102
148
|
/** Every notice taken, in the order of `ids`, with the source it was raised with — what `restore`
|
|
103
149
|
* puts back when no model heard the admission. */
|
|
104
|
-
raised?: Array<{ notice:
|
|
150
|
+
raised?: Array<{ notice: LedgerBody; source?: string }>;
|
|
105
151
|
};
|
|
106
152
|
|
|
107
153
|
/** What a Workspace notice's raise knows of its kill: the cut Turn's retry, the conversations its
|
|
@@ -129,6 +175,7 @@ export class NoticeLedger {
|
|
|
129
175
|
* retry `retried`. */
|
|
130
176
|
readonly #asRetry = new Map<string, string>();
|
|
131
177
|
readonly #newId: () => string;
|
|
178
|
+
readonly #steering: SteeringRecord[];
|
|
132
179
|
|
|
133
180
|
constructor(state: NoticeLedgerState | undefined, newId: () => string) {
|
|
134
181
|
this.#pending = (state?.pending ?? []).map((p) => ({
|
|
@@ -142,6 +189,71 @@ export class NoticeLedger {
|
|
|
142
189
|
this.#retriesSeen = new Set(state?.retriesSeen ?? []);
|
|
143
190
|
this.#members = new Map(Object.entries(state?.members ?? {}).map(([ws, ms]) => [ws, ms.map((m) => ({ ...m }))]));
|
|
144
191
|
this.#newId = newId;
|
|
192
|
+
this.#steering = (state?.steering ?? []).map((r) => ({ ...r }));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Hold a Steering message for `slot` (ADR-0002): its open Turn hears it at a step boundary, or
|
|
196
|
+
* else its next Turn's admission carries it. Recorded `held`. Answers its id. */
|
|
197
|
+
steer(message: SteeringMessage, slot: string): string {
|
|
198
|
+
const id = this.#newId();
|
|
199
|
+
this.#pending.push({ id, notice: message, to: slot });
|
|
200
|
+
this.#steering.push({
|
|
201
|
+
id,
|
|
202
|
+
agent: slot,
|
|
203
|
+
author: message.author,
|
|
204
|
+
text: message.text,
|
|
205
|
+
at: message.at,
|
|
206
|
+
outcome: "held",
|
|
207
|
+
});
|
|
208
|
+
this.#trimRecord();
|
|
209
|
+
return id;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The Machine instance at `machine` (a slot's Machine path) ended (ADR-0002): every message held
|
|
213
|
+
* for a slot of it, or of a Machine below it, retires, recorded `not-delivered`. With no
|
|
214
|
+
* `machine`, the run ended, and every held message retires. Answers whether anything changed. */
|
|
215
|
+
retire(machine?: string): boolean {
|
|
216
|
+
let changed = false;
|
|
217
|
+
for (const p of [...this.#pending]) {
|
|
218
|
+
if (!isSteering(p.notice)) continue;
|
|
219
|
+
const at = slotMachine(p.to);
|
|
220
|
+
if (machine !== undefined && at !== machine && !at.startsWith(`${machine}.`)) continue;
|
|
221
|
+
this.#pending.splice(this.#pending.indexOf(p), 1);
|
|
222
|
+
this.#reserved.delete(p.id);
|
|
223
|
+
this.#outcome(p.notice, p.to, "not-delivered");
|
|
224
|
+
changed = true;
|
|
225
|
+
}
|
|
226
|
+
return changed;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The run's record of its Steering messages, oldest first. */
|
|
230
|
+
steeringRecord(): SteeringRecord[] {
|
|
231
|
+
return this.#steering.map((r) => ({ ...r }));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The outcome of a message, on its record. */
|
|
235
|
+
#outcome(body: SteeringMessage, slot: string, outcome: SteeringOutcome): void {
|
|
236
|
+
const record = this.#steering.find((r) => recordOf(r, body, slot));
|
|
237
|
+
if (record !== undefined) record.outcome = outcome;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Whether a message's record says that it retired: one put back after its Machine ended stays
|
|
241
|
+
* retired. */
|
|
242
|
+
#retired(body: SteeringMessage, slot: string): boolean {
|
|
243
|
+
return this.#steering.some((r) => r.outcome === "not-delivered" && recordOf(r, body, slot));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Keep the newest {@link STEERING_RECORD_MAX} records, and every held one. */
|
|
247
|
+
#trimRecord(): void {
|
|
248
|
+
let over = this.#steering.length - STEERING_RECORD_MAX;
|
|
249
|
+
for (let i = 0; over > 0 && i < this.#steering.length; ) {
|
|
250
|
+
if (this.#steering[i]!.outcome === "held") {
|
|
251
|
+
i++;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
this.#steering.splice(i, 1);
|
|
255
|
+
over--;
|
|
256
|
+
}
|
|
145
257
|
}
|
|
146
258
|
|
|
147
259
|
/** Raise a notice for `to` (a conversation's base id, or a Sandbox name — the notice's own
|
|
@@ -240,7 +352,7 @@ export class NoticeLedger {
|
|
|
240
352
|
|
|
241
353
|
/** What {@link take} would reserve in this scope, reserving nothing: a step boundary asks first
|
|
242
354
|
* whether a guard kill is among them, and so whether it must learn who was told. */
|
|
243
|
-
peek(scope: NoticeScope): Array<{ id: string; notice:
|
|
355
|
+
peek(scope: NoticeScope): Array<{ id: string; notice: LedgerBody; source?: string }> {
|
|
244
356
|
return this.#eligible(scope, scope.taker ?? scope.conversation).map((p) => ({
|
|
245
357
|
id: p.id,
|
|
246
358
|
notice: p.notice,
|
|
@@ -253,7 +365,17 @@ export class NoticeLedger {
|
|
|
253
365
|
* for the other conversations: it is pending again for `taker`, the conversation that did not hear
|
|
254
366
|
* it, which is a member of its audience again. Nothing changes when the notice is pending for that
|
|
255
367
|
* conversation already. Answers whether anything changed. */
|
|
256
|
-
restore(notice:
|
|
368
|
+
restore(notice: LedgerBody, to: string, source?: string, taker?: string): boolean {
|
|
369
|
+
// A Steering message has one taker: pending again for its slot, unless its Machine instance
|
|
370
|
+
// ended while the admission that carried it was unheard.
|
|
371
|
+
if (isSteering(notice)) {
|
|
372
|
+
const same = (p: PendingNotice) =>
|
|
373
|
+
p.to === to && isSteering(p.notice) && p.notice.at === notice.at && p.notice.text === notice.text;
|
|
374
|
+
if (this.#retired(notice, to) || this.#pending.some(same)) return false;
|
|
375
|
+
this.#pending.push({ id: this.#newId(), notice, to });
|
|
376
|
+
this.#outcome(notice, to, "held");
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
257
379
|
const pending =
|
|
258
380
|
source === undefined
|
|
259
381
|
? notice.scope === "workspace" && taker !== undefined
|
|
@@ -303,6 +425,7 @@ export class NoticeLedger {
|
|
|
303
425
|
this.#reserved.delete(key);
|
|
304
426
|
if (p.notice.scope !== "workspace") {
|
|
305
427
|
this.#pending.splice(at, 1);
|
|
428
|
+
if (isSteering(p.notice)) this.#outcome(p.notice, p.to, "heard");
|
|
306
429
|
continue;
|
|
307
430
|
}
|
|
308
431
|
const slot = this.#asRetry.get(key);
|
|
@@ -421,6 +544,7 @@ export class NoticeLedger {
|
|
|
421
544
|
...(this.#members.size === 0
|
|
422
545
|
? {}
|
|
423
546
|
: { members: Object.fromEntries([...this.#members].map(([ws, ms]) => [ws, ms.map((m) => ({ ...m }))])) }),
|
|
547
|
+
...(this.#steering.length === 0 ? {} : { steering: this.#steering.map((r) => ({ ...r })) }),
|
|
424
548
|
};
|
|
425
549
|
}
|
|
426
550
|
|
|
@@ -434,10 +558,14 @@ export class NoticeLedger {
|
|
|
434
558
|
(scope.exceptPrefix === undefined || p.source === undefined || !p.source.startsWith(scope.exceptPrefix)) &&
|
|
435
559
|
(p.notice.scope === "conversation"
|
|
436
560
|
? p.to === scope.conversation
|
|
437
|
-
:
|
|
438
|
-
p.to === scope.
|
|
439
|
-
|
|
440
|
-
|
|
561
|
+
: p.notice.scope === "slot"
|
|
562
|
+
? p.to === scope.slot
|
|
563
|
+
: taker !== undefined &&
|
|
564
|
+
p.to === scope.workspace &&
|
|
565
|
+
!(p.heardBy ?? []).includes(taker) &&
|
|
566
|
+
(p.audience === undefined ||
|
|
567
|
+
p.audience.includes(taker) ||
|
|
568
|
+
this.#retryFor(p, scope, taker) !== undefined)),
|
|
441
569
|
);
|
|
442
570
|
}
|
|
443
571
|
|
package/src/open-body.ts
CHANGED
|
@@ -51,6 +51,7 @@ import { fail, FAILURE_GUARD, failureGuard } from "./fail.ts";
|
|
|
51
51
|
import { agent } from "./harness-client.ts";
|
|
52
52
|
import { SENT_BY } from "./party.ts";
|
|
53
53
|
import { composesSandbox, sandboxPartsOf, wrapperBodyOf } from "./parts.ts";
|
|
54
|
+
import { isGateSlot } from "./gate.ts";
|
|
54
55
|
import { open } from "./open.ts";
|
|
55
56
|
import { actorPath, boundRun } from "./registration.ts";
|
|
56
57
|
import { COMPILED_CHART, jr2Setup } from "./setup.ts";
|
|
@@ -115,7 +116,9 @@ export function jsonOver(value: unknown, cap: number): boolean {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/** The parts of the Chart wrapper that a Chart can use, by their keys in `actors`, `guards`,
|
|
118
|
-
* `actions` and `delays`. `
|
|
119
|
+
* `actions` and `delays`. `gates` lends Gate slots (`gate({ … })`) that hear a Hook, by slot name:
|
|
120
|
+
* the door refuses a Chart that invokes one whose `delivers` names an event that is not in the
|
|
121
|
+
* Chart's `events`. `gate: true` lends the Gate, so that a Chart can wait for an answer from
|
|
119
122
|
* outside the run. `fail: true` lends the Part that fails the run, so that a Chart can end its run
|
|
120
123
|
* as a Failure (ADR-0073): the Machine decides which ends a Chart can choose. A Chart can use no
|
|
121
124
|
* other part. */
|
|
@@ -123,6 +126,7 @@ export type Lends = {
|
|
|
123
126
|
agents?: string[];
|
|
124
127
|
effects?: string[];
|
|
125
128
|
machines?: string[];
|
|
129
|
+
gates?: string[];
|
|
126
130
|
guards?: string[];
|
|
127
131
|
actions?: string[];
|
|
128
132
|
delays?: string[];
|
|
@@ -161,7 +165,7 @@ export type OpenBodySlot = { readonly openBody: OpenBodyDeclaration };
|
|
|
161
165
|
/** The logic `openBody()` returns: actor logic that carries the brand. */
|
|
162
166
|
export type OpenBodyLogic = UnknownActorLogic & OpenBodySlot;
|
|
163
167
|
|
|
164
|
-
const LEND_KINDS = ["agents", "effects", "machines", "guards", "actions", "delays"] as const;
|
|
168
|
+
const LEND_KINDS = ["agents", "effects", "machines", "gates", "guards", "actions", "delays"] as const;
|
|
165
169
|
const WORKSPACE: readonly WorkspaceAccess[] = ["write", "read", "none"];
|
|
166
170
|
const CEILING_KEYS = ["turns", "wallClock", "transitions", "fanOut", "size"] as const;
|
|
167
171
|
const DURATION = /^(\d+(\.\d+)?)(s|m|h)$/;
|
|
@@ -170,9 +174,9 @@ const MAX_WALL_CLOCK_MS = 2_147_483_647;
|
|
|
170
174
|
/** A name a Chart's grammar reads as its own, so a lent part by that name would be shadowed. */
|
|
171
175
|
const reservedName = (kind: (typeof LEND_KINDS)[number], v: string): string | undefined => {
|
|
172
176
|
if (v.includes(".")) return `"${v}" holds a dot; a lent name is a slot key, and dotted names are jr2's and xstate's`;
|
|
173
|
-
if ((kind === "agents" || kind === "effects" || kind === "machines") && v === "gate")
|
|
177
|
+
if ((kind === "agents" || kind === "effects" || kind === "machines" || kind === "gates") && v === "gate")
|
|
174
178
|
return `"gate" is the kit's Gate; lend a slot of another name`;
|
|
175
|
-
if ((kind === "agents" || kind === "effects" || kind === "machines") && v === "fail")
|
|
179
|
+
if ((kind === "agents" || kind === "effects" || kind === "machines" || kind === "gates") && v === "fail")
|
|
176
180
|
return `"fail" is the kit's Part that fails the run, lent as \`fail: true\`; lend a slot of another name`;
|
|
177
181
|
if (kind === "actions" && (GENERIC_ACTIONS as readonly string[]).includes(v))
|
|
178
182
|
return `"${v}" is a generic action every Chart has; lend an action of another name`;
|
|
@@ -520,7 +524,12 @@ const EMPTY_LENT: LentParts = { actors: {}, guards: {}, actions: {}, delays: {}
|
|
|
520
524
|
/** The lent names of a declaration, by the implementation map each resolves in. */
|
|
521
525
|
function lentNames(decl: OpenBodyDeclaration) {
|
|
522
526
|
return {
|
|
523
|
-
actors: [
|
|
527
|
+
actors: [
|
|
528
|
+
...(decl.lends.agents ?? []),
|
|
529
|
+
...(decl.lends.effects ?? []),
|
|
530
|
+
...(decl.lends.machines ?? []),
|
|
531
|
+
...(decl.lends.gates ?? []),
|
|
532
|
+
],
|
|
524
533
|
guards: [...(decl.lends.guards ?? [])],
|
|
525
534
|
actions: [...(decl.lends.actions ?? [])],
|
|
526
535
|
delays: [...(decl.lends.delays ?? [])],
|
|
@@ -977,7 +986,7 @@ function gateInput(value: unknown, self: AnyActorRef): unknown {
|
|
|
977
986
|
return { ...(value as Record<string, unknown>), gate: [...actorPath(self), id].join(".") };
|
|
978
987
|
}
|
|
979
988
|
|
|
980
|
-
function invokeOf(inv: ChartInvoke, agents: ReadonlySet<string>): unknown {
|
|
989
|
+
function invokeOf(inv: ChartInvoke, agents: ReadonlySet<string>, gates: ReadonlySet<string>): unknown {
|
|
981
990
|
const out: Record<string, unknown> = { src: inv.src };
|
|
982
991
|
if (inv.id !== undefined) out.id = inv.id;
|
|
983
992
|
if (inv.input !== undefined) {
|
|
@@ -985,7 +994,7 @@ function invokeOf(inv: ChartInvoke, agents: ReadonlySet<string>): unknown {
|
|
|
985
994
|
// Resolved at invoke time, with `{ context, event, input }` (ADR-0070).
|
|
986
995
|
out.input = agents.has(inv.src)
|
|
987
996
|
? (args: Args) => turnInput(resolveAt(template, args), placementOf(args.context?.[CHART_INPUT_KEY]))
|
|
988
|
-
: inv.src === "gate"
|
|
997
|
+
: inv.src === "gate" || gates.has(inv.src)
|
|
989
998
|
? (args: Args & { self: AnyActorRef }) => gateInput(resolveAt(template, args), args.self)
|
|
990
999
|
: (args: Args) => resolveAt(template, args);
|
|
991
1000
|
}
|
|
@@ -994,14 +1003,16 @@ function invokeOf(inv: ChartInvoke, agents: ReadonlySet<string>): unknown {
|
|
|
994
1003
|
return out;
|
|
995
1004
|
}
|
|
996
1005
|
|
|
997
|
-
function stateOf(st: ChartState, agents: ReadonlySet<string>): Record<string, unknown> {
|
|
1006
|
+
function stateOf(st: ChartState, agents: ReadonlySet<string>, gates: ReadonlySet<string>): Record<string, unknown> {
|
|
998
1007
|
const out: Record<string, unknown> = {};
|
|
999
1008
|
if (st.type !== undefined) out.type = st.type;
|
|
1000
1009
|
if (st.initial !== undefined) out.initial = st.initial;
|
|
1001
1010
|
if (st.history !== undefined) out.history = st.history;
|
|
1002
|
-
if (st.states !== undefined) out.states = mapValues(st.states, (child) => stateOf(child, agents));
|
|
1011
|
+
if (st.states !== undefined) out.states = mapValues(st.states, (child) => stateOf(child, agents, gates));
|
|
1003
1012
|
if (st.invoke !== undefined)
|
|
1004
|
-
out.invoke = Array.isArray(st.invoke)
|
|
1013
|
+
out.invoke = Array.isArray(st.invoke)
|
|
1014
|
+
? st.invoke.map((inv) => invokeOf(inv, agents, gates))
|
|
1015
|
+
: invokeOf(st.invoke, agents, gates);
|
|
1005
1016
|
if (st.on !== undefined) out.on = mapValues(st.on, transitionsOf);
|
|
1006
1017
|
if (st.always !== undefined) out.always = transitionsOf(st.always);
|
|
1007
1018
|
if (st.after !== undefined) out.after = mapValues(st.after, transitionsOf);
|
|
@@ -1051,6 +1062,9 @@ export function compileChart(
|
|
|
1051
1062
|
...Object.keys(lent.actors).filter((slot) => isAgent(lent.actors[slot])),
|
|
1052
1063
|
...Object.keys(minted),
|
|
1053
1064
|
]);
|
|
1065
|
+
// The lent Gate slots (ADR-0011): an authored gate id sits below the body's actor path, as the kit
|
|
1066
|
+
// Gate's does.
|
|
1067
|
+
const gateSlots = new Set(Object.keys(lent.actors).filter((slot) => isGateSlot(lent.actors[slot])));
|
|
1054
1068
|
const initialContext = text.context ?? {};
|
|
1055
1069
|
const config = {
|
|
1056
1070
|
id: text.id ?? options.id ?? "chart",
|
|
@@ -1059,7 +1073,7 @@ export function compileChart(
|
|
|
1059
1073
|
[CHART_INPUT_KEY]: input ?? {},
|
|
1060
1074
|
}),
|
|
1061
1075
|
initial: text.initial,
|
|
1062
|
-
states: mapValues(text.states, (st) => stateOf(st, agentSlots)),
|
|
1076
|
+
states: mapValues(text.states, (st) => stateOf(st, agentSlots, gateSlots)),
|
|
1063
1077
|
output: ({ context, event }: Args) =>
|
|
1064
1078
|
({
|
|
1065
1079
|
outcome: "done",
|
package/src/open-effect.ts
CHANGED
|
@@ -28,8 +28,9 @@ import type { Arg, EffectLogic, EffectParts, EffectSnapshot } from "./effect.ts"
|
|
|
28
28
|
import type { Secret, Text } from "./http-effect.ts";
|
|
29
29
|
|
|
30
30
|
/** The parts of an Effect a package may leave Open: an http url, a command's argv, a secret's name,
|
|
31
|
-
* a dispatch's target (its url and token together, ADR-0071)
|
|
32
|
-
|
|
31
|
+
* a dispatch's target (its url and token together, ADR-0071), an `mcp` Effect's server and tool
|
|
32
|
+
* (ADR-0079). */
|
|
33
|
+
export type OpenEffectPart = "url" | "argv" | "secret" | "target" | "server" | "tool";
|
|
33
34
|
|
|
34
35
|
// What a composer binds an Effect's Open parts to, each key one part (ADR-0054); a dispatch's
|
|
35
36
|
// target is its url and token, bound as one (ADR-0071). One object for an
|
|
@@ -39,7 +40,8 @@ export type OpenEffectPart = "url" | "argv" | "secret" | "target";
|
|
|
39
40
|
* The values that bind the Open parts of one Effect. A packaged Machine leaves a part of an Effect
|
|
40
41
|
* Open when it cannot know it. That part is the url of an `http` Effect, the argv of a `command`,
|
|
41
42
|
* the name of a secret, written `secret(open)`, or the target of a `dispatch`: the url of another
|
|
42
|
-
* Orchestrator and the secret that holds its Instance token, bound as one
|
|
43
|
+
* Orchestrator and the secret that holds its Instance token, bound as one, or the server or the tool
|
|
44
|
+
* of an `mcp` Effect. You bind it once, where you register the
|
|
43
45
|
* Machine, with `customize(machine, { effects: { <slot>: binding } })`. Each key binds one Open
|
|
44
46
|
* part. For an Effect with two Open parts, one object holds both keys.
|
|
45
47
|
*
|
|
@@ -101,6 +103,11 @@ export type EffectBinding = {
|
|
|
101
103
|
* `jr2.config.ts`. The Effector reads it from a file of that name. */
|
|
102
104
|
readonly secret?: string;
|
|
103
105
|
readonly target?: { readonly url: string; readonly token: Secret };
|
|
106
|
+
/** The server of an `mcp` Effect whose server is Open: a name of the `mcp` block of
|
|
107
|
+
* `jr2.config.ts`. The server's url and its secret are the config's. */
|
|
108
|
+
readonly server?: string;
|
|
109
|
+
/** The tool of an `mcp.call` Effect whose tool is Open: a tool name of the server. */
|
|
110
|
+
readonly tool?: string;
|
|
104
111
|
};
|
|
105
112
|
|
|
106
113
|
declare const openPart: unique symbol;
|
|
@@ -135,7 +142,11 @@ export function openPartsOf(parts: EffectParts): OpenEffectPart[] {
|
|
|
135
142
|
if (parts.kind === "http" && parts.url === open) out.push("url");
|
|
136
143
|
if (parts.kind === "command" && parts.argv === open) out.push("argv");
|
|
137
144
|
if (parts.kind === "dispatch" && parts.target === open) out.push("target");
|
|
138
|
-
if (parts.kind
|
|
145
|
+
if (parts.kind === "mcp") {
|
|
146
|
+
if (parts.server === open) out.push("server");
|
|
147
|
+
if (parts.tool === open) out.push("tool");
|
|
148
|
+
}
|
|
149
|
+
if (parts.kind !== "push" && parts.kind !== "mcp" && parts.openSecret) out.push("secret");
|
|
139
150
|
return out;
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -151,6 +162,8 @@ const BINDING_SAMPLE: Record<OpenEffectPart, string> = {
|
|
|
151
162
|
argv: 'argv: ["<program>", "<arg>"]',
|
|
152
163
|
secret: 'secret: "<name>"',
|
|
153
164
|
target: 'target: { url: "https://<host>", token: secret("<name>") }',
|
|
165
|
+
server: 'server: "<name>"',
|
|
166
|
+
tool: 'tool: "<tool>"',
|
|
154
167
|
};
|
|
155
168
|
|
|
156
169
|
/** One binding object for these Open parts — `{ url: "…", secret: "<name>" }` for two. */
|
package/src/open.ts
CHANGED
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
* - a Repo Slot: `repos: { app: open }`, or the whole map: `repos: open`;
|
|
15
15
|
* - an Agent's model: `agent({ model: open, … })`;
|
|
16
16
|
* - an Effect's url or argv: `http({ url: open, … })`, `command(open)`;
|
|
17
|
-
* - the secret of an Effect's auth, of a header, or of a command's `env`: `secret(open)
|
|
17
|
+
* - the secret of an Effect's auth, of a header, or of a command's `env`: `secret(open)`;
|
|
18
|
+
* - the look of a Gate slot: `gate({ …, look: open })`.
|
|
18
19
|
*
|
|
19
20
|
* `jr2 up` refuses a Workflow with an Open part that nobody bound, and prints the `customize()`
|
|
20
21
|
* line that binds it: ``refusing: workflow "<name>" leaves <part> open — bind it where the Machine
|
|
21
22
|
* is registered: export const machine = customize(<import>, …) (<adr>)``. A run never sees an
|
|
22
|
-
* Open part.
|
|
23
|
+
* Open part. An Open look is the one exception: `jr2 up` warns about it and does not refuse,
|
|
24
|
+
* because a person can still answer the Gate with `jr2 send`.
|
|
23
25
|
*
|
|
24
26
|
* @example
|
|
25
27
|
* ```ts
|
package/src/parts.ts
CHANGED
|
@@ -48,8 +48,9 @@
|
|
|
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";
|
|
@@ -601,6 +602,20 @@ export type CarriedEffect = {
|
|
|
601
602
|
input?: Record<string, unknown>;
|
|
602
603
|
open?: Array<"target">;
|
|
603
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
|
+
}
|
|
604
619
|
);
|
|
605
620
|
|
|
606
621
|
/** One state that can fail the run (ADR-0073): it invokes a `fail()` slot. `state` is the state's
|
|
@@ -649,9 +664,22 @@ export type CarriedParts = {
|
|
|
649
664
|
/**
|
|
650
665
|
* The Instance Harness is needed (ADR-0031): an Agent declares `workspace: "none"`, or an Open
|
|
651
666
|
* body allows it. `models` are the models its Turns may run: those Agents', and those Open bodies'
|
|
652
|
-
* allowed ones.
|
|
667
|
+
* allowed ones. `servers` are the MCP servers those Agents name (ADR-0079), sorted, deduped.
|
|
668
|
+
* Absent when nothing needs it.
|
|
653
669
|
*/
|
|
654
|
-
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[];
|
|
655
683
|
};
|
|
656
684
|
|
|
657
685
|
/** A machine actor, told apart from a promise/callback/observable one by having a state tree.
|
|
@@ -742,6 +770,7 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
742
770
|
const openEffects: CarriedParts["openEffects"] = [];
|
|
743
771
|
const sizes: CarriedSize[] = [];
|
|
744
772
|
const openBodies: CarriedParts["openBodies"] = [];
|
|
773
|
+
const mcpAgents: CarriedMcpAgent[] = [];
|
|
745
774
|
// The Machine that declares each Open body: where its lent Agents are read (not carried data).
|
|
746
775
|
const bodyWrappers = new Map<CarriedParts["openBodies"][number], AnyStateMachine>();
|
|
747
776
|
let sandboxed = false;
|
|
@@ -832,8 +861,11 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
832
861
|
const body = wrapperBodyOf(machine);
|
|
833
862
|
for (const { state, slot } of failingStatesOf(machine)) failures.push({ machine: machine.id, state, slot, path });
|
|
834
863
|
for (const [name, logic] of Object.entries(machine.implementations.actors as Record<string, unknown>)) {
|
|
835
|
-
if (isAgent(logic))
|
|
836
|
-
|
|
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)) {
|
|
837
869
|
const body = { slot: name, declaration: openBodyDeclarationOf(logic), path };
|
|
838
870
|
openBodies.push(body);
|
|
839
871
|
bodyWrappers.set(body, machine);
|
|
@@ -842,6 +874,10 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
842
874
|
effects.push(effect);
|
|
843
875
|
if (effect.kind !== "push" && effect.open !== undefined)
|
|
844
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));
|
|
845
881
|
} else {
|
|
846
882
|
const child = asMachine(logic);
|
|
847
883
|
if (child) walk(child, slotPath(path, name, body), name === body ? route : [...route, name]);
|
|
@@ -858,6 +894,7 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
858
894
|
const noneBodies = openBodies.filter((b) => bodyTurnsRunIn(bodyWrappers.get(b)!, b.declaration, "instance"));
|
|
859
895
|
const harnessModels = new Set<string>(menuOnly.map((a) => a.definition.model).filter(bound));
|
|
860
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)));
|
|
861
898
|
return {
|
|
862
899
|
agents,
|
|
863
900
|
failures,
|
|
@@ -871,7 +908,10 @@ export function partsOf(machines: Iterable<AnyStateMachine>): CarriedParts {
|
|
|
871
908
|
composesSandbox: sandboxed,
|
|
872
909
|
openBodies,
|
|
873
910
|
models: [...models].sort(),
|
|
874
|
-
...(menuOnly.length || noneBodies.length
|
|
911
|
+
...(menuOnly.length || noneBodies.length
|
|
912
|
+
? { instanceHarness: { models: [...harnessModels].sort(), servers: [...harnessServers].sort() } }
|
|
913
|
+
: {}),
|
|
914
|
+
mcpAgents,
|
|
875
915
|
};
|
|
876
916
|
}
|
|
877
917
|
|
|
@@ -924,6 +964,11 @@ export function placedEffectsOf(root: AnyStateMachine): CarriedEffect[] {
|
|
|
924
964
|
effects.push(carriedEffectOf(name, path, route, logic.effect));
|
|
925
965
|
continue;
|
|
926
966
|
}
|
|
967
|
+
const look = lookReadOf(logic);
|
|
968
|
+
if (look) {
|
|
969
|
+
effects.push(carriedEffectOf(`${name}.look`, path, route, look.effect));
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
927
972
|
const child = asMachine(logic);
|
|
928
973
|
if (child) walk(child, slotPath(path, name, body), name === body ? route : [...route, name], on);
|
|
929
974
|
}
|
|
@@ -976,6 +1021,22 @@ export function carriedEffectOf(
|
|
|
976
1021
|
...(parts.file ? { file: true as const } : {}),
|
|
977
1022
|
};
|
|
978
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"> } : {}),
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
979
1040
|
if (parts.kind === "push") {
|
|
980
1041
|
const { sign, squash, force, pin, timeoutMs, slot } = parts;
|
|
981
1042
|
return {
|
|
@@ -1040,6 +1101,10 @@ export type WorkspacePod = {
|
|
|
1040
1101
|
* down to the next `workspace()`, which is a pod of its own, and every model an Open body below
|
|
1041
1102
|
* it allows a Chart's own Agents when it allows "write" or "read" (ADR-0070). Sorted, deduped. */
|
|
1042
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 }>;
|
|
1043
1108
|
/** The Effects placed in this pod's Sandbox Effector (ADR-0068): every `push`, file-body `http` and
|
|
1044
1109
|
* credentialed `command` slot below the `workspace()`, down to the next `workspace()` — read off
|
|
1045
1110
|
* their plain data, statically. Empty → the pod gets no Sandbox Effector and no Secret. */
|
|
@@ -1076,6 +1141,7 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1076
1141
|
on: Set<AnyStateMachine>,
|
|
1077
1142
|
effects: Map<string, CarriedEffect>,
|
|
1078
1143
|
cuts: Set<string>,
|
|
1144
|
+
mcp: NonNullable<WorkspacePod["mcp"]>,
|
|
1079
1145
|
path: OpenSlot["path"],
|
|
1080
1146
|
route: PlacementRoute,
|
|
1081
1147
|
): void => {
|
|
@@ -1083,12 +1149,14 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1083
1149
|
on.add(machine);
|
|
1084
1150
|
const body = wrapperBodyOf(machine);
|
|
1085
1151
|
const visit = (child: AnyStateMachine, at: OpenSlot["path"], to: PlacementRoute) => {
|
|
1086
|
-
if (!composesSandbox(child)) modelsIn(child, models, on, effects, cuts, at, to);
|
|
1152
|
+
if (!composesSandbox(child)) modelsIn(child, models, on, effects, cuts, mcp, at, to);
|
|
1087
1153
|
};
|
|
1088
1154
|
for (const [name, logic] of Object.entries(machine.implementations.actors as Record<string, unknown>)) {
|
|
1089
1155
|
if (isAgent(logic)) {
|
|
1090
1156
|
const { model, workspace } = logic.definition;
|
|
1091
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 });
|
|
1092
1160
|
continue;
|
|
1093
1161
|
}
|
|
1094
1162
|
// A Turn under an Open body may run in this pod with any model the body allows (ADR-0070):
|
|
@@ -1123,7 +1191,8 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1123
1191
|
const models = new Set<string>();
|
|
1124
1192
|
const effects = new Map<string, CarriedEffect>();
|
|
1125
1193
|
const cuts = new Set<string>();
|
|
1126
|
-
|
|
1194
|
+
const mcp: NonNullable<WorkspacePod["mcp"]> = [];
|
|
1195
|
+
modelsIn(machine, models, new Set(), effects, cuts, mcp, path, route);
|
|
1127
1196
|
const parts = sandboxPartsOf(machine);
|
|
1128
1197
|
const bindings: Record<string, string | undefined> = {};
|
|
1129
1198
|
if (parts.repos !== open) {
|
|
@@ -1139,6 +1208,7 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1139
1208
|
models: [...models].sort(),
|
|
1140
1209
|
effects: [...effects.values()],
|
|
1141
1210
|
...(cuts.size ? { cuts: [...cuts] } : {}),
|
|
1211
|
+
...(mcp.length ? { mcp } : {}),
|
|
1142
1212
|
...(parts.effector ? { effector: { ...parts.effector } } : {}),
|
|
1143
1213
|
...(parts.image !== undefined ? { image: parts.image } : {}),
|
|
1144
1214
|
bindings,
|
|
@@ -1157,6 +1227,14 @@ export function workspacePodsOf(root: AnyStateMachine): WorkspacePod[] {
|
|
|
1157
1227
|
return pods;
|
|
1158
1228
|
}
|
|
1159
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
|
+
|
|
1160
1238
|
/** Every state of `machine`'s own tree that invokes a `fail()` slot (ADR-0073), in document order,
|
|
1161
1239
|
* with the slot key: a state that can fail the run. A child Machine's states are its own walk's. */
|
|
1162
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;
|
package/src/pick.ts
CHANGED
|
@@ -424,8 +424,8 @@ export function fromPick<N extends string, I extends z.ZodObject, C, E extends E
|
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
/**
|
|
427
|
-
* One reply since the pick, as the author reads it
|
|
428
|
-
* (`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
|
|
429
429
|
* pick — typed by the Machine's events. A reply is from outside the Machine: the events it sends
|
|
430
430
|
* itself are not links.
|
|
431
431
|
*/
|