@oxygen-agent/cli 1.696.2 → 1.717.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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/help.js +2 -0
  3. package/dist/http-client.js +11 -1
  4. package/dist/index.js +524 -86
  5. package/dist/transcript.js +2 -2
  6. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +4 -0
  7. package/node_modules/@oxygen/shared/dist/billing.d.ts +1 -1
  8. package/node_modules/@oxygen/shared/dist/billing.js +7 -6
  9. package/node_modules/@oxygen/shared/dist/dnc-identities.d.ts +53 -0
  10. package/node_modules/@oxygen/shared/dist/dnc-identities.js +175 -0
  11. package/node_modules/@oxygen/shared/dist/file-import.d.ts +36 -1
  12. package/node_modules/@oxygen/shared/dist/file-import.js +80 -1
  13. package/node_modules/@oxygen/shared/dist/image-sniff.d.ts +39 -0
  14. package/node_modules/@oxygen/shared/dist/image-sniff.js +75 -0
  15. package/node_modules/@oxygen/shared/dist/import-limits.d.ts +28 -0
  16. package/node_modules/@oxygen/shared/dist/import-limits.js +30 -0
  17. package/node_modules/@oxygen/shared/dist/index.d.ts +2 -0
  18. package/node_modules/@oxygen/shared/dist/index.js +2 -0
  19. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -4
  20. package/node_modules/@oxygen/shared/dist/mailbox-import.js +18 -10
  21. package/node_modules/@oxygen/shared/dist/member-columns.d.ts +64 -0
  22. package/node_modules/@oxygen/shared/dist/member-columns.js +111 -0
  23. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.js +1 -1
  24. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +65 -0
  25. package/node_modules/@oxygen/shared/dist/object-storage.js +100 -0
  26. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +2 -1
  27. package/node_modules/@oxygen/shared/dist/plan-limits.js +12 -2
  28. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +9 -0
  29. package/node_modules/@oxygen/shared/dist/spend-safety.js +10 -0
  30. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  31. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  32. package/node_modules/@oxygen/shared/package.json +20 -0
  33. package/node_modules/@oxygen/workflows/dist/graph/lint.js +79 -11
  34. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +679 -0
  35. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +80 -1
  36. package/node_modules/@oxygen/workflows/dist/graph/remap.js +8 -1
  37. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +72 -3
  38. package/node_modules/@oxygen/workflows/dist/graph/types.js +12 -0
  39. package/node_modules/@oxygen/workflows/dist/index.d.ts +16 -0
  40. package/node_modules/@oxygen/workflows/dist/index.js +45 -3
  41. package/node_modules/@oxygen/workflows/dist/portable.d.ts +43 -0
  42. package/node_modules/@oxygen/workflows/dist/portable.js +319 -0
  43. package/node_modules/@oxygen/workflows/dist/usage-estimate.js +18 -4
  44. package/package.json +1 -1
@@ -10,7 +10,7 @@
10
10
  // full copy of those rules here would drift from the linter that actually runs.
11
11
  //
12
12
  // Pure data: this module ships to the browser with the rest of the graph barrel.
13
- import { MAX_WORKFLOW_NODE_RETRY_ATTEMPTS, MAX_WORKFLOW_NODE_RETRY_WAIT_SECONDS, WORKFLOW_GRAPH_COMPILER_VERSION, WORKFLOW_GRAPH_MANIFEST_VERSION, } from "./types.js";
13
+ import { MAX_WORKFLOW_NODE_RETRY_ATTEMPTS, MAX_WORKFLOW_NODE_RETRY_WAIT_SECONDS, PORTABLE_WORKFLOW_DEFINITION_COMPILER_VERSION, PORTABLE_WORKFLOW_DEFINITION_VERSION, WORKFLOW_GRAPH_COMPILER_VERSION, WORKFLOW_GRAPH_MANIFEST_VERSION, } from "./types.js";
14
14
  const valueRefSchema = {
15
15
  type: "object",
16
16
  description: "How a node input is bound at run time. Scope paths are 'trigger.input.x', 'steps.<node_id>.output.y', or 'loop.<loop_node_id>.item.z'.",
@@ -122,6 +122,15 @@ const nodeSchema = {
122
122
  description: "tool nodes: parameter name -> value ref.",
123
123
  additionalProperties: valueRefSchema,
124
124
  },
125
+ payload_source: {
126
+ type: "string",
127
+ description: "Imported v1 tool nodes only: sandboxed payload function preserved until converted to mapped params.",
128
+ },
129
+ legacy_context: {
130
+ type: "string",
131
+ enum: ["v1"],
132
+ description: "Imported v1 source receives the original raw-step-output context shape.",
133
+ },
125
134
  when: conditionSchema,
126
135
  cases: {
127
136
  type: "array",
@@ -155,6 +164,16 @@ const nodeSchema = {
155
164
  duration_seconds: { type: "number", exclusiveMinimum: 0, description: "wait nodes: exactly one of duration_seconds or until." },
156
165
  until: valueRefSchema,
157
166
  run_source: { type: "string", description: "code nodes: sandboxed pure-function source." },
167
+ recipe: {
168
+ type: "object",
169
+ description: "Imported durable-recipe code nodes: the complete executable recipe manifest. Exactly one of run_source or recipe is required.",
170
+ additionalProperties: true,
171
+ },
172
+ configuration: {
173
+ type: "object",
174
+ description: "Imported durable-recipe code nodes: configuration field -> value ref.",
175
+ additionalProperties: valueRefSchema,
176
+ },
158
177
  workflow_id: { type: "string", description: "workflow nodes: the child workflow to run." },
159
178
  input: { type: "object", description: "workflow nodes: child input name -> value ref.", additionalProperties: valueRefSchema },
160
179
  },
@@ -203,3 +222,63 @@ export const workflowGraphManifestSchema = {
203
222
  },
204
223
  required: ["manifest_version", "workflow", "nodes", "edges", "source_hash", "compiler_version"],
205
224
  };
225
+ export const portableWorkflowDefinitionSchema = {
226
+ $schema: "https://json-schema.org/draft/2020-12/schema",
227
+ title: "OXYGEN Portable Workflow Definition",
228
+ description: "A portable, editable oxygen-workflows-v2 graph. It never contains source-workspace connection UUIDs or standing trigger authority.",
229
+ type: "object",
230
+ additionalProperties: false,
231
+ properties: {
232
+ kind: { const: "oxygen-workflow-definition" },
233
+ definition_version: { const: PORTABLE_WORKFLOW_DEFINITION_VERSION },
234
+ graph: workflowGraphManifestSchema,
235
+ requirements: {
236
+ type: "object",
237
+ additionalProperties: false,
238
+ properties: {
239
+ connections: {
240
+ type: "array",
241
+ items: {
242
+ type: "object",
243
+ additionalProperties: false,
244
+ properties: {
245
+ binding_id: { type: "string", minLength: 1 },
246
+ integration_id: { type: "string", minLength: 1 },
247
+ node_ids: { type: "array", minItems: 1, items: { type: "string", minLength: 1 } },
248
+ account_hint: {
249
+ type: "object",
250
+ additionalProperties: false,
251
+ properties: {
252
+ external_account_id: { type: "string" },
253
+ display_name: { type: "string" },
254
+ auth_scheme: { type: "string" },
255
+ },
256
+ },
257
+ },
258
+ required: ["binding_id", "integration_id", "node_ids"],
259
+ },
260
+ },
261
+ },
262
+ required: ["connections"],
263
+ },
264
+ exported_at: { type: "string", format: "date-time" },
265
+ exported_from: {
266
+ type: "object",
267
+ additionalProperties: false,
268
+ properties: {
269
+ workflow_slug: { type: "string" },
270
+ revision_version: { type: "integer", minimum: 1 },
271
+ oxygen_version: { type: "string" },
272
+ },
273
+ },
274
+ compiler_version: { const: PORTABLE_WORKFLOW_DEFINITION_COMPILER_VERSION },
275
+ },
276
+ required: [
277
+ "kind",
278
+ "definition_version",
279
+ "graph",
280
+ "requirements",
281
+ "exported_at",
282
+ "compiler_version",
283
+ ],
284
+ };
@@ -204,8 +204,15 @@ export function remapNodeRefs(node, ids) {
204
204
  request: Object.fromEntries(Object.entries(node.request).map(([key, ref]) => [key, remapValueRef(ref, ids)])),
205
205
  }),
206
206
  };
207
+ case "code":
208
+ return node.configuration === undefined
209
+ ? node
210
+ : {
211
+ ...node,
212
+ configuration: Object.fromEntries(Object.entries(node.configuration).map(([key, ref]) => [key, remapValueRef(ref, ids)])),
213
+ };
207
214
  default:
208
- // trigger, merge, code — no bindings. A `code` node's source is JavaScript
215
+ // trigger, merge — no bindings. A source code node's JavaScript
209
216
  // reading `ctx.steps`, deliberately NOT rewritten: it is arbitrary code, and
210
217
  // string-substituting inside it would be a guess.
211
218
  return node;
@@ -1,6 +1,8 @@
1
- import type { JsonSchema, WorkflowMode, WorkflowStatus, WorkflowStepEffect, WorkflowTriggerManifest } from "../index.js";
1
+ import type { JsonSchema, RecipeManifest, WorkflowMode, WorkflowStatus, WorkflowStepEffect, WorkflowTriggerManifest } from "../index.js";
2
2
  export declare const WORKFLOW_GRAPH_MANIFEST_VERSION = 2;
3
3
  export declare const WORKFLOW_GRAPH_COMPILER_VERSION = "oxygen-workflows-v2";
4
+ export declare const PORTABLE_WORKFLOW_DEFINITION_VERSION = 1;
5
+ export declare const PORTABLE_WORKFLOW_DEFINITION_COMPILER_VERSION = "oxygen-workflow-definition-v1";
4
6
  export declare const RESERVED_NODE_IDS: ReadonlySet<string>;
5
7
  export declare const LOOP_BODY_HANDLE = "loop_body";
6
8
  export declare const LOOP_DONE_HANDLE = "loop_done";
@@ -144,6 +146,15 @@ export type WorkflowGraphNode =
144
146
  /** Per-node managed-credit ceiling, same semantics as v1 tool steps. */
145
147
  max_credits?: number;
146
148
  params: Record<string, WorkflowValueRef>;
149
+ /**
150
+ * Lossless bridge for a v1 code-authored tool step. The canvas cannot infer
151
+ * declarative field bindings from arbitrary JavaScript, so imported steps
152
+ * keep their sandboxed payload function until the author explicitly replaces
153
+ * it with schema-mapped `params` in the inspector.
154
+ */
155
+ payload_source?: string;
156
+ /** Imported payload functions receive the original v1 `{ input, steps, mode }` shape. */
157
+ legacy_context?: "v1";
147
158
  })
148
159
  /** Passes execution on only when `when` holds. */
149
160
  | (WorkflowGraphNodeBase & {
@@ -212,10 +223,21 @@ export type WorkflowGraphNode =
212
223
  reason?: WorkflowValueRef;
213
224
  request?: Record<string, WorkflowValueRef>;
214
225
  })
215
- /** Sandboxed transform source, the v2 spelling of a v1 transform step. */
226
+ /**
227
+ * Sandboxed transform source, or an imported durable recipe bundle.
228
+ *
229
+ * A recipe is deliberately ONE executable node: its bundle is the runtime
230
+ * authority, while visual_plan remains inspectable metadata. Turning those
231
+ * descriptive checkpoints into editable graph nodes would make the canvas lie
232
+ * about what deletion or rewiring actually changes.
233
+ */
216
234
  | (WorkflowGraphNodeBase & {
217
235
  kind: "code";
218
- run_source: string;
236
+ run_source?: string;
237
+ recipe?: RecipeManifest;
238
+ configuration?: Record<string, WorkflowValueRef>;
239
+ /** Imported v1 source reads raw step outputs rather than v2 output envelopes. */
240
+ legacy_context?: "v1";
219
241
  })
220
242
  /** Calls another workflow as a child run. */
221
243
  | (WorkflowGraphNodeBase & {
@@ -259,11 +281,58 @@ export type WorkflowGraphManifest = {
259
281
  compiler_version: typeof WORKFLOW_GRAPH_COMPILER_VERSION;
260
282
  created_at: string;
261
283
  };
284
+ /**
285
+ * A connection requirement in a portable workflow definition.
286
+ *
287
+ * Connection UUIDs are organization-local credentials and never travel in the
288
+ * artifact. Only exact source-account pins appear here. Unpinned provider nodes
289
+ * and Oxygen-managed nodes remain absent and keep ordinary workspace-default or
290
+ * managed resolution in the destination.
291
+ */
292
+ export type PortableWorkflowConnectionRequirement = {
293
+ /** Artifact-local key used by import flags and UI controls. */
294
+ binding_id: string;
295
+ integration_id: string;
296
+ /** Tool nodes that shared this exact source account pin. */
297
+ node_ids: string[];
298
+ /** Display/matching hints only; never a source connection UUID or secret. */
299
+ account_hint?: {
300
+ external_account_id?: string;
301
+ display_name?: string;
302
+ auth_scheme?: string;
303
+ };
304
+ };
305
+ /**
306
+ * Portable, editable representation of one deterministic workflow graph.
307
+ *
308
+ * This is deliberately narrower than a Blueprint (which may also carry tables,
309
+ * prompts, and column grafts) and narrower than AnyWorkflowManifest (which also
310
+ * includes legacy source-authored generations). A portable definition always
311
+ * opens as the same v2 graph the worker executes.
312
+ */
313
+ export type PortableWorkflowDefinitionV1 = {
314
+ kind: "oxygen-workflow-definition";
315
+ definition_version: typeof PORTABLE_WORKFLOW_DEFINITION_VERSION;
316
+ /** Named `graph`, not `manifest`: old CLIs treat `{manifest}` as directly applyable. */
317
+ graph: WorkflowGraphManifest;
318
+ requirements: {
319
+ connections: PortableWorkflowConnectionRequirement[];
320
+ };
321
+ exported_at: string;
322
+ exported_from?: {
323
+ workflow_slug?: string;
324
+ revision_version?: number;
325
+ oxygen_version?: string;
326
+ };
327
+ compiler_version: typeof PORTABLE_WORKFLOW_DEFINITION_COMPILER_VERSION;
328
+ };
262
329
  /**
263
330
  * Shape gate only — enough to route a manifest to the v2 code path. Full
264
331
  * structural validation is the linter's job, not this guard's.
265
332
  */
266
333
  export declare function isWorkflowGraphManifest(value: unknown): value is WorkflowGraphManifest;
334
+ /** Shape gate only; use the portable-definition linter before importing. */
335
+ export declare function isPortableWorkflowDefinitionV1(value: unknown): value is PortableWorkflowDefinitionV1;
267
336
  export declare function isTriggerNode(node: WorkflowGraphNode): node is WorkflowGraphNodeOfKind<"trigger">;
268
337
  export declare function isToolNode(node: WorkflowGraphNode): node is WorkflowGraphNodeOfKind<"tool">;
269
338
  export declare function isSwitchNode(node: WorkflowGraphNode): node is WorkflowGraphNodeOfKind<"switch">;
@@ -1,5 +1,7 @@
1
1
  export const WORKFLOW_GRAPH_MANIFEST_VERSION = 2;
2
2
  export const WORKFLOW_GRAPH_COMPILER_VERSION = "oxygen-workflows-v2";
3
+ export const PORTABLE_WORKFLOW_DEFINITION_VERSION = 1;
4
+ export const PORTABLE_WORKFLOW_DEFINITION_COMPILER_VERSION = "oxygen-workflow-definition-v1";
3
5
  // Node ids become keys on the runtime scope object (`steps.<id>.output`), exactly
4
6
  // as v1 step ids become keys on ctx.steps. The same JavaScript-internals collision
5
7
  // applies: a node named `__proto__` would silently lose its output.
@@ -73,6 +75,16 @@ export function isWorkflowGraphManifest(value) {
73
75
  && Array.isArray(value.nodes)
74
76
  && Array.isArray(value.edges);
75
77
  }
78
+ /** Shape gate only; use the portable-definition linter before importing. */
79
+ export function isPortableWorkflowDefinitionV1(value) {
80
+ return isRecord(value)
81
+ && value.kind === "oxygen-workflow-definition"
82
+ && value.definition_version === PORTABLE_WORKFLOW_DEFINITION_VERSION
83
+ && value.compiler_version === PORTABLE_WORKFLOW_DEFINITION_COMPILER_VERSION
84
+ && isWorkflowGraphManifest(value.graph)
85
+ && isRecord(value.requirements)
86
+ && Array.isArray(value.requirements.connections);
87
+ }
76
88
  function isNodeOfKind(node, kind) {
77
89
  return node.kind === kind;
78
90
  }
@@ -5,6 +5,7 @@ export * from "./event-dispatch.js";
5
5
  export type { CanonicalWorkflowToolEffect } from "./tool-effects.js";
6
6
  export * from "./graph/index.js";
7
7
  export * from "./graph/lint.js";
8
+ export * from "./portable.js";
8
9
  export declare const WORKFLOW_MANIFEST_VERSION = 1;
9
10
  export declare const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
10
11
  export declare const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
@@ -146,6 +147,13 @@ export type RecipeManifest = {
146
147
  };
147
148
  trigger?: WorkflowTriggerManifest;
148
149
  input_schema?: JsonSchema;
150
+ /**
151
+ * Operator-editable recipe settings, distinct from the per-delivery trigger
152
+ * payload. When present, the canvas renders this schema with the same mapped
153
+ * field controls used by tool/action configuration.
154
+ */
155
+ configuration_schema?: JsonSchema;
156
+ configuration?: Record<string, unknown>;
149
157
  runtime: RecipeRuntime;
150
158
  bundle: string;
151
159
  bundle_format: "esm";
@@ -392,6 +400,8 @@ export declare function buildRecipeManifest(input: {
392
400
  status?: WorkflowStatus;
393
401
  trigger?: WorkflowTriggerManifest;
394
402
  inputSchema?: JsonSchema;
403
+ configurationSchema?: JsonSchema;
404
+ configuration?: Record<string, unknown>;
395
405
  bundle: string;
396
406
  toolsUsed: string[];
397
407
  visualPlan?: RecipeVisualPlanManifest;
@@ -399,6 +409,12 @@ export declare function buildRecipeManifest(input: {
399
409
  sourceHash?: string;
400
410
  createdAt?: Date;
401
411
  }): RecipeManifest;
412
+ /** Canonical executable identity shared by recipe compilers and repair paths. */
413
+ export declare function hashRecipeSource(input: {
414
+ bundle: string;
415
+ configurationSchema?: JsonSchema;
416
+ configuration?: Record<string, unknown>;
417
+ }): string;
402
418
  export declare function validateOptionalMaxCredits(value: unknown, path: string, add: (path: string, code: string, message: string) => void): void;
403
419
  export declare function lintWorkflowManifest(// skipcq: JS-R1005
404
420
  value: unknown, options?: WorkflowManifestValidationOptions): WorkflowLintResult;
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import * as vm from "node:vm"; // skipcq: JS-C1003
3
3
  import { isWorkflowGraphManifest } from "./graph/types.js";
4
+ import { lintWorkflowGraphManifest } from "./graph/lint.js";
4
5
  export * from "./usage-estimate.js";
5
6
  // Pure event-dispatch semantics, shared by the apps/web and apps/worker event
6
7
  // dispatchers so the filter gate, idempotency rule, and delivery verdict cannot
@@ -11,6 +12,7 @@ export * from "./event-dispatch.js";
11
12
  // scanner below — so it is re-exported here, from the node entry point only.
12
13
  export * from "./graph/index.js";
13
14
  export * from "./graph/lint.js";
15
+ export * from "./portable.js";
14
16
  export const WORKFLOW_MANIFEST_VERSION = 1;
15
17
  export const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
16
18
  export const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
@@ -278,7 +280,13 @@ export function buildRecipeManifest(input) {
278
280
  if (typeof bundle !== "string" || bundle.length === 0) {
279
281
  throw new Error("Recipe bundle source is required.");
280
282
  }
281
- const sourceHash = input.sourceHash ?? hashWorkflowSource(bundle);
283
+ const sourceHash = input.sourceHash ?? hashRecipeSource({
284
+ bundle,
285
+ ...(input.configurationSchema !== undefined
286
+ ? { configurationSchema: input.configurationSchema }
287
+ : {}),
288
+ ...(input.configuration !== undefined ? { configuration: input.configuration } : {}),
289
+ });
282
290
  const manifest = {
283
291
  manifest_version: WORKFLOW_MANIFEST_VERSION,
284
292
  workflow: {
@@ -288,6 +296,8 @@ export function buildRecipeManifest(input) {
288
296
  },
289
297
  ...(input.trigger ? { trigger: input.trigger } : {}),
290
298
  ...(input.inputSchema ? { input_schema: input.inputSchema } : {}),
299
+ ...(input.configurationSchema ? { configuration_schema: input.configurationSchema } : {}),
300
+ ...(input.configuration ? { configuration: input.configuration } : {}),
291
301
  runtime: "durable",
292
302
  bundle,
293
303
  bundle_format: "esm",
@@ -300,6 +310,20 @@ export function buildRecipeManifest(input) {
300
310
  };
301
311
  return manifest;
302
312
  }
313
+ /** Canonical executable identity shared by recipe compilers and repair paths. */
314
+ export function hashRecipeSource(input) {
315
+ // Preserve the historical bundle-only identity for recipes that have no
316
+ // editable configuration. Configuration-bearing revisions include schema +
317
+ // values because either can change the behavior of the same bundle source.
318
+ if (input.configurationSchema === undefined && input.configuration === undefined) {
319
+ return hashWorkflowSource(input.bundle);
320
+ }
321
+ return hashWorkflowSource(JSON.stringify({
322
+ bundle: input.bundle,
323
+ configuration_schema: input.configurationSchema ?? null,
324
+ configuration: input.configuration ?? null,
325
+ }, workflowJsonReplacer));
326
+ }
303
327
  // Shared positive-finite validator for the optional per-run / per-step
304
328
  // max_credits caps (Gap 2). `undefined` is valid (no cap); anything else must be
305
329
  // a positive finite number, or lint rejects the manifest.
@@ -314,6 +338,9 @@ export function validateOptionalMaxCredits(value, path, add) {
314
338
  }
315
339
  export function lintWorkflowManifest(// skipcq: JS-R1005
316
340
  value, options = {}) {
341
+ if (isWorkflowGraphManifest(value)) {
342
+ return lintWorkflowGraphManifest(value, options);
343
+ }
317
344
  if (isRecord(value)
318
345
  && typeof value.compiler_version === "string"
319
346
  && value.compiler_version.startsWith("oxygen-recipes-")) {
@@ -432,7 +459,7 @@ value, options = {}) {
432
459
  if (resolveEffect) {
433
460
  const canonicalEffect = resolveEffect(step.tool);
434
461
  if (canonicalEffect !== null && step.effect !== canonicalEffect) {
435
- add(`${path}.effect`, "tool_effect_mismatch", `Tool '${step.tool}' has canonical effect '${canonicalEffect}', not '${String(step.effect)}'.`);
462
+ add(`${path}.effect`, "tool_effect_mismatch", `Tool '${step.tool}' has canonical effect '${canonicalEffect}', not '${String(step.effect)}'. Set step.effect to '${canonicalEffect}'.`);
436
463
  }
437
464
  }
438
465
  }
@@ -504,6 +531,17 @@ value, options = {}) {
504
531
  }
505
532
  if (value.trigger !== undefined)
506
533
  validateTrigger(value.trigger, "$.trigger", add);
534
+ if (value.configuration_schema !== undefined && !isRecord(value.configuration_schema)) {
535
+ add("$.configuration_schema", "invalid_configuration_schema", "Recipe configuration_schema must be a JSON Schema object.");
536
+ }
537
+ if (value.configuration !== undefined && !isRecord(value.configuration)) {
538
+ add("$.configuration", "invalid_configuration", "Recipe configuration must be an object.");
539
+ }
540
+ else if (isRecord(value.configuration_schema)) {
541
+ for (const issue of validateJsonSchemaValue(isRecord(value.configuration) ? value.configuration : {}, value.configuration_schema, "$.configuration")) {
542
+ add(issue.path, issue.code, issue.message);
543
+ }
544
+ }
507
545
  if (!isNonEmptyString(value.bundle)) {
508
546
  add("$.bundle", "missing_bundle", "Recipe bundle is required.");
509
547
  }
@@ -1756,7 +1794,11 @@ export function workflowManifestCanInvokeTool(manifest) {
1756
1794
  const viaRecipe = Array.isArray(manifest.tools_used)
1757
1795
  && manifest.tools_used.some((toolId) => isNonEmptyString(toolId));
1758
1796
  const viaGraph = Array.isArray(manifest.nodes)
1759
- && manifest.nodes.some((node) => isRecord(node) && (node.kind === "tool" || node.kind === "workflow"));
1797
+ && manifest.nodes.some((node) => isRecord(node) && (node.kind === "tool"
1798
+ || node.kind === "workflow"
1799
+ || (isRecord(node.recipe)
1800
+ && Array.isArray(node.recipe.tools_used)
1801
+ && node.recipe.tools_used.some((toolId) => isNonEmptyString(toolId)))));
1760
1802
  const viaSteps = Array.isArray(manifest.steps)
1761
1803
  && manifest.steps.some((step) => isRecord(step) && step.kind === "tool");
1762
1804
  return viaRecipe || viaGraph || viaSteps;
@@ -0,0 +1,43 @@
1
+ import { type PortableWorkflowDefinitionV1, type WorkflowGraphManifest } from "./graph/types.js";
2
+ export type PortableWorkflowDefinitionIssue = {
3
+ path: string;
4
+ code: string;
5
+ message: string;
6
+ };
7
+ export type PortableWorkflowDefinitionLintResult = {
8
+ ok: boolean;
9
+ issues: PortableWorkflowDefinitionIssue[];
10
+ };
11
+ export declare class PortableWorkflowDefinitionValidationError extends Error {
12
+ readonly issues: PortableWorkflowDefinitionIssue[];
13
+ constructor(issues: PortableWorkflowDefinitionIssue[]);
14
+ }
15
+ type ExportedFrom = NonNullable<PortableWorkflowDefinitionV1["exported_from"]>;
16
+ export type PortableWorkflowSourceConnection = {
17
+ integrationId: string;
18
+ externalAccountId?: string | null;
19
+ displayName?: string | null;
20
+ authScheme?: string | null;
21
+ };
22
+ /** The graph editor and server use this same material rule. */
23
+ export declare function hashPortableWorkflowGraphManifest(manifest: WorkflowGraphManifest): string;
24
+ export declare function lintPortableWorkflowGraph(manifest: unknown): PortableWorkflowDefinitionLintResult;
25
+ export declare function assertPortableWorkflowGraph(manifest: unknown): asserts manifest is WorkflowGraphManifest;
26
+ export declare function lintPortableWorkflowDefinition(value: unknown): PortableWorkflowDefinitionLintResult;
27
+ export declare function assertPortableWorkflowDefinition(value: unknown): asserts value is PortableWorkflowDefinitionV1;
28
+ export declare function buildPortableWorkflowDefinition(input: {
29
+ manifest: unknown;
30
+ exportedAt?: Date;
31
+ exportedFrom?: ExportedFrom;
32
+ /** Source connection metadata keyed by source-only UUID; UUIDs are never serialized. */
33
+ sourceConnections?: Record<string, PortableWorkflowSourceConnection>;
34
+ }): PortableWorkflowDefinitionV1;
35
+ export declare function materializePortableWorkflowDefinition(input: {
36
+ definition: unknown;
37
+ connectionBindings?: Record<string, string>;
38
+ workflowId?: string;
39
+ workflowName?: string;
40
+ webhookTriggerId?: string;
41
+ importedAt?: Date;
42
+ }): WorkflowGraphManifest;
43
+ export {};