@covibes/zeroshot 1.0.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/CHANGELOG.md +167 -0
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/index.js +3990 -0
- package/cluster-templates/base-templates/debug-workflow.json +181 -0
- package/cluster-templates/base-templates/full-workflow.json +455 -0
- package/cluster-templates/base-templates/single-worker.json +48 -0
- package/cluster-templates/base-templates/worker-validator.json +131 -0
- package/cluster-templates/conductor-bootstrap.json +122 -0
- package/cluster-templates/conductor-junior-bootstrap.json +69 -0
- package/docker/zeroshot-cluster/Dockerfile +132 -0
- package/lib/completion.js +174 -0
- package/lib/id-detector.js +53 -0
- package/lib/settings.js +97 -0
- package/lib/stream-json-parser.js +236 -0
- package/package.json +121 -0
- package/src/agent/agent-config.js +121 -0
- package/src/agent/agent-context-builder.js +241 -0
- package/src/agent/agent-hook-executor.js +329 -0
- package/src/agent/agent-lifecycle.js +555 -0
- package/src/agent/agent-stuck-detector.js +256 -0
- package/src/agent/agent-task-executor.js +1034 -0
- package/src/agent/agent-trigger-evaluator.js +67 -0
- package/src/agent-wrapper.js +459 -0
- package/src/agents/git-pusher-agent.json +20 -0
- package/src/attach/attach-client.js +438 -0
- package/src/attach/attach-server.js +543 -0
- package/src/attach/index.js +35 -0
- package/src/attach/protocol.js +220 -0
- package/src/attach/ring-buffer.js +121 -0
- package/src/attach/socket-discovery.js +242 -0
- package/src/claude-task-runner.js +468 -0
- package/src/config-router.js +80 -0
- package/src/config-validator.js +598 -0
- package/src/github.js +103 -0
- package/src/isolation-manager.js +1042 -0
- package/src/ledger.js +429 -0
- package/src/logic-engine.js +223 -0
- package/src/message-bus-bridge.js +139 -0
- package/src/message-bus.js +202 -0
- package/src/name-generator.js +232 -0
- package/src/orchestrator.js +1938 -0
- package/src/schemas/sub-cluster.js +156 -0
- package/src/sub-cluster-wrapper.js +545 -0
- package/src/task-runner.js +28 -0
- package/src/template-resolver.js +347 -0
- package/src/tui/CHANGES.txt +133 -0
- package/src/tui/LAYOUT.md +261 -0
- package/src/tui/README.txt +192 -0
- package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
- package/src/tui/data-poller.js +325 -0
- package/src/tui/demo.js +208 -0
- package/src/tui/formatters.js +123 -0
- package/src/tui/index.js +193 -0
- package/src/tui/keybindings.js +383 -0
- package/src/tui/layout.js +317 -0
- package/src/tui/renderer.js +194 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream JSON Parser for Claude Code output
|
|
3
|
+
*
|
|
4
|
+
* Parses NDJSON (newline-delimited JSON) streaming output from Claude Code.
|
|
5
|
+
* Extracts: text output, tool calls, tool results, thinking, errors.
|
|
6
|
+
*
|
|
7
|
+
* Event types from Claude Code stream-json format:
|
|
8
|
+
* - system: Session initialization
|
|
9
|
+
* - stream_event: Real-time streaming (content_block_start, content_block_delta, etc.)
|
|
10
|
+
* - assistant: Complete assistant message with content array
|
|
11
|
+
* - user: Tool results
|
|
12
|
+
* - result: Final task result
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse a single JSON line and extract displayable content
|
|
17
|
+
* @param {string} line - Single line of NDJSON
|
|
18
|
+
* @returns {Object|null} Parsed event with type and content, or null if not displayable
|
|
19
|
+
*/
|
|
20
|
+
function parseStreamLine(line) {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (!trimmed || !trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let event;
|
|
27
|
+
try {
|
|
28
|
+
event = JSON.parse(trimmed);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// stream_event - real-time streaming updates
|
|
34
|
+
if (event.type === 'stream_event' && event.event) {
|
|
35
|
+
return parseStreamEvent(event.event);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// assistant - complete message with content blocks
|
|
39
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
40
|
+
return parseAssistantMessage(event.message);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// user - tool result
|
|
44
|
+
if (event.type === 'user' && event.message?.content) {
|
|
45
|
+
return parseUserMessage(event.message);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// result - final task result
|
|
49
|
+
if (event.type === 'result') {
|
|
50
|
+
return {
|
|
51
|
+
type: 'result',
|
|
52
|
+
success: event.subtype === 'success',
|
|
53
|
+
result: event.result,
|
|
54
|
+
error: event.is_error ? event.result : null,
|
|
55
|
+
cost: event.total_cost_usd,
|
|
56
|
+
duration: event.duration_ms,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// system - session init (skip, not user-facing)
|
|
61
|
+
if (event.type === 'system') {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse stream_event inner event
|
|
70
|
+
*/
|
|
71
|
+
function parseStreamEvent(inner) {
|
|
72
|
+
// content_block_start - tool use or text block starting
|
|
73
|
+
if (inner.type === 'content_block_start' && inner.content_block) {
|
|
74
|
+
const block = inner.content_block;
|
|
75
|
+
|
|
76
|
+
if (block.type === 'tool_use') {
|
|
77
|
+
return {
|
|
78
|
+
type: 'tool_start',
|
|
79
|
+
toolName: block.name,
|
|
80
|
+
toolId: block.id,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (block.type === 'thinking') {
|
|
85
|
+
return {
|
|
86
|
+
type: 'thinking_start',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// text block start - usually empty, skip
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// content_block_delta - incremental content
|
|
95
|
+
if (inner.type === 'content_block_delta' && inner.delta) {
|
|
96
|
+
const delta = inner.delta;
|
|
97
|
+
|
|
98
|
+
if (delta.type === 'text_delta' && delta.text) {
|
|
99
|
+
return {
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: delta.text,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (delta.type === 'thinking_delta' && delta.thinking) {
|
|
106
|
+
return {
|
|
107
|
+
type: 'thinking',
|
|
108
|
+
text: delta.thinking,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (delta.type === 'input_json_delta' && delta.partial_json) {
|
|
113
|
+
return {
|
|
114
|
+
type: 'tool_input',
|
|
115
|
+
json: delta.partial_json,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// content_block_stop - block ended
|
|
121
|
+
if (inner.type === 'content_block_stop') {
|
|
122
|
+
return {
|
|
123
|
+
type: 'block_end',
|
|
124
|
+
index: inner.index,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// message_start - skip text extraction here since it will come via text_delta events
|
|
129
|
+
// (extracting here causes duplicate text output)
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse assistant message content blocks
|
|
136
|
+
* NOTE: Skip text blocks here since they were already streamed via text_delta events.
|
|
137
|
+
* Extracting text here would cause duplicate output.
|
|
138
|
+
*/
|
|
139
|
+
function parseAssistantMessage(message) {
|
|
140
|
+
const results = [];
|
|
141
|
+
|
|
142
|
+
for (const block of message.content) {
|
|
143
|
+
// Skip text blocks - already streamed via text_delta events
|
|
144
|
+
// Extracting here causes duplicate output
|
|
145
|
+
|
|
146
|
+
if (block.type === 'tool_use') {
|
|
147
|
+
results.push({
|
|
148
|
+
type: 'tool_call',
|
|
149
|
+
toolName: block.name,
|
|
150
|
+
toolId: block.id,
|
|
151
|
+
input: block.input,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (block.type === 'thinking' && block.thinking) {
|
|
156
|
+
// Skip thinking blocks too - already streamed via thinking_delta
|
|
157
|
+
// But keep for non-streaming contexts (direct API responses)
|
|
158
|
+
// Only emit if we haven't seen streaming deltas (detected by having results already)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (results.length === 1) {
|
|
163
|
+
return results[0];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (results.length > 1) {
|
|
167
|
+
return {
|
|
168
|
+
type: 'multi',
|
|
169
|
+
events: results,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Parse user message (tool results)
|
|
178
|
+
*/
|
|
179
|
+
function parseUserMessage(message) {
|
|
180
|
+
const results = [];
|
|
181
|
+
|
|
182
|
+
for (const block of message.content) {
|
|
183
|
+
if (block.type === 'tool_result') {
|
|
184
|
+
results.push({
|
|
185
|
+
type: 'tool_result',
|
|
186
|
+
toolId: block.tool_use_id,
|
|
187
|
+
content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
|
|
188
|
+
isError: block.is_error || false,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (results.length === 1) {
|
|
194
|
+
return results[0];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (results.length > 1) {
|
|
198
|
+
return {
|
|
199
|
+
type: 'multi',
|
|
200
|
+
events: results,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Parse multiple lines of NDJSON
|
|
209
|
+
* @param {string} chunk - Chunk of text potentially containing multiple JSON lines
|
|
210
|
+
* @returns {Array} Array of parsed events
|
|
211
|
+
*/
|
|
212
|
+
function parseChunk(chunk) {
|
|
213
|
+
const events = [];
|
|
214
|
+
const lines = chunk.split('\n');
|
|
215
|
+
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
const event = parseStreamLine(line);
|
|
218
|
+
if (event) {
|
|
219
|
+
if (event.type === 'multi') {
|
|
220
|
+
events.push(...event.events);
|
|
221
|
+
} else {
|
|
222
|
+
events.push(event);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return events;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
parseStreamLine,
|
|
232
|
+
parseChunk,
|
|
233
|
+
parseStreamEvent,
|
|
234
|
+
parseAssistantMessage,
|
|
235
|
+
parseUserMessage,
|
|
236
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@covibes/zeroshot",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Multi-agent orchestration engine for Claude - cluster coordinator and CLI",
|
|
5
|
+
"main": "src/orchestrator.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"zeroshot": "./cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"directories": {
|
|
10
|
+
"example": "examples",
|
|
11
|
+
"test": "tests"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "mocha 'tests/**/*.test.js'",
|
|
15
|
+
"test:coverage": "c8 npm test",
|
|
16
|
+
"test:coverage:report": "c8 --reporter=html npm test && echo 'Coverage report generated at coverage/index.html'",
|
|
17
|
+
"start": "node cli/index.js",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"lint:fix": "eslint . --fix",
|
|
21
|
+
"format": "prettier --write .",
|
|
22
|
+
"format:check": "prettier --check .",
|
|
23
|
+
"deadcode": "ts-prune --skip node_modules",
|
|
24
|
+
"deadcode:files": "unimported",
|
|
25
|
+
"deadcode:deps": "depcheck",
|
|
26
|
+
"deadcode:all": "npm run deadcode && npm run deadcode:files && npm run deadcode:deps",
|
|
27
|
+
"check": "npm run typecheck && npm run lint",
|
|
28
|
+
"check:all": "npm run check && npm run deadcode:all",
|
|
29
|
+
"release": "semantic-release",
|
|
30
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm test"
|
|
31
|
+
},
|
|
32
|
+
"c8": {
|
|
33
|
+
"reporter": [
|
|
34
|
+
"text",
|
|
35
|
+
"lcov",
|
|
36
|
+
"html"
|
|
37
|
+
],
|
|
38
|
+
"include": [
|
|
39
|
+
"src/**/*.js",
|
|
40
|
+
"lib/**/*.js",
|
|
41
|
+
"cli/**/*.js"
|
|
42
|
+
],
|
|
43
|
+
"exclude": [
|
|
44
|
+
"**/*.test.js",
|
|
45
|
+
"**/tests/**"
|
|
46
|
+
],
|
|
47
|
+
"all": true
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"ai",
|
|
51
|
+
"agents",
|
|
52
|
+
"multi-agent",
|
|
53
|
+
"orchestration",
|
|
54
|
+
"claude",
|
|
55
|
+
"automation"
|
|
56
|
+
],
|
|
57
|
+
"author": "Covibes",
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"homepage": "https://github.com/covibes/zeroshot",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/covibes/zeroshot.git"
|
|
63
|
+
},
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/covibes/zeroshot/issues"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public",
|
|
72
|
+
"registry": "https://registry.npmjs.org/"
|
|
73
|
+
},
|
|
74
|
+
"files": [
|
|
75
|
+
"src/",
|
|
76
|
+
"lib/",
|
|
77
|
+
"cli/",
|
|
78
|
+
"cluster-templates/",
|
|
79
|
+
"hooks/",
|
|
80
|
+
"docker/",
|
|
81
|
+
"README.md",
|
|
82
|
+
"LICENSE",
|
|
83
|
+
"CHANGELOG.md"
|
|
84
|
+
],
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"ajv": "^8.17.1",
|
|
87
|
+
"ansi-to-html": "^0.7.2",
|
|
88
|
+
"better-sqlite3": "^12.5.0",
|
|
89
|
+
"blessed": "^0.1.81",
|
|
90
|
+
"blessed-contrib": "^4.11.0",
|
|
91
|
+
"chalk": "^4.1.2",
|
|
92
|
+
"commander": "^14.0.2",
|
|
93
|
+
"md-to-pdf": "^5.2.5",
|
|
94
|
+
"node-pty": "^1.0.0",
|
|
95
|
+
"omelette": "^0.4.17",
|
|
96
|
+
"pidusage": "^4.0.1",
|
|
97
|
+
"proper-lockfile": "^4.1.2"
|
|
98
|
+
},
|
|
99
|
+
"devDependencies": {
|
|
100
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
101
|
+
"@semantic-release/git": "^10.0.1",
|
|
102
|
+
"@semantic-release/github": "^11.0.6",
|
|
103
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
104
|
+
"@types/node": "^25.0.3",
|
|
105
|
+
"c8": "^10.1.3",
|
|
106
|
+
"chai": "^6.2.1",
|
|
107
|
+
"depcheck": "^1.4.7",
|
|
108
|
+
"eslint": "^9.39.1",
|
|
109
|
+
"eslint-config-prettier": "^10.1.8",
|
|
110
|
+
"eslint-plugin-unused-imports": "^4.3.0",
|
|
111
|
+
"mocha": "^11.7.5",
|
|
112
|
+
"semantic-release": "^25.0.2",
|
|
113
|
+
"sinon": "^21.0.0",
|
|
114
|
+
"ts-prune": "^0.10.3",
|
|
115
|
+
"typescript": "^5.9.3",
|
|
116
|
+
"unimported": "^1.31.0"
|
|
117
|
+
},
|
|
118
|
+
"overrides": {
|
|
119
|
+
"xml2js": "^0.5.0"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentConfig - Agent configuration validation and defaults
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Config validation and normalization
|
|
6
|
+
* - Default values for optional fields
|
|
7
|
+
* - Model configuration setup
|
|
8
|
+
* - Safety checks for test mode
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Default max iterations (high limit - let the user decide when to give up)
|
|
12
|
+
const DEFAULT_MAX_ITERATIONS = 100;
|
|
13
|
+
|
|
14
|
+
// Task timeout - DISABLED (tasks run until completion or explicit kill)
|
|
15
|
+
// Originally: 2 hours - caused premature termination of long-running tasks
|
|
16
|
+
// Now: Infinity - tasks only stop on completion, explicit kill, or external error
|
|
17
|
+
const DEFAULT_TASK_TIMEOUT_MS = Infinity;
|
|
18
|
+
|
|
19
|
+
// Stale detection - ENABLED by default using multi-indicator analysis (safe from false positives)
|
|
20
|
+
// Multi-indicator approach checks: process state, CPU usage, context switches, network I/O
|
|
21
|
+
// Only flags as stuck when ALL indicators show inactivity (score >= 3.5)
|
|
22
|
+
// Single-indicator detection (just output freshness) was too risky - this is safe.
|
|
23
|
+
const DEFAULT_STALE_DURATION_MS = 30 * 60 * 1000; // 30 minutes before triggering analysis
|
|
24
|
+
const DEFAULT_LIVENESS_CHECK_ENABLED = true; // Safe with multi-indicator detection
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate and normalize agent configuration
|
|
28
|
+
* @param {Object} config - Raw agent configuration
|
|
29
|
+
* @param {Object} options - Agent wrapper options
|
|
30
|
+
* @returns {Object} Normalized configuration
|
|
31
|
+
*/
|
|
32
|
+
function validateAgentConfig(config, options = {}) {
|
|
33
|
+
// CRITICAL: Enforce JSON schema output by default to prevent parse failures and crashes
|
|
34
|
+
// Agents MUST return structured output so hooks can safely use {{result.*}} templates
|
|
35
|
+
if (!config.outputFormat) {
|
|
36
|
+
config.outputFormat = 'json';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// If outputFormat is json but no schema defined, use a minimal default schema
|
|
40
|
+
// This prevents uncaught exceptions when parsing agent output
|
|
41
|
+
if (config.outputFormat === 'json' && !config.jsonSchema) {
|
|
42
|
+
config.jsonSchema = {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
summary: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Brief summary of what was done',
|
|
48
|
+
},
|
|
49
|
+
result: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Detailed result or output',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['summary', 'result'],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Model configuration: support both static model and dynamic rules
|
|
59
|
+
let modelConfig;
|
|
60
|
+
if (config.modelRules) {
|
|
61
|
+
modelConfig = { type: 'rules', rules: config.modelRules };
|
|
62
|
+
} else {
|
|
63
|
+
modelConfig = { type: 'static', model: config.model || 'sonnet' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Prompt configuration: support static prompt OR iteration-based rules
|
|
67
|
+
// Formats:
|
|
68
|
+
// prompt: "string" -> static
|
|
69
|
+
// prompt: { system: "string" } -> static
|
|
70
|
+
// prompt: { initial: "...", subsequent: "..." } -> iteration 1 vs 2+
|
|
71
|
+
// prompt: { iterations: [...] } -> full control
|
|
72
|
+
let promptConfig = null;
|
|
73
|
+
if (config.prompt?.iterations) {
|
|
74
|
+
promptConfig = { type: 'rules', rules: config.prompt.iterations };
|
|
75
|
+
} else if (config.prompt?.initial || config.prompt?.subsequent) {
|
|
76
|
+
const rules = [];
|
|
77
|
+
if (config.prompt.initial) rules.push({ match: '1', system: config.prompt.initial });
|
|
78
|
+
if (config.prompt.subsequent) rules.push({ match: '2+', system: config.prompt.subsequent });
|
|
79
|
+
promptConfig = { type: 'rules', rules };
|
|
80
|
+
} else if (typeof config.prompt === 'string') {
|
|
81
|
+
promptConfig = { type: 'static', system: config.prompt };
|
|
82
|
+
} else if (config.prompt?.system) {
|
|
83
|
+
promptConfig = { type: 'static', system: config.prompt.system };
|
|
84
|
+
} else if (config.prompt) {
|
|
85
|
+
throw new Error(`Agent "${config.id}": invalid prompt format`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build normalized config
|
|
89
|
+
const normalizedConfig = {
|
|
90
|
+
...config,
|
|
91
|
+
modelConfig,
|
|
92
|
+
promptConfig,
|
|
93
|
+
maxIterations: config.maxIterations || DEFAULT_MAX_ITERATIONS,
|
|
94
|
+
timeout: config.timeout || DEFAULT_TASK_TIMEOUT_MS,
|
|
95
|
+
staleDuration: config.staleDuration || DEFAULT_STALE_DURATION_MS,
|
|
96
|
+
enableLivenessCheck: config.enableLivenessCheck ?? DEFAULT_LIVENESS_CHECK_ENABLED, // On by default, opt-out with false
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// SAFETY: In test mode, verify mock is provided for agents that execute tasks
|
|
100
|
+
// Check if this agent executes tasks (vs orchestrator agents that only publish messages)
|
|
101
|
+
const executesTask = config.triggers?.some(
|
|
102
|
+
(trigger) => !trigger.action || trigger.action === 'execute_task'
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (options.testMode && !options.mockSpawnFn && executesTask) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`AgentWrapper: testMode=true but no mockSpawnFn provided for agent '${config.id}'. ` +
|
|
108
|
+
`This would cause real Claude API calls. ABORTING.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return normalizedConfig;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
validateAgentConfig,
|
|
117
|
+
DEFAULT_MAX_ITERATIONS,
|
|
118
|
+
DEFAULT_TASK_TIMEOUT_MS,
|
|
119
|
+
DEFAULT_STALE_DURATION_MS,
|
|
120
|
+
DEFAULT_LIVENESS_CHECK_ENABLED,
|
|
121
|
+
};
|