ai-workflow-logic 0.2.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-workflow-logic",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "AI workflow domain projections and pure runtime helpers with an explicit Halfcode filesystem adapter",
6
6
  "type": "module",
@@ -10,19 +10,21 @@
10
10
  "./browser": "./src/browser.ts",
11
11
  "./checkpoint-filesystem": "./src/checkpoint-filesystem.ts",
12
12
  "./filesystem": "./src/filesystem.ts",
13
- "./run-freeze": "./src/run-freeze.ts"
13
+ "./run-freeze": "./src/run-freeze.ts",
14
+ "./agent-code-execution": "./src/agent-code-execution.ts",
15
+ "./definitions": "./src/definitions.ts"
14
16
  },
15
17
  "dependencies": {
16
- "ai-workflow-contract": "0.2.0",
18
+ "ai-workflow-contract": "0.2.2",
17
19
  "ajv": "8.20.0",
18
20
  "flow-step-space-contract": "0.1.1",
19
- "flow-step-space-logic": "0.1.1",
20
- "halfcode-compiler.xnl": "0.3.0",
21
+ "flow-step-space-logic": "0.1.2",
22
+ "halfcode-compiler.xnl": "0.3.2",
21
23
  "holarchy-core-contract": "0.2.0",
22
- "task-manager-contract": "0.1.3",
23
- "task-manager-logic": "0.1.6",
24
- "work-ctrl-flow-contract": "0.1.3",
25
- "work-ctrl-flow-logic": "0.1.5"
24
+ "task-manager-contract": "0.1.4",
25
+ "task-manager-logic": "0.1.7",
26
+ "work-ctrl-flow-contract": "0.1.4",
27
+ "work-ctrl-flow-logic": "0.1.6"
26
28
  },
27
29
  "files": [
28
30
  "src"
@@ -0,0 +1,14 @@
1
+ import type { AIAgentCodeExecutionIdentity, AIAgentCodeResourceProjection, CompiledAIAgentCodeExecution } from 'ai-workflow-contract';
2
+
3
+ interface CodeExecutionAuthority {
4
+ readonly projection: AIAgentCodeResourceProjection;
5
+ readonly identity: AIAgentCodeExecutionIdentity;
6
+ }
7
+ const authorities = new WeakMap<object, CodeExecutionAuthority>();
8
+ export function markAIAgentCodeExecution(value: CompiledAIAgentCodeExecution, authority: CodeExecutionAuthority): CompiledAIAgentCodeExecution {
9
+ authorities.set(value, Object.freeze(authority));
10
+ return value;
11
+ }
12
+ export function authorityForAIAgentCodeExecution(value: CompiledAIAgentCodeExecution): CodeExecutionAuthority | undefined {
13
+ return authorities.get(value);
14
+ }
@@ -0,0 +1,98 @@
1
+ import type { AIAgentCodeResourceProjection, CompiledAIAgentCodeExecution, FlowClosedObject, FrozenAIAgentCodeArtifact } from 'ai-workflow-contract';
2
+ import { captureCodeClosure, type CodeBinding, type CodeExecutionEnvironment } from 'halfcode-compiler.xnl/application-assembly';
3
+ import { bindCodeClosure, type CodeClosureExecutionRuntime } from 'halfcode-compiler.xnl/authoring-runtime';
4
+ import { digestCanonical, type ResourcePackageReadPort, type ResolvedResourceRecord, type Sha256Digest } from 'halfcode-compiler.xnl/resource-core';
5
+ import { markAIAgentCodeExecution } from './agent-code-brand.js';
6
+ import { isAuthenticAIAgentCodeProjection } from './agent-code-projection.js';
7
+ import { normalizeFlowClosedObject } from './run-checkpoint.js';
8
+ import { resolutionForAIAgentCodeProjection } from './agent-execution-resources.js';
9
+
10
+ export interface LegacyAIAgentCodeBinding {
11
+ readonly binding: CodeBinding;
12
+ readonly config: FlowClosedObject;
13
+ readonly assets?: Readonly<Record<string, 'text' | 'json'>>;
14
+ }
15
+ export interface CompileAIAgentCodeRuntime extends CodeClosureExecutionRuntime {
16
+ readonly source: ResourcePackageReadPort;
17
+ readonly sourceRoot: string;
18
+ readonly entryPath: string;
19
+ readonly environment: CodeExecutionEnvironment;
20
+ /** Explicit host compatibility adapter, never an implementation-name switch here. */
21
+ readonly resolveLegacy?: (projection: Extract<AIAgentCodeResourceProjection, { mode: 'legacy' }>) => LegacyAIAgentCodeBinding;
22
+ }
23
+
24
+ export async function compileAgentContextPipeline(runtime: CompileAIAgentCodeRuntime, projection: AIAgentCodeResourceProjection): Promise<CompiledAIAgentCodeExecution> {
25
+ if (projection.kind !== 'AgentContextPipeline') fail('Expected AgentContextPipeline');
26
+ return compile(runtime, projection);
27
+ }
28
+ export async function compileAgentMessageSource(runtime: CompileAIAgentCodeRuntime, projection: AIAgentCodeResourceProjection): Promise<CompiledAIAgentCodeExecution> {
29
+ if (projection.kind !== 'AgentMessageSource') fail('Expected AgentMessageSource');
30
+ return compile(runtime, projection);
31
+ }
32
+ async function compile(runtime: CompileAIAgentCodeRuntime, projection: AIAgentCodeResourceProjection): Promise<CompiledAIAgentCodeExecution> {
33
+ assertProjection(projection);
34
+ const resourceResolution = resolutionForAIAgentCodeProjection(projection);
35
+ const recipe = projection.mode === 'native' ? projection : runtime.resolveLegacy?.(projection);
36
+ if (!recipe) fail('Legacy recipe requires an explicit builtin binding resolver');
37
+ const config = normalizeFlowClosedObject(recipe.config);
38
+ const closure = await captureCodeClosure({ source: runtime.source }, {
39
+ binding: recipe.binding,
40
+ sourceRoot: runtime.sourceRoot,
41
+ entryPath: runtime.entryPath,
42
+ assets: recipe.assets,
43
+ environment: runtime.environment,
44
+ });
45
+ const artifact: FrozenAIAgentCodeArtifact = Object.freeze({
46
+ schemaVersion: 'ai-workflow.agent-code-artifact/v1',
47
+ resourceId: projection.resource.resourceId,
48
+ resourceDigest: codeResourceDigest(projection),
49
+ ...(resourceResolution ? { resourceResolution } : {}),
50
+ config,
51
+ closure,
52
+ });
53
+ return restoreAIAgentCodeExecution(runtime, projection, artifact, digestCanonical(artifact));
54
+ }
55
+
56
+ /** Rebuilds an execution handle against an authentic frozen registry projection. */
57
+ export function restoreAIAgentCodeExecution(
58
+ runtime: CodeClosureExecutionRuntime,
59
+ projection: AIAgentCodeResourceProjection,
60
+ input: FrozenAIAgentCodeArtifact,
61
+ expectedDigest: Sha256Digest,
62
+ ): CompiledAIAgentCodeExecution {
63
+ assertProjection(projection);
64
+ const closed = normalizeFlowClosedObject(input, 'agent-code-artifact');
65
+ if (!/^sha256:[a-f0-9]{64}$/.test(expectedDigest) || digestCanonical(closed) !== expectedDigest) fail('Artifact differs from trusted expected digest');
66
+ if (Object.keys(closed).some(key => !['schemaVersion', 'resourceId', 'resourceDigest', 'resourceResolution', 'config', 'closure'].includes(key))) fail('Unsupported artifact field');
67
+ if (closed.schemaVersion !== 'ai-workflow.agent-code-artifact/v1'
68
+ || closed.resourceId !== projection.resource.resourceId
69
+ || closed.resourceDigest !== codeResourceDigest(projection)) fail('Artifact does not belong to this exact resource recipe');
70
+ if (digestCanonical(closed.resourceResolution ?? null) !== digestCanonical(resolutionForAIAgentCodeProjection(projection) ?? null)) fail('Artifact reader resolution differs from the exact execution observation');
71
+ // Closed-data validation above detaches and freezes all arrays/maps before narrowing.
72
+ const artifact = closed as unknown as FrozenAIAgentCodeArtifact;
73
+ const config = normalizeFlowClosedObject(artifact.config);
74
+ if (projection.mode === 'native'
75
+ && (digestCanonical(artifact.closure.binding) !== digestCanonical(projection.binding)
76
+ || digestCanonical(config) !== digestCanonical(projection.config))) fail('Artifact binding/config differs from native resource');
77
+ const execute = bindCodeClosure(runtime, artifact.closure, artifact.closure.digest);
78
+ const handle = Object.freeze({ artifact, artifactDigest: expectedDigest, execute: (runtimeInput: unknown, inputValue: unknown) => execute(runtimeInput, inputValue, config) });
79
+ return markAIAgentCodeExecution(handle, {
80
+ projection,
81
+ identity: Object.freeze({ resourceId: artifact.resourceId, resourceDigest: artifact.resourceDigest, artifactDigest: expectedDigest, closureDigest: artifact.closure.digest }),
82
+ });
83
+ }
84
+
85
+ function assertProjection(projection: AIAgentCodeResourceProjection): void {
86
+ if (!isAuthenticAIAgentCodeProjection(projection)) fail('An authentic code resource projection is required');
87
+ }
88
+ function codeResourceDigest(projection: AIAgentCodeResourceProjection): Sha256Digest {
89
+ const resource = projection.resource;
90
+ const resolved = resource as Partial<ResolvedResourceRecord>;
91
+ const resourceResolution = resolutionForAIAgentCodeProjection(projection);
92
+ return digestCanonical({
93
+ resourceId: resource.resourceId, kind: resource.kind, metadata: resource.metadata, node: resource.node,
94
+ ...(resourceResolution ? { resourceResolution } : {}),
95
+ ...(resolved.stage === 'resolved' ? { readerId: resolved.readerId, readerSpecVersion: resolved.readerSpecVersion, effectiveContentDigest: resolved.effectiveContentDigest } : {}),
96
+ });
97
+ }
98
+ function fail(message: string): never { throw new Error(`AI_AGENT_CODE_EXECUTION_INVALID: ${message}`); }
@@ -0,0 +1,98 @@
1
+ import type { AIAgentCodeResourceKind, AIAgentCodeResourceProjection, AIWorkflowTypedResourceRef } from 'ai-workflow-contract';
2
+ import type { ResourceDependencyEdge, ResourceNode, ResourceRecord, ResourceValue } from 'halfcode-compiler.xnl/resource-core';
3
+ import { normalizeFlowClosedObject } from './run-checkpoint.js';
4
+
5
+ const authenticCodeProjections = new WeakSet<object>();
6
+ export function isAuthenticAIAgentCodeProjection(value: AIAgentCodeResourceProjection): boolean {
7
+ return authenticCodeProjections.has(value);
8
+ }
9
+
10
+ /** Pure projection; compiler, filesystem and runtime binding stay in the server entry. */
11
+ export function projectAIAgentCodeResources(
12
+ resources: ReadonlyMap<string, ResourceRecord>,
13
+ edges: ResourceDependencyEdge[],
14
+ ): readonly AIAgentCodeResourceProjection[] {
15
+ return Object.freeze([...resources.values()]
16
+ .filter((resource) => resource.kind === 'AgentContextPipeline' || resource.kind === 'AgentMessageSource')
17
+ .sort((a, b) => a.resourceId < b.resourceId ? -1 : a.resourceId > b.resourceId ? 1 : 0)
18
+ .map((resource) => {
19
+ const kind = resource.kind as AIAgentCodeResourceKind;
20
+ const node = resource.node;
21
+ allowed(Object.keys(node.subdomains), ['Content', 'CodeBinding', 'Config', 'Assets', 'DependencyRefs']);
22
+ const version = resource.metadata.specVersion;
23
+ if (version !== 1 && version !== 2) fail(`Unsupported code spec version: ${version}`);
24
+ const contentNode = node.subdomains.Content;
25
+ const bindingNode = node.subdomains.CodeBinding;
26
+ if (contentNode && !bindingNode) {
27
+ if (Object.keys(node.subdomains).some(key => key !== 'Content')) fail('Legacy Content cannot contain native config/dependencies');
28
+ const content = contentNode.text;
29
+ if (contentNode.tag !== 'Content' || typeof content !== 'string' || content.length === 0) fail('Legacy Content must contain non-empty text');
30
+ return mark(Object.freeze({ kind, resource, mode: 'legacy', content, config: Object.freeze({}), assets: Object.freeze({}), dependencies: Object.freeze([]) }));
31
+ }
32
+ if (version !== 2 || !bindingNode || contentNode) fail('Native code requires spec 2 and exactly one CodeBinding');
33
+ leaf(bindingNode, 'CodeBinding', ['packageName', 'module', 'exportName']);
34
+ const packageName = text(bindingNode.properties.packageName, 'packageName');
35
+ const module = text(bindingNode.properties.module, 'module');
36
+ const exportName = text(bindingNode.properties.exportName, 'exportName');
37
+ if (!module.startsWith('./')) fail('CodeBinding.module must be package-relative');
38
+ safePath(module.slice(2));
39
+ if (packageName.startsWith('.') || packageName.startsWith('/') || /[\\\s]/.test(packageName)) fail('Invalid CodeBinding.packageName');
40
+ const binding = Object.freeze({ packageName, module, exportName, moduleSpecifier: `${packageName}/${module.slice(2)}` });
41
+ const configNode = node.subdomains.Config;
42
+ if (configNode) leaf(configNode, 'Config', ['value']);
43
+ const config = normalizeFlowClosedObject(configNode?.properties.value ?? {});
44
+ const assets: Record<string, 'text' | 'json'> = Object.create(null);
45
+ for (const value of list(node.subdomains.Assets, 'Assets')) {
46
+ leaf(value, 'Asset', ['path', 'format']);
47
+ const path = safePath(text(value.properties.path, 'Asset.path'));
48
+ const format = value.properties.format;
49
+ if ((format !== 'text' && format !== 'json') || Object.hasOwn(assets, path)) fail('Invalid or duplicate Asset');
50
+ assets[path] = format;
51
+ }
52
+ const dependencies: AIWorkflowTypedResourceRef[] = [];
53
+ const ids = new Set<string>();
54
+ const targets = new Set<string>();
55
+ for (const value of list(node.subdomains.DependencyRefs, 'DependencyRefs')) {
56
+ leaf(value, 'DependencyRef', ['kind', 'ref']);
57
+ const id = text(value.resourceId, 'DependencyRef.id');
58
+ const ref = text(value.properties.ref, 'DependencyRef.ref');
59
+ const targetKind = text(value.properties.kind, 'DependencyRef.kind');
60
+ if (!ref.startsWith('resource://') || !ref.slice(11) || /\s/.test(ref)) fail('Invalid dependency resource ref');
61
+ const target = resources.get(ref.slice(11));
62
+ if (!target || target.kind !== targetKind || ids.has(id) || targets.has(ref)) fail('Missing, mistyped or duplicate dependency');
63
+ ids.add(id); targets.add(ref);
64
+ dependencies.push(Object.freeze({ kind: targetKind, ref: ref as `resource://${string}`, resource: target }));
65
+ edges.push(Object.freeze({ fromResourceId: resource.resourceId, toResourceId: target.resourceId, relation: 'agent-code-dependency', declaredBy: resource.resourceId }));
66
+ }
67
+ return mark(Object.freeze({ kind, resource, mode: 'native', binding, config, assets: Object.freeze(assets), dependencies: Object.freeze(dependencies) }));
68
+ }));
69
+ }
70
+
71
+ function mark<T extends AIAgentCodeResourceProjection>(value: T): T { authenticCodeProjections.add(value); return value; }
72
+ function text(value: unknown, label: string): string {
73
+ if (typeof value !== 'string' || !value || value.trim() !== value) fail(`Expected canonical ${label}`);
74
+ return value;
75
+ }
76
+ function safePath(value: string): string {
77
+ if (value.startsWith('/') || /[\\\0?#]/.test(value) || value.split('/').some(part => !part || part === '.' || part === '..')) fail('Invalid code-owned path');
78
+ return value;
79
+ }
80
+ function allowed(actual: readonly string[], expected: readonly string[]): void {
81
+ if (actual.some(key => !expected.includes(key))) fail('Unsupported code recipe field');
82
+ }
83
+ function leaf(value: ResourceNode, tag: string, properties: readonly string[]): void {
84
+ if (value.tag !== tag || value.body.length || Object.keys(value.subdomains).length || value.text) fail(`Invalid ${tag} node`);
85
+ allowed(Object.keys(value.properties), properties);
86
+ }
87
+ function list(value: ResourceNode | undefined, tag: string): ResourceNode[] {
88
+ if (!value) return [];
89
+ if (value.tag !== tag || Object.keys(value.properties).length || Object.keys(value.subdomains).length || value.text) fail(`Invalid ${tag} list`);
90
+ return value.body.map(item => {
91
+ if (!isNode(item)) fail(`Expected node in ${tag}`);
92
+ return item;
93
+ });
94
+ }
95
+ function isNode(value: ResourceValue): value is ResourceNode {
96
+ return !!value && typeof value === 'object' && !Array.isArray(value) && 'tag' in value && 'body' in value;
97
+ }
98
+ function fail(message: string): never { throw new Error(`AI_AGENT_CODE_INVALID: ${message}`); }