@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.
Files changed (36) hide show
  1. package/defaults/pipeline.yaml +25 -0
  2. package/dist/argo-graph.js +7 -7
  3. package/dist/bench/eval-report.js +27 -0
  4. package/dist/cli/program.js +19 -3
  5. package/dist/commands/bench-command.js +18 -0
  6. package/dist/config/load.js +17 -0
  7. package/dist/config/schemas.d.ts +22 -5
  8. package/dist/config/schemas.js +20 -7
  9. package/dist/context/repo-map.js +203 -0
  10. package/dist/install-commands/opencode.js +10 -1
  11. package/dist/mcp/gateway.js +3 -3
  12. package/dist/moka-submit.d.ts +6 -6
  13. package/dist/pipeline-init.js +18 -12
  14. package/dist/pipeline-runtime.js +12 -1
  15. package/dist/planning/compile.d.ts +8 -3
  16. package/dist/planning/compile.js +7 -7
  17. package/dist/planning/generate.d.ts +6 -1
  18. package/dist/planning/generate.js +29 -7
  19. package/dist/runner-command-contract.d.ts +6 -1
  20. package/dist/runner-command-contract.js +6 -5
  21. package/dist/runner-event-schema.d.ts +6 -6
  22. package/dist/runner-event-sink.js +6 -5
  23. package/dist/runner.d.ts +6 -1
  24. package/dist/runner.js +3 -3
  25. package/dist/runtime/agent-node/agent-node.js +22 -4
  26. package/dist/runtime/local-scheduler.js +45 -0
  27. package/dist/runtime/opencode-server.js +6 -3
  28. package/dist/runtime/parallel-node/parallel-node.js +77 -58
  29. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +49 -4
  30. package/dist/runtime/run-journal.js +21 -0
  31. package/dist/runtime/scheduler.js +122 -93
  32. package/dist/runtime/select-candidate/select-candidate.js +13 -1
  33. package/dist/runtime/services/worktree-service.js +18 -0
  34. package/dist/schedule/passes/candidates.js +17 -8
  35. package/docs/config-architecture.md +105 -0
  36. package/package.json +7 -2
@@ -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 DEFAULT_SKILL_INSTALL_ARGS = [
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
- async function installDefaultSkills(cwd) {
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
- ...DEFAULT_SKILL_INSTALL_ARGS
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
- await (options.skillInstaller ?? installDefaultSkills)(cwd);
37
- return { files: (await installCommands({
38
- cwd,
39
- force: true,
40
- host: "all"
41
- })).items.map((item) => item.path) };
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");
@@ -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 { LocalScheduler } from "./runtime/scheduler.js";
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 class WorkflowPlannerError extends Error {
11
- code: WorkflowPlannerErrorCode;
12
- issues: WorkflowPlannerIssue[];
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 {
@@ -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 Error {
6
- code;
7
- issues;
6
+ var WorkflowPlannerError = class extends Data.TaggedError("WorkflowPlannerError") {
8
7
  constructor(code, message, issues = []) {
9
- super(message);
10
- this.name = "WorkflowPlannerError";
11
- this.code = code;
12
- this.issues = issues;
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 class ScheduleArtifactError extends Error {
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 Error {
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: dependentsByNeed(nodes),
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 === "drain-merge");
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 = dependentsByNeed(nodes);
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 class RunnerCommandPayloadValidationError extends Error {
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 Error {
75
- issues;
75
+ var RunnerCommandPayloadValidationError = class extends Data.TaggedError("RunnerCommandPayloadValidationError") {
76
76
  constructor(message, issues) {
77
- super(message);
78
- this.name = "RunnerCommandPayloadValidationError";
79
- this.issues = issues;
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 Error {
24
- status;
24
+ var EventSinkHttpError = class extends Data.TaggedError("EventSinkHttpError") {
25
25
  constructor(status, message) {
26
- super(message);
27
- this.name = "EventSinkHttpError";
28
- this.status = status;
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 class RunnerCapabilityError extends Error {
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 Error {
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 renderAgentPrompt(node, context) {
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
- (profile ? readInstructions(context.worktreePath, profile.instructions) : "").trim(),
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 Error {
5
+ var OpencodeServerStartupError = class extends Data.TaggedError("OpencodeServerStartupError") {
5
6
  constructor(message, options) {
6
- super(message, options);
7
- this.name = "OpencodeServerStartupError";
7
+ super({
8
+ message,
9
+ cause: options?.cause
10
+ });
8
11
  }
9
12
  };
10
13
  async function openOpencodeServer(options) {