@oisincoveney/pipeline 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/defaults/pipeline.yaml +25 -0
- package/dist/argo-graph.js +7 -7
- package/dist/bench/eval-report.js +27 -0
- package/dist/cli/program.js +19 -3
- package/dist/commands/bench-command.js +18 -0
- package/dist/config/load.js +17 -0
- package/dist/config/schemas.d.ts +22 -5
- package/dist/config/schemas.js +20 -7
- package/dist/context/repo-map.js +203 -0
- package/dist/install-commands/opencode.js +10 -1
- package/dist/mcp/gateway.js +3 -3
- package/dist/moka-submit.d.ts +6 -6
- package/dist/pipeline-init.js +18 -12
- package/dist/pipeline-runtime.js +12 -1
- package/dist/planning/compile.d.ts +8 -3
- package/dist/planning/compile.js +7 -7
- package/dist/planning/generate.d.ts +6 -1
- package/dist/planning/generate.js +29 -7
- package/dist/runner-command-contract.d.ts +6 -1
- package/dist/runner-command-contract.js +6 -5
- package/dist/runner-event-schema.d.ts +6 -6
- package/dist/runner-event-sink.js +6 -5
- package/dist/runner.d.ts +6 -1
- package/dist/runner.js +3 -3
- package/dist/runtime/agent-node/agent-node.js +22 -4
- package/dist/runtime/local-scheduler.js +45 -0
- package/dist/runtime/opencode-server.js +6 -3
- package/dist/runtime/parallel-node/parallel-node.js +77 -58
- package/dist/runtime/parallel-worktrees/parallel-worktrees.js +49 -4
- package/dist/runtime/run-journal.js +21 -0
- package/dist/runtime/scheduler.js +122 -93
- package/dist/runtime/select-candidate/select-candidate.js +13 -1
- package/dist/runtime/services/worktree-service.js +18 -0
- package/dist/schedule/passes/candidates.js +17 -8
- package/docs/config-architecture.md +105 -0
- package/package.json +7 -2
package/dist/pipeline-init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { installCommands } from "./install-commands.js";
|
|
|
2
2
|
import { execa } from "execa";
|
|
3
3
|
//#region src/pipeline-init.ts
|
|
4
4
|
const DEFAULT_SKILL_INSTALL_SOURCE = "oisin-ee/skills";
|
|
5
|
-
const
|
|
5
|
+
const SKILL_INSTALL_AGENT_ARGS = [
|
|
6
6
|
"--agent",
|
|
7
7
|
"opencode",
|
|
8
8
|
"--agent",
|
|
@@ -11,17 +11,19 @@ const DEFAULT_SKILL_INSTALL_ARGS = [
|
|
|
11
11
|
"claude-code",
|
|
12
12
|
"--skill",
|
|
13
13
|
"*",
|
|
14
|
-
"--yes"
|
|
15
|
-
"--copy"
|
|
14
|
+
"--yes"
|
|
16
15
|
];
|
|
17
|
-
|
|
16
|
+
function skillInstallArgs(scope) {
|
|
17
|
+
return scope === "personal" ? [...SKILL_INSTALL_AGENT_ARGS, "--global"] : [...SKILL_INSTALL_AGENT_ARGS, "--copy"];
|
|
18
|
+
}
|
|
19
|
+
async function installDefaultSkills(cwd, scope) {
|
|
18
20
|
try {
|
|
19
21
|
await execa("npx", [
|
|
20
22
|
"--yes",
|
|
21
23
|
"skills",
|
|
22
24
|
"add",
|
|
23
25
|
DEFAULT_SKILL_INSTALL_SOURCE,
|
|
24
|
-
...
|
|
26
|
+
...skillInstallArgs(scope)
|
|
25
27
|
], {
|
|
26
28
|
cwd,
|
|
27
29
|
stdio: "inherit"
|
|
@@ -33,17 +35,21 @@ async function installDefaultSkills(cwd) {
|
|
|
33
35
|
}
|
|
34
36
|
async function initPipelineProject(options = {}) {
|
|
35
37
|
const cwd = options.cwd ?? process.cwd();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const scope = options.scope ?? "project";
|
|
39
|
+
await (options.skillInstaller ?? ((target) => installDefaultSkills(target, scope)))(cwd);
|
|
40
|
+
return {
|
|
41
|
+
files: (await installCommands({
|
|
42
|
+
cwd,
|
|
43
|
+
force: true,
|
|
44
|
+
host: "all"
|
|
45
|
+
})).items.map((item) => item.path),
|
|
46
|
+
scope
|
|
47
|
+
};
|
|
42
48
|
}
|
|
43
49
|
function formatPipelineInitResult(result) {
|
|
44
50
|
return [
|
|
45
51
|
"Initialized package-owned pipeline support:",
|
|
46
|
-
"installed default skills",
|
|
52
|
+
result.scope === "personal" ? "installed default skills at user/global scope (inherited by every repo, no per-repo copy)" : "installed default skills (repo-local copy)",
|
|
47
53
|
...result.files.map((path) => `generated ${path}`),
|
|
48
54
|
"no repo-local pipeline config files were created"
|
|
49
55
|
].join("\n");
|
package/dist/pipeline-runtime.js
CHANGED
|
@@ -18,12 +18,14 @@ import "./runtime/builtins/index.js";
|
|
|
18
18
|
import { diffChangedFiles, snapshotChangedFiles } from "./runtime/changed-files/changed-files.js";
|
|
19
19
|
import { evaluateNodeGates } from "./runtime/gates/gates.js";
|
|
20
20
|
import "./runtime/gates/index.js";
|
|
21
|
+
import { LocalScheduler } from "./runtime/local-scheduler.js";
|
|
21
22
|
import { NodeStateTracker } from "./runtime/node-state-tracker.js";
|
|
22
23
|
import { configUsesOpencode, leaseOpencodeRuntime } from "./runtime/opencode-runtime.js";
|
|
23
24
|
import { executeParallelNode } from "./runtime/parallel-node/parallel-node.js";
|
|
24
25
|
import "./runtime/parallel-node/index.js";
|
|
25
26
|
import { decideNodeRetry, nodeRetryPolicy } from "./runtime/retry.js";
|
|
26
|
-
import {
|
|
27
|
+
import { fileRunJournal } from "./runtime/run-journal.js";
|
|
28
|
+
import { join } from "node:path";
|
|
27
29
|
//#region src/pipeline-runtime.ts
|
|
28
30
|
function runPipelineFromConfig(options) {
|
|
29
31
|
return withOpencodeRuntime(options, (resolved) => runPipelineWithContext(createRuntimeContext(resolved)));
|
|
@@ -78,6 +80,14 @@ async function runWithLeasedOpencode(options, config, worktreePath, run) {
|
|
|
78
80
|
await lease.release();
|
|
79
81
|
}
|
|
80
82
|
}
|
|
83
|
+
function runJournalPath(context, dir) {
|
|
84
|
+
return join(context.worktreePath ?? process.cwd(), dir, `${context.runId}.jsonl`);
|
|
85
|
+
}
|
|
86
|
+
function resolveRunJournal(context) {
|
|
87
|
+
const durability = context.config.durability;
|
|
88
|
+
if (!(durability?.enabled && context.runId)) return;
|
|
89
|
+
return fileRunJournal(runJournalPath(context, durability.dir));
|
|
90
|
+
}
|
|
81
91
|
async function runPipelineWithContext(context) {
|
|
82
92
|
return finishRuntime(context, await new LocalScheduler({
|
|
83
93
|
buildResult: (outcome, nodes, failure) => workflowRuntimeResult(context, outcome, nodes, failure),
|
|
@@ -89,6 +99,7 @@ async function runPipelineWithContext(context) {
|
|
|
89
99
|
at: now(),
|
|
90
100
|
type: "READY"
|
|
91
101
|
}),
|
|
102
|
+
resolveJournal: (nextContext) => resolveRunJournal(nextContext),
|
|
92
103
|
runWorkflowHook: (event, failure, nextContext) => dispatchHooks(nextContext, event, failure),
|
|
93
104
|
shouldContinueAfterNodeResult: (result, nextContext) => shouldContinueAfterNodeResult(result, nextContext),
|
|
94
105
|
skipNode: (nodeId, reason, nextContext) => recordSkippedNodeState(nextContext, nodeId, reason, now())
|
|
@@ -7,9 +7,14 @@ interface WorkflowPlannerIssue {
|
|
|
7
7
|
message: string;
|
|
8
8
|
path?: string;
|
|
9
9
|
}
|
|
10
|
-
declare
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
declare const WorkflowPlannerError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
11
|
+
readonly _tag: "WorkflowPlannerError";
|
|
12
|
+
} & Readonly<A>;
|
|
13
|
+
declare class WorkflowPlannerError extends WorkflowPlannerError_base<{
|
|
14
|
+
readonly code: WorkflowPlannerErrorCode;
|
|
15
|
+
readonly message: string;
|
|
16
|
+
readonly issues: WorkflowPlannerIssue[];
|
|
17
|
+
}> {
|
|
13
18
|
constructor(code: WorkflowPlannerErrorCode, message: string, issues?: WorkflowPlannerIssue[]);
|
|
14
19
|
}
|
|
15
20
|
interface PlannedWorkflowNode {
|
package/dist/planning/compile.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { uniqueStrings } from "../strings.js";
|
|
2
2
|
import { findDependencyCycles } from "./graph.js";
|
|
3
|
+
import { Data } from "effect";
|
|
3
4
|
import { Graph } from "@dagrejs/graphlib";
|
|
4
5
|
//#region src/planning/compile.ts
|
|
5
|
-
var WorkflowPlannerError = class extends
|
|
6
|
-
code;
|
|
7
|
-
issues;
|
|
6
|
+
var WorkflowPlannerError = class extends Data.TaggedError("WorkflowPlannerError") {
|
|
8
7
|
constructor(code, message, issues = []) {
|
|
9
|
-
super(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
super({
|
|
9
|
+
code,
|
|
10
|
+
message,
|
|
11
|
+
issues
|
|
12
|
+
});
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
15
|
function compileWorkflowPlan(config, workflowId = config.default_workflow) {
|
|
@@ -1594,7 +1594,12 @@ interface SchedulePlanningContext {
|
|
|
1594
1594
|
workUnits: BacklogWorkUnit[];
|
|
1595
1595
|
}
|
|
1596
1596
|
declare function parseScheduleArtifact(source: string, sourcePath?: string): ScheduleArtifact;
|
|
1597
|
-
declare
|
|
1597
|
+
declare const ScheduleArtifactError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
1598
|
+
readonly _tag: "ScheduleArtifactError";
|
|
1599
|
+
} & Readonly<A>;
|
|
1600
|
+
declare class ScheduleArtifactError extends ScheduleArtifactError_base<{
|
|
1601
|
+
readonly message: string;
|
|
1602
|
+
}> {
|
|
1598
1603
|
constructor(message: string);
|
|
1599
1604
|
}
|
|
1600
1605
|
declare function compileScheduleArtifact(config: PipelineConfig, artifact: ScheduleArtifact, projectRoot?: string): CompiledScheduleArtifact;
|
|
@@ -17,6 +17,7 @@ import { plannerPrompt, plannerRepairPrompt } from "../schedule/prompts.js";
|
|
|
17
17
|
import { compileWorkflowPlan } from "./compile.js";
|
|
18
18
|
import { parseDocument, stringify } from "yaml";
|
|
19
19
|
import { z } from "zod";
|
|
20
|
+
import { Data } from "effect";
|
|
20
21
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
21
22
|
import { join } from "node:path";
|
|
22
23
|
//#region src/planning/generate.ts
|
|
@@ -30,10 +31,12 @@ const SCHEDULE_BUILTINS = [
|
|
|
30
31
|
"duplication",
|
|
31
32
|
"fallow",
|
|
32
33
|
"lint",
|
|
34
|
+
"select-candidate",
|
|
33
35
|
"semgrep",
|
|
34
36
|
"test",
|
|
35
37
|
"typecheck"
|
|
36
38
|
];
|
|
39
|
+
const PARALLEL_MERGE_BUILTINS = new Set(["drain-merge", "select-candidate"]);
|
|
37
40
|
const scheduleArtifactSchema = z.object({
|
|
38
41
|
generated_at: z.string().datetime(),
|
|
39
42
|
kind: z.literal(SCHEDULE_KIND),
|
|
@@ -54,10 +57,9 @@ function parseScheduleArtifact(source, sourcePath = "schedule.yaml") {
|
|
|
54
57
|
if (!parsed.success) throw new ScheduleArtifactError([`Invalid schedule artifact ${sourcePath}:`, ...parsed.error.issues.map((issue) => issue.path.length > 0 ? `- ${issue.path.join(".")}: ${issue.message}` : `- ${issue.message}`)].join("\n"));
|
|
55
58
|
return parsed.data;
|
|
56
59
|
}
|
|
57
|
-
var ScheduleArtifactError = class extends
|
|
60
|
+
var ScheduleArtifactError = class extends Data.TaggedError("ScheduleArtifactError") {
|
|
58
61
|
constructor(message) {
|
|
59
|
-
super(message);
|
|
60
|
-
this.name = "ScheduleArtifactError";
|
|
62
|
+
super({ message });
|
|
61
63
|
}
|
|
62
64
|
};
|
|
63
65
|
function compileScheduleArtifact(config, artifact, projectRoot) {
|
|
@@ -255,11 +257,28 @@ function unsafeParallelWorktreeIssues(config, artifact) {
|
|
|
255
257
|
return [`parallel node '${workflowId}.${node.id}' has write-capable children sharing a worktree without isolated worktree roots or drain-merge integration`];
|
|
256
258
|
}));
|
|
257
259
|
}
|
|
260
|
+
function registerContainmentEdge(parent, child, index) {
|
|
261
|
+
const dependents = index.get(child.id) ?? [];
|
|
262
|
+
dependents.push(parent);
|
|
263
|
+
index.set(child.id, dependents);
|
|
264
|
+
}
|
|
265
|
+
function addParallelContainmentEdges(nodes, index) {
|
|
266
|
+
for (const node of nodes) {
|
|
267
|
+
if (node.kind !== "parallel") continue;
|
|
268
|
+
for (const child of node.nodes) registerContainmentEdge(node, child, index);
|
|
269
|
+
addParallelContainmentEdges(node.nodes, index);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function dependentsByNeedWithContainment(nested, flat) {
|
|
273
|
+
const index = dependentsByNeed(flat);
|
|
274
|
+
addParallelContainmentEdges(nested, index);
|
|
275
|
+
return index;
|
|
276
|
+
}
|
|
258
277
|
function workflowNodeIssues(artifact, collectIssues) {
|
|
259
278
|
return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
|
|
260
279
|
const nodes = flattenWorkflowNodes(workflow.nodes);
|
|
261
280
|
return collectIssues({
|
|
262
|
-
dependentsByNeed:
|
|
281
|
+
dependentsByNeed: dependentsByNeedWithContainment(workflow.nodes, nodes),
|
|
263
282
|
nodes,
|
|
264
283
|
workflowId
|
|
265
284
|
});
|
|
@@ -272,7 +291,7 @@ function isWriteCapableParallelChild(config, node) {
|
|
|
272
291
|
return false;
|
|
273
292
|
}
|
|
274
293
|
function hasDownstreamDrainMerge(nodeId, index) {
|
|
275
|
-
return hasReachableDependent(nodeId, index, (node) => node.kind === "builtin" && node.builtin
|
|
294
|
+
return hasReachableDependent(nodeId, index, (node) => node.kind === "builtin" && PARALLEL_MERGE_BUILTINS.has(node.builtin));
|
|
276
295
|
}
|
|
277
296
|
function generatedRootWorkflowIssues(artifact) {
|
|
278
297
|
const workflowIds = Object.keys(artifact.workflows);
|
|
@@ -324,9 +343,9 @@ function workUnitDependencyIssues(config, artifact, workUnits) {
|
|
|
324
343
|
const dependenciesByUnit = new Map(workUnits.map((unit) => [unit.id, (unit.dependencies ?? []).filter((id) => workUnitIds.has(id))]));
|
|
325
344
|
return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
|
|
326
345
|
const nodes = flattenWorkflowNodes(workflow.nodes);
|
|
327
|
-
const index =
|
|
346
|
+
const index = dependentsByNeedWithContainment(workflow.nodes, nodes);
|
|
328
347
|
const nodesByWorkUnit = nodesByAssignedWorkUnit(nodes);
|
|
329
|
-
return nodes.filter((node) => isImplementationNode(config, node)).flatMap((node) => {
|
|
348
|
+
return nodes.filter((node) => isImplementationNode(config, node) || isSelectCandidateNode(node)).flatMap((node) => {
|
|
330
349
|
const dependentId = node.task_context?.id;
|
|
331
350
|
if (!dependentId) return [];
|
|
332
351
|
return (dependenciesByUnit.get(dependentId) ?? []).flatMap((prerequisiteId) => {
|
|
@@ -336,6 +355,9 @@ function workUnitDependencyIssues(config, artifact, workUnits) {
|
|
|
336
355
|
});
|
|
337
356
|
});
|
|
338
357
|
}
|
|
358
|
+
function isSelectCandidateNode(node) {
|
|
359
|
+
return node.kind === "builtin" && node.builtin === "select-candidate";
|
|
360
|
+
}
|
|
339
361
|
function nodesByAssignedWorkUnit(nodes) {
|
|
340
362
|
const grouped = /* @__PURE__ */ new Map();
|
|
341
363
|
for (const node of nodes) {
|
|
@@ -140,8 +140,13 @@ interface RunnerCommandPayloadValidationIssue {
|
|
|
140
140
|
message: string;
|
|
141
141
|
path: string;
|
|
142
142
|
}
|
|
143
|
-
declare
|
|
143
|
+
declare const RunnerCommandPayloadValidationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
144
|
+
readonly _tag: "RunnerCommandPayloadValidationError";
|
|
145
|
+
} & Readonly<A>;
|
|
146
|
+
declare class RunnerCommandPayloadValidationError extends RunnerCommandPayloadValidationError_base<{
|
|
147
|
+
readonly message: string;
|
|
144
148
|
readonly issues: RunnerCommandPayloadValidationIssue[];
|
|
149
|
+
}> {
|
|
145
150
|
constructor(message: string, issues: RunnerCommandPayloadValidationIssue[]);
|
|
146
151
|
}
|
|
147
152
|
interface BuildRunnerCommandPayloadOptions {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseJson } from "./safe-json.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { Data } from "effect";
|
|
3
4
|
import { readFileSync } from "node:fs";
|
|
4
5
|
import parseGitUrl from "git-url-parse";
|
|
5
6
|
//#region src/runner-command-contract.ts
|
|
@@ -71,12 +72,12 @@ const runnerCommandPayloadSchema = z.object({
|
|
|
71
72
|
task: runnerTaskSchema,
|
|
72
73
|
workflow: runnerWorkflowIdentitySchema
|
|
73
74
|
}).strict();
|
|
74
|
-
var RunnerCommandPayloadValidationError = class extends
|
|
75
|
-
issues;
|
|
75
|
+
var RunnerCommandPayloadValidationError = class extends Data.TaggedError("RunnerCommandPayloadValidationError") {
|
|
76
76
|
constructor(message, issues) {
|
|
77
|
-
super(
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
super({
|
|
78
|
+
message,
|
|
79
|
+
issues
|
|
80
|
+
});
|
|
80
81
|
}
|
|
81
82
|
};
|
|
82
83
|
function resolveRunnerEventSinkAuthToken(options) {
|
|
@@ -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>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mapRuntimeEventToRunnerEventRecords } from "./runner-command-contract.js";
|
|
2
|
+
import { Data } from "effect";
|
|
2
3
|
import ky, { isHTTPError } from "ky";
|
|
3
4
|
//#region src/runner-event-sink.ts
|
|
4
5
|
const DEFAULT_BATCH_SIZE = 50;
|
|
@@ -20,12 +21,12 @@ const RETRYABLE_STATUS_CODES = [
|
|
|
20
21
|
510,
|
|
21
22
|
511
|
|
22
23
|
];
|
|
23
|
-
var EventSinkHttpError = class extends
|
|
24
|
-
status;
|
|
24
|
+
var EventSinkHttpError = class extends Data.TaggedError("EventSinkHttpError") {
|
|
25
25
|
constructor(status, message) {
|
|
26
|
-
super(
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
super({
|
|
27
|
+
status,
|
|
28
|
+
message
|
|
29
|
+
});
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
function createRunnerEventSink(options) {
|
package/dist/runner.d.ts
CHANGED
|
@@ -80,7 +80,12 @@ interface RunnerLaunchInput {
|
|
|
80
80
|
prompt: string;
|
|
81
81
|
worktreePath: string;
|
|
82
82
|
}
|
|
83
|
-
declare
|
|
83
|
+
declare const RunnerCapabilityError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
84
|
+
readonly _tag: "RunnerCapabilityError";
|
|
85
|
+
} & Readonly<A>;
|
|
86
|
+
declare class RunnerCapabilityError extends RunnerCapabilityError_base<{
|
|
87
|
+
readonly message: string;
|
|
88
|
+
}> {
|
|
84
89
|
constructor(message: string);
|
|
85
90
|
}
|
|
86
91
|
/**
|
package/dist/runner.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
1
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
//#region src/runner.ts
|
|
5
|
-
var RunnerCapabilityError = class extends
|
|
6
|
+
var RunnerCapabilityError = class extends Data.TaggedError("RunnerCapabilityError") {
|
|
6
7
|
constructor(message) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = "RunnerCapabilityError";
|
|
8
|
+
super({ message });
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
const OPENCODE_EXCLUDES = [
|
|
@@ -6,9 +6,10 @@ import { normalizeJsonSource, readJsonSchemaSource, validateJsonSchemaSource } f
|
|
|
6
6
|
import "../json-validation/index.js";
|
|
7
7
|
import { emit, emitAgentFinish, emitAgentStart } from "../events/events.js";
|
|
8
8
|
import "../events/index.js";
|
|
9
|
+
import { estimateTokens } from "../../token-estimator.js";
|
|
10
|
+
import { buildRepoMapContext } from "../../context/repo-map.js";
|
|
9
11
|
import { gatewayServerForProfile } from "../../mcp/gateway.js";
|
|
10
12
|
import { selectNodeModel } from "../../model-resolver.js";
|
|
11
|
-
import { estimateTokens } from "../../token-estimator.js";
|
|
12
13
|
import { handoffFinalizerPrompt, parseHandoff, renderHandoff, synthesizeMinimalHandoff } from "../handoff.js";
|
|
13
14
|
import { readFileSync } from "node:fs";
|
|
14
15
|
//#region src/runtime/agent-node/agent-node.ts
|
|
@@ -18,7 +19,7 @@ async function executeAgentNode(node, context, attempt) {
|
|
|
18
19
|
exitCode: 1,
|
|
19
20
|
output: ""
|
|
20
21
|
};
|
|
21
|
-
const prompt = renderAgentPrompt(node, context);
|
|
22
|
+
const prompt = await renderAgentPrompt(node, context);
|
|
22
23
|
const decision = decideNodeModel(prompt, node, context.config.token_budget);
|
|
23
24
|
if (decision.overBudget) return {
|
|
24
25
|
evidence: [
|
|
@@ -307,11 +308,28 @@ function createOutputRepairPlan(inputs) {
|
|
|
307
308
|
function normalizeAgentOutput(plan, stdout) {
|
|
308
309
|
return normalizeRunnerOutput(plan, stdout);
|
|
309
310
|
}
|
|
310
|
-
function
|
|
311
|
+
async function repoMapSection(node, context) {
|
|
312
|
+
const repoMap = context.config.repo_map;
|
|
313
|
+
if (!repoMap?.enabled) return "";
|
|
314
|
+
try {
|
|
315
|
+
return (await buildRepoMapContext({
|
|
316
|
+
artifacts: node.needs.flatMap((need) => context.nodeStateStore.handoff(need)?.artifacts ?? []),
|
|
317
|
+
taskText: context.task,
|
|
318
|
+
tokenBudget: repoMap.token_budget,
|
|
319
|
+
worktreePath: context.worktreePath
|
|
320
|
+
})).context;
|
|
321
|
+
} catch {
|
|
322
|
+
return "";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function renderAgentPrompt(node, context) {
|
|
311
326
|
const profile = node.profile ? context.config.profiles[node.profile] : void 0;
|
|
327
|
+
const instructions = profile ? readInstructions(context.worktreePath, profile.instructions) : "";
|
|
328
|
+
const repoMap = await repoMapSection(node, context);
|
|
312
329
|
return [
|
|
313
|
-
|
|
330
|
+
instructions.trim(),
|
|
314
331
|
"",
|
|
332
|
+
repoMap,
|
|
315
333
|
`Task: ${context.task}`,
|
|
316
334
|
`Workflow: ${context.workflowId}`,
|
|
317
335
|
`Node: ${node.id}`,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { runWorkflowLifecycle } from "./workflow-lifecycle.js";
|
|
2
|
+
import { runWorkflowScheduler } from "./scheduler.js";
|
|
3
|
+
//#region src/runtime/local-scheduler.ts
|
|
4
|
+
/**
|
|
5
|
+
* The local, in-process scheduler seam. It owns workflow lifecycle hooks and
|
|
6
|
+
* delegates the topological fan-out/gating loop to `runWorkflowScheduler`,
|
|
7
|
+
* passing the durability journal (PIPE-83.10) through to it.
|
|
8
|
+
*/
|
|
9
|
+
var LocalScheduler = class {
|
|
10
|
+
options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
async runWorkflow(plan, context) {
|
|
15
|
+
const options = this.options;
|
|
16
|
+
if (!options) throw new Error("LocalScheduler requires runtime options to run workflow");
|
|
17
|
+
return (await runWorkflowLifecycle({
|
|
18
|
+
buildResult: options.buildResult,
|
|
19
|
+
emitWorkflowPlanned: () => options.emitWorkflowPlanned(context),
|
|
20
|
+
emitWorkflowStarted: () => options.emitWorkflowStarted(context),
|
|
21
|
+
executeWorkflow: () => runWorkflowScheduler({
|
|
22
|
+
failFast: plan.execution.failFast,
|
|
23
|
+
fanOutWidth: context.config.token_budget?.fan_out_width,
|
|
24
|
+
isCancelled: () => options.isCancelled(context),
|
|
25
|
+
journal: options.resolveJournal?.(context),
|
|
26
|
+
markNodeReady: (nodeId) => options.markNodeReady(nodeId, context),
|
|
27
|
+
maxParallelNodes: context.maxParallelNodes,
|
|
28
|
+
nodes: plan.topologicalOrder.map((node) => ({
|
|
29
|
+
category: node.category,
|
|
30
|
+
dependents: node.dependents,
|
|
31
|
+
id: node.id,
|
|
32
|
+
index: node.index,
|
|
33
|
+
needs: node.needs
|
|
34
|
+
})),
|
|
35
|
+
runNode: (nodeId) => options.executeNode(nodeId, context),
|
|
36
|
+
shouldContinueAfterNodeResult: (result) => options.shouldContinueAfterNodeResult(result, context),
|
|
37
|
+
skipNode: (nodeId, reason) => options.skipNode(nodeId, reason, context)
|
|
38
|
+
}),
|
|
39
|
+
isCancelled: () => options.isCancelled(context),
|
|
40
|
+
runWorkflowHook: (event, failure) => options.runWorkflowHook(event, failure, context)
|
|
41
|
+
})).result;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { LocalScheduler };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
1
2
|
import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk";
|
|
2
3
|
//#region src/runtime/opencode-server.ts
|
|
3
4
|
const DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
|
|
4
|
-
var OpencodeServerStartupError = class extends
|
|
5
|
+
var OpencodeServerStartupError = class extends Data.TaggedError("OpencodeServerStartupError") {
|
|
5
6
|
constructor(message, options) {
|
|
6
|
-
super(
|
|
7
|
-
|
|
7
|
+
super({
|
|
8
|
+
message,
|
|
9
|
+
cause: options?.cause
|
|
10
|
+
});
|
|
8
11
|
}
|
|
9
12
|
};
|
|
10
13
|
async function openOpencodeServer(options) {
|