@oisincoveney/pipeline 2.8.0 → 2.8.2
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/dist/argo-submit.d.ts +2 -4
- package/dist/argo-submit.js +80 -80
- package/dist/cluster-doctor.js +89 -101
- package/dist/config/defaults.js +9 -19
- package/dist/config/load.js +32 -39
- package/dist/config/schemas.d.ts +1 -1
- package/dist/gates.js +6 -225
- package/dist/mcp/gateway-error.js +15 -0
- package/dist/mcp/gateway.js +119 -220
- package/dist/moka-global-config.js +20 -20
- package/dist/moka-submit.d.ts +1 -1
- package/dist/pipeline-runtime.js +580 -371
- package/dist/run-state/git-refs.js +124 -94
- package/dist/runner-command-contract.d.ts +2 -2
- package/dist/runner-event-sink.js +37 -69
- package/dist/runtime/agent-node/agent-node.js +214 -173
- package/dist/runtime/builtins/builtins.js +344 -57
- package/dist/runtime/changed-files/changed-files.js +15 -27
- package/dist/runtime/changed-files/index.js +2 -0
- package/dist/runtime/drain-merge/drain-merge.js +124 -82
- package/dist/runtime/gates/gates.js +46 -28
- package/dist/runtime/hooks/hooks.js +74 -29
- package/dist/runtime/opencode-server.js +27 -21
- package/dist/runtime/opencode-session-executor.js +101 -44
- package/dist/runtime/parallel-node/parallel-node.js +24 -5
- package/dist/runtime/select-candidate/select-candidate.js +45 -29
- package/dist/runtime/services/agent-node-runtime-service.js +15 -0
- package/dist/runtime/services/command-executor-service.js +8 -0
- package/dist/runtime/services/config-io-service.js +42 -0
- package/dist/runtime/services/drain-merge-git-service.js +10 -0
- package/dist/runtime/services/git-porcelain-service.js +38 -0
- package/dist/runtime/services/kubernetes-argo-service.d.ts +13 -0
- package/dist/runtime/services/kubernetes-argo-service.js +81 -0
- package/dist/runtime/services/mcp-gateway-service.js +184 -0
- package/dist/runtime/services/opencode-sdk-service.js +27 -0
- package/dist/runtime/services/runner-event-sink-http-service.js +80 -0
- package/dist/runtime/services/select-candidate-service.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { PipelineMcpGatewayError } from "../../mcp/gateway-error.js";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
//#region src/runtime/services/mcp-gateway-service.ts
|
|
5
|
+
function gatewayError(message) {
|
|
6
|
+
return new PipelineMcpGatewayError(message);
|
|
7
|
+
}
|
|
8
|
+
function execaErrorMessage(error) {
|
|
9
|
+
const subprocessError = error;
|
|
10
|
+
const subprocessMessage = subprocessError.shortMessage || subprocessError.stderr;
|
|
11
|
+
if (subprocessMessage) return subprocessMessage.trim();
|
|
12
|
+
return error instanceof Error ? error.message : String(error);
|
|
13
|
+
}
|
|
14
|
+
function execaGatewayError(error) {
|
|
15
|
+
return error instanceof PipelineMcpGatewayError ? error : gatewayError(execaErrorMessage(error));
|
|
16
|
+
}
|
|
17
|
+
function toolHiveWorkloadTransport(item) {
|
|
18
|
+
if (typeof item.transport_type === "string") return item.transport_type;
|
|
19
|
+
if (typeof item.transport === "string") return item.transport;
|
|
20
|
+
}
|
|
21
|
+
function parseToolHiveWorkloads(stdout) {
|
|
22
|
+
const trimmed = stdout.trim();
|
|
23
|
+
if (!trimmed) return [];
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(trimmed);
|
|
27
|
+
} catch {
|
|
28
|
+
throw gatewayError("ToolHive list returned malformed JSON while reconciling MCP gateway workloads.");
|
|
29
|
+
}
|
|
30
|
+
if (!Array.isArray(parsed)) throw gatewayError("ToolHive list returned a non-array payload while reconciling MCP gateway workloads.");
|
|
31
|
+
return parsed.flatMap((item) => toToolHiveWorkload(item));
|
|
32
|
+
}
|
|
33
|
+
function asString(value) {
|
|
34
|
+
return typeof value === "string" ? value : void 0;
|
|
35
|
+
}
|
|
36
|
+
function toToolHiveWorkload(item) {
|
|
37
|
+
if (!item || typeof item.name !== "string") return [];
|
|
38
|
+
return [{
|
|
39
|
+
name: item.name,
|
|
40
|
+
status: asString(item.status),
|
|
41
|
+
transport: toolHiveWorkloadTransport(item),
|
|
42
|
+
url: asString(item.url)
|
|
43
|
+
}];
|
|
44
|
+
}
|
|
45
|
+
function isHealthyGatewayStatus(status) {
|
|
46
|
+
return status >= 200 && status < 300 || status === 405;
|
|
47
|
+
}
|
|
48
|
+
function activeDockerHost(cwd) {
|
|
49
|
+
return Effect.tryPromise({
|
|
50
|
+
catch: () => void 0,
|
|
51
|
+
try: async () => {
|
|
52
|
+
const result = await execa("docker", ["context", "inspect"], {
|
|
53
|
+
cwd,
|
|
54
|
+
stdin: "ignore"
|
|
55
|
+
});
|
|
56
|
+
const host = JSON.parse(result.stdout)[0]?.Endpoints?.docker?.Host;
|
|
57
|
+
return typeof host === "string" && host.length > 0 ? host : void 0;
|
|
58
|
+
}
|
|
59
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
60
|
+
}
|
|
61
|
+
function toolhiveEnv(cwd) {
|
|
62
|
+
if (process.env.DOCKER_HOST) return Effect.succeed(process.env);
|
|
63
|
+
return activeDockerHost(cwd).pipe(Effect.map((dockerHost) => dockerHost ? {
|
|
64
|
+
...process.env,
|
|
65
|
+
DOCKER_HOST: dockerHost
|
|
66
|
+
} : process.env));
|
|
67
|
+
}
|
|
68
|
+
function fetchGateway(url, init) {
|
|
69
|
+
return Effect.tryPromise({
|
|
70
|
+
catch: execaGatewayError,
|
|
71
|
+
try: () => fetch(url, init)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
var McpGatewayService = class extends Context.Tag("McpGatewayService")() {};
|
|
75
|
+
const McpGatewayServiceLive = Layer.succeed(McpGatewayService, {
|
|
76
|
+
callGatewayRpc: (url, body, authorization) => fetchGateway(url, {
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
headers: {
|
|
79
|
+
Accept: "application/json",
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
...authorization ? { Authorization: authorization } : {}
|
|
82
|
+
},
|
|
83
|
+
method: "POST"
|
|
84
|
+
}).pipe(Effect.flatMap((response) => {
|
|
85
|
+
if (!response.ok) return Effect.fail(gatewayError(`Gateway MCP request failed: HTTP ${response.status}.`));
|
|
86
|
+
return Effect.tryPromise({
|
|
87
|
+
catch: execaGatewayError,
|
|
88
|
+
try: () => response.json()
|
|
89
|
+
});
|
|
90
|
+
})),
|
|
91
|
+
firstHealthyGatewayResponse: (urls, authorization) => Effect.gen(function* () {
|
|
92
|
+
for (const url of urls) {
|
|
93
|
+
const response = yield* fetchGateway(url, {
|
|
94
|
+
headers: {
|
|
95
|
+
Accept: "application/json, text/event-stream",
|
|
96
|
+
...authorization ? { Authorization: authorization } : {}
|
|
97
|
+
},
|
|
98
|
+
method: "GET"
|
|
99
|
+
});
|
|
100
|
+
if (isHealthyGatewayStatus(response.status)) return response;
|
|
101
|
+
}
|
|
102
|
+
}),
|
|
103
|
+
listToolHiveGroupWorkloads: (group, cwd) => Effect.gen(function* () {
|
|
104
|
+
const env = yield* toolhiveEnv(cwd);
|
|
105
|
+
const result = yield* Effect.tryPromise({
|
|
106
|
+
catch: execaGatewayError,
|
|
107
|
+
try: () => execa("thv", [
|
|
108
|
+
"list",
|
|
109
|
+
"--group",
|
|
110
|
+
group,
|
|
111
|
+
"--format",
|
|
112
|
+
"json"
|
|
113
|
+
], {
|
|
114
|
+
cwd,
|
|
115
|
+
env,
|
|
116
|
+
stdin: "ignore"
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
return yield* Effect.try({
|
|
120
|
+
catch: execaGatewayError,
|
|
121
|
+
try: () => parseToolHiveWorkloads(result.stdout)
|
|
122
|
+
});
|
|
123
|
+
}),
|
|
124
|
+
localGatewayStatus: (cwd) => Effect.gen(function* () {
|
|
125
|
+
const env = yield* toolhiveEnv(cwd);
|
|
126
|
+
return (yield* Effect.tryPromise({
|
|
127
|
+
catch: execaGatewayError,
|
|
128
|
+
try: () => execa("thv", ["list"], {
|
|
129
|
+
cwd,
|
|
130
|
+
env
|
|
131
|
+
})
|
|
132
|
+
})).stdout.trim();
|
|
133
|
+
}),
|
|
134
|
+
runToolHiveVersion: (cwd) => Effect.gen(function* () {
|
|
135
|
+
const env = yield* toolhiveEnv(cwd);
|
|
136
|
+
yield* Effect.tryPromise({
|
|
137
|
+
catch: execaGatewayError,
|
|
138
|
+
try: () => execa("thv", ["version"], {
|
|
139
|
+
cwd,
|
|
140
|
+
env,
|
|
141
|
+
stdin: "ignore"
|
|
142
|
+
})
|
|
143
|
+
});
|
|
144
|
+
}),
|
|
145
|
+
serveToolHiveVmcp: (configPath, cwd) => Effect.gen(function* () {
|
|
146
|
+
const env = yield* toolhiveEnv(cwd);
|
|
147
|
+
yield* Effect.tryPromise({
|
|
148
|
+
catch: execaGatewayError,
|
|
149
|
+
try: () => execa("thv", [
|
|
150
|
+
"vmcp",
|
|
151
|
+
"serve",
|
|
152
|
+
"--config",
|
|
153
|
+
configPath,
|
|
154
|
+
"--host",
|
|
155
|
+
"127.0.0.1",
|
|
156
|
+
"--port",
|
|
157
|
+
"4483"
|
|
158
|
+
], {
|
|
159
|
+
cwd,
|
|
160
|
+
env,
|
|
161
|
+
stderr: "inherit",
|
|
162
|
+
stdout: "inherit"
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
}),
|
|
166
|
+
validateToolHiveVmcp: (configPath, cwd) => Effect.gen(function* () {
|
|
167
|
+
const env = yield* toolhiveEnv(cwd);
|
|
168
|
+
yield* Effect.tryPromise({
|
|
169
|
+
catch: execaGatewayError,
|
|
170
|
+
try: () => execa("thv", [
|
|
171
|
+
"vmcp",
|
|
172
|
+
"validate",
|
|
173
|
+
"--config",
|
|
174
|
+
configPath
|
|
175
|
+
], {
|
|
176
|
+
cwd,
|
|
177
|
+
env,
|
|
178
|
+
stdin: "ignore"
|
|
179
|
+
})
|
|
180
|
+
});
|
|
181
|
+
})
|
|
182
|
+
});
|
|
183
|
+
//#endregion
|
|
184
|
+
export { McpGatewayService, McpGatewayServiceLive };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
+
//#region src/runtime/services/opencode-sdk-service.ts
|
|
4
|
+
var OpencodeSdkService = class extends Context.Tag("OpencodeSdkService")() {};
|
|
5
|
+
const OpencodeSdkServiceLive = Layer.succeed(OpencodeSdkService, {
|
|
6
|
+
createClient: (opts) => Effect.try(() => createOpencodeClient(opts)),
|
|
7
|
+
createSession: (client, args) => Effect.tryPromise({
|
|
8
|
+
catch: (error) => error,
|
|
9
|
+
try: () => client.session.create(args)
|
|
10
|
+
}),
|
|
11
|
+
promptSession: (client, args) => Effect.tryPromise({
|
|
12
|
+
catch: (error) => error,
|
|
13
|
+
try: () => client.session.prompt(args)
|
|
14
|
+
}),
|
|
15
|
+
spawnServer: (args, spawn = createOpencode) => Effect.tryPromise({
|
|
16
|
+
catch: (error) => error,
|
|
17
|
+
try: () => spawn(args)
|
|
18
|
+
}),
|
|
19
|
+
subscribeEvents: (client) => Effect.tryPromise({
|
|
20
|
+
catch: (error) => error,
|
|
21
|
+
try: async () => {
|
|
22
|
+
return { stream: (await client.event.subscribe()).stream };
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
//#endregion
|
|
27
|
+
export { OpencodeSdkService, OpencodeSdkServiceLive };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Context, Data, Effect, Layer } from "effect";
|
|
2
|
+
import ky, { isHTTPError } from "ky";
|
|
3
|
+
//#region src/runtime/services/runner-event-sink-http-service.ts
|
|
4
|
+
var EventSinkHttpError = class extends Data.TaggedError("EventSinkHttpError") {
|
|
5
|
+
constructor(status, message) {
|
|
6
|
+
super({
|
|
7
|
+
status,
|
|
8
|
+
message
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const RETRYABLE_STATUS_CODES = [
|
|
13
|
+
408,
|
|
14
|
+
429,
|
|
15
|
+
500,
|
|
16
|
+
501,
|
|
17
|
+
502,
|
|
18
|
+
503,
|
|
19
|
+
504,
|
|
20
|
+
505,
|
|
21
|
+
506,
|
|
22
|
+
507,
|
|
23
|
+
508,
|
|
24
|
+
509,
|
|
25
|
+
510,
|
|
26
|
+
511
|
|
27
|
+
];
|
|
28
|
+
function authHeaderName(request) {
|
|
29
|
+
return request.authHeader ?? "Authorization";
|
|
30
|
+
}
|
|
31
|
+
function httpErrorData(error) {
|
|
32
|
+
return formatHttpErrorData(isHTTPError(error) ? error.data : void 0);
|
|
33
|
+
}
|
|
34
|
+
function formatHttpErrorData(data) {
|
|
35
|
+
if (typeof data === "string") return data;
|
|
36
|
+
return data === void 0 ? "" : JSON.stringify(data);
|
|
37
|
+
}
|
|
38
|
+
function toError(error) {
|
|
39
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
40
|
+
}
|
|
41
|
+
function toHttpError(error) {
|
|
42
|
+
const data = httpErrorData(error);
|
|
43
|
+
const status = isHTTPError(error) ? error.response.status : 0;
|
|
44
|
+
return new EventSinkHttpError(status, `Event sink responded with ${status}${data ? `: ${data}` : ""}`);
|
|
45
|
+
}
|
|
46
|
+
function mapPostError(error) {
|
|
47
|
+
return isHTTPError(error) ? toHttpError(error) : toError(error);
|
|
48
|
+
}
|
|
49
|
+
function kyFetchAdapter(fetchImpl) {
|
|
50
|
+
return async (input, init) => {
|
|
51
|
+
const request = new Request(input, init);
|
|
52
|
+
return fetchImpl(request.url, {
|
|
53
|
+
body: await request.clone().text(),
|
|
54
|
+
headers: request.headers,
|
|
55
|
+
method: request.method,
|
|
56
|
+
signal: request.signal
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function postBatch(request) {
|
|
61
|
+
return Effect.tryPromise({
|
|
62
|
+
catch: mapPostError,
|
|
63
|
+
try: () => ky.post(request.url, {
|
|
64
|
+
fetch: kyFetchAdapter(request.fetch),
|
|
65
|
+
headers: { [authHeaderName(request)]: `Bearer ${request.authToken}` },
|
|
66
|
+
json: { events: request.events },
|
|
67
|
+
retry: {
|
|
68
|
+
delay: () => request.retryDelayMs,
|
|
69
|
+
limit: request.maxRetries,
|
|
70
|
+
methods: ["post"],
|
|
71
|
+
retryOnTimeout: true,
|
|
72
|
+
statusCodes: RETRYABLE_STATUS_CODES
|
|
73
|
+
}
|
|
74
|
+
}).then(() => void 0)
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
var RunnerEventSinkHttpService = class extends Context.Tag("RunnerEventSinkHttpService")() {};
|
|
78
|
+
const RunnerEventSinkHttpServiceLive = Layer.succeed(RunnerEventSinkHttpService, { postBatch });
|
|
79
|
+
//#endregion
|
|
80
|
+
export { RunnerEventSinkHttpService, RunnerEventSinkHttpServiceLive };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { promoteWorktreeChanges } from "../parallel-worktrees/parallel-worktrees.js";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
//#region src/runtime/services/select-candidate-service.ts
|
|
4
|
+
var SelectCandidateService = class extends Context.Tag("SelectCandidateService")() {};
|
|
5
|
+
const SelectCandidateServiceLive = Layer.succeed(SelectCandidateService, {
|
|
6
|
+
executeRunner: (executor, plan, options) => Effect.tryPromise({
|
|
7
|
+
catch: (error) => error,
|
|
8
|
+
try: () => Promise.resolve(executor(plan, options))
|
|
9
|
+
}),
|
|
10
|
+
promoteWinner: (repoRoot, runId, parentNodeId, childNodeId) => Effect.sync(() => promoteWorktreeChanges(repoRoot, runId, parentNodeId, childNodeId))
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
export { SelectCandidateService, SelectCandidateServiceLive };
|
package/package.json
CHANGED
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"prepack": "bun run build:cli"
|
|
127
127
|
},
|
|
128
128
|
"type": "module",
|
|
129
|
-
"version": "2.8.
|
|
129
|
+
"version": "2.8.2",
|
|
130
130
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
131
131
|
"main": "./dist/index.js",
|
|
132
132
|
"types": "./dist/index.d.ts",
|