@lightcone-ai/daemon 0.9.15 → 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} +78 -56
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',
|
|
@@ -69,20 +101,20 @@ export class XhsLoginSession {
|
|
|
69
101
|
'--headless=new',
|
|
70
102
|
`--user-data-dir=${this._profileDir}`,
|
|
71
103
|
'--window-size=800,900',
|
|
104
|
+
'--disable-blink-features=AutomationControlled',
|
|
105
|
+
'--disable-infobars',
|
|
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',
|
|
72
107
|
'about:blank',
|
|
73
|
-
], {
|
|
74
|
-
stdio: 'ignore',
|
|
75
|
-
detached: false,
|
|
76
|
-
});
|
|
108
|
+
], { stdio: 'ignore', detached: false });
|
|
77
109
|
|
|
78
110
|
this._proc.on('error', (err) => {
|
|
79
|
-
console.error(`[
|
|
111
|
+
console.error(`[BrowserLogin][${this._platform}] Chrome spawn error: ${err.message}`);
|
|
80
112
|
this._stopTimers();
|
|
81
|
-
connection.send({ type: '
|
|
113
|
+
connection.send({ type: 'browser:login_error', platform: this._platform, error: `Chrome 启动失败: ${err.message}` });
|
|
82
114
|
});
|
|
83
115
|
|
|
84
116
|
this._proc.on('exit', (code) => {
|
|
85
|
-
console.log(`[
|
|
117
|
+
console.log(`[BrowserLogin][${this._platform}] Chrome exited (code=${code})`);
|
|
86
118
|
this._stopTimers();
|
|
87
119
|
});
|
|
88
120
|
|
|
@@ -98,7 +130,6 @@ export class XhsLoginSession {
|
|
|
98
130
|
}
|
|
99
131
|
if (!ready) throw new Error('Chrome CDP did not become ready in time');
|
|
100
132
|
|
|
101
|
-
// Get WebSocket debugger URL
|
|
102
133
|
const pagesJson = await httpGet(`http://localhost:${CDP_PORT}/json`);
|
|
103
134
|
const pages = JSON.parse(pagesJson);
|
|
104
135
|
const page = pages.find(p => p.type === 'page') ?? pages[0];
|
|
@@ -108,9 +139,11 @@ export class XhsLoginSession {
|
|
|
108
139
|
|
|
109
140
|
await this.send('Page.enable', {});
|
|
110
141
|
await this.send('Network.enable', {});
|
|
111
|
-
await this.send('Page.
|
|
142
|
+
await this.send('Page.addScriptToEvaluateOnNewDocument', {
|
|
143
|
+
source: `Object.defineProperty(navigator, 'webdriver', { get: () => undefined });`,
|
|
144
|
+
});
|
|
145
|
+
await this.send('Page.navigate', { url: this._config.loginUrl });
|
|
112
146
|
|
|
113
|
-
// Start sending screenshots and checking login
|
|
114
147
|
this._startPolling(connection);
|
|
115
148
|
}
|
|
116
149
|
|
|
@@ -133,10 +166,7 @@ export class XhsLoginSession {
|
|
|
133
166
|
});
|
|
134
167
|
ws.on('error', reject);
|
|
135
168
|
ws.on('close', () => {
|
|
136
|
-
for (const [, entry] of this._pending) {
|
|
137
|
-
clearTimeout(entry.timer);
|
|
138
|
-
entry.reject(new Error('WebSocket closed'));
|
|
139
|
-
}
|
|
169
|
+
for (const [, entry] of this._pending) { clearTimeout(entry.timer); entry.reject(new Error('WebSocket closed')); }
|
|
140
170
|
this._pending.clear();
|
|
141
171
|
this._ws = null;
|
|
142
172
|
});
|
|
@@ -147,10 +177,7 @@ export class XhsLoginSession {
|
|
|
147
177
|
return new Promise((resolve, reject) => {
|
|
148
178
|
if (!this._ws) return reject(new Error('WebSocket not connected'));
|
|
149
179
|
const id = this._nextId++;
|
|
150
|
-
const timer = setTimeout(() => {
|
|
151
|
-
this._pending.delete(id);
|
|
152
|
-
reject(new Error(`CDP timeout for ${method}`));
|
|
153
|
-
}, 10_000);
|
|
180
|
+
const timer = setTimeout(() => { this._pending.delete(id); reject(new Error(`CDP timeout for ${method}`)); }, 10_000);
|
|
154
181
|
this._pending.set(id, { resolve, reject, timer });
|
|
155
182
|
this._ws.send(JSON.stringify({ id, method, params }));
|
|
156
183
|
});
|
|
@@ -158,37 +185,34 @@ export class XhsLoginSession {
|
|
|
158
185
|
|
|
159
186
|
async screenshot() {
|
|
160
187
|
const result = await this.send('Page.captureScreenshot', { format: 'jpeg', quality: 70 });
|
|
161
|
-
return result.data;
|
|
188
|
+
return result.data;
|
|
162
189
|
}
|
|
163
190
|
|
|
164
191
|
async isLoggedIn() {
|
|
165
192
|
const result = await this.send('Network.getAllCookies', {});
|
|
166
|
-
|
|
167
|
-
return cookies.some(c => c.name === 'web_session' && c.value?.length > 0);
|
|
193
|
+
return this._config.isLoggedIn(result.cookies ?? []);
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
_startPolling(connection) {
|
|
171
|
-
// Send screenshots periodically
|
|
172
197
|
this._screenshotTimer = setInterval(async () => {
|
|
173
198
|
try {
|
|
174
199
|
const screenshot = await this.screenshot();
|
|
175
|
-
connection.send({ type: '
|
|
200
|
+
connection.send({ type: 'browser:screenshot', platform: this._platform, screenshot });
|
|
176
201
|
} catch (err) {
|
|
177
|
-
console.error(
|
|
202
|
+
console.error(`[BrowserLogin][${this._platform}] Screenshot error:`, err.message);
|
|
178
203
|
}
|
|
179
204
|
}, SCREENSHOT_INTERVAL_MS);
|
|
180
205
|
|
|
181
|
-
// Check login status periodically
|
|
182
206
|
this._loginCheckTimer = setInterval(async () => {
|
|
183
207
|
try {
|
|
184
208
|
const loggedIn = await this.isLoggedIn();
|
|
185
209
|
if (loggedIn) {
|
|
186
210
|
this._stopTimers();
|
|
187
|
-
connection.send({ type: '
|
|
211
|
+
connection.send({ type: 'browser:login_complete', platform: this._platform, profileDir: this._profileDir });
|
|
188
212
|
this.close();
|
|
189
213
|
}
|
|
190
214
|
} catch (err) {
|
|
191
|
-
console.error(
|
|
215
|
+
console.error(`[BrowserLogin][${this._platform}] Login check error:`, err.message);
|
|
192
216
|
}
|
|
193
217
|
}, LOGIN_CHECK_INTERVAL_MS);
|
|
194
218
|
}
|
|
@@ -200,29 +224,27 @@ export class XhsLoginSession {
|
|
|
200
224
|
|
|
201
225
|
close() {
|
|
202
226
|
this._stopTimers();
|
|
203
|
-
if (this._ws) {
|
|
204
|
-
|
|
205
|
-
this._ws = null;
|
|
206
|
-
}
|
|
207
|
-
if (this._proc) {
|
|
208
|
-
try { this._proc.kill('SIGKILL'); } catch { /* ignore */ }
|
|
209
|
-
this._proc = null;
|
|
210
|
-
}
|
|
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; }
|
|
211
229
|
}
|
|
212
230
|
}
|
|
213
231
|
|
|
214
|
-
// Singleton
|
|
215
|
-
|
|
232
|
+
// ── Singleton map (platform → session) ───────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
const _sessions = new Map();
|
|
216
235
|
|
|
217
|
-
export function getSession() { return
|
|
236
|
+
export function getSession(platform) { return _sessions.get(platform) ?? null; }
|
|
218
237
|
|
|
219
|
-
export async function startSession(connection, userId) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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;
|
|
224
245
|
}
|
|
225
246
|
|
|
226
|
-
export function stopSession() {
|
|
227
|
-
|
|
247
|
+
export function stopSession(platform) {
|
|
248
|
+
const session = _sessions.get(platform);
|
|
249
|
+
if (session) { session.close(); _sessions.delete(platform); }
|
|
228
250
|
}
|