@lightcone-ai/daemon 0.15.39 → 0.15.41
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.
|
@@ -93,7 +93,7 @@ const IMAGE_PUBLISH_URL = 'https://creator.xiaohongshu.com/publish/publish?targe
|
|
|
93
93
|
const VIDEO_PUBLISH_URL = 'https://creator.xiaohongshu.com/publish/publish?source=video';
|
|
94
94
|
|
|
95
95
|
const CREATOR_HOME_URL = 'https://creator.xiaohongshu.com/new/home';
|
|
96
|
-
const IMAGE_FILE_INPUT_SELECTOR = 'input.upload-input[type="file"][accept*=".jpg"], input[type="file"][accept*=".png"], input[type="file"][accept*=".webp"]';
|
|
96
|
+
const IMAGE_FILE_INPUT_SELECTOR = 'input.upload-input[type="file"][accept*=".jpg"], input[type="file"][accept*="image"], input[type="file"][accept*=".jpg"], input[type="file"][accept*=".png"], input[type="file"][accept*=".webp"]';
|
|
97
97
|
const VIDEO_FILE_INPUT_SELECTOR = 'input.upload-input[type="file"][accept*=".mp4"], input[type="file"][accept*=".mov"], input[type="file"][accept*=".mpeg"]';
|
|
98
98
|
const TITLE_SELECTOR = 'input[placeholder*="标题"], input.d-text[type="text"]';
|
|
99
99
|
const CONTENT_SELECTOR = '.ProseMirror[contenteditable="true"], [contenteditable="true"][role="textbox"], [contenteditable="true"]';
|
|
@@ -409,18 +409,38 @@ export class XhsAdapter extends PublisherAdapter {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
async _waitForUploadSettled(expectedCount, timeoutMs) {
|
|
412
|
+
if (expectedCount === 0) return;
|
|
412
413
|
const deadline = Date.now() + timeoutMs;
|
|
414
|
+
let sawProgress = false;
|
|
415
|
+
let stableRounds = 0;
|
|
416
|
+
|
|
413
417
|
while (Date.now() < deadline) {
|
|
414
418
|
await sleep(1000);
|
|
415
419
|
await this._assertNoBlockingErrors();
|
|
416
420
|
const state = await this._inspectPage();
|
|
417
421
|
const text = state.text ?? '';
|
|
422
|
+
|
|
418
423
|
if (/上传失败|上传出错|格式不支持|文件过大/.test(text)) {
|
|
419
424
|
throw new Error(`UPLOAD_FAILED: ${(state.errors ?? []).join(';') || '页面提示上传失败'}`);
|
|
420
425
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
426
|
+
|
|
427
|
+
const inProgress = /上传中|处理中|转码中|正在上传|解析中/.test(text);
|
|
428
|
+
if (inProgress) { sawProgress = true; stableRounds = 0; continue; }
|
|
429
|
+
|
|
430
|
+
// Definitive upload completion signals
|
|
431
|
+
if (/图片编辑|视频编辑|笔记预览|上传成功|重新上传|更换图片|已上传|封面/.test(text)) return;
|
|
432
|
+
|
|
433
|
+
stableRounds++;
|
|
434
|
+
|
|
435
|
+
// If we saw upload start and then it stopped, done (give 2 stable rounds)
|
|
436
|
+
if (sawProgress && stableRounds >= 2) return;
|
|
437
|
+
|
|
438
|
+
// hasPublishEditor alone is not enough — the form is present from page load.
|
|
439
|
+
// Only treat it as "done" after 10+ stable seconds (gives time for upload progress to appear)
|
|
440
|
+
if (state.hasPublishEditor && stableRounds >= 10) {
|
|
441
|
+
console.error('[XhsAdapter] _waitForUploadSettled: hasPublishEditor stable for 10s, proceeding');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
424
444
|
}
|
|
425
445
|
throw new Error(`UPLOAD_TIMEOUT: 等待小红书上传完成超时`);
|
|
426
446
|
}
|
|
@@ -509,16 +529,54 @@ export class XhsAdapter extends PublisherAdapter {
|
|
|
509
529
|
async _uploadFiles(filePaths, type = 'image') {
|
|
510
530
|
await humanPause(600, 1700, 'before-set-files');
|
|
511
531
|
const selector = type === 'video' ? VIDEO_FILE_INPUT_SELECTOR : IMAGE_FILE_INPUT_SELECTOR;
|
|
512
|
-
|
|
513
|
-
|
|
532
|
+
|
|
533
|
+
// Use DOM.getDocument + DOM.querySelector for a stable nodeId
|
|
534
|
+
let nodeId = null;
|
|
535
|
+
const deadline = Date.now() + 10000;
|
|
536
|
+
while (Date.now() < deadline) {
|
|
537
|
+
try {
|
|
538
|
+
const docResult = await this._cdp.send('DOM.getDocument', { depth: 0 });
|
|
539
|
+
const qResult = await this._cdp.send('DOM.querySelector', {
|
|
540
|
+
nodeId: docResult.root.nodeId,
|
|
541
|
+
selector,
|
|
542
|
+
});
|
|
543
|
+
if (qResult.nodeId) { nodeId = qResult.nodeId; break; }
|
|
544
|
+
} catch (e) {}
|
|
545
|
+
await sleep(500);
|
|
546
|
+
}
|
|
547
|
+
if (!nodeId) throw new Error(`No ${type} file input found on page`);
|
|
548
|
+
|
|
549
|
+
// Force multiple-file support and ensure input is enabled before setting files
|
|
550
|
+
await this._cdp.send('Runtime.evaluate', {
|
|
551
|
+
expression: `
|
|
552
|
+
(function() {
|
|
553
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
554
|
+
if (el) {
|
|
555
|
+
el.removeAttribute('disabled');
|
|
556
|
+
if (!el.hasAttribute('multiple')) el.setAttribute('multiple', '');
|
|
557
|
+
}
|
|
558
|
+
})()
|
|
559
|
+
`,
|
|
514
560
|
returnByValue: false,
|
|
515
561
|
});
|
|
516
|
-
if (!result.result?.objectId) throw new Error(`No ${type} file input found on page`);
|
|
517
562
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
563
|
+
console.error(`[XhsAdapter] _uploadFiles: type=${type} files=${filePaths.length} nodeId=${nodeId}`);
|
|
564
|
+
await this._cdp.send('DOM.setFileInputFiles', { nodeId, files: filePaths });
|
|
565
|
+
|
|
566
|
+
// Dispatch extra change/input events in case the Vue handler needs them
|
|
567
|
+
await this._cdp.send('Runtime.evaluate', {
|
|
568
|
+
expression: `
|
|
569
|
+
(function() {
|
|
570
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
571
|
+
if (el) {
|
|
572
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
573
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
574
|
+
}
|
|
575
|
+
})()
|
|
576
|
+
`,
|
|
577
|
+
returnByValue: false,
|
|
521
578
|
});
|
|
579
|
+
|
|
522
580
|
await humanPause(1200, 2600, 'after-set-files');
|
|
523
581
|
}
|
|
524
582
|
|
package/package.json
CHANGED
package/src/drivers/claude.js
CHANGED
|
@@ -138,7 +138,7 @@ Only top-level workspace / DM messages can become tasks. Messages inside threads
|
|
|
138
138
|
**Primary-agent dispatch hard rule (fail-closed):**
|
|
139
139
|
- If your role is the workspace primary agent/owner and a user sends an execution request, you MUST call \`${t("create_tasks")}\` first and include an explicit \`scenario_type\`, then immediately send a visible acknowledgment/update via \`${t("send_message")}\`.
|
|
140
140
|
- Execution requests include requests like content writing, short-video scripting, research, design/asset production, implementation, or any request that requires downstream execution instead of a simple answer.
|
|
141
|
-
- Use \`scenario_type\` values declared by your scenario manifest/dispatch protocol (for example: \`trend_scan\`, \`
|
|
141
|
+
- Use \`scenario_type\` values declared by your scenario manifest/dispatch protocol (for example: \`trend_scan\`, \`topic_scan\`, \`research\`, \`text_writing\`, \`video_scripting\`, \`publish\`).
|
|
142
142
|
- Do not route execution work with only \`${t("send_message")}\`: skipping \`${t("create_tasks")}\` can cause downstream \`${t("claim_tasks")}\` failures and deadlock the workflow.
|
|
143
143
|
- If the request is a direct Q&A (no downstream execution dispatch needed), reply directly with \`${t("send_message")}\` and do not force \`${t("create_tasks")}\`.
|
|
144
144
|
|