@agentproto/workflow-loader 0.1.5 → 0.2.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/dist/index.mjs +132 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { readFile, stat } from 'fs/promises';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
2
3
|
import { isAbsolute, join, dirname } from 'path';
|
|
3
4
|
import { pathToFileURL } from 'url';
|
|
4
5
|
import { parseWorkflowManifest, workflowFromManifest } from '@agentproto/workflow/manifest';
|
|
6
|
+
import { assertKnownStepRefs } from '@agentproto/workflow';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* @agentproto/workflow-loader v0.1.0-alpha
|
|
@@ -53,6 +55,129 @@ var WorkflowLoadError = class extends Error {
|
|
|
53
55
|
function isWorkflowHandle(v) {
|
|
54
56
|
return typeof v === "object" && v !== null && typeof v.id === "string" && Array.isArray(v.steps);
|
|
55
57
|
}
|
|
58
|
+
function translateSubworkflowWith(steps, knownStepIds) {
|
|
59
|
+
if (!Array.isArray(steps)) return;
|
|
60
|
+
for (const step of steps) {
|
|
61
|
+
if (step === null || typeof step !== "object") continue;
|
|
62
|
+
const s = step;
|
|
63
|
+
if (s.kind === "subworkflow" && s.with !== void 0) {
|
|
64
|
+
const id = typeof s.id === "string" ? s.id : "(unid)";
|
|
65
|
+
if (s.inputs !== void 0) {
|
|
66
|
+
throw new WorkflowLoadError(
|
|
67
|
+
`subworkflow step '${id}' declares both 'with' and 'inputs' \u2014 use one`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
assertKnownStepRefs(
|
|
71
|
+
s.with,
|
|
72
|
+
knownStepIds,
|
|
73
|
+
`subworkflow step '${id}' with`,
|
|
74
|
+
{
|
|
75
|
+
makeError: (message) => new WorkflowLoadError(message),
|
|
76
|
+
extendLabel: true
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
s.inputs = s.with;
|
|
80
|
+
delete s.with;
|
|
81
|
+
}
|
|
82
|
+
translateSubworkflowWith(s.steps, knownStepIds);
|
|
83
|
+
if (Array.isArray(s.branches)) {
|
|
84
|
+
for (const br of s.branches)
|
|
85
|
+
translateSubworkflowWith(br?.steps, knownStepIds);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function applyAgentHarnessPromptFiles(steps, workflowMdPath) {
|
|
90
|
+
if (!Array.isArray(steps)) return;
|
|
91
|
+
for (const step of steps) {
|
|
92
|
+
if (step === null || typeof step !== "object") continue;
|
|
93
|
+
const s = step;
|
|
94
|
+
if (s.kind === "agent" && s.harness && typeof s.harness === "object") {
|
|
95
|
+
const harness = s.harness;
|
|
96
|
+
const promptFile = harness.promptFile;
|
|
97
|
+
if (typeof promptFile === "string" && promptFile.length > 0) {
|
|
98
|
+
const abs = isAbsolute(promptFile) ? promptFile : join(dirname(workflowMdPath), promptFile);
|
|
99
|
+
let bytes;
|
|
100
|
+
try {
|
|
101
|
+
bytes = await readFile(abs);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const id = typeof s.id === "string" ? s.id : "(unid)";
|
|
104
|
+
throw new WorkflowLoadError(
|
|
105
|
+
`agent step '${id}': cannot read harness.promptFile '${promptFile}': ${err instanceof Error ? err.message : String(err)}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
s.prompt = bytes.toString("utf8").trim();
|
|
109
|
+
harness.promptSha = createHash("sha256").update(bytes).digest("hex");
|
|
110
|
+
}
|
|
111
|
+
await applyAgentHarnessKnowledge(harness, s, workflowMdPath);
|
|
112
|
+
}
|
|
113
|
+
await applyAgentHarnessPromptFiles(s.steps, workflowMdPath);
|
|
114
|
+
if (Array.isArray(s.branches)) {
|
|
115
|
+
for (const br of s.branches) {
|
|
116
|
+
await applyAgentHarnessPromptFiles(br?.steps, workflowMdPath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function hasRef(s) {
|
|
122
|
+
return s.includes("$");
|
|
123
|
+
}
|
|
124
|
+
async function applyAgentHarnessKnowledge(harness, step, workflowMdPath) {
|
|
125
|
+
const knowledge = harness.knowledge;
|
|
126
|
+
if (!Array.isArray(knowledge)) return;
|
|
127
|
+
const id = typeof step.id === "string" ? step.id : "(unid)";
|
|
128
|
+
for (const [i, sel] of knowledge.entries()) {
|
|
129
|
+
if (sel === null || typeof sel !== "object") continue;
|
|
130
|
+
const selector = sel;
|
|
131
|
+
if (selector.mode !== void 0 && selector.mode !== "files") {
|
|
132
|
+
throw new WorkflowLoadError(
|
|
133
|
+
`agent step '${id}': harness.knowledge[${i}].mode must be "files" (v1 supports no other mode); got ${typeof selector.mode === "string" ? `"${selector.mode}"` : String(selector.mode)}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (selector.deferred !== void 0) {
|
|
137
|
+
throw new WorkflowLoadError(
|
|
138
|
+
`agent step '${id}': harness.knowledge[${i}].deferred is an internal loader field and cannot be authored`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const workspace = selector.workspace;
|
|
142
|
+
if (typeof workspace !== "string" || workspace.length === 0) continue;
|
|
143
|
+
const tagStrings = [
|
|
144
|
+
...Array.isArray(selector.anyOf) ? selector.anyOf : [],
|
|
145
|
+
...Array.isArray(selector.allOf) ? selector.allOf : [],
|
|
146
|
+
...Array.isArray(selector.kinds) ? selector.kinds : []
|
|
147
|
+
].filter((t) => typeof t === "string");
|
|
148
|
+
if (hasRef(workspace) || tagStrings.some(hasRef)) {
|
|
149
|
+
selector.deferred = true;
|
|
150
|
+
if (hasRef(workspace)) continue;
|
|
151
|
+
}
|
|
152
|
+
const abs = isAbsolute(workspace) ? workspace : join(dirname(workflowMdPath), workspace);
|
|
153
|
+
let st;
|
|
154
|
+
try {
|
|
155
|
+
st = await stat(abs);
|
|
156
|
+
} catch {
|
|
157
|
+
st = null;
|
|
158
|
+
}
|
|
159
|
+
if (st === null || !st.isDirectory()) {
|
|
160
|
+
throw new WorkflowLoadError(
|
|
161
|
+
`agent step '${id}': harness.knowledge[${i}].workspace '${workspace}' does not name an existing directory (resolved to '${abs}')`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
selector.workspace = abs;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function collectManifestStepIds(steps, ids = /* @__PURE__ */ new Set()) {
|
|
168
|
+
if (!Array.isArray(steps)) return ids;
|
|
169
|
+
for (const step of steps) {
|
|
170
|
+
if (step === null || typeof step !== "object") continue;
|
|
171
|
+
const s = step;
|
|
172
|
+
if (typeof s.id === "string") ids.add(s.id);
|
|
173
|
+
collectManifestStepIds(s.steps, ids);
|
|
174
|
+
if (Array.isArray(s.branches)) {
|
|
175
|
+
for (const br of s.branches)
|
|
176
|
+
collectManifestStepIds(br?.steps, ids);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return ids;
|
|
180
|
+
}
|
|
56
181
|
async function importEntryHandle(workflowMdPath, entry) {
|
|
57
182
|
const abs = isAbsolute(entry) ? entry : join(dirname(workflowMdPath), entry);
|
|
58
183
|
let href = pathToFileURL(abs).href;
|
|
@@ -90,8 +215,14 @@ async function loadWorkflowHandle(workflowMdPath) {
|
|
|
90
215
|
);
|
|
91
216
|
}
|
|
92
217
|
const manifest = parseWorkflowManifest(source);
|
|
218
|
+
manifest.frontmatter = structuredClone(manifest.frontmatter);
|
|
93
219
|
const entry = manifest.frontmatter.entry;
|
|
94
|
-
if (!entry)
|
|
220
|
+
if (!entry) {
|
|
221
|
+
const knownStepIds = collectManifestStepIds(manifest.frontmatter.steps);
|
|
222
|
+
translateSubworkflowWith(manifest.frontmatter.steps, knownStepIds);
|
|
223
|
+
await applyAgentHarnessPromptFiles(manifest.frontmatter.steps, workflowMdPath);
|
|
224
|
+
return workflowFromManifest(manifest);
|
|
225
|
+
}
|
|
95
226
|
const handle = await importEntryHandle(workflowMdPath, entry);
|
|
96
227
|
reconcileEntry(manifest, handle);
|
|
97
228
|
return handle;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reconcile.ts","../src/load-workflow.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Reconcile a WORKFLOW.md manifest against the handle its `entry` module\n * exports. An entry-backed workflow declares its step graph twice — once as\n * governance-facing manifest data, once as the runnable handle — so the two\n * MUST agree, or the manifest is lying about what actually runs. This checks\n * the workflow id and the top-level step (id, kind) sequence; nested step\n * lists (map / loop / parallel bodies) are the entry's concern.\n */\n\nimport type { WorkflowManifest } from \"@agentproto/workflow/manifest\"\nimport type { WorkflowHandle } from \"@agentproto/workflow\"\n\nexport class WorkflowReconcileError extends Error {\n constructor(message: string) {\n super(`reconcileEntry: ${message}`)\n this.name = \"WorkflowReconcileError\"\n }\n}\n\ninterface IdKind {\n readonly id: string\n readonly kind: string\n}\n\nconst idKind = (steps: readonly IdKind[]): IdKind[] =>\n steps.map((s) => ({ id: s.id, kind: s.kind }))\n\nconst summary = (steps: readonly IdKind[]): string =>\n steps.map((s) => `${s.id}:${s.kind}`).join(\" → \") || \"(none)\"\n\nexport function reconcileEntry(\n manifest: WorkflowManifest,\n handle: WorkflowHandle,\n): void {\n const fm = manifest.frontmatter\n if (fm.id !== handle.id) {\n throw new WorkflowReconcileError(\n `manifest id '${fm.id}' ≠ entry id '${handle.id}'.`,\n )\n }\n const declared = idKind(fm.steps as readonly IdKind[])\n const actual = idKind(handle.steps as readonly IdKind[])\n if (declared.length !== actual.length) {\n throw new WorkflowReconcileError(\n `step count differs — manifest declares ${declared.length} [${summary(declared)}], ` +\n `entry has ${actual.length} [${summary(actual)}].`,\n )\n }\n for (let i = 0; i < declared.length; i++) {\n const d = declared[i]!\n const a = actual[i]!\n if (d.id !== a.id || d.kind !== a.kind) {\n throw new WorkflowReconcileError(\n `step ${i} differs — manifest '${d.id}:${d.kind}' ≠ entry '${a.id}:${a.kind}'. ` +\n `manifest [${summary(declared)}] vs entry [${summary(actual)}].`,\n )\n }\n }\n}\n","/**\n * loadWorkflowHandle — read a WORKFLOW.md off disk into a WorkflowHandle.\n *\n * The pure packages stop at the page boundary: `@agentproto/workflow` parses a\n * manifest *string*, `@agentproto/workflow-runtime` compiles an in-memory\n * handle. Neither touches the filesystem. This is the host-side seam that does:\n *\n * read file → parse manifest → (if `entry`) import the module + reconcile\n *\n * With no `entry`, the manifest IS the workflow (purely-declarative path). With\n * an `entry`, its default-exported handle is the source of truth for runtime\n * step logic and the manifest is reconciled against it — the two MUST agree.\n *\n * The entry import mirrors the CLI's adapter loader: resolve relative to the\n * manifest's directory, import via a `file://` URL so an absolute path works\n * without an `exports` declaration.\n */\n\nimport { readFile, stat } from \"node:fs/promises\"\nimport { dirname, isAbsolute, join } from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\n\nimport {\n parseWorkflowManifest,\n workflowFromManifest,\n type WorkflowManifest,\n} from \"@agentproto/workflow/manifest\"\nimport type { WorkflowHandle } from \"@agentproto/workflow\"\n\nimport { reconcileEntry } from \"./reconcile.js\"\n\nexport class WorkflowLoadError extends Error {\n constructor(message: string) {\n super(`loadWorkflow: ${message}`)\n this.name = \"WorkflowLoadError\"\n }\n}\n\n/** A handle is a plain object carrying an `id` and a `steps` array. */\nfunction isWorkflowHandle(v: unknown): v is WorkflowHandle {\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as { id?: unknown }).id === \"string\" &&\n Array.isArray((v as { steps?: unknown }).steps)\n )\n}\n\nasync function importEntryHandle(\n workflowMdPath: string,\n entry: string,\n): Promise<WorkflowHandle> {\n const abs = isAbsolute(entry) ? entry : join(dirname(workflowMdPath), entry)\n\n // Cache-bust the ESM import by mtime so a long-lived daemon re-reads an edited\n // entry.mjs instead of serving Node's process-lifetime URL-cached module —\n // which, because the manifest IS re-read fresh, would otherwise fail\n // reconcileEntry with a spurious step-count mismatch after an edit. Fresh\n // daemons / CI boot cold, so they never need it — this only helps the dev loop.\n //\n // Skipped under the Vite/vitest transform (`process.env.VITEST`): Vite owns\n // import() resolution there and rejects a query on a file URL (and a failed\n // attempt poisons its resolver for the plain retry too), while a test process\n // never has the long-lived-daemon staleness this guards against.\n let href = pathToFileURL(abs).href\n if (!process.env.VITEST) {\n try {\n const url = pathToFileURL(abs)\n url.searchParams.set(\"v\", String((await stat(abs)).mtimeMs))\n href = url.href\n } catch {\n // stat race with an in-flight edit — fall back to the plain URL.\n }\n }\n\n let mod: Record<string, unknown>\n try {\n mod = (await import(href)) as Record<string, unknown>\n } catch (err) {\n throw new WorkflowLoadError(\n `cannot import entry '${entry}': ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n const exported = mod.default\n if (!isWorkflowHandle(exported)) {\n throw new WorkflowLoadError(\n `entry '${entry}' must default-export a WorkflowHandle (e.g. from defineWorkflow) ` +\n `carrying an 'id' + 'steps'; got ${exported === undefined ? \"no default export\" : typeof exported}.`,\n )\n }\n return exported\n}\n\n/**\n * Load and validate a WORKFLOW.md at `workflowMdPath` into a WorkflowHandle.\n * Throws {@link WorkflowLoadError} on a missing/unreadable file or a bad entry\n * module, the parser's error on invalid frontmatter, and\n * {@link WorkflowReconcileError} when an entry's graph disagrees with the\n * manifest. The returned handle still needs `compileWorkflow` + a tool registry\n * to run — this stops at the validated, in-memory handle.\n */\nexport async function loadWorkflowHandle(\n workflowMdPath: string,\n): Promise<WorkflowHandle> {\n let source: string\n try {\n source = await readFile(workflowMdPath, \"utf8\")\n } catch (err) {\n throw new WorkflowLoadError(\n `cannot read '${workflowMdPath}': ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n const manifest: WorkflowManifest = parseWorkflowManifest(source)\n const entry = manifest.frontmatter.entry\n if (!entry) return workflowFromManifest(manifest)\n const handle = await importEntryHandle(workflowMdPath, entry)\n reconcileEntry(manifest, handle)\n return handle\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/reconcile.ts","../src/load-workflow.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Reconcile a WORKFLOW.md manifest against the handle its `entry` module\n * exports. An entry-backed workflow declares its step graph twice — once as\n * governance-facing manifest data, once as the runnable handle — so the two\n * MUST agree, or the manifest is lying about what actually runs. This checks\n * the workflow id and the top-level step (id, kind) sequence; nested step\n * lists (map / loop / parallel bodies) are the entry's concern.\n */\n\nimport type { WorkflowManifest } from \"@agentproto/workflow/manifest\"\nimport type { WorkflowHandle } from \"@agentproto/workflow\"\n\nexport class WorkflowReconcileError extends Error {\n constructor(message: string) {\n super(`reconcileEntry: ${message}`)\n this.name = \"WorkflowReconcileError\"\n }\n}\n\ninterface IdKind {\n readonly id: string\n readonly kind: string\n}\n\nconst idKind = (steps: readonly IdKind[]): IdKind[] =>\n steps.map((s) => ({ id: s.id, kind: s.kind }))\n\nconst summary = (steps: readonly IdKind[]): string =>\n steps.map((s) => `${s.id}:${s.kind}`).join(\" → \") || \"(none)\"\n\nexport function reconcileEntry(\n manifest: WorkflowManifest,\n handle: WorkflowHandle,\n): void {\n const fm = manifest.frontmatter\n if (fm.id !== handle.id) {\n throw new WorkflowReconcileError(\n `manifest id '${fm.id}' ≠ entry id '${handle.id}'.`,\n )\n }\n const declared = idKind(fm.steps as readonly IdKind[])\n const actual = idKind(handle.steps as readonly IdKind[])\n if (declared.length !== actual.length) {\n throw new WorkflowReconcileError(\n `step count differs — manifest declares ${declared.length} [${summary(declared)}], ` +\n `entry has ${actual.length} [${summary(actual)}].`,\n )\n }\n for (let i = 0; i < declared.length; i++) {\n const d = declared[i]!\n const a = actual[i]!\n if (d.id !== a.id || d.kind !== a.kind) {\n throw new WorkflowReconcileError(\n `step ${i} differs — manifest '${d.id}:${d.kind}' ≠ entry '${a.id}:${a.kind}'. ` +\n `manifest [${summary(declared)}] vs entry [${summary(actual)}].`,\n )\n }\n }\n}\n","/**\n * loadWorkflowHandle — read a WORKFLOW.md off disk into a WorkflowHandle.\n *\n * The pure packages stop at the page boundary: `@agentproto/workflow` parses a\n * manifest *string*, `@agentproto/workflow-runtime` compiles an in-memory\n * handle. Neither touches the filesystem. This is the host-side seam that does:\n *\n * read file → parse manifest → (if `entry`) import the module + reconcile\n *\n * With no `entry`, the manifest IS the workflow (purely-declarative path). With\n * an `entry`, its default-exported handle is the source of truth for runtime\n * step logic and the manifest is reconciled against it — the two MUST agree.\n *\n * The entry import mirrors the CLI's adapter loader: resolve relative to the\n * manifest's directory, import via a `file://` URL so an absolute path works\n * without an `exports` declaration.\n */\n\nimport { readFile, stat } from \"node:fs/promises\"\nimport { createHash } from \"node:crypto\"\nimport { dirname, isAbsolute, join } from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\n\nimport {\n parseWorkflowManifest,\n workflowFromManifest,\n type WorkflowManifest,\n} from \"@agentproto/workflow/manifest\"\nimport type { WorkflowHandle } from \"@agentproto/workflow\"\nimport { assertKnownStepRefs } from \"@agentproto/workflow\"\n\nimport { reconcileEntry } from \"./reconcile.js\"\n\nexport class WorkflowLoadError extends Error {\n constructor(message: string) {\n super(`loadWorkflow: ${message}`)\n this.name = \"WorkflowLoadError\"\n }\n}\n\n/** A handle is a plain object carrying an `id` and a `steps` array. */\nfunction isWorkflowHandle(v: unknown): v is WorkflowHandle {\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as { id?: unknown }).id === \"string\" &&\n Array.isArray((v as { steps?: unknown }).steps)\n )\n}\n\n/**\n * Translate a declarative `subworkflow` step's `with:` block into the `inputs`\n * projection the runtime compiler threads into `step.input(b)` (AIP-16 ref\n * grammar: `$input.*`, `$steps.<id>.*`, literals — resolved against the\n * PARENT's bindings). Without this, a manifest `with:` is dead documentation:\n * the child receives the parent's raw input verbatim. Steps without `with:`\n * are left untouched. Nested step lists (map / loop / parallel bodies and\n * branch arms) are walked too.\n */\nfunction translateSubworkflowWith(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n steps: unknown,\n knownStepIds: ReadonlySet<string>,\n): void {\n if (!Array.isArray(steps)) return\n for (const step of steps) {\n if (step === null || typeof step !== \"object\") continue\n const s = step as Record<string, unknown>\n if (s.kind === \"subworkflow\" && s.with !== undefined) {\n const id = typeof s.id === \"string\" ? s.id : \"(unid)\"\n if (s.inputs !== undefined) {\n throw new WorkflowLoadError(\n `subworkflow step '${id}' declares both 'with' and 'inputs' — use one`,\n )\n }\n assertKnownStepRefs(\n s.with,\n knownStepIds,\n `subworkflow step '${id}' with`,\n {\n makeError: (message) => new WorkflowLoadError(message),\n extendLabel: true,\n },\n )\n s.inputs = s.with\n delete s.with\n }\n translateSubworkflowWith(s.steps, knownStepIds)\n if (Array.isArray(s.branches)) {\n for (const br of s.branches)\n translateSubworkflowWith((br as Record<string, unknown>)?.steps, knownStepIds)\n }\n }\n}\n\n/**\n * Resolve every `kind: \"agent\"` step's `harness.promptFile` (AIP-15 P2),\n * relative to the WORKFLOW.md's own directory: read the file's raw bytes,\n * use its decoded text as the step's `prompt` (replacing any inline one),\n * and record `harness.promptSha` (sha256 hex of the raw bytes) on the step\n * so a consumer can verify exactly which prompt version a run used. Nested\n * step lists (map / loop / parallel bodies, branch arms) are walked too —\n * same traversal shape as {@link translateSubworkflowWith}.\n */\nasync function applyAgentHarnessPromptFiles(\n steps: unknown,\n workflowMdPath: string,\n): Promise<void> {\n if (!Array.isArray(steps)) return\n for (const step of steps) {\n if (step === null || typeof step !== \"object\") continue\n const s = step as Record<string, unknown>\n if (s.kind === \"agent\" && s.harness && typeof s.harness === \"object\") {\n const harness = s.harness as Record<string, unknown>\n const promptFile = harness.promptFile\n if (typeof promptFile === \"string\" && promptFile.length > 0) {\n const abs = isAbsolute(promptFile)\n ? promptFile\n : join(dirname(workflowMdPath), promptFile)\n let bytes: Buffer\n try {\n bytes = await readFile(abs)\n } catch (err) {\n const id = typeof s.id === \"string\" ? s.id : \"(unid)\"\n throw new WorkflowLoadError(\n `agent step '${id}': cannot read harness.promptFile '${promptFile}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n s.prompt = bytes.toString(\"utf8\").trim()\n harness.promptSha = createHash(\"sha256\").update(bytes).digest(\"hex\")\n }\n await applyAgentHarnessKnowledge(harness, s, workflowMdPath)\n }\n await applyAgentHarnessPromptFiles(s.steps, workflowMdPath)\n if (Array.isArray(s.branches)) {\n for (const br of s.branches) {\n await applyAgentHarnessPromptFiles((br as Record<string, unknown>)?.steps, workflowMdPath)\n }\n }\n }\n}\n\n/**\n * True when a selector string carries a `$…` run-time reference — such a\n * string is left untouched here and resolved per run by\n * `resolveKnowledgeSelectors` in `@agentproto/workflow-runtime`.\n */\nfunction hasRef(s: string): boolean {\n return s.includes(\"$\")\n}\n\n/**\n * Resolve every `harness.knowledge[]` selector's `workspace` (AIP-15 P2),\n * relative to the WORKFLOW.md's own directory — the same rule\n * `harness.promptFile` follows — rewriting it in place to the absolute path\n * so the runtime materializer never re-resolves against a different cwd. A\n * workspace directory that does not exist fails the load, as does a `mode`\n * other than the v1-only `\"files\"`.\n *\n * Exception: a selector whose `workspace` (or any tag/kind string) contains a\n * `$` carries run-time references (AIP-16 grammar) — its strings are left\n * verbatim, no relative resolution and no existence check happens here, and\n * the selector is flagged `deferred: true` (an internal field the runtime\n * consumes and strips; a user-authored `deferred` is rejected).\n */\nasync function applyAgentHarnessKnowledge(\n harness: Record<string, unknown>,\n step: Record<string, unknown>,\n workflowMdPath: string,\n): Promise<void> {\n const knowledge = harness.knowledge\n if (!Array.isArray(knowledge)) return\n const id = typeof step.id === \"string\" ? step.id : \"(unid)\"\n for (const [i, sel] of knowledge.entries()) {\n if (sel === null || typeof sel !== \"object\") continue\n const selector = sel as Record<string, unknown>\n if (selector.mode !== undefined && selector.mode !== \"files\") {\n throw new WorkflowLoadError(\n `agent step '${id}': harness.knowledge[${i}].mode must be \"files\" (v1 supports no other mode); got ${\n typeof selector.mode === \"string\" ? `\"${selector.mode}\"` : String(selector.mode)\n }`,\n )\n }\n if (selector.deferred !== undefined) {\n throw new WorkflowLoadError(\n `agent step '${id}': harness.knowledge[${i}].deferred is an internal loader field and cannot be authored`,\n )\n }\n const workspace = selector.workspace\n if (typeof workspace !== \"string\" || workspace.length === 0) continue\n const tagStrings = [\n ...(Array.isArray(selector.anyOf) ? selector.anyOf : []),\n ...(Array.isArray(selector.allOf) ? selector.allOf : []),\n ...(Array.isArray(selector.kinds) ? selector.kinds : []),\n ].filter((t): t is string => typeof t === \"string\")\n if (hasRef(workspace) || tagStrings.some(hasRef)) {\n selector.deferred = true\n // A workspace without a ref still resolves (and is still existence-\n // checked) against the WORKFLOW.md dir even when only the tags/kinds\n // carry refs — otherwise a relative `./corpus` would be joined to the\n // run cwd at materialization time, which is not its base.\n if (hasRef(workspace)) continue\n }\n const abs = isAbsolute(workspace)\n ? workspace\n : join(dirname(workflowMdPath), workspace)\n let st\n try {\n st = await stat(abs)\n } catch {\n st = null\n }\n if (st === null || !st.isDirectory()) {\n throw new WorkflowLoadError(\n `agent step '${id}': harness.knowledge[${i}].workspace '${workspace}' does not name an existing directory (resolved to '${abs}')`,\n )\n }\n selector.workspace = abs\n }\n}\n\n/** Collect every step id declared anywhere in a manifest step list,\n * including nested map / loop / parallel / branch bodies. */\nfunction collectManifestStepIds(steps: unknown, ids: Set<string> = new Set()): Set<string> {\n if (!Array.isArray(steps)) return ids\n for (const step of steps) {\n if (step === null || typeof step !== \"object\") continue\n const s = step as Record<string, unknown>\n if (typeof s.id === \"string\") ids.add(s.id)\n collectManifestStepIds(s.steps, ids)\n if (Array.isArray(s.branches)) {\n for (const br of s.branches)\n collectManifestStepIds((br as Record<string, unknown>)?.steps, ids)\n }\n }\n return ids\n}\n\nasync function importEntryHandle(\n workflowMdPath: string,\n entry: string,\n): Promise<WorkflowHandle> {\n const abs = isAbsolute(entry) ? entry : join(dirname(workflowMdPath), entry)\n\n // Cache-bust the ESM import by mtime so a long-lived daemon re-reads an edited\n // entry.mjs instead of serving Node's process-lifetime URL-cached module —\n // which, because the manifest IS re-read fresh, would otherwise fail\n // reconcileEntry with a spurious step-count mismatch after an edit. Fresh\n // daemons / CI boot cold, so they never need it — this only helps the dev loop.\n //\n // Skipped under the Vite/vitest transform (`process.env.VITEST`): Vite owns\n // import() resolution there and rejects a query on a file URL (and a failed\n // attempt poisons its resolver for the plain retry too), while a test process\n // never has the long-lived-daemon staleness this guards against.\n let href = pathToFileURL(abs).href\n if (!process.env.VITEST) {\n try {\n const url = pathToFileURL(abs)\n url.searchParams.set(\"v\", String((await stat(abs)).mtimeMs))\n href = url.href\n } catch {\n // stat race with an in-flight edit — fall back to the plain URL.\n }\n }\n\n let mod: Record<string, unknown>\n try {\n mod = (await import(href)) as Record<string, unknown>\n } catch (err) {\n throw new WorkflowLoadError(\n `cannot import entry '${entry}': ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n const exported = mod.default\n if (!isWorkflowHandle(exported)) {\n throw new WorkflowLoadError(\n `entry '${entry}' must default-export a WorkflowHandle (e.g. from defineWorkflow) ` +\n `carrying an 'id' + 'steps'; got ${exported === undefined ? \"no default export\" : typeof exported}.`,\n )\n }\n return exported\n}\n\n/**\n * Load and validate a WORKFLOW.md at `workflowMdPath` into a WorkflowHandle.\n * Throws {@link WorkflowLoadError} on a missing/unreadable file or a bad entry\n * module, the parser's error on invalid frontmatter, and\n * {@link WorkflowReconcileError} when an entry's graph disagrees with the\n * manifest. The returned handle still needs `compileWorkflow` + a tool registry\n * to run — this stops at the validated, in-memory handle.\n */\nexport async function loadWorkflowHandle(\n workflowMdPath: string,\n): Promise<WorkflowHandle> {\n let source: string\n try {\n source = await readFile(workflowMdPath, \"utf8\")\n } catch (err) {\n throw new WorkflowLoadError(\n `cannot read '${workflowMdPath}': ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n const manifest: WorkflowManifest = parseWorkflowManifest(source)\n // gray-matter memoizes its YAML parse by content — a second load of an\n // identical manifest would otherwise hand back the SAME objects a previous\n // load already rewrote in place (absolute workspaces, prompt text, the\n // internal `deferred` flag), breaking the authored-field rejection and\n // re-resolving against a stale base. Clone so every load starts clean.\n manifest.frontmatter = structuredClone(manifest.frontmatter)\n const entry = manifest.frontmatter.entry\n if (!entry) {\n const knownStepIds = collectManifestStepIds(manifest.frontmatter.steps)\n translateSubworkflowWith(manifest.frontmatter.steps, knownStepIds)\n await applyAgentHarnessPromptFiles(manifest.frontmatter.steps, workflowMdPath)\n return workflowFromManifest(manifest)\n }\n const handle = await importEntryHandle(workflowMdPath, entry)\n reconcileEntry(manifest, handle)\n return handle\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/workflow-loader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Host-side loader for AIP-15 WORKFLOW.md: reads a manifest off disk, imports its optional TypeScript entry module, and reconciles the entry's step graph against the declared manifest. The I/O layer the pure @agentproto/workflow + @agentproto/workflow-runtime packages deliberately omit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -38,15 +38,15 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@agentproto/workflow": "0.
|
|
41
|
+
"@agentproto/workflow": "0.6.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@types/node": "^25.
|
|
44
|
+
"@types/node": "^25.9.5",
|
|
45
45
|
"tsup": "^8.5.1",
|
|
46
46
|
"typescript": "^5.9.3",
|
|
47
|
-
"vitest": "^3.2.
|
|
47
|
+
"vitest": "^3.2.7",
|
|
48
48
|
"zod": "^4.5.4",
|
|
49
|
-
"@agentproto/tooling": "0.1.0
|
|
49
|
+
"@agentproto/tooling": "0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"dev": "tsup --watch",
|