@jr2/orchestrator 0.5.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/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +56 -20
- package/docs/concepts/architecture.md +30 -17
- package/docs/concepts/charts.md +6 -3
- package/docs/concepts/converge.md +3 -0
- package/docs/concepts/effects.md +86 -31
- package/docs/concepts/machines-and-workflows.md +9 -0
- package/docs/concepts/pools-and-sources.md +6 -6
- package/docs/concepts/secrets-and-trust.md +126 -42
- package/docs/concepts/the-menu.md +3 -0
- package/docs/concepts/workspaces.md +129 -51
- package/docs/get-started/an-agent-in-a-sandbox.md +21 -19
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +17 -4
- package/docs/guides/bind-open-parts.md +5 -4
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +1 -1
- package/docs/guides/let-an-agent-write-a-chart.md +5 -1
- package/docs/guides/look-inside-a-sandbox.md +18 -15
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +19 -18
- package/docs/guides/set-an-agents-access-and-dials.md +67 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/use-a-hosted-provider.md +6 -0
- package/docs/guides/watch-a-run.md +47 -24
- package/docs/guides/write-a-workflow.md +1 -1
- package/docs/llms.txt +33 -12
- package/docs/reference/authoring-surface/agent.md +34 -11
- package/docs/reference/authoring-surface/answer.md +19 -3
- package/docs/reference/authoring-surface/arg.md +27 -5
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/command.md +36 -21
- package/docs/reference/authoring-surface/fail.md +17 -4
- package/docs/reference/authoring-surface/file.md +36 -16
- package/docs/reference/authoring-surface/http.md +108 -8
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +1 -1
- package/docs/reference/authoring-surface/open.md +36 -5
- package/docs/reference/authoring-surface/paramsof.md +22 -4
- package/docs/reference/authoring-surface/pool.md +4 -4
- package/docs/reference/authoring-surface/push.md +30 -12
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +25 -4
- package/docs/reference/authoring-surface/source.md +3 -3
- package/docs/reference/authoring-surface/type-agentdefinition.md +2 -2
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +5 -6
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +22 -4
- package/docs/reference/authoring-surface/type-customize.md +4 -2
- package/docs/reference/authoring-surface/type-dispatchoutput.md +8 -1
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +2 -4
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +7 -1
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +2 -2
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +32 -7
- package/docs/reference/authoring-surface/type-reposlot.md +5 -5
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcespec.md +10 -10
- package/docs/reference/authoring-surface/type-workspaced.md +29 -8
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +65 -37
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +30 -10
- package/docs/reference/cli.md +1 -1
- package/docs/reference/config.md +13 -4
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +17 -10
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +29 -19
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +30 -30
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +35 -17
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +8 -2
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +6 -4
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +21 -9
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +2 -1
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +8 -9
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +62 -65
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +9 -1
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +13 -12
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +4 -1
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +18 -19
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +8 -8
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +4 -4
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +24 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +5 -3
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +110 -67
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +101 -74
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +5 -6
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +2 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +181 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +74 -63
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +31 -20
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +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 +3 -0
- package/docs/reference/glossary.md +51 -12
- package/docs/reference/http-api.md +45 -7
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +5 -4
- package/package.json +3 -3
- package/src/actor.ts +161 -35
- package/src/agent.ts +6 -6
- package/src/ambient.ts +3 -5
- package/src/chart.ts +186 -19
- package/src/charts.ts +9 -2
- package/src/config.ts +16 -6
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +251 -77
- package/src/customize.ts +11 -3
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +343 -127
- package/src/effector-wire.ts +6 -3
- package/src/fail.ts +17 -4
- package/src/harness-client.ts +74 -31
- 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 +88 -32
- package/src/http.ts +56 -17
- package/src/index.ts +15 -1
- package/src/internal.ts +1 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +3 -4
- package/src/model-keys.ts +24 -0
- package/src/names.ts +19 -6
- package/src/notices.ts +443 -54
- package/src/open-body.ts +85 -19
- package/src/open.ts +36 -5
- package/src/parts.ts +40 -28
- package/src/party.ts +33 -0
- package/src/pick.ts +41 -7
- package/src/pool.ts +44 -23
- package/src/registration.ts +53 -11
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +106 -20
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +63 -16
- package/src/secret-placement.ts +9 -2
- package/src/setup.ts +86 -6
- package/src/tokens.ts +107 -32
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +10 -10
- package/src/workspace.ts +264 -253
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
|
@@ -9,21 +9,24 @@ Function, in `@jr2/orchestrator/src/http-effect.ts`. It is an export of `@jr2/or
|
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
function file<S extends z.ZodType | undefined = undefined>(path: FromInput<S, string>, options: {
|
|
12
|
-
slot?: string;
|
|
13
12
|
segments?: FromInput<S, readonly string[]>;
|
|
14
13
|
contentType?: string;
|
|
15
14
|
} = {}): FileBody<S>
|
|
16
15
|
```
|
|
17
16
|
|
|
18
17
|
Make a Worktree file the body of an `http` Effect: `body: file("dist/report.pdf")`. The Effect
|
|
19
|
-
then runs in the Sandbox Effector of the Workspace whose body invokes it.
|
|
20
|
-
|
|
18
|
+
then runs in the Sandbox Effector of the Workspace whose body invokes it. Under a Workspace with
|
|
19
|
+
Repo Slots, the invoke's input names the Worktree: `input: { worktree: "/work/app/fix" }`. It is a
|
|
20
|
+
Worktree that a cut of this run made. That Effector opens the file in that Worktree and sends it.
|
|
21
|
+
In a Workspace with no Repo Slot, the input names no `worktree`, and the path is relative to
|
|
21
22
|
`/work`, the folder that its steps share. Use it with `PUT`, `POST` or `PATCH`. `path` and
|
|
22
23
|
`segments` can be functions of the input of the Effect.
|
|
23
24
|
|
|
24
|
-
Some paths and files give the output `refused`, and then nothing is sent. The
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Some inputs, paths and files give the output `refused`, and then nothing is sent. The `worktree`
|
|
26
|
+
has no cut in this Sandbox: `no cut made <path> in this Sandbox: cut it with worktree(); nothing
|
|
27
|
+
was sent`. Or the input names a `worktree` in a Workspace with no Repo Slot. Or the path has a
|
|
28
|
+
symlink in it, has `..`, or is absolute. Or the file is a FIFO, a directory, or a file with more
|
|
29
|
+
than one link.
|
|
27
30
|
|
|
28
31
|
It refuses, when you call it:
|
|
29
32
|
- a path that is not a string or a function: `file(path): the path is a string, or a function of
|
|
@@ -43,16 +46,17 @@ And at `jr2 up`, an Effect that no `workspace()` invokes: `workflow "<workflow>"
|
|
|
43
46
|
there is no Worktree to read; invoke it from a workspace()'s body, or send a json or text body
|
|
44
47
|
(ADR-0068)`.
|
|
45
48
|
|
|
49
|
+
And when it starts, under a Workspace with Repo Slots, an input with no `worktree`: ``http
|
|
50
|
+
"<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its input names no
|
|
51
|
+
`worktree` ``.
|
|
52
|
+
|
|
46
53
|
**Parameters**
|
|
47
54
|
|
|
48
|
-
- `path` ([`FromInput`](type-frominput.md)): The file, relative to the Worktree of the
|
|
49
|
-
with no Repo Slot: a string, or a function of the input of the Effect
|
|
55
|
+
- `path` ([`FromInput`](type-frominput.md)): The file, relative to the Worktree that `worktree` of the input names, or to
|
|
56
|
+
`/work` in a Workspace with no Repo Slot: a string, or a function of the input of the Effect
|
|
57
|
+
that gives one.
|
|
50
58
|
- `options`: Optional. Each field says what it does.
|
|
51
59
|
|
|
52
|
-
- `slot` (optional): `string`
|
|
53
|
-
|
|
54
|
-
The Repo Slot whose Worktree holds the file. Write it only when the Workspace has more than
|
|
55
|
-
one.
|
|
56
60
|
- `segments` (optional): `FromInput<S, readonly string[]>`
|
|
57
61
|
|
|
58
62
|
Path segments that jr2 adds to the end of the url, as `path` does in `request` for a json or
|
|
@@ -73,17 +77,22 @@ import {
|
|
|
73
77
|
fail,
|
|
74
78
|
file,
|
|
75
79
|
http,
|
|
80
|
+
isReady,
|
|
76
81
|
jr2Setup,
|
|
77
82
|
secret,
|
|
78
83
|
workspace,
|
|
84
|
+
worktree,
|
|
79
85
|
type Workspaced,
|
|
80
86
|
} from "@jr2/orchestrator";
|
|
81
87
|
|
|
82
88
|
type Input = Workspaced<{}, "app">;
|
|
83
89
|
|
|
90
|
+
const reportTree = (context: Input) => `${context.workspace.repos.app}/report` as const;
|
|
91
|
+
|
|
84
92
|
const body = jr2Setup({
|
|
85
93
|
types: {} as { context: Input; input: Input },
|
|
86
94
|
actors: {
|
|
95
|
+
cut: worktree(),
|
|
87
96
|
build: command(["npm", "run", "report"]),
|
|
88
97
|
upload: http({
|
|
89
98
|
method: "PUT",
|
|
@@ -100,18 +109,29 @@ const body = jr2Setup({
|
|
|
100
109
|
id: "report",
|
|
101
110
|
context: ({ input }) => input,
|
|
102
111
|
on: { "workspace.lost": ".lost" },
|
|
103
|
-
initial: "
|
|
112
|
+
initial: "cutting",
|
|
104
113
|
states: {
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
cutting: {
|
|
115
|
+
invoke: {
|
|
116
|
+
src: "cut",
|
|
117
|
+
input: ({ context }) => ({ path: reportTree(context), branch: "report" }),
|
|
118
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "building" }, { target: "notCut" }],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
building: {
|
|
122
|
+
invoke: { src: "build", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "uploading" },
|
|
123
|
+
},
|
|
124
|
+
uploading: {
|
|
125
|
+
invoke: { src: "upload", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "done" },
|
|
126
|
+
},
|
|
107
127
|
done: { type: "final" },
|
|
128
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
108
129
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
109
130
|
},
|
|
110
131
|
});
|
|
111
132
|
|
|
112
133
|
export const machine = workspace(body, {
|
|
113
134
|
repos: { app: { url: "https://github.com/you/app.git" } },
|
|
114
|
-
spec: () => ({ branch: "report" }),
|
|
115
135
|
});
|
|
116
136
|
```
|
|
117
137
|
|
|
@@ -7,6 +7,8 @@ description: "Make an `http` Effect: a call to a URL, with Effect secrets that t
|
|
|
7
7
|
|
|
8
8
|
Function, in `@jr2/orchestrator/src/http-effect.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
|
|
9
9
|
|
|
10
|
+
## Overload 1
|
|
11
|
+
|
|
10
12
|
```ts
|
|
11
13
|
function http<
|
|
12
14
|
S extends z.ZodType | undefined = undefined,
|
|
@@ -14,8 +16,8 @@ function http<
|
|
|
14
16
|
const A extends HttpDefinition<S>["auth"] = undefined,
|
|
15
17
|
const H extends HttpDefinition<S>["headers"] = undefined,
|
|
16
18
|
>(
|
|
17
|
-
def: HttpDefinition<S> & { url: U; auth?: A; headers?: H },
|
|
18
|
-
): EffectLogic<
|
|
19
|
+
def: HttpDefinition<S> & { url: U; auth?: A; headers?: H; body: FileBody<S> },
|
|
20
|
+
): EffectLogic<HttpFileInput<S>, HttpOutput> & OpenOf<HttpOpenParts<U, A, H>>
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
Make an `http` Effect: a call to a URL, with Effect secrets that the Agent never reads. Put it
|
|
@@ -23,8 +25,9 @@ in `actors`, and invoke it by its key: `actors: { comment: http({ ... }) }`, the
|
|
|
23
25
|
src: "comment" }`.
|
|
24
26
|
|
|
25
27
|
It runs in the Instance Effector, which `jr2 up` deploys when a Machine has an `http` Effect.
|
|
26
|
-
With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it
|
|
27
|
-
The Effector makes the call. The
|
|
28
|
+
With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it,
|
|
29
|
+
and under Repo Slots its input names the Worktree in `worktree`. The Effector makes the call. The
|
|
30
|
+
Orchestrator holds no secret.
|
|
28
31
|
|
|
29
32
|
Every outcome is output (`HttpOutput`): `response`, `unreachable`, `refused` or `unknown`. A 4xx
|
|
30
33
|
or 5xx status is output too. jr2 sends the call at most one time, unless you mark it
|
|
@@ -91,12 +94,15 @@ customize() share: actors: { <name>: http(...) }, then invoke: { src: "<name>" }
|
|
|
91
94
|
A function that makes the part of the request that changes for each run, from the input:
|
|
92
95
|
`path` segments, and a `json` or `text` body. It cannot change the host, a header or an
|
|
93
96
|
Effect secret. Do not write it beside `body`.
|
|
94
|
-
- `body
|
|
97
|
+
- `body`: `FileBody<S>`
|
|
95
98
|
|
|
96
|
-
A Worktree file as the body: `file(path, {
|
|
97
|
-
runs in the Sandbox Effector of its Workspace.
|
|
99
|
+
A Worktree file as the body: `file(path, { segments?, contentType? })`. The Effect then
|
|
100
|
+
runs in the Sandbox Effector of its Workspace. Under a Workspace with Repo Slots, the invoke's
|
|
101
|
+
input names a Worktree that a cut of this run made, in `worktree`; with none, it refuses when
|
|
102
|
+
it starts: ``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its
|
|
103
|
+
input names no `worktree` ``. Do not write `request` beside it: the `segments`
|
|
98
104
|
of `file` are the path. It refuses another body: ``http({ body }): a body is file(path, {
|
|
99
|
-
|
|
105
|
+
segments?, contentType? }) — json and text come from `request` ``.
|
|
100
106
|
- `timeout` (optional): [`Duration`](type-duration.md)
|
|
101
107
|
|
|
102
108
|
How long jr2 waits for the answer: a number and a unit, such as `"30s"` or `"2m"`. Default
|
|
@@ -157,3 +163,97 @@ export const machine = jr2Setup({
|
|
|
157
163
|
- [Secrets and trust](../../concepts/secrets-and-trust.md)
|
|
158
164
|
- [ADR-0068: An Effector holds an Effect's secrets and signs its wire](../decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)
|
|
159
165
|
- [ADR-0067: An Effect is an actor slot, and runs at most once unless marked idempotent](../decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)
|
|
166
|
+
|
|
167
|
+
## Overload 2
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
function http<
|
|
171
|
+
S extends z.ZodType | undefined = undefined,
|
|
172
|
+
const U extends HttpDefinition<S>["url"] = string,
|
|
173
|
+
const A extends HttpDefinition<S>["auth"] = undefined,
|
|
174
|
+
const H extends HttpDefinition<S>["headers"] = undefined,
|
|
175
|
+
>(
|
|
176
|
+
def: HttpDefinition<S> & { url: U; auth?: A; headers?: H },
|
|
177
|
+
): EffectLogic<HttpInput<S>, HttpOutput> & OpenOf<HttpOpenParts<U, A, H>>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Parameters**
|
|
181
|
+
|
|
182
|
+
- `def`
|
|
183
|
+
|
|
184
|
+
- `method`: [`HttpMethod`](type-httpmethod.md)
|
|
185
|
+
|
|
186
|
+
The HTTP method: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`. It refuses
|
|
187
|
+
another: `http({ method: "<method>" }): the method is one of GET, HEAD, POST, PUT, PATCH,
|
|
188
|
+
DELETE, OPTIONS`.
|
|
189
|
+
- `url`: `string | Text | typeof open`
|
|
190
|
+
|
|
191
|
+
The url: `http://` or `https://`, then a literal host. No input, param or Effect secret can
|
|
192
|
+
change where the call goes. An Effect secret can be only in the path:
|
|
193
|
+
`` text`https://api.telegram.org/bot${secret("tg")}/sendMessage` ``.
|
|
194
|
+
|
|
195
|
+
Or `open`: a packaged Machine that cannot know the host leaves the url Open, and the composer
|
|
196
|
+
binds it one time with `customize(machine, { effects: { <slot>: { url } } })`.
|
|
197
|
+
|
|
198
|
+
It refuses:
|
|
199
|
+
- a scheme or host that is not literal: `` http({ url }): the url is an http:// or https:// url
|
|
200
|
+
whose scheme and host are literal — a secret may sit only in its path, after a "/":
|
|
201
|
+
text`https://api.example.com/bot${secret("tg")}/send` (ADR-0068) ``
|
|
202
|
+
- an Effect secret after `?` or `#`: `` http({ url }): secret("<name>") sits in the url's query
|
|
203
|
+
or fragment — a secret may sit only in its path; send it in `auth` or a header instead:
|
|
204
|
+
headers: { "x-api-key": text`${secret("...")}` } (ADR-0068) ``
|
|
205
|
+
- a host that is not a host: `http({ url }): "<scheme>://<host>" is not a host`
|
|
206
|
+
- a user name or password in the url: ``http({ url }): the url carries userinfo — use `auth` ``
|
|
207
|
+
- `headers` (optional): `Readonly<Record<string, string | Text>>`
|
|
208
|
+
|
|
209
|
+
Headers that are the same for each run. Each name is lowercase. A value is a string, or a
|
|
210
|
+
`text` with an Effect secret.
|
|
211
|
+
|
|
212
|
+
It refuses:
|
|
213
|
+
- a name that is not lowercase, or that frames or routes the request (`host`,
|
|
214
|
+
`content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`,
|
|
215
|
+
`keep-alive`, `proxy-*`): `http({ headers }): "<name>" is not a header an Effect sets — a
|
|
216
|
+
lowercase name that does not frame or route the request (ADR-0068)`
|
|
217
|
+
- an `authorization` header beside `auth`: ``http({ headers }): "authorization" is set by
|
|
218
|
+
`auth` — write one of them``
|
|
219
|
+
- `auth` (optional): `{ readonly bearer: Secret; } | { readonly basic: { user: Text; password: Text; }; } | Text`
|
|
220
|
+
|
|
221
|
+
The `Authorization` header: `bearer(secret("x"))`, `basic(user, password)`, or a `text` that
|
|
222
|
+
is the whole value.
|
|
223
|
+
- `input` (optional): `S`
|
|
224
|
+
|
|
225
|
+
The zod schema of the input of the Effect: what the `input` of the `invoke`, or `fromPick`,
|
|
226
|
+
gives it. jr2 parses the input with it before the call. When it starts, input that does not
|
|
227
|
+
agree with it makes the Effect fail (its `onError` route): `http "<slot>": its input does not
|
|
228
|
+
match the definition's input schema — <path>: <message>`, one `<path>: <message>` for each
|
|
229
|
+
problem, joined by `; `.
|
|
230
|
+
- `request` (optional): `(input: S extends z.ZodType ? z.infer<S> : void) => HttpRequestParts`
|
|
231
|
+
|
|
232
|
+
A function that makes the part of the request that changes for each run, from the input:
|
|
233
|
+
`path` segments, and a `json` or `text` body. It cannot change the host, a header or an
|
|
234
|
+
Effect secret. Do not write it beside `body`.
|
|
235
|
+
- `body` (optional): `FileBody<S>`
|
|
236
|
+
|
|
237
|
+
A Worktree file as the body: `file(path, { segments?, contentType? })`. The Effect then
|
|
238
|
+
runs in the Sandbox Effector of its Workspace. Under a Workspace with Repo Slots, the invoke's
|
|
239
|
+
input names a Worktree that a cut of this run made, in `worktree`; with none, it refuses when
|
|
240
|
+
it starts: ``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its
|
|
241
|
+
input names no `worktree` ``. Do not write `request` beside it: the `segments`
|
|
242
|
+
of `file` are the path. It refuses another body: ``http({ body }): a body is file(path, {
|
|
243
|
+
segments?, contentType? }) — json and text come from `request` ``.
|
|
244
|
+
- `timeout` (optional): [`Duration`](type-duration.md)
|
|
245
|
+
|
|
246
|
+
How long jr2 waits for the answer: a number and a unit, such as `"30s"` or `"2m"`. Default
|
|
247
|
+
`"30s"`. With no answer in that time, the output is `unknown`. It refuses a value that is not
|
|
248
|
+
a positive duration: `http: timeout "<timeout>" is not a positive duration (like "30s",
|
|
249
|
+
"2m")`.
|
|
250
|
+
- `maxBody` (optional): `number`
|
|
251
|
+
|
|
252
|
+
The largest number of bytes of a text response body that the output keeps. Default 65536.
|
|
253
|
+
When jr2 cuts the body, `truncated` is `true`. It refuses a value that is not a positive whole
|
|
254
|
+
number: `http({ maxBody }): a positive number of bytes`.
|
|
255
|
+
- `idempotent` (optional): `boolean`
|
|
256
|
+
|
|
257
|
+
`true` when it is safe to send the call two times: it has its own idempotency key, or it only
|
|
258
|
+
reads. Default `false`. After an Orchestrator restart, jr2 cannot always tell if the call went
|
|
259
|
+
out. Then it sends an idempotent call again, and gives any other call the output `unknown`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: isReady
|
|
3
|
+
description: Is it `ready`?
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `isReady`
|
|
7
|
+
|
|
8
|
+
Constant, in `@jr2/orchestrator/src/worktree.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const isReady: <O extends { outcome: string; }>(output: O) => output is Extract<O, { outcome: "ready"; }>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Is it `ready`? For a guard. A Chart's form is the guard `jr2.ready`.
|
|
@@ -28,7 +28,18 @@ Open part.
|
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
import { z } from "zod";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
agent,
|
|
33
|
+
customize,
|
|
34
|
+
defineEvent,
|
|
35
|
+
fail,
|
|
36
|
+
isReady,
|
|
37
|
+
jr2Setup,
|
|
38
|
+
open,
|
|
39
|
+
workspace,
|
|
40
|
+
worktree,
|
|
41
|
+
type Workspaced,
|
|
42
|
+
} from "@jr2/orchestrator";
|
|
32
43
|
|
|
33
44
|
const finish = defineEvent({
|
|
34
45
|
name: "finish",
|
|
@@ -36,19 +47,40 @@ const finish = defineEvent({
|
|
|
36
47
|
input: z.object({}),
|
|
37
48
|
});
|
|
38
49
|
|
|
50
|
+
type Input = Workspaced<{}, "app">;
|
|
51
|
+
|
|
52
|
+
const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
53
|
+
|
|
39
54
|
const body = jr2Setup({
|
|
55
|
+
types: {} as { context: Input; input: Input },
|
|
40
56
|
events: [finish],
|
|
41
|
-
actors: {
|
|
57
|
+
actors: {
|
|
58
|
+
coder: agent({ model: open, instructions: "You fix the failing test." }),
|
|
59
|
+
cut: worktree(),
|
|
60
|
+
fail: fail(),
|
|
61
|
+
},
|
|
42
62
|
}).createMachine({
|
|
43
63
|
id: "fix",
|
|
64
|
+
context: ({ input }) => input,
|
|
44
65
|
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
45
|
-
initial: "
|
|
66
|
+
initial: "cutting",
|
|
46
67
|
states: {
|
|
68
|
+
cutting: {
|
|
69
|
+
invoke: {
|
|
70
|
+
src: "cut",
|
|
71
|
+
input: ({ context }) => ({ path: fixTree(context), branch: "jr2/fix" }),
|
|
72
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
47
75
|
coding: {
|
|
48
|
-
invoke: {
|
|
76
|
+
invoke: {
|
|
77
|
+
src: "coder",
|
|
78
|
+
input: ({ context }) => ({ prompt: "Make `node --test` pass.", cwd: fixTree(context) }),
|
|
79
|
+
},
|
|
49
80
|
on: { finish: "done" },
|
|
50
81
|
},
|
|
51
82
|
done: { type: "final" },
|
|
83
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
52
84
|
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
53
85
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
54
86
|
},
|
|
@@ -57,7 +89,6 @@ const body = jr2Setup({
|
|
|
57
89
|
// What a package exports: the repository and the model are Open.
|
|
58
90
|
export const fix = workspace(body, {
|
|
59
91
|
repos: { app: open },
|
|
60
|
-
spec: () => ({ branch: "jr2/fix" }),
|
|
61
92
|
});
|
|
62
93
|
|
|
63
94
|
// What an Instance's `workflows/fix.ts` exports: the same Machine, its Open parts bound.
|
|
@@ -50,9 +50,11 @@ import {
|
|
|
50
50
|
command,
|
|
51
51
|
defineEvent,
|
|
52
52
|
fail,
|
|
53
|
+
isReady,
|
|
53
54
|
jr2Setup,
|
|
54
55
|
paramsOf,
|
|
55
56
|
workspace,
|
|
57
|
+
worktree,
|
|
56
58
|
type Workspaced,
|
|
57
59
|
} from "@jr2/orchestrator";
|
|
58
60
|
|
|
@@ -70,6 +72,8 @@ const finished = defineEvent({
|
|
|
70
72
|
|
|
71
73
|
type Input = Workspaced<{}, "app">;
|
|
72
74
|
|
|
75
|
+
const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
76
|
+
|
|
73
77
|
const body = jr2Setup({
|
|
74
78
|
types: {} as { context: Input; input: Input },
|
|
75
79
|
events: [runTest, finished],
|
|
@@ -78,6 +82,7 @@ const body = jr2Setup({
|
|
|
78
82
|
model: "anthropic/claude-haiku-5-5",
|
|
79
83
|
instructions: "You are a careful engineer.",
|
|
80
84
|
}),
|
|
85
|
+
cut: worktree(),
|
|
81
86
|
test: command(["node", "--test", arg("file")]),
|
|
82
87
|
fail: fail(),
|
|
83
88
|
},
|
|
@@ -85,12 +90,22 @@ const body = jr2Setup({
|
|
|
85
90
|
id: "fix-tests",
|
|
86
91
|
context: ({ input }) => input,
|
|
87
92
|
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
88
|
-
initial: "
|
|
93
|
+
initial: "cutting",
|
|
89
94
|
states: {
|
|
95
|
+
cutting: {
|
|
96
|
+
invoke: {
|
|
97
|
+
src: "cut",
|
|
98
|
+
input: ({ context }) => ({ path: fixTree(context), branch: "fix-tests" }),
|
|
99
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
90
102
|
coding: {
|
|
91
103
|
invoke: {
|
|
92
104
|
src: "coder",
|
|
93
|
-
input: {
|
|
105
|
+
input: ({ context }) => ({
|
|
106
|
+
prompt: "Make the tests pass. Run the tests of one module at a time.",
|
|
107
|
+
cwd: fixTree(context),
|
|
108
|
+
}),
|
|
94
109
|
},
|
|
95
110
|
on: { finished: "done" },
|
|
96
111
|
initial: "ready",
|
|
@@ -100,13 +115,17 @@ const body = jr2Setup({
|
|
|
100
115
|
invoke: {
|
|
101
116
|
src: "test",
|
|
102
117
|
// A plain mapper: the Machine makes the file name from the pick's `module`.
|
|
103
|
-
input: ({ event }) => ({
|
|
118
|
+
input: ({ context, event }) => ({
|
|
119
|
+
file: `test/${paramsOf(event, runTest).module}.test.js`,
|
|
120
|
+
worktree: fixTree(context),
|
|
121
|
+
}),
|
|
104
122
|
onDone: "ready",
|
|
105
123
|
},
|
|
106
124
|
},
|
|
107
125
|
},
|
|
108
126
|
},
|
|
109
127
|
done: { type: "final" },
|
|
128
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
110
129
|
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
111
130
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
112
131
|
},
|
|
@@ -114,7 +133,6 @@ const body = jr2Setup({
|
|
|
114
133
|
|
|
115
134
|
export const machine = workspace(body, {
|
|
116
135
|
repos: { app: { url: "https://github.com/you/app.git" } },
|
|
117
|
-
spec: () => ({ branch: "fix-tests" }),
|
|
118
136
|
});
|
|
119
137
|
```
|
|
120
138
|
|
|
@@ -51,7 +51,7 @@ its own.
|
|
|
51
51
|
The run input of the Pool, as a zod object: what a caller sends to start a run. `cap` and
|
|
52
52
|
`itemInput` read it, typed by it. A worker never gets it: each worker gets its item. Default:
|
|
53
53
|
none, and then their `input` is `unknown`.
|
|
54
|
-
- `source`: `SourceSpec<T>`
|
|
54
|
+
- `source`: `SourceSpec<T, TInput> | SourceSpec<T>`
|
|
55
55
|
|
|
56
56
|
Where the items come from. Make it with [`source`](source.md).
|
|
57
57
|
- `itemId`: `(item: T) => string`
|
|
@@ -83,7 +83,7 @@ its own.
|
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
85
|
import { fromPromise } from "xstate";
|
|
86
|
-
import { customize, pool, source } from "@jr2/orchestrator";
|
|
86
|
+
import { customize, pool, source, type PoolNextInput } from "@jr2/orchestrator";
|
|
87
87
|
import { task } from "@jr2/machines";
|
|
88
88
|
|
|
89
89
|
type Issue = { id: string; title: string };
|
|
@@ -94,7 +94,7 @@ const backlog: Issue[] = [
|
|
|
94
94
|
];
|
|
95
95
|
|
|
96
96
|
const issues = source<Issue>({
|
|
97
|
-
next: fromPromise(async ({ input }: { input:
|
|
97
|
+
next: fromPromise(async ({ input }: { input: PoolNextInput }) => {
|
|
98
98
|
const issue = backlog.find((i) => !input.active.includes(i.id)) ?? null;
|
|
99
99
|
if (issue) backlog.splice(backlog.indexOf(issue), 1);
|
|
100
100
|
return issue;
|
|
@@ -151,7 +151,7 @@ an example.
|
|
|
151
151
|
The run input of the Pool, as a zod object: what a caller sends to start a run. `cap` and
|
|
152
152
|
`itemInput` read it, typed by it. A worker never gets it: each worker gets its item. Default:
|
|
153
153
|
none, and then their `input` is `unknown`.
|
|
154
|
-
- `source`: `SourceSpec<T>`
|
|
154
|
+
- `source`: `SourceSpec<T, TInput> | SourceSpec<T>`
|
|
155
155
|
|
|
156
156
|
Where the items come from. Make it with [`source`](source.md).
|
|
157
157
|
- `itemId`: `(item: T) => string`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: push
|
|
3
|
-
description: "Make a `push` Effect: it pushes the branch of
|
|
3
|
+
description: "Make a `push` Effect: it pushes the branch of a Worktree to the Binding of its Repo Slot."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `push`
|
|
@@ -8,17 +8,16 @@ description: "Make a `push` Effect: it pushes the branch of the Workspace to the
|
|
|
8
8
|
Function, in `@jr2/orchestrator/src/sandbox-effect.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
function push<
|
|
12
|
-
const O extends PushOptions,
|
|
13
|
-
>(
|
|
14
|
-
options?: O,
|
|
15
|
-
): EffectLogic<PushInput<O> | void, PushOutput>
|
|
11
|
+
function push<const O extends PushOptions>(options?: O): EffectLogic<PushInput<O>, PushOutput>
|
|
16
12
|
```
|
|
17
13
|
|
|
18
|
-
Make a `push` Effect: it pushes the branch of
|
|
14
|
+
Make a `push` Effect: it pushes the branch of a Worktree to the Binding of its Repo Slot.
|
|
19
15
|
Put it in `actors`, and invoke it from the body of a `workspace()`: `actors: { publish: push({
|
|
20
16
|
sign: true }) }`.
|
|
21
17
|
|
|
18
|
+
Its input names the Worktree: `{ worktree }`. It has no branch. jr2 reads the branch from the
|
|
19
|
+
`worktree()` cut of that path, so an Agent that moves `HEAD` does not change what jr2 pushes.
|
|
20
|
+
|
|
22
21
|
It runs in the Sandbox Effector of the Workspace, with the push credential of the
|
|
23
22
|
`git.credentials` entry that the Binding matches. The Agent never reads that credential. It
|
|
24
23
|
pushes one commit: the tip of the Agent's branch when the Effect starts, or, with `pin`, the
|
|
@@ -26,8 +25,10 @@ commit that the input names. After an Orchestrator restart, jr2 pushes only that
|
|
|
26
25
|
again. With `sign` or `squash`, the commit on the remote is not the Agent's own. Then jr2 moves
|
|
27
26
|
the Agent's branch to it.
|
|
28
27
|
|
|
29
|
-
Every outcome is output (`PushOutput`): `pushed`, `rejected`, `diverged` or `unknown`.
|
|
30
|
-
|
|
28
|
+
Every outcome is output (`PushOutput`): `pushed`, `rejected`, `diverged` or `unknown`. A
|
|
29
|
+
`worktree` that no cut made in this Sandbox, a Worktree of another Repo Slot, and a detached
|
|
30
|
+
Worktree are `rejected` at `stage` `start`, and nothing is pushed. Each field of
|
|
31
|
+
[`PushOptions`](type-pushoptions.md) tells what it refuses. When it starts, it also refuses a push
|
|
31
32
|
outside a Workspace: `push "<slot>" runs in the Sandbox Effector of a Workspace, and its state is
|
|
32
33
|
not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`.
|
|
33
34
|
|
|
@@ -45,9 +46,11 @@ import {
|
|
|
45
46
|
agent,
|
|
46
47
|
defineEvent,
|
|
47
48
|
fail,
|
|
49
|
+
isReady,
|
|
48
50
|
jr2Setup,
|
|
49
51
|
push,
|
|
50
52
|
workspace,
|
|
53
|
+
worktree,
|
|
51
54
|
type Workspaced,
|
|
52
55
|
} from "@jr2/orchestrator";
|
|
53
56
|
|
|
@@ -59,6 +62,8 @@ const committed = defineEvent({
|
|
|
59
62
|
|
|
60
63
|
type Input = Workspaced<{}, "app">;
|
|
61
64
|
|
|
65
|
+
const fixTree = (context: Input) => `${context.workspace.repos.app}/fix` as const;
|
|
66
|
+
|
|
62
67
|
const body = jr2Setup({
|
|
63
68
|
types: {} as { context: Input; input: Input },
|
|
64
69
|
events: [committed],
|
|
@@ -67,6 +72,7 @@ const body = jr2Setup({
|
|
|
67
72
|
model: "anthropic/claude-haiku-5-5",
|
|
68
73
|
instructions: "You are a careful engineer.",
|
|
69
74
|
}),
|
|
75
|
+
cut: worktree(),
|
|
70
76
|
publish: push({ sign: true }),
|
|
71
77
|
fail: fail(),
|
|
72
78
|
},
|
|
@@ -74,10 +80,20 @@ const body = jr2Setup({
|
|
|
74
80
|
id: "fix-test",
|
|
75
81
|
context: ({ input }) => input,
|
|
76
82
|
on: { "workspace.lost": ".failed", "agent.fault": ".failed" },
|
|
77
|
-
initial: "
|
|
83
|
+
initial: "cutting",
|
|
78
84
|
states: {
|
|
85
|
+
cutting: {
|
|
86
|
+
invoke: {
|
|
87
|
+
src: "cut",
|
|
88
|
+
input: ({ context }) => ({ path: fixTree(context), branch: "fix-test" }),
|
|
89
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
79
92
|
coding: {
|
|
80
|
-
invoke: {
|
|
93
|
+
invoke: {
|
|
94
|
+
src: "coder",
|
|
95
|
+
input: ({ context }) => ({ prompt: "Fix the failing test, and commit the fix.", cwd: fixTree(context) }),
|
|
96
|
+
},
|
|
81
97
|
// The Agent moves the Machine on with a pick. An Agent's actor never ends, so `onDone`
|
|
82
98
|
// never fires.
|
|
83
99
|
on: { committed: "publishing" },
|
|
@@ -85,6 +101,8 @@ const body = jr2Setup({
|
|
|
85
101
|
publishing: {
|
|
86
102
|
invoke: {
|
|
87
103
|
src: "publish",
|
|
104
|
+
// jr2 reads the branch from the cut of this Worktree.
|
|
105
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
88
106
|
onDone: [
|
|
89
107
|
{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" },
|
|
90
108
|
{ target: "failed" },
|
|
@@ -92,6 +110,7 @@ const body = jr2Setup({
|
|
|
92
110
|
},
|
|
93
111
|
},
|
|
94
112
|
done: { type: "final" },
|
|
113
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
95
114
|
// Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
|
|
96
115
|
failed: { invoke: { src: "fail", input: { reason: "the fix was not pushed" } } },
|
|
97
116
|
},
|
|
@@ -99,7 +118,6 @@ const body = jr2Setup({
|
|
|
99
118
|
|
|
100
119
|
export const machine = workspace(body, {
|
|
101
120
|
repos: { app: { url: "https://github.com/you/app.git" } },
|
|
102
|
-
spec: () => ({ branch: "fix-test" }),
|
|
103
121
|
});
|
|
104
122
|
```
|
|
105
123
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: readyOf
|
|
3
|
+
description: The `ready` output of an `onDone` arm that `isReady` guards.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `readyOf`
|
|
7
|
+
|
|
8
|
+
Function, in `@jr2/orchestrator/src/worktree.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
function readyOf<O extends { outcome: string }>(output: O): Extract<O, { outcome: "ready" }>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The `ready` output of an `onDone` arm that `isReady` guards. xstate carries no guard's narrowing
|
|
15
|
+
to its actions, so an `assign` reads the output through this.
|
|
16
|
+
|
|
17
|
+
**Parameters**
|
|
18
|
+
|
|
19
|
+
- `output`
|
|
20
|
+
|
|
21
|
+
- `outcome`: `string`
|
|
@@ -56,13 +56,25 @@ And at `jr2 up`:
|
|
|
56
56
|
**Example**
|
|
57
57
|
|
|
58
58
|
```ts
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
command,
|
|
61
|
+
fail,
|
|
62
|
+
isReady,
|
|
63
|
+
jr2Setup,
|
|
64
|
+
secret,
|
|
65
|
+
workspace,
|
|
66
|
+
worktree,
|
|
67
|
+
type Workspaced,
|
|
68
|
+
} from "@jr2/orchestrator";
|
|
60
69
|
|
|
61
70
|
type Input = Workspaced<{}, "app">;
|
|
62
71
|
|
|
72
|
+
const deployTree = (context: Input) => `${context.workspace.repos.app}/deploy` as const;
|
|
73
|
+
|
|
63
74
|
const body = jr2Setup({
|
|
64
75
|
types: {} as { context: Input; input: Input },
|
|
65
76
|
actors: {
|
|
77
|
+
cut: worktree(),
|
|
66
78
|
// The command reads the file at $KUBECONFIG. The Agent never reads the secret.
|
|
67
79
|
deploy: command(["kubectl", "apply", "-f", "k8s/"], {
|
|
68
80
|
env: { KUBECONFIG: secret("kube").path },
|
|
@@ -73,17 +85,26 @@ const body = jr2Setup({
|
|
|
73
85
|
id: "deploy",
|
|
74
86
|
context: ({ input }) => input,
|
|
75
87
|
on: { "workspace.lost": ".lost" },
|
|
76
|
-
initial: "
|
|
88
|
+
initial: "cutting",
|
|
77
89
|
states: {
|
|
78
|
-
|
|
90
|
+
cutting: {
|
|
91
|
+
invoke: {
|
|
92
|
+
src: "cut",
|
|
93
|
+
input: ({ context }) => ({ path: deployTree(context), branch: "deploy" }),
|
|
94
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "deploying" }, { target: "notCut" }],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
deploying: {
|
|
98
|
+
invoke: { src: "deploy", input: ({ context }) => ({ worktree: deployTree(context) }), onDone: "done" },
|
|
99
|
+
},
|
|
79
100
|
done: { type: "final" },
|
|
101
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
80
102
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
81
103
|
},
|
|
82
104
|
});
|
|
83
105
|
|
|
84
106
|
export const machine = workspace(body, {
|
|
85
107
|
repos: { app: { url: "https://github.com/you/app.git" } },
|
|
86
|
-
spec: () => ({ branch: "deploy" }),
|
|
87
108
|
});
|
|
88
109
|
```
|
|
89
110
|
|