@oisincoveney/pipeline 2.0.0 → 2.1.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/.agents/skills/claude-code-opencode-execute/SKILL.md +116 -0
- package/defaults/opencode-ecosystem.yaml +51 -1
- package/dist/argo-workflow.js +0 -19
- package/dist/cli/program.d.ts +1 -0
- package/dist/cli/program.js +17 -6
- package/dist/cluster-doctor.js +69 -48
- package/dist/codex-auth-sync.js +1 -1
- package/dist/config/defaults.d.ts +37 -0
- package/dist/config/defaults.js +42 -12
- package/dist/config/schemas.d.ts +5 -5
- package/dist/hooks.d.ts +1 -1
- package/dist/install-commands/claude-code.js +24 -0
- package/dist/install-commands/opencode.js +434 -0
- package/dist/install-commands/shared.js +29 -0
- package/dist/install-commands.js +21 -447
- package/dist/moka-submit.d.ts +1 -1
- package/dist/opencode-project-config.js +23 -5
- package/dist/runner-command-contract.d.ts +2 -2
- package/dist/schedule/backlog-context.js +114 -0
- package/dist/schedule/baseline.js +267 -0
- package/dist/schedule/planner.js +3 -373
- package/package.json +1 -1
package/dist/config/defaults.js
CHANGED
|
@@ -73,6 +73,9 @@ skills:
|
|
|
73
73
|
quick:
|
|
74
74
|
path: .agents/skills/quick/SKILL.md
|
|
75
75
|
source_root: package
|
|
76
|
+
claude-code-opencode-execute:
|
|
77
|
+
path: .agents/skills/claude-code-opencode-execute/SKILL.md
|
|
78
|
+
source_root: package
|
|
76
79
|
critique:
|
|
77
80
|
path: .agents/skills/critique/SKILL.md
|
|
78
81
|
source_root: package
|
|
@@ -141,6 +144,7 @@ profiles:
|
|
|
141
144
|
repair: { enabled: true, max_attempts: 1 }
|
|
142
145
|
moka-inspector:
|
|
143
146
|
runner: opencode
|
|
147
|
+
model: openai/gpt-5.5-low
|
|
144
148
|
description: Inspect the repository without modifying files.
|
|
145
149
|
instructions: { inline: "Inspect the repository without modifying files." }
|
|
146
150
|
skills: [research]
|
|
@@ -150,6 +154,7 @@ profiles:
|
|
|
150
154
|
network: { mode: inherit }
|
|
151
155
|
moka-schedule-planner:
|
|
152
156
|
runner: opencode
|
|
157
|
+
model: openai/gpt-5.5-xhigh
|
|
153
158
|
description: Refine a baseline schedule into a specialized approved-plan artifact.
|
|
154
159
|
instructions: { inline: "Generate exactly one workflow named root as an explicit schedule graph. Return YAML only." }
|
|
155
160
|
timeout_ms: 300000
|
|
@@ -230,6 +235,7 @@ profiles:
|
|
|
230
235
|
repair: { enabled: true, max_attempts: 1 }
|
|
231
236
|
moka-learner:
|
|
232
237
|
runner: opencode
|
|
238
|
+
model: openai/gpt-5.5-low
|
|
233
239
|
description: Store durable lessons from the completed run.
|
|
234
240
|
instructions: { inline: "Store durable lessons from the completed run when useful." }
|
|
235
241
|
skills: [migrate]
|
|
@@ -289,54 +295,54 @@ scheduler:
|
|
|
289
295
|
backlog-intake:
|
|
290
296
|
category: intake
|
|
291
297
|
profile: moka-researcher
|
|
292
|
-
models: [openai/gpt-5.5-
|
|
298
|
+
models: [openai/gpt-5.5-medium]
|
|
293
299
|
red-tests:
|
|
294
300
|
category: red
|
|
295
301
|
profile: moka-test-writer
|
|
296
|
-
models: [openai/gpt-5.5, kimi-for-coding/kimi-k2-thinking]
|
|
302
|
+
models: [openai/gpt-5.5-high, kimi-for-coding/kimi-k2-thinking]
|
|
297
303
|
green-implementation:
|
|
298
304
|
category: green
|
|
299
305
|
profile: moka-code-writer
|
|
300
|
-
models: [openai/gpt-5.5, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
|
|
306
|
+
models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
|
|
301
307
|
verification:
|
|
302
308
|
category: verification
|
|
303
309
|
profile: moka-verifier
|
|
304
|
-
models: [openai/gpt-5.5]
|
|
310
|
+
models: [openai/gpt-5.5-medium]
|
|
305
311
|
execute:
|
|
306
312
|
required_categories: [intake, research, red, green, mechanical, acceptance, verification, learn]
|
|
307
313
|
nodes:
|
|
308
314
|
backlog-intake:
|
|
309
315
|
category: intake
|
|
310
316
|
profile: moka-researcher
|
|
311
|
-
models: [openai/gpt-5.5-
|
|
317
|
+
models: [openai/gpt-5.5-medium]
|
|
312
318
|
research:
|
|
313
319
|
category: research
|
|
314
320
|
profile: moka-researcher
|
|
315
|
-
models: [openai/gpt-5.5-
|
|
321
|
+
models: [openai/gpt-5.5-medium, kimi-for-coding/k2p6]
|
|
316
322
|
red-tests:
|
|
317
323
|
category: red
|
|
318
324
|
profile: moka-test-writer
|
|
319
|
-
models: [openai/gpt-5.5, kimi-for-coding/kimi-k2-thinking]
|
|
325
|
+
models: [openai/gpt-5.5-high, kimi-for-coding/kimi-k2-thinking]
|
|
320
326
|
green-backend:
|
|
321
327
|
category: green
|
|
322
328
|
profile: moka-code-writer
|
|
323
|
-
models: [openai/gpt-5.5, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
|
|
329
|
+
models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
|
|
324
330
|
green-frontend:
|
|
325
331
|
category: green
|
|
326
332
|
profile: moka-code-writer
|
|
327
|
-
models: [openai/gpt-5.5, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
|
|
333
|
+
models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
|
|
328
334
|
acceptance-review:
|
|
329
335
|
category: acceptance
|
|
330
336
|
profile: moka-acceptance-reviewer
|
|
331
|
-
models: [openai/gpt-5.5]
|
|
337
|
+
models: [openai/gpt-5.5-medium]
|
|
332
338
|
verification:
|
|
333
339
|
category: verification
|
|
334
340
|
profile: moka-verifier
|
|
335
|
-
models: [openai/gpt-5.5]
|
|
341
|
+
models: [openai/gpt-5.5-medium]
|
|
336
342
|
learn:
|
|
337
343
|
category: learn
|
|
338
344
|
profile: moka-learner
|
|
339
|
-
models: [openai/gpt-5.5-
|
|
345
|
+
models: [openai/gpt-5.5-low]
|
|
340
346
|
schedules:
|
|
341
347
|
quick-schedule:
|
|
342
348
|
baseline: quick
|
|
@@ -385,6 +391,29 @@ const ecosystemCodeSchema = z.object({
|
|
|
385
391
|
role: z.string().min(1),
|
|
386
392
|
source: z.string().url()
|
|
387
393
|
}).strict();
|
|
394
|
+
const ecosystemProviderModelOptionsSchema = z.object({
|
|
395
|
+
include: ecosystemStringArraySchema,
|
|
396
|
+
reasoningEffort: z.enum([
|
|
397
|
+
"none",
|
|
398
|
+
"low",
|
|
399
|
+
"medium",
|
|
400
|
+
"high",
|
|
401
|
+
"xhigh"
|
|
402
|
+
]),
|
|
403
|
+
reasoningSummary: z.enum(["auto", "detailed"]),
|
|
404
|
+
store: z.literal(false),
|
|
405
|
+
textVerbosity: z.enum([
|
|
406
|
+
"low",
|
|
407
|
+
"medium",
|
|
408
|
+
"high"
|
|
409
|
+
])
|
|
410
|
+
}).strict();
|
|
411
|
+
const ecosystemProviderModelSchema = z.object({
|
|
412
|
+
id: z.string().min(1),
|
|
413
|
+
options: ecosystemProviderModelOptionsSchema,
|
|
414
|
+
provider: z.string().min(1),
|
|
415
|
+
role: z.string().min(1)
|
|
416
|
+
}).strict();
|
|
388
417
|
const ecosystemMcpBackendSchema = z.object({
|
|
389
418
|
credentials: ecosystemStringArraySchema,
|
|
390
419
|
id: z.string().min(1),
|
|
@@ -421,6 +450,7 @@ const openCodeEcosystemManifestSchema = z.object({
|
|
|
421
450
|
mcp_backends: z.array(ecosystemMcpBackendSchema).min(1),
|
|
422
451
|
official_dependencies: z.array(ecosystemDependencySchema).min(1),
|
|
423
452
|
prompts: z.array(ecosystemProfileResourceSchema).min(1),
|
|
453
|
+
provider_models: z.array(ecosystemProviderModelSchema).min(1),
|
|
424
454
|
runtime: ecosystemRuntimeSchema,
|
|
425
455
|
skills: z.array(ecosystemProfileResourceSchema).min(1),
|
|
426
456
|
sources: z.array(ecosystemSourceSchema).min(1),
|
package/dist/config/schemas.d.ts
CHANGED
|
@@ -310,6 +310,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
310
310
|
skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
311
311
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
312
312
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
313
|
+
task: "task";
|
|
313
314
|
read: "read";
|
|
314
315
|
list: "list";
|
|
315
316
|
grep: "grep";
|
|
@@ -317,7 +318,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
317
318
|
bash: "bash";
|
|
318
319
|
edit: "edit";
|
|
319
320
|
write: "write";
|
|
320
|
-
task: "task";
|
|
321
321
|
}>>>;
|
|
322
322
|
}, z.core.$strict>>>;
|
|
323
323
|
runner_command: z.ZodDefault<z.ZodObject<{
|
|
@@ -343,8 +343,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
343
343
|
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
344
344
|
path: z.ZodString;
|
|
345
345
|
source_root: z.ZodDefault<z.ZodEnum<{
|
|
346
|
-
package: "package";
|
|
347
346
|
project: "project";
|
|
347
|
+
package: "package";
|
|
348
348
|
}>>;
|
|
349
349
|
}, z.core.$strict>>>;
|
|
350
350
|
runners: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -369,6 +369,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
369
369
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
370
370
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
371
371
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
372
|
+
task: "task";
|
|
372
373
|
read: "read";
|
|
373
374
|
list: "list";
|
|
374
375
|
grep: "grep";
|
|
@@ -376,7 +377,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
376
377
|
bash: "bash";
|
|
377
378
|
edit: "edit";
|
|
378
379
|
write: "write";
|
|
379
|
-
task: "task";
|
|
380
380
|
}>>>;
|
|
381
381
|
}, z.core.$strict>;
|
|
382
382
|
command: z.ZodOptional<z.ZodString>;
|
|
@@ -471,8 +471,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
471
471
|
schedules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
472
472
|
description: z.ZodOptional<z.ZodString>;
|
|
473
473
|
baseline: z.ZodEnum<{
|
|
474
|
-
execute: "execute";
|
|
475
474
|
quick: "quick";
|
|
475
|
+
execute: "execute";
|
|
476
476
|
}>;
|
|
477
477
|
max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
|
|
478
478
|
node_catalog: z.ZodOptional<z.ZodString>;
|
|
@@ -484,8 +484,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
484
484
|
skills: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
485
485
|
path: z.ZodString;
|
|
486
486
|
source_root: z.ZodDefault<z.ZodEnum<{
|
|
487
|
-
package: "package";
|
|
488
487
|
project: "project";
|
|
488
|
+
package: "package";
|
|
489
489
|
}>>;
|
|
490
490
|
}, z.core.$strict>>>;
|
|
491
491
|
task_context: z.ZodOptional<z.ZodObject<{
|
package/dist/hooks.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ declare const hookResultSchema: z.ZodObject<{
|
|
|
13
13
|
taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
14
|
}, z.core.$strict>>;
|
|
15
15
|
status: z.ZodEnum<{
|
|
16
|
-
fail: "fail";
|
|
17
16
|
pass: "pass";
|
|
17
|
+
fail: "fail";
|
|
18
18
|
skip: "skip";
|
|
19
19
|
}>;
|
|
20
20
|
summary: z.ZodOptional<z.ZodString>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { resolvePackageAssetPath } from "../package-assets.js";
|
|
2
|
+
import { GENERATED_MARKER, OWNER_MARKER_PREFIX, invocationForHost } from "./shared.js";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
//#region src/install-commands/claude-code.ts
|
|
5
|
+
const CLAUDE_CODE_OPENCODE_EXECUTE_SKILL = "claude-code-opencode-execute";
|
|
6
|
+
const PACKAGE_SKILL_PATH = `.agents/skills/${CLAUDE_CODE_OPENCODE_EXECUTE_SKILL}/SKILL.md`;
|
|
7
|
+
function generatedClaudeSkillContent() {
|
|
8
|
+
return readFileSync(resolvePackageAssetPath(PACKAGE_SKILL_PATH), "utf8").replace("# Claude Code OpenCode Execute", [
|
|
9
|
+
GENERATED_MARKER,
|
|
10
|
+
`${OWNER_MARKER_PREFIX}host=claude-code -->`,
|
|
11
|
+
"",
|
|
12
|
+
"# Claude Code OpenCode Execute"
|
|
13
|
+
].join("\n"));
|
|
14
|
+
}
|
|
15
|
+
function claudeCodeDefinitions() {
|
|
16
|
+
return [{
|
|
17
|
+
content: generatedClaudeSkillContent(),
|
|
18
|
+
host: "claude-code",
|
|
19
|
+
invocation: invocationForHost("claude-code", CLAUDE_CODE_OPENCODE_EXECUTE_SKILL),
|
|
20
|
+
path: `.claude/skills/${CLAUDE_CODE_OPENCODE_EXECUTE_SKILL}/SKILL.md`
|
|
21
|
+
}];
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { claudeCodeDefinitions };
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST } from "../config/defaults.js";
|
|
2
|
+
import { resolvePackageAssetPath } from "../package-assets.js";
|
|
3
|
+
import "../config.js";
|
|
4
|
+
import { compileWorkflowPlan } from "../workflow-planner.js";
|
|
5
|
+
import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
|
|
6
|
+
import { AGENTS_MD_END, AGENTS_MD_START, COMMAND_HOSTS, GENERATED_MARKER, GENERATED_TS_MARKER, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, invocationForHost } from "./shared.js";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { basename } from "node:path";
|
|
9
|
+
import matter from "gray-matter";
|
|
10
|
+
//#region src/install-commands/opencode.ts
|
|
11
|
+
const OPENCODE_ORCHESTRATOR_AGENT_ID = "MoKa Orchestrator";
|
|
12
|
+
const MOKA_PROFILE_PREFIX = "moka-";
|
|
13
|
+
function header(host) {
|
|
14
|
+
return [
|
|
15
|
+
GENERATED_MARKER,
|
|
16
|
+
`${OWNER_MARKER_PREFIX}host=${host} -->`,
|
|
17
|
+
""
|
|
18
|
+
].join("\n");
|
|
19
|
+
}
|
|
20
|
+
function markdown(data, body) {
|
|
21
|
+
return `${matter.stringify(body.trimEnd(), data).trimEnd()}\n`;
|
|
22
|
+
}
|
|
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
|
+
function entrypointCommandDefinitions(_host, config, makeDefinition) {
|
|
37
|
+
return entrypointEntries(config).map(([id, entrypoint]) => makeDefinition(id, entrypoint));
|
|
38
|
+
}
|
|
39
|
+
function nativeProfileEntries(host, config) {
|
|
40
|
+
return profileEntries(config).filter(([id, profile]) => id !== config.orchestrator?.profile && canRunNatively(host, profile));
|
|
41
|
+
}
|
|
42
|
+
function orchestratorProfile(config) {
|
|
43
|
+
if (!config.orchestrator) return;
|
|
44
|
+
const profile = config.profiles[config.orchestrator.profile];
|
|
45
|
+
if (!profile) throw new Error(`Orchestrator profile '${config.orchestrator.profile}' is not declared.`);
|
|
46
|
+
return { ...profile };
|
|
47
|
+
}
|
|
48
|
+
function resolvedHostModel(config, host, profile) {
|
|
49
|
+
const runner = config.runners[profile.runner];
|
|
50
|
+
const hostRunner = config.runners[host];
|
|
51
|
+
if (profile.host_models?.[host]) return profile.host_models[host];
|
|
52
|
+
if (runner?.host_models?.[host]) return runner.host_models[host];
|
|
53
|
+
if (profile.runner === host) return profile.model ?? runner?.model;
|
|
54
|
+
return hostRunner?.model;
|
|
55
|
+
}
|
|
56
|
+
function canRunNatively(host, profile) {
|
|
57
|
+
if (profile.runner === host) return true;
|
|
58
|
+
return host === "opencode" && isModelRunner(profile.runner);
|
|
59
|
+
}
|
|
60
|
+
function isModelRunner(runnerId) {
|
|
61
|
+
return COMMAND_HOSTS.some((host) => host === runnerId);
|
|
62
|
+
}
|
|
63
|
+
function agentDispatchRoutes(host, config, workflowId = config.default_workflow) {
|
|
64
|
+
return compileWorkflowPlan(config, workflowId).topologicalOrder.flatMap((node) => {
|
|
65
|
+
if (!(node.kind === "agent" && node.profile)) return [];
|
|
66
|
+
const profile = config.profiles[node.profile];
|
|
67
|
+
if (!profile) return [];
|
|
68
|
+
return [dispatchRouteForAgent(host, config, {
|
|
69
|
+
needs: node.needs,
|
|
70
|
+
nodeId: node.id,
|
|
71
|
+
profile,
|
|
72
|
+
profileId: node.profile
|
|
73
|
+
})];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function dispatchRouteForAgent(host, config, route) {
|
|
77
|
+
const runnerId = route.profile.runner;
|
|
78
|
+
if (runnerId === host) {
|
|
79
|
+
const model = resolvedHostModel(config, host, route.profile);
|
|
80
|
+
return {
|
|
81
|
+
...route,
|
|
82
|
+
kind: "native-named-agent",
|
|
83
|
+
...model ? { model } : {},
|
|
84
|
+
nativeAgentId: nativeAgentIdForHost(host, route.profileId),
|
|
85
|
+
runnerId
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (host === "opencode" && isModelRunner(runnerId)) {
|
|
89
|
+
const model = resolvedHostModel(config, host, route.profile);
|
|
90
|
+
return {
|
|
91
|
+
...route,
|
|
92
|
+
kind: "native-model-agent",
|
|
93
|
+
...model ? { model } : {},
|
|
94
|
+
nativeAgentId: nativeAgentIdForHost(host, route.profileId),
|
|
95
|
+
runnerId
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...route,
|
|
100
|
+
kind: "cli",
|
|
101
|
+
runnerId
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function nativeAgentIdForHost(host, profileId) {
|
|
105
|
+
return host === "opencode" ? opencodeAgentName(profileId) : profileId;
|
|
106
|
+
}
|
|
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
|
+
function grants(actor) {
|
|
116
|
+
return [
|
|
117
|
+
`model: ${actor.model ?? "default"}`,
|
|
118
|
+
`tools: ${(actor.tools ?? []).join(", ") || "none"}`,
|
|
119
|
+
`rules: ${(actor.rules ?? []).join(", ") || "none"}`,
|
|
120
|
+
`skills: ${(actor.skills ?? []).join(", ") || "none"}`,
|
|
121
|
+
`mcp_servers: ${(actor.mcp_servers ?? []).join(", ") || "none"}`,
|
|
122
|
+
`filesystem: ${actor.filesystem?.mode ?? "default"}`,
|
|
123
|
+
`network: ${actor.network?.mode ?? "default"}`,
|
|
124
|
+
..."output" in actor ? [`output: ${actor.output?.format ?? "text"}`] : []
|
|
125
|
+
].join("\n");
|
|
126
|
+
}
|
|
127
|
+
function orchestratorBlock(config) {
|
|
128
|
+
const profile = orchestratorProfile(config);
|
|
129
|
+
if (!profile) return "Configured orchestrator: none";
|
|
130
|
+
return [
|
|
131
|
+
"Configured orchestrator:",
|
|
132
|
+
grants(profile),
|
|
133
|
+
`hooks: ${Object.keys(config.hooks.functions).join(", ") || "none"}`,
|
|
134
|
+
"",
|
|
135
|
+
instructionsPointer(profile)
|
|
136
|
+
].join("\n");
|
|
137
|
+
}
|
|
138
|
+
function dispatchBlock(host, config, workflowId = config.default_workflow) {
|
|
139
|
+
const routes = agentDispatchRoutes(host, config, workflowId);
|
|
140
|
+
if (routes.length === 0) return;
|
|
141
|
+
const plan = compileWorkflowPlan(config, workflowId);
|
|
142
|
+
const nativeRoutes = routes.filter((route) => route.kind !== "cli");
|
|
143
|
+
const cliRoutes = routes.filter((route) => route.kind === "cli");
|
|
144
|
+
return [
|
|
145
|
+
`Run workflow \`${plan.workflowId}\` for the user task.`,
|
|
146
|
+
"",
|
|
147
|
+
nativeDispatchBlock(host, nativeRoutes),
|
|
148
|
+
cliDispatchBlock(host, cliRoutes),
|
|
149
|
+
nodePromptContract(plan.workflowId, routes),
|
|
150
|
+
"Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
|
|
151
|
+
"If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
|
|
152
|
+
"Do not bypass configured runner subprocesses or package-configured gates when executing nodes.",
|
|
153
|
+
"Use the listed Task tool routes for native nodes, and run nodes with satisfied dependencies in parallel whenever the host supports concurrent subagent work.",
|
|
154
|
+
hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes)
|
|
155
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
156
|
+
}
|
|
157
|
+
function entrypointDispatchBlock(host, config, id, entrypoint) {
|
|
158
|
+
if ("workflow" in entrypoint) return dispatchBlock(host, config, entrypoint.workflow);
|
|
159
|
+
return [
|
|
160
|
+
`Generate a schedule for entrypoint \`${id}\` and the user task.`,
|
|
161
|
+
`The schedule policy is \`${entrypoint.schedule}\`.`,
|
|
162
|
+
id === "quick" ? "Run `moka submit --quick <task description>` to submit the graph as an Argo Workflow." : `Run \`moka submit <task description>\` to submit the \`${id}\` graph as an Argo Workflow.`,
|
|
163
|
+
"The pipeline runtime executes as Argo DAG tasks using the package-owned runner image.",
|
|
164
|
+
"Configure the target in `~/.config/moka/config.yaml`; use `--kubeconfig <path>` and `--namespace <namespace>` only for explicit command overrides.",
|
|
165
|
+
"Use `moka submit --schedule <schedule.yaml> <task description>` only when rerunning an existing schedule artifact."
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
function scheduledEntrypointK8sNote(entrypoint) {
|
|
169
|
+
if ("workflow" in entrypoint) return;
|
|
170
|
+
return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
|
|
171
|
+
}
|
|
172
|
+
function orchestratorEntrypointDispatchBlock(host, config) {
|
|
173
|
+
const scheduledEntrypoints = entrypointEntries(config).filter(([, entrypoint]) => !("workflow" in entrypoint));
|
|
174
|
+
if (scheduledEntrypoints.length === 0) return dispatchBlock(host, config);
|
|
175
|
+
return scheduledEntrypoints.map(([id, entrypoint]) => entrypointDispatchBlock(host, config, id, entrypoint)).filter((block) => Boolean(block)).join("\n\n");
|
|
176
|
+
}
|
|
177
|
+
function nativeDispatchBlock(host, routes) {
|
|
178
|
+
if (routes.length === 0) return;
|
|
179
|
+
return [
|
|
180
|
+
`${hostDisplayName(host)} native routes:`,
|
|
181
|
+
...routes.map(nativeDispatchLine),
|
|
182
|
+
""
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
function nativeDispatchLine(route) {
|
|
186
|
+
const needs = needsSummary(route.needs);
|
|
187
|
+
const model = route.model ? ` model=${route.model}` : "";
|
|
188
|
+
return `- ${route.nodeId}: Task tool subagent_type=${route.nativeAgentId}${model} runner=${route.runnerId} needs=${needs}`;
|
|
189
|
+
}
|
|
190
|
+
function cliDispatchBlock(host, routes) {
|
|
191
|
+
if (routes.length === 0) return;
|
|
192
|
+
return [
|
|
193
|
+
`These nodes are not ${hostDisplayName(host)} native routes.`,
|
|
194
|
+
"CLI routes:",
|
|
195
|
+
...routes.map(cliDispatchLine),
|
|
196
|
+
""
|
|
197
|
+
].join("\n");
|
|
198
|
+
}
|
|
199
|
+
function cliDispatchLine(route) {
|
|
200
|
+
return `- ${route.nodeId}: ${route.runnerId} CLI profile=${route.profileId} command=\`${runnerCliCommand(route)}\` needs=${needsSummary(route.needs)}`;
|
|
201
|
+
}
|
|
202
|
+
function runnerCliCommand(route) {
|
|
203
|
+
if (route.runnerId === "opencode") return `opencode run --agent "${opencodeAgentName(route.profileId)}" --format json --dir <repo-root> <node prompt>`;
|
|
204
|
+
throw new Error(`runner '${route.runnerId}' cannot be represented as a supported native or CLI route`);
|
|
205
|
+
}
|
|
206
|
+
function nodePromptContract(workflowId, routes) {
|
|
207
|
+
return [
|
|
208
|
+
routes.some((route) => route.kind === "cli") ? "For each CLI node prompt include:" : "For each native node prompt include:",
|
|
209
|
+
"- user task",
|
|
210
|
+
`- workflow id: ${workflowId}`,
|
|
211
|
+
"- node id",
|
|
212
|
+
"- profile id",
|
|
213
|
+
"- runner id",
|
|
214
|
+
"- profile instructions reference",
|
|
215
|
+
"- profile grants",
|
|
216
|
+
"- dependency outputs",
|
|
217
|
+
""
|
|
218
|
+
].join("\n");
|
|
219
|
+
}
|
|
220
|
+
function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
|
|
221
|
+
if (cliRoutes.length > 0 && nativeRoutes.length > 0) return `Do not claim CLI routes are ${hostDisplayName(host)} native routes.`;
|
|
222
|
+
if (cliRoutes.length > 0 && nativeRoutes.length === 0) return `Do not claim these nodes are ${hostDisplayName(host)} subagents.`;
|
|
223
|
+
}
|
|
224
|
+
function hostDisplayName(host) {
|
|
225
|
+
return {
|
|
226
|
+
"claude-code": "Claude Code",
|
|
227
|
+
opencode: "OpenCode"
|
|
228
|
+
}[host];
|
|
229
|
+
}
|
|
230
|
+
function needsSummary(needs) {
|
|
231
|
+
return needs.length > 0 ? needs.join(",") : "none";
|
|
232
|
+
}
|
|
233
|
+
function compactLines(lines) {
|
|
234
|
+
return lines.filter((line) => line !== void 0);
|
|
235
|
+
}
|
|
236
|
+
const OPENCODE_PERMISSION_TOOLS = [
|
|
237
|
+
"bash",
|
|
238
|
+
"edit",
|
|
239
|
+
"glob",
|
|
240
|
+
"grep",
|
|
241
|
+
"list",
|
|
242
|
+
"read",
|
|
243
|
+
"write"
|
|
244
|
+
];
|
|
245
|
+
function opencodePermission(actor, options = {}) {
|
|
246
|
+
const allowed = new Set(actor.tools ?? []);
|
|
247
|
+
return {
|
|
248
|
+
...opencodeToolPermissions(allowed),
|
|
249
|
+
external_directory: "deny",
|
|
250
|
+
lsp: "allow",
|
|
251
|
+
skill: opencodeSkillPermission(actor.skills ?? []),
|
|
252
|
+
task: opencodeTaskPermission(allowed, options.allowedTaskAgents ?? [])
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function opencodeToolPermissions(allowed) {
|
|
256
|
+
return Object.fromEntries(OPENCODE_PERMISSION_TOOLS.map((tool) => [tool, allowed.has(tool) ? "allow" : "deny"]));
|
|
257
|
+
}
|
|
258
|
+
function opencodeSkillPermission(skills) {
|
|
259
|
+
return namedOpencodePermissionMap(skills);
|
|
260
|
+
}
|
|
261
|
+
function opencodeTaskPermission(allowed, allowedTaskAgents) {
|
|
262
|
+
return allowedTaskAgents.length > 0 ? namedOpencodePermissionMap(allowedTaskAgents) : toolPermission(allowed, "task");
|
|
263
|
+
}
|
|
264
|
+
function namedOpencodePermissionMap(names) {
|
|
265
|
+
return names.length > 0 ? {
|
|
266
|
+
"*": "deny",
|
|
267
|
+
...Object.fromEntries(names.map((name) => [name, "allow"]))
|
|
268
|
+
} : "deny";
|
|
269
|
+
}
|
|
270
|
+
function toolPermission(allowed, tool) {
|
|
271
|
+
return allowed.has(tool) ? "allow" : "deny";
|
|
272
|
+
}
|
|
273
|
+
function renderOpenCodeProjectConfig(config) {
|
|
274
|
+
return formatOpenCodeProjectJson({
|
|
275
|
+
...config.mcp_gateway ? JSON.parse(renderOpenCodeGatewayConfig(config)) : { $schema: "https://opencode.ai/config.json" },
|
|
276
|
+
lsp: true,
|
|
277
|
+
...opencodePluginConfig(),
|
|
278
|
+
...opencodeProviderConfig()
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function opencodeProviderConfig() {
|
|
282
|
+
const provider = {};
|
|
283
|
+
for (const model of DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.provider_models) {
|
|
284
|
+
provider[model.provider] ??= { models: {} };
|
|
285
|
+
provider[model.provider].models[model.id] = { options: model.options };
|
|
286
|
+
}
|
|
287
|
+
return Object.keys(provider).length > 0 ? { provider } : {};
|
|
288
|
+
}
|
|
289
|
+
function opencodePluginConfig() {
|
|
290
|
+
const plugins = DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => npmPluginPackage(item)).sort((a, b) => a.localeCompare(b));
|
|
291
|
+
return plugins.length > 0 ? { plugin: plugins } : {};
|
|
292
|
+
}
|
|
293
|
+
function npmPluginPackage(item) {
|
|
294
|
+
if (item.plugin?.kind === "npm") return [item.plugin.package];
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
function formatOpenCodeProjectJson(value) {
|
|
298
|
+
return `${JSON.stringify(value, null, 2).replace(SINGLE_OPENCODE_PLUGIN_ARRAY_RE, "\n \"plugin\": [$1]")}\n`;
|
|
299
|
+
}
|
|
300
|
+
function localPluginDefinitions() {
|
|
301
|
+
return DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => {
|
|
302
|
+
if (item.plugin?.kind !== "local") return [];
|
|
303
|
+
const plugin = readFileSync(resolvePackageAssetPath(item.plugin.source_path), "utf8").trimEnd();
|
|
304
|
+
return [{
|
|
305
|
+
content: [
|
|
306
|
+
GENERATED_TS_MARKER,
|
|
307
|
+
`${OWNER_TS_MARKER_PREFIX}host=opencode`,
|
|
308
|
+
"",
|
|
309
|
+
plugin,
|
|
310
|
+
""
|
|
311
|
+
].join("\n"),
|
|
312
|
+
host: "opencode",
|
|
313
|
+
invocation: invocationForHost("opencode"),
|
|
314
|
+
path: item.plugin.target_path
|
|
315
|
+
}];
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function opencodeDefinitions(config, cwd) {
|
|
319
|
+
const orchestrator = orchestratorProfile(config);
|
|
320
|
+
return [
|
|
321
|
+
...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
|
|
322
|
+
content: markdown({
|
|
323
|
+
...orchestrator ? { agent: OPENCODE_ORCHESTRATOR_AGENT_ID } : {},
|
|
324
|
+
description: entrypointDescription(id, entrypoint)
|
|
325
|
+
}, compactLines([
|
|
326
|
+
header("opencode").trimEnd(),
|
|
327
|
+
"",
|
|
328
|
+
`Invoke this command with \`${invocationForHost("opencode", id)}\`.`,
|
|
329
|
+
"",
|
|
330
|
+
orchestratorBlock(config),
|
|
331
|
+
"",
|
|
332
|
+
scheduledEntrypointK8sNote(entrypoint),
|
|
333
|
+
scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
|
|
334
|
+
entrypointDispatchBlock("opencode", config, id, entrypoint)
|
|
335
|
+
]).join("\n")),
|
|
336
|
+
host: "opencode",
|
|
337
|
+
invocation: invocationForHost("opencode", id),
|
|
338
|
+
path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
|
|
339
|
+
})),
|
|
340
|
+
{
|
|
341
|
+
content: renderOpenCodeProjectConfig(config),
|
|
342
|
+
host: "opencode",
|
|
343
|
+
invocation: invocationForHost("opencode"),
|
|
344
|
+
path: ".opencode/opencode.json"
|
|
345
|
+
},
|
|
346
|
+
...orchestrator ? [{
|
|
347
|
+
content: markdown({
|
|
348
|
+
description: "Orchestrate the configured pipeline and enforce gates.",
|
|
349
|
+
mode: "primary",
|
|
350
|
+
name: OPENCODE_ORCHESTRATOR_AGENT_ID,
|
|
351
|
+
permission: opencodePermission(orchestrator, { allowedTaskAgents: agentDispatchRoutes("opencode", config).filter((route) => route.kind !== "cli").map((route) => route.nativeAgentId).filter((id) => Boolean(id)) })
|
|
352
|
+
}, compactLines([
|
|
353
|
+
header("opencode").trimEnd(),
|
|
354
|
+
"",
|
|
355
|
+
orchestratorBlock(config),
|
|
356
|
+
"",
|
|
357
|
+
orchestratorEntrypointDispatchBlock("opencode", config)
|
|
358
|
+
]).join("\n")),
|
|
359
|
+
host: "opencode",
|
|
360
|
+
invocation: invocationForHost("opencode"),
|
|
361
|
+
path: `.opencode/agents/${OPENCODE_ORCHESTRATOR_AGENT_ID}.md`
|
|
362
|
+
}] : [],
|
|
363
|
+
...nativeProfileEntries("opencode", config).map(([id, profile]) => ({
|
|
364
|
+
content: markdown({
|
|
365
|
+
name: nativeAgentIdForHost("opencode", id),
|
|
366
|
+
description: profile.description ?? id,
|
|
367
|
+
hidden: false,
|
|
368
|
+
mode: "all",
|
|
369
|
+
...opencodeModelProjection(config, profile),
|
|
370
|
+
permission: opencodePermission(profile)
|
|
371
|
+
}, [
|
|
372
|
+
header("opencode").trimEnd(),
|
|
373
|
+
"",
|
|
374
|
+
profile.description ?? id,
|
|
375
|
+
"",
|
|
376
|
+
"Configured grants:",
|
|
377
|
+
grants(profile),
|
|
378
|
+
"",
|
|
379
|
+
instructionsPointer(profile)
|
|
380
|
+
].join("\n")),
|
|
381
|
+
host: "opencode",
|
|
382
|
+
invocation: invocationForHost("opencode"),
|
|
383
|
+
path: `.opencode/agents/${nativeAgentIdForHost("opencode", id)}.md`
|
|
384
|
+
})),
|
|
385
|
+
...localPluginDefinitions(),
|
|
386
|
+
projectAgentsMdDefinition(cwd, "opencode")
|
|
387
|
+
];
|
|
388
|
+
}
|
|
389
|
+
function projectAgentsMdDefinition(cwd, host) {
|
|
390
|
+
const repoName = basename(cwd);
|
|
391
|
+
return {
|
|
392
|
+
block: {
|
|
393
|
+
end: AGENTS_MD_END,
|
|
394
|
+
start: AGENTS_MD_START
|
|
395
|
+
},
|
|
396
|
+
content: [
|
|
397
|
+
AGENTS_MD_START,
|
|
398
|
+
GENERATED_MARKER,
|
|
399
|
+
`${OWNER_MARKER_PREFIX}host=opencode -->`,
|
|
400
|
+
"",
|
|
401
|
+
"## Pipeline Guidance",
|
|
402
|
+
"",
|
|
403
|
+
"This repository uses package-owned `@oisincoveney/pipeline` config.",
|
|
404
|
+
"",
|
|
405
|
+
"- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
|
|
406
|
+
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
407
|
+
"- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
|
|
408
|
+
"",
|
|
409
|
+
"## Pipeline Memory",
|
|
410
|
+
"",
|
|
411
|
+
`Use Qdrant collection \`${repoName}\` for this repository.`,
|
|
412
|
+
"",
|
|
413
|
+
`- Call \`qdrant-find\` before research with \`collection_name: ${repoName}\` unless the user explicitly disables memory.`,
|
|
414
|
+
`- Call \`qdrant-store\` during LEARN with \`collection_name: ${repoName}\` for durable lessons worth reusing.`,
|
|
415
|
+
"- Include metadata with at least `repo`, `phase`, `workflow` or `entrypoint`, `task`, and `outcome` when storing lessons.",
|
|
416
|
+
"",
|
|
417
|
+
AGENTS_MD_END,
|
|
418
|
+
""
|
|
419
|
+
].join("\n"),
|
|
420
|
+
host,
|
|
421
|
+
invocation: invocationForHost(host),
|
|
422
|
+
path: "AGENTS.md"
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function opencodeModelProjection(config, profile) {
|
|
426
|
+
const model = resolvedHostModel(config, "opencode", profile);
|
|
427
|
+
return model ? { model } : {};
|
|
428
|
+
}
|
|
429
|
+
function instructionsPointer(actor) {
|
|
430
|
+
if (actor.instructions.path) return `Instructions: ${actor.instructions.path}`;
|
|
431
|
+
return `Instructions:\n${actor.instructions.inline ?? ""}`;
|
|
432
|
+
}
|
|
433
|
+
//#endregion
|
|
434
|
+
export { opencodeDefinitions };
|