@lightcone-ai/daemon 0.9.16 → 0.9.17
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/package.json +1 -1
- package/src/agent-manager.js +18 -12
- package/src/{xhs-login.js → browser-login.js} +72 -58
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import { buildSystemPrompt } from './drivers/claude.js';
|
|
6
6
|
import { buildCodexSpawn, buildCodexSystemPrompt, parseCodexLine } from './drivers/codex.js';
|
|
7
7
|
import { buildKimiSpawn, buildKimiInitMessages, parseKimiLine, encodeKimiStdin } from './drivers/kimi.js';
|
|
8
|
-
import { startSession, stopSession } from './
|
|
8
|
+
import { startSession, stopSession, profileDir } from './browser-login.js';
|
|
9
9
|
|
|
10
10
|
export class AgentManager {
|
|
11
11
|
constructor({ serverUrl, machineApiKey }) {
|
|
@@ -26,8 +26,8 @@ export class AgentManager {
|
|
|
26
26
|
case 'agent:start': return this._startAgent(msg, connection);
|
|
27
27
|
case 'agent:stop': return this._stopAgent(msg.agentId, msg.teamId, connection);
|
|
28
28
|
case 'agent:deliver': return this._deliverMessage(msg, connection);
|
|
29
|
-
case '
|
|
30
|
-
case '
|
|
29
|
+
case 'browser:start_login': return this._startBrowserLogin(msg, connection);
|
|
30
|
+
case 'browser:stop_login': return this._stopBrowserLogin(msg);
|
|
31
31
|
case 'ping': return connection.send({ type: 'pong' });
|
|
32
32
|
default:
|
|
33
33
|
console.log(`[AgentManager] Unhandled: ${msg.type}`);
|
|
@@ -237,7 +237,11 @@ export class AgentManager {
|
|
|
237
237
|
if (a === '{platform_mcp_path}')
|
|
238
238
|
return new URL('../../mcp-servers/platform/index.js', import.meta.url).pathname;
|
|
239
239
|
if (a === '{xhs_profile_dir}')
|
|
240
|
-
return
|
|
240
|
+
return profileDir('xhs', config.userId ?? 'default');
|
|
241
|
+
if (a === '{douyin_profile_dir}')
|
|
242
|
+
return profileDir('douyin', config.userId ?? 'default');
|
|
243
|
+
if (a === '{kuaishou_profile_dir}')
|
|
244
|
+
return profileDir('kuaishou', config.userId ?? 'default');
|
|
241
245
|
return a;
|
|
242
246
|
});
|
|
243
247
|
const resolvedEnv = {};
|
|
@@ -369,19 +373,21 @@ export class AgentManager {
|
|
|
369
373
|
this._flushPending(key, connection);
|
|
370
374
|
}
|
|
371
375
|
|
|
372
|
-
async
|
|
373
|
-
|
|
376
|
+
async _startBrowserLogin(msg, connection) {
|
|
377
|
+
const platform = msg.platform ?? 'xhs';
|
|
378
|
+
console.log(`[AgentManager] Starting browser login for platform=${platform}`);
|
|
374
379
|
try {
|
|
375
|
-
await startSession(connection, msg.userId ?? 'default');
|
|
380
|
+
await startSession(platform, connection, msg.userId ?? 'default');
|
|
376
381
|
} catch (err) {
|
|
377
|
-
console.error(
|
|
378
|
-
connection.send({ type: '
|
|
382
|
+
console.error(`[AgentManager] Browser login start failed (${platform}):`, err.message);
|
|
383
|
+
connection.send({ type: 'browser:login_error', platform, error: err.message });
|
|
379
384
|
}
|
|
380
385
|
}
|
|
381
386
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
387
|
+
_stopBrowserLogin(msg) {
|
|
388
|
+
const platform = msg.platform ?? 'xhs';
|
|
389
|
+
console.log(`[AgentManager] Stopping browser login for platform=${platform}`);
|
|
390
|
+
stopSession(platform);
|
|
385
391
|
}
|
|
386
392
|
|
|
387
393
|
_stopAgent(agentId, teamId, connection) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Generic browser login via Chrome CDP.
|
|
3
|
+
* Supports multiple platforms (xhs, douyin, etc.)
|
|
4
|
+
* Runs on the daemon machine where Chrome is installed.
|
|
5
|
+
* Screenshots are sent back to server via daemon WebSocket.
|
|
5
6
|
*/
|
|
6
7
|
import { spawn } from 'child_process';
|
|
7
8
|
import { homedir } from 'os';
|
|
@@ -10,12 +11,33 @@ import path from 'path';
|
|
|
10
11
|
import http from 'http';
|
|
11
12
|
import { WebSocket } from 'ws';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// ── Platform configs ──────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export const PLATFORM_CONFIGS = {
|
|
17
|
+
xhs: {
|
|
18
|
+
loginUrl: 'https://www.xiaohongshu.com',
|
|
19
|
+
isLoggedIn: (cookies) => cookies.some(c => c.name === 'web_session' && c.value?.length > 0),
|
|
20
|
+
},
|
|
21
|
+
douyin: {
|
|
22
|
+
loginUrl: 'https://www.douyin.com',
|
|
23
|
+
isLoggedIn: (cookies) => cookies.some(c =>
|
|
24
|
+
(c.name === 'sessionid' || c.name === 'sid_guard') && c.value?.length > 0
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
kuaishou: {
|
|
28
|
+
loginUrl: 'https://www.kuaishou.com',
|
|
29
|
+
isLoggedIn: (cookies) => cookies.some(c =>
|
|
30
|
+
(c.name === 'userId' || c.name === 'passToken') && c.value?.length > 0
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function profileDir(platform, userId) {
|
|
36
|
+
return path.join(homedir(), '.lightcone', 'chrome-profiles', `${platform}-${userId}`);
|
|
16
37
|
}
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
// ── Chrome detection ──────────────────────────────────────────────────────────
|
|
40
|
+
|
|
19
41
|
function detectChrome() {
|
|
20
42
|
if (process.env.CHROME_BIN) return process.env.CHROME_BIN;
|
|
21
43
|
const candidates = [
|
|
@@ -31,10 +53,14 @@ function detectChrome() {
|
|
|
31
53
|
}
|
|
32
54
|
return candidates[0];
|
|
33
55
|
}
|
|
56
|
+
|
|
34
57
|
const CHROME_BIN = detectChrome();
|
|
58
|
+
const CDP_PORT = 9225;
|
|
35
59
|
const SCREENSHOT_INTERVAL_MS = 2000;
|
|
36
60
|
const LOGIN_CHECK_INTERVAL_MS = 3000;
|
|
37
61
|
|
|
62
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
38
64
|
function httpGet(url) {
|
|
39
65
|
return new Promise((resolve, reject) => {
|
|
40
66
|
http.get(url, (res) => {
|
|
@@ -49,19 +75,25 @@ function sleep(ms) {
|
|
|
49
75
|
return new Promise(r => setTimeout(r, ms));
|
|
50
76
|
}
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
// ── BrowserLoginSession ───────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export class BrowserLoginSession {
|
|
81
|
+
constructor(platform) {
|
|
82
|
+
this._platform = platform;
|
|
83
|
+
this._config = PLATFORM_CONFIGS[platform];
|
|
84
|
+
if (!this._config) throw new Error(`Unknown platform: ${platform}`);
|
|
54
85
|
this._proc = null;
|
|
55
86
|
this._ws = null;
|
|
56
87
|
this._nextId = 1;
|
|
57
88
|
this._pending = new Map();
|
|
58
89
|
this._screenshotTimer = null;
|
|
59
90
|
this._loginCheckTimer = null;
|
|
91
|
+
this._profileDir = null;
|
|
60
92
|
}
|
|
61
93
|
|
|
62
94
|
async start(connection, userId) {
|
|
63
|
-
this._profileDir =
|
|
64
|
-
|
|
95
|
+
this._profileDir = profileDir(this._platform, userId);
|
|
96
|
+
|
|
65
97
|
this._proc = spawn(CHROME_BIN, [
|
|
66
98
|
`--remote-debugging-port=${CDP_PORT}`,
|
|
67
99
|
'--no-sandbox',
|
|
@@ -71,22 +103,18 @@ export class XhsLoginSession {
|
|
|
71
103
|
'--window-size=800,900',
|
|
72
104
|
'--disable-blink-features=AutomationControlled',
|
|
73
105
|
'--disable-infobars',
|
|
74
|
-
'--excludeSwitches=enable-automation',
|
|
75
106
|
'--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
76
107
|
'about:blank',
|
|
77
|
-
], {
|
|
78
|
-
stdio: 'ignore',
|
|
79
|
-
detached: false,
|
|
80
|
-
});
|
|
108
|
+
], { stdio: 'ignore', detached: false });
|
|
81
109
|
|
|
82
110
|
this._proc.on('error', (err) => {
|
|
83
|
-
console.error(`[
|
|
111
|
+
console.error(`[BrowserLogin][${this._platform}] Chrome spawn error: ${err.message}`);
|
|
84
112
|
this._stopTimers();
|
|
85
|
-
connection.send({ type: '
|
|
113
|
+
connection.send({ type: 'browser:login_error', platform: this._platform, error: `Chrome 启动失败: ${err.message}` });
|
|
86
114
|
});
|
|
87
115
|
|
|
88
116
|
this._proc.on('exit', (code) => {
|
|
89
|
-
console.log(`[
|
|
117
|
+
console.log(`[BrowserLogin][${this._platform}] Chrome exited (code=${code})`);
|
|
90
118
|
this._stopTimers();
|
|
91
119
|
});
|
|
92
120
|
|
|
@@ -102,7 +130,6 @@ export class XhsLoginSession {
|
|
|
102
130
|
}
|
|
103
131
|
if (!ready) throw new Error('Chrome CDP did not become ready in time');
|
|
104
132
|
|
|
105
|
-
// Get WebSocket debugger URL
|
|
106
133
|
const pagesJson = await httpGet(`http://localhost:${CDP_PORT}/json`);
|
|
107
134
|
const pages = JSON.parse(pagesJson);
|
|
108
135
|
const page = pages.find(p => p.type === 'page') ?? pages[0];
|
|
@@ -112,13 +139,11 @@ export class XhsLoginSession {
|
|
|
112
139
|
|
|
113
140
|
await this.send('Page.enable', {});
|
|
114
141
|
await this.send('Network.enable', {});
|
|
115
|
-
// Hide automation fingerprint
|
|
116
142
|
await this.send('Page.addScriptToEvaluateOnNewDocument', {
|
|
117
143
|
source: `Object.defineProperty(navigator, 'webdriver', { get: () => undefined });`,
|
|
118
144
|
});
|
|
119
|
-
await this.send('Page.navigate', { url:
|
|
145
|
+
await this.send('Page.navigate', { url: this._config.loginUrl });
|
|
120
146
|
|
|
121
|
-
// Start sending screenshots and checking login
|
|
122
147
|
this._startPolling(connection);
|
|
123
148
|
}
|
|
124
149
|
|
|
@@ -141,10 +166,7 @@ export class XhsLoginSession {
|
|
|
141
166
|
});
|
|
142
167
|
ws.on('error', reject);
|
|
143
168
|
ws.on('close', () => {
|
|
144
|
-
for (const [, entry] of this._pending) {
|
|
145
|
-
clearTimeout(entry.timer);
|
|
146
|
-
entry.reject(new Error('WebSocket closed'));
|
|
147
|
-
}
|
|
169
|
+
for (const [, entry] of this._pending) { clearTimeout(entry.timer); entry.reject(new Error('WebSocket closed')); }
|
|
148
170
|
this._pending.clear();
|
|
149
171
|
this._ws = null;
|
|
150
172
|
});
|
|
@@ -155,10 +177,7 @@ export class XhsLoginSession {
|
|
|
155
177
|
return new Promise((resolve, reject) => {
|
|
156
178
|
if (!this._ws) return reject(new Error('WebSocket not connected'));
|
|
157
179
|
const id = this._nextId++;
|
|
158
|
-
const timer = setTimeout(() => {
|
|
159
|
-
this._pending.delete(id);
|
|
160
|
-
reject(new Error(`CDP timeout for ${method}`));
|
|
161
|
-
}, 10_000);
|
|
180
|
+
const timer = setTimeout(() => { this._pending.delete(id); reject(new Error(`CDP timeout for ${method}`)); }, 10_000);
|
|
162
181
|
this._pending.set(id, { resolve, reject, timer });
|
|
163
182
|
this._ws.send(JSON.stringify({ id, method, params }));
|
|
164
183
|
});
|
|
@@ -166,37 +185,34 @@ export class XhsLoginSession {
|
|
|
166
185
|
|
|
167
186
|
async screenshot() {
|
|
168
187
|
const result = await this.send('Page.captureScreenshot', { format: 'jpeg', quality: 70 });
|
|
169
|
-
return result.data;
|
|
188
|
+
return result.data;
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
async isLoggedIn() {
|
|
173
192
|
const result = await this.send('Network.getAllCookies', {});
|
|
174
|
-
|
|
175
|
-
return cookies.some(c => c.name === 'web_session' && c.value?.length > 0);
|
|
193
|
+
return this._config.isLoggedIn(result.cookies ?? []);
|
|
176
194
|
}
|
|
177
195
|
|
|
178
196
|
_startPolling(connection) {
|
|
179
|
-
// Send screenshots periodically
|
|
180
197
|
this._screenshotTimer = setInterval(async () => {
|
|
181
198
|
try {
|
|
182
199
|
const screenshot = await this.screenshot();
|
|
183
|
-
connection.send({ type: '
|
|
200
|
+
connection.send({ type: 'browser:screenshot', platform: this._platform, screenshot });
|
|
184
201
|
} catch (err) {
|
|
185
|
-
console.error(
|
|
202
|
+
console.error(`[BrowserLogin][${this._platform}] Screenshot error:`, err.message);
|
|
186
203
|
}
|
|
187
204
|
}, SCREENSHOT_INTERVAL_MS);
|
|
188
205
|
|
|
189
|
-
// Check login status periodically
|
|
190
206
|
this._loginCheckTimer = setInterval(async () => {
|
|
191
207
|
try {
|
|
192
208
|
const loggedIn = await this.isLoggedIn();
|
|
193
209
|
if (loggedIn) {
|
|
194
210
|
this._stopTimers();
|
|
195
|
-
connection.send({ type: '
|
|
211
|
+
connection.send({ type: 'browser:login_complete', platform: this._platform, profileDir: this._profileDir });
|
|
196
212
|
this.close();
|
|
197
213
|
}
|
|
198
214
|
} catch (err) {
|
|
199
|
-
console.error(
|
|
215
|
+
console.error(`[BrowserLogin][${this._platform}] Login check error:`, err.message);
|
|
200
216
|
}
|
|
201
217
|
}, LOGIN_CHECK_INTERVAL_MS);
|
|
202
218
|
}
|
|
@@ -208,29 +224,27 @@ export class XhsLoginSession {
|
|
|
208
224
|
|
|
209
225
|
close() {
|
|
210
226
|
this._stopTimers();
|
|
211
|
-
if (this._ws) {
|
|
212
|
-
|
|
213
|
-
this._ws = null;
|
|
214
|
-
}
|
|
215
|
-
if (this._proc) {
|
|
216
|
-
try { this._proc.kill('SIGKILL'); } catch { /* ignore */ }
|
|
217
|
-
this._proc = null;
|
|
218
|
-
}
|
|
227
|
+
if (this._ws) { try { this._ws.close(); } catch {} this._ws = null; }
|
|
228
|
+
if (this._proc) { try { this._proc.kill('SIGKILL'); } catch {} this._proc = null; }
|
|
219
229
|
}
|
|
220
230
|
}
|
|
221
231
|
|
|
222
|
-
// Singleton
|
|
223
|
-
|
|
232
|
+
// ── Singleton map (platform → session) ───────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
const _sessions = new Map();
|
|
224
235
|
|
|
225
|
-
export function getSession() { return
|
|
236
|
+
export function getSession(platform) { return _sessions.get(platform) ?? null; }
|
|
226
237
|
|
|
227
|
-
export async function startSession(connection, userId) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
238
|
+
export async function startSession(platform, connection, userId) {
|
|
239
|
+
const existing = _sessions.get(platform);
|
|
240
|
+
if (existing) { existing.close(); _sessions.delete(platform); }
|
|
241
|
+
const session = new BrowserLoginSession(platform);
|
|
242
|
+
_sessions.set(platform, session);
|
|
243
|
+
await session.start(connection, userId);
|
|
244
|
+
return session;
|
|
232
245
|
}
|
|
233
246
|
|
|
234
|
-
export function stopSession() {
|
|
235
|
-
|
|
247
|
+
export function stopSession(platform) {
|
|
248
|
+
const session = _sessions.get(platform);
|
|
249
|
+
if (session) { session.close(); _sessions.delete(platform); }
|
|
236
250
|
}
|