@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.
Files changed (38) hide show
  1. package/dist/argo-submit.d.ts +2 -4
  2. package/dist/argo-submit.js +80 -80
  3. package/dist/cluster-doctor.js +89 -101
  4. package/dist/config/defaults.js +9 -19
  5. package/dist/config/load.js +32 -39
  6. package/dist/config/schemas.d.ts +1 -1
  7. package/dist/gates.js +6 -225
  8. package/dist/mcp/gateway-error.js +15 -0
  9. package/dist/mcp/gateway.js +119 -220
  10. package/dist/moka-global-config.js +20 -20
  11. package/dist/moka-submit.d.ts +1 -1
  12. package/dist/pipeline-runtime.js +580 -371
  13. package/dist/run-state/git-refs.js +124 -94
  14. package/dist/runner-command-contract.d.ts +2 -2
  15. package/dist/runner-event-sink.js +37 -69
  16. package/dist/runtime/agent-node/agent-node.js +214 -173
  17. package/dist/runtime/builtins/builtins.js +344 -57
  18. package/dist/runtime/changed-files/changed-files.js +15 -27
  19. package/dist/runtime/changed-files/index.js +2 -0
  20. package/dist/runtime/drain-merge/drain-merge.js +124 -82
  21. package/dist/runtime/gates/gates.js +46 -28
  22. package/dist/runtime/hooks/hooks.js +74 -29
  23. package/dist/runtime/opencode-server.js +27 -21
  24. package/dist/runtime/opencode-session-executor.js +101 -44
  25. package/dist/runtime/parallel-node/parallel-node.js +24 -5
  26. package/dist/runtime/select-candidate/select-candidate.js +45 -29
  27. package/dist/runtime/services/agent-node-runtime-service.js +15 -0
  28. package/dist/runtime/services/command-executor-service.js +8 -0
  29. package/dist/runtime/services/config-io-service.js +42 -0
  30. package/dist/runtime/services/drain-merge-git-service.js +10 -0
  31. package/dist/runtime/services/git-porcelain-service.js +38 -0
  32. package/dist/runtime/services/kubernetes-argo-service.d.ts +13 -0
  33. package/dist/runtime/services/kubernetes-argo-service.js +81 -0
  34. package/dist/runtime/services/mcp-gateway-service.js +184 -0
  35. package/dist/runtime/services/opencode-sdk-service.js +27 -0
  36. package/dist/runtime/services/runner-event-sink-http-service.js +80 -0
  37. package/dist/runtime/services/select-candidate-service.js +13 -0
  38. 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.0",
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",