ai-ctrl-workflow-logic 0.1.3 → 0.1.5
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 +9 -8
- package/src/filesystem.ts +125 -12
- package/src/index.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-ctrl-workflow-logic",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "WorkCtrlFlow-backed AI Ctrl Workflow adapter logic",
|
|
6
6
|
"type": "module",
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"./filesystem": "./src/filesystem.ts"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"ai-ctrl-workflow-contract": "0.1.
|
|
15
|
-
"ai-workflow-contract": "0.1.
|
|
16
|
-
"ai-workflow-logic": "0.1.
|
|
17
|
-
"
|
|
18
|
-
"instant-ctrl-flow-
|
|
19
|
-
"
|
|
20
|
-
"work-ctrl-flow-
|
|
14
|
+
"ai-ctrl-workflow-contract": "0.1.3",
|
|
15
|
+
"ai-workflow-contract": "0.1.6",
|
|
16
|
+
"ai-workflow-logic": "0.1.6",
|
|
17
|
+
"flow-step-space-logic": "0.1.1",
|
|
18
|
+
"instant-ctrl-flow-contract": "0.1.2",
|
|
19
|
+
"instant-ctrl-flow-logic": "0.1.4",
|
|
20
|
+
"work-ctrl-flow-contract": "0.1.3",
|
|
21
|
+
"work-ctrl-flow-logic": "0.1.3"
|
|
21
22
|
},
|
|
22
23
|
"files": [
|
|
23
24
|
"src"
|
package/src/filesystem.ts
CHANGED
|
@@ -7,8 +7,25 @@ import type {
|
|
|
7
7
|
FlowClosedValue,
|
|
8
8
|
AIWorkflowProfileDurableState,
|
|
9
9
|
} from 'ai-workflow-contract';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createFilesystemFlowRunCheckpointRuntime,
|
|
12
|
+
createFilesystemFlowRunCheckpointStore,
|
|
13
|
+
type FilesystemFlowRunCheckpointStore,
|
|
14
|
+
} from 'ai-workflow-logic/checkpoint-filesystem';
|
|
15
|
+
import {
|
|
16
|
+
createInitialRunStepExtensions,
|
|
17
|
+
normalizeFlowClosedValue,
|
|
18
|
+
type FlowRunProfileAuthorityPort,
|
|
19
|
+
type FlowRunProfileAuthorityRequest,
|
|
20
|
+
type FrozenFlowRunProfileAuthority,
|
|
21
|
+
} from 'ai-workflow-logic';
|
|
11
22
|
import { createFilesystemFlowCodeResolver, type FlowModuleImporter } from 'instant-ctrl-flow-logic/filesystem';
|
|
23
|
+
import type { FlowNodeSpec } from 'instant-ctrl-flow-contract';
|
|
24
|
+
import {
|
|
25
|
+
assembleDefinitionStepProfileDirectory,
|
|
26
|
+
createFilesystemDefinitionStepProfileRuntime,
|
|
27
|
+
} from 'flow-step-space-logic/filesystem';
|
|
28
|
+
import type { DefinitionStepAssemblyRuntime, DefinitionStepForest } from 'flow-step-space-logic';
|
|
12
29
|
import {
|
|
13
30
|
loadMaterializedFlowInstance,
|
|
14
31
|
materializeFlowInstance,
|
|
@@ -33,15 +50,20 @@ export interface MaterializedAICtrlWorkflowControllerOptions
|
|
|
33
50
|
readonly durableAI: AIWorkflowProfileDurableState;
|
|
34
51
|
readonly projectCheckpointOutput?: (snapshot: WorkCtrlFlowSnapshot) => FlowClosedValue;
|
|
35
52
|
readonly importModule?: FlowModuleImporter;
|
|
53
|
+
readonly extensionCodecs?: DefinitionStepAssemblyRuntime['extensionCodecs'];
|
|
36
54
|
readonly agentEffects?: AIAgentHostEffectPort;
|
|
37
55
|
readonly agentTaskProofs?: Readonly<Record<string, FrozenAIAgentTaskBinding>>;
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
export interface MaterializedAICtrlWorkflowController extends AICtrlWorkflowController {
|
|
41
59
|
readonly checkpointStore: FilesystemFlowRunCheckpointStore;
|
|
60
|
+
readonly profileAuthority: FlowRunProfileAuthorityPort;
|
|
61
|
+
readonly extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'];
|
|
42
62
|
readonly instanceId: string;
|
|
43
63
|
}
|
|
44
64
|
|
|
65
|
+
const emptyExtensionCodecs = Object.freeze({ resolve: () => undefined });
|
|
66
|
+
|
|
45
67
|
/** Frozen AICtrl definition plus canonical filesystem checkpoint owner. */
|
|
46
68
|
export function createMaterializedAICtrlWorkflowControllerFromDirectory(
|
|
47
69
|
options: MaterializedAICtrlWorkflowControllerOptions,
|
|
@@ -49,10 +71,11 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
|
|
|
49
71
|
if (options.agentEffects && (!options.agentTaskProofs || Object.keys(options.agentTaskProofs).length === 0)) {
|
|
50
72
|
throw new Error('AI_AGENT_TASK_PROOF_REQUIRED: materialized AI Ctrl Agent effects require run-freeze task proofs.');
|
|
51
73
|
}
|
|
74
|
+
const extensionCodecs = options.extensionCodecs ?? emptyExtensionCodecs;
|
|
52
75
|
const instancesDir = path.join(options.storeRoot, 'instances');
|
|
53
76
|
const instanceDir = path.join(instancesDir, options.instanceId);
|
|
54
77
|
const validateDefinition = (definitionDir: string) => {
|
|
55
|
-
loadAICtrlBinding(definitionDir);
|
|
78
|
+
loadAICtrlBinding(definitionDir, extensionCodecs);
|
|
56
79
|
};
|
|
57
80
|
const instance = fs.existsSync(instanceDir)
|
|
58
81
|
? loadMaterializedFlowInstance(instanceDir, options.instanceId, validateDefinition)
|
|
@@ -68,8 +91,25 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
|
|
|
68
91
|
if (options.bundleDir || options.source) throw new Error('bundleDir and source identity must be provided together.');
|
|
69
92
|
throw new Error('bundleDir and source identity are required for first admission.');
|
|
70
93
|
})();
|
|
71
|
-
const binding = loadAICtrlBinding(instance.definitionDir);
|
|
72
|
-
const
|
|
94
|
+
const binding = loadAICtrlBinding(instance.definitionDir, extensionCodecs);
|
|
95
|
+
const profileAuthority = createMaterializedAICtrlWorkflowProfileAuthority({
|
|
96
|
+
storeRoot: options.storeRoot,
|
|
97
|
+
extensionCodecs,
|
|
98
|
+
});
|
|
99
|
+
const frozenProfile = profileAuthority.load({
|
|
100
|
+
instanceId: options.instanceId,
|
|
101
|
+
definition: instance.descriptor.definition,
|
|
102
|
+
profileKind: 'AICtrlWorkflow',
|
|
103
|
+
});
|
|
104
|
+
const checkpointStore = createFilesystemFlowRunCheckpointStore(
|
|
105
|
+
createFilesystemFlowRunCheckpointRuntime({
|
|
106
|
+
storeRoot: options.storeRoot,
|
|
107
|
+
extensionCodecs,
|
|
108
|
+
profileAuthority,
|
|
109
|
+
}),
|
|
110
|
+
{},
|
|
111
|
+
{},
|
|
112
|
+
);
|
|
73
113
|
const store = createAICtrlWorkflowCheckpointStore(
|
|
74
114
|
{ checkpointStore },
|
|
75
115
|
{
|
|
@@ -77,6 +117,7 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
|
|
|
77
117
|
definition: instance.descriptor.definition,
|
|
78
118
|
config: options.checkpointConfig,
|
|
79
119
|
ai: options.durableAI,
|
|
120
|
+
initialStepExtensions: createInitialRunStepExtensions(frozenProfile),
|
|
80
121
|
...(options.projectCheckpointOutput ? { projectOutput: options.projectCheckpointOutput } : {}),
|
|
81
122
|
},
|
|
82
123
|
);
|
|
@@ -89,6 +130,7 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
|
|
|
89
130
|
durableAI: _durableAI,
|
|
90
131
|
projectCheckpointOutput: _projectCheckpointOutput,
|
|
91
132
|
importModule: _importModule,
|
|
133
|
+
extensionCodecs: _extensionCodecs,
|
|
92
134
|
agentEffects: _agentEffects,
|
|
93
135
|
agentTaskProofs: _agentTaskProofs,
|
|
94
136
|
...controllerOptions
|
|
@@ -124,19 +166,90 @@ export function createMaterializedAICtrlWorkflowControllerFromDirectory(
|
|
|
124
166
|
return controller.start(treeId, startOptions);
|
|
125
167
|
},
|
|
126
168
|
};
|
|
127
|
-
return Object.assign(materializedController, {
|
|
169
|
+
return Object.assign(materializedController, {
|
|
170
|
+
checkpointStore,
|
|
171
|
+
profileAuthority,
|
|
172
|
+
extensionCodecs,
|
|
173
|
+
instanceId: options.instanceId,
|
|
174
|
+
});
|
|
128
175
|
}
|
|
129
176
|
|
|
130
|
-
function loadAICtrlBinding(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
177
|
+
function loadAICtrlBinding(
|
|
178
|
+
definitionDir: string,
|
|
179
|
+
extensionCodecs: DefinitionStepAssemblyRuntime['extensionCodecs'],
|
|
180
|
+
) {
|
|
181
|
+
const assembled = assembleDefinitionStepProfileDirectory(
|
|
182
|
+
createFilesystemDefinitionStepProfileRuntime({ rootDir: definitionDir, extensionCodecs }),
|
|
183
|
+
{},
|
|
184
|
+
{ profileRoot: 'AICtrlWorkflow' },
|
|
136
185
|
);
|
|
186
|
+
if (assembled.diagnostics.length > 0) {
|
|
187
|
+
throw new Error(`AICtrlWorkflow StepSpace is invalid: ${assembled.diagnostics.map((item) => item.code).join(', ')}`);
|
|
188
|
+
}
|
|
189
|
+
const sources = Object.fromEntries((assembled.sources as readonly ({ readonly ref: string; readonly content: string } | { readonly name: string; readonly content: string })[])
|
|
190
|
+
.map((source) => ['name' in source ? source.name : source.ref, source.content]));
|
|
137
191
|
const loaded = loadAICtrlWorkflowSources(sources, { baseUri: definitionDir });
|
|
138
192
|
if (!loaded.binding || loaded.diagnostics.length > 0) {
|
|
139
193
|
throw new Error(`AICtrlWorkflow definition is invalid: ${loaded.diagnostics.map((item) => item.code).join(', ')}`);
|
|
140
194
|
}
|
|
141
|
-
return
|
|
195
|
+
return assembled.forest
|
|
196
|
+
? { ...loaded.binding, definition: { ...loaded.binding.definition, definitionStepForest: assembled.forest } }
|
|
197
|
+
: loaded.binding;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function profileAuthorityFromBinding(
|
|
201
|
+
binding: ReturnType<typeof loadAICtrlBinding>,
|
|
202
|
+
): FrozenFlowRunProfileAuthority {
|
|
203
|
+
const forest = binding.definition.definitionStepForest as DefinitionStepForest | undefined;
|
|
204
|
+
const definitionStepIds: string[] = [];
|
|
205
|
+
const visit = (node: FlowNodeSpec): void => {
|
|
206
|
+
if (node.id) definitionStepIds.push(node.id);
|
|
207
|
+
node.children.forEach(visit);
|
|
208
|
+
Object.values(node.sections).forEach(visit);
|
|
209
|
+
};
|
|
210
|
+
binding.definition.statements.forEach(visit);
|
|
211
|
+
return Object.freeze({
|
|
212
|
+
profileKind: 'AICtrlWorkflow' as const,
|
|
213
|
+
definitionStepIds: Object.freeze([...definitionStepIds]),
|
|
214
|
+
extensionsByStepId: Object.freeze(Object.fromEntries((forest?.stepOrder ?? []).flatMap((stepId) => {
|
|
215
|
+
const extensions = Object.fromEntries(Object.entries(forest!.steps[stepId].extensions).map(([kind, extension]) => [kind, Object.freeze({
|
|
216
|
+
...(extension.schemaRef === undefined ? {} : { schemaRef: extension.schemaRef }),
|
|
217
|
+
initialValue: extension.value as import('ai-workflow-contract').FlowClosedValue,
|
|
218
|
+
})]));
|
|
219
|
+
return Object.keys(extensions).length === 0 ? [] : [[stepId, Object.freeze(extensions)]];
|
|
220
|
+
}))),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function createMaterializedAICtrlWorkflowProfileAuthority(
|
|
225
|
+
runtime: Pick<MaterializedAICtrlWorkflowControllerOptions, 'storeRoot' | 'extensionCodecs'>,
|
|
226
|
+
): FlowRunProfileAuthorityPort {
|
|
227
|
+
const extensionCodecs = runtime.extensionCodecs ?? emptyExtensionCodecs;
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
load(request: FlowRunProfileAuthorityRequest) {
|
|
230
|
+
if (request.profileKind !== 'AICtrlWorkflow') {
|
|
231
|
+
throw new Error(`AI Ctrl profile authority cannot load ${request.profileKind}.`);
|
|
232
|
+
}
|
|
233
|
+
const instanceDir = path.join(runtime.storeRoot, 'instances', request.instanceId);
|
|
234
|
+
const instance = loadMaterializedFlowInstance(instanceDir, request.instanceId, (definitionDir) => {
|
|
235
|
+
loadAICtrlBinding(definitionDir, extensionCodecs);
|
|
236
|
+
});
|
|
237
|
+
if (canonicalClosed(normalizeFlowClosedValue(instance.descriptor.definition, 'profileAuthority.instanceDefinition'))
|
|
238
|
+
!== canonicalClosed(normalizeFlowClosedValue(request.definition, 'profileAuthority.requestDefinition'))) {
|
|
239
|
+
throw new Error('AI Ctrl profile authority definition identity differs from the checkpoint.');
|
|
240
|
+
}
|
|
241
|
+
return profileAuthorityFromBinding(loadAICtrlBinding(instance.definitionDir, extensionCodecs));
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const compareCodeUnits = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0;
|
|
247
|
+
|
|
248
|
+
function canonicalClosed(value: import('ai-workflow-contract').FlowClosedValue): string {
|
|
249
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
250
|
+
if (Array.isArray(value)) return `[${value.map(canonicalClosed).join(',')}]`;
|
|
251
|
+
const record = value as import('ai-workflow-contract').FlowClosedObject;
|
|
252
|
+
return `{${Object.keys(record).sort(compareCodeUnits).map((key) => (
|
|
253
|
+
`${JSON.stringify(key)}:${canonicalClosed(record[key])}`
|
|
254
|
+
)).join(',')}}`;
|
|
142
255
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
FlowDefinitionArtifactIdentity,
|
|
21
21
|
FlowRunCheckpointKey,
|
|
22
22
|
FlowRunCheckpointRuntime,
|
|
23
|
+
FlowRunStepExtensions,
|
|
23
24
|
} from 'ai-workflow-contract';
|
|
24
25
|
import {
|
|
25
26
|
commitFlowRunCheckpoint,
|
|
@@ -52,6 +53,8 @@ export interface AICtrlWorkflowCheckpointStoreOptions {
|
|
|
52
53
|
readonly definition: FlowDefinitionArtifactIdentity;
|
|
53
54
|
readonly config: FlowClosedValue;
|
|
54
55
|
readonly ai: AIWorkflowProfileDurableState;
|
|
56
|
+
/** Frozen definition-derived initial facts; later saves preserve the canonical owner value. */
|
|
57
|
+
readonly initialStepExtensions?: FlowRunStepExtensions;
|
|
55
58
|
readonly projectOutput?: (snapshot: WorkCtrlFlowSnapshot) => FlowClosedValue;
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -106,6 +109,11 @@ export function createAICtrlWorkflowCheckpointStore(
|
|
|
106
109
|
output: options.projectOutput?.(snapshot) ?? current?.output ?? null,
|
|
107
110
|
controllerSidecars: ctrlControllerSidecars(durableSnapshot) as unknown as FlowClosedObject,
|
|
108
111
|
nodeSidecars: ctrlNodeSidecars(durableSnapshot) as unknown as FlowClosedObject,
|
|
112
|
+
...(current?.stepExtensions !== undefined
|
|
113
|
+
? { stepExtensions: current.stepExtensions }
|
|
114
|
+
: options.initialStepExtensions === undefined
|
|
115
|
+
? {}
|
|
116
|
+
: { stepExtensions: options.initialStepExtensions }),
|
|
109
117
|
profile: {
|
|
110
118
|
kind: 'AICtrlWorkflow',
|
|
111
119
|
snapshot: durableSnapshot as unknown as FlowClosedObject,
|