@oisincoveney/pipeline 2.1.0 → 2.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/defaults/pipeline.yaml +111 -0
- package/defaults/profiles.yaml +212 -0
- package/defaults/runners.yaml +24 -0
- package/dist/argo-graph.js +59 -27
- package/dist/argo-submit.js +28 -4
- package/dist/claude-settings-config.js +44 -0
- package/dist/cli/program.js +1 -1
- package/dist/config/defaults.js +7 -353
- package/dist/git-remote-url.js +30 -0
- package/dist/hooks.d.ts +1 -1
- package/dist/install-commands/claude-code.js +153 -17
- package/dist/install-commands/opencode.js +37 -33
- package/dist/install-commands/shared.js +27 -6
- package/dist/install-commands.js +26 -22
- package/dist/json-config-merge.js +47 -0
- package/dist/mcp/gateway.js +9 -1
- package/dist/moka-submit.d.ts +6 -6
- package/dist/moka-submit.js +4 -3
- package/dist/opencode-project-config.js +2 -45
- package/dist/pipeline-runtime.js +50 -8
- package/dist/runner-event-schema.d.ts +349 -0
- package/dist/runner-event-schema.js +185 -0
- package/dist/runner-output.js +2 -2
- package/dist/runner.d.ts +2 -0
- package/dist/runtime/agent-node/agent-node.js +1 -0
- package/dist/runtime/contracts/contracts.d.ts +2 -0
- package/dist/runtime/node-state-store.js +7 -0
- package/dist/runtime/opencode-adapter.js +28 -8
- package/dist/runtime/opencode-agent-name.js +18 -0
- package/dist/runtime/opencode-runtime.js +62 -0
- package/dist/runtime/opencode-server.js +67 -0
- package/dist/runtime/opencode-session-executor.js +206 -0
- package/docs/adr-opencode-first-goal-loop-runtime.md +1 -1
- package/docs/config-architecture.md +11 -6
- package/docs/mcp-gateway.md +4 -4
- package/docs/operator-guide.md +7 -5
- package/docs/slash-command-adapter-contract.md +1 -0
- package/package.json +6 -1
- package/.agents/skills/claude-code-opencode-execute/SKILL.md +0 -116
|
@@ -2,14 +2,15 @@ import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST } from "../config/defaults.js";
|
|
|
2
2
|
import { resolvePackageAssetPath } from "../package-assets.js";
|
|
3
3
|
import "../config.js";
|
|
4
4
|
import { compileWorkflowPlan } from "../workflow-planner.js";
|
|
5
|
+
import { mergeOpenCodeProjectConfig } from "../opencode-project-config.js";
|
|
5
6
|
import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
|
|
6
|
-
import {
|
|
7
|
+
import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
|
|
8
|
+
import { AGENTS_MD_END, AGENTS_MD_START, COMMAND_HOSTS, GENERATED_MARKER, GENERATED_TS_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries } from "./shared.js";
|
|
7
9
|
import { readFileSync } from "node:fs";
|
|
8
10
|
import { basename } from "node:path";
|
|
9
11
|
import matter from "gray-matter";
|
|
10
12
|
//#region src/install-commands/opencode.ts
|
|
11
13
|
const OPENCODE_ORCHESTRATOR_AGENT_ID = "MoKa Orchestrator";
|
|
12
|
-
const MOKA_PROFILE_PREFIX = "moka-";
|
|
13
14
|
function header(host) {
|
|
14
15
|
return [
|
|
15
16
|
GENERATED_MARKER,
|
|
@@ -20,19 +21,6 @@ function header(host) {
|
|
|
20
21
|
function markdown(data, body) {
|
|
21
22
|
return `${matter.stringify(body.trimEnd(), data).trimEnd()}\n`;
|
|
22
23
|
}
|
|
23
|
-
function profileEntries(config) {
|
|
24
|
-
return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b));
|
|
25
|
-
}
|
|
26
|
-
function entrypointEntries(config) {
|
|
27
|
-
const entries = Object.entries(config.entrypoints);
|
|
28
|
-
return entries.length > 0 ? entries : [["execute", {
|
|
29
|
-
description: "Run the configured pipeline workflow",
|
|
30
|
-
workflow: config.default_workflow
|
|
31
|
-
}]];
|
|
32
|
-
}
|
|
33
|
-
function entrypointDescription(id, entrypoint) {
|
|
34
|
-
return entrypoint.description ?? `Run the ${id} workflow`;
|
|
35
|
-
}
|
|
36
24
|
function entrypointCommandDefinitions(_host, config, makeDefinition) {
|
|
37
25
|
return entrypointEntries(config).map(([id, entrypoint]) => makeDefinition(id, entrypoint));
|
|
38
26
|
}
|
|
@@ -104,14 +92,6 @@ function dispatchRouteForAgent(host, config, route) {
|
|
|
104
92
|
function nativeAgentIdForHost(host, profileId) {
|
|
105
93
|
return host === "opencode" ? opencodeAgentName(profileId) : profileId;
|
|
106
94
|
}
|
|
107
|
-
function opencodeAgentName(profileId) {
|
|
108
|
-
if (!profileId.startsWith(MOKA_PROFILE_PREFIX)) return profileId;
|
|
109
|
-
return `MoKa ${profileId.slice(5).split("-").map(opencodeAgentNamePart).join(" ")}`;
|
|
110
|
-
}
|
|
111
|
-
function opencodeAgentNamePart(part) {
|
|
112
|
-
if (part === "opencode") return "OpenCode";
|
|
113
|
-
return `${part.charAt(0).toUpperCase()}${part.slice(1)}`;
|
|
114
|
-
}
|
|
115
95
|
function grants(actor) {
|
|
116
96
|
return [
|
|
117
97
|
`model: ${actor.model ?? "default"}`,
|
|
@@ -223,16 +203,13 @@ function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
|
|
|
223
203
|
}
|
|
224
204
|
function hostDisplayName(host) {
|
|
225
205
|
return {
|
|
226
|
-
|
|
227
|
-
|
|
206
|
+
opencode: "OpenCode",
|
|
207
|
+
"claude-code": "Claude Code"
|
|
228
208
|
}[host];
|
|
229
209
|
}
|
|
230
210
|
function needsSummary(needs) {
|
|
231
211
|
return needs.length > 0 ? needs.join(",") : "none";
|
|
232
212
|
}
|
|
233
|
-
function compactLines(lines) {
|
|
234
|
-
return lines.filter((line) => line !== void 0);
|
|
235
|
-
}
|
|
236
213
|
const OPENCODE_PERMISSION_TOOLS = [
|
|
237
214
|
"bash",
|
|
238
215
|
"edit",
|
|
@@ -426,9 +403,36 @@ function opencodeModelProjection(config, profile) {
|
|
|
426
403
|
const model = resolvedHostModel(config, "opencode", profile);
|
|
427
404
|
return model ? { model } : {};
|
|
428
405
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
406
|
+
/**
|
|
407
|
+
* The opencode HostAdapter. Encapsulates all opencode-specific command
|
|
408
|
+
* generation, resource roots, and config-merge behaviour.
|
|
409
|
+
*/
|
|
410
|
+
const opencodeAdapter = {
|
|
411
|
+
host: "opencode",
|
|
412
|
+
resourceRoots: [
|
|
413
|
+
".opencode/commands",
|
|
414
|
+
".opencode/agents",
|
|
415
|
+
".opencode/plugins",
|
|
416
|
+
".opencode/skills"
|
|
417
|
+
],
|
|
418
|
+
definitions(config, cwd) {
|
|
419
|
+
return opencodeDefinitions(config, cwd);
|
|
420
|
+
},
|
|
421
|
+
mergeDefinition(definition, existingContent) {
|
|
422
|
+
if (definition.path !== ".opencode/opencode.json") return;
|
|
423
|
+
const merged = mergeOpenCodeProjectConfig(existingContent, JSON.parse(definition.content));
|
|
424
|
+
if (!merged.ok) return {
|
|
425
|
+
ok: false,
|
|
426
|
+
content: definition.content
|
|
427
|
+
};
|
|
428
|
+
return {
|
|
429
|
+
ok: true,
|
|
430
|
+
content: merged.content
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
isAlwaysForced(definition) {
|
|
434
|
+
return definition.path === OPENCODE_PROJECT_CONFIG_PATH;
|
|
435
|
+
}
|
|
436
|
+
};
|
|
433
437
|
//#endregion
|
|
434
|
-
export {
|
|
438
|
+
export { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, opencodeAdapter, projectAgentsMdDefinition, resolvedHostModel, scheduledEntrypointK8sNote };
|
|
@@ -9,21 +9,42 @@ const AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
|
|
|
9
9
|
const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
|
|
10
10
|
const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
|
|
11
11
|
const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
|
|
12
|
+
const CLAUDE_PROJECT_CONFIG_PATH = ".claude/settings.json";
|
|
12
13
|
const OPENCODE_COMMAND_PREFIX = "moka-";
|
|
13
14
|
const ENTRYPOINT_PATH_PATTERNS = {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/],
|
|
16
|
+
"claude-code": [/^\.claude\/commands\/(?:moka-)?([^/]+)\.md$/]
|
|
16
17
|
};
|
|
17
18
|
const COMMAND_HOSTS = ["opencode", "claude-code"];
|
|
19
|
+
function profileEntries(config) {
|
|
20
|
+
return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b));
|
|
21
|
+
}
|
|
22
|
+
function entrypointEntries(config) {
|
|
23
|
+
const entries = Object.entries(config.entrypoints);
|
|
24
|
+
return entries.length > 0 ? entries : [["execute", {
|
|
25
|
+
description: "Run the configured pipeline workflow",
|
|
26
|
+
workflow: config.default_workflow
|
|
27
|
+
}]];
|
|
28
|
+
}
|
|
29
|
+
function entrypointDescription(id, entrypoint) {
|
|
30
|
+
return entrypoint.description ?? `Run the ${id} workflow`;
|
|
31
|
+
}
|
|
32
|
+
function instructionsPointer(actor) {
|
|
33
|
+
if (actor.instructions.path) return `Instructions: ${actor.instructions.path}`;
|
|
34
|
+
return `Instructions:\n${actor.instructions.inline ?? ""}`;
|
|
35
|
+
}
|
|
36
|
+
function compactLines(lines) {
|
|
37
|
+
return lines.filter((line) => line !== void 0);
|
|
38
|
+
}
|
|
18
39
|
function invocationForHost(host, entrypointId = "execute") {
|
|
19
40
|
return `${{
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
opencode: "/",
|
|
42
|
+
"claude-code": "/"
|
|
22
43
|
}[host]}${commandIdForHost(host, entrypointId)} <task description>`;
|
|
23
44
|
}
|
|
24
45
|
function commandIdForHost(host, entrypointId) {
|
|
25
|
-
if (host === "opencode") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
|
|
46
|
+
if (host === "opencode" || host === "claude-code") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
|
|
26
47
|
return entrypointId;
|
|
27
48
|
}
|
|
28
49
|
//#endregion
|
|
29
|
-
export { AGENTS_MD_END, AGENTS_MD_START, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, invocationForHost };
|
|
50
|
+
export { AGENTS_MD_END, AGENTS_MD_START, CLAUDE_PROJECT_CONFIG_PATH, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries };
|
package/dist/install-commands.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { loadPipelineConfig } from "./config/load.js";
|
|
2
2
|
import "./config.js";
|
|
3
|
-
import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
|
|
4
3
|
import { COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, invocationForHost } from "./install-commands/shared.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { opencodeAdapter } from "./install-commands/opencode.js";
|
|
5
|
+
import { claudeCodeAdapter } from "./install-commands/claude-code.js";
|
|
7
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
8
7
|
import { dirname, join, relative } from "node:path";
|
|
9
8
|
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
10
9
|
//#region src/install-commands.ts
|
|
10
|
+
const ADAPTERS = {
|
|
11
|
+
opencode: opencodeAdapter,
|
|
12
|
+
"claude-code": claudeCodeAdapter
|
|
13
|
+
};
|
|
11
14
|
function definitionsFor(host, config, cwd) {
|
|
12
|
-
|
|
13
|
-
"claude-code": () => claudeCodeDefinitions(),
|
|
14
|
-
opencode: () => opencodeDefinitions(config, cwd)
|
|
15
|
-
};
|
|
16
|
-
return dedupeDefinitionsByPath((host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => definitions[name]()));
|
|
15
|
+
return dedupeDefinitionsByPath((host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => ADAPTERS[name].definitions(config, cwd)));
|
|
17
16
|
}
|
|
18
17
|
function dedupeDefinitionsByPath(definitions) {
|
|
19
18
|
const lastIndexes = /* @__PURE__ */ new Map();
|
|
@@ -25,15 +24,9 @@ function dedupeDefinitionsByPath(definitions) {
|
|
|
25
24
|
function selectedHosts(host) {
|
|
26
25
|
return host === "all" ? [...COMMAND_HOSTS] : [host];
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
".opencode/commands",
|
|
32
|
-
".opencode/agents",
|
|
33
|
-
".opencode/plugins",
|
|
34
|
-
".opencode/skills"
|
|
35
|
-
]
|
|
36
|
-
};
|
|
27
|
+
function resourceRootsFor(host) {
|
|
28
|
+
return ADAPTERS[host].resourceRoots;
|
|
29
|
+
}
|
|
37
30
|
async function listFiles(root) {
|
|
38
31
|
if (!existsSync(root)) return [];
|
|
39
32
|
if (statSync(root).isFile()) return [root];
|
|
@@ -49,7 +42,7 @@ function generatedHostFor(content) {
|
|
|
49
42
|
}
|
|
50
43
|
async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
|
|
51
44
|
const hosts = new Set(selectedHosts(host));
|
|
52
|
-
const roots = selectedHosts(host).flatMap((selectedHost) =>
|
|
45
|
+
const roots = selectedHosts(host).flatMap((selectedHost) => resourceRootsFor(selectedHost));
|
|
53
46
|
return (await Promise.all(roots.map((root) => listFiles(join(cwd, root))))).flat().flatMap((absolutePath) => {
|
|
54
47
|
const generatedHost = generatedHostFor(readFileSync(absolutePath, "utf8"));
|
|
55
48
|
if (!(generatedHost && hosts.has(generatedHost))) return [];
|
|
@@ -70,12 +63,19 @@ function entrypointIdFromGeneratedPath(host, path) {
|
|
|
70
63
|
}
|
|
71
64
|
}
|
|
72
65
|
function resolveDefinitionContent(definition, target) {
|
|
73
|
-
|
|
66
|
+
const adapter = ADAPTERS[definition.host];
|
|
67
|
+
if (!(adapter.mergeDefinition && existsSync(target))) return {
|
|
68
|
+
conflict: false,
|
|
69
|
+
content: definition.content
|
|
70
|
+
};
|
|
71
|
+
return applyMergeDefinition(adapter.mergeDefinition.bind(adapter), definition, target);
|
|
72
|
+
}
|
|
73
|
+
function applyMergeDefinition(merge, definition, target) {
|
|
74
|
+
const merged = merge(definition, readFileSync(target, "utf8"));
|
|
75
|
+
if (!merged) return {
|
|
74
76
|
conflict: false,
|
|
75
77
|
content: definition.content
|
|
76
78
|
};
|
|
77
|
-
const projection = JSON.parse(definition.content);
|
|
78
|
-
const merged = mergeOpenCodeProjectConfig(readFileSync(target, "utf8"), projection);
|
|
79
79
|
if (!merged.ok) return {
|
|
80
80
|
conflict: true,
|
|
81
81
|
content: definition.content
|
|
@@ -108,9 +108,13 @@ function upsertGeneratedBlock(current, content, block) {
|
|
|
108
108
|
const separator = current.trimEnd().length > 0 ? "\n\n" : "";
|
|
109
109
|
return `${current.trimEnd()}${separator}${content}`;
|
|
110
110
|
}
|
|
111
|
+
function adapterForcesDefinition(definition) {
|
|
112
|
+
const fn = ADAPTERS[definition.host].isAlwaysForced;
|
|
113
|
+
return fn ? fn(definition) : false;
|
|
114
|
+
}
|
|
111
115
|
function installActionForDefinition(definition, target, resolved, force) {
|
|
112
116
|
if (resolved.conflict) return "conflict";
|
|
113
|
-
return actionFor(target, resolved.content, force || definition
|
|
117
|
+
return actionFor(target, resolved.content, force || adapterForcesDefinition(definition), definition.block);
|
|
114
118
|
}
|
|
115
119
|
async function writeDefinition(definition, target, content) {
|
|
116
120
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
2
|
+
//#region src/json-config-merge.ts
|
|
3
|
+
const JSON_FORMAT_OPTIONS = {
|
|
4
|
+
insertSpaces: true,
|
|
5
|
+
tabSize: 2
|
|
6
|
+
};
|
|
7
|
+
function parseJsonRecord(currentText) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
const value = parse(currentText, errors, {
|
|
10
|
+
allowTrailingComma: true,
|
|
11
|
+
disallowComments: false
|
|
12
|
+
});
|
|
13
|
+
if (errors.length > 0 || !isRecord(value)) return {
|
|
14
|
+
errors,
|
|
15
|
+
ok: false
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
ok: true,
|
|
19
|
+
value
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function setIfMissing(content, parsed, path, value) {
|
|
23
|
+
if (value === void 0 || hasPath(parsed, path)) return content;
|
|
24
|
+
return applyJsonEdit(content, path, value);
|
|
25
|
+
}
|
|
26
|
+
function hasPath(value, path) {
|
|
27
|
+
let cursor = value;
|
|
28
|
+
for (const segment of path) {
|
|
29
|
+
if (!(isRecord(cursor) && segment in cursor)) return false;
|
|
30
|
+
cursor = cursor[segment];
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function applyJsonEdit(content, path, value) {
|
|
35
|
+
return applyEdits(content, modify(content, path, value, { formattingOptions: JSON_FORMAT_OPTIONS }));
|
|
36
|
+
}
|
|
37
|
+
function formatJson(value) {
|
|
38
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
39
|
+
}
|
|
40
|
+
function ensureTrailingNewline(value) {
|
|
41
|
+
return value.endsWith("\n") ? value : `${value}\n`;
|
|
42
|
+
}
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { applyJsonEdit, ensureTrailingNewline, formatJson, isRecord, parseJsonRecord, setIfMissing };
|
package/dist/mcp/gateway.js
CHANGED
|
@@ -66,6 +66,14 @@ function renderOpenCodeGatewayConfig(config) {
|
|
|
66
66
|
} }
|
|
67
67
|
}, null, 2)}\n`;
|
|
68
68
|
}
|
|
69
|
+
function renderClaudeGatewayMcpServers(config) {
|
|
70
|
+
const gateway = configuredGateway(config);
|
|
71
|
+
return { [PIPELINE_GATEWAY_SERVER_ID]: {
|
|
72
|
+
headers: gatewayOpenCodeHeaders(gateway),
|
|
73
|
+
type: "http",
|
|
74
|
+
url: gatewayUrl(gateway)
|
|
75
|
+
} };
|
|
76
|
+
}
|
|
69
77
|
function configureGatewayHosts(config, options) {
|
|
70
78
|
return selectedGatewayHosts(options.host).map((host) => {
|
|
71
79
|
const path = gatewayHostConfigPath(options.scope, options.cwd);
|
|
@@ -422,4 +430,4 @@ function legacyContentHit(cwd, path, pattern) {
|
|
|
422
430
|
return pattern.test(readFileSync(fullPath, "utf8")) ? path : void 0;
|
|
423
431
|
}
|
|
424
432
|
//#endregion
|
|
425
|
-
export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, reconcileGateway, renderGatewayConfig, renderOpenCodeGatewayConfig, runGatewayDoctor, startLocalGateway };
|
|
433
|
+
export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, reconcileGateway, renderClaudeGatewayMcpServers, renderGatewayConfig, renderOpenCodeGatewayConfig, runGatewayDoctor, startLocalGateway };
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -5,13 +5,13 @@ import { z } from "zod";
|
|
|
5
5
|
//#region src/moka-submit.d.ts
|
|
6
6
|
declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
|
|
7
7
|
"workflow.start": "workflow.start";
|
|
8
|
+
"node.finish": "node.finish";
|
|
9
|
+
"node.start": "node.start";
|
|
8
10
|
"workflow.success": "workflow.success";
|
|
9
11
|
"workflow.failure": "workflow.failure";
|
|
10
12
|
"workflow.complete": "workflow.complete";
|
|
11
|
-
"node.start": "node.start";
|
|
12
13
|
"node.success": "node.success";
|
|
13
14
|
"node.error": "node.error";
|
|
14
|
-
"node.finish": "node.finish";
|
|
15
15
|
"gate.failure": "gate.failure";
|
|
16
16
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
17
17
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
94
94
|
}, z.core.$strict>>;
|
|
95
95
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
96
96
|
"workflow.start": "workflow.start";
|
|
97
|
+
"node.finish": "node.finish";
|
|
98
|
+
"node.start": "node.start";
|
|
97
99
|
"workflow.success": "workflow.success";
|
|
98
100
|
"workflow.failure": "workflow.failure";
|
|
99
101
|
"workflow.complete": "workflow.complete";
|
|
100
|
-
"node.start": "node.start";
|
|
101
102
|
"node.success": "node.success";
|
|
102
103
|
"node.error": "node.error";
|
|
103
|
-
"node.finish": "node.finish";
|
|
104
104
|
"gate.failure": "gate.failure";
|
|
105
105
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
106
106
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -207,13 +207,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
207
207
|
}, z.core.$strict>>;
|
|
208
208
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
209
209
|
"workflow.start": "workflow.start";
|
|
210
|
+
"node.finish": "node.finish";
|
|
211
|
+
"node.start": "node.start";
|
|
210
212
|
"workflow.success": "workflow.success";
|
|
211
213
|
"workflow.failure": "workflow.failure";
|
|
212
214
|
"workflow.complete": "workflow.complete";
|
|
213
|
-
"node.start": "node.start";
|
|
214
215
|
"node.success": "node.success";
|
|
215
216
|
"node.error": "node.error";
|
|
216
|
-
"node.finish": "node.finish";
|
|
217
217
|
"gate.failure": "gate.failure";
|
|
218
218
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
219
219
|
failure: z.ZodDefault<z.ZodEnum<{
|
package/dist/moka-submit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
|
|
1
2
|
import { buildRunnerCommandPayload, runnerDeliverySchema, runnerHookPolicySchema, runnerRepositoryContextSchema, runnerRunIdentitySchema, runnerTaskSchema } from "./runner-command-contract.js";
|
|
2
3
|
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./schedule/planner.js";
|
|
3
4
|
import "./schedule-planner.js";
|
|
@@ -353,7 +354,7 @@ function explicitSubmissionContext(options) {
|
|
|
353
354
|
if (!(options.repository && options.run)) return null;
|
|
354
355
|
assertRepositoryCredentialConfiguration(options);
|
|
355
356
|
return {
|
|
356
|
-
repository: options.repository,
|
|
357
|
+
repository: normalizeRunnerRepositoryForSubmit(options.repository),
|
|
357
358
|
run: options.run
|
|
358
359
|
};
|
|
359
360
|
}
|
|
@@ -362,11 +363,11 @@ function resolveRequiredGit(options, dependencies) {
|
|
|
362
363
|
return resolveGit(options.worktreePath, dependencies);
|
|
363
364
|
}
|
|
364
365
|
function repositoryContext(options, git) {
|
|
365
|
-
return options.repository ?? {
|
|
366
|
+
return normalizeRunnerRepositoryForSubmit(options.repository ?? {
|
|
366
367
|
baseBranch: git.baseBranch,
|
|
367
368
|
sha: git.sha,
|
|
368
369
|
url: git.url
|
|
369
|
-
};
|
|
370
|
+
});
|
|
370
371
|
}
|
|
371
372
|
function assertRepositoryCredentialConfiguration(options) {
|
|
372
373
|
if (!options.gitCredentialsSecretName) throw new Error("gitCredentialsSecretName is required for runner git clone, fetch, and push operations");
|
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { applyJsonEdit, ensureTrailingNewline, formatJson, parseJsonRecord, setIfMissing } from "./json-config-merge.js";
|
|
2
2
|
//#region src/opencode-project-config.ts
|
|
3
|
-
const JSON_FORMAT_OPTIONS = {
|
|
4
|
-
insertSpaces: true,
|
|
5
|
-
tabSize: 2
|
|
6
|
-
};
|
|
7
3
|
function mergeOpenCodeProjectConfig(currentText, projection) {
|
|
8
4
|
if (currentText === void 0) return {
|
|
9
5
|
content: formatJson(projection),
|
|
10
6
|
ok: true
|
|
11
7
|
};
|
|
12
|
-
const parsed =
|
|
8
|
+
const parsed = parseJsonRecord(currentText);
|
|
13
9
|
if (!parsed.ok) return parsed;
|
|
14
10
|
return {
|
|
15
11
|
content: ensureTrailingNewline(applyOpenCodeProjection(currentText, parsed.value, projection)),
|
|
16
12
|
ok: true
|
|
17
13
|
};
|
|
18
14
|
}
|
|
19
|
-
function parseOpenCodeProjectConfig(currentText) {
|
|
20
|
-
const errors = [];
|
|
21
|
-
const value = parse(currentText, errors, {
|
|
22
|
-
allowTrailingComma: true,
|
|
23
|
-
disallowComments: false
|
|
24
|
-
});
|
|
25
|
-
if (errors.length > 0 || !isRecord(value)) return {
|
|
26
|
-
errors,
|
|
27
|
-
ok: false
|
|
28
|
-
};
|
|
29
|
-
return {
|
|
30
|
-
ok: true,
|
|
31
|
-
value
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
15
|
function applyOpenCodeProjection(currentText, parsed, projection) {
|
|
35
16
|
return applyProviderProjection(applyPluginProjection(applyMcpProjection(setIfMissing(setIfMissing(currentText, parsed, ["$schema"], projection.$schema), parsed, ["lsp"], projection.lsp), parsed, projection), parsed, projection), parsed, projection);
|
|
36
17
|
}
|
|
@@ -49,18 +30,6 @@ function applyPluginProjection(content, parsed, projection) {
|
|
|
49
30
|
const plugins = mergePluginEntries(parsed.plugin, projection.plugin ?? []);
|
|
50
31
|
return plugins.length > 0 ? applyJsonEdit(content, ["plugin"], plugins) : content;
|
|
51
32
|
}
|
|
52
|
-
function setIfMissing(content, parsed, path, value) {
|
|
53
|
-
if (value === void 0 || hasPath(parsed, path)) return content;
|
|
54
|
-
return applyJsonEdit(content, path, value);
|
|
55
|
-
}
|
|
56
|
-
function hasPath(value, path) {
|
|
57
|
-
let cursor = value;
|
|
58
|
-
for (const segment of path) {
|
|
59
|
-
if (!(isRecord(cursor) && segment in cursor)) return false;
|
|
60
|
-
cursor = cursor[segment];
|
|
61
|
-
}
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
33
|
function mergePluginEntries(existing, projected) {
|
|
65
34
|
const projectedByKey = new Map(projected.map((plugin) => [pluginKey(plugin), plugin]));
|
|
66
35
|
const merged = [];
|
|
@@ -86,17 +55,5 @@ function pluginName(specifier) {
|
|
|
86
55
|
const versionSeparator = specifier.indexOf("@", 1);
|
|
87
56
|
return versionSeparator === -1 ? specifier : specifier.slice(0, versionSeparator);
|
|
88
57
|
}
|
|
89
|
-
function applyJsonEdit(content, path, value) {
|
|
90
|
-
return applyEdits(content, modify(content, path, value, { formattingOptions: JSON_FORMAT_OPTIONS }));
|
|
91
|
-
}
|
|
92
|
-
function formatJson(value) {
|
|
93
|
-
return `${JSON.stringify(value, null, 2)}\n`;
|
|
94
|
-
}
|
|
95
|
-
function ensureTrailingNewline(value) {
|
|
96
|
-
return value.endsWith("\n") ? value : `${value}\n`;
|
|
97
|
-
}
|
|
98
|
-
function isRecord(value) {
|
|
99
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
100
|
-
}
|
|
101
58
|
//#endregion
|
|
102
59
|
export { mergeOpenCodeProjectConfig };
|
package/dist/pipeline-runtime.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { loadPipelineConfig } from "./config/load.js";
|
|
2
|
+
import "./config.js";
|
|
1
3
|
import { executeCommand } from "./runtime/command-executor/command-executor.js";
|
|
2
4
|
import "./runtime/command-executor/index.js";
|
|
3
5
|
import { parseJsonObject } from "./runtime/json-validation/json-validation.js";
|
|
@@ -17,24 +19,64 @@ import { diffChangedFiles, snapshotChangedFiles } from "./runtime/changed-files/
|
|
|
17
19
|
import { evaluateNodeGates } from "./runtime/gates/gates.js";
|
|
18
20
|
import "./runtime/gates/index.js";
|
|
19
21
|
import { NodeStateTracker } from "./runtime/node-state-tracker.js";
|
|
22
|
+
import { configUsesOpencode, leaseOpencodeRuntime } from "./runtime/opencode-runtime.js";
|
|
20
23
|
import { executeParallelNode } from "./runtime/parallel-node/parallel-node.js";
|
|
21
24
|
import "./runtime/parallel-node/index.js";
|
|
22
25
|
import { decideNodeRetry, nodeRetryPolicy } from "./runtime/retry.js";
|
|
23
26
|
import { LocalScheduler } from "./runtime/scheduler.js";
|
|
24
27
|
//#region src/pipeline-runtime.ts
|
|
25
28
|
function runPipelineFromConfig(options) {
|
|
26
|
-
return runPipelineWithContext(createRuntimeContext(
|
|
29
|
+
return withOpencodeRuntime(options, (resolved) => runPipelineWithContext(createRuntimeContext(resolved)));
|
|
27
30
|
}
|
|
28
31
|
function runScheduledWorkflowTask(options) {
|
|
29
32
|
const { dependencyOutputs, nodeId, ...runtimeOptions } = options;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
return withOpencodeRuntime(runtimeOptions, (resolved) => {
|
|
34
|
+
const context = createRuntimeContext(resolved);
|
|
35
|
+
hydrateScheduledDependencyStates(context, nodeId);
|
|
36
|
+
hydrateDependencyOutputs(context, dependencyOutputs);
|
|
37
|
+
recordNodeEvent(context, nodeId, {
|
|
38
|
+
at: now(),
|
|
39
|
+
type: "READY"
|
|
40
|
+
});
|
|
41
|
+
return executePlannedNode(nodeId, context);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* When the config uses opencode and the caller did not inject an executor,
|
|
46
|
+
* open one opencode server for the run, drive nodes through the SDK executor,
|
|
47
|
+
* and tear the server down afterward. Command-only configs and callers that
|
|
48
|
+
* supply their own executor (tests, embedders) are passed through untouched.
|
|
49
|
+
*/
|
|
50
|
+
async function withOpencodeRuntime(options, run) {
|
|
51
|
+
if (options.executor) return await run(options);
|
|
52
|
+
const { config, worktreePath } = resolveConfigForRun(options);
|
|
53
|
+
return configUsesOpencode(config) ? await runWithLeasedOpencode(options, config, worktreePath, run) : await run({
|
|
54
|
+
...options,
|
|
55
|
+
config
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function resolveConfigForRun(options) {
|
|
59
|
+
const worktreePath = options.worktreePath ?? process.cwd();
|
|
60
|
+
return {
|
|
61
|
+
config: options.config ?? loadPipelineConfig(worktreePath),
|
|
62
|
+
worktreePath
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function runWithLeasedOpencode(options, config, worktreePath, run) {
|
|
66
|
+
const lease = await leaseOpencodeRuntime({
|
|
67
|
+
config,
|
|
68
|
+
...options.signal ? { signal: options.signal } : {},
|
|
69
|
+
worktreePath
|
|
36
70
|
});
|
|
37
|
-
|
|
71
|
+
try {
|
|
72
|
+
return await run({
|
|
73
|
+
...options,
|
|
74
|
+
config,
|
|
75
|
+
executor: lease.executor
|
|
76
|
+
});
|
|
77
|
+
} finally {
|
|
78
|
+
await lease.release();
|
|
79
|
+
}
|
|
38
80
|
}
|
|
39
81
|
async function runPipelineWithContext(context) {
|
|
40
82
|
return finishRuntime(context, await new LocalScheduler({
|