@covibes/zeroshot 5.3.0 → 5.4.1
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/LICENSE +1 -1
- package/README.md +8 -385
- package/cli/index.js +108 -4615
- package/package.json +16 -125
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -150
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -253
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -697
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -152
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -212
- package/lib/id-detector.js +0 -53
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -56
- package/lib/settings.js +0 -405
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -193
- package/src/agent/agent-context-builder.js +0 -335
- package/src/agent/agent-hook-executor.js +0 -402
- package/src/agent/agent-lifecycle.js +0 -683
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1431
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -200
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -111
- package/src/agent-wrapper.js +0 -570
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -523
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1660
- package/src/copy-worker.js +0 -43
- package/src/github.js +0 -139
- package/src/isolation-manager.js +0 -1381
- package/src/ledger.js +0 -614
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2519
- package/src/preflight.js +0 -485
- package/src/process-metrics.js +0 -566
- package/src/providers/anthropic/cli-builder.js +0 -45
- package/src/providers/anthropic/index.js +0 -134
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -159
- package/src/providers/base-provider.js +0 -181
- package/src/providers/capabilities.js +0 -51
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -92
- package/src/providers/index.js +0 -75
- package/src/providers/openai/cli-builder.js +0 -122
- package/src/providers/openai/index.js +0 -135
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -129
- package/src/schemas/sub-cluster.js +0 -156
- package/src/status-footer.js +0 -980
- package/src/sub-cluster-wrapper.js +0 -560
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -369
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -325
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -128
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -383
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -194
- package/task-lib/attachable-watcher.js +0 -272
- package/task-lib/claude-recovery.js +0 -119
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -411
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -98
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -10
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -144
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -230
- package/task-lib/tui/formatters.js +0 -166
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -119
- package/task-lib/tui.js +0 -384
- package/task-lib/watcher.js +0 -188
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentHookExecutor - Hook transformation and execution
|
|
3
|
-
*
|
|
4
|
-
* Provides:
|
|
5
|
-
* - Hook execution (publish_message, stop_cluster, etc.)
|
|
6
|
-
* - Template variable substitution
|
|
7
|
-
* - Transform script execution in VM sandbox
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const vm = require('vm');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Execute a hook
|
|
14
|
-
* THROWS on failure - no silent errors
|
|
15
|
-
* @param {Object} params - Hook execution parameters
|
|
16
|
-
* @param {Object} params.hook - Hook configuration
|
|
17
|
-
* @param {Object} params.agent - Agent instance
|
|
18
|
-
* @param {Object} params.message - Triggering message
|
|
19
|
-
* @param {Object} params.result - Agent execution result
|
|
20
|
-
* @param {Object} params.messageBus - Message bus instance
|
|
21
|
-
* @param {Object} params.cluster - Cluster object
|
|
22
|
-
* @param {Object} params.orchestrator - Orchestrator instance
|
|
23
|
-
* @returns {Promise<void>}
|
|
24
|
-
*/
|
|
25
|
-
async function executeHook(params) {
|
|
26
|
-
const { hook, agent, message, result, cluster } = params;
|
|
27
|
-
|
|
28
|
-
if (!hook) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Build context for hook execution
|
|
33
|
-
const context = {
|
|
34
|
-
result,
|
|
35
|
-
triggeringMessage: message,
|
|
36
|
-
agent,
|
|
37
|
-
cluster,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// NO try/catch - errors must propagate
|
|
41
|
-
if (hook.action === 'publish_message') {
|
|
42
|
-
let messageToPublish;
|
|
43
|
-
|
|
44
|
-
if (hook.transform) {
|
|
45
|
-
// NEW: Execute transform script to generate message
|
|
46
|
-
messageToPublish = await executeTransform({
|
|
47
|
-
transform: hook.transform,
|
|
48
|
-
context,
|
|
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
|
|
62
|
-
agent._publish(messageToPublish);
|
|
63
|
-
} else if (hook.action === 'execute_system_command') {
|
|
64
|
-
throw new Error('execute_system_command not implemented');
|
|
65
|
-
} else {
|
|
66
|
-
throw new Error(`Unknown hook action: ${hook.action}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Execute a hook transform script
|
|
72
|
-
* Transform scripts return the message to publish, with access to:
|
|
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;
|
|
85
|
-
|
|
86
|
-
if (engine !== 'javascript') {
|
|
87
|
-
throw new Error(`Unsupported transform engine: ${engine}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Parse result output if we have a result
|
|
91
|
-
// VALIDATION: Check if script uses result.* variables and fail early if no output
|
|
92
|
-
const scriptUsesResult = /\bresult\.[a-zA-Z]/.test(script);
|
|
93
|
-
let resultData = null;
|
|
94
|
-
|
|
95
|
-
if (context.result?.output) {
|
|
96
|
-
try {
|
|
97
|
-
resultData = await agent._parseResultOutput(context.result.output);
|
|
98
|
-
} catch (parseError) {
|
|
99
|
-
// FAIL FAST: Result parsing failed - don't continue with null data
|
|
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`);
|
|
109
|
-
throw new Error(
|
|
110
|
-
`Transform script cannot run: result parsing failed. ` +
|
|
111
|
-
`Agent: ${agent.id}, Error: ${parseError.message}`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// DEFENSIVE: Validate result has expected fields if script accesses them
|
|
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]);
|
|
118
|
-
const missingFields = accessedFields.filter((f) => resultData[f] === undefined);
|
|
119
|
-
if (missingFields.length > 0) {
|
|
120
|
-
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
|
-
throw new Error(
|
|
131
|
-
`Transform script accesses undefined fields: ${missingFields.join(', ')}. ` +
|
|
132
|
-
`Agent ${agent.id} (task ${taskId}) output missing required fields. ` +
|
|
133
|
-
`Check agent's jsonSchema and output format.`
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
} else if (scriptUsesResult) {
|
|
137
|
-
const taskId = context.result?.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
138
|
-
const outputLength = (context.result?.output || '').length;
|
|
139
|
-
throw new Error(
|
|
140
|
-
`Transform script uses result.* variables but no output was captured. ` +
|
|
141
|
-
`Agent: ${agent.id}, TaskID: ${taskId}, Iteration: ${agent.iteration}, ` +
|
|
142
|
-
`Output length: ${outputLength}. ` +
|
|
143
|
-
`Check that the task completed successfully and the get-log-path command exists.`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Helper functions exposed to transform scripts
|
|
148
|
-
const helpers = {
|
|
149
|
-
/**
|
|
150
|
-
* Get cluster config based on complexity and task type
|
|
151
|
-
* Returns: { base: 'template-name', params: { ... } }
|
|
152
|
-
*/
|
|
153
|
-
getConfig: require('../config-router').getConfig,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// Build sandbox context
|
|
157
|
-
const sandbox = {
|
|
158
|
-
result: resultData,
|
|
159
|
-
triggeringMessage: context.triggeringMessage,
|
|
160
|
-
helpers,
|
|
161
|
-
JSON,
|
|
162
|
-
console: {
|
|
163
|
-
log: (...args) => agent._log('[transform]', ...args),
|
|
164
|
-
error: (...args) => console.error('[transform]', ...args),
|
|
165
|
-
warn: (...args) => console.warn('[transform]', ...args),
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Execute in VM sandbox with timeout
|
|
170
|
-
const vmContext = vm.createContext(sandbox);
|
|
171
|
-
const wrappedScript = `(function() { ${script} })()`;
|
|
172
|
-
|
|
173
|
-
let result;
|
|
174
|
-
try {
|
|
175
|
-
result = vm.runInContext(wrappedScript, vmContext, { timeout: 5000 });
|
|
176
|
-
} catch (err) {
|
|
177
|
-
throw new Error(`Transform script error: ${err.message}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Validate result
|
|
181
|
-
if (!result || typeof result !== 'object') {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`Transform script must return an object with topic and content, got: ${typeof result}`
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
if (!result.topic) {
|
|
187
|
-
throw new Error(`Transform script result must have a 'topic' property`);
|
|
188
|
-
}
|
|
189
|
-
if (!result.content) {
|
|
190
|
-
throw new Error(`Transform script result must have a 'content' property`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// CRITICAL: Extra validation for CLUSTER_OPERATIONS - this is the make-or-break message
|
|
194
|
-
// If this message is malformed, the cluster will hang forever
|
|
195
|
-
if (result.topic === 'CLUSTER_OPERATIONS') {
|
|
196
|
-
const operations = result.content?.data?.operations;
|
|
197
|
-
if (!operations) {
|
|
198
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
199
|
-
console.error(`🔴 CLUSTER_OPERATIONS MALFORMED - MISSING OPERATIONS ARRAY`);
|
|
200
|
-
console.error(`${'='.repeat(80)}`);
|
|
201
|
-
console.error(`Agent: ${agent.id}`);
|
|
202
|
-
console.error(`Result: ${JSON.stringify(result, null, 2)}`);
|
|
203
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
204
|
-
throw new Error(
|
|
205
|
-
`CLUSTER_OPERATIONS message missing operations array. ` +
|
|
206
|
-
`Agent ${agent.id} transform script returned invalid structure.`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
if (!Array.isArray(operations)) {
|
|
210
|
-
throw new Error(`CLUSTER_OPERATIONS.operations must be an array, got: ${typeof operations}`);
|
|
211
|
-
}
|
|
212
|
-
if (operations.length === 0) {
|
|
213
|
-
throw new Error(`CLUSTER_OPERATIONS.operations is empty - no operations to execute`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Validate each operation has required 'action' field
|
|
217
|
-
for (let i = 0; i < operations.length; i++) {
|
|
218
|
-
const op = operations[i];
|
|
219
|
-
if (!op || !op.action) {
|
|
220
|
-
throw new Error(`CLUSTER_OPERATIONS.operations[${i}] missing required 'action' field`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
agent._log(`✅ CLUSTER_OPERATIONS validated: ${operations.length} operations`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Substitute template variables in hook config
|
|
232
|
-
* ONLY parses result output if result.* variables are used
|
|
233
|
-
* THROWS on any error - no silent failures
|
|
234
|
-
* @param {Object} params - Substitution parameters
|
|
235
|
-
* @param {Object} params.config - Hook configuration
|
|
236
|
-
* @param {Object} params.context - Execution context
|
|
237
|
-
* @param {Object} params.agent - Agent instance
|
|
238
|
-
* @param {Object} params.cluster - Cluster object
|
|
239
|
-
* @returns {Promise<Object>} Substituted configuration
|
|
240
|
-
*/
|
|
241
|
-
async function substituteTemplate(params) {
|
|
242
|
-
const { config, context, agent, cluster } = params;
|
|
243
|
-
|
|
244
|
-
if (!config) {
|
|
245
|
-
throw new Error('_substituteTemplate: config is required');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const json = JSON.stringify(config);
|
|
249
|
-
|
|
250
|
-
// Check if ANY result.* variables are used BEFORE parsing
|
|
251
|
-
// Generic pattern - no hardcoded field names, works with any agent config
|
|
252
|
-
const usesResultVars = /\{\{result\.[^}]+\}\}/.test(json);
|
|
253
|
-
|
|
254
|
-
let resultData = null;
|
|
255
|
-
if (usesResultVars) {
|
|
256
|
-
if (!context.result) {
|
|
257
|
-
throw new Error(
|
|
258
|
-
`Hook uses result.* variables but no result in context. ` +
|
|
259
|
-
`Agent: ${agent.id}, TaskID: ${agent.currentTaskId}, Iteration: ${agent.iteration}`
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
if (!context.result.output) {
|
|
263
|
-
// Log detailed context for debugging
|
|
264
|
-
const taskId = context.result.taskId || agent.currentTaskId || 'UNKNOWN';
|
|
265
|
-
console.error(`\n${'='.repeat(80)}`);
|
|
266
|
-
console.error(`🔴 HOOK FAILURE - EMPTY OUTPUT`);
|
|
267
|
-
console.error(`${'='.repeat(80)}`);
|
|
268
|
-
console.error(`Agent: ${agent.id}`);
|
|
269
|
-
console.error(`Task ID: ${taskId}`);
|
|
270
|
-
console.error(`Iteration: ${context.result.iteration || agent.iteration}`);
|
|
271
|
-
console.error(`Result success: ${context.result.success}`);
|
|
272
|
-
console.error(`Result error: ${context.result.error || 'none'}`);
|
|
273
|
-
console.error(`Output length: ${(context.result.output || '').length}`);
|
|
274
|
-
console.error(`Hook config: ${JSON.stringify(config, null, 2)}`);
|
|
275
|
-
|
|
276
|
-
// Auto-fetch and publish task logs for debugging
|
|
277
|
-
let taskLogs = 'Task logs unavailable';
|
|
278
|
-
if (taskId !== 'UNKNOWN') {
|
|
279
|
-
console.error(`\nFetching task logs for ${taskId}...`);
|
|
280
|
-
try {
|
|
281
|
-
const { execSync } = require('child_process');
|
|
282
|
-
const ctPath = agent._getClaudeTasksPath();
|
|
283
|
-
taskLogs = execSync(`${ctPath} logs ${taskId} --lines 100`, {
|
|
284
|
-
encoding: 'utf-8',
|
|
285
|
-
timeout: 5000,
|
|
286
|
-
maxBuffer: 1024 * 1024, // 1MB
|
|
287
|
-
}).trim();
|
|
288
|
-
console.error(`✓ Retrieved ${taskLogs.split('\n').length} lines of logs`);
|
|
289
|
-
} catch (err) {
|
|
290
|
-
taskLogs = `Failed to retrieve logs: ${err.message}`;
|
|
291
|
-
console.error(`✗ Failed to retrieve logs: ${err.message}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
console.error(`${'='.repeat(80)}\n`);
|
|
295
|
-
|
|
296
|
-
// Publish task logs to message bus for visibility in zeroshot logs
|
|
297
|
-
agent._publish({
|
|
298
|
-
topic: 'AGENT_ERROR',
|
|
299
|
-
receiver: 'broadcast',
|
|
300
|
-
content: {
|
|
301
|
-
text: `Task logs for ${taskId} (last 100 lines)`,
|
|
302
|
-
data: {
|
|
303
|
-
taskId,
|
|
304
|
-
logs: taskLogs,
|
|
305
|
-
logsPreview: taskLogs.split('\n').slice(-20).join('\n'), // Last 20 lines as preview
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
throw new Error(
|
|
311
|
-
`Hook uses result.* variables but result.output is empty. ` +
|
|
312
|
-
`Agent: ${agent.id}, TaskID: ${taskId}, ` +
|
|
313
|
-
`Iteration: ${context.result.iteration || agent.iteration}, ` +
|
|
314
|
-
`Success: ${context.result.success}. ` +
|
|
315
|
-
`Task logs posted to message bus.`
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
// Parse result output - WILL THROW if no JSON block
|
|
319
|
-
resultData = await agent._parseResultOutput(context.result.output);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Helper to escape a value for JSON string substitution
|
|
323
|
-
// Uses JSON.stringify for ALL escaping - no manual replace() calls
|
|
324
|
-
const escapeForJsonString = (value) => {
|
|
325
|
-
if (value === null || value === undefined) {
|
|
326
|
-
throw new Error(`Cannot escape null/undefined value for JSON`);
|
|
327
|
-
}
|
|
328
|
-
// JSON.stringify handles ALL escaping (newlines, quotes, backslashes, control chars)
|
|
329
|
-
// .slice(1, -1) strips the outer quotes it adds
|
|
330
|
-
// For arrays/objects: stringify twice - once for JSON, once to escape for string embedding
|
|
331
|
-
const stringified =
|
|
332
|
-
typeof value === 'string' ? JSON.stringify(value) : JSON.stringify(JSON.stringify(value));
|
|
333
|
-
return stringified.slice(1, -1);
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
let substituted = json
|
|
337
|
-
.replace(/\{\{cluster\.id\}\}/g, cluster.id)
|
|
338
|
-
.replace(/\{\{cluster\.createdAt\}\}/g, String(cluster.createdAt))
|
|
339
|
-
.replace(/\{\{iteration\}\}/g, String(agent.iteration))
|
|
340
|
-
.replace(/\{\{error\.message\}\}/g, escapeForJsonString(context.error?.message ?? ''))
|
|
341
|
-
.replace(/\{\{result\.output\}\}/g, escapeForJsonString(context.result?.output ?? ''));
|
|
342
|
-
|
|
343
|
-
// Substitute ALL result.* variables dynamically from parsed resultData
|
|
344
|
-
if (resultData) {
|
|
345
|
-
// Generic substitution - replace {{result.fieldName}} with resultData[fieldName]
|
|
346
|
-
// No hardcoded field names - works with any agent output schema
|
|
347
|
-
// CRITICAL: For booleans/nulls/numbers, we need to match and remove surrounding quotes
|
|
348
|
-
// to produce valid JSON (e.g., "{{result.approved}}" -> true, not "true")
|
|
349
|
-
substituted = substituted.replace(/"?\{\{result\.([^}]+)\}\}"?/g, (match, fieldName) => {
|
|
350
|
-
const value = resultData[fieldName];
|
|
351
|
-
if (value === undefined) {
|
|
352
|
-
// Missing fields should gracefully default to null or empty values
|
|
353
|
-
// This allows optional schema fields without hardcoding field names
|
|
354
|
-
// If a field is truly required, the schema validation will catch it
|
|
355
|
-
console.warn(
|
|
356
|
-
`⚠️ Agent ${agent.id}: Template variable {{result.${fieldName}}} not found in output. ` +
|
|
357
|
-
`If this field is required by the schema, the agent violated its own schema. ` +
|
|
358
|
-
`Defaulting to null. Agent output keys: ${Object.keys(resultData).join(', ')}`
|
|
359
|
-
);
|
|
360
|
-
return 'null';
|
|
361
|
-
}
|
|
362
|
-
// Booleans, numbers, and null should be unquoted JSON primitives
|
|
363
|
-
if (typeof value === 'boolean' || typeof value === 'number' || value === null) {
|
|
364
|
-
return String(value);
|
|
365
|
-
}
|
|
366
|
-
// Strings need to be quoted and escaped for JSON
|
|
367
|
-
return JSON.stringify(value);
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Check for unsubstituted KNOWN template variables only
|
|
372
|
-
// KNOWN patterns: {{cluster.X}}, {{iteration}}, {{error.X}}, {{result.X}}
|
|
373
|
-
// Content may contain arbitrary {{...}} patterns (React dangerouslySetInnerHTML, Mustache, etc.)
|
|
374
|
-
// Those are NOT template variables - they're just content that happens to contain braces
|
|
375
|
-
const KNOWN_TEMPLATE_PREFIXES = ['cluster', 'iteration', 'error', 'result'];
|
|
376
|
-
const knownVariablePattern = new RegExp(
|
|
377
|
-
`\\{\\{(${KNOWN_TEMPLATE_PREFIXES.join('|')})(\\.[a-zA-Z_][a-zA-Z0-9_]*)?\\}\\}`,
|
|
378
|
-
'g'
|
|
379
|
-
);
|
|
380
|
-
const remaining = substituted.match(knownVariablePattern);
|
|
381
|
-
if (remaining) {
|
|
382
|
-
throw new Error(`Unsubstituted template variables: ${remaining.join(', ')}`);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Parse and validate result
|
|
386
|
-
let result;
|
|
387
|
-
try {
|
|
388
|
-
result = JSON.parse(substituted);
|
|
389
|
-
} catch (e) {
|
|
390
|
-
console.error('JSON parse failed. Substituted string:');
|
|
391
|
-
console.error(substituted);
|
|
392
|
-
throw new Error(`Template substitution produced invalid JSON: ${e.message}`);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return result;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
module.exports = {
|
|
399
|
-
executeHook,
|
|
400
|
-
executeTransform,
|
|
401
|
-
substituteTemplate,
|
|
402
|
-
};
|