@oisincoveney/pipeline 3.2.0 → 3.3.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/defaults/pipeline.yaml +2 -2
- package/dist/cli/program.js +1 -9
- package/dist/hooks.d.ts +1 -1
- package/dist/moka-submit.d.ts +6 -6
- package/dist/pipeline-runtime.js +3 -1
- package/dist/planning/generate.js +2 -0
- package/dist/run-control/runtime-reporter.js +12 -3
- package/dist/run-control/store.js +2 -0
- package/dist/run-control/workspace.js +23 -0
- package/dist/runner-event-schema.d.ts +8 -8
- package/dist/runtime/agent-node/agent-node.js +4 -3
- package/dist/runtime/context/context.js +2 -1
- package/dist/runtime/contracts/contracts.d.ts +1 -0
- package/dist/runtime/opencode-runtime.js +15 -0
- package/dist/schedule/passes/coverage.js +9 -9
- package/package.json +1 -1
package/defaults/pipeline.yaml
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/dist/cli/program.js
CHANGED
|
@@ -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];
|
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>;
|
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<{
|
|
@@ -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<{
|
package/dist/pipeline-runtime.js
CHANGED
|
@@ -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
|
}
|
|
@@ -5,6 +5,7 @@ import { dependentsByNeed, flattenNodes, hasReachableDependent } from "./graph.j
|
|
|
5
5
|
import { createRunnerLaunchPlan, runLaunchPlan } from "../runner.js";
|
|
6
6
|
import { normalizeRunnerOutput } from "../runner-output.js";
|
|
7
7
|
import { compileWorkflowPlan } from "./compile.js";
|
|
8
|
+
import { ensurePipelineWorkspaceIgnore } from "../run-control/workspace.js";
|
|
8
9
|
import { loadBacklogPlanningContext } from "../schedule/backlog-context.js";
|
|
9
10
|
import { baselineScheduleArtifact } from "../schedule/baseline.js";
|
|
10
11
|
import { isCoverageNode, isImplementationNode, isWriteCapableParallelChild } from "../schedule/scheduling-roles.js";
|
|
@@ -111,6 +112,7 @@ function assertSchedulePassOrder() {
|
|
|
111
112
|
].join("\0")) throw new ScheduleArtifactError("Schedule pass order is misconfigured");
|
|
112
113
|
}
|
|
113
114
|
function persistScheduleArtifact(worktreePath, artifact) {
|
|
115
|
+
ensurePipelineWorkspaceIgnore(worktreePath);
|
|
114
116
|
const relativePath = join(".pipeline", "runs", artifact.schedule_id, "schedule.yaml");
|
|
115
117
|
const fullPath = join(worktreePath, relativePath);
|
|
116
118
|
mkdirSync(join(worktreePath, ".pipeline", "runs", artifact.schedule_id), { recursive: true });
|
|
@@ -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
|
|
23
|
+
const persisted = persistRuntimeEventEffect(input, event, projectRuntimeEvent(event, {
|
|
24
24
|
activeHookPreviousStatuses,
|
|
25
25
|
observedNodeStatuses
|
|
26
|
-
});
|
|
27
|
-
writeChain = writeChain.then(() => withRunStateLock(() => Effect.runPromise(
|
|
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 };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensurePipelineWorkspaceIgnore } from "./workspace.js";
|
|
1
2
|
import { DEFAULT_RUN_CONTROL_STALE_DETECTION, parseMokaNodeStatus, parseMokaRunController, parseMokaRunEvent, parseMokaRunManifest, parseMokaRunStatus, parseRunControlStaleDetection, parseRunEffort, parseRunMode, parseRunTarget } from "./contracts.js";
|
|
2
3
|
import { Effect } from "effect";
|
|
3
4
|
import { isAbsolute, join } from "node:path";
|
|
@@ -15,6 +16,7 @@ function createRunEffect(input) {
|
|
|
15
16
|
return Effect.gen(function* () {
|
|
16
17
|
const { manifest, nodeIds, runId } = yield* Effect.sync(() => createRunManifest(input));
|
|
17
18
|
const paths = runPaths(input.workspaceRoot, runId);
|
|
19
|
+
yield* Effect.sync(() => ensurePipelineWorkspaceIgnore(input.workspaceRoot));
|
|
18
20
|
yield* mkdirEffect(paths.runsRoot, { recursive: true });
|
|
19
21
|
yield* mkdirEffect(paths.runRoot, { recursive: true });
|
|
20
22
|
yield* mkdirEffect(paths.nodesRoot, { recursive: true });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region src/run-control/workspace.ts
|
|
4
|
+
const PIPELINE_DIR = ".pipeline";
|
|
5
|
+
const GITIGNORE_PATH = join(PIPELINE_DIR, ".gitignore");
|
|
6
|
+
const GITIGNORE_CONTENT = "*\n";
|
|
7
|
+
/**
|
|
8
|
+
* Ensures `.pipeline/.gitignore` exists in `worktreePath` so that moka's
|
|
9
|
+
* runtime artifacts are self-ignored and never picked up by lint/format tools
|
|
10
|
+
* running inside the target repo.
|
|
11
|
+
*
|
|
12
|
+
* Idempotent: creates the file only if it does not already exist; never
|
|
13
|
+
* overwrites an existing `.pipeline/.gitignore`.
|
|
14
|
+
*/
|
|
15
|
+
function ensurePipelineWorkspaceIgnore(worktreePath) {
|
|
16
|
+
const pipelineDir = join(worktreePath, PIPELINE_DIR);
|
|
17
|
+
const gitignorePath = join(worktreePath, GITIGNORE_PATH);
|
|
18
|
+
if (existsSync(gitignorePath)) return;
|
|
19
|
+
mkdirSync(pipelineDir, { recursive: true });
|
|
20
|
+
writeFileSync(gitignorePath, GITIGNORE_CONTENT);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ensurePipelineWorkspaceIgnore };
|
|
@@ -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>;
|
|
@@ -103,8 +103,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
103
103
|
nodeId: z.ZodOptional<z.ZodString>;
|
|
104
104
|
outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
105
105
|
status: z.ZodEnum<{
|
|
106
|
-
fail: "fail";
|
|
107
106
|
pass: "pass";
|
|
107
|
+
fail: "fail";
|
|
108
108
|
skip: "skip";
|
|
109
109
|
}>;
|
|
110
110
|
summary: 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>;
|
|
@@ -273,8 +273,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
|
|
|
273
273
|
nodeId: z.ZodOptional<z.ZodString>;
|
|
274
274
|
outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
275
275
|
status: z.ZodEnum<{
|
|
276
|
-
fail: "fail";
|
|
277
276
|
pass: "pass";
|
|
277
|
+
fail: "fail";
|
|
278
278
|
skip: "skip";
|
|
279
279
|
}>;
|
|
280
280
|
summary: 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,
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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.
|
|
129
|
+
"version": "3.3.1",
|
|
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",
|