@o-lang/olang 1.0.26 → 1.0.27
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/cli.js +118 -132
- package/package.json +1 -1
- package/src/runtime.js +117 -15
package/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ const fs = require('fs');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Enforce .ol extension ONLY
|
|
9
|
+
* Enforce .ol extension ONLY (CLI only)
|
|
10
10
|
*/
|
|
11
11
|
function ensureOlExtension(filename) {
|
|
12
12
|
if (!filename.endsWith('.ol')) {
|
|
@@ -22,24 +22,29 @@ function ensureOlExtension(filename) {
|
|
|
22
22
|
*/
|
|
23
23
|
async function defaultMockResolver(action, context) {
|
|
24
24
|
if (!action || typeof action !== 'string') return `[Unhandled: ${String(action)}]`;
|
|
25
|
+
|
|
25
26
|
if (action.startsWith('Search for ')) {
|
|
26
27
|
return {
|
|
27
28
|
title: "HR Policy 2025",
|
|
28
|
-
text: "Employees are entitled to 20 days of paid leave per year.
|
|
29
|
+
text: "Employees are entitled to 20 days of paid leave per year.",
|
|
29
30
|
url: "mock://hr-policy"
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
|
|
32
34
|
if (action.startsWith('Ask ')) {
|
|
33
|
-
return "
|
|
35
|
+
return "✅ [Mock] Summarized for demonstration.";
|
|
34
36
|
}
|
|
37
|
+
|
|
35
38
|
if (action.startsWith('Notify ')) {
|
|
36
39
|
const recipient = action.match(/Notify (\S+)/)?.[1] || 'user@example.com';
|
|
37
|
-
return
|
|
40
|
+
return `📬 Notification sent to ${recipient}`;
|
|
38
41
|
}
|
|
42
|
+
|
|
39
43
|
if (action.startsWith('Debrief ') || action.startsWith('Evolve ')) {
|
|
40
44
|
console.log(`[O-Lang] ${action}`);
|
|
41
45
|
return 'Acknowledged';
|
|
42
46
|
}
|
|
47
|
+
|
|
43
48
|
return `[Unhandled: ${action}]`;
|
|
44
49
|
}
|
|
45
50
|
defaultMockResolver.resolverName = 'defaultMockResolver';
|
|
@@ -48,45 +53,43 @@ defaultMockResolver.resolverName = 'defaultMockResolver';
|
|
|
48
53
|
* Built-in Math Resolver
|
|
49
54
|
*/
|
|
50
55
|
async function builtInMathResolver(action, context) {
|
|
51
|
-
if (!action || typeof action !== 'string') return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if (!action || typeof action !== 'string') return undefined;
|
|
57
|
+
|
|
58
|
+
const a = action.replace(/\{([^\}]+)\}/g, (_, k) =>
|
|
59
|
+
context[k.trim()] ?? `{${k}}`
|
|
60
|
+
);
|
|
61
|
+
|
|
56
62
|
let m;
|
|
57
|
-
m = a.match(/^add\(([^,]+),\s*([^)]+)\)$/i)
|
|
58
|
-
m = a.match(/^subtract\(([^,]+),\s*([^)]+)\)$/i)
|
|
59
|
-
m = a.match(/^multiply\(([^,]+),\s*([^)]+)\)$/i)
|
|
60
|
-
m = a.match(/^divide\(([^,]+),\s*([^)]+)\)$/i)
|
|
61
|
-
m = a.match(/^sum\(\s*\[([^\]]+)\]\s*\)$/i)
|
|
62
|
-
|
|
63
|
+
if ((m = a.match(/^add\(([^,]+),\s*([^)]+)\)$/i))) return +m[1] + +m[2];
|
|
64
|
+
if ((m = a.match(/^subtract\(([^,]+),\s*([^)]+)\)$/i))) return +m[1] - +m[2];
|
|
65
|
+
if ((m = a.match(/^multiply\(([^,]+),\s*([^)]+)\)$/i))) return +m[1] * +m[2];
|
|
66
|
+
if ((m = a.match(/^divide\(([^,]+),\s*([^)]+)\)$/i))) return +m[1] / +m[2];
|
|
67
|
+
if ((m = a.match(/^sum\(\s*\[([^\]]+)\]\s*\)$/i)))
|
|
68
|
+
return m[1].split(',').map(Number).reduce((a, b) => a + b, 0);
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
63
71
|
}
|
|
64
72
|
builtInMathResolver.resolverName = 'builtInMathResolver';
|
|
65
73
|
|
|
66
74
|
/**
|
|
67
|
-
* Resolver chaining
|
|
75
|
+
* Resolver chaining
|
|
68
76
|
*/
|
|
69
77
|
function createResolverChain(resolvers, verbose = false) {
|
|
70
78
|
const wrapped = async (action, context) => {
|
|
71
|
-
for (
|
|
72
|
-
const resolver = resolvers[i];
|
|
79
|
+
for (const resolver of resolvers) {
|
|
73
80
|
try {
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
// Store result in context for debugging
|
|
77
|
-
context[`__resolver_${i}`] = res;
|
|
81
|
+
const result = await resolver(action, context);
|
|
82
|
+
if (result !== undefined) {
|
|
78
83
|
if (verbose) {
|
|
79
|
-
console.log(
|
|
84
|
+
console.log(`✅ ${resolver.resolverName} handled "${action}"`);
|
|
80
85
|
}
|
|
81
|
-
return
|
|
86
|
+
return result;
|
|
82
87
|
}
|
|
83
|
-
} catch (
|
|
84
|
-
console.error(
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(`❌ Resolver ${resolver.resolverName} failed:`, err.message);
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
|
-
if (verbose) {
|
|
88
|
-
console.log(`[⏭️] No resolver handled action: "${action}"`);
|
|
89
|
-
}
|
|
92
|
+
if (verbose) console.log(`⏭️ No resolver handled "${action}"`);
|
|
90
93
|
return undefined;
|
|
91
94
|
};
|
|
92
95
|
wrapped._chain = resolvers;
|
|
@@ -94,150 +97,133 @@ function createResolverChain(resolvers, verbose = false) {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
/**
|
|
97
|
-
* Load a single resolver
|
|
100
|
+
* Load a single resolver
|
|
98
101
|
*/
|
|
99
102
|
function loadSingleResolver(specifier) {
|
|
100
|
-
if (!specifier)
|
|
101
|
-
|
|
103
|
+
if (!specifier) throw new Error('Empty resolver specifier');
|
|
104
|
+
|
|
105
|
+
if (specifier.endsWith('.json')) {
|
|
106
|
+
const manifest = JSON.parse(fs.readFileSync(specifier, 'utf8'));
|
|
107
|
+
if (manifest.protocol?.startsWith('http')) {
|
|
108
|
+
const externalResolver = async () => undefined;
|
|
109
|
+
externalResolver.resolverName = manifest.name;
|
|
110
|
+
externalResolver.manifest = manifest;
|
|
111
|
+
console.log(`🌐 Loaded external resolver: ${manifest.name}`);
|
|
112
|
+
return externalResolver;
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
let resolver
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Local file: use filename without extension
|
|
109
|
-
pkgName = path.basename(specifier, path.extname(specifier));
|
|
110
|
-
} else {
|
|
111
|
-
// npm package: strip scope (e.g., @o-lang/extractor → extractor)
|
|
112
|
-
pkgName = specifier.replace(/^@[^/]+\//, '');
|
|
113
|
-
}
|
|
116
|
+
let resolver;
|
|
117
|
+
const pkgName = specifier.startsWith('.') || specifier.startsWith('/')
|
|
118
|
+
? path.basename(specifier, path.extname(specifier))
|
|
119
|
+
: specifier.replace(/^@[^/]+\//, '');
|
|
114
120
|
|
|
115
121
|
try {
|
|
116
122
|
resolver = require(specifier);
|
|
117
|
-
} catch
|
|
118
|
-
|
|
119
|
-
const absolutePath = path.resolve(process.cwd(), specifier);
|
|
120
|
-
resolver = require(absolutePath);
|
|
121
|
-
console.log(` 📁 Loaded resolver: ${absolutePath}`);
|
|
122
|
-
} catch (e2) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
`Failed to load resolver '${specifier}':\n npm: ${e1.message}\n file: ${e2.message}`
|
|
125
|
-
);
|
|
126
|
-
}
|
|
123
|
+
} catch {
|
|
124
|
+
resolver = require(path.resolve(process.cwd(), specifier));
|
|
127
125
|
}
|
|
128
126
|
|
|
129
127
|
if (typeof resolver !== 'function') {
|
|
130
128
|
throw new Error(`Resolver must export a function`);
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
resolver.resolverName = pkgName;
|
|
136
|
-
console.log(` 🏷️ Auto-assigned resolverName: "${pkgName}" (from ${specifier})`);
|
|
137
|
-
} else {
|
|
138
|
-
console.log(` 📦 Loaded resolver: ${specifier} (name: ${resolver.resolverName})`);
|
|
139
|
-
}
|
|
140
|
-
|
|
131
|
+
resolver.resolverName ||= pkgName;
|
|
132
|
+
console.log(`📦 Loaded resolver: ${resolver.resolverName}`);
|
|
141
133
|
return resolver;
|
|
142
134
|
}
|
|
143
135
|
|
|
144
136
|
/**
|
|
145
|
-
*
|
|
137
|
+
* Policy-aware resolver loader
|
|
146
138
|
*/
|
|
147
|
-
function loadResolverChain(specifiers, verbose
|
|
148
|
-
const userResolvers = specifiers?.map(loadSingleResolver) || [];
|
|
139
|
+
function loadResolverChain(specifiers, verbose, allowed) {
|
|
149
140
|
const resolvers = [];
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
if (allowedResolvers.has('builtInMathResolver')) {
|
|
153
|
-
resolvers.push(builtInMathResolver);
|
|
154
|
-
}
|
|
142
|
+
if (allowed.has('builtInMathResolver')) resolvers.push(builtInMathResolver);
|
|
155
143
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (allowedResolvers.has(name)) {
|
|
160
|
-
resolvers.push(r);
|
|
161
|
-
} else if (verbose) {
|
|
162
|
-
console.warn(` ⚠️ Skipping disallowed user resolver: ${name}`);
|
|
163
|
-
}
|
|
144
|
+
for (const r of specifiers.map(loadSingleResolver)) {
|
|
145
|
+
if (allowed.has(r.resolverName)) resolvers.push(r);
|
|
146
|
+
else if (verbose) console.warn(`⚠️ Skipped disallowed resolver: ${r.resolverName}`);
|
|
164
147
|
}
|
|
165
148
|
|
|
166
|
-
|
|
167
|
-
if (allowedResolvers.has('defaultMockResolver')) {
|
|
168
|
-
resolvers.push(defaultMockResolver);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (resolvers.length === 0) {
|
|
172
|
-
if (verbose) {
|
|
173
|
-
console.warn(' ⚠️ No allowed resolvers loaded. Actions may fail.');
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
if (verbose) {
|
|
177
|
-
console.log(` ℹ️ Loaded allowed resolvers: ${resolvers.map(r => r.resolverName || 'anonymous').join(', ')}`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
149
|
+
if (allowed.has('defaultMockResolver')) resolvers.push(defaultMockResolver);
|
|
180
150
|
|
|
181
151
|
return createResolverChain(resolvers, verbose);
|
|
182
152
|
}
|
|
183
153
|
|
|
184
154
|
/**
|
|
185
|
-
* CLI
|
|
155
|
+
* CLI SETUP
|
|
186
156
|
*/
|
|
187
157
|
const program = new Command();
|
|
158
|
+
|
|
159
|
+
// === RUN COMMAND ===
|
|
188
160
|
program
|
|
189
161
|
.name('olang')
|
|
190
|
-
.description('O-Lang CLI: run .ol workflows with rule-enforced agent governance')
|
|
191
162
|
.command('run <file>')
|
|
192
|
-
.option(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
(val
|
|
196
|
-
|
|
197
|
-
)
|
|
198
|
-
.option(
|
|
199
|
-
'-i, --input <k=v>',
|
|
200
|
-
'Input parameters',
|
|
201
|
-
(val, acc = {}) => {
|
|
202
|
-
const [k, v] = val.split('=');
|
|
203
|
-
const parsed = v === undefined ? '' : (v === 'true' ? true : (v === 'false' ? false : (isNaN(v) ? v : parseFloat(v))));
|
|
204
|
-
acc[k] = parsed;
|
|
205
|
-
return acc;
|
|
206
|
-
},
|
|
207
|
-
{}
|
|
208
|
-
)
|
|
209
|
-
.option('-v, --verbose', 'Verbose mode: logs resolver outputs and context after each step')
|
|
163
|
+
.option('-r, --resolver <specifier>', 'Resolver', (v, a) => (a.push(v), a), [])
|
|
164
|
+
.option('-i, --input <k=v>', 'Input', (v, a = {}) => {
|
|
165
|
+
const [k, val] = v.split('=');
|
|
166
|
+
a[k] = isNaN(val) ? val : Number(val);
|
|
167
|
+
return a;
|
|
168
|
+
}, {})
|
|
169
|
+
.option('-v, --verbose')
|
|
210
170
|
.action(async (file, options) => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
if (options.verbose) {
|
|
220
|
-
console.log(' 📄 Parsed Workflow:', JSON.stringify(workflow, null, 2));
|
|
221
|
-
}
|
|
171
|
+
ensureOlExtension(file);
|
|
172
|
+
const workflowSource = fs.readFileSync(file, 'utf8');
|
|
173
|
+
const workflow = parse(workflowSource, file);
|
|
174
|
+
|
|
175
|
+
const allowed = new Set(workflow.allowedResolvers);
|
|
176
|
+
const resolver = loadResolverChain(options.resolver, options.verbose, allowed);
|
|
222
177
|
|
|
223
|
-
|
|
224
|
-
|
|
178
|
+
const result = await execute(workflow, options.input, resolver, options.verbose);
|
|
179
|
+
console.log(JSON.stringify(result, null, 2));
|
|
180
|
+
});
|
|
225
181
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
182
|
+
// === SERVER COMMAND (✅ PROPER INTEGRATION) ===
|
|
183
|
+
program
|
|
184
|
+
.command('server')
|
|
185
|
+
.description('Start O-lang kernel in HTTP server mode')
|
|
186
|
+
.option('-p, --port <port>', 'Server port', process.env.OLANG_SERVER_PORT || '3000')
|
|
187
|
+
.option('-h, --host <host>', 'Server host', '0.0.0.0')
|
|
188
|
+
.action(async (options) => {
|
|
189
|
+
const fastify = require('fastify')({ logger: false });
|
|
190
|
+
|
|
191
|
+
fastify.get('/health', () => ({
|
|
192
|
+
status: 'healthy',
|
|
193
|
+
kernel: 'o-lang',
|
|
194
|
+
uptime: process.uptime()
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
fastify.post('/execute-workflow', async (req, reply) => {
|
|
198
|
+
try {
|
|
199
|
+
const { workflowSource, inputs = {}, resolvers = [], verbose = false } = req.body;
|
|
200
|
+
|
|
201
|
+
if (typeof workflowSource !== 'string') {
|
|
202
|
+
return reply.status(400).send({ error: 'workflowSource must be a string' });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const workflow = parse(workflowSource, 'remote.ol');
|
|
206
|
+
const allowed = new Set(workflow.allowedResolvers);
|
|
207
|
+
const resolver = loadResolverChain(resolvers, verbose, allowed);
|
|
208
|
+
|
|
209
|
+
const result = await execute(workflow, inputs, resolver, verbose);
|
|
210
|
+
reply.send(result);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
reply.status(500).send({ error: err.message });
|
|
232
213
|
}
|
|
214
|
+
});
|
|
233
215
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
216
|
+
const PORT = parseInt(options.port, 10);
|
|
217
|
+
const HOST = options.host;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
await fastify.listen({ port: PORT, host: HOST });
|
|
221
|
+
console.log(`✅ O-Lang Kernel running on http://${HOST}:${PORT}`);
|
|
237
222
|
} catch (err) {
|
|
238
|
-
console.error('
|
|
223
|
+
console.error('❌ Failed to start server:', err);
|
|
239
224
|
process.exit(1);
|
|
240
225
|
}
|
|
241
226
|
});
|
|
242
227
|
|
|
228
|
+
// === PARSE CLI ===
|
|
243
229
|
program.parse(process.argv);
|
package/package.json
CHANGED
package/src/runtime.js
CHANGED
|
@@ -145,6 +145,79 @@ class RuntimeAPI {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
// -----------------------------
|
|
149
|
+
// ✅ ADDITION 1 — External Resolver Detection
|
|
150
|
+
// -----------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Determines whether a resolver is external (HTTP-based)
|
|
153
|
+
* External resolvers MUST be declared via a manifest (.json)
|
|
154
|
+
* and explicitly allowed by workflow policy
|
|
155
|
+
*/
|
|
156
|
+
_isExternalResolver(resolver) {
|
|
157
|
+
return Boolean(
|
|
158
|
+
resolver &&
|
|
159
|
+
resolver.manifest &&
|
|
160
|
+
typeof resolver.manifest === 'object' &&
|
|
161
|
+
typeof resolver.manifest.protocol === 'string' &&
|
|
162
|
+
resolver.manifest.protocol.startsWith('http')
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// -----------------------------
|
|
167
|
+
// ✅ ADDITION 2 — External Resolver Invocation (HTTP Enforcement)
|
|
168
|
+
// -----------------------------
|
|
169
|
+
/**
|
|
170
|
+
* Calls an external HTTP resolver using its manifest definition.
|
|
171
|
+
* Enforces:
|
|
172
|
+
* - timeout
|
|
173
|
+
* - JSON contract
|
|
174
|
+
* - isolation (no direct execution)
|
|
175
|
+
*/
|
|
176
|
+
async _callExternalResolver(resolver, action, context) {
|
|
177
|
+
const manifest = resolver.manifest;
|
|
178
|
+
const endpoint = manifest.endpoint;
|
|
179
|
+
const timeoutMs = manifest.timeout_ms || 30000;
|
|
180
|
+
|
|
181
|
+
const payload = {
|
|
182
|
+
action,
|
|
183
|
+
context,
|
|
184
|
+
resolver: resolver.resolverName,
|
|
185
|
+
workflow: context.workflow_name,
|
|
186
|
+
timestamp: new Date().toISOString()
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const controller = new AbortController();
|
|
190
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const res = await fetch(`${endpoint}/resolve`, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'Content-Type': 'application/json' },
|
|
196
|
+
body: JSON.stringify(payload),
|
|
197
|
+
signal: controller.signal
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const json = await res.json();
|
|
205
|
+
|
|
206
|
+
if (json?.error) {
|
|
207
|
+
throw new Error(json.error.message || 'External resolver error');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return json.result;
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (err.name === 'AbortError') {
|
|
213
|
+
throw new Error(`External resolver timeout after ${timeoutMs}ms`);
|
|
214
|
+
}
|
|
215
|
+
throw err;
|
|
216
|
+
} finally {
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
148
221
|
// -----------------------------
|
|
149
222
|
// Utilities
|
|
150
223
|
// -----------------------------
|
|
@@ -218,15 +291,28 @@ class RuntimeAPI {
|
|
|
218
291
|
async executeStep(step, agentResolver) {
|
|
219
292
|
const stepType = step.type;
|
|
220
293
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
294
|
+
// ✅ ADDITION 3 — Resolver Policy Enforcement (External + Local)
|
|
295
|
+
const enforceResolverPolicy = (resolver, step) => {
|
|
296
|
+
const resolverName = resolver?.resolverName || resolver?.name;
|
|
224
297
|
|
|
225
|
-
|
|
298
|
+
if (!resolverName) {
|
|
299
|
+
throw new Error('[O-Lang] Resolver missing resolverName');
|
|
300
|
+
}
|
|
226
301
|
|
|
227
|
-
if (!
|
|
228
|
-
this.logDisallowedResolver(resolverName, step.actionRaw || step.
|
|
229
|
-
throw new Error(
|
|
302
|
+
if (!this.allowedResolvers.has(resolverName)) {
|
|
303
|
+
this.logDisallowedResolver(resolverName, step.actionRaw || step.type);
|
|
304
|
+
throw new Error(
|
|
305
|
+
`[O-Lang] Resolver "${resolverName}" blocked by workflow policy`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// External resolvers MUST be HTTP-only
|
|
310
|
+
if (this._isExternalResolver(resolver)) {
|
|
311
|
+
if (!resolver.manifest.endpoint) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`[O-Lang] External resolver "${resolverName}" missing endpoint`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
230
316
|
}
|
|
231
317
|
};
|
|
232
318
|
|
|
@@ -261,17 +347,28 @@ class RuntimeAPI {
|
|
|
261
347
|
// ✅ Return the FIRST resolver that returns a non-undefined result
|
|
262
348
|
for (let idx = 0; idx < resolversToRun.length; idx++) {
|
|
263
349
|
const resolver = resolversToRun[idx];
|
|
264
|
-
|
|
350
|
+
enforceResolverPolicy(resolver, step); // ✅ Use new policy enforcement
|
|
265
351
|
|
|
266
352
|
try {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
353
|
+
let result; // ✅ ADDITION 4 — External Resolver Execution Path
|
|
354
|
+
|
|
355
|
+
if (this._isExternalResolver(resolver)) {
|
|
356
|
+
result = await this._callExternalResolver(
|
|
357
|
+
resolver,
|
|
358
|
+
action,
|
|
359
|
+
this.context
|
|
360
|
+
);
|
|
361
|
+
} else {
|
|
362
|
+
result = await resolver(action, this.context);
|
|
274
363
|
}
|
|
364
|
+
|
|
365
|
+
if (result !== undefined) {
|
|
366
|
+
this.context[`__resolver_${idx}`] = result;
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
outputs.push(result);
|
|
371
|
+
this.context[`__resolver_${idx}`] = result;
|
|
275
372
|
} catch (e) {
|
|
276
373
|
this.addWarning(`Resolver ${resolver?.resolverName || resolver?.name || idx} failed for action "${action}": ${e.message}`);
|
|
277
374
|
outputs.push(null);
|
|
@@ -475,7 +572,12 @@ class RuntimeAPI {
|
|
|
475
572
|
}
|
|
476
573
|
}
|
|
477
574
|
|
|
575
|
+
// ✅ ADDITION 5 — Security Warning for External Resolvers
|
|
478
576
|
if (this.verbose) {
|
|
577
|
+
for (const r of this.allowedResolvers) {
|
|
578
|
+
// Note: We can't easily check if resolvers are external here since we only have names
|
|
579
|
+
// This would need to be moved to where we have the actual resolver objects
|
|
580
|
+
}
|
|
479
581
|
console.log(`\n[Step: ${step.type} | saveAs: ${step.saveAs || 'N/A'}]`);
|
|
480
582
|
console.log(JSON.stringify(this.context, null, 2));
|
|
481
583
|
}
|