@kritchoff/agent-browser 0.9.46 โ 0.9.49
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/README.sdk.md +89 -37
- package/dist/orchestrator.d.ts +14 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +225 -0
- package/dist/orchestrator.js.map +1 -0
- package/package.json +1 -1
- package/sdk.sh +0 -20
- package/start.sh +15 -6
package/README.sdk.md
CHANGED
|
@@ -1,77 +1,129 @@
|
|
|
1
1
|
# @wootzapp/agent-browser SDK
|
|
2
2
|
|
|
3
|
-
The official SDK for controlling the WootzApp Agent Browser environment.
|
|
3
|
+
The official Node.js SDK for controlling the WootzApp Agent Browser environment.
|
|
4
|
+
|
|
5
|
+
This SDK provides a **Real Android Browser** (WootzApp) wrapped in a Docker container, controlled by a high-speed Playwright daemon. It is specifically designed for AI Agents to navigate the mobile web, bypassing bot detection, and generating LLM-friendly semantic trees (AXTree).
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
9
|
+
- **Real Mobile Environment**: Full Android 14 OS with Touch Events and Mobile Viewports.
|
|
10
|
+
- **Zero-Config Setup**: The SDK automatically downloads and orchestrates the required Docker containers.
|
|
11
|
+
- **Hyper-Speed Warm Boots**: Uses advanced VDI Volume Mounting to boot the environment in **< 5 seconds** after the first run.
|
|
12
|
+
- **Fast Resets**: Cleans the browser state via Android userspace reboot in **~15 seconds**.
|
|
13
|
+
- **Playwright Parity**: Control the mobile browser using standard Playwright commands (`click`, `type`, `waitForSelector`).
|
|
14
|
+
- **Semantic AXTree**: Built-in `snapshot()` method generates a clean, text-based UI tree optimized for LLM reasoning.
|
|
15
|
+
|
|
16
|
+
---
|
|
11
17
|
|
|
12
18
|
## Prerequisites
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
-
|
|
20
|
+
1. **Docker Engine**: Must be installed and running.
|
|
21
|
+
- *Linux Users*: Ensure your user is in the `docker` group (`sudo usermod -aG docker $USER`).
|
|
22
|
+
2. **Node.js**: v18+ is required.
|
|
23
|
+
|
|
24
|
+
---
|
|
16
25
|
|
|
17
26
|
## Installation
|
|
18
27
|
|
|
28
|
+
Install the SDK in your project:
|
|
29
|
+
|
|
19
30
|
```bash
|
|
20
31
|
npm install @kritchoff/agent-browser
|
|
21
32
|
```
|
|
22
33
|
|
|
23
|
-
|
|
34
|
+
*(Optional but recommended)* Install `tsx` to run TypeScript files natively:
|
|
35
|
+
```bash
|
|
36
|
+
npm install -D tsx
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start Guide
|
|
42
|
+
|
|
43
|
+
Create a file named `agent.ts`:
|
|
24
44
|
|
|
25
45
|
```typescript
|
|
26
46
|
import { WootzAgent } from '@kritchoff/agent-browser';
|
|
27
47
|
|
|
28
48
|
async function main() {
|
|
49
|
+
// 1. Initialize the controller
|
|
29
50
|
const agent = new WootzAgent();
|
|
30
51
|
|
|
31
|
-
console.log('
|
|
32
|
-
// First run:
|
|
33
|
-
//
|
|
52
|
+
console.log('๐ Booting Environment...');
|
|
53
|
+
// First run: Downloads 3GB image and cold boots (~90s).
|
|
54
|
+
// Next run: Instant Hyper-Speed Warm Boot (~5s).
|
|
34
55
|
await agent.start();
|
|
35
56
|
|
|
36
|
-
console.log('
|
|
37
|
-
await agent.
|
|
57
|
+
console.log('๐ Navigating to Google...');
|
|
58
|
+
await agent.navigate('https://google.com');
|
|
38
59
|
|
|
39
|
-
|
|
60
|
+
console.log('๐ธ Capturing Semantic Tree for LLM...');
|
|
61
|
+
const uiTree = await agent.snapshot();
|
|
62
|
+
console.log(uiTree);
|
|
40
63
|
|
|
41
|
-
console.log('
|
|
42
|
-
|
|
43
|
-
await agent.
|
|
64
|
+
console.log('โจ๏ธ Typing and Searching...');
|
|
65
|
+
await agent.type('textarea[name="q"]', 'WootzApp AI');
|
|
66
|
+
await agent.press('Enter');
|
|
44
67
|
|
|
45
|
-
console.log('
|
|
68
|
+
console.log('๐งน Fast Reset for next task...');
|
|
69
|
+
// Wipes all tabs, cookies, and cache in ~15s
|
|
70
|
+
await agent.reset();
|
|
71
|
+
|
|
72
|
+
console.log('๐ Shutting down...');
|
|
73
|
+
// Completely destroys containers and releases ports
|
|
46
74
|
await agent.stop();
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
main().catch(console.error);
|
|
50
78
|
```
|
|
51
79
|
|
|
52
|
-
|
|
80
|
+
Run your agent:
|
|
81
|
+
```bash
|
|
82
|
+
npx tsx agent.ts
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## CLI Usage (Global Install)
|
|
88
|
+
|
|
89
|
+
You can also use the SDK directly from your terminal to debug or control the browser manually.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install -g @kritchoff/agent-browser
|
|
93
|
+
|
|
94
|
+
# Start the environment
|
|
95
|
+
agent-browser start
|
|
96
|
+
|
|
97
|
+
# Run commands
|
|
98
|
+
agent-browser navigate https://news.ycombinator.com
|
|
99
|
+
agent-browser click ".titleline a"
|
|
100
|
+
agent-browser snapshot
|
|
101
|
+
|
|
102
|
+
# Clean the browser
|
|
103
|
+
agent-browser reset
|
|
104
|
+
|
|
105
|
+
# Stop
|
|
106
|
+
agent-browser stop
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
53
110
|
|
|
54
|
-
|
|
55
|
-
2. **Subsequent Runs**: The SDK detects the snapshot and performs a "Warm Boot", injecting the saved state directly into memory. This bypasses the Android boot sequence.
|
|
56
|
-
3. **Reset**: Instead of restarting the container, `agent.reset()` triggers an Android userspace reboot, which is significantly faster than a full system restart.
|
|
111
|
+
## Troubleshooting
|
|
57
112
|
|
|
58
|
-
|
|
113
|
+
### `Error: Failed to connect to agent daemon (ECONNREFUSED)`
|
|
114
|
+
- **Cause**: The container failed to bind port `32001` to your host machine.
|
|
115
|
+
- **Fix**: Run `agent.stop()` or `docker rm -f $(docker ps -aq)` to clear old/stuck containers, then run `agent.start()` again. The SDK has built-in self-healing, but a manual hard reset always works.
|
|
59
116
|
|
|
60
|
-
### `
|
|
61
|
-
|
|
62
|
-
-
|
|
117
|
+
### `net::ERR_NAME_NOT_RESOLVED`
|
|
118
|
+
- **Cause**: The Android Emulator temporarily lost its internet connection after a Warm Boot.
|
|
119
|
+
- **Fix**: The SDK automatically toggles Airplane Mode to fix this, but if it persists, ensure your host machine has a stable internet connection before starting the agent.
|
|
63
120
|
|
|
64
|
-
### `
|
|
65
|
-
|
|
121
|
+
### `Selector "..." matched X elements (Strict Mode Violation)`
|
|
122
|
+
- **Cause**: Playwright requires selectors to point to exactly one element.
|
|
123
|
+
- **Fix**: Use more specific selectors, or use Playwright's `>> nth=0` pseudo-selector to pick the first match (e.g., `agent.click('a >> nth=0')`).
|
|
66
124
|
|
|
67
|
-
|
|
68
|
-
Stops the Docker containers gracefully.
|
|
125
|
+
---
|
|
69
126
|
|
|
70
|
-
|
|
71
|
-
Performs a fast reset of the browser environment without stopping the container. Use this between agent tasks to ensure a clean state.
|
|
127
|
+
## Next Steps
|
|
72
128
|
|
|
73
|
-
|
|
74
|
-
Executes a raw agent command.
|
|
75
|
-
- `agent.command('open', 'url')`
|
|
76
|
-
- `agent.command('click', '@e1')`
|
|
77
|
-
- `agent.command('type', '@e2', 'hello')`
|
|
129
|
+
For a complete list of all available commands (clicking, typing, tabbing, network interception), please read the [COMMANDS.md](./COMMANDS.md) file.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class Orchestrator {
|
|
2
|
+
private composeFile;
|
|
3
|
+
constructor(distMode: boolean);
|
|
4
|
+
private log;
|
|
5
|
+
private runCommand;
|
|
6
|
+
private getContainerId;
|
|
7
|
+
private isPortMapped;
|
|
8
|
+
private pullImagesWithRetry;
|
|
9
|
+
start(): Promise<void>;
|
|
10
|
+
stop(): Promise<void>;
|
|
11
|
+
reset(): Promise<void>;
|
|
12
|
+
private waitForLog;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAUA,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAS;gBAEhB,QAAQ,EAAE,OAAO;IAS7B,OAAO,CAAC,GAAG;IAWX,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,YAAY;YASN,mBAAmB;IAkBpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoFtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4DnC,OAAO,CAAC,UAAU;CAmBnB"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
7
|
+
const CACHE_DIR = path.join(PROJECT_ROOT, 'cache');
|
|
8
|
+
const SNAPSHOT_DIR = path.join(CACHE_DIR, 'snapshots', 'quickboot');
|
|
9
|
+
export class Orchestrator {
|
|
10
|
+
composeFile;
|
|
11
|
+
constructor(distMode) {
|
|
12
|
+
this.composeFile = path.join(PROJECT_ROOT, distMode ? 'docker-compose.sdk.yml' : 'docker-compose.prod.yml');
|
|
13
|
+
// Ensure cache dir exists
|
|
14
|
+
if (!fs.existsSync(path.join(CACHE_DIR, 'snapshots'))) {
|
|
15
|
+
fs.mkdirSync(path.join(CACHE_DIR, 'snapshots'), { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
log(msg, level = 'info') {
|
|
19
|
+
const colors = {
|
|
20
|
+
info: '\x1b[34m', // Blue
|
|
21
|
+
success: '\x1b[32m', // Green
|
|
22
|
+
warn: '\x1b[33m', // Yellow
|
|
23
|
+
error: '\x1b[31m', // Red
|
|
24
|
+
reset: '\x1b[0m'
|
|
25
|
+
};
|
|
26
|
+
console.log(`${colors[level]}[SDK]${colors.reset} ${msg}`);
|
|
27
|
+
}
|
|
28
|
+
runCommand(cmd, env = {}) {
|
|
29
|
+
try {
|
|
30
|
+
execSync(cmd, {
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
env: { ...process.env, ...env }
|
|
33
|
+
});
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
getContainerId(serviceName) {
|
|
41
|
+
try {
|
|
42
|
+
const output = execSync(`docker compose -f "${this.composeFile}" ps -q ${serviceName}`, { encoding: 'utf-8' });
|
|
43
|
+
return output.trim() || null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
isPortMapped(containerId, port) {
|
|
50
|
+
try {
|
|
51
|
+
execSync(`docker port "${containerId}" ${port}`, { stdio: 'ignore' });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async pullImagesWithRetry() {
|
|
59
|
+
if (!this.composeFile.endsWith('docker-compose.sdk.yml'))
|
|
60
|
+
return;
|
|
61
|
+
this.log("Please wait while we get things ready...", 'info');
|
|
62
|
+
const maxRetries = 10;
|
|
63
|
+
for (let count = 0; count < maxRetries; count++) {
|
|
64
|
+
if (this.runCommand(`docker compose -f "${this.composeFile}" pull`)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.log(`Download failed/interrupted. Retrying (${count + 1}/${maxRetries}) in 10s...`, 'warn');
|
|
68
|
+
await new Promise(r => setTimeout(r, 10000));
|
|
69
|
+
}
|
|
70
|
+
this.log(`Failed to download images after ${maxRetries} attempts. Check internet connection.`, 'error');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
async start() {
|
|
74
|
+
this.log(`Initializing Agent Environment...`, 'info');
|
|
75
|
+
// 1. Pull Images (Robustness)
|
|
76
|
+
await this.pullImagesWithRetry();
|
|
77
|
+
// 2. Clean previous state to prevent network namespace corruption
|
|
78
|
+
this.log("Cleaning up previous container state...", 'info');
|
|
79
|
+
this.runCommand(`docker compose -f "${this.composeFile}" down -v --remove-orphans`);
|
|
80
|
+
const hasSnapshot = fs.existsSync(SNAPSHOT_DIR);
|
|
81
|
+
if (hasSnapshot) {
|
|
82
|
+
// === WARM START ===
|
|
83
|
+
this.log("Found cached baseline snapshot. Performing HYPER-SPEED WARM BOOT...", 'success');
|
|
84
|
+
const success = this.runCommand(`docker compose -f "${this.composeFile}" up -d`, {
|
|
85
|
+
EMULATOR_SNAPSHOT_NAME: 'quickboot'
|
|
86
|
+
});
|
|
87
|
+
if (!success)
|
|
88
|
+
throw new Error("Startup failed during docker compose up.");
|
|
89
|
+
// Verify Port Mapping
|
|
90
|
+
const agentCont = this.getContainerId('agent-service');
|
|
91
|
+
if (agentCont && !this.isPortMapped(agentCont, 3000)) {
|
|
92
|
+
this.log("Port 3000 (Host 32001) is not mapped! Container config is stale.", 'warn');
|
|
93
|
+
this.log("Forcing full restart to apply network settings...", 'info');
|
|
94
|
+
this.runCommand(`docker compose -f "${this.composeFile}" down -v --remove-orphans`);
|
|
95
|
+
await new Promise(r => setTimeout(r, 5000)); // Wait for OS port release
|
|
96
|
+
this.runCommand(`docker compose -f "${this.composeFile}" up -d`, {
|
|
97
|
+
EMULATOR_SNAPSHOT_NAME: 'quickboot'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// === COLD START ===
|
|
103
|
+
this.log("No baseline found. Performing FIRST RUN SETUP (Cold Boot)...", 'warn');
|
|
104
|
+
this.log("This will take ~60-90 seconds, but only once.", 'warn');
|
|
105
|
+
const success = this.runCommand(`docker compose -f "${this.composeFile}" up -d`, {
|
|
106
|
+
EMULATOR_SNAPSHOT_NAME: ''
|
|
107
|
+
});
|
|
108
|
+
if (!success)
|
|
109
|
+
throw new Error("Startup failed during docker compose up.");
|
|
110
|
+
let androidCont = this.getContainerId('android-service');
|
|
111
|
+
const agentCont = this.getContainerId('agent-service');
|
|
112
|
+
if (!androidCont)
|
|
113
|
+
throw new Error("Error: Android container not found.");
|
|
114
|
+
// Verify Port Mapping
|
|
115
|
+
if (agentCont && !this.isPortMapped(agentCont, 3000)) {
|
|
116
|
+
this.log("Port 3000 (Host 32001) is not mapped! Container config is stale.", 'warn');
|
|
117
|
+
this.log("Forcing full restart to apply network settings...", 'info');
|
|
118
|
+
this.runCommand(`docker compose -f "${this.composeFile}" down -v --remove-orphans`);
|
|
119
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
120
|
+
this.runCommand(`docker compose -f "${this.composeFile}" up -d`, {
|
|
121
|
+
EMULATOR_SNAPSHOT_NAME: ''
|
|
122
|
+
});
|
|
123
|
+
androidCont = this.getContainerId('android-service') || androidCont;
|
|
124
|
+
}
|
|
125
|
+
// Wait for Android to boot fully
|
|
126
|
+
this.log("Waiting for Android OS to boot (this takes a moment)...", 'info');
|
|
127
|
+
await this.waitForLog(androidCont, "Emulator boot complete");
|
|
128
|
+
this.log("Waiting for Browser Installation...", 'info');
|
|
129
|
+
await this.waitForLog(androidCont, "APK installation complete");
|
|
130
|
+
this.log("Waiting for CDP Bridge...", 'info');
|
|
131
|
+
await this.waitForLog(androidCont, "CDP Bridge ready");
|
|
132
|
+
// Save Snapshot
|
|
133
|
+
this.log("Saving emulator state (quickboot)...", 'info');
|
|
134
|
+
const saved = this.runCommand(`docker exec "${androidCont}" adb emu avd snapshot save quickboot`);
|
|
135
|
+
if (saved) {
|
|
136
|
+
this.log("Snapshot saved to host volume.", 'success');
|
|
137
|
+
this.log("Setup Complete! Future runs will launch instantly.", 'success');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw new Error("Failed to save snapshot inside emulator.");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async stop() {
|
|
145
|
+
this.log("Stopping environment...", 'info');
|
|
146
|
+
this.runCommand(`docker compose -f "${this.composeFile}" down -v --remove-orphans`);
|
|
147
|
+
this.log("Environment cleaned and stopped.", 'success');
|
|
148
|
+
}
|
|
149
|
+
async reset() {
|
|
150
|
+
this.log("Performing Fast Browser Reset...", 'info');
|
|
151
|
+
const androidCont = this.getContainerId('android-service');
|
|
152
|
+
if (!androidCont) {
|
|
153
|
+
this.log("Android container not running.", 'error');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.log("Initiating fast reset (userspace reboot)...", 'info');
|
|
157
|
+
try {
|
|
158
|
+
execSync(`docker exec "${androidCont}" adb shell reboot userspace`, { stdio: 'ignore' });
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Ignored, command drops connection
|
|
162
|
+
}
|
|
163
|
+
this.log("Waiting for device to come online...", 'info');
|
|
164
|
+
const start = Date.now();
|
|
165
|
+
let online = false;
|
|
166
|
+
while (Date.now() - start < 30000) {
|
|
167
|
+
try {
|
|
168
|
+
const state = execSync(`docker exec "${androidCont}" adb get-state`, { encoding: 'utf-8' }).trim();
|
|
169
|
+
if (state === 'device') {
|
|
170
|
+
execSync(`docker exec "${androidCont}" adb shell echo ok`, { stdio: 'ignore' });
|
|
171
|
+
online = true;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Still offline
|
|
177
|
+
}
|
|
178
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
179
|
+
}
|
|
180
|
+
if (!online)
|
|
181
|
+
throw new Error("Timeout waiting for device after reboot");
|
|
182
|
+
this.log("Device online", 'success');
|
|
183
|
+
this.log("Waiting for browser CDP...", 'info');
|
|
184
|
+
const cdpStart = Date.now();
|
|
185
|
+
let cdpReady = false;
|
|
186
|
+
while (Date.now() - cdpStart < 30000) {
|
|
187
|
+
try {
|
|
188
|
+
execSync(`docker exec "${androidCont}" curl -s --connect-timeout 2 http://localhost:9224/json/version`, { stdio: 'ignore' });
|
|
189
|
+
cdpReady = true;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// If it takes too long, try forcing the browser to open
|
|
194
|
+
if (Date.now() - cdpStart > 15000) {
|
|
195
|
+
try {
|
|
196
|
+
execSync(`docker exec "${androidCont}" adb shell am start -n com.wootzapp.web/com.aspect.chromium.ChromiumMain -a android.intent.action.VIEW -d 'about:blank'`, { stdio: 'ignore' });
|
|
197
|
+
}
|
|
198
|
+
catch { }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
202
|
+
}
|
|
203
|
+
if (!cdpReady)
|
|
204
|
+
throw new Error("Timeout waiting for CDP");
|
|
205
|
+
this.log("Fast reset complete!", 'success');
|
|
206
|
+
}
|
|
207
|
+
waitForLog(container, pattern) {
|
|
208
|
+
return new Promise((resolve) => {
|
|
209
|
+
const tail = spawn('docker', ['logs', '-f', container]);
|
|
210
|
+
tail.stdout.on('data', (data) => {
|
|
211
|
+
if (data.toString().includes(pattern)) {
|
|
212
|
+
tail.kill();
|
|
213
|
+
resolve();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
tail.stderr.on('data', (data) => {
|
|
217
|
+
if (data.toString().includes(pattern)) {
|
|
218
|
+
tail.kill();
|
|
219
|
+
resolve();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/sdk.sh
CHANGED
|
@@ -89,16 +89,6 @@ cmd_start() {
|
|
|
89
89
|
log_success "Found cached baseline snapshot. Performing HYPER-SPEED WARM BOOT..."
|
|
90
90
|
export EMULATOR_SNAPSHOT_NAME="quickboot"
|
|
91
91
|
"$SDK_ROOT/start.sh"
|
|
92
|
-
|
|
93
|
-
cd "$SDK_ROOT"
|
|
94
|
-
AGENT_CONT=$(docker compose ps -q agent-service)
|
|
95
|
-
if [ -n "$AGENT_CONT" ] && ! docker port "$AGENT_CONT" 3000 >/dev/null 2>&1; then
|
|
96
|
-
log_warn "Port 3000 (Host 32001) is not mapped! Container config is stale."
|
|
97
|
-
log_info "Forcing full restart to apply network settings..."
|
|
98
|
-
docker compose -f "$COMPOSE_FILE" down -v --remove-orphans
|
|
99
|
-
sleep 5
|
|
100
|
-
"$SDK_ROOT/start.sh"
|
|
101
|
-
fi
|
|
102
92
|
else
|
|
103
93
|
# === COLD START & FREEZE ===
|
|
104
94
|
log_warn "No baseline found. Performing FIRST RUN SETUP (Cold Boot)..."
|
|
@@ -117,22 +107,12 @@ cmd_start() {
|
|
|
117
107
|
|
|
118
108
|
cd "$SDK_ROOT"
|
|
119
109
|
CONTAINER=$(docker compose ps -q android-service)
|
|
120
|
-
AGENT_CONT=$(docker compose ps -q agent-service)
|
|
121
110
|
|
|
122
111
|
if [ -z "$CONTAINER" ]; then
|
|
123
112
|
log_error "Error: Android container not found."
|
|
124
113
|
exit 1
|
|
125
114
|
fi
|
|
126
115
|
|
|
127
|
-
if ! docker port "$AGENT_CONT" 3000 >/dev/null 2>&1; then
|
|
128
|
-
log_warn "Port 3000 (Host 32001) is not mapped! Container config is stale."
|
|
129
|
-
log_info "Forcing full restart to apply network settings..."
|
|
130
|
-
docker compose -f "$COMPOSE_FILE" down -v --remove-orphans
|
|
131
|
-
sleep 5
|
|
132
|
-
"$SDK_ROOT/start.sh"
|
|
133
|
-
CONTAINER=$(docker compose ps -q android-service)
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
116
|
log_info "Saving emulator state (quickboot)..."
|
|
137
117
|
if docker exec "$CONTAINER" adb emu avd snapshot save quickboot; then
|
|
138
118
|
log_success "Snapshot saved to host volume."
|
package/start.sh
CHANGED
|
@@ -64,19 +64,28 @@ wait_for_log() {
|
|
|
64
64
|
|
|
65
65
|
echo -n " Waiting for $label... "
|
|
66
66
|
until docker logs "$container" 2>&1 | grep -q "$pattern"; do
|
|
67
|
-
sleep
|
|
67
|
+
sleep 1 # Faster polling
|
|
68
68
|
done
|
|
69
69
|
echo -e "${GREEN}Done!${NC}"
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
wait_for_log "$ANDROID_CONTAINER" "
|
|
75
|
-
|
|
72
|
+
if [ -z "$SNAPSHOT_PATH" ] && [ -z "$EMULATOR_SNAPSHOT_NAME" ]; then
|
|
73
|
+
# --- COLD BOOT PATH (Wait for everything) ---
|
|
74
|
+
wait_for_log "$ANDROID_CONTAINER" "Emulator boot complete" "Android Emulator Boot"
|
|
75
|
+
wait_for_log "$ANDROID_CONTAINER" "APK installation complete" "WootzApp Installation"
|
|
76
|
+
wait_for_log "$ANDROID_CONTAINER" "CDP Bridge ready" "CDP Bridge"
|
|
77
|
+
else
|
|
78
|
+
# --- WARM BOOT PATH (Hyper-Speed) ---
|
|
79
|
+
# We skip Boot and APK installation because they are inside the snapshot.
|
|
80
|
+
# We only wait for the Daemon to reconnect to the resumed emulator.
|
|
81
|
+
echo -e "${GREEN} Snapshot detected. Skipping OS boot and APK install waits.${NC}"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# We always wait for the Daemon because it's the final bridge to the SDK
|
|
76
85
|
wait_for_log "$AGENT_CONTAINER" "Daemon listening on TCP" "Agent Daemon Connection"
|
|
77
86
|
|
|
78
87
|
# 3.5 Network Rehydration (Warm Boot Only)
|
|
79
|
-
if [ -n "$SNAPSHOT_PATH" ]; then
|
|
88
|
+
if [ -n "$SNAPSHOT_PATH" ] || [ -n "$EMULATOR_SNAPSHOT_NAME" ]; then
|
|
80
89
|
echo " Rehydrating Android network connection..."
|
|
81
90
|
# Toggle airplane mode to force DHCP lease renewal
|
|
82
91
|
docker exec "$ANDROID_CONTAINER" adb shell cmd connectivity airplane-mode enable
|