@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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 './xhs-login.js';
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 'xhs:start_login': return this._startXhsLogin(msg, connection);
30
- case 'xhs:stop_login': return this._stopXhsLogin();
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 path.join(homedir(), '.lightcone', 'chrome-profiles', 'xhs');
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 _startXhsLogin(msg, connection) {
373
- console.log('[AgentManager] Starting XHS login session');
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('[AgentManager] XHS login start failed:', err.message);
378
- connection.send({ type: 'xhs:login_error', error: err.message });
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
- _stopXhsLogin() {
383
- console.log('[AgentManager] Stopping XHS login session');
384
- stopSession();
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
- * XHS (Xiaohongshu) login session via Chrome CDP.
3
- * Runs on the daemon machine (VPS) where Chrome is installed.
4
- * Screenshots are sent back to the server via the daemon WebSocket connection.
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
- export function xhsProfileDir(userId) {
14
- // User-scoped so multiple users on the same hosted machine don't conflict
15
- return path.join(homedir(), '.lightcone', 'chrome-profiles', `xhs-${userId}`);
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
- const CDP_PORT = 9225;
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
- export class XhsLoginSession {
53
- constructor() {
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 = xhsProfileDir(userId);
64
- // Spawn Chrome
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(`[XhsLogin] Chrome spawn error: ${err.message}`);
111
+ console.error(`[BrowserLogin][${this._platform}] Chrome spawn error: ${err.message}`);
80
112
  this._stopTimers();
81
- connection.send({ type: 'xhs:login_error', error: `Chrome 启动失败: ${err.message}。请确认 Chrome 已安装(路径: ${CHROME_BIN}),或通过 CHROME_BIN 环境变量指定路径` });
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(`[XhsLogin] Chrome exited (code=${code})`);
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.navigate', { url: 'https://www.xiaohongshu.com' });
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; // base64 jpeg
188
+ return result.data;
162
189
  }
163
190
 
164
191
  async isLoggedIn() {
165
192
  const result = await this.send('Network.getAllCookies', {});
166
- const cookies = result.cookies ?? [];
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: 'xhs:screenshot', screenshot });
200
+ connection.send({ type: 'browser:screenshot', platform: this._platform, screenshot });
176
201
  } catch (err) {
177
- console.error('[XhsLogin] Screenshot error:', err.message);
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: 'xhs:login_complete', profileDir: this._profileDir });
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('[XhsLogin] Login check error:', err.message);
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
- try { this._ws.close(); } catch { /* ignore */ }
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
- let _session = null;
232
+ // ── Singleton map (platform → session) ───────────────────────────────────────
233
+
234
+ const _sessions = new Map();
216
235
 
217
- export function getSession() { return _session; }
236
+ export function getSession(platform) { return _sessions.get(platform) ?? null; }
218
237
 
219
- export async function startSession(connection, userId) {
220
- if (_session) { _session.close(); _session = null; }
221
- _session = new XhsLoginSession();
222
- await _session.start(connection, userId);
223
- return _session;
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
- if (_session) { _session.close(); _session = null; }
247
+ export function stopSession(platform) {
248
+ const session = _sessions.get(platform);
249
+ if (session) { session.close(); _sessions.delete(platform); }
228
250
  }