@agentproto/workflow-loader 0.1.0-alpha.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeremy André and agentproto contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ import { WorkflowHandle } from '@agentproto/workflow';
2
+ import { WorkflowManifest } from '@agentproto/workflow/manifest';
3
+
4
+ /**
5
+ * loadWorkflowHandle — read a WORKFLOW.md off disk into a WorkflowHandle.
6
+ *
7
+ * The pure packages stop at the page boundary: `@agentproto/workflow` parses a
8
+ * manifest *string*, `@agentproto/workflow-runtime` compiles an in-memory
9
+ * handle. Neither touches the filesystem. This is the host-side seam that does:
10
+ *
11
+ * read file → parse manifest → (if `entry`) import the module + reconcile
12
+ *
13
+ * With no `entry`, the manifest IS the workflow (purely-declarative path). With
14
+ * an `entry`, its default-exported handle is the source of truth for runtime
15
+ * step logic and the manifest is reconciled against it — the two MUST agree.
16
+ *
17
+ * The entry import mirrors the CLI's adapter loader: resolve relative to the
18
+ * manifest's directory, import via a `file://` URL so an absolute path works
19
+ * without an `exports` declaration.
20
+ */
21
+
22
+ declare class WorkflowLoadError extends Error {
23
+ constructor(message: string);
24
+ }
25
+ /**
26
+ * Load and validate a WORKFLOW.md at `workflowMdPath` into a WorkflowHandle.
27
+ * Throws {@link WorkflowLoadError} on a missing/unreadable file or a bad entry
28
+ * module, the parser's error on invalid frontmatter, and
29
+ * {@link WorkflowReconcileError} when an entry's graph disagrees with the
30
+ * manifest. The returned handle still needs `compileWorkflow` + a tool registry
31
+ * to run — this stops at the validated, in-memory handle.
32
+ */
33
+ declare function loadWorkflowHandle(workflowMdPath: string): Promise<WorkflowHandle>;
34
+
35
+ /**
36
+ * Reconcile a WORKFLOW.md manifest against the handle its `entry` module
37
+ * exports. An entry-backed workflow declares its step graph twice — once as
38
+ * governance-facing manifest data, once as the runnable handle — so the two
39
+ * MUST agree, or the manifest is lying about what actually runs. This checks
40
+ * the workflow id and the top-level step (id, kind) sequence; nested step
41
+ * lists (map / loop / parallel bodies) are the entry's concern.
42
+ */
43
+
44
+ declare class WorkflowReconcileError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ declare function reconcileEntry(manifest: WorkflowManifest, handle: WorkflowHandle): void;
48
+
49
+ export { WorkflowLoadError, WorkflowReconcileError, loadWorkflowHandle, reconcileEntry };
package/dist/index.mjs ADDED
@@ -0,0 +1,93 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { isAbsolute, join, dirname } from 'path';
3
+ import { pathToFileURL } from 'url';
4
+ import { parseWorkflowManifest, workflowFromManifest } from '@agentproto/workflow/manifest';
5
+
6
+ /**
7
+ * @agentproto/workflow-loader v0.1.0-alpha
8
+ * Disk loader + entry reconciliation for AIP-15 WORKFLOW.md.
9
+ */
10
+
11
+
12
+ // src/reconcile.ts
13
+ var WorkflowReconcileError = class extends Error {
14
+ constructor(message) {
15
+ super(`reconcileEntry: ${message}`);
16
+ this.name = "WorkflowReconcileError";
17
+ }
18
+ };
19
+ var idKind = (steps) => steps.map((s) => ({ id: s.id, kind: s.kind }));
20
+ var summary = (steps) => steps.map((s) => `${s.id}:${s.kind}`).join(" \u2192 ") || "(none)";
21
+ function reconcileEntry(manifest, handle) {
22
+ const fm = manifest.frontmatter;
23
+ if (fm.id !== handle.id) {
24
+ throw new WorkflowReconcileError(
25
+ `manifest id '${fm.id}' \u2260 entry id '${handle.id}'.`
26
+ );
27
+ }
28
+ const declared = idKind(fm.steps);
29
+ const actual = idKind(handle.steps);
30
+ if (declared.length !== actual.length) {
31
+ throw new WorkflowReconcileError(
32
+ `step count differs \u2014 manifest declares ${declared.length} [${summary(declared)}], entry has ${actual.length} [${summary(actual)}].`
33
+ );
34
+ }
35
+ for (let i = 0; i < declared.length; i++) {
36
+ const d = declared[i];
37
+ const a = actual[i];
38
+ if (d.id !== a.id || d.kind !== a.kind) {
39
+ throw new WorkflowReconcileError(
40
+ `step ${i} differs \u2014 manifest '${d.id}:${d.kind}' \u2260 entry '${a.id}:${a.kind}'. manifest [${summary(declared)}] vs entry [${summary(actual)}].`
41
+ );
42
+ }
43
+ }
44
+ }
45
+
46
+ // src/load-workflow.ts
47
+ var WorkflowLoadError = class extends Error {
48
+ constructor(message) {
49
+ super(`loadWorkflow: ${message}`);
50
+ this.name = "WorkflowLoadError";
51
+ }
52
+ };
53
+ function isWorkflowHandle(v) {
54
+ return typeof v === "object" && v !== null && typeof v.id === "string" && Array.isArray(v.steps);
55
+ }
56
+ async function importEntryHandle(workflowMdPath, entry) {
57
+ const abs = isAbsolute(entry) ? entry : join(dirname(workflowMdPath), entry);
58
+ let mod;
59
+ try {
60
+ mod = await import(pathToFileURL(abs).href);
61
+ } catch (err) {
62
+ throw new WorkflowLoadError(
63
+ `cannot import entry '${entry}': ${err instanceof Error ? err.message : String(err)}`
64
+ );
65
+ }
66
+ const exported = mod.default;
67
+ if (!isWorkflowHandle(exported)) {
68
+ throw new WorkflowLoadError(
69
+ `entry '${entry}' must default-export a WorkflowHandle (e.g. from defineWorkflow) carrying an 'id' + 'steps'; got ${exported === void 0 ? "no default export" : typeof exported}.`
70
+ );
71
+ }
72
+ return exported;
73
+ }
74
+ async function loadWorkflowHandle(workflowMdPath) {
75
+ let source;
76
+ try {
77
+ source = await readFile(workflowMdPath, "utf8");
78
+ } catch (err) {
79
+ throw new WorkflowLoadError(
80
+ `cannot read '${workflowMdPath}': ${err instanceof Error ? err.message : String(err)}`
81
+ );
82
+ }
83
+ const manifest = parseWorkflowManifest(source);
84
+ const entry = manifest.frontmatter.entry;
85
+ if (!entry) return workflowFromManifest(manifest);
86
+ const handle = await importEntryHandle(workflowMdPath, entry);
87
+ reconcileEntry(manifest, handle);
88
+ return handle;
89
+ }
90
+
91
+ export { WorkflowLoadError, WorkflowReconcileError, loadWorkflowHandle, reconcileEntry };
92
+ //# sourceMappingURL=index.mjs.map
93
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@agentproto/workflow-loader",
3
+ "version": "0.1.0-alpha.0",
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
+ "keywords": [
6
+ "agentproto",
7
+ "aip-15",
8
+ "workflow",
9
+ "loader",
10
+ "manifest",
11
+ "open-standard",
12
+ "agentic"
13
+ ],
14
+ "homepage": "https://agentproto.sh/docs/aip-15",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/agentproto/ts"
18
+ },
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "main": "dist/index.mjs",
22
+ "module": "dist/index.mjs",
23
+ "types": "dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.mjs",
28
+ "default": "./dist/index.mjs"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "@agentproto/workflow": "0.1.0-alpha.1"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^25.6.2",
45
+ "tsup": "^8.5.1",
46
+ "typescript": "^5.9.3",
47
+ "vitest": "^3.2.4",
48
+ "zod": "^4.4.3",
49
+ "@agentproto/tooling": "0.1.0-alpha.0"
50
+ },
51
+ "scripts": {
52
+ "dev": "tsup --watch",
53
+ "build": "tsup",
54
+ "clean": "rm -rf dist",
55
+ "check-types": "tsc --noEmit",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest"
58
+ }
59
+ }