@oisincoveney/pipeline 3.2.0 → 3.3.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.
@@ -86,7 +86,7 @@ scheduler:
86
86
  green-implementation:
87
87
  category: green
88
88
  profile: moka-code-writer
89
- models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
89
+ models: [opencode-go/qwen3.7-max, openai/gpt-5.5-high, kimi-for-coding/k2p6]
90
90
  verification:
91
91
  category: verification
92
92
  profile: moka-verifier
@@ -109,7 +109,7 @@ scheduler:
109
109
  green-backend:
110
110
  category: green
111
111
  profile: moka-code-writer
112
- models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
112
+ models: [opencode-go/qwen3.7-max, openai/gpt-5.5-high, kimi-for-coding/k2p6]
113
113
  green-frontend:
114
114
  category: green
115
115
  profile: moka-code-writer
@@ -4,7 +4,7 @@ import { flattenNodes } from "../planning/graph.js";
4
4
  import { configureGatewayHosts, localGatewayStatus, reconcileGateway, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "../mcp/gateway.js";
5
5
  import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.js";
6
6
  import { compileWorkflowPlan } from "../planning/compile.js";
7
- import { generateRuntimeRunId } from "../runtime/context/context.js";
7
+ import { generateRuntimeRunId, resolveWorkflowSelection } from "../runtime/context/context.js";
8
8
  import "../runtime/context/index.js";
9
9
  import { runPipelineFromConfig } from "../pipeline-runtime.js";
10
10
  import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
@@ -567,14 +567,6 @@ function formatWorkflowPlanNode(node, config, worktreePath) {
567
567
  node.artifacts?.length ? `artifacts=${node.artifacts.map((artifact) => artifact.path).join(",")}` : "artifacts=none"
568
568
  ].filter(Boolean).join(" ");
569
569
  }
570
- function resolveWorkflowSelection(config, workflowId, entrypointId) {
571
- if (workflowId) return workflowId;
572
- if (!entrypointId) return;
573
- const entrypoint = config.entrypoints[entrypointId];
574
- if (!entrypoint) throw new Error(`Unknown pipeline entrypoint '${entrypointId}'`);
575
- if ("schedule" in entrypoint) throw new Error(`Pipeline entrypoint '${entrypointId}' generates schedule '${entrypoint.schedule}'; use the entrypoint to create a schedule, then run with --schedule.`);
576
- return entrypoint.workflow;
577
- }
578
570
  function formatOrchestratorPlan(config, worktreePath) {
579
571
  if (!config.orchestrator) return "Orchestrator: not configured";
580
572
  const orchestrator = config.profiles[config.orchestrator.profile];
@@ -481,8 +481,8 @@ declare const configSchema: z.ZodObject<{
481
481
  schedules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
482
482
  description: z.ZodOptional<z.ZodString>;
483
483
  baseline: z.ZodEnum<{
484
- quick: "quick";
485
484
  execute: "execute";
485
+ quick: "quick";
486
486
  }>;
487
487
  max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
488
488
  node_catalog: z.ZodOptional<z.ZodString>;
@@ -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<{
@@ -160,8 +160,8 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
160
160
  }, z.core.$strict>>;
161
161
  serviceAccountName: z.ZodOptional<z.ZodString>;
162
162
  mode: z.ZodEnum<{
163
- full: "full";
164
163
  quick: "quick";
164
+ full: "full";
165
165
  }>;
166
166
  schedulePath: z.ZodOptional<z.ZodString>;
167
167
  scheduleYaml: z.ZodOptional<z.ZodString>;
@@ -206,13 +206,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
206
206
  }, z.core.$strict>>;
207
207
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
208
208
  "workflow.start": "workflow.start";
209
+ "node.finish": "node.finish";
210
+ "node.start": "node.start";
209
211
  "workflow.success": "workflow.success";
210
212
  "workflow.failure": "workflow.failure";
211
213
  "workflow.complete": "workflow.complete";
212
- "node.start": "node.start";
213
214
  "node.success": "node.success";
214
215
  "node.error": "node.error";
215
- "node.finish": "node.finish";
216
216
  "gate.failure": "gate.failure";
217
217
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
218
218
  failure: z.ZodDefault<z.ZodEnum<{
@@ -77,10 +77,12 @@ function runWithLeasedOpencode(options, config, worktreePath, run) {
77
77
  ...options.signal ? { signal: options.signal } : {},
78
78
  worktreePath
79
79
  })), (lease) => Effect.promise(() => lease.release()));
80
+ const availableModels = yield* Effect.promise(() => lease.availableModels());
80
81
  return yield* run({
81
82
  ...options,
82
83
  config,
83
- executor: lease.executor
84
+ executor: lease.executor,
85
+ ...availableModels ? { availableModels } : {}
84
86
  });
85
87
  }));
86
88
  }
@@ -20,11 +20,11 @@ function createRunStoreRuntimeReporterRuntime(input) {
20
20
  const activeHookPreviousStatuses = /* @__PURE__ */ new Map();
21
21
  let writeChain = Promise.resolve();
22
22
  const enqueue = (event) => {
23
- const projection = projectRuntimeEvent(event, {
23
+ const persisted = persistRuntimeEventEffect(input, event, projectRuntimeEvent(event, {
24
24
  activeHookPreviousStatuses,
25
25
  observedNodeStatuses
26
- });
27
- writeChain = writeChain.then(() => withRunStateLock(() => Effect.runPromise(persistRuntimeEventEffect(input, event, projection, now))));
26
+ }), now).pipe(Effect.catchAll((error) => warnPersistSkipped(input, event, error)));
27
+ writeChain = writeChain.then(() => withRunStateLock(() => Effect.runPromise(persisted)));
28
28
  };
29
29
  const flushEffect = () => Effect.tryPromise({
30
30
  catch: (error) => error,
@@ -184,5 +184,14 @@ function timestamp(now) {
184
184
  function assertNever(value) {
185
185
  throw new Error(`Unhandled runtime reporter value: ${String(value)}`);
186
186
  }
187
+ function warnPersistSkipped(input, event, error) {
188
+ return Effect.sync(() => {
189
+ const nodeId = "nodeId" in event && typeof event.nodeId === "string" ? ` node=${event.nodeId}` : "";
190
+ process.stderr.write(`run-control: skipped persisting ${event.type}${nodeId} for run ${input.runId}: ${errorMessage(error)}\n`);
191
+ });
192
+ }
193
+ function errorMessage(error) {
194
+ return error instanceof Error ? error.message : String(error);
195
+ }
187
196
  //#endregion
188
197
  export { createRunStoreRuntimeReporter };
@@ -43,8 +43,8 @@ declare const runnerDeliverySchema: z.ZodObject<{
43
43
  declare const mokaSubmissionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
44
44
  kind: z.ZodLiteral<"graph">;
45
45
  mode: z.ZodEnum<{
46
- full: "full";
47
46
  quick: "quick";
47
+ full: "full";
48
48
  }>;
49
49
  }, z.core.$strict>, z.ZodObject<{
50
50
  argv: z.ZodArray<z.ZodString>;
@@ -104,8 +104,8 @@ declare const runnerCommandPayloadSchema: z.ZodObject<{
104
104
  submission: z.ZodDefault<z.ZodDiscriminatedUnion<[z.ZodObject<{
105
105
  kind: z.ZodLiteral<"graph">;
106
106
  mode: z.ZodEnum<{
107
- full: "full";
108
107
  quick: "quick";
108
+ full: "full";
109
109
  }>;
110
110
  }, z.core.$strict>, z.ZodObject<{
111
111
  argv: z.ZodArray<z.ZodString>;
@@ -10,8 +10,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
10
10
  at: z.ZodOptional<z.ZodString>;
11
11
  sequence: z.ZodNumber;
12
12
  type: z.ZodEnum<{
13
- "workflow.start": "workflow.start";
14
13
  "workflow.planned": "workflow.planned";
14
+ "workflow.start": "workflow.start";
15
15
  }>;
16
16
  workflowPlan: z.ZodObject<{
17
17
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -55,10 +55,10 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
55
55
  }>;
56
56
  }, z.core.$strip>;
57
57
  type: z.ZodEnum<{
58
- "node.start": "node.start";
59
- "node.finish": "node.finish";
60
58
  "agent.finish": "agent.finish";
61
59
  "agent.start": "agent.start";
60
+ "node.finish": "node.finish";
61
+ "node.start": "node.start";
62
62
  }>;
63
63
  }, z.core.$strip>, z.ZodObject<{
64
64
  at: z.ZodOptional<z.ZodString>;
@@ -180,8 +180,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
180
180
  at: z.ZodOptional<z.ZodString>;
181
181
  sequence: z.ZodNumber;
182
182
  type: z.ZodEnum<{
183
- "workflow.start": "workflow.start";
184
183
  "workflow.planned": "workflow.planned";
184
+ "workflow.start": "workflow.start";
185
185
  }>;
186
186
  workflowPlan: z.ZodObject<{
187
187
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -225,10 +225,10 @@ declare const runnerEventBatchSchema: z.ZodObject<{
225
225
  }>;
226
226
  }, z.core.$strip>;
227
227
  type: z.ZodEnum<{
228
- "node.start": "node.start";
229
- "node.finish": "node.finish";
230
228
  "agent.finish": "agent.finish";
231
229
  "agent.start": "agent.start";
230
+ "node.finish": "node.finish";
231
+ "node.start": "node.start";
232
232
  }>;
233
233
  }, z.core.$strip>, z.ZodObject<{
234
234
  at: z.ZodOptional<z.ZodString>;
@@ -25,7 +25,7 @@ function executeAgentNodeEffect(node, context, attempt) {
25
25
  output: ""
26
26
  };
27
27
  const prompt = yield* renderAgentPromptEffect(node, context);
28
- const decision = decideNodeModel(prompt, node, context.config.token_budget);
28
+ const decision = decideNodeModel(prompt, node, context.config.token_budget, context.availableModels);
29
29
  if (decision.overBudget) return {
30
30
  evidence: [
31
31
  `agent boundary node=${node.id} profile=${node.profile}`,
@@ -155,14 +155,15 @@ function createHandoffFinalizerPlan(context, node, runner, rawOutput) {
155
155
  * with a fallback array but no fitting model is `overBudget` — the caller fails
156
156
  * it fast rather than truncating.
157
157
  */
158
- function decideNodeModel(prompt, node, budget) {
158
+ function decideNodeModel(prompt, node, budget, availableModels) {
159
159
  const estimatedTokens = estimateTokens(prompt);
160
160
  if (!(budget && node.models?.length)) return {
161
161
  estimatedTokens,
162
162
  overBudget: false,
163
- selection: selectNodeModel(node)
163
+ selection: selectNodeModel(node, { available: availableModels })
164
164
  };
165
165
  const selection = selectNodeModel(node, {
166
+ available: availableModels,
166
167
  budget,
167
168
  estimatedTokens
168
169
  });
@@ -18,6 +18,7 @@ function createRuntimeContext(options) {
18
18
  const observability = options.reporter ? createPublicRuntimeObservabilityEmitter(options.reporter, workflowId) : void 0;
19
19
  return {
20
20
  agentInvocations: [],
21
+ ...options.availableModels ? { availableModels: options.availableModels } : {},
21
22
  ...runId ? { runId } : {},
22
23
  config,
23
24
  executor: options.executor ?? runLaunchPlan,
@@ -70,4 +71,4 @@ function generateRuntimeRunId() {
70
71
  return `run-${randomUUID()}`;
71
72
  }
72
73
  //#endregion
73
- export { createRuntimeContext, generateRuntimeRunId, normalizeMaxParallelNodes, resolveWorkflowSelection };
74
+ export { createRuntimeContext, generateRuntimeRunId, resolveWorkflowSelection };
@@ -248,6 +248,7 @@ type PipelineRuntimeEvent = {
248
248
  * single-node, schedule-driven execution path.
249
249
  */
250
250
  interface PipelineRuntimeOptions {
251
+ availableModels?: ReadonlySet<string>;
251
252
  config?: PipelineConfig;
252
253
  entrypoint?: string;
253
254
  executor?: (plan: RunnerLaunchPlan, options: RunnerExecutionOptions) => AgentResult | Promise<AgentResult>;
@@ -27,6 +27,7 @@ function leaseOpencodeRuntimeEffect(input) {
27
27
  const registry = createOpencodeSessionRegistry();
28
28
  let handle;
29
29
  let pending;
30
+ let availableModelsPending;
30
31
  const ensureExecutor = () => {
31
32
  pending ??= Effect.runPromise(Effect.provide(ensureExecutorEffect(input, registry), OpencodeRuntimeServerServiceLive)).then((executor) => {
32
33
  handle = executor.handle;
@@ -34,7 +35,12 @@ function leaseOpencodeRuntimeEffect(input) {
34
35
  });
35
36
  return pending;
36
37
  };
38
+ const resolveAvailableModels = () => {
39
+ availableModelsPending ??= ensureExecutor().then(() => handle ? queryAvailableOpencodeModels(handle.client) : void 0).catch(() => void 0);
40
+ return availableModelsPending;
41
+ };
37
42
  return Effect.succeed({
43
+ availableModels: resolveAvailableModels,
38
44
  executor: async (plan, options) => {
39
45
  return await (await ensureExecutor())(plan, options);
40
46
  },
@@ -46,6 +52,15 @@ function leaseOpencodeRuntimeEffect(input) {
46
52
  }
47
53
  });
48
54
  }
55
+ /**
56
+ * Collect every model the leased server can resolve (each authenticated
57
+ * provider's models as `provider/model`) from the opencode `/config/providers`
58
+ * endpoint, for availability-aware model selection.
59
+ */
60
+ async function queryAvailableOpencodeModels(client) {
61
+ const providers = (await client.config.providers()).data?.providers ?? [];
62
+ return new Set(providers.flatMap((provider) => Object.keys(provider.models ?? {}).map((modelId) => `${provider.id}/${modelId}`)));
63
+ }
49
64
  function ensureExecutorEffect(input, registry) {
50
65
  return Effect.gen(function* () {
51
66
  const handle = yield* (yield* OpencodeRuntimeServerService).open(input);
@@ -22,15 +22,11 @@ function addWorkflowImplementationCoverage(config, workflow, coverageProfileId)
22
22
  };
23
23
  }
24
24
  function addNodeScopeImplementationCoverage(config, nodes, coverageProfileId) {
25
- const scopedNodes = nodes.map((node) => node.kind === "parallel" ? {
26
- ...node,
27
- nodes: addNodeScopeImplementationCoverage(config, node.nodes, coverageProfileId)
28
- } : node);
29
- const index = dependentsByNeed(scopedNodes);
30
- const uncovered = scopedNodes.filter((node) => isImplementationNode(config, node)).filter((node) => !hasDownstreamCoverage(config, node.id, index));
31
- if (uncovered.length === 0) return scopedNodes;
32
- const coverageNodeId = uniqueGeneratedId("generated-coverage", new Set(scopedNodes.map((node) => node.id)), "generated-coverage");
33
- return [...scopedNodes, {
25
+ const index = dependentsByNeed(nodes);
26
+ const uncovered = nodes.filter((node) => nodeNeedsImplementationCoverage(config, node)).filter((node) => !hasDownstreamCoverage(config, node.id, index));
27
+ if (uncovered.length === 0) return nodes;
28
+ const coverageNodeId = uniqueGeneratedId("generated-coverage", new Set(nodes.map((node) => node.id)), "generated-coverage");
29
+ return [...nodes, {
34
30
  gates: generatedCoverageGates(coverageNodeId),
35
31
  id: coverageNodeId,
36
32
  kind: "agent",
@@ -38,6 +34,10 @@ function addNodeScopeImplementationCoverage(config, nodes, coverageProfileId) {
38
34
  profile: coverageProfileId
39
35
  }];
40
36
  }
37
+ function nodeNeedsImplementationCoverage(config, node) {
38
+ if (isImplementationNode(config, node)) return true;
39
+ return node.kind === "parallel" && node.nodes.some((child) => nodeNeedsImplementationCoverage(config, child));
40
+ }
41
41
  function generatedCoverageProfileId(config) {
42
42
  const coverageProfiles = Object.entries(config.profiles).filter(([, profile]) => profile.scheduling_roles?.includes("coverage")).map(([id]) => id);
43
43
  if (coverageProfiles.length === 0) return null;
package/package.json CHANGED
@@ -126,7 +126,7 @@
126
126
  "prepack": "bun run build:cli"
127
127
  },
128
128
  "type": "module",
129
- "version": "3.2.0",
129
+ "version": "3.3.0",
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",