@lightcone-ai/daemon 0.15.27 → 0.15.29
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.
|
@@ -858,7 +858,7 @@ export function planVideo({
|
|
|
858
858
|
const coreMessage = toSafeString(understanding?.core_message || understanding?.coreMessage || understanding?.title) || '本页价值点概览';
|
|
859
859
|
|
|
860
860
|
const hotspots = collectHotspots(understanding);
|
|
861
|
-
const
|
|
861
|
+
const baseSkipZones = collectSkipZones(understanding);
|
|
862
862
|
const coreRange = resolveCoreRange(understanding, hotspots);
|
|
863
863
|
const semanticSlots = collectSemanticSlots(understanding);
|
|
864
864
|
const recruitmentFailClosedEnabled = shouldApplyRecruitmentFailClosed(understanding);
|
|
@@ -878,6 +878,16 @@ export function planVideo({
|
|
|
878
878
|
const modeInfo = resolveNarrationMode({ understanding, slots: semanticSlots });
|
|
879
879
|
const semanticSummary = buildSemanticSummary(semanticSlots);
|
|
880
880
|
|
|
881
|
+
const isRecruitmentMode = modeInfo.mode === 'job_intel_broadcast' || modeInfo.mode === 'job_alert';
|
|
882
|
+
// For recruitment pages, treat the entry/CTA region as a hard skip zone so the
|
|
883
|
+
// camera never scrolls into the QR-code / application-link area.
|
|
884
|
+
const entrySkipRange = isRecruitmentMode
|
|
885
|
+
? normalizeYRange(semanticSlots.entry_or_cta?.focus_region)
|
|
886
|
+
: null;
|
|
887
|
+
const skipZones = entrySkipRange
|
|
888
|
+
? [...baseSkipZones, entrySkipRange]
|
|
889
|
+
: baseSkipZones;
|
|
890
|
+
|
|
881
891
|
const hotspotHighlights = pickHighlightHotspots({
|
|
882
892
|
hotspots,
|
|
883
893
|
skipZones,
|
|
@@ -986,7 +996,11 @@ export function planVideo({
|
|
|
986
996
|
previousY = highlight.target_y;
|
|
987
997
|
}
|
|
988
998
|
|
|
989
|
-
|
|
999
|
+
// Recruitment pages: never navigate to entry/QR-code area for CTA.
|
|
1000
|
+
const ctaSlotCandidates = isRecruitmentMode
|
|
1001
|
+
? ['process', 'company']
|
|
1002
|
+
: ['entry_or_cta', 'process', 'company'];
|
|
1003
|
+
const ctaSlotKey = pickFirstAvailableSlot(semanticSlots, ctaSlotCandidates);
|
|
990
1004
|
const ctaSlot = ctaSlotKey ? semanticSlots[ctaSlotKey] : null;
|
|
991
1005
|
const ctaAnchor = focusRegionToHighlight({
|
|
992
1006
|
focusRegion: ctaSlot?.focus_region,
|
|
@@ -995,15 +1009,18 @@ export function planVideo({
|
|
|
995
1009
|
text: ctaSlotKey ? (slotValueToText(ctaSlot) || slotLabel(ctaSlotKey)) : '收尾行动',
|
|
996
1010
|
confidence: ctaSlot?.confidence ?? 0.38,
|
|
997
1011
|
});
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1012
|
+
// Recruitment mode: always scroll back to core start — never scroll to entry/QR area.
|
|
1013
|
+
const ctaAction = isRecruitmentMode
|
|
1014
|
+
? buildCtaAction({ coreRange })
|
|
1015
|
+
: (ctaAnchor && modeInfo.mode !== 'refuse_auto_broadcast'
|
|
1016
|
+
? {
|
|
1017
|
+
type: 'scroll_to_dwell',
|
|
1018
|
+
target_hotspot: ctaAnchor.id,
|
|
1019
|
+
target_y: ctaAnchor.target_y,
|
|
1020
|
+
transition_ms: 900,
|
|
1021
|
+
reason: '收尾聚焦行动提示',
|
|
1022
|
+
}
|
|
1023
|
+
: buildCtaAction({ coreRange }));
|
|
1007
1024
|
|
|
1008
1025
|
phasePlan.push({
|
|
1009
1026
|
phase_id: 'cta',
|
|
@@ -1013,7 +1030,7 @@ export function planVideo({
|
|
|
1013
1030
|
semantic_slot: ctaSlotKey,
|
|
1014
1031
|
focus_region: ctaAnchor?.y_range ?? null,
|
|
1015
1032
|
confidence: ctaSlot ? Number(clampNumber(ctaSlot.confidence, 0, 1, 0).toFixed(2)) : null,
|
|
1016
|
-
guidance: '
|
|
1033
|
+
guidance: '介绍完最后一个信息点直接结束,禁止加任何收口语(不说"感兴趣去原文查看""截止见正文"等),禁止提及 URL、网址、二维码或投递入口',
|
|
1017
1034
|
});
|
|
1018
1035
|
|
|
1019
1036
|
const cappedPlan = phasePlan.slice(0, 5);
|
|
@@ -1137,27 +1154,7 @@ function buildPhaseSentence({ phase, strategy, phaseIndex }) {
|
|
|
1137
1154
|
}
|
|
1138
1155
|
|
|
1139
1156
|
if (role === 'cta') {
|
|
1140
|
-
|
|
1141
|
-
return ensureSentenceLength(
|
|
1142
|
-
`感兴趣的同学去原文查看完整岗位要求和截止时间,对照自己情况再决定要不要投。`
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
if (mode === 'job_alert') {
|
|
1147
|
-
return ensureSentenceLength(
|
|
1148
|
-
`细节不完整,建议收藏原文,等正式公告发布后再核对岗位方向、城市和流程再决定。`
|
|
1149
|
-
);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
if (mode === 'refuse_auto_broadcast') {
|
|
1153
|
-
return ensureSentenceLength(
|
|
1154
|
-
'页面信息不完整,暂不播报。建议直接去官方渠道确认公司、岗位和发布时间后再做判断。'
|
|
1155
|
-
);
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
return ensureSentenceLength(
|
|
1159
|
-
'具体要求和截止时间见原文,建议直接查看官方公告,避免依赖摘要信息做投递判断。'
|
|
1160
|
-
);
|
|
1157
|
+
return null;
|
|
1161
1158
|
}
|
|
1162
1159
|
|
|
1163
1160
|
if (mode === 'job_intel_broadcast') {
|
|
@@ -1369,6 +1366,8 @@ export async function detailSections({
|
|
|
1369
1366
|
phaseIndex: i,
|
|
1370
1367
|
});
|
|
1371
1368
|
|
|
1369
|
+
if (sentence == null) continue;
|
|
1370
|
+
|
|
1372
1371
|
const voice = resolveVoiceForPhase({ profile, role, index: i });
|
|
1373
1372
|
const phaseBudgetMs = clampInt((toFiniteNumber(phase?.duration_s) ?? 6) * 1000, 1000, 180000, 6000);
|
|
1374
1373
|
|
|
@@ -1444,6 +1443,7 @@ export async function detailSections({
|
|
|
1444
1443
|
const totalDurationMs = sections.reduce((sum, item) => sum + item.dwell_ms, 0);
|
|
1445
1444
|
|
|
1446
1445
|
return {
|
|
1446
|
+
detail_sections_version: 1,
|
|
1447
1447
|
sections,
|
|
1448
1448
|
outro_video_id: toSafeString(strategy?.outro_video_id || strategy?.outroVideoId) || DEFAULT_OUTRO_VIDEO_ID,
|
|
1449
1449
|
total_duration_ms: totalDurationMs,
|
package/package.json
CHANGED
|
@@ -38,7 +38,14 @@ export async function launchChromiumMobile({
|
|
|
38
38
|
hasTouch = true,
|
|
39
39
|
headless = false,
|
|
40
40
|
channel = 'chrome',
|
|
41
|
-
launchArgs = [
|
|
41
|
+
launchArgs = [
|
|
42
|
+
'--no-sandbox',
|
|
43
|
+
'--disable-dev-shm-usage',
|
|
44
|
+
'--kiosk',
|
|
45
|
+
'--disable-infobars',
|
|
46
|
+
'--no-first-run',
|
|
47
|
+
'--no-default-browser-check',
|
|
48
|
+
],
|
|
42
49
|
playwrightModule = 'playwright',
|
|
43
50
|
launchOptions = {},
|
|
44
51
|
contextOptions = {},
|
|
@@ -195,7 +195,7 @@ export async function recordUrlNarration({
|
|
|
195
195
|
xvfbStopTimeoutMs = 5000,
|
|
196
196
|
postPlanTailMs = 600,
|
|
197
197
|
} = {}) {
|
|
198
|
-
const zoom = Number.isFinite(Number(page_zoom)) && Number(page_zoom) > 0 ? Number(page_zoom) : 1.
|
|
198
|
+
const zoom = Number.isFinite(Number(page_zoom)) && Number(page_zoom) > 0 ? Number(page_zoom) : 1.1;
|
|
199
199
|
const rawPhases = normalizePlanPhases(plan);
|
|
200
200
|
const phases = zoom !== 1.0 ? rawPhases.map(p => scalePhaseY(p, zoom)) : rawPhases;
|
|
201
201
|
const executablePlan = {
|
package/src/chat-bridge.js
CHANGED
|
@@ -1481,7 +1481,6 @@ server.tool('record_url_narration',
|
|
|
1481
1481
|
}).optional().describe('Default 1080x1920 (mobile portrait). Override only if the plan requires a different shape.'),
|
|
1482
1482
|
fps: z.number().optional().describe('Default 30. Do not change unless needed.'),
|
|
1483
1483
|
settle_ms: z.number().optional().describe('Default 4000. Settle wait after navigation before recording starts.'),
|
|
1484
|
-
page_zoom: z.number().optional().describe('Browser zoom factor applied before recording. Default 1.1 (10% zoom in). Set to 1.0 to disable. Plan Y coordinates are automatically scaled by this factor.'),
|
|
1485
1484
|
},
|
|
1486
1485
|
async (args) => {
|
|
1487
1486
|
if (isBlockedCvmaxEditorVideoTool('record_url_narration')) {
|
|
@@ -48,6 +48,19 @@ function derivePhaseCount({ plan, recorderOutput }) {
|
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const PIPELINE_SENTINEL_KEY = 'detail_sections_version';
|
|
52
|
+
|
|
53
|
+
function assertPipelineCompliance(plan) {
|
|
54
|
+
if (!isPlainObject(plan)) return;
|
|
55
|
+
if (!plan[PIPELINE_SENTINEL_KEY]) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'pipeline_violation: plan must come from detail_sections output. '
|
|
58
|
+
+ 'Required pipeline: analyze_page → plan_video → detail_sections → generate_voiceover → record_url_narration → compose_video → submit_to_library. '
|
|
59
|
+
+ 'Do not hand-write phases or bypass detail_sections.'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
export function validateRecordUrlNarrationArgs(args = {}) {
|
|
52
65
|
const normalizedUrl = normalizeText(args.url);
|
|
53
66
|
if (!normalizedUrl) {
|
|
@@ -113,6 +126,12 @@ export async function runRecordUrlNarrationTool({
|
|
|
113
126
|
return toolError(`Error: ${error.message}`);
|
|
114
127
|
}
|
|
115
128
|
|
|
129
|
+
try {
|
|
130
|
+
assertPipelineCompliance(validatedInput.plan);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return toolError(`Error: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
116
135
|
try {
|
|
117
136
|
const result = await runMandatoryLocalToolFn({
|
|
118
137
|
toolName: 'record_url_narration',
|
|
@@ -141,7 +160,6 @@ export async function runRecordUrlNarrationTool({
|
|
|
141
160
|
viewport: finalInput.viewport,
|
|
142
161
|
fps: finalInput.fps,
|
|
143
162
|
settle_ms: finalInput.settle_ms,
|
|
144
|
-
page_zoom: finalInput.page_zoom,
|
|
145
163
|
});
|
|
146
164
|
|
|
147
165
|
return {
|