@covibes/zeroshot 5.2.1 → 5.4.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/CHANGELOG.md +174 -189
- package/README.md +226 -195
- package/cli/commands/providers.js +149 -0
- package/cli/index.js +3145 -2366
- package/cli/lib/first-run.js +40 -3
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +99 -316
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +105 -36
- package/cluster-templates/conductor-bootstrap.json +9 -7
- package/lib/docker-config.js +14 -1
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +57 -0
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +298 -15
- package/lib/stream-json-parser.js +4 -238
- package/package.json +27 -6
- package/scripts/setup-merge-queue.sh +170 -0
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +140 -63
- package/src/agent/agent-context-builder.js +336 -165
- package/src/agent/agent-hook-executor.js +337 -67
- package/src/agent/agent-lifecycle.js +386 -287
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +944 -683
- package/src/agent/output-extraction.js +217 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +146 -0
- package/src/agent-wrapper.js +112 -31
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +145 -44
- package/src/config-router.js +13 -13
- package/src/config-validator.js +1049 -563
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +499 -320
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +50 -11
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1348 -757
- package/src/preflight.js +306 -149
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +73 -0
- package/src/providers/anthropic/index.js +204 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +177 -0
- package/src/providers/base-provider.js +251 -0
- package/src/providers/capabilities.js +60 -0
- package/src/providers/google/cli-builder.js +55 -0
- package/src/providers/google/index.js +116 -0
- package/src/providers/google/models.js +24 -0
- package/src/providers/google/output-parser.js +101 -0
- package/src/providers/index.js +91 -0
- package/src/providers/openai/cli-builder.js +133 -0
- package/src/providers/openai/index.js +136 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +143 -0
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +92 -36
- package/src/task-runner.js +8 -6
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/layout.js +20 -3
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +150 -111
- package/task-lib/claude-recovery.js +156 -0
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +3 -3
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +128 -50
- package/task-lib/scheduler.js +3 -3
- package/task-lib/store.js +464 -152
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +157 -100
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -103
|
@@ -5,9 +5,94 @@
|
|
|
5
5
|
* - Hook execution (publish_message, stop_cluster, etc.)
|
|
6
6
|
* - Template variable substitution
|
|
7
7
|
* - Transform script execution in VM sandbox
|
|
8
|
+
* - Logic scripts for conditional config (like triggers)
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
const vm = require('vm');
|
|
12
|
+
const { execSync } = require('../lib/safe-exec'); // Enforces timeouts
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Deep merge two objects, with source taking precedence
|
|
16
|
+
* @param {Object} target - Base object
|
|
17
|
+
* @param {Object} source - Override object
|
|
18
|
+
* @returns {Object} Merged object
|
|
19
|
+
*/
|
|
20
|
+
function deepMerge(target, source) {
|
|
21
|
+
if (!source || typeof source !== 'object') return target;
|
|
22
|
+
if (!target || typeof target !== 'object') return source;
|
|
23
|
+
|
|
24
|
+
const result = { ...target };
|
|
25
|
+
for (const key of Object.keys(source)) {
|
|
26
|
+
if (
|
|
27
|
+
source[key] &&
|
|
28
|
+
typeof source[key] === 'object' &&
|
|
29
|
+
!Array.isArray(source[key]) &&
|
|
30
|
+
target[key] &&
|
|
31
|
+
typeof target[key] === 'object' &&
|
|
32
|
+
!Array.isArray(target[key])
|
|
33
|
+
) {
|
|
34
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
35
|
+
} else {
|
|
36
|
+
result[key] = source[key];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function parseResultDataForHookLogic({ agent, result }) {
|
|
43
|
+
if (!result?.output) return null;
|
|
44
|
+
try {
|
|
45
|
+
return await agent._parseResultOutput(result.output);
|
|
46
|
+
} catch (parseError) {
|
|
47
|
+
agent._log(
|
|
48
|
+
`⚠️ Hook logic: result parsing failed, continuing with null: ${parseError.message}`
|
|
49
|
+
);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function resolveHookConfigWithLogic({ hook, agent, context, result }) {
|
|
55
|
+
if (!hook.logic) return hook.config;
|
|
56
|
+
|
|
57
|
+
const resultData = await parseResultDataForHookLogic({ agent, result });
|
|
58
|
+
const overrides = evaluateHookLogic({
|
|
59
|
+
logic: hook.logic,
|
|
60
|
+
resultData,
|
|
61
|
+
agent,
|
|
62
|
+
context,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!overrides) {
|
|
66
|
+
return hook.config;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
agent._log(`Hook logic returned overrides: ${JSON.stringify(overrides)}`);
|
|
70
|
+
return deepMerge(hook.config, overrides);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function resolvePublishMessage({ hook, agent, context, cluster, result }) {
|
|
74
|
+
if (hook.transform) {
|
|
75
|
+
return executeTransform({
|
|
76
|
+
transform: hook.transform,
|
|
77
|
+
context,
|
|
78
|
+
agent,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const effectiveConfig = await resolveHookConfigWithLogic({
|
|
83
|
+
hook,
|
|
84
|
+
agent,
|
|
85
|
+
context,
|
|
86
|
+
result,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return substituteTemplate({
|
|
90
|
+
config: effectiveConfig,
|
|
91
|
+
context,
|
|
92
|
+
agent,
|
|
93
|
+
cluster,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
11
96
|
|
|
12
97
|
/**
|
|
13
98
|
* Execute a hook
|
|
@@ -22,7 +107,7 @@ const vm = require('vm');
|
|
|
22
107
|
* @param {Object} params.orchestrator - Orchestrator instance
|
|
23
108
|
* @returns {Promise<void>}
|
|
24
109
|
*/
|
|
25
|
-
function executeHook(params) {
|
|
110
|
+
async function executeHook(params) {
|
|
26
111
|
const { hook, agent, message, result, cluster } = params;
|
|
27
112
|
|
|
28
113
|
if (!hook) {
|
|
@@ -39,62 +124,82 @@ function executeHook(params) {
|
|
|
39
124
|
|
|
40
125
|
// NO try/catch - errors must propagate
|
|
41
126
|
if (hook.action === 'publish_message') {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
agent,
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
// Existing: Use template substitution
|
|
53
|
-
messageToPublish = substituteTemplate({
|
|
54
|
-
config: hook.config,
|
|
55
|
-
context,
|
|
56
|
-
agent,
|
|
57
|
-
cluster,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Publish via agent's _publish method
|
|
127
|
+
const messageToPublish = await resolvePublishMessage({
|
|
128
|
+
hook,
|
|
129
|
+
agent,
|
|
130
|
+
context,
|
|
131
|
+
cluster,
|
|
132
|
+
result,
|
|
133
|
+
});
|
|
62
134
|
agent._publish(messageToPublish);
|
|
63
|
-
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (hook.action === 'execute_system_command') {
|
|
64
139
|
throw new Error('execute_system_command not implemented');
|
|
65
|
-
} else {
|
|
66
|
-
throw new Error(`Unknown hook action: ${hook.action}`);
|
|
67
140
|
}
|
|
141
|
+
|
|
142
|
+
throw new Error(`Unknown hook action: ${hook.action}`);
|
|
68
143
|
}
|
|
69
144
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* - result: parsed agent output
|
|
74
|
-
* - triggeringMessage: the message that triggered the agent
|
|
75
|
-
* - helpers: { getConfig(complexity, taskType) }
|
|
76
|
-
* @param {Object} params - Transform parameters
|
|
77
|
-
* @param {Object} params.transform - Transform configuration
|
|
78
|
-
* @param {Object} params.context - Execution context
|
|
79
|
-
* @param {Object} params.agent - Agent instance
|
|
80
|
-
* @returns {Object} Message to publish
|
|
81
|
-
*/
|
|
82
|
-
function executeTransform(params) {
|
|
83
|
-
const { transform, context, agent } = params;
|
|
84
|
-
const { engine, script } = transform;
|
|
145
|
+
function getAccessedFields(script) {
|
|
146
|
+
return [...script.matchAll(/result\.([a-zA-Z_]+)/g)].map((m) => m[1]);
|
|
147
|
+
}
|
|
85
148
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
149
|
+
function logTransformParseFailure({ agent, context, parseError }) {
|
|
150
|
+
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
151
|
+
console.error(`\n${'='.repeat(80)}`);
|
|
152
|
+
console.error(`🔴 TRANSFORM SCRIPT BLOCKED - RESULT PARSING FAILED`);
|
|
153
|
+
console.error(`${'='.repeat(80)}`);
|
|
154
|
+
console.error(`Agent: ${agent.id}, Role: ${agent.role}`);
|
|
155
|
+
console.error(`TaskID: ${taskId}`);
|
|
156
|
+
console.error(`Parse error: ${parseError.message}`);
|
|
157
|
+
console.error(`Output (last 500 chars): ${(context.result.output || '').slice(-500)}`);
|
|
158
|
+
console.error(`${'='.repeat(80)}\n`);
|
|
159
|
+
}
|
|
89
160
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
161
|
+
function logMissingResultFields({ agent, context, accessedFields, missingFields, resultData }) {
|
|
162
|
+
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
163
|
+
console.error(`\n${'='.repeat(80)}`);
|
|
164
|
+
console.error(`🔴 TRANSFORM SCRIPT BLOCKED - MISSING REQUIRED FIELDS`);
|
|
165
|
+
console.error(`${'='.repeat(80)}`);
|
|
166
|
+
console.error(`Agent: ${agent.id}, Role: ${agent.role}, TaskID: ${taskId}`);
|
|
167
|
+
console.error(`Script accesses: ${accessedFields.join(', ')}`);
|
|
168
|
+
console.error(`Missing from result: ${missingFields.join(', ')}`);
|
|
169
|
+
console.error(`Result keys: ${Object.keys(resultData).join(', ')}`);
|
|
170
|
+
console.error(`Result data: ${JSON.stringify(resultData, null, 2)}`);
|
|
171
|
+
console.error(`${'='.repeat(80)}\n`);
|
|
172
|
+
}
|
|
94
173
|
|
|
174
|
+
async function parseTransformResultData({ context, agent, script, scriptUsesResult }) {
|
|
95
175
|
if (context.result?.output) {
|
|
96
|
-
resultData =
|
|
97
|
-
|
|
176
|
+
let resultData = null;
|
|
177
|
+
try {
|
|
178
|
+
resultData = await agent._parseResultOutput(context.result.output);
|
|
179
|
+
} catch (parseError) {
|
|
180
|
+
logTransformParseFailure({ agent, context, parseError });
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Transform script cannot run: result parsing failed. ` +
|
|
183
|
+
`Agent: ${agent.id}, Error: ${parseError.message}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const accessedFields = getAccessedFields(script);
|
|
188
|
+
const missingFields = accessedFields.filter((f) => resultData[f] === undefined);
|
|
189
|
+
if (missingFields.length > 0) {
|
|
190
|
+
logMissingResultFields({ agent, context, accessedFields, missingFields, resultData });
|
|
191
|
+
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Transform script accesses undefined fields: ${missingFields.join(', ')}. ` +
|
|
194
|
+
`Agent ${agent.id} (task ${taskId}) output missing required fields. ` +
|
|
195
|
+
`Check agent's jsonSchema and output format.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return resultData;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (scriptUsesResult) {
|
|
98
203
|
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
99
204
|
const outputLength = (context.result?.output || '').length;
|
|
100
205
|
throw new Error(
|
|
@@ -105,17 +210,15 @@ function executeTransform(params) {
|
|
|
105
210
|
);
|
|
106
211
|
}
|
|
107
212
|
|
|
108
|
-
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function buildTransformSandbox({ resultData, context, agent }) {
|
|
109
217
|
const helpers = {
|
|
110
|
-
/**
|
|
111
|
-
* Get cluster config based on complexity and task type
|
|
112
|
-
* Returns: { base: 'template-name', params: { ... } }
|
|
113
|
-
*/
|
|
114
218
|
getConfig: require('../config-router').getConfig,
|
|
115
219
|
};
|
|
116
220
|
|
|
117
|
-
|
|
118
|
-
const sandbox = {
|
|
221
|
+
return {
|
|
119
222
|
result: resultData,
|
|
120
223
|
triggeringMessage: context.triggeringMessage,
|
|
121
224
|
helpers,
|
|
@@ -126,19 +229,20 @@ function executeTransform(params) {
|
|
|
126
229
|
warn: (...args) => console.warn('[transform]', ...args),
|
|
127
230
|
},
|
|
128
231
|
};
|
|
232
|
+
}
|
|
129
233
|
|
|
130
|
-
|
|
234
|
+
function runTransformScript(script, sandbox) {
|
|
131
235
|
const vmContext = vm.createContext(sandbox);
|
|
132
236
|
const wrappedScript = `(function() { ${script} })()`;
|
|
133
237
|
|
|
134
|
-
let result;
|
|
135
238
|
try {
|
|
136
|
-
|
|
239
|
+
return vm.runInContext(wrappedScript, vmContext, { timeout: 5000 });
|
|
137
240
|
} catch (err) {
|
|
138
241
|
throw new Error(`Transform script error: ${err.message}`);
|
|
139
242
|
}
|
|
243
|
+
}
|
|
140
244
|
|
|
141
|
-
|
|
245
|
+
function validateTransformResult(result) {
|
|
142
246
|
if (!result || typeof result !== 'object') {
|
|
143
247
|
throw new Error(
|
|
144
248
|
`Transform script must return an object with topic and content, got: ${typeof result}`
|
|
@@ -150,6 +254,76 @@ function executeTransform(params) {
|
|
|
150
254
|
if (!result.content) {
|
|
151
255
|
throw new Error(`Transform script result must have a 'content' property`);
|
|
152
256
|
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function validateClusterOperationsResult(result, agent) {
|
|
260
|
+
if (result.topic !== 'CLUSTER_OPERATIONS') return;
|
|
261
|
+
|
|
262
|
+
const operations = result.content?.data?.operations;
|
|
263
|
+
if (!operations) {
|
|
264
|
+
console.error(`\n${'='.repeat(80)}`);
|
|
265
|
+
console.error(`🔴 CLUSTER_OPERATIONS MALFORMED - MISSING OPERATIONS ARRAY`);
|
|
266
|
+
console.error(`${'='.repeat(80)}`);
|
|
267
|
+
console.error(`Agent: ${agent.id}`);
|
|
268
|
+
console.error(`Result: ${JSON.stringify(result, null, 2)}`);
|
|
269
|
+
console.error(`${'='.repeat(80)}\n`);
|
|
270
|
+
throw new Error(
|
|
271
|
+
`CLUSTER_OPERATIONS message missing operations array. ` +
|
|
272
|
+
`Agent ${agent.id} transform script returned invalid structure.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (!Array.isArray(operations)) {
|
|
276
|
+
throw new Error(`CLUSTER_OPERATIONS.operations must be an array, got: ${typeof operations}`);
|
|
277
|
+
}
|
|
278
|
+
if (operations.length === 0) {
|
|
279
|
+
throw new Error(`CLUSTER_OPERATIONS.operations is empty - no operations to execute`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < operations.length; i++) {
|
|
283
|
+
const op = operations[i];
|
|
284
|
+
if (!op || !op.action) {
|
|
285
|
+
throw new Error(`CLUSTER_OPERATIONS.operations[${i}] missing required 'action' field`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
agent._log(`✅ CLUSTER_OPERATIONS validated: ${operations.length} operations`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Execute a hook transform script
|
|
294
|
+
* Transform scripts return the message to publish, with access to:
|
|
295
|
+
* - result: parsed agent output
|
|
296
|
+
* - triggeringMessage: the message that triggered the agent
|
|
297
|
+
* - helpers: { getConfig(complexity, taskType) }
|
|
298
|
+
* @param {Object} params - Transform parameters
|
|
299
|
+
* @param {Object} params.transform - Transform configuration
|
|
300
|
+
* @param {Object} params.context - Execution context
|
|
301
|
+
* @param {Object} params.agent - Agent instance
|
|
302
|
+
* @returns {Promise<Object>} Message to publish
|
|
303
|
+
*/
|
|
304
|
+
async function executeTransform(params) {
|
|
305
|
+
const { transform, context, agent } = params;
|
|
306
|
+
const { engine, script } = transform;
|
|
307
|
+
|
|
308
|
+
if (engine !== 'javascript') {
|
|
309
|
+
throw new Error(`Unsupported transform engine: ${engine}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Parse result output if we have a result
|
|
313
|
+
// VALIDATION: Check if script uses result.* variables and fail early if no output
|
|
314
|
+
const scriptUsesResult = /\bresult\.[a-zA-Z]/.test(script);
|
|
315
|
+
const resultData = await parseTransformResultData({
|
|
316
|
+
context,
|
|
317
|
+
agent,
|
|
318
|
+
script,
|
|
319
|
+
scriptUsesResult,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const sandbox = buildTransformSandbox({ resultData, context, agent });
|
|
323
|
+
const result = runTransformScript(script, sandbox);
|
|
324
|
+
|
|
325
|
+
validateTransformResult(result);
|
|
326
|
+
validateClusterOperationsResult(result, agent);
|
|
153
327
|
|
|
154
328
|
return result;
|
|
155
329
|
}
|
|
@@ -163,9 +337,9 @@ function executeTransform(params) {
|
|
|
163
337
|
* @param {Object} params.context - Execution context
|
|
164
338
|
* @param {Object} params.agent - Agent instance
|
|
165
339
|
* @param {Object} params.cluster - Cluster object
|
|
166
|
-
* @returns {Object} Substituted configuration
|
|
340
|
+
* @returns {Promise<Object>} Substituted configuration
|
|
167
341
|
*/
|
|
168
|
-
function substituteTemplate(params) {
|
|
342
|
+
async function substituteTemplate(params) {
|
|
169
343
|
const { config, context, agent, cluster } = params;
|
|
170
344
|
|
|
171
345
|
if (!config) {
|
|
@@ -205,7 +379,6 @@ function substituteTemplate(params) {
|
|
|
205
379
|
if (taskId !== 'UNKNOWN') {
|
|
206
380
|
console.error(`\nFetching task logs for ${taskId}...`);
|
|
207
381
|
try {
|
|
208
|
-
const { execSync } = require('child_process');
|
|
209
382
|
const ctPath = agent._getClaudeTasksPath();
|
|
210
383
|
taskLogs = execSync(`${ctPath} logs ${taskId} --lines 100`, {
|
|
211
384
|
encoding: 'utf-8',
|
|
@@ -243,7 +416,7 @@ function substituteTemplate(params) {
|
|
|
243
416
|
);
|
|
244
417
|
}
|
|
245
418
|
// Parse result output - WILL THROW if no JSON block
|
|
246
|
-
resultData = agent._parseResultOutput(context.result.output);
|
|
419
|
+
resultData = await agent._parseResultOutput(context.result.output);
|
|
247
420
|
}
|
|
248
421
|
|
|
249
422
|
// Helper to escape a value for JSON string substitution
|
|
@@ -260,12 +433,25 @@ function substituteTemplate(params) {
|
|
|
260
433
|
return stringified.slice(1, -1);
|
|
261
434
|
};
|
|
262
435
|
|
|
436
|
+
// Helper to escape template-like patterns in substituted values
|
|
437
|
+
// This prevents content containing "{{result.foo}}" from being flagged as unsubstituted variables
|
|
438
|
+
// Uses Unicode escape for first brace: {{ -> \u007B{ (invisible to humans, breaks pattern match)
|
|
439
|
+
const escapeTemplatePatterns = (str) => {
|
|
440
|
+
return str.replace(/\{\{/g, '\\u007B{');
|
|
441
|
+
};
|
|
442
|
+
|
|
263
443
|
let substituted = json
|
|
264
|
-
.replace(/\{\{cluster\.id\}\}/g, cluster.id)
|
|
444
|
+
.replace(/\{\{cluster\.id\}\}/g, escapeTemplatePatterns(cluster.id))
|
|
265
445
|
.replace(/\{\{cluster\.createdAt\}\}/g, String(cluster.createdAt))
|
|
266
446
|
.replace(/\{\{iteration\}\}/g, String(agent.iteration))
|
|
267
|
-
.replace(
|
|
268
|
-
|
|
447
|
+
.replace(
|
|
448
|
+
/\{\{error\.message\}\}/g,
|
|
449
|
+
escapeTemplatePatterns(escapeForJsonString(context.error?.message ?? ''))
|
|
450
|
+
)
|
|
451
|
+
.replace(
|
|
452
|
+
/\{\{result\.output\}\}/g,
|
|
453
|
+
escapeTemplatePatterns(escapeForJsonString(context.result?.output ?? ''))
|
|
454
|
+
);
|
|
269
455
|
|
|
270
456
|
// Substitute ALL result.* variables dynamically from parsed resultData
|
|
271
457
|
if (resultData) {
|
|
@@ -291,7 +477,8 @@ function substituteTemplate(params) {
|
|
|
291
477
|
return String(value);
|
|
292
478
|
}
|
|
293
479
|
// Strings need to be quoted and escaped for JSON
|
|
294
|
-
|
|
480
|
+
// Also escape any template-like patterns in the content to prevent false positives
|
|
481
|
+
return escapeTemplatePatterns(JSON.stringify(value));
|
|
295
482
|
});
|
|
296
483
|
}
|
|
297
484
|
|
|
@@ -322,8 +509,91 @@ function substituteTemplate(params) {
|
|
|
322
509
|
return result;
|
|
323
510
|
}
|
|
324
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Evaluate hook logic script to get config overrides
|
|
514
|
+
* Similar to trigger logic, but returns an object to merge into config
|
|
515
|
+
* @param {Object} params - Evaluation parameters
|
|
516
|
+
* @param {Object} params.logic - Logic configuration { engine, script }
|
|
517
|
+
* @param {Object} params.resultData - Parsed agent result data
|
|
518
|
+
* @param {Object} params.agent - Agent instance
|
|
519
|
+
* @param {Object} params.context - Execution context
|
|
520
|
+
* @returns {Object|null} Config overrides to merge, or null if none
|
|
521
|
+
*/
|
|
522
|
+
function evaluateHookLogic(params) {
|
|
523
|
+
const { logic, resultData, agent, context } = params;
|
|
524
|
+
|
|
525
|
+
if (!logic || !logic.script) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (logic.engine !== 'javascript') {
|
|
530
|
+
throw new Error(`Unsupported hook logic engine: ${logic.engine}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Build sandbox context - similar to LogicEngine but focused on result data
|
|
534
|
+
const sandbox = {
|
|
535
|
+
// The parsed result from agent output - this is the main input
|
|
536
|
+
result: resultData || {},
|
|
537
|
+
|
|
538
|
+
// Agent context
|
|
539
|
+
agent: {
|
|
540
|
+
id: agent.id,
|
|
541
|
+
role: agent.role,
|
|
542
|
+
iteration: agent.iteration || 0,
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
// Triggering message (if available)
|
|
546
|
+
message: context.triggeringMessage || null,
|
|
547
|
+
|
|
548
|
+
// Safe built-ins
|
|
549
|
+
Set,
|
|
550
|
+
Map,
|
|
551
|
+
Array,
|
|
552
|
+
Object,
|
|
553
|
+
String,
|
|
554
|
+
Number,
|
|
555
|
+
Boolean,
|
|
556
|
+
Math,
|
|
557
|
+
Date,
|
|
558
|
+
JSON,
|
|
559
|
+
|
|
560
|
+
// Console for debugging (logs to agent log)
|
|
561
|
+
console: {
|
|
562
|
+
log: (...args) => agent._log('[hook-logic]', ...args),
|
|
563
|
+
error: (...args) => console.error('[hook-logic]', ...args),
|
|
564
|
+
warn: (...args) => console.warn('[hook-logic]', ...args),
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// Execute in VM sandbox with timeout
|
|
569
|
+
const vmContext = vm.createContext(sandbox);
|
|
570
|
+
const wrappedScript = `(function() { 'use strict'; ${logic.script} })()`;
|
|
571
|
+
|
|
572
|
+
let result;
|
|
573
|
+
try {
|
|
574
|
+
result = vm.runInContext(wrappedScript, vmContext, { timeout: 1000 });
|
|
575
|
+
} catch (err) {
|
|
576
|
+
throw new Error(`Hook logic script error: ${err.message}`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Logic scripts can return:
|
|
580
|
+
// - undefined/null: no overrides
|
|
581
|
+
// - object: merge into config
|
|
582
|
+
if (result === undefined || result === null) {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (typeof result !== 'object') {
|
|
587
|
+
throw new Error(`Hook logic script must return an object or undefined, got: ${typeof result}`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
|
|
325
593
|
module.exports = {
|
|
326
594
|
executeHook,
|
|
327
595
|
executeTransform,
|
|
328
596
|
substituteTemplate,
|
|
597
|
+
evaluateHookLogic,
|
|
598
|
+
deepMerge,
|
|
329
599
|
};
|