@covibes/zeroshot 5.3.0 → 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/README.md +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- 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-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- 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 +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- 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 +76 -35
- 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/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- 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 +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
|
@@ -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
|
|
@@ -39,101 +124,82 @@ async 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 = await 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 {Promise<Object>} Message to publish
|
|
81
|
-
*/
|
|
82
|
-
async 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) {
|
|
176
|
+
let resultData = null;
|
|
96
177
|
try {
|
|
97
178
|
resultData = await agent._parseResultOutput(context.result.output);
|
|
98
179
|
} catch (parseError) {
|
|
99
|
-
|
|
100
|
-
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
101
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
102
|
-
console.error(`🔴 TRANSFORM SCRIPT BLOCKED - RESULT PARSING FAILED`);
|
|
103
|
-
console.error(`${'='.repeat(80)}`);
|
|
104
|
-
console.error(`Agent: ${agent.id}, Role: ${agent.role}`);
|
|
105
|
-
console.error(`TaskID: ${taskId}`);
|
|
106
|
-
console.error(`Parse error: ${parseError.message}`);
|
|
107
|
-
console.error(`Output (last 500 chars): ${(context.result.output || '').slice(-500)}`);
|
|
108
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
180
|
+
logTransformParseFailure({ agent, context, parseError });
|
|
109
181
|
throw new Error(
|
|
110
182
|
`Transform script cannot run: result parsing failed. ` +
|
|
111
183
|
`Agent: ${agent.id}, Error: ${parseError.message}`
|
|
112
184
|
);
|
|
113
185
|
}
|
|
114
186
|
|
|
115
|
-
|
|
116
|
-
// Extract field names from script (e.g., result.complexity, result.taskType)
|
|
117
|
-
const accessedFields = [...script.matchAll(/result\.([a-zA-Z_]+)/g)].map((m) => m[1]);
|
|
187
|
+
const accessedFields = getAccessedFields(script);
|
|
118
188
|
const missingFields = accessedFields.filter((f) => resultData[f] === undefined);
|
|
119
189
|
if (missingFields.length > 0) {
|
|
190
|
+
logMissingResultFields({ agent, context, accessedFields, missingFields, resultData });
|
|
120
191
|
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
121
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
122
|
-
console.error(`🔴 TRANSFORM SCRIPT BLOCKED - MISSING REQUIRED FIELDS`);
|
|
123
|
-
console.error(`${'='.repeat(80)}`);
|
|
124
|
-
console.error(`Agent: ${agent.id}, Role: ${agent.role}, TaskID: ${taskId}`);
|
|
125
|
-
console.error(`Script accesses: ${accessedFields.join(', ')}`);
|
|
126
|
-
console.error(`Missing from result: ${missingFields.join(', ')}`);
|
|
127
|
-
console.error(`Result keys: ${Object.keys(resultData).join(', ')}`);
|
|
128
|
-
console.error(`Result data: ${JSON.stringify(resultData, null, 2)}`);
|
|
129
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
130
192
|
throw new Error(
|
|
131
193
|
`Transform script accesses undefined fields: ${missingFields.join(', ')}. ` +
|
|
132
194
|
`Agent ${agent.id} (task ${taskId}) output missing required fields. ` +
|
|
133
195
|
`Check agent's jsonSchema and output format.`
|
|
134
196
|
);
|
|
135
197
|
}
|
|
136
|
-
|
|
198
|
+
|
|
199
|
+
return resultData;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (scriptUsesResult) {
|
|
137
203
|
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
138
204
|
const outputLength = (context.result?.output || '').length;
|
|
139
205
|
throw new Error(
|
|
@@ -144,17 +210,15 @@ async function executeTransform(params) {
|
|
|
144
210
|
);
|
|
145
211
|
}
|
|
146
212
|
|
|
147
|
-
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function buildTransformSandbox({ resultData, context, agent }) {
|
|
148
217
|
const helpers = {
|
|
149
|
-
/**
|
|
150
|
-
* Get cluster config based on complexity and task type
|
|
151
|
-
* Returns: { base: 'template-name', params: { ... } }
|
|
152
|
-
*/
|
|
153
218
|
getConfig: require('../config-router').getConfig,
|
|
154
219
|
};
|
|
155
220
|
|
|
156
|
-
|
|
157
|
-
const sandbox = {
|
|
221
|
+
return {
|
|
158
222
|
result: resultData,
|
|
159
223
|
triggeringMessage: context.triggeringMessage,
|
|
160
224
|
helpers,
|
|
@@ -165,19 +229,20 @@ async function executeTransform(params) {
|
|
|
165
229
|
warn: (...args) => console.warn('[transform]', ...args),
|
|
166
230
|
},
|
|
167
231
|
};
|
|
232
|
+
}
|
|
168
233
|
|
|
169
|
-
|
|
234
|
+
function runTransformScript(script, sandbox) {
|
|
170
235
|
const vmContext = vm.createContext(sandbox);
|
|
171
236
|
const wrappedScript = `(function() { ${script} })()`;
|
|
172
237
|
|
|
173
|
-
let result;
|
|
174
238
|
try {
|
|
175
|
-
|
|
239
|
+
return vm.runInContext(wrappedScript, vmContext, { timeout: 5000 });
|
|
176
240
|
} catch (err) {
|
|
177
241
|
throw new Error(`Transform script error: ${err.message}`);
|
|
178
242
|
}
|
|
243
|
+
}
|
|
179
244
|
|
|
180
|
-
|
|
245
|
+
function validateTransformResult(result) {
|
|
181
246
|
if (!result || typeof result !== 'object') {
|
|
182
247
|
throw new Error(
|
|
183
248
|
`Transform script must return an object with topic and content, got: ${typeof result}`
|
|
@@ -189,41 +254,77 @@ async function executeTransform(params) {
|
|
|
189
254
|
if (!result.content) {
|
|
190
255
|
throw new Error(`Transform script result must have a 'content' property`);
|
|
191
256
|
}
|
|
257
|
+
}
|
|
192
258
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
215
281
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
throw new Error(`CLUSTER_OPERATIONS.operations[${i}] missing required 'action' field`);
|
|
221
|
-
}
|
|
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`);
|
|
222
286
|
}
|
|
287
|
+
}
|
|
223
288
|
|
|
224
|
-
|
|
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}`);
|
|
225
310
|
}
|
|
226
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);
|
|
327
|
+
|
|
227
328
|
return result;
|
|
228
329
|
}
|
|
229
330
|
|
|
@@ -278,7 +379,6 @@ async function substituteTemplate(params) {
|
|
|
278
379
|
if (taskId !== 'UNKNOWN') {
|
|
279
380
|
console.error(`\nFetching task logs for ${taskId}...`);
|
|
280
381
|
try {
|
|
281
|
-
const { execSync } = require('child_process');
|
|
282
382
|
const ctPath = agent._getClaudeTasksPath();
|
|
283
383
|
taskLogs = execSync(`${ctPath} logs ${taskId} --lines 100`, {
|
|
284
384
|
encoding: 'utf-8',
|
|
@@ -333,12 +433,25 @@ async function substituteTemplate(params) {
|
|
|
333
433
|
return stringified.slice(1, -1);
|
|
334
434
|
};
|
|
335
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
|
+
|
|
336
443
|
let substituted = json
|
|
337
|
-
.replace(/\{\{cluster\.id\}\}/g, cluster.id)
|
|
444
|
+
.replace(/\{\{cluster\.id\}\}/g, escapeTemplatePatterns(cluster.id))
|
|
338
445
|
.replace(/\{\{cluster\.createdAt\}\}/g, String(cluster.createdAt))
|
|
339
446
|
.replace(/\{\{iteration\}\}/g, String(agent.iteration))
|
|
340
|
-
.replace(
|
|
341
|
-
|
|
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
|
+
);
|
|
342
455
|
|
|
343
456
|
// Substitute ALL result.* variables dynamically from parsed resultData
|
|
344
457
|
if (resultData) {
|
|
@@ -364,7 +477,8 @@ async function substituteTemplate(params) {
|
|
|
364
477
|
return String(value);
|
|
365
478
|
}
|
|
366
479
|
// Strings need to be quoted and escaped for JSON
|
|
367
|
-
|
|
480
|
+
// Also escape any template-like patterns in the content to prevent false positives
|
|
481
|
+
return escapeTemplatePatterns(JSON.stringify(value));
|
|
368
482
|
});
|
|
369
483
|
}
|
|
370
484
|
|
|
@@ -395,8 +509,91 @@ async function substituteTemplate(params) {
|
|
|
395
509
|
return result;
|
|
396
510
|
}
|
|
397
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
|
+
|
|
398
593
|
module.exports = {
|
|
399
594
|
executeHook,
|
|
400
595
|
executeTransform,
|
|
401
596
|
substituteTemplate,
|
|
597
|
+
evaluateHookLogic,
|
|
598
|
+
deepMerge,
|
|
402
599
|
};
|