@testdriverai/runner 7.8.0-canary.13 → 7.8.0-canary.15
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/lib/ably-service.js +41 -3
- package/lib/automation.js +10 -5
- package/package.json +1 -1
- package/scripts-desktop/start-agent.sh +105 -0
package/lib/ably-service.js
CHANGED
|
@@ -275,6 +275,9 @@ class AblyService extends EventEmitter {
|
|
|
275
275
|
|
|
276
276
|
this.emit('log', `Command received: ${type} (requestId=${requestId})`);
|
|
277
277
|
|
|
278
|
+
// Stop re-publishing runner.ready once we get the first command
|
|
279
|
+
this._stopReadySignal();
|
|
280
|
+
|
|
278
281
|
// Per-command timeout: use message.timeout if provided, else default 120s
|
|
279
282
|
// Prevents hanging forever if screenshot capture or S3 upload stalls
|
|
280
283
|
const commandTimeout = (message.timeout && message.timeout > 0)
|
|
@@ -415,8 +418,8 @@ class AblyService extends EventEmitter {
|
|
|
415
418
|
|
|
416
419
|
// Detect discontinuity: channel re-attached but message continuity was lost.
|
|
417
420
|
// Use historyBeforeSubscribe() on each subscription to recover missed messages.
|
|
418
|
-
if (current === 'attached' && stateChange.resumed === false && previous) {
|
|
419
|
-
this.emit('log', `Ably channel [session]: DISCONTINUITY (resumed=false)${reasonMsg ? ' — ' + reasonMsg : ''}`);
|
|
421
|
+
if (current === 'attached' && stateChange.resumed === false && previous === 'attached') {
|
|
422
|
+
this.emit('log', `Ably channel [session]: DISCONTINUITY (resumed=false)${reasonMsg ? ' — ' + reasonMsg : ''}`);
|
|
420
423
|
|
|
421
424
|
Sentry.withScope((scope) => {
|
|
422
425
|
scope.setTag('ably.client', 'runner');
|
|
@@ -459,7 +462,7 @@ class AblyService extends EventEmitter {
|
|
|
459
462
|
// Signal readiness to SDK — commands sent before this would be lost
|
|
460
463
|
const readyPayload = {
|
|
461
464
|
type: 'runner.ready',
|
|
462
|
-
os: 'windows',
|
|
465
|
+
os: process.platform === 'win32' ? 'windows' : 'linux',
|
|
463
466
|
sandboxId: this._sandboxId,
|
|
464
467
|
runnerVersion: getLocalVersion() || 'unknown',
|
|
465
468
|
timestamp: Date.now(),
|
|
@@ -473,6 +476,39 @@ class AblyService extends EventEmitter {
|
|
|
473
476
|
}
|
|
474
477
|
await this._sessionChannel.publish('control', readyPayload);
|
|
475
478
|
this.emit('log', 'Published runner.ready signal');
|
|
479
|
+
|
|
480
|
+
// Re-publish runner.ready every 3s for up to 60s.
|
|
481
|
+
// The SDK may connect after the first publish (race condition),
|
|
482
|
+
// and Ably channel history may not be enabled. Repeating ensures
|
|
483
|
+
// the SDK catches at least one live runner.ready message.
|
|
484
|
+
this._readyInterval = setInterval(async () => {
|
|
485
|
+
try {
|
|
486
|
+
readyPayload.timestamp = Date.now();
|
|
487
|
+
await this._sessionChannel.publish('control', readyPayload);
|
|
488
|
+
this.emit('log', 'Re-published runner.ready signal');
|
|
489
|
+
} catch (err) {
|
|
490
|
+
this.emit('log', `Failed to re-publish runner.ready: ${err.message}`);
|
|
491
|
+
}
|
|
492
|
+
}, 3000);
|
|
493
|
+
|
|
494
|
+
// Stop after 60s regardless
|
|
495
|
+
this._readyTimeout = setTimeout(() => {
|
|
496
|
+
this._stopReadySignal();
|
|
497
|
+
}, 60000);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Stop the repeated runner.ready signal (called on first command or after timeout).
|
|
502
|
+
*/
|
|
503
|
+
_stopReadySignal() {
|
|
504
|
+
if (this._readyInterval) {
|
|
505
|
+
clearInterval(this._readyInterval);
|
|
506
|
+
this._readyInterval = null;
|
|
507
|
+
}
|
|
508
|
+
if (this._readyTimeout) {
|
|
509
|
+
clearTimeout(this._readyTimeout);
|
|
510
|
+
this._readyTimeout = null;
|
|
511
|
+
}
|
|
476
512
|
}
|
|
477
513
|
|
|
478
514
|
/**
|
|
@@ -617,6 +653,8 @@ class AblyService extends EventEmitter {
|
|
|
617
653
|
async close() {
|
|
618
654
|
this.emit('log', 'Closing Ably service...');
|
|
619
655
|
|
|
656
|
+
this._stopReadySignal();
|
|
657
|
+
|
|
620
658
|
if (this._statsInterval) {
|
|
621
659
|
clearInterval(this._statsInterval);
|
|
622
660
|
this._statsInterval = null;
|
package/lib/automation.js
CHANGED
|
@@ -45,8 +45,10 @@ const API_KEY = process.env.TD_API_KEY;
|
|
|
45
45
|
// shell injection and escaping issues.
|
|
46
46
|
|
|
47
47
|
const PYTHON = IS_WINDOWS ? 'python' : 'python3';
|
|
48
|
+
// On Linux, ensure DISPLAY is set (use env var or fallback to :0)
|
|
49
|
+
// The os.environ.get() preserves the parent's DISPLAY setting for E2B's :1 display
|
|
48
50
|
const PY_IMPORT = IS_LINUX
|
|
49
|
-
? "import os; os.environ
|
|
51
|
+
? "import os; os.environ.setdefault('DISPLAY', ':0'); import pyautogui, sys; pyautogui.FAILSAFE = False; "
|
|
50
52
|
: 'import pyautogui, sys; pyautogui.FAILSAFE = False; ';
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -525,11 +527,14 @@ class Automation extends EventEmitter {
|
|
|
525
527
|
const timeout = Math.ceil((data.timeout || 300000) / 1000); // ms to seconds
|
|
526
528
|
const requestId = data.requestId;
|
|
527
529
|
|
|
528
|
-
// Buffer stdout chunks to ~
|
|
530
|
+
// Buffer stdout chunks to ~32KB before emitting over Ably.
|
|
529
531
|
// This reduces message count while keeping each message well under
|
|
530
|
-
// Ably's 64KB limit.
|
|
531
|
-
//
|
|
532
|
-
|
|
532
|
+
// Ably's 64KB limit. 32KB leaves headroom for the JSON envelope +
|
|
533
|
+
// string escaping while halving the message count vs the previous
|
|
534
|
+
// 16KB size, helping avoid Ably's per-channel rate limit on verbose
|
|
535
|
+
// commands. The SDK accumulates these chunks and reconstructs the
|
|
536
|
+
// full stdout — the final response only carries returncode + stderr.
|
|
537
|
+
const CHUNK_FLUSH_SIZE = 32 * 1024; // 32KB
|
|
533
538
|
let chunkBuffer = '';
|
|
534
539
|
const flushChunkBuffer = () => {
|
|
535
540
|
if (chunkBuffer.length > 0) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ─── TestDriver Sandbox Agent Startup ────────────────────────────────────────
|
|
3
|
+
# Starts the sandbox-agent.js (Ably-based automation agent) inside the E2B
|
|
4
|
+
# sandbox. This script is called by the API after writing the config file
|
|
5
|
+
# to /tmp/testdriver-agent.json.
|
|
6
|
+
#
|
|
7
|
+
# This matches the Windows runner pattern: the agent runs locally on the
|
|
8
|
+
# sandbox and executes commands via pyautogui (instead of @e2b/desktop RPC).
|
|
9
|
+
#
|
|
10
|
+
# Usage: bash /opt/testdriver-runner/scripts-desktop/start-agent.sh [&]
|
|
11
|
+
#
|
|
12
|
+
# Prerequisites:
|
|
13
|
+
# - Desktop environment running (start-desktop.sh completed)
|
|
14
|
+
# - Config file at /tmp/testdriver-agent.json with Ably credentials
|
|
15
|
+
# - Node.js installed
|
|
16
|
+
# - Runner installed at /opt/testdriver-runner
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
export DISPLAY="${DISPLAY:-:0}"
|
|
21
|
+
export XAUTHORITY="${XAUTHORITY:-${HOME}/.Xauthority}"
|
|
22
|
+
|
|
23
|
+
RUNNER_DIR="/opt/testdriver-runner"
|
|
24
|
+
CONFIG_PATH="/tmp/testdriver-agent.json"
|
|
25
|
+
LOG_FILE="/tmp/sandbox-agent.log"
|
|
26
|
+
PID_FILE="/tmp/sandbox-agent.pid"
|
|
27
|
+
|
|
28
|
+
log() {
|
|
29
|
+
echo "[$(date -Iseconds)] [start-agent] $1" | tee -a "$LOG_FILE"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ─── Check if already running ─────────────────────────────────────────────────
|
|
33
|
+
if [ -f "$PID_FILE" ]; then
|
|
34
|
+
existing_pid=$(cat "$PID_FILE")
|
|
35
|
+
if kill -0 "$existing_pid" 2>/dev/null; then
|
|
36
|
+
log "Agent already running (PID: $existing_pid), exiting"
|
|
37
|
+
exit 0
|
|
38
|
+
else
|
|
39
|
+
log "Stale PID file found, removing"
|
|
40
|
+
rm -f "$PID_FILE"
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# ─── Verify prerequisites ─────────────────────────────────────────────────────
|
|
45
|
+
if [ ! -d "$RUNNER_DIR" ]; then
|
|
46
|
+
log "ERROR: Runner not found at $RUNNER_DIR"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if [ ! -f "$RUNNER_DIR/sandbox-agent.js" ]; then
|
|
51
|
+
log "ERROR: sandbox-agent.js not found in $RUNNER_DIR"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if ! command -v node &> /dev/null; then
|
|
56
|
+
log "ERROR: Node.js not installed"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# ─── Wait for config file (with timeout) ─────────────────────────────────────
|
|
61
|
+
# The API writes the config file before calling this script, but we add a
|
|
62
|
+
# brief wait just in case there's any race condition.
|
|
63
|
+
WAIT_TIMEOUT=30
|
|
64
|
+
WAIT_INTERVAL=1
|
|
65
|
+
elapsed=0
|
|
66
|
+
|
|
67
|
+
log "Waiting for config file: $CONFIG_PATH"
|
|
68
|
+
while [ ! -f "$CONFIG_PATH" ] && [ $elapsed -lt $WAIT_TIMEOUT ]; do
|
|
69
|
+
sleep $WAIT_INTERVAL
|
|
70
|
+
elapsed=$((elapsed + WAIT_INTERVAL))
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
if [ ! -f "$CONFIG_PATH" ]; then
|
|
74
|
+
log "ERROR: Config file not found after ${WAIT_TIMEOUT}s: $CONFIG_PATH"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
log "Config file found"
|
|
79
|
+
|
|
80
|
+
# ─── Start the agent ──────────────────────────────────────────────────────────
|
|
81
|
+
log "Starting sandbox-agent.js..."
|
|
82
|
+
log "DISPLAY=$DISPLAY, RUNNER_DIR=$RUNNER_DIR"
|
|
83
|
+
|
|
84
|
+
# Run in background, redirect output to log file
|
|
85
|
+
cd "$RUNNER_DIR"
|
|
86
|
+
nohup node sandbox-agent.js >> "$LOG_FILE" 2>&1 &
|
|
87
|
+
AGENT_PID=$!
|
|
88
|
+
|
|
89
|
+
# Write PID file for process management
|
|
90
|
+
echo "$AGENT_PID" > "$PID_FILE"
|
|
91
|
+
|
|
92
|
+
log "Agent started (PID: $AGENT_PID)"
|
|
93
|
+
log "Log file: $LOG_FILE"
|
|
94
|
+
|
|
95
|
+
# Brief pause to catch any immediate startup errors
|
|
96
|
+
sleep 2
|
|
97
|
+
|
|
98
|
+
if kill -0 "$AGENT_PID" 2>/dev/null; then
|
|
99
|
+
log "Agent running successfully"
|
|
100
|
+
exit 0
|
|
101
|
+
else
|
|
102
|
+
log "ERROR: Agent exited unexpectedly. Check $LOG_FILE for details"
|
|
103
|
+
tail -20 "$LOG_FILE" | while read line; do log " $line"; done
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|