@opensoftware/foreman 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +347 -2
- package/dist/adapters/factory.js +33 -0
- package/dist/adapters/factory.js.map +1 -0
- package/dist/adapters/forge.js +16 -0
- package/dist/adapters/forge.js.map +1 -0
- package/dist/adapters/gatekeeper.js +18 -0
- package/dist/adapters/gatekeeper.js.map +1 -0
- package/dist/adapters/harness.js +64 -0
- package/dist/adapters/harness.js.map +1 -0
- package/dist/adapters/nats.js +31 -0
- package/dist/adapters/nats.js.map +1 -0
- package/dist/adapters/sandbox.js +56 -0
- package/dist/adapters/sandbox.js.map +1 -0
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/runs.js +68 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/serve.js +116 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/domain/definition.js +58 -0
- package/dist/domain/definition.js.map +1 -0
- package/dist/domain/run.js +191 -0
- package/dist/domain/run.js.map +1 -0
- package/dist/domain/time.js +3 -0
- package/dist/domain/time.js.map +1 -0
- package/dist/workflows/activities.js +123 -0
- package/dist/workflows/activities.js.map +1 -0
- package/dist/workflows/run.js +58 -0
- package/dist/workflows/run.js.map +1 -0
- package/dist/workflows.bundle.js +12234 -0
- package/package.json +41 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// A stage's agent through the harness port (ADR 0050, ADR 0067): the adapter runs as a program that speaks ACP on
|
|
2
|
+
// stdio, and the foreman is its client. One session per stage, in the run's folder, with one prompt; a prompt sent
|
|
3
|
+
// while the turn runs is a steering message. In a sandbox, the program is the sandbox adapter's exec (ADR 0068). The
|
|
4
|
+
// foreman keeps what the agent wrote, to find the run's pull request in it (ADR 0069).
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { Readable, Writable } from "node:stream";
|
|
7
|
+
import { client, ndJsonStream, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
|
8
|
+
export async function startTurn(options) {
|
|
9
|
+
const [file, ...args] = options.command;
|
|
10
|
+
// The adapter's own messages go to the foreman's standard error.
|
|
11
|
+
const child = spawn(file, args, { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "inherit"] });
|
|
12
|
+
const exited = new Promise((resolve) => {
|
|
13
|
+
child.on("exit", () => resolve("exited"));
|
|
14
|
+
child.on("error", () => resolve("exited"));
|
|
15
|
+
});
|
|
16
|
+
let transcript = "";
|
|
17
|
+
const connection = client({ name: "opensoftware-foreman" })
|
|
18
|
+
.onNotification("session/update", ({ params }) => {
|
|
19
|
+
const { update } = params;
|
|
20
|
+
// Message chunks join, so a link split across chunks stays whole.
|
|
21
|
+
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text")
|
|
22
|
+
transcript += update.content.text;
|
|
23
|
+
else if (update.sessionUpdate.startsWith("tool_call"))
|
|
24
|
+
transcript += `\n${JSON.stringify(update)}\n`;
|
|
25
|
+
})
|
|
26
|
+
// The adapter refuses tool kinds outside its --allow list itself; the foreman allows the rest (ADR 0067).
|
|
27
|
+
.onRequest("session/request_permission", ({ params }) => {
|
|
28
|
+
const allow = params.options.find((option) => option.kind.startsWith("allow"));
|
|
29
|
+
return { outcome: allow ? { outcome: "selected", optionId: allow.optionId } : { outcome: "cancelled" } };
|
|
30
|
+
})
|
|
31
|
+
.connect(ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout)));
|
|
32
|
+
const { agent } = connection;
|
|
33
|
+
const close = async () => {
|
|
34
|
+
connection.close();
|
|
35
|
+
child.stdin.end();
|
|
36
|
+
await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1000))]);
|
|
37
|
+
child.kill();
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const handshake = (async () => {
|
|
41
|
+
await agent.request("initialize", { protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} });
|
|
42
|
+
return agent.request("session/new", { cwd: options.folder, mcpServers: [] });
|
|
43
|
+
})();
|
|
44
|
+
const answer = await Promise.race([handshake, exited]);
|
|
45
|
+
if (typeof answer === "string")
|
|
46
|
+
throw new Error(`the harness ${options.command.join(" ")} exited`);
|
|
47
|
+
const { sessionId } = answer;
|
|
48
|
+
const send = (text) => agent.request("session/prompt", { sessionId, prompt: [{ type: "text", text }] });
|
|
49
|
+
const turn = send(options.prompt).then(({ stopReason }) => stopReason, () => "exited");
|
|
50
|
+
return {
|
|
51
|
+
sessionId,
|
|
52
|
+
transcript: () => transcript,
|
|
53
|
+
done: Promise.race([turn, exited]),
|
|
54
|
+
steer: (text) => void send(text).catch(() => undefined),
|
|
55
|
+
cancel: () => void agent.notify("session/cancel", { sessionId }).catch(() => undefined),
|
|
56
|
+
close,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await close();
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/adapters/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// NATS (ADR 0067): the foreman publishes run.started@v1 and run.completed@v1 and listens for work.received@v1 and
|
|
2
|
+
// gate.decided@v1 on core NATS subjects.
|
|
3
|
+
import { connect } from "@nats-io/transport-node";
|
|
4
|
+
export const SUBJECTS = {
|
|
5
|
+
workReceived: "osf.work.received.v1",
|
|
6
|
+
gateDecided: "osf.gate.decided.v1",
|
|
7
|
+
runStarted: "osf.run.started.v1",
|
|
8
|
+
runCompleted: "osf.run.completed.v1",
|
|
9
|
+
};
|
|
10
|
+
export async function openBus(url, warn) {
|
|
11
|
+
const connection = await connect({ servers: url });
|
|
12
|
+
return {
|
|
13
|
+
async publish(subject, event) {
|
|
14
|
+
connection.publish(subject, JSON.stringify(event));
|
|
15
|
+
await connection.flush();
|
|
16
|
+
},
|
|
17
|
+
subscribe(subject, handle) {
|
|
18
|
+
connection.subscribe(subject, {
|
|
19
|
+
callback: (_error, message) => {
|
|
20
|
+
Promise.resolve()
|
|
21
|
+
.then(() => handle(message.json()))
|
|
22
|
+
.catch((failure) => warn(`${subject}: ${failure.message}`));
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
async close() {
|
|
27
|
+
await connection.drain();
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=nats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nats.js","sourceRoot":"","sources":["../../src/adapters/nats.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,yCAAyC;AACzC,OAAO,EAAE,OAAO,EAAuB,MAAM,yBAAyB,CAAC;AAEvE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,YAAY,EAAE,sBAAsB;IACpC,WAAW,EAAE,qBAAqB;IAClC,UAAU,EAAE,oBAAoB;IAChC,YAAY,EAAE,sBAAsB;CAC5B,CAAC;AASX,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAW,EAAE,IAA4B;IACrE,MAAM,UAAU,GAAmB,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IACnE,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK;YAC1B,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACnD,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;QAC3B,CAAC;QACD,SAAS,CAAC,OAAO,EAAE,MAAM;YACvB,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE;gBAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;oBAC5B,OAAO,CAAC,OAAO,EAAE;yBACd,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;yBAClC,KAAK,CAAC,CAAC,OAAc,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACvE,CAAC;aACF,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,KAAK;YACT,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// A stage's sandbox through the sandbox port (ADR 0054, ADR 0068). The adapter is a program; the foreman creates a
|
|
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
|
+
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join, posix } from "node:path";
|
|
7
|
+
/** Runs one command of the adapter; a failure names the command and what the adapter wrote to standard error. */
|
|
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
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A new sandbox from the spec for the files of `local`, the run's folder, which it holds at `<working folder>/<name>`
|
|
21
|
+
* after `copyIn`. The caller destroys it.
|
|
22
|
+
*/
|
|
23
|
+
export async function createSandbox(adapter, spec, local, name, env) {
|
|
24
|
+
const run = adapterRun(adapter, env);
|
|
25
|
+
const temporary = mkdtempSync(join(tmpdir(), "foreman-sandbox-"));
|
|
26
|
+
let id;
|
|
27
|
+
try {
|
|
28
|
+
writeFileSync(join(temporary, "spec.json"), JSON.stringify(spec));
|
|
29
|
+
id = (await run("create", join(temporary, "spec.json"))).split("\n").pop();
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
async copyIn() {
|
|
36
|
+
const working = await run("exec", id, "--", "pwd");
|
|
37
|
+
// The path does not exist in the sandbox yet, so every adapter copies the folder itself, not into it.
|
|
38
|
+
await run("copy", local, `${id}:${name}`);
|
|
39
|
+
return posix.join(working, name);
|
|
40
|
+
},
|
|
41
|
+
exec: (command) => [...adapter, "exec", id, "--", ...command],
|
|
42
|
+
async copyOut() {
|
|
43
|
+
const out = mkdtempSync(join(tmpdir(), "foreman-sandbox-out-"));
|
|
44
|
+
try {
|
|
45
|
+
await run("copy", `${id}:${name}`, join(out, name));
|
|
46
|
+
rmSync(local, { recursive: true, force: true });
|
|
47
|
+
cpSync(join(out, name), local, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
rmSync(out, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
destroy: async () => void (await run("destroy", id)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/adapters/sandbox.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,wHAAwH;AACxH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,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;AAaxC,iHAAiH;AACjH,SAAS,UAAU,CAAC,OAAiB,EAAE,GAAuC;IAC5E,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,yBAAyB,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;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAiB,EACjB,IAAiB,EACjB,KAAa,EACb,IAAY,EACZ,GAAuC;IAEvC,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrC,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,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAY,CAAC;IACvF,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/cli.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// foreman <command>: the entry layer only parses arguments and calls the commands.
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { runProgram } from "@opensoftware/cli";
|
|
5
|
+
import { serve } from "./commands/serve.js";
|
|
6
|
+
const HELP = `Usage: foreman <command>
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
serve Serve the run API on PORT and run the factory's workflow in FOREMAN_FACTORY on Temporal at
|
|
10
|
+
FOREMAN_TEMPORAL_ADDRESS.
|
|
11
|
+
`;
|
|
12
|
+
const { positionals, values } = parseArgs({ allowPositionals: true, options: { help: { type: "boolean" } } });
|
|
13
|
+
const serving = () => serve(process.env);
|
|
14
|
+
process.exitCode = await runProgram({
|
|
15
|
+
name: "foreman",
|
|
16
|
+
usage: HELP,
|
|
17
|
+
command: positionals[0] === "serve" ? serving : undefined,
|
|
18
|
+
help: values.help,
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,mFAAmF;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAgB,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE5C,MAAM,IAAI,GAAG;;;;;CAKZ,CAAC;AAEF,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;AAC9G,MAAM,OAAO,GAAY,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAClD,OAAO,CAAC,QAAQ,GAAG,MAAM,UAAU,CAAC;IAClC,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;IACzD,IAAI,EAAE,MAAM,CAAC,IAAI;CAClB,CAAC,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// The runs of the foreman as Temporal workflows (ADR 0067): start one per work item, read its view, signal it.
|
|
2
|
+
import { UsageError } from "@opensoftware/cli";
|
|
3
|
+
import { validate } from "@opensoftware/contracts";
|
|
4
|
+
import { WorkflowExecutionAlreadyStartedError, WorkflowIdReusePolicy, WorkflowNotFoundError, } from "@temporalio/client";
|
|
5
|
+
import { firstView, hasEnded, runIdOf, signalOf } from "../domain/run.js";
|
|
6
|
+
import { factoryRun, gateSignal, steeringQuery, steerSignal, viewQuery } from "../workflows/run.js";
|
|
7
|
+
export class Conflict extends Error {
|
|
8
|
+
}
|
|
9
|
+
function checked(format, document, what) {
|
|
10
|
+
const problems = validate(format, document);
|
|
11
|
+
if (problems.length)
|
|
12
|
+
throw new UsageError(`${what}: ${problems.map((problem) => `${problem.path || "/"}: ${problem.message}`).join("; ")}`);
|
|
13
|
+
return document;
|
|
14
|
+
}
|
|
15
|
+
export function runs(options) {
|
|
16
|
+
const { client, taskQueue } = options;
|
|
17
|
+
const handle = (runId) => client.workflow.getHandle(runId);
|
|
18
|
+
const view = (runId) => handle(runId)
|
|
19
|
+
.query(viewQuery)
|
|
20
|
+
.catch((error) => (error instanceof WorkflowNotFoundError ? undefined : Promise.reject(error)));
|
|
21
|
+
return {
|
|
22
|
+
async start(document) {
|
|
23
|
+
const workItem = checked("work-item", document, "the body is not a work item");
|
|
24
|
+
const { definition, version } = await options.factory();
|
|
25
|
+
const first = firstView(workItem, definition, version, options.now());
|
|
26
|
+
try {
|
|
27
|
+
await client.workflow.start(factoryRun, {
|
|
28
|
+
workflowId: first.runId,
|
|
29
|
+
taskQueue,
|
|
30
|
+
// One run per work item, also after it ended.
|
|
31
|
+
workflowIdReusePolicy: WorkflowIdReusePolicy.REJECT_DUPLICATE,
|
|
32
|
+
args: [{ view: first, definition }],
|
|
33
|
+
});
|
|
34
|
+
return { created: true, view: first };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!(error instanceof WorkflowExecutionAlreadyStartedError))
|
|
38
|
+
throw error;
|
|
39
|
+
return { created: false, view: (await view(first.runId)) };
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
view,
|
|
43
|
+
async signal(runId, document) {
|
|
44
|
+
const signal = signalOf(document);
|
|
45
|
+
if (typeof signal === "string")
|
|
46
|
+
throw new UsageError(`the body is not a signal: ${signal}`);
|
|
47
|
+
const current = await view(runId);
|
|
48
|
+
if (!current)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (hasEnded(current))
|
|
51
|
+
throw new Conflict(`${runId} is ${current.state}`);
|
|
52
|
+
if (signal.type === "steer") {
|
|
53
|
+
await handle(runId).signal(steerSignal, signal.message);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
await handle(runId).cancel();
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
},
|
|
60
|
+
async steering(runId) {
|
|
61
|
+
return handle(runId).query(steeringQuery);
|
|
62
|
+
},
|
|
63
|
+
async decided(workItemId, decision) {
|
|
64
|
+
await handle(runIdOf(workItemId)).signal(gateSignal, decision);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/commands/runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
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. It runs
|
|
3
|
+
// until SIGTERM or SIGINT.
|
|
4
|
+
import { timingSafeEqual } from "node:crypto";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { UsageError } from "@opensoftware/cli";
|
|
8
|
+
import { Client, Connection } from "@temporalio/client";
|
|
9
|
+
import { NativeConnection, Worker } from "@temporalio/worker";
|
|
10
|
+
import Fastify, {} from "fastify";
|
|
11
|
+
import { readFactory } from "../adapters/factory.js";
|
|
12
|
+
import { gatekeeper } from "../adapters/gatekeeper.js";
|
|
13
|
+
import { openBus, SUBJECTS } from "../adapters/nats.js";
|
|
14
|
+
import { isoNow } from "../domain/time.js";
|
|
15
|
+
import { activities } from "../workflows/activities.js";
|
|
16
|
+
import { Conflict, runs } from "./runs.js";
|
|
17
|
+
const BUNDLE = fileURLToPath(new URL("../workflows.bundle.js", import.meta.url));
|
|
18
|
+
const problem = (reply, status, title, detail) => reply.code(status).type("application/problem+json").send({ title, status, detail });
|
|
19
|
+
/** Whether the request carries the token; compared in constant time. */
|
|
20
|
+
function authorized(header, token) {
|
|
21
|
+
const [sent, wanted] = [Buffer.from(header ?? ""), Buffer.from(`Bearer ${token}`)];
|
|
22
|
+
return sent.length === wanted.length && timingSafeEqual(sent, wanted);
|
|
23
|
+
}
|
|
24
|
+
function api(work, token) {
|
|
25
|
+
const app = Fastify();
|
|
26
|
+
app.setErrorHandler((error, _request, reply) => {
|
|
27
|
+
if (error instanceof UsageError)
|
|
28
|
+
return problem(reply, 400, "Bad request", error.message);
|
|
29
|
+
if (error instanceof Conflict)
|
|
30
|
+
return problem(reply, 409, "The run ended", error.message);
|
|
31
|
+
return problem(reply, error.statusCode ?? 500, "The request failed", error.message);
|
|
32
|
+
});
|
|
33
|
+
app.addHook("onRequest", async (request, reply) => {
|
|
34
|
+
if (!authorized(request.headers.authorization, token))
|
|
35
|
+
return problem(reply, 401, "Unauthorized", "send Authorization: Bearer <FOREMAN_API_TOKEN>");
|
|
36
|
+
});
|
|
37
|
+
const unknown = (reply, runId) => problem(reply, 404, "No such run", `the server has no run ${runId}`);
|
|
38
|
+
app.post("/v1/runs", async (request, reply) => {
|
|
39
|
+
const { created, view } = await work.start(request.body);
|
|
40
|
+
return reply.code(created ? 201 : 200).send(view);
|
|
41
|
+
});
|
|
42
|
+
app.get("/v1/runs/:runId", async (request, reply) => {
|
|
43
|
+
const view = await work.view(request.params.runId);
|
|
44
|
+
return view ?? unknown(reply, request.params.runId);
|
|
45
|
+
});
|
|
46
|
+
app.post("/v1/runs/:runId/signals", async (request, reply) => {
|
|
47
|
+
const view = await work.signal(request.params.runId, request.body);
|
|
48
|
+
return view ? reply.code(202).send(view) : unknown(reply, request.params.runId);
|
|
49
|
+
});
|
|
50
|
+
return app;
|
|
51
|
+
}
|
|
52
|
+
/** Starts runs for work items from intake and hands gate decisions to the runs that wait for them. */
|
|
53
|
+
function listen(bus, work) {
|
|
54
|
+
bus.subscribe(SUBJECTS.workReceived, async (event) => {
|
|
55
|
+
await work.start(event.data);
|
|
56
|
+
});
|
|
57
|
+
bus.subscribe(SUBJECTS.gateDecided, async (event) => {
|
|
58
|
+
const { data } = event;
|
|
59
|
+
await work.decided(data.workItemId, { gateId: data.gateId, state: data.decision });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export async function serve(env) {
|
|
63
|
+
const token = env.FOREMAN_API_TOKEN;
|
|
64
|
+
if (!token)
|
|
65
|
+
throw new UsageError("set FOREMAN_API_TOKEN: the token that callers of the API send");
|
|
66
|
+
const factoryPath = resolve(env.FOREMAN_FACTORY || "factory.yaml");
|
|
67
|
+
await readFactory(factoryPath);
|
|
68
|
+
const now = () => isoNow(env.OSF_NOW);
|
|
69
|
+
const warn = (text) => process.stderr.write(`foreman: ${text}\n`);
|
|
70
|
+
const address = env.FOREMAN_TEMPORAL_ADDRESS || "localhost:7233";
|
|
71
|
+
// Unset, the client and the worker use the namespace "default".
|
|
72
|
+
const namespace = env.FOREMAN_TEMPORAL_NAMESPACE;
|
|
73
|
+
const taskQueue = env.FOREMAN_TASK_QUEUE || "foreman";
|
|
74
|
+
const pollMs = Number(env.FOREMAN_POLL_MS || 1000);
|
|
75
|
+
const bus = env.FOREMAN_NATS_URL ? await openBus(env.FOREMAN_NATS_URL, warn) : undefined;
|
|
76
|
+
const client = new Client({ connection: await Connection.connect({ address }), namespace });
|
|
77
|
+
const work = runs({ client, taskQueue, factory: () => readFactory(factoryPath), now });
|
|
78
|
+
const worker = await Worker.create({
|
|
79
|
+
connection: await NativeConnection.connect({ address }),
|
|
80
|
+
namespace,
|
|
81
|
+
taskQueue,
|
|
82
|
+
workflowBundle: { codePath: BUNDLE },
|
|
83
|
+
activities: activities({
|
|
84
|
+
now,
|
|
85
|
+
bus,
|
|
86
|
+
gatekeeper: gatekeeper(env.FOREMAN_GATEKEEPER_URL, env.FOREMAN_GATEKEEPER_TOKEN),
|
|
87
|
+
harnesses: JSON.parse(env.FOREMAN_HARNESSES || "{}"),
|
|
88
|
+
sandboxes: JSON.parse(env.FOREMAN_SANDBOXES || "{}"),
|
|
89
|
+
forges: JSON.parse(env.FOREMAN_FORGES || "{}"),
|
|
90
|
+
forgeEnv: {
|
|
91
|
+
...(env.FOREMAN_FORGE_URL ? { FORGE_URL: env.FOREMAN_FORGE_URL } : {}),
|
|
92
|
+
...(env.FOREMAN_FORGE_TOKEN ? { FORGE_TOKEN: env.FOREMAN_FORGE_TOKEN } : {}),
|
|
93
|
+
},
|
|
94
|
+
checkPollMs: Number(env.FOREMAN_CHECK_POLL_MS || 30_000),
|
|
95
|
+
data: resolve(env.OSF_DATA || ".foreman"),
|
|
96
|
+
pollMs,
|
|
97
|
+
steering: (runId) => work.steering(runId),
|
|
98
|
+
env,
|
|
99
|
+
}),
|
|
100
|
+
maxHeartbeatThrottleInterval: pollMs,
|
|
101
|
+
defaultHeartbeatThrottleInterval: pollMs,
|
|
102
|
+
});
|
|
103
|
+
const running = worker.run();
|
|
104
|
+
if (bus)
|
|
105
|
+
listen(bus, work);
|
|
106
|
+
const app = api(work, token);
|
|
107
|
+
const port = Number(env.PORT || 8080);
|
|
108
|
+
await app.listen({ port, host: "0.0.0.0" });
|
|
109
|
+
process.stdout.write(`foreman serves the run API on port ${port}, task queue ${taskQueue} at ${address}\n`);
|
|
110
|
+
// Temporal's runtime stops the worker on SIGTERM and SIGINT; the server stops with it.
|
|
111
|
+
await running;
|
|
112
|
+
await app.close();
|
|
113
|
+
await bus?.close();
|
|
114
|
+
return { lines: [] };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,oHAAoH;AACpH,2BAA2B;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,OAAO,EAAE,EAAqB,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAY,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAa,IAAI,EAAE,MAAM,WAAW,CAAC;AAItD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,wBAAwB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEjF,MAAM,OAAO,GAAG,CAAC,KAAmB,EAAE,MAAc,EAAE,KAAa,EAAE,MAAc,EAAE,EAAE,CACrF,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAEtF,wEAAwE;AACxE,SAAS,UAAU,CAAC,MAA0B,EAAE,KAAa;IAC3D,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC;IACnF,OAAO,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,GAAG,CAAC,IAAU,EAAE,KAAa;IACpC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,eAAe,CAAC,CAAC,KAAsC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;QAC9E,IAAI,KAAK,YAAY,UAAU;YAAE,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1F,IAAI,KAAK,YAAY,QAAQ;YAAE,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1F,OAAO,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,IAAI,GAAG,EAAE,oBAAoB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAChD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC;YACnD,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,EAAE,gDAAgD,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,CAAC,KAAmB,EAAE,KAAa,EAAE,EAAE,CACrD,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,yBAAyB,KAAK,EAAE,CAAC,CAAC;IACvE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC5C,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzD,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAgC,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACjF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnD,OAAO,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,IAAI,CAAgC,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC1F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sGAAsG;AACtG,SAAS,MAAM,CAAC,GAAQ,EAAE,IAAU;IAClC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACnD,MAAM,IAAI,CAAC,KAAK,CAAE,KAA6B,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAClD,MAAM,EAAE,IAAI,EAAE,GAAG,KAAoB,CAAC;QACtC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,GAAQ;IAClC,MAAM,KAAK,GAAG,GAAG,CAAC,iBAAiB,CAAC;IACpC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,UAAU,CAAC,+DAA+D,CAAC,CAAC;IAClG,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,cAAc,CAAC,CAAC;IACnE,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,GAAG,CAAC,wBAAwB,IAAI,gBAAgB,CAAC;IACjE,gEAAgE;IAChE,MAAM,SAAS,GAAG,GAAG,CAAC,0BAA0B,CAAC;IACjD,MAAM,SAAS,GAAG,GAAG,CAAC,kBAAkB,IAAI,SAAS,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEzF,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;QACjC,UAAU,EAAE,MAAM,gBAAgB,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;QACvD,SAAS;QACT,SAAS;QACT,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;QACpC,UAAU,EAAE,UAAU,CAAC;YACrB,GAAG;YACH,GAAG;YACH,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,sBAAsB,EAAE,GAAG,CAAC,wBAAwB,CAAC;YAChF,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAA2B;YAC9E,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAA2B;YAC9E,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAA2B;YACxE,QAAQ,EAAE;gBACR,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtE,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC7E;YACD,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,IAAI,MAAM,CAAC;YACxD,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,UAAU,CAAC;YACzC,MAAM;YACN,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YACzC,GAAG;SACJ,CAAC;QACF,4BAA4B,EAAE,MAAM;QACpC,gCAAgC,EAAE,MAAM;KACzC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,GAAG;QAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAE3B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IACtC,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,IAAI,gBAAgB,SAAS,OAAO,OAAO,IAAI,CAAC,CAAC;IAC5G,uFAAuF;IACvF,MAAM,OAAO,CAAC;IACd,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAClB,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC;IACnB,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The problems that only the foreman sees: two sandbox adapters, a stage whose agent the factory does not declare, or
|
|
3
|
+
* checks without exactly one forge adapter.
|
|
4
|
+
*/
|
|
5
|
+
export function stageProblems(factory) {
|
|
6
|
+
const adapters = Object.keys(factory.providers?.sandbox ?? {}).length;
|
|
7
|
+
const sandbox = adapters > 1 ? [{ path: "/providers/sandbox", message: `name one sandbox adapter, not ${adapters}` }] : [];
|
|
8
|
+
return sandbox.concat(stageAgentProblems(factory), checkProblems(factory));
|
|
9
|
+
}
|
|
10
|
+
function checkProblems(factory) {
|
|
11
|
+
if (Object.keys(factory.providers?.forge ?? {}).length === 1)
|
|
12
|
+
return [];
|
|
13
|
+
return (factory.workflow ?? []).flatMap((stage, index) => stage.checks?.length
|
|
14
|
+
? [{ path: `/workflow/${index}/checks`, message: "name one forge adapter under providers.forge" }]
|
|
15
|
+
: []);
|
|
16
|
+
}
|
|
17
|
+
function stageAgentProblems(factory) {
|
|
18
|
+
return (factory.workflow ?? []).flatMap((stage, index) => {
|
|
19
|
+
const agent = agentOf(stage);
|
|
20
|
+
return factory.agents[agent]
|
|
21
|
+
? []
|
|
22
|
+
: [{ path: `/workflow/${index}/${stage.agent ? "agent" : "stage"}`, message: `no agent "${agent}"` }];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** A stage's agent defaults to the agent with the stage's name. */
|
|
26
|
+
export const agentOf = (stage) => stage.agent ?? stage.stage;
|
|
27
|
+
/** The definition of a checked factory, with the text of each prompt from `read`. */
|
|
28
|
+
export function definitionOf(factory, read) {
|
|
29
|
+
const forge = Object.keys(factory.providers?.forge ?? {})[0];
|
|
30
|
+
return {
|
|
31
|
+
factory: factory.factory,
|
|
32
|
+
sandboxAdapter: Object.keys(factory.providers?.sandbox ?? {})[0] ?? "docker",
|
|
33
|
+
...(forge ? { forgeAdapter: forge } : {}),
|
|
34
|
+
stages: (factory.workflow ?? []).map((stage) => {
|
|
35
|
+
const agent = agentOf(stage);
|
|
36
|
+
const declared = factory.agents[agent];
|
|
37
|
+
return {
|
|
38
|
+
stage: stage.stage,
|
|
39
|
+
agent,
|
|
40
|
+
harness: declared.harness,
|
|
41
|
+
prompt: read(declared.prompt),
|
|
42
|
+
...(stage.gate ? { gate: stage.gate } : {}),
|
|
43
|
+
...(stage.checks?.length ? { checks: stage.checks } : {}),
|
|
44
|
+
...(declared.sandbox ? { sandbox: { ...declared.sandbox, secrets: secretsOf(declared.secrets ?? []) } } : {}),
|
|
45
|
+
};
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The variable of a secret reference: its name in upper case, every character outside A-Z and 0-9 as "_". */
|
|
50
|
+
export const secretVariable = (reference) => reference
|
|
51
|
+
.slice(reference.indexOf("/") + 1)
|
|
52
|
+
.toUpperCase()
|
|
53
|
+
.replace(/[^A-Z0-9]/g, "_");
|
|
54
|
+
/** The agent's secret references by their variables, as the sandbox spec holds them (ADR 0068). */
|
|
55
|
+
function secretsOf(references) {
|
|
56
|
+
return Object.fromEntries(references.map((reference) => [secretVariable(reference), reference]));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/domain/definition.ts"],"names":[],"mappings":"AAiCA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAA0B;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACtE,MAAM,OAAO,GACX,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,iCAAiC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7G,OAAO,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,aAAa,CAAC,OAA0B;IAC/C,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACxE,OAAO,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACvD,KAAK,CAAC,MAAM,EAAE,MAAM;QAClB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,KAAK,SAAS,EAAE,OAAO,EAAE,8CAA8C,EAAE,CAAC;QAClG,CAAC,CAAC,EAAE,CACP,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,OAA0B;IACpD,OAAO,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;YAC1B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,KAAK,GAAG,EAAE,CAAC,CAAC;IAC1G,CAAC,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAwC,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC;AAExG,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,OAA0B,EAAE,IAA8B;IACrF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ;QAC5E,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAwC,CAAC;YAC9E,OAAO;gBACL,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,KAAK;gBACL,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC7B,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3C,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzD,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9G,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED,8GAA8G;AAC9G,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAU,EAAE,CAC1D,SAAS;KACN,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;KACjC,WAAW,EAAE;KACb,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;AAEhC,mGAAmG;AACnG,SAAS,SAAS,CAAC,UAAoB;IACrC,OAAO,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;AACnG,CAAC"}
|