@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
package/scripts/record-demo.sh
DELETED
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# record-demo.sh - Create ephemeral project, run zeroshot demo, record it, cleanup
|
|
3
|
-
#
|
|
4
|
-
# Usage:
|
|
5
|
-
# ./scripts/record-demo.sh # Interactive mode (you run zeroshot manually)
|
|
6
|
-
# ./scripts/record-demo.sh --record # Record with asciinema automatically
|
|
7
|
-
#
|
|
8
|
-
# Output: zeroshot-demo.cast (asciinema recording)
|
|
9
|
-
# Convert to gif: agg zeroshot-demo.cast zeroshot-demo.gif --idle-time-limit 2
|
|
10
|
-
|
|
11
|
-
set -e
|
|
12
|
-
|
|
13
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
15
|
-
DEMO_DIR=""
|
|
16
|
-
RECORD_MODE=false
|
|
17
|
-
FORCE_MODE=false
|
|
18
|
-
LOCK_FILE="/tmp/zeroshot-demo-recording.lock"
|
|
19
|
-
CAST_FILE="$REPO_ROOT/zeroshot-demo.cast"
|
|
20
|
-
|
|
21
|
-
# Check for existing recording session
|
|
22
|
-
check_existing_session() {
|
|
23
|
-
# Check lock file
|
|
24
|
-
if [[ -f "$LOCK_FILE" ]]; then
|
|
25
|
-
local old_pid
|
|
26
|
-
old_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
|
|
27
|
-
if [[ -n "$old_pid" ]] && kill -0 "$old_pid" 2>/dev/null; then
|
|
28
|
-
echo "ERROR: Recording already in progress (PID $old_pid)"
|
|
29
|
-
echo "Kill it with: kill $old_pid"
|
|
30
|
-
exit 1
|
|
31
|
-
fi
|
|
32
|
-
# Stale lock file
|
|
33
|
-
rm -f "$LOCK_FILE"
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# Check for any asciinema processes recording to our file
|
|
37
|
-
local existing_asciinema
|
|
38
|
-
existing_asciinema=$(pgrep -f "asciinema.*zeroshot-demo.cast" 2>/dev/null || true)
|
|
39
|
-
if [[ -n "$existing_asciinema" ]]; then
|
|
40
|
-
echo "ERROR: Existing asciinema process(es) found: $existing_asciinema"
|
|
41
|
-
echo "Kill them with: pkill -f 'asciinema.*zeroshot-demo.cast'"
|
|
42
|
-
exit 1
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# Check for any running zeroshot clusters
|
|
46
|
-
local existing_zeroshot
|
|
47
|
-
existing_zeroshot=$(pgrep -f "zeroshot.*rate limiting" 2>/dev/null || true)
|
|
48
|
-
if [[ -n "$existing_zeroshot" ]]; then
|
|
49
|
-
echo "ERROR: Existing zeroshot process(es) found: $existing_zeroshot"
|
|
50
|
-
echo "Kill them with: pkill -f 'zeroshot.*rate limiting'"
|
|
51
|
-
exit 1
|
|
52
|
-
fi
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# Parse args
|
|
56
|
-
while [[ $# -gt 0 ]]; do
|
|
57
|
-
case "$1" in
|
|
58
|
-
--record) RECORD_MODE=true; shift ;;
|
|
59
|
-
--force|-f) FORCE_MODE=true; shift ;;
|
|
60
|
-
*) echo "Unknown option: $1"; exit 1 ;;
|
|
61
|
-
esac
|
|
62
|
-
done
|
|
63
|
-
|
|
64
|
-
# Cleanup on exit
|
|
65
|
-
cleanup() {
|
|
66
|
-
# Remove lock file
|
|
67
|
-
rm -f "$LOCK_FILE"
|
|
68
|
-
|
|
69
|
-
if [[ -n "$DEMO_DIR" && -d "$DEMO_DIR" ]]; then
|
|
70
|
-
echo ""
|
|
71
|
-
echo "Cleaning up $DEMO_DIR..."
|
|
72
|
-
rm -rf "$DEMO_DIR"
|
|
73
|
-
echo "Done."
|
|
74
|
-
fi
|
|
75
|
-
}
|
|
76
|
-
trap cleanup EXIT
|
|
77
|
-
|
|
78
|
-
# Check for conflicts before doing anything
|
|
79
|
-
check_existing_session
|
|
80
|
-
|
|
81
|
-
# Create temp project
|
|
82
|
-
DEMO_DIR=$(mktemp -d -t zeroshot-demo-XXXXXX)
|
|
83
|
-
echo "Creating demo project in $DEMO_DIR"
|
|
84
|
-
|
|
85
|
-
cd "$DEMO_DIR"
|
|
86
|
-
|
|
87
|
-
# Initialize package.json
|
|
88
|
-
cat > package.json << 'EOF'
|
|
89
|
-
{
|
|
90
|
-
"name": "demo-api",
|
|
91
|
-
"version": "1.0.0",
|
|
92
|
-
"type": "commonjs",
|
|
93
|
-
"scripts": {
|
|
94
|
-
"dev": "ts-node src/index.ts",
|
|
95
|
-
"build": "tsc",
|
|
96
|
-
"start": "node dist/index.js"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
EOF
|
|
100
|
-
|
|
101
|
-
# Install dependencies (quiet)
|
|
102
|
-
echo "Installing dependencies..."
|
|
103
|
-
npm install --silent express typescript ts-node @types/express @types/node
|
|
104
|
-
|
|
105
|
-
# Create tsconfig
|
|
106
|
-
cat > tsconfig.json << 'EOF'
|
|
107
|
-
{
|
|
108
|
-
"compilerOptions": {
|
|
109
|
-
"target": "ES2020",
|
|
110
|
-
"module": "commonjs",
|
|
111
|
-
"strict": true,
|
|
112
|
-
"esModuleInterop": true,
|
|
113
|
-
"outDir": "dist",
|
|
114
|
-
"rootDir": "src"
|
|
115
|
-
},
|
|
116
|
-
"include": ["src/**/*"]
|
|
117
|
-
}
|
|
118
|
-
EOF
|
|
119
|
-
|
|
120
|
-
# Create minimal server with users "database"
|
|
121
|
-
mkdir -p src
|
|
122
|
-
|
|
123
|
-
cat > src/db.ts << 'EOF'
|
|
124
|
-
// Simple in-memory database
|
|
125
|
-
export interface User {
|
|
126
|
-
id: number;
|
|
127
|
-
username: string;
|
|
128
|
-
email: string;
|
|
129
|
-
password_hash: string;
|
|
130
|
-
avatar: string;
|
|
131
|
-
created_at: Date;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export const users: User[] = [
|
|
135
|
-
{
|
|
136
|
-
id: 1,
|
|
137
|
-
username: "alice",
|
|
138
|
-
email: "alice@example.com",
|
|
139
|
-
password_hash: "$2b$10$X7VYKzPQ...",
|
|
140
|
-
avatar: "https://example.com/alice.jpg",
|
|
141
|
-
created_at: new Date("2024-01-15"),
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
id: 2,
|
|
145
|
-
username: "bob",
|
|
146
|
-
email: "bob@example.com",
|
|
147
|
-
password_hash: "$2b$10$Y8WZLaQR...",
|
|
148
|
-
avatar: "https://example.com/bob.jpg",
|
|
149
|
-
created_at: new Date("2024-02-20"),
|
|
150
|
-
},
|
|
151
|
-
];
|
|
152
|
-
EOF
|
|
153
|
-
|
|
154
|
-
cat > src/index.ts << 'EOF'
|
|
155
|
-
import express from "express";
|
|
156
|
-
|
|
157
|
-
const app = express();
|
|
158
|
-
const PORT = 3000;
|
|
159
|
-
|
|
160
|
-
app.use(express.json());
|
|
161
|
-
|
|
162
|
-
// Health check
|
|
163
|
-
app.get("/health", (_, res) => {
|
|
164
|
-
res.json({ status: "ok" });
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
app.listen(PORT, () => {
|
|
168
|
-
console.log(`Server running on http://localhost:${PORT}`);
|
|
169
|
-
});
|
|
170
|
-
EOF
|
|
171
|
-
|
|
172
|
-
# Initialize git repo
|
|
173
|
-
git init --quiet
|
|
174
|
-
git add -A
|
|
175
|
-
git commit --quiet -m "Initial commit: Express API with users database"
|
|
176
|
-
|
|
177
|
-
echo ""
|
|
178
|
-
echo "=========================================="
|
|
179
|
-
echo "Demo project ready!"
|
|
180
|
-
echo "=========================================="
|
|
181
|
-
echo ""
|
|
182
|
-
echo "Directory: $DEMO_DIR"
|
|
183
|
-
echo ""
|
|
184
|
-
echo "Suggested demo task:"
|
|
185
|
-
echo " zeroshot 'Add PUT /users/:id endpoint to update user profile'"
|
|
186
|
-
echo ""
|
|
187
|
-
|
|
188
|
-
if [[ "$RECORD_MODE" == "true" ]]; then
|
|
189
|
-
echo "Recording with asciinema..."
|
|
190
|
-
echo ""
|
|
191
|
-
|
|
192
|
-
# Check asciinema is installed
|
|
193
|
-
if ! command -v asciinema &> /dev/null; then
|
|
194
|
-
echo "Error: asciinema not installed. Run: pip install asciinema"
|
|
195
|
-
exit 1
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
# Warn if cast file exists (skip with --force)
|
|
199
|
-
if [[ -f "$CAST_FILE" ]] && [[ "$FORCE_MODE" != "true" ]]; then
|
|
200
|
-
echo "WARNING: $CAST_FILE already exists!"
|
|
201
|
-
echo "Previous recording will be OVERWRITTEN."
|
|
202
|
-
echo ""
|
|
203
|
-
read -p "Continue? [y/N] " -n 1 -r
|
|
204
|
-
echo ""
|
|
205
|
-
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
206
|
-
echo "Aborted."
|
|
207
|
-
exit 1
|
|
208
|
-
fi
|
|
209
|
-
fi
|
|
210
|
-
|
|
211
|
-
# Create lock file with our PID
|
|
212
|
-
echo $$ > "$LOCK_FILE"
|
|
213
|
-
|
|
214
|
-
# Record the session
|
|
215
|
-
# CRITICAL: Use bash -l -c to get full PATH (includes npm global binaries)
|
|
216
|
-
ZEROSHOT_PATH=$(which zeroshot)
|
|
217
|
-
TASK="Add rate limiting middleware: sliding window algorithm (not fixed window), per-IP tracking with in-memory store and automatic TTL cleanup to prevent memory leaks, configurable limits per endpoint. Return 429 Too Many Requests with Retry-After header (seconds until reset) and X-RateLimit-Remaining header on ALL responses. Must handle both IPv4 and IPv6, normalizing IPv6 to consistent format."
|
|
218
|
-
|
|
219
|
-
# SIGNAL ISOLATION: Use setsid to create new session, completely immune to terminal signals
|
|
220
|
-
# The recording process will NOT be killed by Ctrl+C or signals to this script
|
|
221
|
-
# We wait for it explicitly and only cleanup AFTER it naturally completes
|
|
222
|
-
echo "Starting recording in isolated session (immune to Ctrl+C)..."
|
|
223
|
-
echo "To kill it manually: pkill -f 'asciinema.*zeroshot-demo.cast'"
|
|
224
|
-
echo ""
|
|
225
|
-
|
|
226
|
-
# Disable cleanup trap during recording - we'll handle it manually
|
|
227
|
-
trap - EXIT
|
|
228
|
-
|
|
229
|
-
# Start asciinema in new session (setsid) so it's immune to our signals
|
|
230
|
-
# Save the session leader PID so we can wait for it
|
|
231
|
-
setsid bash -c "
|
|
232
|
-
# Ignore all signals - this recording WILL NOT DIE
|
|
233
|
-
trap '' INT TERM HUP QUIT
|
|
234
|
-
|
|
235
|
-
cd '$DEMO_DIR'
|
|
236
|
-
asciinema rec \
|
|
237
|
-
--overwrite \
|
|
238
|
-
--title 'Zeroshot Demo' \
|
|
239
|
-
--command \"bash -l -c '$ZEROSHOT_PATH \\\"$TASK\\\"'\" \
|
|
240
|
-
'$CAST_FILE'
|
|
241
|
-
" &
|
|
242
|
-
RECORDING_PID=$!
|
|
243
|
-
|
|
244
|
-
# Update lock file with the actual recording PID
|
|
245
|
-
echo $RECORDING_PID > "$LOCK_FILE"
|
|
246
|
-
|
|
247
|
-
echo "Recording started (session PID: $RECORDING_PID)"
|
|
248
|
-
echo "Waiting for recording to complete..."
|
|
249
|
-
echo ""
|
|
250
|
-
|
|
251
|
-
# Wait for recording to finish - this script will NOT kill it on Ctrl+C
|
|
252
|
-
# Ignore signals while waiting
|
|
253
|
-
trap '' INT TERM HUP
|
|
254
|
-
wait $RECORDING_PID 2>/dev/null || true
|
|
255
|
-
|
|
256
|
-
# Recording finished naturally - now cleanup
|
|
257
|
-
echo ""
|
|
258
|
-
echo "Recording saved to: $CAST_FILE"
|
|
259
|
-
echo ""
|
|
260
|
-
echo "Convert to gif with:"
|
|
261
|
-
echo " agg $CAST_FILE $REPO_ROOT/zeroshot-demo.gif --idle-time-limit 2"
|
|
262
|
-
|
|
263
|
-
# Now do cleanup
|
|
264
|
-
rm -f "$LOCK_FILE"
|
|
265
|
-
if [[ -n "$DEMO_DIR" && -d "$DEMO_DIR" ]]; then
|
|
266
|
-
echo ""
|
|
267
|
-
echo "Cleaning up $DEMO_DIR..."
|
|
268
|
-
rm -rf "$DEMO_DIR"
|
|
269
|
-
echo "Done."
|
|
270
|
-
fi
|
|
271
|
-
else
|
|
272
|
-
echo "Interactive mode - run zeroshot manually:"
|
|
273
|
-
echo ""
|
|
274
|
-
echo " cd $DEMO_DIR"
|
|
275
|
-
echo " zeroshot 'Add PUT /users/:id endpoint to update user profile'"
|
|
276
|
-
echo ""
|
|
277
|
-
echo "Press Enter when done to cleanup..."
|
|
278
|
-
read -r
|
|
279
|
-
fi
|
package/scripts/test-install.sh
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test zeroshot installation on any platform
|
|
3
|
-
# Run: ./scripts/test-install.sh
|
|
4
|
-
|
|
5
|
-
set -e
|
|
6
|
-
|
|
7
|
-
echo "=== Zeroshot Install Test ==="
|
|
8
|
-
echo "Platform: $(uname -s) $(uname -m)"
|
|
9
|
-
echo "Node: $(node --version)"
|
|
10
|
-
echo "npm: $(npm --version)"
|
|
11
|
-
echo ""
|
|
12
|
-
|
|
13
|
-
echo "1. Installing dependencies..."
|
|
14
|
-
npm install
|
|
15
|
-
|
|
16
|
-
echo ""
|
|
17
|
-
echo "2. Linking CLI..."
|
|
18
|
-
npm link
|
|
19
|
-
|
|
20
|
-
echo ""
|
|
21
|
-
echo "3. Testing CLI commands..."
|
|
22
|
-
|
|
23
|
-
echo " zeroshot --version"
|
|
24
|
-
zeroshot --version
|
|
25
|
-
|
|
26
|
-
echo ""
|
|
27
|
-
echo " zeroshot --help"
|
|
28
|
-
zeroshot --help | head -20
|
|
29
|
-
|
|
30
|
-
echo ""
|
|
31
|
-
echo " zeroshot list"
|
|
32
|
-
zeroshot list 2>/dev/null || echo " (no clusters yet - this is expected)"
|
|
33
|
-
|
|
34
|
-
echo ""
|
|
35
|
-
echo " zeroshot config list"
|
|
36
|
-
zeroshot config list
|
|
37
|
-
|
|
38
|
-
echo ""
|
|
39
|
-
echo "=== ALL TESTS PASSED ==="
|
|
40
|
-
echo "Zeroshot installed successfully on $(uname -s)!"
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Validate all cluster templates for config errors
|
|
4
|
-
* Run in CI to prevent broken templates from being merged
|
|
5
|
-
*
|
|
6
|
-
* Usage: node scripts/validate-templates.js
|
|
7
|
-
* Exit codes: 0 = all valid, 1 = validation errors found
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { validateConfig } = require('../src/config-validator');
|
|
13
|
-
|
|
14
|
-
const TEMPLATES_DIR = path.join(__dirname, '../cluster-templates');
|
|
15
|
-
|
|
16
|
-
function findJsonFiles(dir) {
|
|
17
|
-
const files = [];
|
|
18
|
-
if (!fs.existsSync(dir)) return files;
|
|
19
|
-
|
|
20
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
21
|
-
const fullPath = path.join(dir, entry.name);
|
|
22
|
-
if (entry.isDirectory()) {
|
|
23
|
-
files.push(...findJsonFiles(fullPath));
|
|
24
|
-
} else if (entry.name.endsWith('.json')) {
|
|
25
|
-
files.push(fullPath);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return files;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function validateTemplate(filePath) {
|
|
32
|
-
const relativePath = path.relative(process.cwd(), filePath);
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
36
|
-
const config = JSON.parse(content);
|
|
37
|
-
|
|
38
|
-
// Skip non-cluster configs (like package.json)
|
|
39
|
-
if (!config.agents && !config.name) {
|
|
40
|
-
return { valid: true, skipped: true };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const result = validateConfig(config);
|
|
44
|
-
|
|
45
|
-
if (!result.valid) {
|
|
46
|
-
console.error(`\n❌ ${relativePath}`);
|
|
47
|
-
for (const error of result.errors) {
|
|
48
|
-
console.error(` ERROR: ${error}`);
|
|
49
|
-
}
|
|
50
|
-
} else if (result.warnings.length > 0) {
|
|
51
|
-
console.warn(`\n⚠️ ${relativePath}`);
|
|
52
|
-
for (const warning of result.warnings) {
|
|
53
|
-
console.warn(` WARN: ${warning}`);
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
console.log(`✓ ${relativePath}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return result;
|
|
60
|
-
} catch (err) {
|
|
61
|
-
console.error(`\n❌ ${relativePath}`);
|
|
62
|
-
console.error(` PARSE ERROR: ${err.message}`);
|
|
63
|
-
return { valid: false, errors: [err.message], warnings: [] };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function main() {
|
|
68
|
-
console.log('Validating cluster templates...\n');
|
|
69
|
-
|
|
70
|
-
const templateFiles = [...findJsonFiles(TEMPLATES_DIR)];
|
|
71
|
-
|
|
72
|
-
let hasErrors = false;
|
|
73
|
-
let validated = 0;
|
|
74
|
-
let skipped = 0;
|
|
75
|
-
|
|
76
|
-
for (const file of templateFiles) {
|
|
77
|
-
const result = validateTemplate(file);
|
|
78
|
-
if (result.skipped) {
|
|
79
|
-
skipped++;
|
|
80
|
-
} else {
|
|
81
|
-
validated++;
|
|
82
|
-
if (!result.valid) {
|
|
83
|
-
hasErrors = true;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.log(`\n${'='.repeat(60)}`);
|
|
89
|
-
console.log(`Validated: ${validated} templates, Skipped: ${skipped} files`);
|
|
90
|
-
|
|
91
|
-
if (hasErrors) {
|
|
92
|
-
console.error('\n❌ VALIDATION FAILED - Fix errors above before merging\n');
|
|
93
|
-
process.exit(1);
|
|
94
|
-
} else {
|
|
95
|
-
console.log('\n✓ All templates valid\n');
|
|
96
|
-
process.exit(0);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
main();
|
|
@@ -1,193 +0,0 @@
|
|
|
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
|
-
* - maxModel ceiling enforcement at config time
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { loadSettings, validateModelAgainstMax, VALID_MODELS } = require('../../lib/settings');
|
|
13
|
-
|
|
14
|
-
const VALID_LEVELS = ['level1', 'level2', 'level3'];
|
|
15
|
-
|
|
16
|
-
// Default max iterations (high limit - let the user decide when to give up)
|
|
17
|
-
const DEFAULT_MAX_ITERATIONS = 100;
|
|
18
|
-
|
|
19
|
-
// Default timeout: 0 = no timeout (task runs until completion or explicit kill)
|
|
20
|
-
// Use positive number for timeout in milliseconds
|
|
21
|
-
const DEFAULT_TIMEOUT = 0;
|
|
22
|
-
|
|
23
|
-
// Stale detection - ENABLED by default using multi-indicator analysis (safe from false positives)
|
|
24
|
-
// Multi-indicator approach checks: process state, CPU usage, context switches, network I/O
|
|
25
|
-
// Only flags as stuck when ALL indicators show inactivity (score >= 3.5)
|
|
26
|
-
// Single-indicator detection (just output freshness) was too risky - this is safe.
|
|
27
|
-
const DEFAULT_STALE_DURATION_MS = 30 * 60 * 1000; // 30 minutes before triggering analysis
|
|
28
|
-
const DEFAULT_LIVENESS_CHECK_ENABLED = true; // Safe with multi-indicator detection
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Validate and normalize agent configuration
|
|
32
|
-
* @param {Object} config - Raw agent configuration
|
|
33
|
-
* @param {Object} options - Agent wrapper options
|
|
34
|
-
* @returns {Object} Normalized configuration
|
|
35
|
-
*/
|
|
36
|
-
function validateAgentConfig(config, options = {}) {
|
|
37
|
-
// CRITICAL: Enforce JSON schema output by default to prevent parse failures and crashes
|
|
38
|
-
// Agents MUST return structured output so hooks can safely use {{result.*}} templates
|
|
39
|
-
if (!config.outputFormat) {
|
|
40
|
-
config.outputFormat = 'json';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// If outputFormat is json but no schema defined, use a minimal default schema
|
|
44
|
-
// This prevents uncaught exceptions when parsing agent output
|
|
45
|
-
if (config.outputFormat === 'json' && !config.jsonSchema) {
|
|
46
|
-
config.jsonSchema = {
|
|
47
|
-
type: 'object',
|
|
48
|
-
properties: {
|
|
49
|
-
summary: {
|
|
50
|
-
type: 'string',
|
|
51
|
-
description: 'Brief summary of what was done',
|
|
52
|
-
},
|
|
53
|
-
result: {
|
|
54
|
-
type: 'string',
|
|
55
|
-
description: 'Detailed result or output',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
required: ['summary', 'result'],
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Model configuration: support both static model and dynamic rules
|
|
63
|
-
// If no model specified, model is null - _selectModel() will use provider defaults
|
|
64
|
-
let modelConfig;
|
|
65
|
-
if (config.modelRules) {
|
|
66
|
-
modelConfig = { type: 'rules', rules: config.modelRules };
|
|
67
|
-
} else {
|
|
68
|
-
modelConfig = {
|
|
69
|
-
type: 'static',
|
|
70
|
-
model: config.model || null,
|
|
71
|
-
modelLevel: config.modelLevel || null,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// COST CEILING/FLOOR ENFORCEMENT: Validate model(s) against maxModel and minModel at config time
|
|
76
|
-
// Catches violations EARLY (config load) instead of at runtime (iteration N)
|
|
77
|
-
const settings = loadSettings();
|
|
78
|
-
const maxModel = settings.maxModel || 'sonnet';
|
|
79
|
-
const minModel = settings.minModel || null;
|
|
80
|
-
|
|
81
|
-
if (modelConfig.type === 'static') {
|
|
82
|
-
if (modelConfig.model && VALID_MODELS.includes(modelConfig.model)) {
|
|
83
|
-
// Static model: validate once (legacy Claude models only)
|
|
84
|
-
try {
|
|
85
|
-
validateModelAgainstMax(modelConfig.model, maxModel, minModel);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
throw new Error(`Agent "${config.id}": ${error.message}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (modelConfig.modelLevel && !VALID_LEVELS.includes(modelConfig.modelLevel)) {
|
|
92
|
-
throw new Error(
|
|
93
|
-
`Agent "${config.id}": invalid modelLevel "${modelConfig.modelLevel}". ` +
|
|
94
|
-
`Valid: ${VALID_LEVELS.join(', ')}`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
} else if (modelConfig.type === 'rules') {
|
|
98
|
-
// Dynamic rules: validate ALL rules upfront (don't wait until iteration N)
|
|
99
|
-
for (const rule of modelConfig.rules) {
|
|
100
|
-
if (rule.model && VALID_MODELS.includes(rule.model)) {
|
|
101
|
-
try {
|
|
102
|
-
validateModelAgainstMax(rule.model, maxModel, minModel);
|
|
103
|
-
} catch {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Agent "${config.id}": modelRule "${rule.iterations}" requests "${rule.model}" ` +
|
|
106
|
-
`but maxModel is "${maxModel}"${minModel ? ` and minModel is "${minModel}"` : ''}. ` +
|
|
107
|
-
`Either adjust the rule's model or change maxModel/minModel settings.`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (rule.modelLevel && !VALID_LEVELS.includes(rule.modelLevel)) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`Agent "${config.id}": modelRule "${rule.iterations}" has invalid modelLevel ` +
|
|
115
|
-
`"${rule.modelLevel}". Valid: ${VALID_LEVELS.join(', ')}`
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Prompt configuration: support static prompt OR iteration-based rules
|
|
122
|
-
// Formats:
|
|
123
|
-
// prompt: "string" -> static
|
|
124
|
-
// prompt: { system: "string" } -> static
|
|
125
|
-
// prompt: { initial: "...", subsequent: "..." } -> iteration 1 vs 2+
|
|
126
|
-
// prompt: { iterations: [...] } -> full control
|
|
127
|
-
let promptConfig = null;
|
|
128
|
-
if (config.prompt?.iterations) {
|
|
129
|
-
promptConfig = { type: 'rules', rules: config.prompt.iterations };
|
|
130
|
-
} else if (config.prompt?.initial || config.prompt?.subsequent) {
|
|
131
|
-
const rules = [];
|
|
132
|
-
if (config.prompt.initial) rules.push({ match: '1', system: config.prompt.initial });
|
|
133
|
-
if (config.prompt.subsequent) rules.push({ match: '2+', system: config.prompt.subsequent });
|
|
134
|
-
promptConfig = { type: 'rules', rules };
|
|
135
|
-
} else if (typeof config.prompt === 'string') {
|
|
136
|
-
promptConfig = { type: 'static', system: config.prompt };
|
|
137
|
-
} else if (config.prompt?.system) {
|
|
138
|
-
promptConfig = { type: 'static', system: config.prompt.system };
|
|
139
|
-
} else if (config.prompt) {
|
|
140
|
-
throw new Error(`Agent "${config.id}": invalid prompt format`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Default timeout to 0 (no timeout) if not specified
|
|
144
|
-
// Use positive number for timeout in milliseconds
|
|
145
|
-
// ROBUST: Handle undefined, null, AND string values from template resolution
|
|
146
|
-
if (config.timeout === undefined || config.timeout === null || config.timeout === '') {
|
|
147
|
-
config.timeout = DEFAULT_TIMEOUT;
|
|
148
|
-
} else {
|
|
149
|
-
// Coerce to number (handles string "0" from template resolution)
|
|
150
|
-
config.timeout = Number(config.timeout);
|
|
151
|
-
}
|
|
152
|
-
if (!Number.isFinite(config.timeout) || config.timeout < 0) {
|
|
153
|
-
throw new Error(
|
|
154
|
-
`Agent "${config.id}": timeout must be a non-negative number (got ${config.timeout}).`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Build normalized config
|
|
159
|
-
const normalizedConfig = {
|
|
160
|
-
...config,
|
|
161
|
-
modelConfig,
|
|
162
|
-
promptConfig,
|
|
163
|
-
maxIterations: config.maxIterations || DEFAULT_MAX_ITERATIONS,
|
|
164
|
-
timeout: config.timeout, // Defaults to 0 (no timeout) if not specified
|
|
165
|
-
staleDuration: config.staleDuration || DEFAULT_STALE_DURATION_MS,
|
|
166
|
-
enableLivenessCheck: config.enableLivenessCheck ?? DEFAULT_LIVENESS_CHECK_ENABLED, // On by default, opt-out with false
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// SAFETY: In test mode, verify mock is provided for agents that execute tasks
|
|
170
|
-
// Check if this agent executes tasks (vs orchestrator agents that only publish messages)
|
|
171
|
-
const executesTask = config.triggers?.some(
|
|
172
|
-
(trigger) => !trigger.action || trigger.action === 'execute_task'
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// Accept either mockSpawnFn OR taskRunner as valid mock sources
|
|
176
|
-
const hasMock = options.mockSpawnFn || options.taskRunner;
|
|
177
|
-
|
|
178
|
-
if (options.testMode && !hasMock && executesTask) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`AgentWrapper: testMode=true but no mockSpawnFn/taskRunner provided for agent '${config.id}'. ` +
|
|
181
|
-
`This would cause real Claude API calls. ABORTING.`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return normalizedConfig;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
module.exports = {
|
|
189
|
-
validateAgentConfig,
|
|
190
|
-
DEFAULT_MAX_ITERATIONS,
|
|
191
|
-
DEFAULT_STALE_DURATION_MS,
|
|
192
|
-
DEFAULT_LIVENESS_CHECK_ENABLED,
|
|
193
|
-
};
|