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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/cli/commands/candidate-promotion.d.ts +15 -0
- package/dist/cli/commands/candidate-promotion.js +197 -0
- package/dist/cli/commands/run-lifecycle.js +1 -0
- package/dist/cli/commands/run.js +76 -42
- package/dist/cli/commands/setup.d.ts +10 -0
- package/dist/cli/commands/setup.js +129 -36
- package/dist/cli/commands/source-assets.d.ts +32 -0
- package/dist/cli/commands/source-assets.js +154 -0
- package/dist/cli/commands/sync.js +52 -12
- package/dist/cli/commands/templates.js +59 -37
- package/dist/cli/index.js +161 -5
- package/dist/compiler/cascade.js +1 -1
- package/dist/hooks/cli.d.ts +6 -0
- package/dist/hooks/cli.js +118 -10
- package/dist/hooks/enforcement-boundary.d.ts +1 -1
- package/dist/hooks/protocol.d.ts +1 -1
- package/dist/hooks/state.d.ts +5 -0
- package/dist/hooks/state.js +21 -6
- package/dist/mcp/tools-state.js +251 -15
- package/dist/runtime/claude-sync-target.d.ts +2 -0
- package/dist/runtime/claude-sync-target.js +1 -1
- package/dist/runtime/evaluator.d.ts +13 -0
- package/dist/runtime/evaluator.js +46 -0
- package/dist/runtime/index.d.ts +8 -4
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/plugin-adapter.d.ts +14 -1
- package/dist/runtime/plugin-adapter.js +23 -9
- package/dist/runtime/run-contracts.d.ts +5 -3
- package/dist/runtime/run-contracts.js +11 -1
- package/dist/runtime/run-controller.d.ts +15 -1
- package/dist/runtime/run-controller.js +235 -94
- package/dist/runtime/skill-adapter.js +24 -53
- package/dist/runtime/verifier.js +101 -2
- package/dist/runtime/workflow-plan-adapter.d.ts +2 -1
- package/dist/runtime/workflow-plan-adapter.js +78 -11
- package/dist/runtime/workflow-runtime-manifest.d.ts +48 -0
- package/dist/runtime/workflow-runtime-manifest.js +196 -0
- package/dist/runtime/workflow-variable-scopes.d.ts +12 -0
- package/dist/runtime/workflow-variable-scopes.js +55 -0
- package/dist/schema/types.d.ts +9 -0
- package/dist/schema/validate.js +29 -1
- package/dist/sources/authoring-packet.d.ts +8 -0
- package/dist/sources/authoring-packet.js +85 -0
- package/dist/sources/discovery.d.ts +7 -0
- package/dist/sources/discovery.js +150 -0
- package/dist/sources/license.d.ts +5 -0
- package/dist/sources/license.js +94 -0
- package/dist/sources/manifest.d.ts +11 -0
- package/dist/sources/manifest.js +84 -0
- package/dist/sources/skill-markdown.d.ts +6 -0
- package/dist/sources/skill-markdown.js +174 -0
- package/dist/sources/types.d.ts +125 -0
- package/dist/sources/types.js +1 -0
- package/dist/templates/asset-refinery.dna.yaml +192 -0
- package/dist/templates/flutter-behavior-lock.dna.yaml +17 -0
- package/dist/templates/flutter-refactoring-rescue.dna.yaml +11 -0
- package/dist/templates/flutter-rewrite.dna.yaml +154 -28
- package/dist/templates/metadata.js +2 -0
- package/dist/templates/multi-agent-handoff-coordination.dna.yaml +102 -37
- package/dist/templates/persistent-executor.dna.yaml +61 -3
- package/dist/templates/research-improvement.dna.yaml +295 -0
- package/dist/templates/subagent-parallel.dna.yaml +90 -17
- package/dist/templates/workflow-evidence-review.dna.yaml +8 -0
- package/dist/workflow/candidate-projection.d.ts +33 -0
- package/dist/workflow/candidate-projection.js +70 -0
- package/package.json +3 -1
- package/scripts/sync-release-plugin.cjs +87 -0
package/dist/runtime/verifier.js
CHANGED
|
@@ -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
|
-
?
|
|
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
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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 (
|
|
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,
|
|
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
|
+
}
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -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 {
|