@lightcone-ai/daemon 0.15.60 → 0.15.62

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.15.60",
3
+ "version": "0.15.62",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -2,9 +2,11 @@ import { spawn } from 'child_process';
2
2
  import { createHash } from 'crypto';
3
3
  import {
4
4
  existsSync,
5
+ cpSync,
5
6
  mkdirSync,
6
7
  readFileSync,
7
8
  readdirSync,
9
+ rmSync,
8
10
  statSync,
9
11
  unlinkSync,
10
12
  writeFileSync,
@@ -230,6 +232,7 @@ export class AgentManager {
230
232
  case 'agent:reprobe': return this._reprobeCapability(msg, connection);
231
233
  case 'publish:job': return this._handlePublishJob(msg, connection);
232
234
  case 'browser:start_login': return this._startBrowserLogin(msg, connection);
235
+ case 'browser:bind_profile': return this._bindBrowserProfile(msg, connection);
233
236
  case 'browser:stop_login': return this._stopBrowserLogin(msg);
234
237
  case 'policy_invalidate': return this._handlePolicyInvalidate(msg, connection);
235
238
  case 'credential_revoked': return this._handleCredentialRevoked(msg, connection);
@@ -1506,6 +1509,34 @@ export class AgentManager {
1506
1509
  }
1507
1510
  }
1508
1511
 
1512
+ _bindBrowserProfile(msg, connection) {
1513
+ const platform = String(msg.platform ?? 'xhs').trim() || 'xhs';
1514
+ const credentialId = String(msg.credentialId ?? '').trim();
1515
+ const sourceProfileDir = String(msg.sourceProfileDir ?? '').trim();
1516
+ if (!credentialId || !sourceProfileDir) {
1517
+ connection.send({
1518
+ type: 'browser:login_error',
1519
+ platform,
1520
+ error: 'browser profile bind missing credentialId/sourceProfileDir',
1521
+ });
1522
+ return;
1523
+ }
1524
+ const targetProfileDir = path.join(homedir(), '.lightcone', 'chrome-profiles', `cred-${credentialId}`);
1525
+ try {
1526
+ if (!existsSync(sourceProfileDir)) {
1527
+ throw new Error(`source profile does not exist: ${sourceProfileDir}`);
1528
+ }
1529
+ rmSync(targetProfileDir, { recursive: true, force: true });
1530
+ mkdirSync(path.dirname(targetProfileDir), { recursive: true });
1531
+ cpSync(sourceProfileDir, targetProfileDir, { recursive: true });
1532
+ console.log(`[AgentManager] Bound browser profile platform=${platform} credential=${credentialId}`);
1533
+ connection.send({ type: 'browser:profile_bound', platform, credentialId });
1534
+ } catch (err) {
1535
+ console.error(`[AgentManager] Failed to bind browser profile (${platform}):`, err.message);
1536
+ connection.send({ type: 'browser:login_error', platform, error: `profile_bind_failed:${err.message}` });
1537
+ }
1538
+ }
1539
+
1509
1540
  _stopBrowserLogin(msg) {
1510
1541
  const platform = msg.platform ?? 'xhs';
1511
1542
  console.log(`[AgentManager] Stopping browser login for platform=${platform}`);
@@ -461,6 +461,77 @@ export class BrowserLoginSession {
461
461
  return true;
462
462
  }
463
463
 
464
+ async _extractAccountIdentity(cookies = []) {
465
+ if (this._platform !== 'xhs') return null;
466
+ const result = await this.send('Runtime.evaluate', {
467
+ expression: `(() => {
468
+ const out = { externalAccountId: '', externalDisplayName: '', externalAvatarUrl: '' };
469
+ const seen = new Set();
470
+ const candidates = [];
471
+ const push = (value, source) => {
472
+ if (value == null) return;
473
+ const text = String(value).trim();
474
+ if (!text || text.length < 3 || text.length > 160 || seen.has(text)) return;
475
+ seen.add(text);
476
+ candidates.push({ value: text, source });
477
+ };
478
+ const visit = (obj, source, depth = 0) => {
479
+ if (!obj || depth > 5) return;
480
+ if (typeof obj === 'string') {
481
+ const trimmed = obj.trim();
482
+ if (!trimmed) return;
483
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
484
+ try { visit(JSON.parse(trimmed), source, depth + 1); } catch {}
485
+ }
486
+ return;
487
+ }
488
+ if (typeof obj !== 'object') return;
489
+ for (const [key, value] of Object.entries(obj)) {
490
+ const normalizedKey = String(key).toLowerCase();
491
+ if (['userid', 'user_id', 'user id', 'creatorid', 'creator_id', 'redid', 'red_id', 'authorid', 'author_id', 'accountid', 'account_id'].includes(normalizedKey)) {
492
+ push(value, source + ':' + key);
493
+ }
494
+ if (!out.externalDisplayName && ['nickname', 'nick_name', 'name', 'username', 'user_name'].includes(normalizedKey) && typeof value === 'string') {
495
+ out.externalDisplayName = value.trim().slice(0, 80);
496
+ }
497
+ if (!out.externalAvatarUrl && ['avatar', 'avatarurl', 'avatar_url', 'image'].includes(normalizedKey) && typeof value === 'string' && /^https?:\\/\\//.test(value)) {
498
+ out.externalAvatarUrl = value.trim();
499
+ }
500
+ if (value && typeof value === 'object') visit(value, source + ':' + key, depth + 1);
501
+ else if (typeof value === 'string' && value.length < 5000) visit(value, source + ':' + key, depth + 1);
502
+ }
503
+ };
504
+ for (const storage of [localStorage, sessionStorage]) {
505
+ for (let i = 0; i < storage.length; i += 1) {
506
+ const key = storage.key(i);
507
+ const value = storage.getItem(key);
508
+ const lk = String(key || '').toLowerCase();
509
+ if (/(user|account|creator|red)/.test(lk)) {
510
+ push(value, 'storage:' + key);
511
+ visit(value, 'storage:' + key);
512
+ }
513
+ }
514
+ }
515
+ for (const key of ['__INITIAL_STATE__', '__INITIAL_DATA__', '__REDUX_STATE__']) {
516
+ if (window[key]) visit(window[key], 'window:' + key);
517
+ }
518
+ out.externalAccountId = candidates[0]?.value || '';
519
+ out.identitySource = candidates[0]?.source || '';
520
+ return out;
521
+ })()`,
522
+ returnByValue: true,
523
+ }, 5000);
524
+ const value = result?.result?.value ?? {};
525
+ const externalAccountId = String(value.externalAccountId ?? '').trim();
526
+ if (!externalAccountId) return null;
527
+ return {
528
+ externalAccountId,
529
+ externalDisplayName: String(value.externalDisplayName ?? '').trim(),
530
+ externalAvatarUrl: String(value.externalAvatarUrl ?? '').trim(),
531
+ identitySource: String(value.identitySource ?? (value.externalAccountId ? 'page' : 'session_cookie')).trim(),
532
+ };
533
+ }
534
+
464
535
  _startPolling(connection, baselineSession) {
465
536
  let _screenshotInProgress = false;
466
537
  this._screenshotTimer = setInterval(async () => {
@@ -485,10 +556,11 @@ export class BrowserLoginSession {
485
556
  try {
486
557
  const cookieResult = await this.send('Network.getAllCookies', {});
487
558
  const baseDomain = new URL(this._config.loginUrl).hostname.split('.').slice(-2).join('.');
488
- const cookies = (cookieResult.cookies ?? []).filter(c =>
489
- c.domain.includes(baseDomain)
490
- );
491
- writeFileSync(path.join(this._profileDir, 'cookies.json'), JSON.stringify(cookies));
559
+ const cookies = (cookieResult.cookies ?? []).filter(c =>
560
+ c.domain.includes(baseDomain)
561
+ );
562
+ var accountIdentity = await this._extractAccountIdentity(cookies);
563
+ writeFileSync(path.join(this._profileDir, 'cookies.json'), JSON.stringify(cookies));
492
564
  console.log(`[BrowserLogin][${this._platform}] Saved ${cookies.length} cookies to cookies.json`);
493
565
  if (this._platform === 'wechat_mp') {
494
566
  const hrefResult = await this.send('Runtime.evaluate', {
@@ -515,9 +587,14 @@ export class BrowserLoginSession {
515
587
  closePublisherSession(this._platform);
516
588
  console.log(`[BrowserLogin][${this._platform}] Closed stale publisher Chrome session after re-login`);
517
589
  } catch {}
518
- connection.send({ type: 'browser:login_complete', platform: this._platform, profileDir: this._profileDir });
519
- await this.close();
520
- }
590
+ await this.close();
591
+ connection.send({
592
+ type: 'browser:login_complete',
593
+ platform: this._platform,
594
+ profileDir: this._profileDir,
595
+ accountIdentity,
596
+ });
597
+ }
521
598
  } catch (err) {
522
599
  console.error(`[BrowserLogin][${this._platform}] Login check error:`, err.message);
523
600
  }
@@ -1529,7 +1529,7 @@ server.tool('request_approval',
1529
1529
  platform: z.string().describe('Target platform, e.g. "x", "xhs", "email"'),
1530
1530
  description: z.string().describe('Human-readable summary of what will happen if approved'),
1531
1531
  payload: z.record(z.any()).describe('Full action parameters (content, media_urls, etc.)'),
1532
- credential_id: z.string().optional().describe('Which credential ID to use for execution'),
1532
+ credential_id: z.string().optional().describe('Which account/credential to use. For publishing, prefer a workspace account_id or real credential UUID. Role aliases like primary/test are accepted only if they uniquely match a workspace account.'),
1533
1533
  },
1534
1534
  async ({ action_type, platform, description, payload, credential_id }) => {
1535
1535
  try {
package/src/connection.js CHANGED
@@ -45,16 +45,18 @@ function parseOptionalDeviceHints() {
45
45
  }
46
46
 
47
47
  export class DaemonConnection {
48
- constructor({ serverUrl, machineApiKey, onMessage, getAgentInventory = null }) {
48
+ constructor({ serverUrl, machineApiKey, onMessage, getAgentInventory }) {
49
+ if (typeof getAgentInventory !== 'function') {
50
+ throw new TypeError('DaemonConnection: getAgentInventory callback is required');
51
+ }
49
52
  this.serverUrl = serverUrl.replace(/^http/, 'ws');
50
53
  this.machineApiKey = machineApiKey;
51
54
  this.onMessage = onMessage;
52
- // Optional callback invoked at 'ready' time to snapshot which agents
53
- // this daemon is currently managing. Server uses this to reconcile
54
- // stale lifecycle state after reconnects (see src/daemon/index.js
55
- // 'ready' handler agents previously on this machine but missing
56
- // from inventory get reset from stale crashed→standby).
57
- this.getAgentInventory = typeof getAgentInventory === 'function' ? getAgentInventory : null;
55
+ // Snapshot of agents this daemon is currently managing, sent at 'ready'
56
+ // time. The server uses it to reconcile stale lifecycle state after
57
+ // reconnects (see src/daemon/index.js 'ready' handler — agents on this
58
+ // machine but missing from inventory get reset from stale crashed→standby).
59
+ this.getAgentInventory = getAgentInventory;
58
60
  this.ws = null;
59
61
  this.reconnectDelay = RECONNECT_INITIAL;
60
62
  this.stopped = false;
@@ -117,16 +119,7 @@ export class DaemonConnection {
117
119
  ...parseOptionalDeviceHints(),
118
120
  };
119
121
 
120
- let agentInventory = [];
121
- if (this.getAgentInventory) {
122
- try {
123
- const snapshot = this.getAgentInventory();
124
- if (Array.isArray(snapshot)) agentInventory = snapshot;
125
- } catch (err) {
126
- console.warn(`[Connection] getAgentInventory failed: ${err?.message ?? err}`);
127
- }
128
- }
129
-
122
+ const agentInventory = this.getAgentInventory();
130
123
  console.log(
131
124
  `[Connection] Ready — host=${hostname} runtimes=[${runtimes.join(',')}] v${DAEMON_VERSION}`
132
125
  + ` inventory=${agentInventory.length}`