@opensoftware/foreman 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -8
- package/dist/adapters/forge.js +4 -11
- package/dist/adapters/forge.js.map +1 -1
- package/dist/adapters/inference.js +14 -0
- package/dist/adapters/inference.js.map +1 -0
- package/dist/adapters/program.js +21 -0
- package/dist/adapters/program.js.map +1 -0
- package/dist/adapters/sandbox.js +3 -15
- package/dist/adapters/sandbox.js.map +1 -1
- package/dist/commands/runs.js +36 -5
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/serve.js +63 -43
- package/dist/commands/serve.js.map +1 -1
- package/dist/domain/definition.js +18 -5
- package/dist/domain/definition.js.map +1 -1
- package/dist/domain/run.js +26 -8
- package/dist/domain/run.js.map +1 -1
- package/dist/workflows/activities.js +43 -9
- package/dist/workflows/activities.js.map +1 -1
- package/dist/workflows/run.js +18 -2
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows.bundle.js +30 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ Run the factory's workflow for each work item: the stages of `factory.yaml` one
|
|
|
8
8
|
through the harness port, with human gates, steering and cancel, on Temporal. A run survives a restart of the
|
|
9
9
|
foreman, and Temporal's UI shows its history. Decisions: [ADR 0067](../../docs/decisions/0067-foreman.md), on
|
|
10
10
|
[ADR 0004](../../docs/decisions/0004-temporal.md), [ADR 0068](../../docs/decisions/0068-foreman-sandboxes.md) for
|
|
11
|
-
sandboxes,
|
|
11
|
+
sandboxes, [ADR 0069](../../docs/decisions/0069-foreman-checks.md) for checks, and
|
|
12
|
+
[ADR 0070](../../docs/decisions/0070-foreman-gateway-and-take-over.md) for the inference gateway and take over.
|
|
12
13
|
|
|
13
14
|
## Quick start
|
|
14
15
|
|
|
@@ -198,6 +199,82 @@ The forge adapter is the one the factory names under `providers.forge`; a factor
|
|
|
198
199
|
`npx -y @opensoftware/forge-<name>`. It gets `FORGE_URL` and `FORGE_TOKEN` from `FOREMAN_FORGE_URL` and
|
|
199
200
|
`FOREMAN_FORGE_TOKEN`.
|
|
200
201
|
|
|
202
|
+
## Inference gateway
|
|
203
|
+
|
|
204
|
+
Give the factory's agents one model endpoint with a budget per run: name an
|
|
205
|
+
[inference adapter](../../ports/inference/README.md) under `providers.inference` and its config under `inference`.
|
|
206
|
+
This factory runs the [LiteLLM gateway](../../adapters/inference/litellm/README.md) on Docker and lets a run spend at
|
|
207
|
+
most 5 USD:
|
|
208
|
+
|
|
209
|
+
```yaml gateway/factory.yaml
|
|
210
|
+
version: 1
|
|
211
|
+
factory: hello
|
|
212
|
+
providers:
|
|
213
|
+
harness:
|
|
214
|
+
acp: "^0"
|
|
215
|
+
inference:
|
|
216
|
+
litellm: "^0"
|
|
217
|
+
inference:
|
|
218
|
+
config: ./inference/litellm.yaml
|
|
219
|
+
budget: 5
|
|
220
|
+
agents:
|
|
221
|
+
plan:
|
|
222
|
+
harness: acp
|
|
223
|
+
model: routes/default
|
|
224
|
+
prompt: ./agents/plan.md
|
|
225
|
+
implement:
|
|
226
|
+
harness: acp
|
|
227
|
+
model: routes/default
|
|
228
|
+
prompt: ./agents/implement.md
|
|
229
|
+
workflow:
|
|
230
|
+
- stage: plan
|
|
231
|
+
- stage: implement
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```yaml inference/litellm.yaml
|
|
235
|
+
model_list:
|
|
236
|
+
- model_name: routes/default
|
|
237
|
+
litellm_params:
|
|
238
|
+
model: anthropic/claude-sonnet-5-5
|
|
239
|
+
api_key: os.environ/OSF_SECRET_ANTHROPIC_API_KEY
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Serve it as in the quick start, with `export OSF_SECRET_ANTHROPIC_API_KEY=…` for the adapter. Then:
|
|
243
|
+
|
|
244
|
+
1. `foreman serve` runs the adapter's `start` with the config, a path relative to `factory.yaml`, before it takes work,
|
|
245
|
+
and `stop` when it stops. A `start` that fails stops the server with exit code 1 and the adapter's message.
|
|
246
|
+
2. Before a run's first stage, the foreman asks the adapter for the run's key: `key <gateway> <run ID> --budget 5`. The
|
|
247
|
+
gateway refuses the run's calls once it spent its budget. The key stays in the foreman's memory, not in Temporal.
|
|
248
|
+
3. Each stage's harness adapter gets the gateway in the variables that the harness adapters read: `OPENAI_BASE_URL`
|
|
249
|
+
(the URL `start` printed) and `ANTHROPIC_BASE_URL` (the same without `/v1`), `OPENAI_API_KEY` and
|
|
250
|
+
`ANTHROPIC_API_KEY` (the run's key), and `OPENAI_MODEL` and `ANTHROPIC_MODEL` (the agent's `model`, a routing
|
|
251
|
+
alias of the config). A sandboxed stage gets them in its sandbox's environment.
|
|
252
|
+
|
|
253
|
+
`FOREMAN_INFERENCES` maps an adapter's name to its command line; an adapter it does not name runs
|
|
254
|
+
`npx -y @opensoftware/inference-<name>`. A factory names at most one inference adapter. Without one, the foreman sets
|
|
255
|
+
none of these variables, and each harness adapter calls the model API it is configured for.
|
|
256
|
+
|
|
257
|
+
## Take over
|
|
258
|
+
|
|
259
|
+
A person can take a running stage over from its agent and hand the run back later
|
|
260
|
+
([ADR 0070](../../docs/decisions/0070-foreman-gateway-and-take-over.md)):
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
curl -H "Authorization: Bearer dev-token" -H "content-type: application/json" \
|
|
264
|
+
--data '{"type": "take-over"}' http://localhost:8080/v1/runs/run_…/signals
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
1. The running stage's agent ends its turn within `FOREMAN_POLL_MS`. A sandboxed stage copies its files out and
|
|
268
|
+
destroys its sandbox, as after every turn.
|
|
269
|
+
2. The stage is `blocked` with `takenOverAt`, and the run is `blocked` with `folder`: the run's folder on the foreman's
|
|
270
|
+
machine, which holds the stage's files. The stage's `sessionId`, the run's `pullRequest` and the work item are the
|
|
271
|
+
rest of the context.
|
|
272
|
+
3. The person works in that folder, then posts `{"type": "hand-back"}`. The stage is `succeeded`, and the next stage
|
|
273
|
+
starts from the files in the folder, or the run is `complete` after the last stage.
|
|
274
|
+
|
|
275
|
+
A take-over needs a running stage, and a hand-back a stage that a person took over; otherwise the answer is 409. A
|
|
276
|
+
cancel ends a run that a person took over.
|
|
277
|
+
|
|
201
278
|
## Commands
|
|
202
279
|
|
|
203
280
|
| Command | What it does |
|
|
@@ -209,18 +286,19 @@ The forge adapter is the one the factory names under `providers.forge`; a factor
|
|
|
209
286
|
| Route | What it does |
|
|
210
287
|
| --- | --- |
|
|
211
288
|
| `POST /v1/runs` | Start the run of a work item (the [work item](../../contracts/work-item/work-item.schema.json) format). 201 with the new run, 200 with the run the same work item started before, 400 with a problem that names the field. |
|
|
212
|
-
| `GET /v1/runs
|
|
213
|
-
| `
|
|
289
|
+
| `GET /v1/runs` | The runs of the server, running runs first, then the runs that ended, the one that ended last first: `{ "runs": [...] }`, each as `GET /v1/runs/{runId}` shows it. `?state=` with `running`, `blocked`, `complete`, `failed` or `cancelled` lists only the runs in that state; another state is 400. |
|
|
290
|
+
| `GET /v1/runs/{runId}` | The run: `state`, `factory`, `factoryVersion`, `pullRequest`, `folder`, `startedAt`, `endedAt` and its `stages`. 404 for a run the server does not know. |
|
|
291
|
+
| `POST /v1/runs/{runId}/signals` | `{ "type": "steer", "message": "…" }` sends the message to the running stage's agent; `{ "type": "cancel" }` cancels the run; `{ "type": "take-over" }` and `{ "type": "hand-back" }` hand the running stage to a person and back ([Take over](#take-over)). 202 with the run, 409 when the run ended or the signal does not fit its state. |
|
|
214
292
|
|
|
215
293
|
The OpenAPI of the routes is in [`contracts/openapi/openapi.json`](../../contracts/openapi/openapi.json); the
|
|
216
294
|
TypeScript type `paths` of `@opensoftware/contracts` types a client. Every route needs `Authorization: Bearer
|
|
217
295
|
<FOREMAN_API_TOKEN>`, else 401. Errors are `application/problem+json` with `title`, `status` and `detail`.
|
|
218
296
|
|
|
219
297
|
A run's ID is `run_` and 20 hex digits of the SHA-256 of the work item's ID, so a work item has one run. A run is
|
|
220
|
-
`running`, `blocked` (at a gate
|
|
298
|
+
`running`, `blocked` (at a gate, waiting for checks, or taken over by a person), `complete`, `failed` or `cancelled`. A stage is `pending`,
|
|
221
299
|
`blocked`, `running`, `succeeded`, `failed`, `rejected` (by its gate) or `cancelled`; it has its harness `sessionId` once
|
|
222
|
-
it started, its `gateId` once its gate was asked, its `checks` once they were read,
|
|
223
|
-
reason the foreman knows.
|
|
300
|
+
it started, its `gateId` once its gate was asked, its `checks` once they were read, its `takenOverAt` once a person
|
|
301
|
+
took it over, and a `reason` when it failed for a reason the foreman knows.
|
|
224
302
|
|
|
225
303
|
## How a run goes
|
|
226
304
|
|
|
@@ -235,7 +313,8 @@ reason the foreman knows.
|
|
|
235
313
|
the agent's prompt file, then the work item's ID, title and description. It allows the tool calls the adapter asks
|
|
236
314
|
about; limit them with the adapter's `--allow`. A turn that ends with `end_turn` succeeds the stage; any other end
|
|
237
315
|
fails or cancels it, ends the run, and leaves the later stages `pending`. The adapter gets
|
|
238
|
-
`OTEL_RESOURCE_ATTRIBUTES` with `osf.run.id`, `osf.work_item.id`, `osf.stage` and `osf.factory.version
|
|
316
|
+
`OTEL_RESOURCE_ATTRIBUTES` with `osf.run.id`, `osf.work_item.id`, `osf.stage` and `osf.factory.version`, and the
|
|
317
|
+
factory's inference gateway with the run's key and the agent's model ([Inference gateway](#inference-gateway)).
|
|
239
318
|
- **Checks.** Before a stage with `checks` starts, the foreman waits until those checks succeeded on the run's pull
|
|
240
319
|
request; a failing one fails the run ([Checks](#checks)).
|
|
241
320
|
- **Gates.** Before a stage with a `gate` starts, the foreman asks gatekeeper at `FOREMAN_GATEKEEPER_URL`
|
|
@@ -244,7 +323,8 @@ reason the foreman knows.
|
|
|
244
323
|
`gate.decided@v1` for that gate arrives on NATS. `approved` starts the stage; `rejected` or `expired` fails the run.
|
|
245
324
|
- **Signals.** A steering message reaches the running stage's agent within `FOREMAN_POLL_MS` as a prompt in its
|
|
246
325
|
session, which the harness port treats as steering. A cancel ends the agent's turn, or the wait at a gate, and the
|
|
247
|
-
run is `cancelled`.
|
|
326
|
+
run is `cancelled`. A person takes a running stage over and hands the run back with the signals `take-over` and
|
|
327
|
+
`hand-back` ([Take over](#take-over)).
|
|
248
328
|
- **Restarts.** The runs live in Temporal's history. A foreman that stops and starts again goes on with them; a stage
|
|
249
329
|
that ran when it stopped runs again, up to 3 times.
|
|
250
330
|
|
|
@@ -283,6 +363,7 @@ the minor version. The one-time setup is the same as for contracts
|
|
|
283
363
|
| `FOREMAN_HARNESSES` | string | `"{}"` | serve: a JSON object from harness name to the command line of its adapter. A harness it does not name runs npx -y @opensoftware/harness-<name> run. |
|
|
284
364
|
| `FOREMAN_SANDBOXES` | string | `"{}"` | serve: a JSON object from sandbox adapter name to the command line of the adapter. An adapter it does not name runs npx -y @opensoftware/sandbox-<name>. The factory names its adapter under providers.sandbox, docker by default. |
|
|
285
365
|
| `FOREMAN_FORGES` | string | `"{}"` | serve: a JSON object from forge adapter name to the command line of the adapter. An adapter it does not name runs npx -y @opensoftware/forge-<name>. The factory names its adapter under providers.forge. |
|
|
366
|
+
| `FOREMAN_INFERENCES` | string | `"{}"` | serve: a JSON object from inference adapter name to the command line of the adapter. An adapter it does not name runs npx -y @opensoftware/inference-<name>. The factory names its adapter under providers.inference. |
|
|
286
367
|
| `FOREMAN_FORGE_URL` | string | | serve: the forge adapter's FORGE_URL, for a factory whose stages have checks. Default: the adapter's own default. |
|
|
287
368
|
| `FOREMAN_FORGE_TOKEN` | string | | serve: the forge adapter's FORGE_TOKEN: a token that may read the pull requests and their checks. Never written to a file. |
|
|
288
369
|
| `FOREMAN_CHECK_POLL_MS` | string | `"30000"` | serve: how long a stage that waits for pending checks waits before it reads them again, in milliseconds. |
|
|
@@ -346,3 +427,21 @@ the minor version. The one-time setup is the same as for contracts
|
|
|
346
427
|
| SCN-FORE-047 | implemented | A run shows its pull request and publishes it when it ends |
|
|
347
428
|
| SCN-FORE-048 | implemented | A cancel ends a run that waits for a check |
|
|
348
429
|
| SCN-FORE-049 | implemented | A factory with checks but without a forge adapter stops the server at the start |
|
|
430
|
+
| SCN-FORE-050 | implemented | A stage's agent calls the factory's gateway with the run's key |
|
|
431
|
+
| SCN-FORE-051 | implemented | Each stage's agent calls the gateway with its own model |
|
|
432
|
+
| SCN-FORE-052 | implemented | The foreman starts one gateway from the factory's inference config |
|
|
433
|
+
| SCN-FORE-053 | implemented | Each run gets its own key with the factory's budget |
|
|
434
|
+
| SCN-FORE-054 | implemented | A sandboxed stage gets the gateway in its sandbox's environment |
|
|
435
|
+
| SCN-FORE-055 | implemented | The foreman stops the gateway when it stops serving |
|
|
436
|
+
| SCN-FORE-056 | implemented | A gateway that does not start stops the server at the start |
|
|
437
|
+
| SCN-FORE-057 | implemented | A factory with an inference adapter but without its config stops the server at the start |
|
|
438
|
+
| SCN-FORE-058 | implemented | A person takes over the running stage |
|
|
439
|
+
| SCN-FORE-059 | implemented | The next stage starts from the files of the person who handed the run back |
|
|
440
|
+
| SCN-FORE-060 | implemented | A take-over destroys the stage's sandbox and keeps its files |
|
|
441
|
+
| SCN-FORE-061 | implemented | A take-over of a run without a running stage is refused |
|
|
442
|
+
| SCN-FORE-062 | implemented | A hand-back of a run nobody took over is refused |
|
|
443
|
+
| SCN-FORE-063 | implemented | A cancel ends a run a person took over |
|
|
444
|
+
| SCN-FORE-064 | implemented | A person lists the runs of the server |
|
|
445
|
+
| SCN-FORE-065 | implemented | A person filters the runs by state |
|
|
446
|
+
| SCN-FORE-066 | implemented | A state that no run can have is refused |
|
|
447
|
+
| SCN-FORE-067 | implemented | A run that waits at its gate is listed as blocked |
|
package/dist/adapters/forge.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
// The run's pull request through the forge port (ADR 0057, ADR 0069). The adapter is a program; the foreman runs its
|
|
2
2
|
// `pr` command and reads the checks of the pull request's head commit.
|
|
3
|
-
import {
|
|
3
|
+
import { adapterRun } from "./program.js";
|
|
4
4
|
/** The checks of the pull request `<repo>#<number>`; a failure names the adapter's error. */
|
|
5
|
-
export function pullRequestChecks(adapter, env, repo, number) {
|
|
6
|
-
const
|
|
7
|
-
return
|
|
8
|
-
execFile(file, [...args, "pr", repo, number], { env }, (error, stdout, stderr) => {
|
|
9
|
-
if (error)
|
|
10
|
-
reject(new Error(`the forge adapter's pr failed: ${stderr.trim() || error.message}`));
|
|
11
|
-
else
|
|
12
|
-
resolve(JSON.parse(stdout).checks ?? []);
|
|
13
|
-
});
|
|
14
|
-
});
|
|
5
|
+
export async function pullRequestChecks(adapter, env, repo, number) {
|
|
6
|
+
const printed = await adapterRun("forge", adapter, env)("pr", repo, number);
|
|
7
|
+
return JSON.parse(printed).checks ?? [];
|
|
15
8
|
}
|
|
16
9
|
//# sourceMappingURL=forge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forge.js","sourceRoot":"","sources":["../../src/adapters/forge.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,uEAAuE;AACvE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"forge.js","sourceRoot":"","sources":["../../src/adapters/forge.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,uEAAuE;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ1C,6FAA6F;AAC7F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAiB,EACjB,GAAuC,EACvC,IAAY,EACZ,MAAc;IAEd,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5E,OAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiC,CAAC,MAAM,IAAI,EAAE,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The factory's inference gateway through the inference port (ADR 0056, ADR 0070). The adapter is a program; the
|
|
2
|
+
// foreman starts one gateway while it serves, asks it for a key per run, and stops it at the end.
|
|
3
|
+
import { adapterRun, lastLine } from "./program.js";
|
|
4
|
+
/** Starts a gateway from the adapter's config file. */
|
|
5
|
+
export async function startGateway(adapter, config, env) {
|
|
6
|
+
const run = adapterRun("inference", adapter, env);
|
|
7
|
+
const { id, url } = JSON.parse(lastLine(await run("start", config)));
|
|
8
|
+
return {
|
|
9
|
+
url,
|
|
10
|
+
key: async (runId, budget) => lastLine(await run("key", id, runId, ...(budget === undefined ? [] : ["--budget", String(budget)]))),
|
|
11
|
+
stop: async () => void (await run("stop", id)),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=inference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference.js","sourceRoot":"","sources":["../../src/adapters/inference.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,kGAAkG;AAClG,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAUpD,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAiB,EACjB,MAAc,EACd,GAAuC;IAEvC,MAAM,GAAG,GAAG,UAAU,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAgC,CAAC;IACpG,OAAO;QACL,GAAG;QACH,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAC3B,QAAQ,CAAC,MAAM,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACtG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;KAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// An adapter of a port that is a program (ADR 0054, ADR 0056): the foreman runs one of its commands and reads what it
|
|
2
|
+
// printed.
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
/**
|
|
5
|
+
* Runs commands of the `port`'s adapter and resolves with what it printed, trimmed; a failure names the port, the command
|
|
6
|
+
* and what the adapter wrote to standard error.
|
|
7
|
+
*/
|
|
8
|
+
export function adapterRun(port, adapter, env) {
|
|
9
|
+
const [file, ...args] = adapter;
|
|
10
|
+
return (...command) => new Promise((resolve, reject) => {
|
|
11
|
+
execFile(file, [...args, ...command], { env }, (error, stdout, stderr) => {
|
|
12
|
+
if (error)
|
|
13
|
+
reject(new Error(`the ${port} adapter's ${command[0]} failed: ${stderr.trim() || error.message}`));
|
|
14
|
+
else
|
|
15
|
+
resolve(stdout.trim());
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** The last line an adapter printed. */
|
|
20
|
+
export const lastLine = (printed) => printed.split("\n").pop();
|
|
21
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../src/adapters/program.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,WAAW;AACX,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAiB,EAAE,GAAuC;IACjG,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,OAAgC,CAAC;IACzD,OAAO,CAAC,GAAG,OAAiB,EAAE,EAAE,CAC9B,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACtC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACvE,IAAI,KAAK;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,cAAc,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;;gBACzG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,wCAAwC;AACxC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAY,CAAC"}
|
package/dist/adapters/sandbox.js
CHANGED
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
// A stage's sandbox through the sandbox port (ADR 0054, ADR 0068). The adapter is a program; the foreman creates a
|
|
2
2
|
// sandbox from the spec, copies the run's folder in, runs the harness with exec, copies the folder out and destroys it.
|
|
3
|
-
import { execFile } from "node:child_process";
|
|
4
3
|
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
5
4
|
import { tmpdir } from "node:os";
|
|
6
5
|
import { join, posix } from "node:path";
|
|
7
|
-
|
|
8
|
-
function adapterRun(adapter, env) {
|
|
9
|
-
const [file, ...args] = adapter;
|
|
10
|
-
return (...command) => new Promise((resolve, reject) => {
|
|
11
|
-
execFile(file, [...args, ...command], { env }, (error, stdout, stderr) => {
|
|
12
|
-
if (error)
|
|
13
|
-
reject(new Error(`the sandbox adapter's ${command[0]} failed: ${stderr.trim() || error.message}`));
|
|
14
|
-
else
|
|
15
|
-
resolve(stdout.trim());
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
}
|
|
6
|
+
import { adapterRun, lastLine } from "./program.js";
|
|
19
7
|
/**
|
|
20
8
|
* A new sandbox from the spec for the files of `local`, the run's folder, which it holds at `<working folder>/<name>`
|
|
21
9
|
* after `copyIn`. The caller destroys it.
|
|
22
10
|
*/
|
|
23
11
|
export async function createSandbox(adapter, spec, local, name, env) {
|
|
24
|
-
const run = adapterRun(adapter, env);
|
|
12
|
+
const run = adapterRun("sandbox", adapter, env);
|
|
25
13
|
const temporary = mkdtempSync(join(tmpdir(), "foreman-sandbox-"));
|
|
26
14
|
let id;
|
|
27
15
|
try {
|
|
28
16
|
writeFileSync(join(temporary, "spec.json"), JSON.stringify(spec));
|
|
29
|
-
id = (await run("create", join(temporary, "spec.json")))
|
|
17
|
+
id = lastLine(await run("create", join(temporary, "spec.json")));
|
|
30
18
|
}
|
|
31
19
|
finally {
|
|
32
20
|
rmSync(temporary, { recursive: true, force: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/adapters/sandbox.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,wHAAwH;AACxH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/adapters/sandbox.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,wHAAwH;AACxH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAYpD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAiB,EACjB,IAAiB,EACjB,KAAa,EACb,IAAY,EACZ,GAAuC;IAEvC,MAAM,GAAG,GAAG,UAAU,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAClE,IAAI,EAAU,CAAC;IACf,IAAI,CAAC;QACH,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,EAAE,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO;QACL,KAAK,CAAC,MAAM;YACV,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACnD,sGAAsG;YACtG,MAAM,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;YAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QAC7D,KAAK,CAAC,OAAO;YACX,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;YAChE,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;gBACpD,MAAM,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACtD,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;KACrD,CAAC;AACJ,CAAC"}
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
// The runs of the foreman as Temporal workflows (ADR 0067): start one per work item, read its view,
|
|
1
|
+
// The runs of the foreman as Temporal workflows (ADR 0067): start one per work item, read its view, list them by state
|
|
2
|
+
// (ADR 0071), signal them: steer, cancel, take over and hand back (ADR 0070).
|
|
2
3
|
import { UsageError } from "@opensoftware/cli";
|
|
3
4
|
import { validate } from "@opensoftware/contracts";
|
|
4
5
|
import { WorkflowExecutionAlreadyStartedError, WorkflowIdReusePolicy, WorkflowNotFoundError, } from "@temporalio/client";
|
|
5
|
-
import { firstView, hasEnded, runIdOf, signalOf } from "../domain/run.js";
|
|
6
|
-
import { factoryRun, gateSignal,
|
|
6
|
+
import { firstView, hasEnded, isRunState, listed, runIdOf, signalOf, } from "../domain/run.js";
|
|
7
|
+
import { controlQuery, factoryRun, gateSignal, handBackSignal, steerSignal, takeOverSignal, viewQuery, } from "../workflows/run.js";
|
|
7
8
|
export class Conflict extends Error {
|
|
9
|
+
title;
|
|
10
|
+
constructor(message, title = "The run ended") {
|
|
11
|
+
super(message);
|
|
12
|
+
this.title = title;
|
|
13
|
+
}
|
|
8
14
|
}
|
|
15
|
+
const NOT_NOW = "The signal does not fit the run's state";
|
|
9
16
|
function checked(format, document, what) {
|
|
10
17
|
const problems = validate(format, document);
|
|
11
18
|
if (problems.length)
|
|
@@ -40,6 +47,19 @@ export function runs(options) {
|
|
|
40
47
|
}
|
|
41
48
|
},
|
|
42
49
|
view,
|
|
50
|
+
async list(state) {
|
|
51
|
+
if (state !== undefined && !isRunState(state))
|
|
52
|
+
throw new UsageError(`state: no run is "${state}"; ask for running, blocked, complete, failed or cancelled`);
|
|
53
|
+
const views = [];
|
|
54
|
+
const query = `WorkflowType = '${factoryRun.name}' AND TaskQueue = '${taskQueue}'`;
|
|
55
|
+
// Temporal's visibility lists running runs first, then the runs that ended, the latest first. A closed run's
|
|
56
|
+
// result is its last view, which is quicker to read than a query that replays its history.
|
|
57
|
+
for await (const run of client.workflow.list({ query }))
|
|
58
|
+
views.push((run.status.name === "COMPLETED"
|
|
59
|
+
? await handle(run.workflowId).result()
|
|
60
|
+
: await view(run.workflowId)));
|
|
61
|
+
return listed(views, state);
|
|
62
|
+
},
|
|
43
63
|
async signal(runId, document) {
|
|
44
64
|
const signal = signalOf(document);
|
|
45
65
|
if (typeof signal === "string")
|
|
@@ -52,13 +72,24 @@ export function runs(options) {
|
|
|
52
72
|
if (signal.type === "steer") {
|
|
53
73
|
await handle(runId).signal(steerSignal, signal.message);
|
|
54
74
|
}
|
|
75
|
+
else if (signal.type === "take-over") {
|
|
76
|
+
const index = current.stages.findIndex((stage) => stage.state === "running");
|
|
77
|
+
if (index < 0)
|
|
78
|
+
throw new Conflict(`no stage of ${runId} is running`, NOT_NOW);
|
|
79
|
+
await handle(runId).signal(takeOverSignal, index);
|
|
80
|
+
}
|
|
81
|
+
else if (signal.type === "hand-back") {
|
|
82
|
+
if (!current.stages.some((stage) => stage.state === "blocked" && stage.takenOverAt))
|
|
83
|
+
throw new Conflict(`nobody took over ${runId}`, NOT_NOW);
|
|
84
|
+
await handle(runId).signal(handBackSignal);
|
|
85
|
+
}
|
|
55
86
|
else {
|
|
56
87
|
await handle(runId).cancel();
|
|
57
88
|
}
|
|
58
89
|
return current;
|
|
59
90
|
},
|
|
60
|
-
async
|
|
61
|
-
return handle(runId).query(
|
|
91
|
+
async control(runId) {
|
|
92
|
+
return handle(runId).query(controlQuery);
|
|
62
93
|
},
|
|
63
94
|
async decided(workItemId, decision) {
|
|
64
95
|
await handle(runIdOf(workItemId)).signal(gateSignal, decision);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/commands/runs.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/commands/runs.ts"],"names":[],"mappings":"AAAA,uHAAuH;AACvH,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAiB,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAEL,oCAAoC,EACpC,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAEL,SAAS,EAET,QAAQ,EACR,UAAU,EACV,MAAM,EAEN,OAAO,EACP,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,GACV,MAAM,qBAAqB,CAAC;AAE7B,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,KAAK,CAAS;IACvB,YAAY,OAAe,EAAE,KAAK,GAAG,eAAe;QAClD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,MAAM,OAAO,GAAG,yCAAyC,CAAC;AAgB1D,SAAS,OAAO,CAAI,MAAc,EAAE,QAAiB,EAAE,IAAY;IACjE,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,IAAI,QAAQ,CAAC,MAAM;QACjB,MAAM,IAAI,UAAU,CAClB,GAAG,IAAI,KAAK,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,IAAI,GAAG,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjG,CAAC;IACJ,OAAO,QAAa,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,OAKpB;IACC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IACtC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,CAAC,KAAa,EAAgC,EAAE,CAC3D,MAAM,CAAC,KAAK,CAAC;SACV,KAAK,CAAC,SAAS,CAAC;SAChB,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3G,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,QAAQ;YAClB,MAAM,QAAQ,GAAG,OAAO,CAAW,WAAW,EAAE,QAAQ,EAAE,6BAA6B,CAAC,CAAC;YACzF,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACxD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE;oBACtC,UAAU,EAAE,KAAK,CAAC,KAAK;oBACvB,SAAS;oBACT,8CAA8C;oBAC9C,qBAAqB,EAAE,qBAAqB,CAAC,gBAAgB;oBAC7D,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;iBACpC,CAAC,CAAC;gBACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,KAAK,YAAY,oCAAoC,CAAC;oBAAE,MAAM,KAAK,CAAC;gBAC1E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAY,EAAE,CAAC;YACxE,CAAC;QACH,CAAC;QACD,IAAI;QACJ,KAAK,CAAC,IAAI,CAAC,KAAK;YACd,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;gBAC3C,MAAM,IAAI,UAAU,CAAC,qBAAqB,KAAK,4DAA4D,CAAC,CAAC;YAC/G,MAAM,KAAK,GAAc,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,mBAAmB,UAAU,CAAC,IAAI,sBAAsB,SAAS,GAAG,CAAC;YACnF,6GAA6G;YAC7G,2FAA2F;YAC3F,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC;gBACrD,KAAK,CAAC,IAAI,CACR,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,WAAW;oBAC9B,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE;oBACvC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAY,CAC3C,CAAC;YACJ,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC9B,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ;YAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAClC,IAAI,OAAO,MAAM,KAAK,QAAQ;gBAAE,MAAM,IAAI,UAAU,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAC;YAC5F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;YAClC,IAAI,CAAC,OAAO;gBAAE,OAAO,SAAS,CAAC;YAC/B,IAAI,QAAQ,CAAC,OAAO,CAAC;gBAAE,MAAM,IAAI,QAAQ,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;YAC1E,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1D,CAAC;iBAAM,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACvC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;gBAC7E,IAAI,KAAK,GAAG,CAAC;oBAAE,MAAM,IAAI,QAAQ,CAAC,eAAe,KAAK,aAAa,EAAE,OAAO,CAAC,CAAC;gBAC9E,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;YACpD,CAAC;iBAAM,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACvC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,CAAC;oBACjF,MAAM,IAAI,QAAQ,CAAC,oBAAoB,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;gBAC3D,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YAC7C,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;YAC/B,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,KAAK;YACjB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ;YAChC,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACjE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// foreman serve: the run API on PORT and a Temporal worker that runs the factory's workflow (ADR 0067). With
|
|
2
|
-
// FOREMAN_NATS_URL it also starts runs for work.received@v1 and passes gate.decided@v1 to the waiting runs.
|
|
3
|
-
// until SIGTERM or SIGINT.
|
|
2
|
+
// FOREMAN_NATS_URL it also starts runs for work.received@v1 and passes gate.decided@v1 to the waiting runs. A factory
|
|
3
|
+
// with an inference adapter gets its gateway while the server runs (ADR 0070). It runs until SIGTERM or SIGINT.
|
|
4
4
|
import { timingSafeEqual } from "node:crypto";
|
|
5
|
-
import { resolve } from "node:path";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { UsageError } from "@opensoftware/cli";
|
|
8
8
|
import { Client, Connection } from "@temporalio/client";
|
|
@@ -10,6 +10,7 @@ import { NativeConnection, Worker } from "@temporalio/worker";
|
|
|
10
10
|
import Fastify, {} from "fastify";
|
|
11
11
|
import { readFactory } from "../adapters/factory.js";
|
|
12
12
|
import { gatekeeper } from "../adapters/gatekeeper.js";
|
|
13
|
+
import { startGateway } from "../adapters/inference.js";
|
|
13
14
|
import { openBus, SUBJECTS } from "../adapters/nats.js";
|
|
14
15
|
import { isoNow } from "../domain/time.js";
|
|
15
16
|
import { activities } from "../workflows/activities.js";
|
|
@@ -27,7 +28,7 @@ function api(work, token) {
|
|
|
27
28
|
if (error instanceof UsageError)
|
|
28
29
|
return problem(reply, 400, "Bad request", error.message);
|
|
29
30
|
if (error instanceof Conflict)
|
|
30
|
-
return problem(reply, 409,
|
|
31
|
+
return problem(reply, 409, error.title, error.message);
|
|
31
32
|
return problem(reply, error.statusCode ?? 500, "The request failed", error.message);
|
|
32
33
|
});
|
|
33
34
|
app.addHook("onRequest", async (request, reply) => {
|
|
@@ -39,6 +40,9 @@ function api(work, token) {
|
|
|
39
40
|
const { created, view } = await work.start(request.body);
|
|
40
41
|
return reply.code(created ? 201 : 200).send(view);
|
|
41
42
|
});
|
|
43
|
+
app.get("/v1/runs", async (request) => ({
|
|
44
|
+
runs: await work.list(request.query.state),
|
|
45
|
+
}));
|
|
42
46
|
app.get("/v1/runs/:runId", async (request, reply) => {
|
|
43
47
|
const view = await work.view(request.params.runId);
|
|
44
48
|
return view ?? unknown(reply, request.params.runId);
|
|
@@ -64,7 +68,7 @@ export async function serve(env) {
|
|
|
64
68
|
if (!token)
|
|
65
69
|
throw new UsageError("set FOREMAN_API_TOKEN: the token that callers of the API send");
|
|
66
70
|
const factoryPath = resolve(env.FOREMAN_FACTORY || "factory.yaml");
|
|
67
|
-
await readFactory(factoryPath);
|
|
71
|
+
const { definition } = await readFactory(factoryPath);
|
|
68
72
|
const now = () => isoNow(env.OSF_NOW);
|
|
69
73
|
const warn = (text) => process.stderr.write(`foreman: ${text}\n`);
|
|
70
74
|
const address = env.FOREMAN_TEMPORAL_ADDRESS || "localhost:7233";
|
|
@@ -73,44 +77,60 @@ export async function serve(env) {
|
|
|
73
77
|
const taskQueue = env.FOREMAN_TASK_QUEUE || "foreman";
|
|
74
78
|
const pollMs = Number(env.FOREMAN_POLL_MS || 1000);
|
|
75
79
|
const bus = env.FOREMAN_NATS_URL ? await openBus(env.FOREMAN_NATS_URL, warn) : undefined;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
connection: await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
80
|
+
const gateway = await gatewayOf(definition.inference, dirname(factoryPath), env);
|
|
81
|
+
// A gateway stops with the server, also when the server cannot start.
|
|
82
|
+
try {
|
|
83
|
+
const client = new Client({ connection: await Connection.connect({ address }), namespace });
|
|
84
|
+
const work = runs({ client, taskQueue, factory: () => readFactory(factoryPath), now });
|
|
85
|
+
const worker = await Worker.create({
|
|
86
|
+
connection: await NativeConnection.connect({ address }),
|
|
87
|
+
namespace,
|
|
88
|
+
taskQueue,
|
|
89
|
+
workflowBundle: { codePath: BUNDLE },
|
|
90
|
+
activities: activities({
|
|
91
|
+
now,
|
|
92
|
+
bus,
|
|
93
|
+
gatekeeper: gatekeeper(env.FOREMAN_GATEKEEPER_URL, env.FOREMAN_GATEKEEPER_TOKEN),
|
|
94
|
+
harnesses: JSON.parse(env.FOREMAN_HARNESSES || "{}"),
|
|
95
|
+
sandboxes: JSON.parse(env.FOREMAN_SANDBOXES || "{}"),
|
|
96
|
+
forges: JSON.parse(env.FOREMAN_FORGES || "{}"),
|
|
97
|
+
forgeEnv: {
|
|
98
|
+
...(env.FOREMAN_FORGE_URL ? { FORGE_URL: env.FOREMAN_FORGE_URL } : {}),
|
|
99
|
+
...(env.FOREMAN_FORGE_TOKEN ? { FORGE_TOKEN: env.FOREMAN_FORGE_TOKEN } : {}),
|
|
100
|
+
},
|
|
101
|
+
checkPollMs: Number(env.FOREMAN_CHECK_POLL_MS || 30_000),
|
|
102
|
+
data: resolve(env.OSF_DATA || ".foreman"),
|
|
103
|
+
pollMs,
|
|
104
|
+
control: (runId) => work.control(runId),
|
|
105
|
+
...(gateway ? { gateway } : {}),
|
|
106
|
+
env,
|
|
107
|
+
}),
|
|
108
|
+
maxHeartbeatThrottleInterval: pollMs,
|
|
109
|
+
defaultHeartbeatThrottleInterval: pollMs,
|
|
110
|
+
});
|
|
111
|
+
const running = worker.run();
|
|
112
|
+
if (bus)
|
|
113
|
+
listen(bus, work);
|
|
114
|
+
const app = api(work, token);
|
|
115
|
+
const port = Number(env.PORT || 8080);
|
|
116
|
+
await app.listen({ port, host: "0.0.0.0" });
|
|
117
|
+
process.stdout.write(`foreman serves the run API on port ${port}, task queue ${taskQueue} at ${address}\n`);
|
|
118
|
+
// Temporal's runtime stops the worker on SIGTERM and SIGINT; the server stops with it.
|
|
119
|
+
await running;
|
|
120
|
+
await app.close();
|
|
121
|
+
await bus?.close();
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
await gateway?.stop();
|
|
125
|
+
}
|
|
114
126
|
return { lines: [] };
|
|
115
127
|
}
|
|
128
|
+
/** Starts the gateway of the factory's inference adapter from its config, a path relative to the factory's folder. */
|
|
129
|
+
async function gatewayOf(inference, folder, env) {
|
|
130
|
+
if (!inference)
|
|
131
|
+
return undefined;
|
|
132
|
+
const lines = JSON.parse(env.FOREMAN_INFERENCES || "{}");
|
|
133
|
+
const adapter = lines[inference.adapter] ?? `npx -y @opensoftware/inference-${inference.adapter}`;
|
|
134
|
+
return startGateway(adapter.split(" "), resolve(folder, inference.config), env);
|
|
135
|
+
}
|
|
116
136
|
//# sourceMappingURL=serve.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The problems that only the foreman sees: two sandbox adapters, a stage whose agent the factory does not
|
|
3
|
-
* checks without exactly one forge adapter.
|
|
2
|
+
* The problems that only the foreman sees: two sandbox or inference adapters, a stage whose agent the factory does not
|
|
3
|
+
* declare, checks without exactly one forge adapter, or an inference adapter without its config.
|
|
4
4
|
*/
|
|
5
5
|
export function stageProblems(factory) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
return oneAdapter(factory, "sandbox").concat(oneAdapter(factory, "inference"), stageAgentProblems(factory), checkProblems(factory), inferenceProblems(factory));
|
|
7
|
+
}
|
|
8
|
+
function oneAdapter(factory, port) {
|
|
9
|
+
const adapters = Object.keys(factory.providers?.[port] ?? {}).length;
|
|
10
|
+
return adapters > 1 ? [{ path: `/providers/${port}`, message: `name one ${port} adapter, not ${adapters}` }] : [];
|
|
11
|
+
}
|
|
12
|
+
function inferenceProblems(factory) {
|
|
13
|
+
const named = Object.keys(factory.providers?.inference ?? {}).length > 0;
|
|
14
|
+
if (named && !factory.inference)
|
|
15
|
+
return [{ path: "/inference", message: "give the inference adapter's config as inference.config" }];
|
|
16
|
+
if (!named && factory.inference)
|
|
17
|
+
return [{ path: "/inference", message: "name one inference adapter under providers.inference" }];
|
|
18
|
+
return [];
|
|
9
19
|
}
|
|
10
20
|
function checkProblems(factory) {
|
|
11
21
|
if (Object.keys(factory.providers?.forge ?? {}).length === 1)
|
|
@@ -27,10 +37,12 @@ export const agentOf = (stage) => stage.agent ?? stage.stage;
|
|
|
27
37
|
/** The definition of a checked factory, with the text of each prompt from `read`. */
|
|
28
38
|
export function definitionOf(factory, read) {
|
|
29
39
|
const forge = Object.keys(factory.providers?.forge ?? {})[0];
|
|
40
|
+
const inference = Object.keys(factory.providers?.inference ?? {})[0];
|
|
30
41
|
return {
|
|
31
42
|
factory: factory.factory,
|
|
32
43
|
sandboxAdapter: Object.keys(factory.providers?.sandbox ?? {})[0] ?? "docker",
|
|
33
44
|
...(forge ? { forgeAdapter: forge } : {}),
|
|
45
|
+
...(inference && factory.inference ? { inference: { adapter: inference, ...factory.inference } } : {}),
|
|
34
46
|
stages: (factory.workflow ?? []).map((stage) => {
|
|
35
47
|
const agent = agentOf(stage);
|
|
36
48
|
const declared = factory.agents[agent];
|
|
@@ -38,6 +50,7 @@ export function definitionOf(factory, read) {
|
|
|
38
50
|
stage: stage.stage,
|
|
39
51
|
agent,
|
|
40
52
|
harness: declared.harness,
|
|
53
|
+
model: declared.model,
|
|
41
54
|
prompt: read(declared.prompt),
|
|
42
55
|
...(stage.gate ? { gate: stage.gate } : {}),
|
|
43
56
|
...(stage.checks?.length ? { checks: stage.checks } : {}),
|