@agentproto/workflow-loader 0.1.0 → 0.1.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 +11 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
1
|
+
import { readFile, stat } from 'fs/promises';
|
|
2
2
|
import { isAbsolute, join, dirname } from 'path';
|
|
3
3
|
import { pathToFileURL } from 'url';
|
|
4
4
|
import { parseWorkflowManifest, workflowFromManifest } from '@agentproto/workflow/manifest';
|
|
@@ -55,9 +55,18 @@ function isWorkflowHandle(v) {
|
|
|
55
55
|
}
|
|
56
56
|
async function importEntryHandle(workflowMdPath, entry) {
|
|
57
57
|
const abs = isAbsolute(entry) ? entry : join(dirname(workflowMdPath), entry);
|
|
58
|
+
let href = pathToFileURL(abs).href;
|
|
59
|
+
if (!process.env.VITEST) {
|
|
60
|
+
try {
|
|
61
|
+
const url = pathToFileURL(abs);
|
|
62
|
+
url.searchParams.set("v", String((await stat(abs)).mtimeMs));
|
|
63
|
+
href = url.href;
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
58
67
|
let mod;
|
|
59
68
|
try {
|
|
60
|
-
mod = await import(
|
|
69
|
+
mod = await import(href);
|
|
61
70
|
} catch (err) {
|
|
62
71
|
throw new WorkflowLoadError(
|
|
63
72
|
`cannot import entry '${entry}': ${err instanceof Error ? err.message : String(err)}`
|
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 } 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 let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(abs).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 { 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/workflow-loader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.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,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@agentproto/workflow": "0.1.
|
|
41
|
+
"@agentproto/workflow": "0.1.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^25.6.2",
|