ai-extension-preview 0.1.15 → 0.1.16
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/dist/index.js +6 -20
- package/dist/plugins/AppPlugin.js +6 -5
- package/dist/plugins/AuthPlugin.js +5 -4
- package/dist/plugins/ConfigPlugin.js +2 -1
- package/dist/plugins/CorePlugin.js +2 -1
- package/dist/plugins/DownloaderPlugin.js +13 -12
- package/dist/plugins/ServerPlugin.js +13 -12
- package/dist/plugins/browser/BrowserManagerPlugin.js +9 -8
- package/dist/plugins/browser/NativeLauncherPlugin.js +6 -5
- package/dist/plugins/browser/WSLLauncherPlugin.js +15 -14
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,17 +2,9 @@
|
|
|
2
2
|
import 'dotenv/config'; // Load .env
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
5
6
|
import os from 'os';
|
|
6
7
|
import { Runtime } from 'skeleton-crew-runtime';
|
|
7
|
-
import { ConfigPlugin } from './plugins/ConfigPlugin.js';
|
|
8
|
-
import { CorePlugin } from './plugins/CorePlugin.js';
|
|
9
|
-
import { DownloaderPlugin } from './plugins/DownloaderPlugin.js';
|
|
10
|
-
import { BrowserManagerPlugin } from './plugins/browser/BrowserManagerPlugin.js';
|
|
11
|
-
import { WSLLauncherPlugin } from './plugins/browser/WSLLauncherPlugin.js';
|
|
12
|
-
import { NativeLauncherPlugin } from './plugins/browser/NativeLauncherPlugin.js';
|
|
13
|
-
import { ServerPlugin } from './plugins/ServerPlugin.js';
|
|
14
|
-
import { AuthPlugin } from './plugins/AuthPlugin.js'; // [NEW]
|
|
15
|
-
import { AppPlugin } from './plugins/AppPlugin.js'; // [NEW]
|
|
16
8
|
import chalk from 'chalk';
|
|
17
9
|
const DEFAULT_HOST = process.env.API_HOST || 'https://ai-extension-builder.01kb6018z1t9tpaza4y5f1c56w.lmapp.run/api';
|
|
18
10
|
const program = new Command();
|
|
@@ -25,6 +17,9 @@ program
|
|
|
25
17
|
.option('--user <user>', 'User ID (if required)')
|
|
26
18
|
.parse(process.argv);
|
|
27
19
|
const options = program.opts();
|
|
20
|
+
// Define __dirname for ESM
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = path.dirname(__filename);
|
|
28
23
|
// Use os.homedir() to ensure we have write permissions
|
|
29
24
|
const HOME_DIR = os.homedir();
|
|
30
25
|
// Initial workdir based on options, or specific 'default' if not yet known.
|
|
@@ -41,19 +36,10 @@ const WORK_DIR = path.join(HOME_DIR, '.ai-extension-preview', options.job || 'de
|
|
|
41
36
|
jobId: initialJobId || '',
|
|
42
37
|
workDir: WORK_DIR
|
|
43
38
|
},
|
|
44
|
-
hostContext: {} // Clear hostContext config wrapping
|
|
39
|
+
hostContext: {}, // Clear hostContext config wrapping
|
|
40
|
+
pluginPaths: [path.join(__dirname, 'plugins')] // [NEW] Auto-discovery
|
|
45
41
|
});
|
|
46
42
|
// Register Plugins
|
|
47
|
-
runtime.logger.info('Registering plugins...');
|
|
48
|
-
runtime.registerPlugin(CorePlugin);
|
|
49
|
-
runtime.registerPlugin(ConfigPlugin);
|
|
50
|
-
runtime.registerPlugin(DownloaderPlugin);
|
|
51
|
-
runtime.registerPlugin(BrowserManagerPlugin);
|
|
52
|
-
runtime.registerPlugin(WSLLauncherPlugin);
|
|
53
|
-
runtime.registerPlugin(NativeLauncherPlugin);
|
|
54
|
-
runtime.registerPlugin(ServerPlugin);
|
|
55
|
-
runtime.registerPlugin(AuthPlugin); // [NEW]
|
|
56
|
-
runtime.registerPlugin(AppPlugin); // [NEW]
|
|
57
43
|
runtime.logger.info('Initializing runtime...');
|
|
58
44
|
await runtime.initialize();
|
|
59
45
|
const ctx = runtime.getContext();
|
|
@@ -8,7 +8,7 @@ export const AppPlugin = {
|
|
|
8
8
|
ctx.actions.registerAction({
|
|
9
9
|
id: 'app:start',
|
|
10
10
|
handler: async () => {
|
|
11
|
-
await ctx.
|
|
11
|
+
await ctx.logger.info('Initializing Local Satellite...');
|
|
12
12
|
// 1. Authenticate (if needed)
|
|
13
13
|
// AuthPlugin will automatically skip if already config'd, or prompt if needed
|
|
14
14
|
// It will also update config via config:set
|
|
@@ -27,7 +27,7 @@ export const AppPlugin = {
|
|
|
27
27
|
// 4. Initial Download/Check
|
|
28
28
|
const success = await ctx.actions.runAction('downloader:check', null);
|
|
29
29
|
if (!success) {
|
|
30
|
-
await ctx.
|
|
30
|
+
await ctx.logger.error('Initial check failed. Could not verify job or download extension.');
|
|
31
31
|
// We don't exit process here, but we might throw to stop flow
|
|
32
32
|
throw new Error('Initial check failed');
|
|
33
33
|
}
|
|
@@ -37,17 +37,17 @@ export const AppPlugin = {
|
|
|
37
37
|
const maxAttempts = 60; // 2 minutes
|
|
38
38
|
// This logic could be in a 'watcher' plugin but fits here for now as part of "Startup Sequence"
|
|
39
39
|
if (!fs.existsSync(manifestPath)) {
|
|
40
|
-
await ctx.
|
|
40
|
+
await ctx.logger.info('[DEBUG] Waiting for extension files...');
|
|
41
41
|
while (!fs.existsSync(manifestPath) && attempts < maxAttempts) {
|
|
42
42
|
await new Promise(r => setTimeout(r, 2000));
|
|
43
43
|
attempts++;
|
|
44
44
|
if (attempts % 5 === 0) {
|
|
45
|
-
await ctx.
|
|
45
|
+
await ctx.logger.info(`Waiting for extension generation... (${attempts * 2}s)`);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
if (!fs.existsSync(manifestPath)) {
|
|
50
|
-
await ctx.
|
|
50
|
+
await ctx.logger.error('Timed out waiting for extension files. Status check succeeded but files are missing.');
|
|
51
51
|
throw new Error('Timeout waiting for files');
|
|
52
52
|
}
|
|
53
53
|
// 6. Launch Browser
|
|
@@ -56,3 +56,4 @@ export const AppPlugin = {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
|
+
export default AppPlugin;
|
|
@@ -2,7 +2,7 @@ import axios from 'axios';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
|
-
|
|
5
|
+
const AuthPlugin = {
|
|
6
6
|
name: 'auth',
|
|
7
7
|
version: '1.0.0',
|
|
8
8
|
dependencies: ['config', 'server'],
|
|
@@ -13,7 +13,7 @@ export const AuthPlugin = {
|
|
|
13
13
|
const hostContext = ctx.config;
|
|
14
14
|
// If we already have JobID and UserID, we might skip, but let's assume we need to verify or start fresh if missing
|
|
15
15
|
if (hostContext.jobId && hostContext.user) {
|
|
16
|
-
await ctx.
|
|
16
|
+
await ctx.logger.info('Auth: Job ID and User ID present. Skipping login.');
|
|
17
17
|
return { jobId: hostContext.jobId, user: hostContext.user, token: hostContext.token };
|
|
18
18
|
}
|
|
19
19
|
// We need the port from ServerPlugin
|
|
@@ -23,7 +23,7 @@ export const AuthPlugin = {
|
|
|
23
23
|
throw new Error('Server port not found. Ensure ServerPlugin is loaded before AuthPlugin logic runs.');
|
|
24
24
|
}
|
|
25
25
|
const host = hostContext.host;
|
|
26
|
-
await ctx.
|
|
26
|
+
await ctx.logger.info(`Auth: Initiating login flow on ${host} with port ${allocatedPort}`);
|
|
27
27
|
try {
|
|
28
28
|
// 1. Init Session with port
|
|
29
29
|
const initRes = await axios({
|
|
@@ -79,10 +79,11 @@ export const AuthPlugin = {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
|
-
await ctx.
|
|
82
|
+
await ctx.logger.error(`Authentication failed: ${error.message}`);
|
|
83
83
|
throw error;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
|
+
export default AuthPlugin;
|
|
@@ -5,7 +5,7 @@ import AdmZip from 'adm-zip';
|
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
import https from 'https';
|
|
7
7
|
let checkInterval;
|
|
8
|
-
|
|
8
|
+
const DownloaderPlugin = {
|
|
9
9
|
name: 'downloader',
|
|
10
10
|
version: '1.0.0',
|
|
11
11
|
dependencies: ['config'],
|
|
@@ -38,7 +38,7 @@ export const DownloaderPlugin = {
|
|
|
38
38
|
// Ignore parse errors
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
ctx.
|
|
41
|
+
ctx.logger.info(`[DEBUG] DownloaderPlugin creating client with userId: ${userId}`);
|
|
42
42
|
return axios.create({
|
|
43
43
|
baseURL: config.host,
|
|
44
44
|
headers: {
|
|
@@ -78,7 +78,7 @@ export const DownloaderPlugin = {
|
|
|
78
78
|
lastModified = fs.readFileSync(VERSION_FILE, 'utf-8').trim();
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
await ctx.
|
|
81
|
+
await ctx.logger.info('Checking for updates...');
|
|
82
82
|
const MAX_RETRIES = 3;
|
|
83
83
|
let attempt = 0;
|
|
84
84
|
while (attempt < MAX_RETRIES) {
|
|
@@ -96,12 +96,12 @@ export const DownloaderPlugin = {
|
|
|
96
96
|
let forceDownload = false;
|
|
97
97
|
const manifestPath = path.join(DIST_DIR, 'manifest.json');
|
|
98
98
|
if (!fs.existsSync(manifestPath)) {
|
|
99
|
-
await ctx.
|
|
99
|
+
await ctx.logger.warn('Version match but files missing. Forcing download...');
|
|
100
100
|
forceDownload = true;
|
|
101
101
|
}
|
|
102
102
|
if (newVersion !== lastModified || forceDownload) {
|
|
103
103
|
if (newVersion !== lastModified) {
|
|
104
|
-
await ctx.
|
|
104
|
+
await ctx.logger.info(`New version detected (Old: "${lastModified}", New: "${newVersion}")`);
|
|
105
105
|
}
|
|
106
106
|
const success = await ctx.actions.runAction('downloader:download', null);
|
|
107
107
|
if (success) {
|
|
@@ -121,12 +121,12 @@ export const DownloaderPlugin = {
|
|
|
121
121
|
attempt++;
|
|
122
122
|
const isNetworkError = error.code === 'EAI_AGAIN' || error.code === 'ENOTFOUND' || error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT';
|
|
123
123
|
if (attempt < MAX_RETRIES && isNetworkError) {
|
|
124
|
-
await ctx.
|
|
124
|
+
await ctx.logger.warn(`Connection failed (${error.code}). Retrying (${attempt}/${MAX_RETRIES})...`);
|
|
125
125
|
await new Promise(r => setTimeout(r, 1000 * attempt)); // Backoff
|
|
126
126
|
continue;
|
|
127
127
|
}
|
|
128
128
|
isChecking = false;
|
|
129
|
-
await ctx.
|
|
129
|
+
await ctx.logger.error(`Check failed: ${error.message}`);
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -199,19 +199,19 @@ console.log('[Hot Reload] Active for Job:', CURRENT_JOB_ID);
|
|
|
199
199
|
const swContent = await fs.readFile(swPath, 'utf-8');
|
|
200
200
|
// Prepend import
|
|
201
201
|
await fs.writeFile(swPath, "import './hot-reload.js';\n" + swContent);
|
|
202
|
-
await ctx.
|
|
202
|
+
await ctx.logger.info('Injected Hot Reload script into background worker.');
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
// MV2 Scripts Strategy (Fallback if user generates MV2)
|
|
206
206
|
else if (manifest.background?.scripts) {
|
|
207
207
|
manifest.background.scripts.push('hot-reload.js');
|
|
208
208
|
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
209
|
-
await ctx.
|
|
209
|
+
await ctx.logger.info('Injected Hot Reload script into background scripts.');
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
catch (injectErr) {
|
|
214
|
-
await ctx.
|
|
214
|
+
await ctx.logger.error(`Hot Reload Injection Failed: ${injectErr.message}`);
|
|
215
215
|
}
|
|
216
216
|
// ----------------------------
|
|
217
217
|
spinner.succeed('Updated extension code!');
|
|
@@ -219,12 +219,13 @@ console.log('[Hot Reload] Active for Job:', CURRENT_JOB_ID);
|
|
|
219
219
|
}
|
|
220
220
|
catch (error) {
|
|
221
221
|
spinner.fail(`Failed to download: ${error.message}`);
|
|
222
|
-
await ctx.
|
|
222
|
+
await ctx.logger.error(`Download failed: ${error.message}`);
|
|
223
223
|
return false;
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
});
|
|
227
227
|
// Polling removed in favor of push-based updates (POST /refresh)
|
|
228
|
-
ctx.
|
|
228
|
+
ctx.logger.info('Ready. Waiting for update signals...');
|
|
229
229
|
}
|
|
230
230
|
};
|
|
231
|
+
export default DownloaderPlugin;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
|
-
|
|
2
|
+
const ServerPlugin = {
|
|
3
3
|
name: 'server',
|
|
4
4
|
version: '1.0.0',
|
|
5
5
|
dependencies: ['config'],
|
|
@@ -15,7 +15,7 @@ export const ServerPlugin = {
|
|
|
15
15
|
ctx.events.on('downloader:updated', (data) => {
|
|
16
16
|
if (data && data.version) {
|
|
17
17
|
currentVersion = data.version;
|
|
18
|
-
ctx.
|
|
18
|
+
ctx.logger.info(`Server: Reporting version ${currentVersion}`);
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
// Create server with request handler
|
|
@@ -53,7 +53,7 @@ export const ServerPlugin = {
|
|
|
53
53
|
if (data.jobId) {
|
|
54
54
|
newJobId = data.jobId;
|
|
55
55
|
ctx.getRuntime().updateConfig({ jobId: newJobId });
|
|
56
|
-
ctx.
|
|
56
|
+
ctx.logger.info(`[API] Switched to new Job ID: ${newJobId}`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -61,11 +61,11 @@ export const ServerPlugin = {
|
|
|
61
61
|
// Ignore parse error
|
|
62
62
|
}
|
|
63
63
|
// Trigger manual check
|
|
64
|
-
ctx.
|
|
64
|
+
ctx.logger.info('[API] Refresh request received');
|
|
65
65
|
ctx.actions.runAction('downloader:check', null).then((result) => {
|
|
66
|
-
ctx.
|
|
66
|
+
ctx.logger.info(`[API] Check result: ${result}`);
|
|
67
67
|
}).catch((err) => {
|
|
68
|
-
ctx.
|
|
68
|
+
ctx.logger.error(`[API] Check failed: ${err.message}`);
|
|
69
69
|
});
|
|
70
70
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
71
71
|
res.end(JSON.stringify({ success: true, jobId: ctx.config.jobId }));
|
|
@@ -74,11 +74,11 @@ export const ServerPlugin = {
|
|
|
74
74
|
}
|
|
75
75
|
else if (req.url === '/disconnect' && req.method === 'POST') {
|
|
76
76
|
// Trigger browser stop
|
|
77
|
-
ctx.
|
|
77
|
+
ctx.logger.info('[API] Disconnect request received');
|
|
78
78
|
ctx.actions.runAction('browser:stop', null).then((result) => {
|
|
79
|
-
ctx.
|
|
79
|
+
ctx.logger.info(`[API] Browser stop result: ${result}`);
|
|
80
80
|
}).catch((err) => {
|
|
81
|
-
ctx.
|
|
81
|
+
ctx.logger.error(`[API] Browser stop failed: ${err.message}`);
|
|
82
82
|
});
|
|
83
83
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
84
84
|
res.end(JSON.stringify({ success: true }));
|
|
@@ -110,7 +110,7 @@ export const ServerPlugin = {
|
|
|
110
110
|
});
|
|
111
111
|
// Success! Port is allocated
|
|
112
112
|
allocatedPort = port;
|
|
113
|
-
await ctx.
|
|
113
|
+
await ctx.logger.info(`Hot Reload Server running on port ${allocatedPort}`);
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
116
116
|
catch (err) {
|
|
@@ -124,13 +124,13 @@ export const ServerPlugin = {
|
|
|
124
124
|
}
|
|
125
125
|
else {
|
|
126
126
|
// Other error, fail immediately
|
|
127
|
-
await ctx.
|
|
127
|
+
await ctx.logger.error(`Server error: ${err.message}`);
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
if (!allocatedPort || !server) {
|
|
133
|
-
await ctx.
|
|
133
|
+
await ctx.logger.error(`Failed to allocate port after ${maxAttempts} attempts (ports ${startPort}-${startPort + maxAttempts - 1})`);
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
136
|
// Store port in context for DownloaderPlugin to use
|
|
@@ -145,3 +145,4 @@ export const ServerPlugin = {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
|
+
export default ServerPlugin;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { findExtensionRoot, validateExtension } from '../../utils/browserUtils.js';
|
|
4
|
-
|
|
4
|
+
const BrowserManagerPlugin = {
|
|
5
5
|
name: 'browser-manager',
|
|
6
6
|
version: '1.0.0',
|
|
7
7
|
dependencies: ['config', 'downloader'],
|
|
@@ -26,12 +26,12 @@ export const BrowserManagerPlugin = {
|
|
|
26
26
|
}
|
|
27
27
|
fs.ensureDirSync(STAGING_DIR);
|
|
28
28
|
fs.copySync(DIST_DIR, STAGING_DIR);
|
|
29
|
-
await ctx.
|
|
29
|
+
await ctx.logger.info(`Synced code to Staging`);
|
|
30
30
|
// Emit staged event (optional)
|
|
31
31
|
ctx.events.emit('browser:staged', { path: STAGING_DIR });
|
|
32
32
|
}
|
|
33
33
|
catch (err) {
|
|
34
|
-
await ctx.
|
|
34
|
+
await ctx.logger.error(`Failed to sync to staging: ${err.message}`);
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
const launchBrowser = async () => {
|
|
@@ -41,10 +41,10 @@ export const BrowserManagerPlugin = {
|
|
|
41
41
|
// 1. Static Validation
|
|
42
42
|
const validation = validateExtension(extensionRoot);
|
|
43
43
|
if (!validation.valid) {
|
|
44
|
-
await ctx.
|
|
44
|
+
await ctx.logger.error(`[CRITICAL] Extension validation failed: ${validation.error} in ${extensionRoot}`);
|
|
45
45
|
}
|
|
46
46
|
else if (extensionRoot !== STAGING_DIR) {
|
|
47
|
-
await ctx.
|
|
47
|
+
await ctx.logger.info(`Detected nested extension at: ${path.basename(extensionRoot)}`);
|
|
48
48
|
}
|
|
49
49
|
// 2. Runtime Verification (Diagnostic) - SKIPPED FOR PERFORMANCE
|
|
50
50
|
// The SandboxRunner spins up a separate headless chrome which is slow and prone to WSL networking issues.
|
|
@@ -82,7 +82,7 @@ export const BrowserManagerPlugin = {
|
|
|
82
82
|
ctx.actions.registerAction({
|
|
83
83
|
id: 'browser:stop',
|
|
84
84
|
handler: async () => {
|
|
85
|
-
await ctx.
|
|
85
|
+
await ctx.logger.info('Stopping browser...');
|
|
86
86
|
const result = await ctx.actions.runAction('launcher:kill', null);
|
|
87
87
|
return result;
|
|
88
88
|
}
|
|
@@ -90,7 +90,7 @@ export const BrowserManagerPlugin = {
|
|
|
90
90
|
// Event: Update detected
|
|
91
91
|
ctx.events.on('downloader:updated', async () => {
|
|
92
92
|
if (isInitialized) {
|
|
93
|
-
await ctx.
|
|
93
|
+
await ctx.logger.info('Update detected. Restarting browser...');
|
|
94
94
|
try {
|
|
95
95
|
await ctx.actions.runAction('browser:stop', {});
|
|
96
96
|
}
|
|
@@ -104,9 +104,10 @@ export const BrowserManagerPlugin = {
|
|
|
104
104
|
});
|
|
105
105
|
// Event: Browser closed (from launcher)
|
|
106
106
|
ctx.events.on('browser:closed', async (data) => {
|
|
107
|
-
await ctx.
|
|
107
|
+
await ctx.logger.info(`Browser closed with code ${data.code}`);
|
|
108
108
|
// Emit event that can be picked up by other plugins (e.g., to notify backend)
|
|
109
109
|
ctx.events.emit('session:terminated', { reason: 'browser_closed' });
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
};
|
|
113
|
+
export default BrowserManagerPlugin;
|
|
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
4
|
import { findChrome, normalizePathToWindows } from '../../utils/browserUtils.js';
|
|
5
5
|
let chromeProcess = null;
|
|
6
|
-
|
|
6
|
+
const NativeLauncherPlugin = {
|
|
7
7
|
name: 'native-launcher',
|
|
8
8
|
version: '1.0.0',
|
|
9
9
|
dependencies: ['config'],
|
|
@@ -56,14 +56,14 @@ export const NativeLauncherPlugin = {
|
|
|
56
56
|
});
|
|
57
57
|
// Monitor process exit
|
|
58
58
|
chromeProcess.on('exit', async (code) => {
|
|
59
|
-
|
|
59
|
+
ctx.logger.info(`[NativeLauncher] Chrome exited with code ${code}`);
|
|
60
60
|
chromeProcess = null;
|
|
61
61
|
ctx.events.emit('browser:closed', { code });
|
|
62
62
|
});
|
|
63
|
-
|
|
63
|
+
ctx.logger.info('[NativeLauncher] Chrome started with PID: ' + chromeProcess.pid);
|
|
64
64
|
}
|
|
65
65
|
catch (spawnErr) {
|
|
66
|
-
|
|
66
|
+
ctx.logger.error(`[NativeLauncher] Spawn Failed: ${spawnErr.message}`);
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
return true;
|
|
@@ -74,7 +74,7 @@ export const NativeLauncherPlugin = {
|
|
|
74
74
|
id: 'launcher:kill',
|
|
75
75
|
handler: async () => {
|
|
76
76
|
if (chromeProcess) {
|
|
77
|
-
|
|
77
|
+
ctx.logger.info('[NativeLauncher] Chrome process force killed.');
|
|
78
78
|
chromeProcess.kill();
|
|
79
79
|
chromeProcess = null;
|
|
80
80
|
return true;
|
|
@@ -90,3 +90,4 @@ export const NativeLauncherPlugin = {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
};
|
|
93
|
+
export default NativeLauncherPlugin;
|
|
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
4
|
import { findChrome } from '../../utils/browserUtils.js';
|
|
5
5
|
let chromePid = null;
|
|
6
|
-
|
|
6
|
+
const WSLLauncherPlugin = {
|
|
7
7
|
name: 'wsl-launcher',
|
|
8
8
|
version: '1.0.0',
|
|
9
9
|
dependencies: ['config'],
|
|
@@ -17,7 +17,7 @@ export const WSLLauncherPlugin = {
|
|
|
17
17
|
handler: async (payload) => {
|
|
18
18
|
const chromePath = findChrome();
|
|
19
19
|
if (!chromePath) {
|
|
20
|
-
await ctx.
|
|
20
|
+
await ctx.logger.error('Chrome not found for detached launch.');
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
23
|
// Hardcoded Safe Paths for WSL Strategy
|
|
@@ -37,7 +37,7 @@ export const WSLLauncherPlugin = {
|
|
|
37
37
|
const winChromePath = chromePath
|
|
38
38
|
.replace(new RegExp(`^/mnt/${driveLetter}/`), `${driveLetter.toUpperCase()}:\\\\`)
|
|
39
39
|
.replace(/\//g, '\\\\');
|
|
40
|
-
await ctx.
|
|
40
|
+
await ctx.logger.info(`WSL Launch Target (Win): ${finalWinExtensionPath}`);
|
|
41
41
|
// Create PowerShell Launch Script with PID capture
|
|
42
42
|
const psContent = `
|
|
43
43
|
$chromePath = "${winChromePath}"
|
|
@@ -74,7 +74,7 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
74
74
|
await fs.writeFile(psPath, psContent);
|
|
75
75
|
}
|
|
76
76
|
catch (e) {
|
|
77
|
-
await ctx.
|
|
77
|
+
await ctx.logger.error(`WSL Write PS1 Failed: ${e.message}`);
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
80
|
// Execute via PowerShell
|
|
@@ -90,11 +90,11 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
90
90
|
const pidMatch = msg.match(/CHROME_PID:(\d+)/);
|
|
91
91
|
if (pidMatch) {
|
|
92
92
|
chromePid = parseInt(pidMatch[1], 10);
|
|
93
|
-
await ctx.
|
|
93
|
+
await ctx.logger.info(`Chrome launched with PID: ${chromePid}`);
|
|
94
94
|
// Start monitoring the process
|
|
95
95
|
monitorProcess(ctx, chromePid);
|
|
96
96
|
}
|
|
97
|
-
await ctx.
|
|
97
|
+
await ctx.logger.info(`[PS1] ${msg.trim()}`);
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
if (child.stderr) {
|
|
@@ -102,10 +102,10 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
102
102
|
const msg = chunk.toString();
|
|
103
103
|
// Ignore minor PS noise unless critical
|
|
104
104
|
if (msg.includes('Exec format error')) {
|
|
105
|
-
await ctx.
|
|
105
|
+
await ctx.logger.error(`CRITICAL: WSL Interop broken.`);
|
|
106
106
|
}
|
|
107
107
|
else if (msg.trim()) {
|
|
108
|
-
await ctx.
|
|
108
|
+
await ctx.logger.error(`Launch Error: ${msg}`);
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
111
|
}
|
|
@@ -117,7 +117,7 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
117
117
|
id: 'launcher:kill',
|
|
118
118
|
handler: async () => {
|
|
119
119
|
if (chromePid) {
|
|
120
|
-
await ctx.
|
|
120
|
+
await ctx.logger.info(`Terminating Chrome process (PID: ${chromePid})...`);
|
|
121
121
|
try {
|
|
122
122
|
// 1. Try Stop-Process first (Graceful)
|
|
123
123
|
const killCmd = `
|
|
@@ -138,22 +138,22 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
138
138
|
const killChild = spawn('powershell.exe', ['-Command', killCmd], { stdio: 'pipe' });
|
|
139
139
|
// Capture output to debug why it might fail
|
|
140
140
|
if (killChild.stdout) {
|
|
141
|
-
killChild.stdout.on('data', d => ctx.
|
|
141
|
+
killChild.stdout.on('data', d => ctx.logger.debug(`[KillParams] ${d}`));
|
|
142
142
|
}
|
|
143
143
|
if (killChild.stderr) {
|
|
144
|
-
killChild.stderr.on('data', d => ctx.
|
|
144
|
+
killChild.stderr.on('data', d => ctx.logger.warn(`[KillMsg] ${d}`));
|
|
145
145
|
}
|
|
146
146
|
await new Promise((resolve) => {
|
|
147
147
|
killChild.on('exit', (code) => {
|
|
148
148
|
resolve();
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
|
-
await ctx.
|
|
151
|
+
await ctx.logger.info('Chrome process termination signal sent.');
|
|
152
152
|
chromePid = null;
|
|
153
153
|
return true;
|
|
154
154
|
}
|
|
155
155
|
catch (err) {
|
|
156
|
-
await ctx.
|
|
156
|
+
await ctx.logger.error(`Kill failed: ${err.message}`);
|
|
157
157
|
return false;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
@@ -177,7 +177,7 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
177
177
|
if (!output.trim() || code !== 0) {
|
|
178
178
|
// Process no longer exists
|
|
179
179
|
clearInterval(checkInterval);
|
|
180
|
-
await ctx.
|
|
180
|
+
await ctx.logger.info('Chrome process exited.');
|
|
181
181
|
chromePid = null;
|
|
182
182
|
ctx.events.emit('browser:closed', { code: 0 });
|
|
183
183
|
}
|
|
@@ -197,3 +197,4 @@ Write-Host "CHROME_PID:$($process.Id)"
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
};
|
|
200
|
+
export default WSLLauncherPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-extension-preview",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Local preview tool for AI Extension Builder",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"node-fetch": "^3.3.2",
|
|
38
38
|
"ora": "^8.1.1",
|
|
39
39
|
"puppeteer-core": "^24.33.0",
|
|
40
|
-
"skeleton-crew-runtime": "0.2.
|
|
40
|
+
"skeleton-crew-runtime": "^0.2.1",
|
|
41
41
|
"web-ext": "^8.3.0",
|
|
42
42
|
"ws": "^8.18.0",
|
|
43
43
|
"zod": "^4.2.1"
|