intentdna 1.8.7 → 1.9.0-rc.2

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 (69) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/cli/commands/candidate-promotion.d.ts +15 -0
  4. package/dist/cli/commands/candidate-promotion.js +197 -0
  5. package/dist/cli/commands/run-lifecycle.js +1 -0
  6. package/dist/cli/commands/run.js +76 -42
  7. package/dist/cli/commands/setup.d.ts +10 -0
  8. package/dist/cli/commands/setup.js +129 -36
  9. package/dist/cli/commands/source-assets.d.ts +32 -0
  10. package/dist/cli/commands/source-assets.js +154 -0
  11. package/dist/cli/commands/sync.js +52 -12
  12. package/dist/cli/commands/templates.js +59 -37
  13. package/dist/cli/index.js +161 -5
  14. package/dist/compiler/cascade.js +1 -1
  15. package/dist/hooks/cli.d.ts +6 -0
  16. package/dist/hooks/cli.js +118 -10
  17. package/dist/hooks/enforcement-boundary.d.ts +1 -1
  18. package/dist/hooks/protocol.d.ts +1 -1
  19. package/dist/hooks/state.d.ts +5 -0
  20. package/dist/hooks/state.js +21 -6
  21. package/dist/mcp/tools-state.js +251 -15
  22. package/dist/runtime/claude-sync-target.d.ts +2 -0
  23. package/dist/runtime/claude-sync-target.js +1 -1
  24. package/dist/runtime/evaluator.d.ts +13 -0
  25. package/dist/runtime/evaluator.js +46 -0
  26. package/dist/runtime/index.d.ts +8 -4
  27. package/dist/runtime/index.js +4 -2
  28. package/dist/runtime/plugin-adapter.d.ts +14 -1
  29. package/dist/runtime/plugin-adapter.js +23 -9
  30. package/dist/runtime/run-contracts.d.ts +5 -3
  31. package/dist/runtime/run-contracts.js +11 -1
  32. package/dist/runtime/run-controller.d.ts +15 -1
  33. package/dist/runtime/run-controller.js +235 -94
  34. package/dist/runtime/skill-adapter.js +24 -53
  35. package/dist/runtime/verifier.js +101 -2
  36. package/dist/runtime/workflow-plan-adapter.d.ts +2 -1
  37. package/dist/runtime/workflow-plan-adapter.js +78 -11
  38. package/dist/runtime/workflow-runtime-manifest.d.ts +48 -0
  39. package/dist/runtime/workflow-runtime-manifest.js +196 -0
  40. package/dist/runtime/workflow-variable-scopes.d.ts +12 -0
  41. package/dist/runtime/workflow-variable-scopes.js +55 -0
  42. package/dist/schema/types.d.ts +9 -0
  43. package/dist/schema/validate.js +29 -1
  44. package/dist/sources/authoring-packet.d.ts +8 -0
  45. package/dist/sources/authoring-packet.js +85 -0
  46. package/dist/sources/discovery.d.ts +7 -0
  47. package/dist/sources/discovery.js +150 -0
  48. package/dist/sources/license.d.ts +5 -0
  49. package/dist/sources/license.js +94 -0
  50. package/dist/sources/manifest.d.ts +11 -0
  51. package/dist/sources/manifest.js +84 -0
  52. package/dist/sources/skill-markdown.d.ts +6 -0
  53. package/dist/sources/skill-markdown.js +174 -0
  54. package/dist/sources/types.d.ts +125 -0
  55. package/dist/sources/types.js +1 -0
  56. package/dist/templates/asset-refinery.dna.yaml +192 -0
  57. package/dist/templates/flutter-behavior-lock.dna.yaml +17 -0
  58. package/dist/templates/flutter-refactoring-rescue.dna.yaml +11 -0
  59. package/dist/templates/flutter-rewrite.dna.yaml +154 -28
  60. package/dist/templates/metadata.js +2 -0
  61. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +102 -37
  62. package/dist/templates/persistent-executor.dna.yaml +61 -3
  63. package/dist/templates/research-improvement.dna.yaml +295 -0
  64. package/dist/templates/subagent-parallel.dna.yaml +90 -17
  65. package/dist/templates/workflow-evidence-review.dna.yaml +8 -0
  66. package/dist/workflow/candidate-projection.d.ts +33 -0
  67. package/dist/workflow/candidate-projection.js +70 -0
  68. package/package.json +3 -1
  69. package/scripts/sync-release-plugin.cjs +87 -0
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { readFile, realpath, stat } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { EvaluatorResultError, parseEvaluatorResult } from "./evaluator.js";
4
5
  export const DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS = 30_000;
5
6
  export const MAX_VERIFIER_EVIDENCE_BYTES = 4096;
6
7
  export const VERIFIER_DIAGNOSTIC_PREFIX = "INTENTDNA_DIAGNOSTIC:";
@@ -8,6 +9,12 @@ export const MAX_VERIFIER_DIAGNOSTIC_BYTES = 2048;
8
9
  const VERIFIER_DIAGNOSTIC_CODE = /^INTENTDNA_DIAGNOSTIC:[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
9
10
  const SAFE_COMMAND_VARIABLE_RE = /^[A-Za-z0-9_-]+$/;
10
11
  const SAFE_PATH_VARIABLE_RE = /^[-A-Za-z0-9_./]+$/;
12
+ function isSafeRelativePathVariable(value) {
13
+ return SAFE_PATH_VARIABLE_RE.test(value) &&
14
+ !value.startsWith("/") &&
15
+ !value.includes("//") &&
16
+ !value.split(/[\\/]+/).includes("..");
17
+ }
11
18
  export function resolveVerifierTemplate(template, variables) {
12
19
  let resolved = template;
13
20
  if (variables?.ARGUMENTS !== undefined) {
@@ -32,9 +39,10 @@ export function validateVerifierTemplateVariables(template, variables, mode = "c
32
39
  const value = variables?.[name];
33
40
  if (value === undefined)
34
41
  continue;
42
+ const isPathVariable = /(?:^|_)(?:path|dir|file)$/.test(name);
35
43
  const matchesSafeCharacters = mode === "path"
36
- ? SAFE_PATH_VARIABLE_RE.test(value) && !value.startsWith("/") && !value.includes("//") && !value.split(/[\\/]+/).includes("..")
37
- : SAFE_COMMAND_VARIABLE_RE.test(value);
44
+ ? isSafeRelativePathVariable(value)
45
+ : SAFE_COMMAND_VARIABLE_RE.test(value) || (isPathVariable && isSafeRelativePathVariable(value));
38
46
  if (!matchesSafeCharacters) {
39
47
  return {
40
48
  valid: false,
@@ -308,6 +316,8 @@ export function getCompletionCheckFields(completion) {
308
316
  fields.push("file_contains");
309
317
  if (completion.command_success !== undefined)
310
318
  fields.push("command_success");
319
+ if (completion.evaluator_result !== undefined)
320
+ fields.push("evaluator_result");
311
321
  return fields;
312
322
  }
313
323
  export function validateCompletionVerifier(completion) {
@@ -340,6 +350,30 @@ export function validateCompletionVerifier(completion) {
340
350
  return { message: "Completion verifier file_contains.pattern must be a non-empty string", evidence: "invalid:file_contains.pattern" };
341
351
  }
342
352
  }
353
+ if (field === "evaluator_result") {
354
+ const evaluatorResult = completion.evaluator_result;
355
+ if (typeof evaluatorResult !== "object"
356
+ || evaluatorResult === null
357
+ || Array.isArray(evaluatorResult)) {
358
+ return {
359
+ message: "Completion verifier evaluator_result must define a path and optional require_score",
360
+ evidence: "invalid:evaluator_result",
361
+ };
362
+ }
363
+ if (typeof evaluatorResult.path !== "string" || !evaluatorResult.path) {
364
+ return {
365
+ message: "Completion verifier evaluator_result.path must be a non-empty string",
366
+ evidence: "invalid:evaluator_result.path",
367
+ };
368
+ }
369
+ if (evaluatorResult.require_score !== undefined
370
+ && typeof evaluatorResult.require_score !== "boolean") {
371
+ return {
372
+ message: "Completion verifier evaluator_result.require_score must be a boolean",
373
+ evidence: "invalid:evaluator_result.require_score",
374
+ };
375
+ }
376
+ }
343
377
  return null;
344
378
  }
345
379
  export async function runCompletionVerifier(completion, ir, context) {
@@ -417,6 +451,71 @@ export async function runCompletionVerifier(completion, ir, context) {
417
451
  return { passed: false, target, evidence, artifact, message: `Required file missing: ${target}` };
418
452
  }
419
453
  }
454
+ if (completion.evaluator_result) {
455
+ const evaluatorResult = completion.evaluator_result;
456
+ const safeVariables = validateVerifierTemplateVariables(evaluatorResult.path, context.variables, "path");
457
+ if (safeVariables.valid === false) {
458
+ return {
459
+ passed: false,
460
+ target: evaluatorResult.path,
461
+ evidence: safeVariables.evidence,
462
+ message: safeVariables.message,
463
+ };
464
+ }
465
+ const target = resolveVerifierTemplate(evaluatorResult.path, context.variables);
466
+ if (hasUnresolvedVerifierTemplate(target)) {
467
+ return {
468
+ passed: false,
469
+ target,
470
+ evidence: "unresolved_template",
471
+ message: unresolvedVerifierTemplateMessage(target),
472
+ };
473
+ }
474
+ const resolvedTarget = await resolveVerifierTarget(context.projectDir, target);
475
+ const artifact = resolvedTarget ?? undefined;
476
+ if (!resolvedTarget) {
477
+ return {
478
+ passed: false,
479
+ target,
480
+ evidence: "path_escape",
481
+ artifact,
482
+ message: `Verifier target escapes project root: ${target}`,
483
+ };
484
+ }
485
+ try {
486
+ const raw = await readFile(resolvedTarget, "utf-8");
487
+ const parsed = parseEvaluatorResult(raw, {
488
+ require_score: evaluatorResult.require_score,
489
+ });
490
+ const evidence = `pass=${String(parsed.pass)} score=${parsed.score ?? "n/a"}`;
491
+ return parsed.pass
492
+ ? {
493
+ passed: true,
494
+ target,
495
+ evidence,
496
+ artifact,
497
+ }
498
+ : {
499
+ passed: false,
500
+ target,
501
+ evidence,
502
+ artifact,
503
+ message: "Evaluator result reported pass=false",
504
+ };
505
+ }
506
+ catch (error) {
507
+ const message = error instanceof EvaluatorResultError
508
+ ? error.message
509
+ : `Required evaluator result missing: ${target}`;
510
+ return {
511
+ passed: false,
512
+ target,
513
+ evidence: "invalid_evaluator_result",
514
+ artifact,
515
+ message,
516
+ };
517
+ }
518
+ }
420
519
  const rawCommand = completion.command_success;
421
520
  const commandResolution = resolveVerifierCommand(rawCommand, ir.verifier_policy, context.variables);
422
521
  if (!commandResolution.command)
@@ -1,5 +1,5 @@
1
1
  import type { RoleDef, WorkflowPlan, WorkflowStep } from "../schema/types.js";
2
- import type { ControllerStepDefinition, MaterializeStepPacketInput } from "./run-controller.js";
2
+ import type { ControllerStepDefinition, MaterializeStepPacketInput, WorkflowRetryLoopPolicy } from "./run-controller.js";
3
3
  import type { TerminalOutcomeKind } from "./run-contracts.js";
4
4
  export type WorkflowPlanAdapterErrorCode = "invalid_plan" | "missing_role" | "invalid_handoff" | "unsupported_routing";
5
5
  export declare class WorkflowPlanAdapterError extends Error {
@@ -18,6 +18,7 @@ export interface WorkflowPlanAdapterOptions {
18
18
  readonly retry_max_delay_ms?: number | null;
19
19
  readonly retryable_outcomes?: readonly TerminalOutcomeKind[];
20
20
  }
21
+ export declare function adaptWorkflowRetryLoop(plan: WorkflowPlan, options?: Pick<WorkflowPlanAdapterOptions, "retry_initial_delay_ms" | "retry_max_delay_ms">): WorkflowRetryLoopPolicy | null;
21
22
  /**
22
23
  * Adapt one compiled workflow plan into Controller definitions. Every
23
24
  * materializer creates a self-contained packet from Controller-resolved inputs;
@@ -63,6 +63,12 @@ function completionText(completion, variables) {
63
63
  if (completion.file_contains !== undefined) {
64
64
  return `Required file ${substitute(completion.file_contains.path, variables)} contains /${completion.file_contains.pattern}/`;
65
65
  }
66
+ if (completion.evaluator_result !== undefined) {
67
+ const score = completion.evaluator_result.require_score === true
68
+ ? " and includes a finite numeric score"
69
+ : "";
70
+ return `Required evaluator result ${substitute(completion.evaluator_result.path, variables)} reports pass=true${score}`;
71
+ }
66
72
  return `Required command succeeds: ${substitute(completion.command_success ?? "", variables)}`;
67
73
  }
68
74
  function checkpointText(checkpoint, variables) {
@@ -184,13 +190,14 @@ function createHandoffs(step, stepById, variables) {
184
190
  }
185
191
  return { handoffs: declarations, external_inputs: externalInputs };
186
192
  }
187
- function retryPolicy(plan, step, options) {
193
+ function retryPolicy(plan, step, options, hasWorkflowLoop) {
188
194
  const initialDelay = options.retry_initial_delay_ms ?? 0;
189
195
  const maxDelay = options.retry_max_delay_ms ?? null;
190
196
  requireNonNegativeInteger(initialDelay, "retry_initial_delay_ms");
191
197
  if (maxDelay !== null)
192
198
  requireNonNegativeInteger(maxDelay, "retry_max_delay_ms");
193
- const maxAttempts = step.max_attempts ?? plan.retry.max_retries + 1;
199
+ const maxAttempts = step.max_attempts
200
+ ?? (hasWorkflowLoop ? 1 : plan.retry.max_retries + 1);
194
201
  if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
195
202
  throw new WorkflowPlanAdapterError("invalid_plan", `step '${step.id}' has an invalid max attempt count`, step.id);
196
203
  }
@@ -304,18 +311,72 @@ function standalonePrompt(plan, adapted, bindings, variables) {
304
311
  }
305
312
  return lines.join("\n");
306
313
  }
307
- function validatePlanRouting(plan) {
308
- if (plan.transitions.length > 0) {
309
- throw new WorkflowPlanAdapterError("unsupported_routing", "conditional workflow transitions cannot be represented by the Foundation Controller dependency contract");
314
+ export function adaptWorkflowRetryLoop(plan, options = {}) {
315
+ const hasLoopRouting = (plan.retry.retry_from !== null
316
+ || plan.transitions.length > 0);
317
+ if (!hasLoopRouting)
318
+ return null;
319
+ if (plan.retry.max_retries < 1) {
320
+ throw new WorkflowPlanAdapterError("unsupported_routing", "workflow retry routing requires max_retries greater than zero");
321
+ }
322
+ const retryFrom = plan.retry.retry_from
323
+ ?? plan.transitions.find((transition) => (transition.condition === "fail"
324
+ || transition.condition === "error"))?.to
325
+ ?? null;
326
+ const retryIndex = retryFrom === null
327
+ ? -1
328
+ : plan.steps.findIndex((step) => step.id === retryFrom);
329
+ if (retryFrom === null || retryIndex < 0) {
330
+ throw new WorkflowPlanAdapterError("unsupported_routing", "workflow retry routing must resolve a valid retry_from Step");
331
+ }
332
+ for (const transition of plan.transitions) {
333
+ const sourceIndex = plan.steps.findIndex((step) => step.id === transition.from);
334
+ if ((transition.condition !== "fail" && transition.condition !== "error")
335
+ || transition.to !== retryFrom
336
+ || sourceIndex < retryIndex) {
337
+ throw new WorkflowPlanAdapterError("unsupported_routing", `transition '${transition.from}' -> '${transition.to}' (${transition.condition}) is outside the bounded retry segment`, transition.from);
338
+ }
339
+ }
340
+ const loopStepIds = plan.steps
341
+ .slice(retryIndex)
342
+ .map((step) => step.id);
343
+ const loopStepSet = new Set(loopStepIds);
344
+ if (plan.parallel_groups.some((group) => (group.step_ids.length > 1
345
+ && group.step_ids.some((stepId) => loopStepSet.has(stepId))))) {
346
+ throw new WorkflowPlanAdapterError("unsupported_routing", "bounded workflow retry does not yet support a parallel retry segment");
310
347
  }
311
- if (plan.retry.retry_from !== null) {
312
- throw new WorkflowPlanAdapterError("unsupported_routing", "workflow retry_from cannot be represented by per-Step Foundation retries");
348
+ for (const [index, step] of plan.steps.entries()) {
349
+ if (step.run_if === null)
350
+ continue;
351
+ if (index < retryIndex && step.run_if === "round == 1")
352
+ continue;
353
+ throw new WorkflowPlanAdapterError("unsupported_routing", `step '${step.id}' uses unsupported loop condition '${step.run_if}'`, step.id);
313
354
  }
355
+ const initialDelay = options.retry_initial_delay_ms ?? 0;
356
+ const maxDelay = options.retry_max_delay_ms ?? null;
357
+ requireNonNegativeInteger(initialDelay, "retry_initial_delay_ms");
358
+ if (maxDelay !== null) {
359
+ requireNonNegativeInteger(maxDelay, "retry_max_delay_ms");
360
+ }
361
+ return {
362
+ retry_from: retryFrom,
363
+ step_ids: loopStepIds,
364
+ completion_step: loopStepIds[loopStepIds.length - 1],
365
+ max_retries: plan.retry.max_retries,
366
+ backoff: plan.retry.backoff,
367
+ initial_delay_ms: initialDelay,
368
+ max_delay_ms: maxDelay,
369
+ };
370
+ }
371
+ function validatePlanRouting(plan, options) {
372
+ const workflowLoop = adaptWorkflowRetryLoop(plan, options);
314
373
  for (const step of plan.steps) {
315
374
  if (step.optional) {
316
375
  throw new WorkflowPlanAdapterError("unsupported_routing", `step '${step.id}' is optional, which the Foundation Controller cannot preserve on failure`, step.id);
317
376
  }
318
- if (step.run_if !== null && plan.retry.max_retries > 0) {
377
+ if (workflowLoop === null
378
+ && step.run_if !== null
379
+ && plan.retry.max_retries > 0) {
319
380
  throw new WorkflowPlanAdapterError("unsupported_routing", `step '${step.id}' uses run_if across retry rounds, which the Foundation Controller cannot evaluate`, step.id);
320
381
  }
321
382
  if (step.on_fail === "handoff"
@@ -326,6 +387,7 @@ function validatePlanRouting(plan) {
326
387
  throw new WorkflowPlanAdapterError("unsupported_routing", `step '${step.id}' uses legacy failure routing not represented by the Foundation Controller`, step.id);
327
388
  }
328
389
  }
390
+ return workflowLoop;
329
391
  }
330
392
  function runsInSingleRound(condition) {
331
393
  if (condition === null)
@@ -349,7 +411,7 @@ function runsInSingleRound(condition) {
349
411
  * no provider transcript or prior-session state is accepted by this API.
350
412
  */
351
413
  export function adaptWorkflowPlan(plan, options) {
352
- validatePlanRouting(plan);
414
+ const workflowLoop = validatePlanRouting(plan, options);
353
415
  if (!options.working_directory) {
354
416
  throw new WorkflowPlanAdapterError("invalid_plan", "working_directory must be non-empty");
355
417
  }
@@ -374,7 +436,7 @@ export function adaptWorkflowPlan(plan, options) {
374
436
  external_inputs,
375
437
  outputs: createOutputContract(step, variables),
376
438
  isolation: isolationForStep(plan.parallel_groups, step.id),
377
- retry: retryPolicy(plan, step, options),
439
+ retry: retryPolicy(plan, step, options, workflowLoop !== null),
378
440
  };
379
441
  });
380
442
  return adapted.map((entry) => ({
@@ -384,6 +446,10 @@ export function adaptWorkflowPlan(plan, options) {
384
446
  retry_policy: entry.retry,
385
447
  handoffs: entry.handoffs,
386
448
  materialize_packet(input) {
449
+ const packetVariables = {
450
+ ...variables,
451
+ round: String(input.workflow_round),
452
+ };
387
453
  const workingDirectory = options.working_directory_for_step?.(entry.step, input) ?? options.working_directory;
388
454
  if (!workingDirectory) {
389
455
  throw new WorkflowPlanAdapterError("invalid_plan", `step '${entry.step.id}' resolved an empty working directory`, entry.step.id);
@@ -395,9 +461,10 @@ export function adaptWorkflowPlan(plan, options) {
395
461
  attempt_id: input.attempt_id,
396
462
  worker_session_id: input.worker_session_id,
397
463
  attempt_number: input.attempt_number,
464
+ workflow_round: input.workflow_round,
398
465
  role: entry.step.role,
399
466
  constraints: entry.constraints,
400
- standalone_prompt: standalonePrompt(plan, entry, input.input_bindings, variables),
467
+ standalone_prompt: standalonePrompt(plan, entry, input.input_bindings, packetVariables),
401
468
  input_bindings: input.input_bindings,
402
469
  output_contract: entry.outputs,
403
470
  workspace: {
@@ -0,0 +1,48 @@
1
+ import type { WorkflowPlan } from "../schema/types.js";
2
+ export declare const WORKFLOW_RUNTIME_MANIFEST_SCHEMA = "intentdna.workflow_runtime_manifest.v1";
3
+ export declare const WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES: Set<string>;
4
+ export interface WorkflowRuntimeBinding {
5
+ workflow: string;
6
+ workflow_asset?: string;
7
+ template_variable_refs: string[];
8
+ runtime_input_refs: string[];
9
+ requires_arguments: boolean;
10
+ resolved_variables: Record<string, string>;
11
+ }
12
+ export interface WorkflowRuntimeManifestPayload {
13
+ schema_version: typeof WORKFLOW_RUNTIME_MANIFEST_SCHEMA;
14
+ compiled_at: string;
15
+ compiled_ir_hash: string;
16
+ source_dna_ids: string[];
17
+ workflows: Record<string, WorkflowRuntimeBinding>;
18
+ }
19
+ export interface WorkflowRuntimeManifest extends WorkflowRuntimeManifestPayload {
20
+ manifest_hash: string;
21
+ }
22
+ export type WorkflowRuntimeManifestValidation = {
23
+ valid: true;
24
+ manifest: WorkflowRuntimeManifest;
25
+ } | {
26
+ valid: false;
27
+ reason: string;
28
+ };
29
+ export declare function workflowContainsToken(plan: WorkflowPlan, token: string): boolean;
30
+ export declare function workflowUsesArguments(plan: WorkflowPlan, resolvedVariables?: Record<string, string>): boolean;
31
+ export declare function valueUsesArguments(value: unknown, resolvedVariables?: Record<string, string>): boolean;
32
+ export declare function collectTemplateVariableRefs(value: unknown): string[];
33
+ export declare function collectWorkflowTemplateVariableRefs(plan: WorkflowPlan): string[];
34
+ export declare function resolveVariableReferences(variables: Record<string, string>): Record<string, string>;
35
+ export declare function resolveWorkflowVariableReferences(variables: Record<string, string>): Record<string, string>;
36
+ export declare function createWorkflowRuntimeManifest(options: {
37
+ plans: Array<{
38
+ plan: WorkflowPlan;
39
+ resolvedVariables: Record<string, string>;
40
+ workflowAsset?: string;
41
+ additionalTemplateVariableRefs?: string[];
42
+ additionalRuntimeValues?: unknown;
43
+ }>;
44
+ compiledAt: string;
45
+ compiledIrHash: string;
46
+ sourceDnaIds: string[];
47
+ }): WorkflowRuntimeManifest;
48
+ export declare function validateWorkflowRuntimeManifest(value: unknown, expectedCompiledIrHash?: string): WorkflowRuntimeManifestValidation;
@@ -0,0 +1,196 @@
1
+ import { createHash } from "node:crypto";
2
+ export const WORKFLOW_RUNTIME_MANIFEST_SCHEMA = "intentdna.workflow_runtime_manifest.v1";
3
+ export const WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES = new Set(["ARGUMENTS", "arguments", "task_id"]);
4
+ function canonicalJson(value) {
5
+ if (Array.isArray(value))
6
+ return value.map((item) => canonicalJson(item));
7
+ if (value && typeof value === "object") {
8
+ const record = value;
9
+ return Object.fromEntries(Object.keys(record).sort().map((key) => [key, canonicalJson(record[key])]));
10
+ }
11
+ return value;
12
+ }
13
+ function payloadHash(payload) {
14
+ const content = JSON.stringify(canonicalJson(payload));
15
+ return `sha256:${createHash("sha256").update(content, "utf-8").digest("hex")}`;
16
+ }
17
+ function visitStrings(value, visit) {
18
+ if (typeof value === "string") {
19
+ visit(value);
20
+ }
21
+ else if (Array.isArray(value)) {
22
+ for (const item of value)
23
+ visitStrings(item, visit);
24
+ }
25
+ else if (value && typeof value === "object") {
26
+ for (const item of Object.values(value))
27
+ visitStrings(item, visit);
28
+ }
29
+ }
30
+ export function workflowContainsToken(plan, token) {
31
+ let found = false;
32
+ visitStrings(plan, (text) => {
33
+ if (text.includes(token))
34
+ found = true;
35
+ });
36
+ return found;
37
+ }
38
+ export function workflowUsesArguments(plan, resolvedVariables = {}) {
39
+ return valueUsesArguments(plan, resolvedVariables);
40
+ }
41
+ export function valueUsesArguments(value, resolvedVariables = {}) {
42
+ let containsArguments = false;
43
+ visitStrings(value, (text) => {
44
+ if (text.includes("$ARGUMENTS"))
45
+ containsArguments = true;
46
+ });
47
+ if (containsArguments)
48
+ return true;
49
+ const flattenedVariables = resolveWorkflowVariableReferences(resolvedVariables);
50
+ return collectTemplateVariableRefs(value).some((name) => WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name) ||
51
+ !Object.prototype.hasOwnProperty.call(flattenedVariables, name) ||
52
+ flattenedVariables[name].includes("$ARGUMENTS"));
53
+ }
54
+ export function collectTemplateVariableRefs(value) {
55
+ const refs = new Set();
56
+ const variablePattern = /\{\{(\w+)\}\}/g;
57
+ visitStrings(value, (text) => {
58
+ for (const match of text.matchAll(variablePattern))
59
+ refs.add(match[1]);
60
+ });
61
+ return [...refs].sort();
62
+ }
63
+ export function collectWorkflowTemplateVariableRefs(plan) {
64
+ return collectTemplateVariableRefs(plan);
65
+ }
66
+ export function resolveVariableReferences(variables) {
67
+ const resolved = {};
68
+ const resolving = [];
69
+ const resolveVariable = (name) => {
70
+ if (resolved[name] !== undefined)
71
+ return resolved[name];
72
+ if (!Object.prototype.hasOwnProperty.call(variables, name)) {
73
+ const parent = resolving[resolving.length - 1] ?? "unknown";
74
+ throw new Error(`Variable ${parent} references undefined variable ${name}`);
75
+ }
76
+ const cycleIndex = resolving.indexOf(name);
77
+ if (cycleIndex >= 0) {
78
+ throw new Error(`Variable reference cycle: ${[...resolving.slice(cycleIndex), name].join(" -> ")}`);
79
+ }
80
+ resolving.push(name);
81
+ const value = variables[name].replace(/\{\{(\w+)\}\}/g, (_match, dependency) => resolveVariable(dependency));
82
+ resolving.pop();
83
+ resolved[name] = value;
84
+ return value;
85
+ };
86
+ for (const name of Object.keys(variables).sort())
87
+ resolveVariable(name);
88
+ return resolved;
89
+ }
90
+ export function resolveWorkflowVariableReferences(variables) {
91
+ return resolveVariableReferences({
92
+ ...variables,
93
+ ARGUMENTS: "$ARGUMENTS",
94
+ arguments: "$ARGUMENTS",
95
+ task_id: "$ARGUMENTS",
96
+ });
97
+ }
98
+ export function createWorkflowRuntimeManifest(options) {
99
+ const workflows = {};
100
+ for (const item of [...options.plans].sort((a, b) => {
101
+ const aId = a.plan.workflow_key ?? a.plan.source_workflow;
102
+ const bId = b.plan.workflow_key ?? b.plan.source_workflow;
103
+ return aId.localeCompare(bId);
104
+ })) {
105
+ const planVariables = resolveWorkflowVariableReferences(item.resolvedVariables);
106
+ const { plan } = item;
107
+ const workflow = plan.workflow_key ?? plan.source_workflow;
108
+ const templateVariableRefs = [
109
+ ...new Set([
110
+ ...collectWorkflowTemplateVariableRefs(plan),
111
+ ...(item.additionalTemplateVariableRefs ?? []),
112
+ ...collectTemplateVariableRefs(item.additionalRuntimeValues),
113
+ ]),
114
+ ].sort();
115
+ const resolvedVariables = Object.fromEntries(templateVariableRefs
116
+ .filter((name) => !WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name))
117
+ .filter((name) => Object.prototype.hasOwnProperty.call(planVariables, name))
118
+ .map((name) => [name, planVariables[name]]));
119
+ const undefinedVariableRefs = templateVariableRefs.filter((name) => !WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name) &&
120
+ !Object.prototype.hasOwnProperty.call(planVariables, name));
121
+ if (undefinedVariableRefs.length > 0) {
122
+ throw new Error(`Workflow ${workflow} references undefined template variable(s): ${undefinedVariableRefs.join(", ")}. Declare them in DNA variables or use $ARGUMENTS/task_id for runtime input.`);
123
+ }
124
+ const runtimeInputRefs = templateVariableRefs.filter((name) => WORKFLOW_RUNTIME_INPUT_VARIABLE_NAMES.has(name) ||
125
+ !Object.prototype.hasOwnProperty.call(planVariables, name));
126
+ const requiresArguments = workflowUsesArguments(plan, planVariables) ||
127
+ valueUsesArguments(item.additionalRuntimeValues, planVariables) ||
128
+ runtimeInputRefs.length > 0;
129
+ workflows[workflow] = {
130
+ workflow,
131
+ ...(item.workflowAsset ? { workflow_asset: item.workflowAsset } : {}),
132
+ template_variable_refs: templateVariableRefs,
133
+ runtime_input_refs: [
134
+ ...(requiresArguments ? ["ARGUMENTS"] : []),
135
+ ...runtimeInputRefs.filter((name) => name !== "ARGUMENTS"),
136
+ ].filter((name, index, names) => names.indexOf(name) === index).sort(),
137
+ requires_arguments: requiresArguments,
138
+ resolved_variables: resolvedVariables,
139
+ };
140
+ }
141
+ const payload = {
142
+ schema_version: WORKFLOW_RUNTIME_MANIFEST_SCHEMA,
143
+ compiled_at: options.compiledAt,
144
+ compiled_ir_hash: options.compiledIrHash,
145
+ source_dna_ids: [...options.sourceDnaIds],
146
+ workflows,
147
+ };
148
+ return { ...payload, manifest_hash: payloadHash(payload) };
149
+ }
150
+ function isStringRecord(value) {
151
+ return value !== null && typeof value === "object" && !Array.isArray(value) &&
152
+ Object.values(value).every((item) => typeof item === "string");
153
+ }
154
+ export function validateWorkflowRuntimeManifest(value, expectedCompiledIrHash) {
155
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
156
+ return { valid: false, reason: "workflow_runtime manifest must be an object" };
157
+ }
158
+ const manifest = value;
159
+ if (manifest.schema_version !== WORKFLOW_RUNTIME_MANIFEST_SCHEMA) {
160
+ return { valid: false, reason: `unsupported workflow_runtime schema: ${String(manifest.schema_version)}` };
161
+ }
162
+ if (typeof manifest.compiled_at !== "string" || !Array.isArray(manifest.source_dna_ids) ||
163
+ !manifest.source_dna_ids.every((item) => typeof item === "string") ||
164
+ !manifest.workflows || typeof manifest.workflows !== "object" || Array.isArray(manifest.workflows) ||
165
+ typeof manifest.manifest_hash !== "string") {
166
+ return { valid: false, reason: "workflow_runtime manifest has invalid metadata" };
167
+ }
168
+ if (typeof manifest.compiled_ir_hash !== "string") {
169
+ return { valid: false, reason: "workflow_runtime compiled_ir_hash is required" };
170
+ }
171
+ if (expectedCompiledIrHash && manifest.compiled_ir_hash !== expectedCompiledIrHash) {
172
+ return { valid: false, reason: "workflow_runtime compiled_ir_hash does not match compiled IR" };
173
+ }
174
+ const workflows = manifest.workflows;
175
+ for (const [workflow, rawBinding] of Object.entries(workflows)) {
176
+ if (!rawBinding || typeof rawBinding !== "object" || Array.isArray(rawBinding)) {
177
+ return { valid: false, reason: `workflow_runtime binding ${workflow} must be an object` };
178
+ }
179
+ const binding = rawBinding;
180
+ if (binding.workflow !== workflow ||
181
+ (binding.workflow_asset !== undefined && typeof binding.workflow_asset !== "string") ||
182
+ !Array.isArray(binding.template_variable_refs) ||
183
+ !binding.template_variable_refs.every((item) => typeof item === "string") ||
184
+ !Array.isArray(binding.runtime_input_refs) ||
185
+ !binding.runtime_input_refs.every((item) => typeof item === "string") ||
186
+ typeof binding.requires_arguments !== "boolean" ||
187
+ !isStringRecord(binding.resolved_variables)) {
188
+ return { valid: false, reason: `workflow_runtime binding ${workflow} is invalid` };
189
+ }
190
+ }
191
+ const { manifest_hash: manifestHash, ...payload } = manifest;
192
+ if (payloadHash(payload) !== manifestHash) {
193
+ return { valid: false, reason: "workflow_runtime manifest_hash is invalid" };
194
+ }
195
+ return { valid: true, manifest: manifest };
196
+ }
@@ -0,0 +1,12 @@
1
+ import type { IntentDNA, VariableDef } from "../schema/types.js";
2
+ export interface WorkflowVariableScopeIndex {
3
+ rawByNamespace: Map<string, Record<string, string | VariableDef>>;
4
+ namespaceLastIndex: Map<string, number>;
5
+ workflowNamespaces: Map<string, string>;
6
+ workflowAssets: Map<string, string>;
7
+ controllerNamespaces: Map<string, string>;
8
+ }
9
+ export declare function indexWorkflowVariableScopes(dnas: IntentDNA[]): WorkflowVariableScopeIndex;
10
+ export declare function defaultResolvedVariableScopes(index: WorkflowVariableScopeIndex): Map<string, Record<string, string>>;
11
+ export declare function flattenResolvedVariableScopes(index: WorkflowVariableScopeIndex, resolvedByNamespace: Map<string, Record<string, string>>): Record<string, string>;
12
+ export declare function resolvedVariablesForNamespace(resolvedByNamespace: Map<string, Record<string, string>>, namespace: string, overrides?: Record<string, string>): Record<string, string>;
@@ -0,0 +1,55 @@
1
+ import { resolveVariableReferences } from "./workflow-runtime-manifest.js";
2
+ export function indexWorkflowVariableScopes(dnas) {
3
+ const sorted = [...dnas].sort((a, b) => a.cascade.priority - b.cascade.priority);
4
+ const rawByNamespace = new Map();
5
+ const namespaceLastIndex = new Map();
6
+ const workflowNamespaces = new Map();
7
+ const workflowAssets = new Map();
8
+ const controllerNamespaces = new Map();
9
+ sorted.forEach((dna, index) => {
10
+ const namespace = dna.namespace ?? "";
11
+ const raw = rawByNamespace.get(namespace) ?? {};
12
+ Object.assign(raw, dna.variables ?? {});
13
+ rawByNamespace.set(namespace, raw);
14
+ namespaceLastIndex.set(namespace, index);
15
+ const workflows = dna.workflows ?? (dna.workflow ? { [dna.workflow.name]: dna.workflow } : {});
16
+ for (const name of Object.keys(workflows)) {
17
+ const workflowKey = namespace ? `${namespace}_${name}` : name;
18
+ workflowNamespaces.set(workflowKey, namespace);
19
+ workflowAssets.set(workflowKey, dna.id);
20
+ }
21
+ for (const name of Object.keys(dna.controllers ?? {})) {
22
+ controllerNamespaces.set(namespace ? `${namespace}_${name}` : name, namespace);
23
+ }
24
+ });
25
+ return {
26
+ rawByNamespace,
27
+ namespaceLastIndex,
28
+ workflowNamespaces,
29
+ workflowAssets,
30
+ controllerNamespaces,
31
+ };
32
+ }
33
+ export function defaultResolvedVariableScopes(index) {
34
+ return new Map([...index.rawByNamespace.entries()].map(([namespace, raw]) => [
35
+ namespace,
36
+ Object.fromEntries(Object.entries(raw).map(([name, value]) => [
37
+ name,
38
+ typeof value === "string" ? value : value.default,
39
+ ])),
40
+ ]));
41
+ }
42
+ export function flattenResolvedVariableScopes(index, resolvedByNamespace) {
43
+ const flat = {};
44
+ for (const [, resolved] of [...resolvedByNamespace.entries()].sort(([a], [b]) => (index.namespaceLastIndex.get(a) ?? -1) - (index.namespaceLastIndex.get(b) ?? -1))) {
45
+ Object.assign(flat, resolved);
46
+ }
47
+ return flat;
48
+ }
49
+ export function resolvedVariablesForNamespace(resolvedByNamespace, namespace, overrides = {}) {
50
+ return resolveVariableReferences({
51
+ ...(namespace ? resolvedByNamespace.get("") : {}),
52
+ ...(resolvedByNamespace.get(namespace) ?? {}),
53
+ ...overrides,
54
+ });
55
+ }
@@ -174,6 +174,10 @@ export declare const WORKFLOW_HANDOFF_EXHAUSTED_VALUES: readonly ["skip", "stop"
174
174
  export type WorkflowHandoffExhaustedAction = typeof WORKFLOW_HANDOFF_EXHAUSTED_VALUES[number];
175
175
  export declare const WORKFLOW_MERGE_STRATEGY_VALUES: readonly ["escalate"];
176
176
  export type WorkflowMergeStrategy = typeof WORKFLOW_MERGE_STRATEGY_VALUES[number];
177
+ export interface EvaluatorResultCompletion {
178
+ path: string;
179
+ require_score?: boolean;
180
+ }
177
181
  /** Completion check for a workflow step */
178
182
  export interface CompletionCheck {
179
183
  file_exists?: string;
@@ -183,6 +187,7 @@ export interface CompletionCheck {
183
187
  pattern: string;
184
188
  };
185
189
  command_success?: string;
190
+ evaluator_result?: EvaluatorResultCompletion;
186
191
  }
187
192
  /** Single step in a workflow */
188
193
  export interface WorkflowStepDef {
@@ -522,6 +527,10 @@ export interface WorkflowState {
522
527
  started_at: string;
523
528
  inputs?: Record<string, string>;
524
529
  resolved_variables?: Record<string, string>;
530
+ resolved_variables_source?: "compiled_manifest" | "legacy_skill";
531
+ workflow_runtime_manifest_hash?: string;
532
+ workflow_runtime_compiled_ir_hash?: string;
533
+ workflow_inputs_pinned?: boolean;
525
534
  completed_artifacts?: CompletedArtifactEntry[];
526
535
  }
527
536
  export interface ConstraintIR {