@agent-foundry/replay-server 1.0.0 → 1.0.2
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/.cursor/dev.mdc +941 -0
- package/.cursor/project.mdc +17 -2
- package/.env +30 -0
- package/Dockerfile +6 -0
- package/README.md +297 -27
- package/dist/cli/render.js +14 -4
- package/dist/cli/render.js.map +1 -1
- package/dist/renderer/PuppeteerRenderer.d.ts +28 -2
- package/dist/renderer/PuppeteerRenderer.d.ts.map +1 -1
- package/dist/renderer/PuppeteerRenderer.js +134 -36
- package/dist/renderer/PuppeteerRenderer.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +200 -46
- package/dist/server/index.js.map +1 -1
- package/dist/services/BundleManager.d.ts +99 -0
- package/dist/services/BundleManager.d.ts.map +1 -0
- package/dist/services/BundleManager.js +410 -0
- package/dist/services/BundleManager.js.map +1 -0
- package/dist/services/OSSClient.d.ts +51 -0
- package/dist/services/OSSClient.d.ts.map +1 -0
- package/dist/services/OSSClient.js +207 -0
- package/dist/services/OSSClient.js.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +7 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/types.d.ts +73 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +5 -0
- package/dist/services/types.js.map +1 -0
- package/docker-compose.local.yml +10 -0
- package/env.example +30 -0
- package/package.json +7 -3
- package/restart.sh +5 -0
- package/samples/jump_arena_5_ja-mksi5fku-qgk5iq.json +1952 -0
- package/scripts/render-pipeline.sh +657 -0
- package/scripts/test-bundle-preload.sh +20 -0
- package/scripts/test-service-sts.sh +176 -0
- package/src/cli/render.ts +18 -7
- package/src/renderer/PuppeteerRenderer.ts +192 -39
- package/src/server/index.ts +249 -68
- package/src/services/BundleManager.ts +503 -0
- package/src/services/OSSClient.ts +286 -0
- package/src/services/index.ts +7 -0
- package/src/services/types.ts +78 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Replay Render Pipeline
|
|
4
|
+
#
|
|
5
|
+
# Full end-to-end rendering workflow:
|
|
6
|
+
# 1. Check/start server
|
|
7
|
+
# 2. Verify/build bundle
|
|
8
|
+
# 3. Preload bundle if needed
|
|
9
|
+
# 4. Submit render job via HTTP API
|
|
10
|
+
# 5. Poll for completion
|
|
11
|
+
# 6. Auto-download video
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# ./render-pipeline.sh -m samples/replay.json
|
|
15
|
+
# ./render-pipeline.sh -m samples/replay.json -o output/video.mp4 --start-server
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Configuration
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
25
|
+
PACKAGE_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
|
26
|
+
REPO_ROOT=$(cd "$PACKAGE_DIR/../.." && pwd)
|
|
27
|
+
|
|
28
|
+
# Default values
|
|
29
|
+
DEFAULT_SERVER_URL="http://localhost:3001"
|
|
30
|
+
DEFAULT_WIDTH=720
|
|
31
|
+
DEFAULT_HEIGHT=1080
|
|
32
|
+
DEFAULT_FPS=16
|
|
33
|
+
DEFAULT_OUTPUT_DIR="$PACKAGE_DIR/output"
|
|
34
|
+
|
|
35
|
+
# Variables
|
|
36
|
+
MANIFEST_FILE=""
|
|
37
|
+
OUTPUT_PATH=""
|
|
38
|
+
WIDTH=$DEFAULT_WIDTH
|
|
39
|
+
HEIGHT=$DEFAULT_HEIGHT
|
|
40
|
+
FPS=$DEFAULT_FPS
|
|
41
|
+
SERVER_URL=$DEFAULT_SERVER_URL
|
|
42
|
+
START_SERVER=false
|
|
43
|
+
BUILD_BUNDLE=false
|
|
44
|
+
VERBOSE=false
|
|
45
|
+
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Colors and Output
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
RED='\033[0;31m'
|
|
51
|
+
GREEN='\033[0;32m'
|
|
52
|
+
YELLOW='\033[1;33m'
|
|
53
|
+
BLUE='\033[0;34m'
|
|
54
|
+
CYAN='\033[0;36m'
|
|
55
|
+
NC='\033[0m' # No Color
|
|
56
|
+
BOLD='\033[1m'
|
|
57
|
+
|
|
58
|
+
log_info() {
|
|
59
|
+
echo -e "${CYAN}ℹ${NC} $1"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
log_success() {
|
|
63
|
+
echo -e "${GREEN}✓${NC} $1"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
log_warning() {
|
|
67
|
+
echo -e "${YELLOW}⚠${NC} $1"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
log_error() {
|
|
71
|
+
echo -e "${RED}✗${NC} $1" >&2
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
log_step() {
|
|
75
|
+
echo -e "${BOLD}${BLUE}▶${NC} $1"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log_verbose() {
|
|
79
|
+
if [ "$VERBOSE" = true ]; then
|
|
80
|
+
echo -e "${CYAN}[DEBUG]${NC} $1"
|
|
81
|
+
fi
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# ============================================================================
|
|
85
|
+
# Helper Functions
|
|
86
|
+
# ============================================================================
|
|
87
|
+
|
|
88
|
+
check_dependencies() {
|
|
89
|
+
local missing_deps=()
|
|
90
|
+
|
|
91
|
+
if ! command -v curl &> /dev/null; then
|
|
92
|
+
missing_deps+=("curl")
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if ! command -v jq &> /dev/null; then
|
|
96
|
+
missing_deps+=("jq")
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
if [ ${#missing_deps[@]} -gt 0 ]; then
|
|
100
|
+
log_error "Missing required dependencies: ${missing_deps[*]}"
|
|
101
|
+
echo ""
|
|
102
|
+
echo "Install instructions:"
|
|
103
|
+
for dep in "${missing_deps[@]}"; do
|
|
104
|
+
case $dep in
|
|
105
|
+
curl)
|
|
106
|
+
echo " macOS: brew install curl"
|
|
107
|
+
echo " Linux: sudo apt install curl"
|
|
108
|
+
;;
|
|
109
|
+
jq)
|
|
110
|
+
echo " macOS: brew install jq"
|
|
111
|
+
echo " Linux: sudo apt install jq"
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
done
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
load_env() {
|
|
120
|
+
local env_file="$PACKAGE_DIR/.env"
|
|
121
|
+
if [ -f "$env_file" ]; then
|
|
122
|
+
log_verbose "Loading environment from $env_file"
|
|
123
|
+
# Export variables from .env (skip comments and empty lines)
|
|
124
|
+
set -a
|
|
125
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
126
|
+
# Skip comments and empty lines
|
|
127
|
+
if [[ ! "$line" =~ ^[[:space:]]*# ]] && [[ -n "$line" ]]; then
|
|
128
|
+
eval "export $line" 2>/dev/null || true
|
|
129
|
+
fi
|
|
130
|
+
done < "$env_file"
|
|
131
|
+
set +a
|
|
132
|
+
fi
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
print_help() {
|
|
136
|
+
cat << EOF
|
|
137
|
+
🎬 Replay Render Pipeline
|
|
138
|
+
|
|
139
|
+
Full end-to-end rendering workflow for AgentFoundry replays.
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
./render-pipeline.sh [options]
|
|
143
|
+
|
|
144
|
+
Required:
|
|
145
|
+
-m, --manifest <path> Path to replay manifest JSON file
|
|
146
|
+
|
|
147
|
+
Optional:
|
|
148
|
+
-o, --output <path> Output video path (default: ./output/<gameId>.mp4)
|
|
149
|
+
--width <px> Video width (default: 1080)
|
|
150
|
+
--height <px> Video height (default: 1920)
|
|
151
|
+
--fps <n> Frames per second (default: 30)
|
|
152
|
+
--server-url <url> Replay server URL (default: http://localhost:3001)
|
|
153
|
+
--start-server Auto-start server if not running
|
|
154
|
+
--build-bundle Auto-build bundle if missing
|
|
155
|
+
-v, --verbose Verbose output
|
|
156
|
+
-h, --help Show this help message
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
# Basic usage
|
|
160
|
+
./render-pipeline.sh -m samples/jump_arena_0_ja-mks5um2x-nksbmz.json
|
|
161
|
+
|
|
162
|
+
# With custom output and settings
|
|
163
|
+
./render-pipeline.sh -m samples/replay.json -o videos/my-replay.mp4 --width 720 --height 1280
|
|
164
|
+
|
|
165
|
+
# Auto-start server if needed
|
|
166
|
+
./render-pipeline.sh -m samples/replay.json --start-server
|
|
167
|
+
|
|
168
|
+
# Build bundle if missing and start server
|
|
169
|
+
./render-pipeline.sh -m samples/replay.json --build-bundle --start-server
|
|
170
|
+
|
|
171
|
+
EOF
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# ============================================================================
|
|
175
|
+
# Argument Parsing
|
|
176
|
+
# ============================================================================
|
|
177
|
+
|
|
178
|
+
parse_args() {
|
|
179
|
+
while [[ $# -gt 0 ]]; do
|
|
180
|
+
case $1 in
|
|
181
|
+
-m|--manifest)
|
|
182
|
+
MANIFEST_FILE="$2"
|
|
183
|
+
shift 2
|
|
184
|
+
;;
|
|
185
|
+
-o|--output)
|
|
186
|
+
OUTPUT_PATH="$2"
|
|
187
|
+
shift 2
|
|
188
|
+
;;
|
|
189
|
+
--width)
|
|
190
|
+
WIDTH="$2"
|
|
191
|
+
shift 2
|
|
192
|
+
;;
|
|
193
|
+
--height)
|
|
194
|
+
HEIGHT="$2"
|
|
195
|
+
shift 2
|
|
196
|
+
;;
|
|
197
|
+
--fps)
|
|
198
|
+
FPS="$2"
|
|
199
|
+
shift 2
|
|
200
|
+
;;
|
|
201
|
+
--server-url)
|
|
202
|
+
SERVER_URL="$2"
|
|
203
|
+
shift 2
|
|
204
|
+
;;
|
|
205
|
+
--start-server)
|
|
206
|
+
START_SERVER=true
|
|
207
|
+
shift
|
|
208
|
+
;;
|
|
209
|
+
--build-bundle)
|
|
210
|
+
BUILD_BUNDLE=true
|
|
211
|
+
shift
|
|
212
|
+
;;
|
|
213
|
+
-v|--verbose)
|
|
214
|
+
VERBOSE=true
|
|
215
|
+
shift
|
|
216
|
+
;;
|
|
217
|
+
-h|--help)
|
|
218
|
+
print_help
|
|
219
|
+
exit 0
|
|
220
|
+
;;
|
|
221
|
+
*)
|
|
222
|
+
log_error "Unknown option: $1"
|
|
223
|
+
echo ""
|
|
224
|
+
print_help
|
|
225
|
+
exit 1
|
|
226
|
+
;;
|
|
227
|
+
esac
|
|
228
|
+
done
|
|
229
|
+
|
|
230
|
+
# Validate required arguments
|
|
231
|
+
if [ -z "$MANIFEST_FILE" ]; then
|
|
232
|
+
log_error "Missing required argument: --manifest"
|
|
233
|
+
echo ""
|
|
234
|
+
print_help
|
|
235
|
+
exit 1
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Resolve manifest path
|
|
239
|
+
if [ ! -f "$MANIFEST_FILE" ]; then
|
|
240
|
+
log_error "Manifest file not found: $MANIFEST_FILE"
|
|
241
|
+
exit 1
|
|
242
|
+
fi
|
|
243
|
+
MANIFEST_FILE=$(cd "$(dirname "$MANIFEST_FILE")" && pwd)/$(basename "$MANIFEST_FILE")
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# ============================================================================
|
|
247
|
+
# Server Management
|
|
248
|
+
# ============================================================================
|
|
249
|
+
|
|
250
|
+
check_server() {
|
|
251
|
+
log_step "Checking replay server..."
|
|
252
|
+
|
|
253
|
+
local health_check
|
|
254
|
+
health_check=$(curl -s -o /dev/null -w "%{http_code}" "$SERVER_URL/health" || echo "000")
|
|
255
|
+
|
|
256
|
+
if [ "$health_check" = "200" ]; then
|
|
257
|
+
log_success "Server is running at $SERVER_URL"
|
|
258
|
+
return 0
|
|
259
|
+
else
|
|
260
|
+
log_warning "Server is not responding at $SERVER_URL"
|
|
261
|
+
return 1
|
|
262
|
+
fi
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
start_server() {
|
|
266
|
+
log_step "Starting replay server..."
|
|
267
|
+
|
|
268
|
+
cd "$PACKAGE_DIR"
|
|
269
|
+
|
|
270
|
+
# Check if already running on the port
|
|
271
|
+
local port=$(echo "$SERVER_URL" | grep -oP '(?<=:)\d+$' || echo "3001")
|
|
272
|
+
if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
273
|
+
log_warning "Port $port is already in use"
|
|
274
|
+
return 1
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Start server in background
|
|
278
|
+
log_info "Starting server with: pnpm run dev"
|
|
279
|
+
nohup pnpm run dev > /tmp/replay-server.log 2>&1 &
|
|
280
|
+
local server_pid=$!
|
|
281
|
+
|
|
282
|
+
log_info "Server PID: $server_pid"
|
|
283
|
+
|
|
284
|
+
# Wait for server to be ready (max 30 seconds)
|
|
285
|
+
local max_wait=30
|
|
286
|
+
local wait_time=0
|
|
287
|
+
while [ $wait_time -lt $max_wait ]; do
|
|
288
|
+
sleep 2
|
|
289
|
+
wait_time=$((wait_time + 2))
|
|
290
|
+
|
|
291
|
+
if curl -s -o /dev/null -w "%{http_code}" "$SERVER_URL/health" | grep -q "200"; then
|
|
292
|
+
log_success "Server is ready (took ${wait_time}s)"
|
|
293
|
+
return 0
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
echo -n "."
|
|
297
|
+
done
|
|
298
|
+
|
|
299
|
+
echo ""
|
|
300
|
+
log_error "Server failed to start within ${max_wait}s"
|
|
301
|
+
log_info "Check logs: tail -f /tmp/replay-server.log"
|
|
302
|
+
return 1
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# ============================================================================
|
|
306
|
+
# Bundle Management
|
|
307
|
+
# ============================================================================
|
|
308
|
+
|
|
309
|
+
parse_manifest() {
|
|
310
|
+
log_step "Parsing manifest..."
|
|
311
|
+
|
|
312
|
+
BUNDLE_ID=$(jq -r '.bundleId // empty' "$MANIFEST_FILE")
|
|
313
|
+
BUNDLE_URL=$(jq -r '.bundleUrl // empty' "$MANIFEST_FILE")
|
|
314
|
+
GAME_ID=$(jq -r '.gameId' "$MANIFEST_FILE")
|
|
315
|
+
|
|
316
|
+
if [ -z "$BUNDLE_ID" ]; then
|
|
317
|
+
log_error "Manifest does not contain bundleId"
|
|
318
|
+
exit 1
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
log_info "Bundle ID: $BUNDLE_ID"
|
|
322
|
+
log_info "Game ID: $GAME_ID"
|
|
323
|
+
|
|
324
|
+
if [ -n "$BUNDLE_URL" ]; then
|
|
325
|
+
log_info "Bundle URL: $BUNDLE_URL"
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# Set default output path if not specified
|
|
329
|
+
if [ -z "$OUTPUT_PATH" ]; then
|
|
330
|
+
OUTPUT_PATH="$DEFAULT_OUTPUT_DIR/${GAME_ID}.mp4"
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
check_bundle() {
|
|
335
|
+
log_step "Checking bundle availability..."
|
|
336
|
+
|
|
337
|
+
# Use GET /api/bundles (list) and filter to avoid route conflict with static middleware
|
|
338
|
+
local response
|
|
339
|
+
response=$(curl -s "$SERVER_URL/api/bundles")
|
|
340
|
+
|
|
341
|
+
log_verbose "Bundles list response: $response"
|
|
342
|
+
|
|
343
|
+
# Extract bundle info from the list
|
|
344
|
+
local bundle_info=$(echo "$response" | jq --arg id "$BUNDLE_ID" '.bundles[]? | select(.bundleId == $id)')
|
|
345
|
+
|
|
346
|
+
if [ -z "$bundle_info" ]; then
|
|
347
|
+
log_warning "Bundle '$BUNDLE_ID' not found on server"
|
|
348
|
+
return 1
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
local status=$(echo "$bundle_info" | jq -r '.status // "ready"')
|
|
352
|
+
|
|
353
|
+
log_verbose "Bundle '$BUNDLE_ID' status: $status"
|
|
354
|
+
|
|
355
|
+
if [ "$status" = "ready" ]; then
|
|
356
|
+
log_success "Bundle '$BUNDLE_ID' is ready"
|
|
357
|
+
return 0
|
|
358
|
+
elif [ "$status" = "downloading" ]; then
|
|
359
|
+
log_info "Bundle '$BUNDLE_ID' is currently downloading..."
|
|
360
|
+
wait_for_bundle
|
|
361
|
+
return 0
|
|
362
|
+
else
|
|
363
|
+
log_warning "Bundle '$BUNDLE_ID' status: $status"
|
|
364
|
+
return 1
|
|
365
|
+
fi
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
preload_bundle() {
|
|
369
|
+
if [ -z "$BUNDLE_URL" ]; then
|
|
370
|
+
log_error "No bundleUrl provided in manifest"
|
|
371
|
+
|
|
372
|
+
if [ "$BUILD_BUNDLE" = true ]; then
|
|
373
|
+
build_bundle
|
|
374
|
+
return 0
|
|
375
|
+
else
|
|
376
|
+
log_error "Cannot preload bundle without bundleUrl"
|
|
377
|
+
log_info "Use --build-bundle flag to build locally"
|
|
378
|
+
exit 1
|
|
379
|
+
fi
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
log_step "Preloading bundle from OSS..."
|
|
383
|
+
|
|
384
|
+
local payload=$(jq -n \
|
|
385
|
+
--arg bundleId "$BUNDLE_ID" \
|
|
386
|
+
--arg bundleUrl "$BUNDLE_URL" \
|
|
387
|
+
'{bundleId: $bundleId, bundleUrl: $bundleUrl}')
|
|
388
|
+
|
|
389
|
+
local response
|
|
390
|
+
response=$(curl -s -X POST "$SERVER_URL/api/bundles/preload" \
|
|
391
|
+
-H "Content-Type: application/json" \
|
|
392
|
+
-d "$payload")
|
|
393
|
+
|
|
394
|
+
log_verbose "Preload response: $response"
|
|
395
|
+
|
|
396
|
+
local status=$(echo "$response" | jq -r '.status')
|
|
397
|
+
|
|
398
|
+
if [ "$status" = "downloading" ] || [ "$status" = "ready" ]; then
|
|
399
|
+
log_success "Bundle preload initiated"
|
|
400
|
+
wait_for_bundle
|
|
401
|
+
else
|
|
402
|
+
log_error "Failed to preload bundle"
|
|
403
|
+
echo "$response" | jq .
|
|
404
|
+
exit 1
|
|
405
|
+
fi
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
build_bundle() {
|
|
409
|
+
log_step "Building bundle locally..."
|
|
410
|
+
|
|
411
|
+
local build_script="$SCRIPT_DIR/build-bundle.sh"
|
|
412
|
+
|
|
413
|
+
if [ ! -f "$build_script" ]; then
|
|
414
|
+
log_error "Build script not found: $build_script"
|
|
415
|
+
exit 1
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
log_info "Running: $build_script $BUNDLE_ID"
|
|
419
|
+
|
|
420
|
+
if bash "$build_script" "$BUNDLE_ID"; then
|
|
421
|
+
log_success "Bundle built successfully"
|
|
422
|
+
else
|
|
423
|
+
log_error "Failed to build bundle"
|
|
424
|
+
exit 1
|
|
425
|
+
fi
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
wait_for_bundle() {
|
|
429
|
+
log_info "Waiting for bundle to be ready..."
|
|
430
|
+
|
|
431
|
+
local max_wait=300 # 5 minutes
|
|
432
|
+
local wait_time=0
|
|
433
|
+
local last_progress=""
|
|
434
|
+
|
|
435
|
+
while [ $wait_time -lt $max_wait ]; do
|
|
436
|
+
sleep 2
|
|
437
|
+
wait_time=$((wait_time + 2))
|
|
438
|
+
|
|
439
|
+
# Use GET /api/bundles (list) and filter for consistency
|
|
440
|
+
local response
|
|
441
|
+
response=$(curl -s "$SERVER_URL/api/bundles")
|
|
442
|
+
local bundle_info=$(echo "$response" | jq --arg id "$BUNDLE_ID" '.bundles[]? | select(.bundleId == $id)')
|
|
443
|
+
|
|
444
|
+
if [ -z "$bundle_info" ]; then
|
|
445
|
+
echo ""
|
|
446
|
+
log_error "Bundle disappeared from server"
|
|
447
|
+
exit 1
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
local status=$(echo "$bundle_info" | jq -r '.status')
|
|
451
|
+
local progress=$(echo "$bundle_info" | jq -r '.progress // 0')
|
|
452
|
+
|
|
453
|
+
if [ "$status" = "ready" ]; then
|
|
454
|
+
echo ""
|
|
455
|
+
log_success "Bundle is ready"
|
|
456
|
+
return 0
|
|
457
|
+
elif [ "$status" = "error" ]; then
|
|
458
|
+
echo ""
|
|
459
|
+
local error=$(echo "$bundle_info" | jq -r '.error')
|
|
460
|
+
log_error "Bundle download failed: $error"
|
|
461
|
+
exit 1
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
# Show progress
|
|
465
|
+
if [ "$progress" != "$last_progress" ]; then
|
|
466
|
+
printf "\r Progress: %d%%" "$progress"
|
|
467
|
+
last_progress="$progress"
|
|
468
|
+
else
|
|
469
|
+
printf "."
|
|
470
|
+
fi
|
|
471
|
+
done
|
|
472
|
+
|
|
473
|
+
echo ""
|
|
474
|
+
log_error "Bundle download timeout (${max_wait}s)"
|
|
475
|
+
exit 1
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# ============================================================================
|
|
479
|
+
# Render Job Management
|
|
480
|
+
# ============================================================================
|
|
481
|
+
|
|
482
|
+
submit_render_job() {
|
|
483
|
+
log_step "Submitting render job..."
|
|
484
|
+
|
|
485
|
+
# Read manifest and add config
|
|
486
|
+
local manifest_content
|
|
487
|
+
manifest_content=$(cat "$MANIFEST_FILE")
|
|
488
|
+
|
|
489
|
+
local config=$(jq -n \
|
|
490
|
+
--arg width "$WIDTH" \
|
|
491
|
+
--arg height "$HEIGHT" \
|
|
492
|
+
--arg fps "$FPS" \
|
|
493
|
+
'{width: ($width | tonumber), height: ($height | tonumber), fps: ($fps | tonumber)}')
|
|
494
|
+
|
|
495
|
+
local payload=$(jq -n \
|
|
496
|
+
--argjson manifest "$manifest_content" \
|
|
497
|
+
--argjson config "$config" \
|
|
498
|
+
'{manifest: $manifest, config: $config}')
|
|
499
|
+
|
|
500
|
+
log_verbose "Render payload: $payload"
|
|
501
|
+
|
|
502
|
+
local response
|
|
503
|
+
response=$(curl -s -X POST "$SERVER_URL/render" \
|
|
504
|
+
-H "Content-Type: application/json" \
|
|
505
|
+
-d "$payload")
|
|
506
|
+
|
|
507
|
+
log_verbose "Render response: $response"
|
|
508
|
+
|
|
509
|
+
JOB_ID=$(echo "$response" | jq -r '.jobId')
|
|
510
|
+
|
|
511
|
+
if [ -z "$JOB_ID" ] || [ "$JOB_ID" = "null" ]; then
|
|
512
|
+
log_error "Failed to submit render job"
|
|
513
|
+
echo "$response" | jq .
|
|
514
|
+
exit 1
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
log_success "Render job submitted"
|
|
518
|
+
log_info "Job ID: $JOB_ID"
|
|
519
|
+
log_info "Resolution: ${WIDTH}x${HEIGHT} @ ${FPS}fps"
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
poll_job_status() {
|
|
523
|
+
log_step "Monitoring render progress..."
|
|
524
|
+
|
|
525
|
+
local last_message=""
|
|
526
|
+
local last_percent=-1
|
|
527
|
+
|
|
528
|
+
while true; do
|
|
529
|
+
sleep 2
|
|
530
|
+
|
|
531
|
+
local response
|
|
532
|
+
response=$(curl -s "$SERVER_URL/status/$JOB_ID")
|
|
533
|
+
|
|
534
|
+
log_verbose "Status response: $response"
|
|
535
|
+
|
|
536
|
+
local status=$(echo "$response" | jq -r '.status')
|
|
537
|
+
local progress=$(echo "$response" | jq -r '.progress // {}')
|
|
538
|
+
local current=$(echo "$progress" | jq -r '.current // 0')
|
|
539
|
+
local total=$(echo "$progress" | jq -r '.total // 1')
|
|
540
|
+
local message=$(echo "$progress" | jq -r '.message // ""')
|
|
541
|
+
|
|
542
|
+
# Calculate percentage
|
|
543
|
+
local percent=0
|
|
544
|
+
if [ "$total" -gt 0 ]; then
|
|
545
|
+
percent=$((current * 100 / total))
|
|
546
|
+
fi
|
|
547
|
+
|
|
548
|
+
# Show progress
|
|
549
|
+
if [ "$percent" != "$last_percent" ] || [ "$message" != "$last_message" ]; then
|
|
550
|
+
local bar_length=40
|
|
551
|
+
local filled=$((percent * bar_length / 100))
|
|
552
|
+
local empty=$((bar_length - filled))
|
|
553
|
+
local bar=$(printf '█%.0s' $(seq 1 $filled))$(printf '░%.0s' $(seq 1 $empty))
|
|
554
|
+
|
|
555
|
+
printf "\r [%s] %3d%% - %s" "$bar" "$percent" "$message"
|
|
556
|
+
last_percent=$percent
|
|
557
|
+
last_message="$message"
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
# Check status
|
|
561
|
+
case $status in
|
|
562
|
+
completed)
|
|
563
|
+
echo ""
|
|
564
|
+
log_success "Render completed"
|
|
565
|
+
return 0
|
|
566
|
+
;;
|
|
567
|
+
failed)
|
|
568
|
+
echo ""
|
|
569
|
+
local error=$(echo "$response" | jq -r '.error')
|
|
570
|
+
log_error "Render failed: $error"
|
|
571
|
+
exit 1
|
|
572
|
+
;;
|
|
573
|
+
pending|processing)
|
|
574
|
+
# Continue polling
|
|
575
|
+
;;
|
|
576
|
+
*)
|
|
577
|
+
echo ""
|
|
578
|
+
log_warning "Unknown status: $status"
|
|
579
|
+
;;
|
|
580
|
+
esac
|
|
581
|
+
done
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
download_video() {
|
|
585
|
+
log_step "Downloading video..."
|
|
586
|
+
|
|
587
|
+
# Ensure output directory exists
|
|
588
|
+
local output_dir=$(dirname "$OUTPUT_PATH")
|
|
589
|
+
mkdir -p "$output_dir"
|
|
590
|
+
|
|
591
|
+
# Download video
|
|
592
|
+
if curl -s -o "$OUTPUT_PATH" "$SERVER_URL/download/$JOB_ID"; then
|
|
593
|
+
local file_size=$(ls -lh "$OUTPUT_PATH" | awk '{print $5}')
|
|
594
|
+
log_success "Video downloaded: $OUTPUT_PATH"
|
|
595
|
+
log_info "File size: $file_size"
|
|
596
|
+
else
|
|
597
|
+
log_error "Failed to download video"
|
|
598
|
+
exit 1
|
|
599
|
+
fi
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
# ============================================================================
|
|
603
|
+
# Main Pipeline
|
|
604
|
+
# ============================================================================
|
|
605
|
+
|
|
606
|
+
main() {
|
|
607
|
+
echo ""
|
|
608
|
+
echo "🎬 Replay Render Pipeline"
|
|
609
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
610
|
+
echo ""
|
|
611
|
+
|
|
612
|
+
# Check dependencies
|
|
613
|
+
check_dependencies
|
|
614
|
+
|
|
615
|
+
# Load environment
|
|
616
|
+
load_env
|
|
617
|
+
|
|
618
|
+
# Parse arguments
|
|
619
|
+
parse_args "$@"
|
|
620
|
+
|
|
621
|
+
# Check server
|
|
622
|
+
if ! check_server; then
|
|
623
|
+
if [ "$START_SERVER" = true ]; then
|
|
624
|
+
start_server || exit 1
|
|
625
|
+
else
|
|
626
|
+
log_error "Server is not running. Use --start-server to start it automatically"
|
|
627
|
+
exit 1
|
|
628
|
+
fi
|
|
629
|
+
fi
|
|
630
|
+
|
|
631
|
+
# Parse manifest
|
|
632
|
+
parse_manifest
|
|
633
|
+
|
|
634
|
+
# Check bundle
|
|
635
|
+
if ! check_bundle; then
|
|
636
|
+
preload_bundle
|
|
637
|
+
fi
|
|
638
|
+
|
|
639
|
+
# Submit render job
|
|
640
|
+
submit_render_job
|
|
641
|
+
|
|
642
|
+
# Poll status
|
|
643
|
+
poll_job_status
|
|
644
|
+
|
|
645
|
+
# Download video
|
|
646
|
+
download_video
|
|
647
|
+
|
|
648
|
+
echo ""
|
|
649
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
650
|
+
log_success "${BOLD}Pipeline completed successfully!${NC}"
|
|
651
|
+
echo ""
|
|
652
|
+
log_info "Output: $OUTPUT_PATH"
|
|
653
|
+
echo ""
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
# Run main function with all arguments
|
|
657
|
+
main "$@"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
SERVER_URL="http://localhost:3001"
|
|
4
|
+
BUNDLE_ID="game-jump-arena"
|
|
5
|
+
BUNDLE_URL="https://agent-foundry.oss-cn-beijing.aliyuncs.com/studio-bundles/92922750-9ea6-4fc7-aeef-a13b8cc9eae5/game-jump-arena/2026.01.24-160727/bundle-1769270846565.tar.gz"
|
|
6
|
+
|
|
7
|
+
set -x
|
|
8
|
+
|
|
9
|
+
payload=$(jq -n \
|
|
10
|
+
--arg bundleId "$BUNDLE_ID" \
|
|
11
|
+
--arg bundleUrl "$BUNDLE_URL" \
|
|
12
|
+
'{bundleId: $bundleId, bundleUrl: $bundleUrl}')
|
|
13
|
+
|
|
14
|
+
curl -s -X POST "$SERVER_URL/api/bundles/preload" \
|
|
15
|
+
-H "Content-Type: application/json" \
|
|
16
|
+
-d "$payload"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
sleep 5
|
|
20
|
+
curl -s "$SERVER_URL/api/bundles"
|