@oisincoveney/pipeline 2.0.0 → 2.0.1
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-workflow.js +0 -19
- package/dist/cli/program.d.ts +1 -0
- package/dist/cli/program.js +7 -4
- package/dist/cluster-doctor.js +69 -48
- package/dist/config/schemas.d.ts +2 -2
- package/dist/install-commands/opencode.js +422 -0
- package/dist/install-commands/shared.js +23 -0
- package/dist/install-commands.js +7 -440
- 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
|
@@ -0,0 +1,422 @@
|
|
|
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 { opencode: "OpenCode" }[host];
|
|
226
|
+
}
|
|
227
|
+
function needsSummary(needs) {
|
|
228
|
+
return needs.length > 0 ? needs.join(",") : "none";
|
|
229
|
+
}
|
|
230
|
+
function compactLines(lines) {
|
|
231
|
+
return lines.filter((line) => line !== void 0);
|
|
232
|
+
}
|
|
233
|
+
const OPENCODE_PERMISSION_TOOLS = [
|
|
234
|
+
"bash",
|
|
235
|
+
"edit",
|
|
236
|
+
"glob",
|
|
237
|
+
"grep",
|
|
238
|
+
"list",
|
|
239
|
+
"read",
|
|
240
|
+
"write"
|
|
241
|
+
];
|
|
242
|
+
function opencodePermission(actor, options = {}) {
|
|
243
|
+
const allowed = new Set(actor.tools ?? []);
|
|
244
|
+
return {
|
|
245
|
+
...opencodeToolPermissions(allowed),
|
|
246
|
+
external_directory: "deny",
|
|
247
|
+
lsp: "allow",
|
|
248
|
+
skill: opencodeSkillPermission(actor.skills ?? []),
|
|
249
|
+
task: opencodeTaskPermission(allowed, options.allowedTaskAgents ?? [])
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function opencodeToolPermissions(allowed) {
|
|
253
|
+
return Object.fromEntries(OPENCODE_PERMISSION_TOOLS.map((tool) => [tool, allowed.has(tool) ? "allow" : "deny"]));
|
|
254
|
+
}
|
|
255
|
+
function opencodeSkillPermission(skills) {
|
|
256
|
+
return namedOpencodePermissionMap(skills);
|
|
257
|
+
}
|
|
258
|
+
function opencodeTaskPermission(allowed, allowedTaskAgents) {
|
|
259
|
+
return allowedTaskAgents.length > 0 ? namedOpencodePermissionMap(allowedTaskAgents) : toolPermission(allowed, "task");
|
|
260
|
+
}
|
|
261
|
+
function namedOpencodePermissionMap(names) {
|
|
262
|
+
return names.length > 0 ? {
|
|
263
|
+
"*": "deny",
|
|
264
|
+
...Object.fromEntries(names.map((name) => [name, "allow"]))
|
|
265
|
+
} : "deny";
|
|
266
|
+
}
|
|
267
|
+
function toolPermission(allowed, tool) {
|
|
268
|
+
return allowed.has(tool) ? "allow" : "deny";
|
|
269
|
+
}
|
|
270
|
+
function renderOpenCodeProjectConfig(config) {
|
|
271
|
+
return formatOpenCodeProjectJson({
|
|
272
|
+
...config.mcp_gateway ? JSON.parse(renderOpenCodeGatewayConfig(config)) : { $schema: "https://opencode.ai/config.json" },
|
|
273
|
+
lsp: true,
|
|
274
|
+
...opencodePluginConfig()
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function opencodePluginConfig() {
|
|
278
|
+
const plugins = DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => npmPluginPackage(item)).sort((a, b) => a.localeCompare(b));
|
|
279
|
+
return plugins.length > 0 ? { plugin: plugins } : {};
|
|
280
|
+
}
|
|
281
|
+
function npmPluginPackage(item) {
|
|
282
|
+
if (item.plugin?.kind === "npm") return [item.plugin.package];
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
function formatOpenCodeProjectJson(value) {
|
|
286
|
+
return `${JSON.stringify(value, null, 2).replace(SINGLE_OPENCODE_PLUGIN_ARRAY_RE, "\n \"plugin\": [$1]")}\n`;
|
|
287
|
+
}
|
|
288
|
+
function localPluginDefinitions() {
|
|
289
|
+
return DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => {
|
|
290
|
+
if (item.plugin?.kind !== "local") return [];
|
|
291
|
+
const plugin = readFileSync(resolvePackageAssetPath(item.plugin.source_path), "utf8").trimEnd();
|
|
292
|
+
return [{
|
|
293
|
+
content: [
|
|
294
|
+
GENERATED_TS_MARKER,
|
|
295
|
+
`${OWNER_TS_MARKER_PREFIX}host=opencode`,
|
|
296
|
+
"",
|
|
297
|
+
plugin,
|
|
298
|
+
""
|
|
299
|
+
].join("\n"),
|
|
300
|
+
host: "opencode",
|
|
301
|
+
invocation: invocationForHost("opencode"),
|
|
302
|
+
path: item.plugin.target_path
|
|
303
|
+
}];
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function opencodeDefinitions(config, cwd) {
|
|
307
|
+
const orchestrator = orchestratorProfile(config);
|
|
308
|
+
return [
|
|
309
|
+
...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
|
|
310
|
+
content: markdown({
|
|
311
|
+
...orchestrator ? { agent: OPENCODE_ORCHESTRATOR_AGENT_ID } : {},
|
|
312
|
+
description: entrypointDescription(id, entrypoint)
|
|
313
|
+
}, compactLines([
|
|
314
|
+
header("opencode").trimEnd(),
|
|
315
|
+
"",
|
|
316
|
+
`Invoke this command with \`${invocationForHost("opencode", id)}\`.`,
|
|
317
|
+
"",
|
|
318
|
+
orchestratorBlock(config),
|
|
319
|
+
"",
|
|
320
|
+
scheduledEntrypointK8sNote(entrypoint),
|
|
321
|
+
scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
|
|
322
|
+
entrypointDispatchBlock("opencode", config, id, entrypoint)
|
|
323
|
+
]).join("\n")),
|
|
324
|
+
host: "opencode",
|
|
325
|
+
invocation: invocationForHost("opencode", id),
|
|
326
|
+
path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
|
|
327
|
+
})),
|
|
328
|
+
{
|
|
329
|
+
content: renderOpenCodeProjectConfig(config),
|
|
330
|
+
host: "opencode",
|
|
331
|
+
invocation: invocationForHost("opencode"),
|
|
332
|
+
path: ".opencode/opencode.json"
|
|
333
|
+
},
|
|
334
|
+
...orchestrator ? [{
|
|
335
|
+
content: markdown({
|
|
336
|
+
description: "Orchestrate the configured pipeline and enforce gates.",
|
|
337
|
+
mode: "primary",
|
|
338
|
+
name: OPENCODE_ORCHESTRATOR_AGENT_ID,
|
|
339
|
+
permission: opencodePermission(orchestrator, { allowedTaskAgents: agentDispatchRoutes("opencode", config).filter((route) => route.kind !== "cli").map((route) => route.nativeAgentId).filter((id) => Boolean(id)) })
|
|
340
|
+
}, compactLines([
|
|
341
|
+
header("opencode").trimEnd(),
|
|
342
|
+
"",
|
|
343
|
+
orchestratorBlock(config),
|
|
344
|
+
"",
|
|
345
|
+
orchestratorEntrypointDispatchBlock("opencode", config)
|
|
346
|
+
]).join("\n")),
|
|
347
|
+
host: "opencode",
|
|
348
|
+
invocation: invocationForHost("opencode"),
|
|
349
|
+
path: `.opencode/agents/${OPENCODE_ORCHESTRATOR_AGENT_ID}.md`
|
|
350
|
+
}] : [],
|
|
351
|
+
...nativeProfileEntries("opencode", config).map(([id, profile]) => ({
|
|
352
|
+
content: markdown({
|
|
353
|
+
name: nativeAgentIdForHost("opencode", id),
|
|
354
|
+
description: profile.description ?? id,
|
|
355
|
+
hidden: false,
|
|
356
|
+
mode: "all",
|
|
357
|
+
...opencodeModelProjection(config, profile),
|
|
358
|
+
permission: opencodePermission(profile)
|
|
359
|
+
}, [
|
|
360
|
+
header("opencode").trimEnd(),
|
|
361
|
+
"",
|
|
362
|
+
profile.description ?? id,
|
|
363
|
+
"",
|
|
364
|
+
"Configured grants:",
|
|
365
|
+
grants(profile),
|
|
366
|
+
"",
|
|
367
|
+
instructionsPointer(profile)
|
|
368
|
+
].join("\n")),
|
|
369
|
+
host: "opencode",
|
|
370
|
+
invocation: invocationForHost("opencode"),
|
|
371
|
+
path: `.opencode/agents/${nativeAgentIdForHost("opencode", id)}.md`
|
|
372
|
+
})),
|
|
373
|
+
...localPluginDefinitions(),
|
|
374
|
+
projectAgentsMdDefinition(cwd, "opencode")
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
function projectAgentsMdDefinition(cwd, host) {
|
|
378
|
+
const repoName = basename(cwd);
|
|
379
|
+
return {
|
|
380
|
+
block: {
|
|
381
|
+
end: AGENTS_MD_END,
|
|
382
|
+
start: AGENTS_MD_START
|
|
383
|
+
},
|
|
384
|
+
content: [
|
|
385
|
+
AGENTS_MD_START,
|
|
386
|
+
GENERATED_MARKER,
|
|
387
|
+
`${OWNER_MARKER_PREFIX}host=opencode -->`,
|
|
388
|
+
"",
|
|
389
|
+
"## Pipeline Guidance",
|
|
390
|
+
"",
|
|
391
|
+
"This repository uses package-owned `@oisincoveney/pipeline` config.",
|
|
392
|
+
"",
|
|
393
|
+
"- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
|
|
394
|
+
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
395
|
+
"- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
|
|
396
|
+
"",
|
|
397
|
+
"## Pipeline Memory",
|
|
398
|
+
"",
|
|
399
|
+
`Use Qdrant collection \`${repoName}\` for this repository.`,
|
|
400
|
+
"",
|
|
401
|
+
`- Call \`qdrant-find\` before research with \`collection_name: ${repoName}\` unless the user explicitly disables memory.`,
|
|
402
|
+
`- Call \`qdrant-store\` during LEARN with \`collection_name: ${repoName}\` for durable lessons worth reusing.`,
|
|
403
|
+
"- Include metadata with at least `repo`, `phase`, `workflow` or `entrypoint`, `task`, and `outcome` when storing lessons.",
|
|
404
|
+
"",
|
|
405
|
+
AGENTS_MD_END,
|
|
406
|
+
""
|
|
407
|
+
].join("\n"),
|
|
408
|
+
host,
|
|
409
|
+
invocation: invocationForHost(host),
|
|
410
|
+
path: "AGENTS.md"
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function opencodeModelProjection(config, profile) {
|
|
414
|
+
const model = resolvedHostModel(config, "opencode", profile);
|
|
415
|
+
return model ? { model } : {};
|
|
416
|
+
}
|
|
417
|
+
function instructionsPointer(actor) {
|
|
418
|
+
if (actor.instructions.path) return `Instructions: ${actor.instructions.path}`;
|
|
419
|
+
return `Instructions:\n${actor.instructions.inline ?? ""}`;
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
export { opencodeDefinitions };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/install-commands/shared.ts
|
|
2
|
+
const GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
|
|
3
|
+
const GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
|
|
4
|
+
const GENERATED_YAML_MARKER = "# Generated by @oisincoveney/pipeline.";
|
|
5
|
+
const OWNER_MARKER_PREFIX = "<!-- @oisincoveney/pipeline:";
|
|
6
|
+
const OWNER_TS_MARKER_PREFIX = "// @oisincoveney/pipeline:";
|
|
7
|
+
const OWNER_YAML_MARKER_PREFIX = "# @oisincoveney/pipeline:";
|
|
8
|
+
const AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
|
|
9
|
+
const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
|
|
10
|
+
const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
|
|
11
|
+
const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
|
|
12
|
+
const OPENCODE_COMMAND_PREFIX = "moka-";
|
|
13
|
+
const ENTRYPOINT_PATH_PATTERNS = { opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/] };
|
|
14
|
+
const COMMAND_HOSTS = ["opencode"];
|
|
15
|
+
function invocationForHost(host, entrypointId = "execute") {
|
|
16
|
+
return `${{ opencode: "/" }[host]}${commandIdForHost(host, entrypointId)} <task description>`;
|
|
17
|
+
}
|
|
18
|
+
function commandIdForHost(host, entrypointId) {
|
|
19
|
+
if (host === "opencode") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
|
|
20
|
+
return entrypointId;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
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 };
|