instant-ctrl-flow-logic 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instant-ctrl-flow-logic",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "InstantCtrlFlow 引擎:xnl bundle 加载 → 行为树编译 → 无状态单次执行",
6
6
  "type": "module",
@@ -11,15 +11,14 @@
11
11
  "./filesystem": "./src/filesystem.ts"
12
12
  },
13
13
  "dependencies": {
14
- "xnl-core": "^0.1.8",
15
14
  "depa-behavior-tree": "0.1.0",
16
- "instant-ctrl-flow-contract": "0.1.0"
15
+ "instant-ctrl-flow-contract": "0.1.0",
16
+ "xnl-core": "^0.1.8"
17
17
  },
18
18
  "files": [
19
19
  "src"
20
20
  ],
21
21
  "publishConfig": {
22
- "access": "public",
23
- "registry": "https://registry.npmjs.com"
22
+ "access": "public"
24
23
  }
25
- }
24
+ }
package/src/handlers.ts CHANGED
@@ -93,6 +93,7 @@ export interface FlowDecisionContext {
93
93
  export interface FlowDecisionHooks {
94
94
  begin(ifPath: string): FlowDecisionContext;
95
95
  record(decision: FlowBranchDecision): Promise<void> | void;
96
+ runtimeForNode?(nodeId: string, runtime: InstantCtrlFlowRuntime): InstantCtrlFlowRuntime;
96
97
  }
97
98
 
98
99
  class FlowFunctionLoader {
@@ -100,8 +101,13 @@ class FlowFunctionLoader {
100
101
  constructor(
101
102
  private spec: FlowBundleSpec,
102
103
  private resolveCode: FlowCodeResolver,
104
+ private runtimeHooks?: FlowDecisionHooks,
103
105
  ) {}
104
106
 
107
+ runtimeForNode(nodeId: string, runtime: InstantCtrlFlowRuntime): InstantCtrlFlowRuntime {
108
+ return this.runtimeHooks?.runtimeForNode?.(nodeId, runtime) ?? runtime;
109
+ }
110
+
105
111
  load(src: string, nodeId: string): Promise<FlowCodeFunction> {
106
112
  let loaded = this.cache.get(src);
107
113
  if (!loaded) {
@@ -128,7 +134,8 @@ function configOf(ctx: Ctx): NodeConfig {
128
134
  async function callCode(ctx: Ctx, loader: FlowFunctionLoader, src: string): Promise<unknown> {
129
135
  const state = flowStateOf(ctx);
130
136
  const fn = await loader.load(src, ctx.node.Key);
131
- return fn((ctx.runtime ?? {}) as InstantCtrlFlowRuntime, state.current, configOf(ctx).config ?? {});
137
+ const runtime = (ctx.runtime ?? {}) as InstantCtrlFlowRuntime;
138
+ return fn(loader.runtimeForNode(ctx.node.Key, runtime), state.current, configOf(ctx).config ?? {});
132
139
  }
133
140
 
134
141
  function fail(state: FlowExecutionState, error: unknown): BehaviorResult {
@@ -277,7 +284,7 @@ class StrategyStartHandler implements ActionHandler<CtrlFlowNodeType, INodeConfi
277
284
  if (state.returned) return BehaviorResult.Success;
278
285
  const key = String(configOf(ctx).fallbackKey);
279
286
  state.current = state.fallbackInputs[key];
280
- state.fault = undefined;
287
+ delete state.fault;
281
288
  return BehaviorResult.Success;
282
289
  }
283
290
  }
@@ -288,7 +295,7 @@ class FallbackStartHandler implements ActionHandler<CtrlFlowNodeType, INodeConfi
288
295
  if (state.returned) return BehaviorResult.Success;
289
296
  const key = String(configOf(ctx).fallbackKey);
290
297
  state.fallbackInputs[key] = state.current;
291
- state.fault = undefined;
298
+ delete state.fault;
292
299
  return BehaviorResult.Success;
293
300
  }
294
301
  }
@@ -440,7 +447,7 @@ export function createFlowHandlers(
440
447
  executeBody?: FlowBodyExecutor,
441
448
  executeTarget?: FlowTargetExecutor,
442
449
  ): Record<string, ActionHandler<CtrlFlowNodeType, INodeConfig>> {
443
- const loader = new FlowFunctionLoader(spec, resolveCode);
450
+ const loader = new FlowFunctionLoader(spec, resolveCode, decisionHooks);
444
451
  const activeDecisions = new Map<string, FlowDecisionContext>();
445
452
  return {
446
453
  'flow.Run': new RunHandler(loader),
package/src/loader.ts CHANGED
@@ -107,9 +107,37 @@ export interface LoadResult {
107
107
  diagnostics: FlowDiagnostic[];
108
108
  }
109
109
 
110
+ export interface LoadCtrlFlowProfileSourcesOptions extends LoadFlowSourcesOptions {
111
+ profileRoot: string;
112
+ substrateForm: FlowForm;
113
+ }
114
+
110
115
  export function loadFlowBundleFromSources(
111
116
  sources: FlowSourceCollection,
112
117
  options: LoadFlowSourcesOptions = {},
118
+ ): LoadResult {
119
+ return loadCtrlFlowSources(sources, options);
120
+ }
121
+
122
+ export function loadCtrlFlowProfileFromSources(
123
+ sources: FlowSourceCollection,
124
+ options: LoadCtrlFlowProfileSourcesOptions,
125
+ ): LoadResult {
126
+ if (!options.profileRoot.trim()) {
127
+ return {
128
+ diagnostics: [{ code: 'invalid-profile-root', message: 'profile root must be a non-empty XNL tag' }],
129
+ };
130
+ }
131
+ return loadCtrlFlowSources(sources, options, {
132
+ rootTag: options.profileRoot,
133
+ substrateForm: options.substrateForm,
134
+ });
135
+ }
136
+
137
+ function loadCtrlFlowSources(
138
+ sources: FlowSourceCollection,
139
+ options: LoadFlowSourcesOptions,
140
+ profile?: { rootTag: string; substrateForm: FlowForm },
113
141
  ): LoadResult {
114
142
  const diagnostics: FlowDiagnostic[] = [];
115
143
  const note = (code: string, message: string) => diagnostics.push({ code, message });
@@ -168,14 +196,25 @@ export function loadFlowBundleFromSources(
168
196
 
169
197
  for (const tag of Object.keys(fileRoots)) noteLegacyTag(tag);
170
198
 
171
- const form = FORMS.find((candidate) => fileRoots[candidate]);
199
+ const form = profile?.substrateForm ?? FORMS.find((candidate) => fileRoots[candidate]);
172
200
  if (!form) {
173
201
  if (noted.size) return { diagnostics };
174
202
  note('no-container', `no container root found in ${options.baseUri ?? 'named source collection'}`);
175
203
  return { diagnostics };
176
204
  }
177
- const container = fileRoots[form].el;
178
- const containerFile = fileRoots[form].file;
205
+ const rootTag = profile?.rootTag ?? form;
206
+ const rootEntry = fileRoots[rootTag];
207
+ if (!rootEntry) {
208
+ note(
209
+ profile ? 'profile-root-missing' : 'no-container',
210
+ profile
211
+ ? `expected profile root <${rootTag}> in ${options.baseUri ?? 'named source collection'}`
212
+ : `no container root found in ${options.baseUri ?? 'named source collection'}`,
213
+ );
214
+ return { diagnostics };
215
+ }
216
+ const container = rootEntry.el;
217
+ const containerFile = rootEntry.file;
179
218
 
180
219
  // Facet discovery is physical; toNodeSpec remains a pure XNL-to-contract projection.
181
220
  const facets: Partial<Record<FacetName, XnlEl>> = {};