@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 +1 -1
- package/src/agent-manager.js +31 -0
- package/src/browser-login.js +84 -7
- package/src/chat-bridge.js +1 -1
- package/src/connection.js +10 -17
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -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}`);
|
package/src/browser-login.js
CHANGED
|
@@ -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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
519
|
-
|
|
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
|
}
|
package/src/chat-bridge.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
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
|
-
|
|
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}`
|