@acpus/core 0.6.0 → 0.7.0-alpha.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.
Files changed (210) hide show
  1. package/README.md +128 -4
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/graph/builder.d.ts +103 -0
  4. package/dist/graph/builder.d.ts.map +1 -0
  5. package/dist/graph/builder.js +209 -0
  6. package/dist/graph/builder.js.map +1 -0
  7. package/dist/graph/lowering.d.ts +8 -0
  8. package/dist/graph/lowering.d.ts.map +1 -0
  9. package/dist/graph/lowering.js +45 -0
  10. package/dist/graph/lowering.js.map +1 -0
  11. package/dist/graph/refs.d.ts +11 -0
  12. package/dist/graph/refs.d.ts.map +1 -0
  13. package/dist/graph/refs.js +12 -0
  14. package/dist/graph/refs.js.map +1 -0
  15. package/dist/graph/scope.d.ts +21 -0
  16. package/dist/graph/scope.d.ts.map +1 -0
  17. package/dist/graph/scope.js +22 -0
  18. package/dist/graph/scope.js.map +1 -0
  19. package/dist/index.d.ts +11 -25
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +4 -15
  22. package/dist/index.js.map +1 -1
  23. package/dist/internal/symbols.d.ts +9 -0
  24. package/dist/internal/symbols.d.ts.map +1 -0
  25. package/dist/internal/symbols.js +9 -0
  26. package/dist/internal/symbols.js.map +1 -0
  27. package/dist/internal/type-utils.d.ts +7 -0
  28. package/dist/internal/type-utils.d.ts.map +1 -0
  29. package/dist/internal/type-utils.js +8 -0
  30. package/dist/internal/type-utils.js.map +1 -0
  31. package/dist/ir/types.d.ts +218 -0
  32. package/dist/ir/types.d.ts.map +1 -0
  33. package/dist/ir/types.js.map +1 -0
  34. package/dist/ir/validator.d.ts +3 -0
  35. package/dist/ir/validator.d.ts.map +1 -0
  36. package/dist/ir/validator.js +671 -0
  37. package/dist/ir/validator.js.map +1 -0
  38. package/dist/ir.d.ts +3 -0
  39. package/dist/ir.d.ts.map +1 -0
  40. package/dist/ir.js +2 -0
  41. package/dist/ir.js.map +1 -0
  42. package/dist/nodes/composite/fanout.d.ts +24 -0
  43. package/dist/nodes/composite/fanout.d.ts.map +1 -0
  44. package/dist/nodes/composite/fanout.js +24 -0
  45. package/dist/nodes/composite/fanout.js.map +1 -0
  46. package/dist/nodes/composite/if.d.ts +12 -0
  47. package/dist/nodes/composite/if.d.ts.map +1 -0
  48. package/dist/nodes/composite/if.js +13 -0
  49. package/dist/nodes/composite/if.js.map +1 -0
  50. package/dist/nodes/composite/loop.d.ts +17 -0
  51. package/dist/nodes/composite/loop.d.ts.map +1 -0
  52. package/dist/nodes/composite/loop.js +22 -0
  53. package/dist/nodes/composite/loop.js.map +1 -0
  54. package/dist/nodes/composite/parallel.d.ts +29 -0
  55. package/dist/nodes/composite/parallel.d.ts.map +1 -0
  56. package/dist/nodes/composite/parallel.js +18 -0
  57. package/dist/nodes/composite/parallel.js.map +1 -0
  58. package/dist/nodes/composite/shared.d.ts +38 -0
  59. package/dist/nodes/composite/shared.d.ts.map +1 -0
  60. package/dist/nodes/composite/shared.js +2 -0
  61. package/dist/nodes/composite/shared.js.map +1 -0
  62. package/dist/nodes/composite/switch.d.ts +22 -0
  63. package/dist/nodes/composite/switch.d.ts.map +1 -0
  64. package/dist/nodes/composite/switch.js +12 -0
  65. package/dist/nodes/composite/switch.js.map +1 -0
  66. package/dist/nodes/control/assert.d.ts +10 -0
  67. package/dist/nodes/control/assert.d.ts.map +1 -0
  68. package/dist/nodes/control/assert.js +14 -0
  69. package/dist/nodes/control/assert.js.map +1 -0
  70. package/dist/nodes/leaf/agent.d.ts +63 -0
  71. package/dist/nodes/leaf/agent.d.ts.map +1 -0
  72. package/dist/nodes/leaf/agent.js +56 -0
  73. package/dist/nodes/leaf/agent.js.map +1 -0
  74. package/dist/nodes/leaf/shared.d.ts +11 -0
  75. package/dist/nodes/leaf/shared.d.ts.map +1 -0
  76. package/dist/nodes/leaf/shared.js +2 -0
  77. package/dist/nodes/leaf/shared.js.map +1 -0
  78. package/dist/nodes/leaf/signal.d.ts +27 -0
  79. package/dist/nodes/leaf/signal.d.ts.map +1 -0
  80. package/dist/nodes/leaf/signal.js +18 -0
  81. package/dist/nodes/leaf/signal.js.map +1 -0
  82. package/dist/nodes/leaf/task.d.ts +71 -0
  83. package/dist/nodes/leaf/task.d.ts.map +1 -0
  84. package/dist/nodes/leaf/task.js +88 -0
  85. package/dist/nodes/leaf/task.js.map +1 -0
  86. package/dist/runtime/dollar.d.ts +42 -0
  87. package/dist/runtime/dollar.d.ts.map +1 -0
  88. package/dist/runtime/dollar.js +82 -0
  89. package/dist/runtime/dollar.js.map +1 -0
  90. package/dist/runtime/secret.d.ts +10 -0
  91. package/dist/runtime/secret.d.ts.map +1 -0
  92. package/dist/runtime/secret.js +12 -0
  93. package/dist/runtime/secret.js.map +1 -0
  94. package/dist/runtime/task-context.d.ts +28 -0
  95. package/dist/runtime/task-context.d.ts.map +1 -0
  96. package/dist/runtime/task-context.js +2 -0
  97. package/dist/runtime/task-context.js.map +1 -0
  98. package/dist/runtime.d.ts +5 -0
  99. package/dist/runtime.d.ts.map +1 -0
  100. package/dist/runtime.js +3 -0
  101. package/dist/runtime.js.map +1 -0
  102. package/dist/schema/index.d.ts +6 -2
  103. package/dist/schema/index.d.ts.map +1 -1
  104. package/dist/schema/index.js +3 -2
  105. package/dist/schema/index.js.map +1 -1
  106. package/dist/schema/lower.d.ts +25 -0
  107. package/dist/schema/lower.d.ts.map +1 -0
  108. package/dist/schema/lower.js +198 -0
  109. package/dist/schema/lower.js.map +1 -0
  110. package/dist/schema/validate.d.ts +18 -0
  111. package/dist/schema/validate.d.ts.map +1 -0
  112. package/dist/schema/validate.js +22 -0
  113. package/dist/schema/validate.js.map +1 -0
  114. package/dist/schema/zod.d.ts +17 -0
  115. package/dist/schema/zod.d.ts.map +1 -0
  116. package/dist/schema/zod.js +15 -0
  117. package/dist/schema/zod.js.map +1 -0
  118. package/dist/schema.d.ts +3 -0
  119. package/dist/schema.d.ts.map +1 -0
  120. package/dist/schema.js +2 -0
  121. package/dist/schema.js.map +1 -0
  122. package/dist/template/template.d.ts +5 -0
  123. package/dist/template/template.d.ts.map +1 -0
  124. package/dist/template/template.js +10 -0
  125. package/dist/template/template.js.map +1 -0
  126. package/dist/workflow.d.ts +3 -0
  127. package/dist/workflow.d.ts.map +1 -0
  128. package/dist/workflow.js +2 -0
  129. package/dist/workflow.js.map +1 -0
  130. package/package.json +35 -20
  131. package/dist/agent-overrides.d.ts +0 -33
  132. package/dist/agent-overrides.d.ts.map +0 -1
  133. package/dist/agent-overrides.js +0 -178
  134. package/dist/agent-overrides.js.map +0 -1
  135. package/dist/cel-ast.d.ts +0 -39
  136. package/dist/cel-ast.d.ts.map +0 -1
  137. package/dist/cel-ast.js +0 -157
  138. package/dist/cel-ast.js.map +0 -1
  139. package/dist/cel-environment.d.ts +0 -6
  140. package/dist/cel-environment.d.ts.map +0 -1
  141. package/dist/cel-environment.js +0 -44
  142. package/dist/cel-environment.js.map +0 -1
  143. package/dist/compiler.d.ts +0 -4
  144. package/dist/compiler.d.ts.map +0 -1
  145. package/dist/compiler.js +0 -702
  146. package/dist/compiler.js.map +0 -1
  147. package/dist/composite-contract.d.ts +0 -46
  148. package/dist/composite-contract.d.ts.map +0 -1
  149. package/dist/composite-contract.js +0 -97
  150. package/dist/composite-contract.js.map +0 -1
  151. package/dist/diagnostics.d.ts +0 -9
  152. package/dist/diagnostics.d.ts.map +0 -1
  153. package/dist/diagnostics.js +0 -16
  154. package/dist/diagnostics.js.map +0 -1
  155. package/dist/duration.d.ts +0 -13
  156. package/dist/duration.d.ts.map +0 -1
  157. package/dist/duration.js +0 -32
  158. package/dist/duration.js.map +0 -1
  159. package/dist/expression-scope.d.ts +0 -23
  160. package/dist/expression-scope.d.ts.map +0 -1
  161. package/dist/expression-scope.js +0 -545
  162. package/dist/expression-scope.js.map +0 -1
  163. package/dist/expressions-shared.d.ts +0 -19
  164. package/dist/expressions-shared.d.ts.map +0 -1
  165. package/dist/expressions-shared.js +0 -27
  166. package/dist/expressions-shared.js.map +0 -1
  167. package/dist/expressions.d.ts +0 -9
  168. package/dist/expressions.d.ts.map +0 -1
  169. package/dist/expressions.js +0 -95
  170. package/dist/expressions.js.map +0 -1
  171. package/dist/hash.d.ts +0 -19
  172. package/dist/hash.d.ts.map +0 -1
  173. package/dist/hash.js +0 -120
  174. package/dist/hash.js.map +0 -1
  175. package/dist/hook-validation.d.ts +0 -9
  176. package/dist/hook-validation.d.ts.map +0 -1
  177. package/dist/hook-validation.js +0 -73
  178. package/dist/hook-validation.js.map +0 -1
  179. package/dist/hooks.d.ts +0 -150
  180. package/dist/hooks.d.ts.map +0 -1
  181. package/dist/hooks.js +0 -18
  182. package/dist/hooks.js.map +0 -1
  183. package/dist/schedule.d.ts +0 -3
  184. package/dist/schedule.d.ts.map +0 -1
  185. package/dist/schedule.js +0 -21
  186. package/dist/schedule.js.map +0 -1
  187. package/dist/schema/dsl.d.ts +0 -34
  188. package/dist/schema/dsl.d.ts.map +0 -1
  189. package/dist/schema/dsl.js +0 -231
  190. package/dist/schema/dsl.js.map +0 -1
  191. package/dist/schema/helpers.d.ts +0 -24
  192. package/dist/schema/helpers.d.ts.map +0 -1
  193. package/dist/schema/helpers.js +0 -61
  194. package/dist/schema/helpers.js.map +0 -1
  195. package/dist/schema-validator.d.ts +0 -29
  196. package/dist/schema-validator.d.ts.map +0 -1
  197. package/dist/schema-validator.js +0 -666
  198. package/dist/schema-validator.js.map +0 -1
  199. package/dist/source-resolver.d.ts +0 -22
  200. package/dist/source-resolver.d.ts.map +0 -1
  201. package/dist/source-resolver.js +0 -43
  202. package/dist/source-resolver.js.map +0 -1
  203. package/dist/types.d.ts +0 -126
  204. package/dist/types.d.ts.map +0 -1
  205. package/dist/types.js.map +0 -1
  206. package/dist/workflow-schema.d.ts +0 -10
  207. package/dist/workflow-schema.d.ts.map +0 -1
  208. package/dist/workflow-schema.js +0 -523
  209. package/dist/workflow-schema.js.map +0 -1
  210. /package/dist/{types.js → ir/types.js} +0 -0
package/dist/compiler.js DELETED
@@ -1,702 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { Ajv } from "ajv";
3
- import { parse as parseYaml } from "yaml";
4
- import { realpathSync } from "node:fs";
5
- import { DiagnosticBag } from "./diagnostics.js";
6
- import { outputMergeFor, keyTemplateForKind } from "./composite-contract.js";
7
- import { createExpressionCollector } from "./expressions.js";
8
- import { validateScopedExpressions } from "./expression-scope.js";
9
- import { createSchedule } from "./schedule.js";
10
- import { compileSchemaDsl } from "./schema/index.js";
11
- import { isRecord } from "./schema/helpers.js";
12
- import { parseDurationMs } from "./duration.js";
13
- import { validateWithSchema } from "./schema-validator.js";
14
- const ajv = new Ajv({ allErrors: true, strict: false });
15
- const GENERATED_ID_PREFIX = "$";
16
- const SAFE_AUTHOR_ID = /^[A-Za-z_][A-Za-z0-9_-]*$/u;
17
- export function compileWorkflow(source, options = {}) {
18
- const diagnostics = new DiagnosticBag();
19
- const sourcePath = normalizeSourcePath(options.sourcePath);
20
- const parsed = parseSource(source, diagnostics);
21
- if (!isWorkflowSpec(parsed)) {
22
- diagnostics.error("SPEC_SHAPE", "Workflow spec must be a YAML object with version, name, and workflow.steps.", "$");
23
- return result(false, diagnostics);
24
- }
25
- const compileOptions = { ...options, sourcePath };
26
- const expanded = expandIncludes(parsed, compileOptions, diagnostics, new Set());
27
- validateWithSchema(expanded, diagnostics);
28
- const stepIds = collectStepIds(expanded.workflow.steps, diagnostics);
29
- const context = {
30
- diagnostics,
31
- stepIds,
32
- agents: isRecord(expanded.agents) ? expanded.agents : {},
33
- sourcePath
34
- };
35
- validateSpec(expanded, context);
36
- const rootChildren = compileSteps(expanded.workflow.steps, ["workflow"], "$.workflow.steps", context);
37
- const root = {
38
- id: "workflow",
39
- kind: "pipeline",
40
- nodePath: ["workflow"],
41
- keyTemplate: keyTemplate(["workflow"]),
42
- children: rootChildren,
43
- metadata: {
44
- implicit: true
45
- }
46
- };
47
- const expressionCollector = createExpressionCollector(diagnostics, stepIds);
48
- expressionCollector.visit(expanded.input ?? {}, "$.input");
49
- expressionCollector.visit(expanded.agents ?? {}, "$.agents");
50
- expressionCollector.visit(expanded.workflow.steps, "$.workflow.steps");
51
- expressionCollector.visit(expanded.outputs ?? {}, "$.outputs");
52
- // Scope-aware validation over the compiled IR: field paths against declared
53
- // schemas, local-root scope, step visibility, and shell-safety in cmd. This
54
- // consumes the schemas already compiled onto node metadata. Fail-quiet.
55
- validateScopedExpressions({
56
- root,
57
- inputSchema: isRecord(expanded.input) ? expanded.input : {},
58
- outputs: isRecord(expanded.outputs) ? expanded.outputs : {},
59
- agents: isRecord(expanded.agents) ? expanded.agents : {},
60
- allStepIds: stepIds,
61
- diagnostics
62
- });
63
- const ok = !diagnostics.hasErrors(options.strict);
64
- if (!ok) {
65
- return result(false, diagnostics);
66
- }
67
- const ir = {
68
- irVersion: 1,
69
- astVersion: 1,
70
- source: {
71
- path: sourcePath,
72
- digest: digest(source)
73
- },
74
- name: expanded.name,
75
- description: expanded.description,
76
- input: expanded.input ?? {},
77
- agents: expanded.agents ?? {},
78
- root,
79
- outputs: expanded.outputs ?? {},
80
- expressions: expressionCollector.expressions
81
- };
82
- return {
83
- ok: true,
84
- diagnostics: diagnostics.diagnostics,
85
- ir,
86
- schedule: createSchedule(ir)
87
- };
88
- }
89
- function normalizeSourcePath(sourcePath) {
90
- if (!sourcePath)
91
- return undefined;
92
- try {
93
- return realpathSync.native(sourcePath);
94
- }
95
- catch {
96
- return sourcePath;
97
- }
98
- }
99
- export function lintWorkflow(source, options = {}) {
100
- const compiled = compileWorkflow(source, options);
101
- return {
102
- ok: compiled.ok,
103
- diagnostics: compiled.diagnostics
104
- };
105
- }
106
- function parseSource(source, diagnostics) {
107
- try {
108
- return parseYaml(source);
109
- }
110
- catch (error) {
111
- diagnostics.error("YAML_PARSE", `Invalid YAML: ${errorMessage(error)}`, "$");
112
- return undefined;
113
- }
114
- }
115
- function expandIncludes(spec, options, diagnostics, seen) {
116
- const steps = [];
117
- for (const [index, step] of spec.workflow.steps.entries()) {
118
- if (typeof step.include === "string") {
119
- if (!options.includeResolver) {
120
- diagnostics.error("INCLUDE_RESOLVER", "Include is present but no include resolver was provided.", `$.workflow.steps[${index}].include`);
121
- continue;
122
- }
123
- if (seen.has(step.include)) {
124
- diagnostics.error("INCLUDE_CYCLE", `Include cycle detected for '${step.include}'.`, `$.workflow.steps[${index}].include`);
125
- continue;
126
- }
127
- let includedSource;
128
- try {
129
- includedSource = options.includeResolver(step.include, options.sourcePath);
130
- }
131
- catch (error) {
132
- diagnostics.error("INCLUDE_RESOLUTION", errorMessage(error), `$.workflow.steps[${index}].include`);
133
- continue;
134
- }
135
- const includedParsed = parseSource(includedSource, diagnostics);
136
- if (!isWorkflowSpec(includedParsed)) {
137
- diagnostics.error("INCLUDE_SHAPE", `Included spec '${step.include}' is not a valid workflow spec.`, `$.workflow.steps[${index}].include`);
138
- continue;
139
- }
140
- const nextSeen = new Set(seen);
141
- nextSeen.add(step.include);
142
- const expanded = expandIncludes(includedParsed, options, diagnostics, nextSeen);
143
- steps.push(...expanded.workflow.steps);
144
- continue;
145
- }
146
- steps.push(step);
147
- }
148
- return {
149
- ...spec,
150
- workflow: {
151
- ...spec.workflow,
152
- steps
153
- }
154
- };
155
- }
156
- function validateSpec(spec, context) {
157
- const { diagnostics } = context;
158
- // Compile input flat-map to JSON Schema
159
- if (isRecord(spec.input)) {
160
- const { schema, errors } = compileSchemaDsl(spec.input, { strictObjectKeys: false });
161
- for (const err of errors) {
162
- diagnostics.error("INPUT_SHAPE", err.message, `$.input.${err.field}`);
163
- }
164
- if (errors.length === 0) {
165
- validateJsonSchema(schema, "$.input", context);
166
- }
167
- // Replace input with compiled schema in the spec so the IR stores the JSON Schema
168
- spec.input = schema;
169
- }
170
- // Semantic checks not covered by JSON Schema
171
- for (const [agentName, agent] of Object.entries(spec.agents ?? {})) {
172
- if (!isRecord(agent))
173
- continue; // Schema handles shape errors
174
- // AGENT_REF: check that referenced agents actually exist (cross-reference)
175
- // This is done in validateAgentStep at step level
176
- }
177
- }
178
- function compileSteps(steps, parentPath, path, context) {
179
- return steps.map((step, index) => compileStep(step, parentPath, `${path}[${index}]`, context));
180
- }
181
- function compileStep(step, parentPath, path, context) {
182
- const id = typeof step.id === "string" && step.id.length > 0 ? step.id : `missing_${parentPath.length}_${path}`;
183
- // Defensive: Schema already validates id; keep as fallback
184
- if (typeof step.id !== "string" || step.id.length === 0) {
185
- context.diagnostics.error("STEP_ID", "Every workflow step must define a non-empty string id.", `${path}.id`);
186
- }
187
- else {
188
- validateAuthorId(step.id, "Step", `${path}.id`, context);
189
- }
190
- const nodePath = [...parentPath, id];
191
- const base = {
192
- id,
193
- nodePath,
194
- keyTemplate: keyTemplate(nodePath)
195
- };
196
- if (step.session_key !== undefined && step.run !== "agent") {
197
- context.diagnostics.error("STEP_SHAPE", "session_key is supported only on run: agent steps.", `${path}.session_key`);
198
- }
199
- if (step.run === "agent") {
200
- validateAgentStep(step, path, context);
201
- validateStepTimeout(step, path, context);
202
- const metadata = pickMetadata(step, ["run", "use", "prompt", "cwd", "session_key", "output", "retry", "timeout", "on_error", "policy"]);
203
- // Snapshot the referenced agent definition into the node so the runtime can
204
- // route to the right executor and build the acpx invocation. `type` defaults
205
- // to "builtin".
206
- const agentName = typeof step.use === "string" ? step.use : undefined;
207
- const agentSpec = agentName ? context.agents[agentName] : undefined;
208
- if (isRecord(agentSpec)) {
209
- metadata.agent = { ...agentSpec, type: agentSpec.type ?? "builtin" };
210
- }
211
- return {
212
- ...base,
213
- kind: "run.agent",
214
- metadata
215
- };
216
- }
217
- if (step.run === "program") {
218
- validateProgramStep(step, path, context);
219
- validateStepTimeout(step, path, context);
220
- const metadata = pickMetadata(step, ["run", "cmd", "env", "cwd", "capture", "expect", "output", "retry", "timeout", "on_error"]);
221
- // Normalize the default `expect.exit_code` so omitting it and explicitly
222
- // declaring `[0]` produce the same IR (and therefore the same Node
223
- // Definition Hash). Without this, authors who add `expect: { exit_code: [0] }`
224
- // for clarity would silently invalidate Forked Run inheritance.
225
- const expect = metadata.expect;
226
- if (isRecord(expect) && Array.isArray(expect.exit_code) && expect.exit_code.length === 1 && expect.exit_code[0] === 0) {
227
- delete expect.exit_code;
228
- if (Object.keys(expect).length === 0)
229
- delete metadata.expect;
230
- }
231
- return {
232
- ...base,
233
- kind: "run.program",
234
- metadata
235
- };
236
- }
237
- if (Array.isArray(step.pipeline)) {
238
- return {
239
- ...base,
240
- kind: "pipeline",
241
- children: compileSteps(asSteps(step.pipeline, `${path}.pipeline`, context), nodePath, `${path}.pipeline`, context),
242
- metadata: pickMetadata(step, ["outputs"])
243
- };
244
- }
245
- if (Array.isArray(step.parallel)) {
246
- return {
247
- ...base,
248
- kind: "parallel",
249
- keyTemplate: keyTemplateForKind("parallel", keyTemplate(nodePath)),
250
- outputMerge: outputMergeFor("parallel"),
251
- branches: compileParallelBranches(step.parallel, nodePath, `${path}.parallel`, context),
252
- metadata: pickMetadata(step, ["max_concurrency", "join"])
253
- };
254
- }
255
- if (isRecord(step.fanout)) {
256
- const fanout = step.fanout;
257
- validateFanout(fanout, path, context);
258
- const pipelineId = generatedDoId("do");
259
- const pipelinePath = [...nodePath, pipelineId];
260
- const children = Array.isArray(fanout.do) ? compileSteps(asSteps(fanout.do, `${path}.fanout.do`, context), pipelinePath, `${path}.fanout.do`, context) : [];
261
- if (!Array.isArray(fanout.do)) {
262
- context.diagnostics.error("FANOUT_DO", "fanout.do must be an array of steps.", `${path}.fanout.do`);
263
- }
264
- return {
265
- ...base,
266
- kind: "fanout",
267
- keyTemplate: keyTemplateForKind("fanout", keyTemplate(nodePath)),
268
- outputMerge: outputMergeFor("fanout"),
269
- children: [generatedPipeline(pipelineId, children, pipelinePath, `${path}.fanout.do`)],
270
- metadata: pickMetadata(fanout, ["over", "key", "max_concurrency", "join", "quorum", "success_criteria"])
271
- };
272
- }
273
- if (isRecord(step.if)) {
274
- const ifSpec = step.if;
275
- let condition;
276
- if (ifSpec.condition !== undefined) {
277
- if (typeof ifSpec.condition === "boolean") {
278
- condition = String(ifSpec.condition);
279
- }
280
- else if (typeof ifSpec.condition === "string") {
281
- condition = ifSpec.condition;
282
- }
283
- else if (typeof ifSpec.condition !== "string") {
284
- const typeDesc = ifSpec.condition === null ? "null" : typeof ifSpec.condition;
285
- context.diagnostics.error("IF_CONDITION_TYPE", `if.condition must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.if.condition`);
286
- }
287
- }
288
- const branches = [];
289
- const thenPipelineId = generatedDoId("then");
290
- const thenPipelinePath = [...nodePath, thenPipelineId];
291
- branches.push({
292
- id: "then",
293
- when: condition,
294
- whenPath: `${path}.if.condition`,
295
- child: generatedPipeline(thenPipelineId, Array.isArray(ifSpec.then) ? compileSteps(asSteps(ifSpec.then, `${path}.if.then`, context), thenPipelinePath, `${path}.if.then`, context) : [], thenPipelinePath, `${path}.if.then`)
296
- });
297
- if (Array.isArray(ifSpec.else)) {
298
- const elsePipelineId = generatedDoId("else");
299
- const elsePipelinePath = [...nodePath, elsePipelineId];
300
- branches.push({
301
- id: "else",
302
- child: generatedPipeline(elsePipelineId, compileSteps(asSteps(ifSpec.else, `${path}.if.else`, context), elsePipelinePath, `${path}.if.else`, context), elsePipelinePath, `${path}.if.else`)
303
- });
304
- }
305
- return {
306
- ...base,
307
- kind: "if",
308
- outputMerge: outputMergeFor("if"),
309
- branches,
310
- metadata: {}
311
- };
312
- }
313
- if (isRecord(step.switch)) {
314
- const switchSpec = step.switch;
315
- const branches = [];
316
- if (Array.isArray(switchSpec.cases)) {
317
- switchSpec.cases.forEach((caseSpec, caseIndex) => {
318
- if (!isRecord(caseSpec)) {
319
- context.diagnostics.error("SWITCH_CASE", "switch.cases entries must be objects.", `${path}.switch.cases[${caseIndex}]`);
320
- return;
321
- }
322
- // Coerce `when`: boolean → string, string → pass through, else error.
323
- let when;
324
- if (caseSpec.when !== undefined) {
325
- if (typeof caseSpec.when === "boolean") {
326
- when = String(caseSpec.when);
327
- }
328
- else if (typeof caseSpec.when === "string") {
329
- when = caseSpec.when;
330
- }
331
- else if (typeof caseSpec.when !== "string") {
332
- const typeDesc = caseSpec.when === null ? "null" : typeof caseSpec.when;
333
- context.diagnostics.error("SWITCH_WHEN_TYPE", `switch.case.when must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.switch.cases[${caseIndex}].when`);
334
- }
335
- }
336
- const pipelineId = generatedDoId(`case_${caseIndex + 1}`);
337
- const pipelinePath = [...nodePath, pipelineId];
338
- branches.push({
339
- id: `case_${caseIndex + 1}`,
340
- when,
341
- whenPath: `${path}.switch.cases[${caseIndex}].when`,
342
- child: generatedPipeline(pipelineId, Array.isArray(caseSpec.do) ? compileSteps(asSteps(caseSpec.do, `${path}.switch.cases[${caseIndex}].do`, context), pipelinePath, `${path}.switch.cases[${caseIndex}].do`, context) : [], pipelinePath, `${path}.switch.cases[${caseIndex}].do`)
343
- });
344
- });
345
- }
346
- else {
347
- context.diagnostics.error("SWITCH_CASES", "switch.cases must be an array.", `${path}.switch.cases`);
348
- }
349
- if (isRecord(switchSpec.default)) {
350
- const pipelineId = generatedDoId("default");
351
- const pipelinePath = [...nodePath, pipelineId];
352
- branches.push({
353
- id: "default",
354
- child: generatedPipeline(pipelineId, Array.isArray(switchSpec.default.do) ? compileSteps(asSteps(switchSpec.default.do, `${path}.switch.default.do`, context), pipelinePath, `${path}.switch.default.do`, context) : [], pipelinePath, `${path}.switch.default.do`)
355
- });
356
- }
357
- return {
358
- ...base,
359
- kind: "switch",
360
- outputMerge: outputMergeFor("switch"),
361
- branches,
362
- metadata: {}
363
- };
364
- }
365
- if (isRecord(step.loop)) {
366
- const loop = step.loop;
367
- // Coerce `until`: boolean → string, string → pass through, else error.
368
- if (loop.until !== undefined) {
369
- if (typeof loop.until === "boolean") {
370
- loop.until = String(loop.until);
371
- }
372
- else if (typeof loop.until !== "string") {
373
- const typeDesc = loop.until === null ? "null" : typeof loop.until;
374
- context.diagnostics.error("LOOP_UNTIL_TYPE", `loop.until must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.loop.until`);
375
- }
376
- }
377
- const pipelineId = generatedDoId("do");
378
- const pipelinePath = [...nodePath, pipelineId];
379
- return {
380
- ...base,
381
- kind: "loop",
382
- keyTemplate: keyTemplateForKind("loop", keyTemplate(nodePath)),
383
- outputMerge: outputMergeFor("loop"),
384
- children: [
385
- generatedPipeline(pipelineId, Array.isArray(loop.do) ? compileSteps(asSteps(loop.do, `${path}.loop.do`, context), pipelinePath, `${path}.loop.do`, context) : [], pipelinePath, `${path}.loop.do`)
386
- ],
387
- metadata: pickMetadata(loop, ["until", "max_iterations"])
388
- };
389
- }
390
- if (isRecord(step.guard)) {
391
- const guard = step.guard;
392
- if (guard.when !== undefined) {
393
- if (typeof guard.when === "boolean") {
394
- guard.when = String(guard.when);
395
- }
396
- else if (typeof guard.when !== "string") {
397
- const typeDesc = guard.when === null ? "null" : typeof guard.when;
398
- context.diagnostics.error("GUARD_WHEN_TYPE", `guard.when must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.guard.when`);
399
- }
400
- }
401
- return {
402
- ...base,
403
- kind: "guard",
404
- metadata: pickMetadata(guard, ["when", "then", "else", "message"])
405
- };
406
- }
407
- if (step.run === "signal") {
408
- // validateSignalStep owns signal timeout validation (SIGNAL_TIMEOUT); do not
409
- // also run validateStepTimeout or an invalid duration emits a duplicate
410
- // STEP_TIMEOUT for the same field.
411
- validateSignalStep(step, path, context);
412
- return {
413
- ...base,
414
- kind: "run.signal",
415
- metadata: pickMetadata(step, ["run", "prompt", "output", "timeout", "on_timeout", "default"])
416
- };
417
- }
418
- if (typeof step.subworkflow === "string") {
419
- return {
420
- ...base,
421
- kind: "subworkflow",
422
- metadata: pickMetadata(step, ["subworkflow", "input"])
423
- };
424
- }
425
- // Defensive: Schema already validates step kind; keep as fallback
426
- context.diagnostics.error("STEP_KIND", "Step must define one of run: agent, run: program, run: signal, parallel, fanout, if, switch, loop, guard, subworkflow, or include.", path);
427
- return {
428
- ...base,
429
- kind: "run.program",
430
- metadata: {}
431
- };
432
- }
433
- function validateAgentStep(step, path, context) {
434
- // AGENT_REF: cross-reference check (not possible in JSON Schema)
435
- if (typeof step.use === "string" && !Object.prototype.hasOwnProperty.call(context.agents, step.use)) {
436
- context.diagnostics.error("AGENT_REF", `run: agent step references unknown agent '${step.use}'.`, `${path}.use`);
437
- }
438
- if (step.retry !== undefined) {
439
- validateRetry(step.retry, path, context);
440
- }
441
- compileOutputSchema(step, path, context, false);
442
- }
443
- function validateProgramStep(step, path, context) {
444
- const outputSchema = compileOutputSchema(step, path, context, false);
445
- // Enforce: output schema requires capture.parse: json
446
- if (outputSchema) {
447
- const capture = step.capture;
448
- if (!capture || capture.parse !== "json") {
449
- context.diagnostics.error("OUTPUT_REQUIRES_JSON", "run: program output schema requires capture.parse: json.", `${path}.output`);
450
- }
451
- }
452
- }
453
- function validateFanout(fanout, path, context) {
454
- // Reject arrays containing non-primitive values (objects, nested arrays)
455
- // since JSON.stringify of objects produces invalid CEL syntax.
456
- // Schema validates that over is string|array; this is a runtime coercion check.
457
- if (Array.isArray(fanout.over)) {
458
- for (let i = 0; i < fanout.over.length; i++) {
459
- const el = fanout.over[i];
460
- if (el !== null && el !== undefined && typeof el === "object") {
461
- context.diagnostics.error("FANOUT_OVER_TYPE", `fanout.over array elements must be primitives (string, number, boolean, null), got ${typeof el} at index ${i}.`, `${path}.fanout.over[${i}]`);
462
- }
463
- }
464
- // Coerce array → JSON string so the IR always stores strings
465
- fanout.over = JSON.stringify(fanout.over);
466
- }
467
- }
468
- function validateSignalStep(step, path, context) {
469
- // Cross-field: a declared timeout requires an on_timeout policy.
470
- if (step.timeout !== undefined && step.on_timeout === undefined) {
471
- context.diagnostics.error("SIGNAL_ON_TIMEOUT", "signal.on_timeout must be fail or default, and is required when timeout is set.", `${path}.on_timeout`);
472
- }
473
- // Cross-field: on_timeout: default requires a literal default payload.
474
- if (step.on_timeout === "default" && !isRecord(step.default)) {
475
- context.diagnostics.error("SIGNAL_DEFAULT", "signal.default is required (as an object) when on_timeout is default.", `${path}.default`);
476
- }
477
- // Duration format validation (not expressible in JSON Schema)
478
- if (typeof step.timeout === "string") {
479
- try {
480
- parseDurationMs(step.timeout, { strict: true });
481
- }
482
- catch {
483
- context.diagnostics.error("SIGNAL_TIMEOUT", "signal.timeout must be a valid duration string (e.g. 5m, 2h, 30s).", `${path}.timeout`);
484
- }
485
- }
486
- const outputSchema = compileOutputSchema(step, path, context, true);
487
- // When `on_timeout: default`, validate the literal `default` payload at compile
488
- // time so an invalid default is a lint error, not a runtime surprise.
489
- if (step.on_timeout === "default" && isRecord(step.default) && outputSchema) {
490
- let validate;
491
- try {
492
- validate = ajv.compile(outputSchema);
493
- }
494
- catch {
495
- return;
496
- }
497
- if (!validate(step.default)) {
498
- context.diagnostics.error("SIGNAL_DEFAULT", `signal.default does not match the declared output schema: ${ajv.errorsText(validate.errors)}`, `${path}.default`);
499
- }
500
- }
501
- }
502
- function compileOutputSchema(step, path, context, strictObjectKeys) {
503
- if (!isRecord(step.output) || Object.keys(step.output).length === 0) {
504
- delete step.output;
505
- return undefined;
506
- }
507
- if ("schema" in step.output) {
508
- context.diagnostics.error("OUTPUT_SHAPE", "The 'schema' key in output is no longer supported as a JSON Schema escape hatch. Use the Acpus Schema DSL directly (e.g. output: { field: string }).", `${path}.output.schema`);
509
- }
510
- const { schema, errors } = compileSchemaDsl(step.output, { strictObjectKeys });
511
- for (const err of errors) {
512
- context.diagnostics.error("OUTPUT_SHAPE", err.message, `${path}.output.${err.field}`);
513
- }
514
- if (errors.length > 0) {
515
- delete step.output;
516
- return undefined;
517
- }
518
- validateJsonSchema(schema, `${path}.output`, context);
519
- step.output = schema;
520
- return schema;
521
- }
522
- function validateRetry(retry, path, context) {
523
- // Duration format validation for backoff (not expressible in JSON Schema)
524
- if (isRecord(retry) && retry.backoff !== undefined) {
525
- if (typeof retry.backoff === "string") {
526
- try {
527
- parseDurationMs(retry.backoff, { strict: true });
528
- }
529
- catch {
530
- context.diagnostics.error("RETRY_SHAPE", "retry.backoff must be a valid duration string.", `${path}.retry.backoff`);
531
- }
532
- }
533
- }
534
- }
535
- function validateStepTimeout(step, path, context) {
536
- if (step.timeout === undefined)
537
- return;
538
- // Duration format validation for string timeouts (not expressible in JSON Schema)
539
- if (typeof step.timeout === "string") {
540
- try {
541
- parseDurationMs(step.timeout, { strict: true });
542
- }
543
- catch {
544
- context.diagnostics.error("STEP_TIMEOUT", "step.timeout must be a valid duration string or number (ms).", `${path}.timeout`);
545
- }
546
- }
547
- }
548
- function validateJsonSchema(schema, path, context) {
549
- if (!isRecord(schema)) {
550
- context.diagnostics.error("JSON_SCHEMA_SHAPE", "Compiled schema must be a JSON Schema object.", path);
551
- return;
552
- }
553
- if (!ajv.validateSchema(schema)) {
554
- const message = ajv.errorsText(ajv.errors);
555
- context.diagnostics.error("JSON_SCHEMA_INVALID", `Invalid JSON Schema: ${message}`, path);
556
- }
557
- }
558
- function collectStepIds(steps, diagnostics) {
559
- const ids = new Set();
560
- const visit = (items, path) => {
561
- items.forEach((step, index) => {
562
- const stepPath = `${path}[${index}]`;
563
- if (typeof step.id === "string") {
564
- if (ids.has(step.id)) {
565
- diagnostics.error("STEP_ID_DUPLICATE", `Duplicate step id '${step.id}'.`, `${stepPath}.id`);
566
- }
567
- ids.add(step.id);
568
- }
569
- if (Array.isArray(step.pipeline)) {
570
- visit(asPlainSteps(step.pipeline), `${stepPath}.pipeline`);
571
- }
572
- if (Array.isArray(step.parallel)) {
573
- step.parallel.forEach((branch, branchIndex) => {
574
- if (isRecord(branch)) {
575
- if (Array.isArray(branch.do)) {
576
- visit(asPlainSteps(branch.do), `${stepPath}.parallel[${branchIndex}].do`);
577
- }
578
- }
579
- });
580
- }
581
- if (isRecord(step.fanout) && Array.isArray(step.fanout.do)) {
582
- visit(asPlainSteps(step.fanout.do), `${stepPath}.fanout.do`);
583
- }
584
- if (isRecord(step.if) && Array.isArray(step.if.then)) {
585
- visit(asPlainSteps(step.if.then), `${stepPath}.if.then`);
586
- }
587
- if (isRecord(step.if) && Array.isArray(step.if.else)) {
588
- visit(asPlainSteps(step.if.else), `${stepPath}.if.else`);
589
- }
590
- if (isRecord(step.switch) && Array.isArray(step.switch.cases)) {
591
- step.switch.cases.forEach((caseSpec, caseIndex) => {
592
- if (isRecord(caseSpec) && Array.isArray(caseSpec.do)) {
593
- visit(asPlainSteps(caseSpec.do), `${stepPath}.switch.cases[${caseIndex}].do`);
594
- }
595
- });
596
- }
597
- if (isRecord(step.switch) && isRecord(step.switch.default) && Array.isArray(step.switch.default.do)) {
598
- visit(asPlainSteps(step.switch.default.do), `${stepPath}.switch.default.do`);
599
- }
600
- if (isRecord(step.loop) && Array.isArray(step.loop.do)) {
601
- visit(asPlainSteps(step.loop.do), `${stepPath}.loop.do`);
602
- }
603
- });
604
- };
605
- visit(steps, "$.workflow.steps");
606
- return ids;
607
- }
608
- function keyTemplate(nodePath) {
609
- return {
610
- astVersion: 1,
611
- nodePath: nodePath.join("/")
612
- };
613
- }
614
- function generatedDoId(label) {
615
- return `${GENERATED_ID_PREFIX}${label}`;
616
- }
617
- function generatedPipeline(id, children, nodePath, sourcePath) {
618
- return {
619
- id,
620
- kind: "pipeline",
621
- nodePath,
622
- keyTemplate: keyTemplate(nodePath),
623
- children,
624
- metadata: {
625
- generated: true,
626
- sourcePath
627
- }
628
- };
629
- }
630
- function compileParallelBranches(value, parentPath, path, context) {
631
- const branchIds = new Set();
632
- return value.map((item, index) => {
633
- let id = `missing_${index}`;
634
- let doChildren = [];
635
- if (!isRecord(item)) {
636
- context.diagnostics.error("STEP_SHAPE", "parallel entries must be branch descriptor objects.", `${path}[${index}]`);
637
- }
638
- else {
639
- id = typeof item.id === "string" && item.id.length > 0 ? item.id : id;
640
- validateAuthorId(id, "Branch", `${path}[${index}].id`, context);
641
- const pipelineId = generatedDoId(id);
642
- const pipelinePath = [...parentPath, pipelineId];
643
- if (Array.isArray(item.do)) {
644
- doChildren = compileSteps(asSteps(item.do, `${path}[${index}].do`, context), pipelinePath, `${path}[${index}].do`, context);
645
- }
646
- if (branchIds.has(id)) {
647
- context.diagnostics.error("STEP_ID_DUPLICATE", `Duplicate parallel branch id '${id}'.`, `${path}[${index}].id`);
648
- }
649
- }
650
- branchIds.add(id);
651
- const pipelineId = generatedDoId(id);
652
- const pipelinePath = [...parentPath, pipelineId];
653
- return {
654
- id,
655
- child: generatedPipeline(pipelineId, doChildren, pipelinePath, `${path}[${index}].do`)
656
- };
657
- });
658
- }
659
- function validateAuthorId(id, label, path, context) {
660
- if (id.startsWith(GENERATED_ID_PREFIX)) {
661
- context.diagnostics.error("STEP_ID_RESERVED", `${label} id '${id}' must not use reserved internal prefix '${GENERATED_ID_PREFIX}'.`, path);
662
- return;
663
- }
664
- if (!SAFE_AUTHOR_ID.test(id)) {
665
- context.diagnostics.error("STEP_ID_INVALID", `${label} id '${id}' must match ${SAFE_AUTHOR_ID.source}.`, path);
666
- }
667
- }
668
- function pickMetadata(source, keys) {
669
- return Object.fromEntries(keys.filter((key) => key in source).map((key) => [key, source[key]]));
670
- }
671
- function asSteps(value, path, context) {
672
- return value.map((item, index) => {
673
- if (!isRecord(item)) {
674
- context.diagnostics.error("STEP_SHAPE", "Step entries must be objects.", `${path}[${index}]`);
675
- return {};
676
- }
677
- return item;
678
- });
679
- }
680
- function asPlainSteps(value) {
681
- return value.filter(isRecord);
682
- }
683
- function isWorkflowSpec(value) {
684
- return (isRecord(value) &&
685
- typeof value.version === "number" &&
686
- typeof value.name === "string" &&
687
- isRecord(value.workflow) &&
688
- Array.isArray(value.workflow.steps));
689
- }
690
- function digest(source) {
691
- return `sha256:${createHash("sha256").update(source).digest("hex")}`;
692
- }
693
- function errorMessage(error) {
694
- return error instanceof Error ? error.message : String(error);
695
- }
696
- function result(ok, diagnostics) {
697
- return {
698
- ok,
699
- diagnostics: diagnostics.diagnostics
700
- };
701
- }
702
- //# sourceMappingURL=compiler.js.map