@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.9.16",
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',
@@ -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(`[XhsLogin] Chrome spawn error: ${err.message}`);
111
+ console.error(`[BrowserLogin][${this._platform}] Chrome spawn error: ${err.message}`);
84
112
  this._stopTimers();
85
- 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}` });
86
114
  });
87
115
 
88
116
  this._proc.on('exit', (code) => {
89
- console.log(`[XhsLogin] Chrome exited (code=${code})`);
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: 'https://www.xiaohongshu.com' });
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; // base64 jpeg
188
+ return result.data;
170
189
  }
171
190
 
172
191
  async isLoggedIn() {
173
192
  const result = await this.send('Network.getAllCookies', {});
174
- const cookies = result.cookies ?? [];
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: 'xhs:screenshot', screenshot });
200
+ connection.send({ type: 'browser:screenshot', platform: this._platform, screenshot });
184
201
  } catch (err) {
185
- console.error('[XhsLogin] Screenshot error:', err.message);
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: 'xhs:login_complete', profileDir: this._profileDir });
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('[XhsLogin] Login check error:', err.message);
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
- try { this._ws.close(); } catch { /* ignore */ }
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
- let _session = null;
232
+ // ── Singleton map (platform → session) ───────────────────────────────────────
233
+
234
+ const _sessions = new Map();
224
235
 
225
- export function getSession() { return _session; }
236
+ export function getSession(platform) { return _sessions.get(platform) ?? null; }
226
237
 
227
- export async function startSession(connection, userId) {
228
- if (_session) { _session.close(); _session = null; }
229
- _session = new XhsLoginSession();
230
- await _session.start(connection, userId);
231
- 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;
232
245
  }
233
246
 
234
- export function stopSession() {
235
- 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); }
236
250
  }