@lightcone-ai/daemon 0.14.17 → 0.14.18
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/mcp-servers/official/keyword-research/index.js +95 -56
- package/mcp-servers/official/keyword-research/keyword-fixtures.json +58 -0
- package/mcp-servers/official/page-understanding/index.js +19 -0
- package/mcp-servers/official/page-understanding/manifest.json +8 -0
- package/mcp-servers/official/platform-policy-db/index.js +117 -163
- package/mcp-servers/official/platform-policy-db/policy-fixtures.json +170 -0
- package/mcp-servers/official/video-narration-planner/core.js +154 -116
- package/mcp-servers/official/video-narration-planner/index.js +29 -0
- package/mcp-servers/official/video-narration-planner/manifest.json +14 -0
- package/mcp-servers/official/video-narration-planner/planner-config.json +112 -0
- package/mcp-servers/official-common/tool-access-policy.js +90 -0
- package/package.json +1 -1
- package/src/agent-manager.js +205 -22
- package/src/chat-bridge.js +55 -12
- package/src/index.js +2 -1
- package/src/lifecycle-protocol.js +51 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fixture_meta": {
|
|
3
|
+
"mode": "fixture",
|
|
4
|
+
"as_of": "2026-04-29",
|
|
5
|
+
"capability": "platform-policy-db",
|
|
6
|
+
"disclaimer": "Policy fixtures are static snapshots; always verify against latest platform notices."
|
|
7
|
+
},
|
|
8
|
+
"platform_aliases": {
|
|
9
|
+
"xiaohongshu": "xhs",
|
|
10
|
+
"redbook": "xhs",
|
|
11
|
+
"wechat": "wechat-mp",
|
|
12
|
+
"wechatmp": "wechat-mp",
|
|
13
|
+
"wechat_mp": "wechat-mp",
|
|
14
|
+
"gzh": "wechat-mp",
|
|
15
|
+
"公众号": "wechat-mp",
|
|
16
|
+
"tiktok_cn": "douyin"
|
|
17
|
+
},
|
|
18
|
+
"platform_policy_data": {
|
|
19
|
+
"xhs": {
|
|
20
|
+
"platform": "xhs",
|
|
21
|
+
"display_name": "小红书",
|
|
22
|
+
"policy_version": "xhs-2026.04",
|
|
23
|
+
"updated_at": "2026-04-22",
|
|
24
|
+
"ai_label_required": true,
|
|
25
|
+
"ad_disclosure_required": true,
|
|
26
|
+
"policy_window_days": 7,
|
|
27
|
+
"required_labels": {
|
|
28
|
+
"ad": "赞助",
|
|
29
|
+
"ai": "AI 生成内容标识"
|
|
30
|
+
},
|
|
31
|
+
"sensitive_terms": [
|
|
32
|
+
{
|
|
33
|
+
"id": "xhs-external-redirect-wechat",
|
|
34
|
+
"term": "微信",
|
|
35
|
+
"severity": "blocker",
|
|
36
|
+
"reason": "站外导流词,易触发限流或违规。",
|
|
37
|
+
"action": "remove_redirect_hint"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "xhs-external-redirect-qr",
|
|
41
|
+
"term": "二维码",
|
|
42
|
+
"severity": "blocker",
|
|
43
|
+
"reason": "站外导流相关词,发布前需清理。",
|
|
44
|
+
"action": "remove_redirect_hint"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "xhs-fake-luxury",
|
|
48
|
+
"term": "高仿",
|
|
49
|
+
"severity": "blocker",
|
|
50
|
+
"reason": "涉嫌仿冒交易,政策高风险。",
|
|
51
|
+
"action": "reject_publish"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "xhs-medical-guarantee",
|
|
55
|
+
"term": "疗效保证",
|
|
56
|
+
"severity": "warn",
|
|
57
|
+
"reason": "医疗功效承诺需附免责声明并谨慎表述。",
|
|
58
|
+
"action": "add_disclaimer"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "xhs-finance-guarantee",
|
|
62
|
+
"term": "稳赚不赔",
|
|
63
|
+
"severity": "blocker",
|
|
64
|
+
"reason": "金融收益承诺违规风险高。",
|
|
65
|
+
"action": "remove_claim"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"douyin": {
|
|
70
|
+
"platform": "douyin",
|
|
71
|
+
"display_name": "抖音",
|
|
72
|
+
"policy_version": "douyin-2026.04",
|
|
73
|
+
"updated_at": "2026-04-20",
|
|
74
|
+
"ai_label_required": true,
|
|
75
|
+
"ad_disclosure_required": true,
|
|
76
|
+
"policy_window_days": 7,
|
|
77
|
+
"required_labels": {
|
|
78
|
+
"ad": "广告",
|
|
79
|
+
"ai": "AI 生成声明"
|
|
80
|
+
},
|
|
81
|
+
"sensitive_terms": [
|
|
82
|
+
{
|
|
83
|
+
"id": "douyin-fake-news",
|
|
84
|
+
"term": "内幕消息",
|
|
85
|
+
"severity": "blocker",
|
|
86
|
+
"reason": "未经证实信息易触发平台处罚。",
|
|
87
|
+
"action": "remove_unverified_claim"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "douyin-disaster-clickbait",
|
|
91
|
+
"term": "震惊全国",
|
|
92
|
+
"severity": "warn",
|
|
93
|
+
"reason": "标题党表达可能降低推荐稳定性。",
|
|
94
|
+
"action": "tone_down_title"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "douyin-finance-guarantee",
|
|
98
|
+
"term": "保本保收益",
|
|
99
|
+
"severity": "blocker",
|
|
100
|
+
"reason": "金融收益保障承诺违规。",
|
|
101
|
+
"action": "remove_claim"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "douyin-medical-cure",
|
|
105
|
+
"term": "包治百病",
|
|
106
|
+
"severity": "blocker",
|
|
107
|
+
"reason": "医疗夸大宣传高风险。",
|
|
108
|
+
"action": "remove_claim"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "douyin-traffic-manipulation",
|
|
112
|
+
"term": "互赞互粉",
|
|
113
|
+
"severity": "warn",
|
|
114
|
+
"reason": "可被识别为异常增长操作。",
|
|
115
|
+
"action": "remove_growth_hack"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"wechat-mp": {
|
|
120
|
+
"platform": "wechat-mp",
|
|
121
|
+
"display_name": "公众号",
|
|
122
|
+
"policy_version": "wechat-mp-2026.04",
|
|
123
|
+
"updated_at": "2026-04-19",
|
|
124
|
+
"ai_label_required": false,
|
|
125
|
+
"ad_disclosure_required": true,
|
|
126
|
+
"policy_window_days": 7,
|
|
127
|
+
"required_labels": {
|
|
128
|
+
"ad": "广告/合作声明",
|
|
129
|
+
"ai": "建议标注 AI 生成来源"
|
|
130
|
+
},
|
|
131
|
+
"sensitive_terms": [
|
|
132
|
+
{
|
|
133
|
+
"id": "wechatmp-clickbait-share",
|
|
134
|
+
"term": "不转不是中国人",
|
|
135
|
+
"severity": "blocker",
|
|
136
|
+
"reason": "诱导分享属于明确违规。",
|
|
137
|
+
"action": "remove_manipulative_phrase"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "wechatmp-political-rumor",
|
|
141
|
+
"term": "内部通知",
|
|
142
|
+
"severity": "warn",
|
|
143
|
+
"reason": "疑似无来源权威口径,需补证据。",
|
|
144
|
+
"action": "add_citation_or_remove"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "wechatmp-medical-guarantee",
|
|
148
|
+
"term": "根治",
|
|
149
|
+
"severity": "warn",
|
|
150
|
+
"reason": "医疗强承诺需免责声明并谨慎表达。",
|
|
151
|
+
"action": "add_disclaimer"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "wechatmp-finance-guarantee",
|
|
155
|
+
"term": "稳赚",
|
|
156
|
+
"severity": "blocker",
|
|
157
|
+
"reason": "金融收益承诺违规。",
|
|
158
|
+
"action": "remove_claim"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "wechatmp-fake-scarcity",
|
|
162
|
+
"term": "最后一天",
|
|
163
|
+
"severity": "warn",
|
|
164
|
+
"reason": "虚假稀缺文案易触发风控。",
|
|
165
|
+
"action": "verify_or_tone_down"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
|
|
4
5
|
export const VISUAL_ACTION_TYPES = Object.freeze([
|
|
5
6
|
'scroll_to_dwell',
|
|
@@ -9,67 +10,13 @@ export const VISUAL_ACTION_TYPES = Object.freeze([
|
|
|
9
10
|
]);
|
|
10
11
|
|
|
11
12
|
const VISUAL_ACTION_SET = new Set(VISUAL_ACTION_TYPES);
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const PLATFORM_PROFILES =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
aliases: ['xhs', 'redbook', 'xiaohongshu', '小红书'],
|
|
20
|
-
duration_range_s: [30, 45],
|
|
21
|
-
default_total_duration_s: 36,
|
|
22
|
-
hook_duration_s: 6,
|
|
23
|
-
cta_duration_s: 6,
|
|
24
|
-
min_highlight_duration_s: 8,
|
|
25
|
-
tone: '种草型、亲切、讲重点',
|
|
26
|
-
voice_preset: 'warm_female_zh_01',
|
|
27
|
-
speed: 1.06,
|
|
28
|
-
}),
|
|
29
|
-
douyin: Object.freeze({
|
|
30
|
-
id: 'douyin',
|
|
31
|
-
aliases: ['douyin', '抖音', 'tiktok_cn'],
|
|
32
|
-
duration_range_s: [25, 40],
|
|
33
|
-
default_total_duration_s: 32,
|
|
34
|
-
hook_duration_s: 5,
|
|
35
|
-
cta_duration_s: 5,
|
|
36
|
-
min_highlight_duration_s: 7,
|
|
37
|
-
tone: '快节奏、结论先行、行动明确',
|
|
38
|
-
voice_preset: 'news_male_zh_01',
|
|
39
|
-
speed: 1.14,
|
|
40
|
-
}),
|
|
41
|
-
wechat_video: Object.freeze({
|
|
42
|
-
id: 'wechat_video',
|
|
43
|
-
aliases: ['wechat_video', 'wechat-video', 'video_channel', '视频号', 'wechat', '公众号视频号'],
|
|
44
|
-
duration_range_s: [35, 60],
|
|
45
|
-
default_total_duration_s: 46,
|
|
46
|
-
hook_duration_s: 7,
|
|
47
|
-
cta_duration_s: 7,
|
|
48
|
-
min_highlight_duration_s: 9,
|
|
49
|
-
tone: '稳健讲解、信息完整、可信表达',
|
|
50
|
-
voice_preset: 'warm_male_zh_01',
|
|
51
|
-
speed: 0.98,
|
|
52
|
-
}),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const PLATFORM_LOOKUP = (() => {
|
|
56
|
-
const map = new Map();
|
|
57
|
-
for (const profile of Object.values(PLATFORM_PROFILES)) {
|
|
58
|
-
map.set(profile.id, profile.id);
|
|
59
|
-
for (const alias of profile.aliases) {
|
|
60
|
-
map.set(String(alias).trim().toLowerCase(), profile.id);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return map;
|
|
64
|
-
})();
|
|
65
|
-
|
|
66
|
-
const SENTENCE_FILLERS = Object.freeze([
|
|
67
|
-
'你可以把这段信息理解成筛选效率的核心杠杆,它决定了你后续要不要投入更多时间。',
|
|
68
|
-
'我会只保留对决策真正有帮助的线索,避免你被页面里的装饰信息分散注意力。',
|
|
69
|
-
'看完这一段后,你应该能立刻得到一个可执行判断,而不是停留在模糊印象。',
|
|
70
|
-
'如果这部分与你的目标不匹配,建议立刻止损切换,不要把精力耗在低价值信息上。',
|
|
71
|
-
'把它和你自己的标准逐条对照,会比完整通读页面更快得到可靠结论。',
|
|
72
|
-
]);
|
|
13
|
+
const PLANNER_CONFIG = loadPlannerConfig();
|
|
14
|
+
const DEFAULT_OUTRO_VIDEO_ID = PLANNER_CONFIG.default_outro_video_id;
|
|
15
|
+
const SENTENCE_MIN_CHARS = PLANNER_CONFIG.sentence_char_range.min;
|
|
16
|
+
const SENTENCE_MAX_CHARS = PLANNER_CONFIG.sentence_char_range.max;
|
|
17
|
+
const PLATFORM_PROFILES = PLANNER_CONFIG.platform_profiles;
|
|
18
|
+
const PLATFORM_LOOKUP = buildPlatformLookup(PLATFORM_PROFILES);
|
|
19
|
+
const SENTENCE_FILLERS = PLANNER_CONFIG.sentence_fillers;
|
|
73
20
|
|
|
74
21
|
const SEMANTIC_SLOT_KEYS = Object.freeze([
|
|
75
22
|
'company',
|
|
@@ -83,65 +30,156 @@ const SEMANTIC_SLOT_KEYS = Object.freeze([
|
|
|
83
30
|
'entry_or_cta',
|
|
84
31
|
]);
|
|
85
32
|
|
|
86
|
-
const NARRATION_MODES =
|
|
87
|
-
'job_intel_broadcast',
|
|
88
|
-
'job_alert',
|
|
89
|
-
'info_summary',
|
|
90
|
-
'refuse_auto_broadcast',
|
|
91
|
-
]);
|
|
33
|
+
const NARRATION_MODES = PLANNER_CONFIG.narration_modes;
|
|
92
34
|
|
|
93
35
|
const NARRATION_MODE_SET = new Set(NARRATION_MODES);
|
|
94
36
|
const SLOT_STATUS_SET = new Set(['present', 'missing']);
|
|
95
37
|
|
|
96
|
-
const RECRUITMENT_KEYWORDS =
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
38
|
+
const RECRUITMENT_KEYWORDS = buildKeywordRegex(PLANNER_CONFIG.recruitment_keyword_terms);
|
|
39
|
+
const RECRUITMENT_TYPE_LABELS = PLANNER_CONFIG.recruitment_type_labels;
|
|
40
|
+
const SLOT_LABELS = PLANNER_CONFIG.slot_labels;
|
|
41
|
+
const MODE_SEVERITY = PLANNER_CONFIG.mode_severity;
|
|
42
|
+
const MODE_MIN_CONFIDENCE = PLANNER_CONFIG.mode_min_confidence;
|
|
43
|
+
const RECRUITMENT_FAIL_CLOSED_GROUPS = PLANNER_CONFIG.recruitment_fail_closed_groups;
|
|
44
|
+
|
|
45
|
+
function isPlainObject(value) {
|
|
46
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function deepFreeze(value) {
|
|
50
|
+
if (!value || typeof value !== 'object') return value;
|
|
51
|
+
if (Object.isFrozen(value)) return value;
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
value.forEach(item => deepFreeze(item));
|
|
54
|
+
return Object.freeze(value);
|
|
55
|
+
}
|
|
56
|
+
for (const key of Object.keys(value)) {
|
|
57
|
+
deepFreeze(value[key]);
|
|
58
|
+
}
|
|
59
|
+
return Object.freeze(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeStringArray(value) {
|
|
63
|
+
if (!Array.isArray(value)) return [];
|
|
64
|
+
return value
|
|
65
|
+
.map(item => String(item ?? '').trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeNumberPair(value, fallback = [100, 150]) {
|
|
70
|
+
if (!Array.isArray(value) || value.length < 2) return [...fallback];
|
|
71
|
+
const first = Number(value[0]);
|
|
72
|
+
const second = Number(value[1]);
|
|
73
|
+
if (!Number.isFinite(first) || !Number.isFinite(second)) return [...fallback];
|
|
74
|
+
const min = Math.min(first, second);
|
|
75
|
+
const max = Math.max(first, second);
|
|
76
|
+
return [Math.round(min), Math.round(max)];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeGroupList(groups) {
|
|
80
|
+
if (!Array.isArray(groups)) return [];
|
|
81
|
+
return groups
|
|
82
|
+
.map((group) => {
|
|
83
|
+
if (!isPlainObject(group)) return null;
|
|
84
|
+
const id = String(group.id ?? '').trim();
|
|
85
|
+
const keys = normalizeStringArray(group.keys);
|
|
86
|
+
if (!id || keys.length === 0) return null;
|
|
87
|
+
return { id, keys };
|
|
88
|
+
})
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeLabelMap(value) {
|
|
93
|
+
if (!isPlainObject(value)) return {};
|
|
94
|
+
const normalized = {};
|
|
95
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
96
|
+
const normalizedKey = String(key ?? '').trim();
|
|
97
|
+
const normalizedValue = String(raw ?? '').trim();
|
|
98
|
+
if (!normalizedKey || !normalizedValue) continue;
|
|
99
|
+
normalized[normalizedKey] = normalizedValue;
|
|
100
|
+
}
|
|
101
|
+
return normalized;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildPlatformLookup(profiles) {
|
|
105
|
+
const map = new Map();
|
|
106
|
+
for (const profile of Object.values(profiles ?? {})) {
|
|
107
|
+
const id = String(profile?.id ?? '').trim().toLowerCase();
|
|
108
|
+
if (!id) continue;
|
|
109
|
+
map.set(id, id);
|
|
110
|
+
const aliases = normalizeStringArray(profile.aliases);
|
|
111
|
+
for (const alias of aliases) {
|
|
112
|
+
map.set(alias.toLowerCase(), id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return map;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function escapeRegexToken(value) {
|
|
119
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildKeywordRegex(terms) {
|
|
123
|
+
const normalizedTerms = normalizeStringArray(terms);
|
|
124
|
+
if (normalizedTerms.length === 0) return /招聘|校招|实习|职位|岗位|career|jobs|intern|campus/i;
|
|
125
|
+
return new RegExp(normalizedTerms.map(escapeRegexToken).join('|'), 'i');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function loadPlannerConfig() {
|
|
129
|
+
let parsed = null;
|
|
130
|
+
try {
|
|
131
|
+
parsed = JSON.parse(
|
|
132
|
+
readFileSync(new URL('./planner-config.json', import.meta.url), 'utf8')
|
|
133
|
+
);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new Error(`video_narration_planner_config_load_failed:${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
if (!isPlainObject(parsed)) {
|
|
138
|
+
throw new Error('video_narration_planner_config_invalid:root_object_required');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const platformProfiles = isPlainObject(parsed.platform_profiles) ? parsed.platform_profiles : {};
|
|
142
|
+
if (!isPlainObject(platformProfiles.xiaohongshu)) {
|
|
143
|
+
throw new Error('video_narration_planner_config_invalid:platform_profiles.xiaohongshu_required');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sentenceRange = normalizeNumberPair(
|
|
147
|
+
[parsed?.sentence_char_range?.min, parsed?.sentence_char_range?.max],
|
|
148
|
+
[100, 150]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const failClosedGroups = normalizeGroupList(parsed.recruitment_fail_closed_groups);
|
|
152
|
+
if (failClosedGroups.length === 0) {
|
|
153
|
+
throw new Error('video_narration_planner_config_invalid:recruitment_fail_closed_groups_required');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const narrationModes = normalizeStringArray(parsed.narration_modes);
|
|
157
|
+
if (narrationModes.length === 0) {
|
|
158
|
+
throw new Error('video_narration_planner_config_invalid:narration_modes_required');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const sentenceFillers = normalizeStringArray(parsed.sentence_fillers);
|
|
162
|
+
if (sentenceFillers.length === 0) {
|
|
163
|
+
throw new Error('video_narration_planner_config_invalid:sentence_fillers_required');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return deepFreeze({
|
|
167
|
+
default_outro_video_id: String(parsed.default_outro_video_id ?? 'outro-default-zh').trim() || 'outro-default-zh',
|
|
168
|
+
sentence_char_range: {
|
|
169
|
+
min: sentenceRange[0],
|
|
170
|
+
max: sentenceRange[1],
|
|
171
|
+
},
|
|
172
|
+
platform_profiles: platformProfiles,
|
|
173
|
+
sentence_fillers: sentenceFillers,
|
|
174
|
+
narration_modes: narrationModes,
|
|
175
|
+
recruitment_keyword_terms: normalizeStringArray(parsed.recruitment_keyword_terms),
|
|
176
|
+
recruitment_type_labels: normalizeLabelMap(parsed.recruitment_type_labels),
|
|
177
|
+
slot_labels: normalizeLabelMap(parsed.slot_labels),
|
|
178
|
+
mode_severity: isPlainObject(parsed.mode_severity) ? parsed.mode_severity : {},
|
|
179
|
+
mode_min_confidence: isPlainObject(parsed.mode_min_confidence) ? parsed.mode_min_confidence : {},
|
|
180
|
+
recruitment_fail_closed_groups: failClosedGroups,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
145
183
|
|
|
146
184
|
function clampNumber(value, min, max, fallback) {
|
|
147
185
|
const num = Number(value);
|
|
@@ -5,6 +5,12 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
import { detailSections, planVideo } from './core.js';
|
|
8
|
+
import {
|
|
9
|
+
findToolBlockRule,
|
|
10
|
+
loadToolBlockRulesFromManifest,
|
|
11
|
+
} from '../../official-common/tool-access-policy.js';
|
|
12
|
+
|
|
13
|
+
const TOOL_BLOCK_RULES = loadToolBlockRulesFromManifest(new URL('./manifest.json', import.meta.url));
|
|
8
14
|
|
|
9
15
|
function isExecutedDirectly(metaUrl) {
|
|
10
16
|
const entry = process.argv[1];
|
|
@@ -35,6 +41,18 @@ function toErrorContent(error) {
|
|
|
35
41
|
};
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
function resolvePlannerToolBlockMessage(toolName, {
|
|
45
|
+
workspaceId = '',
|
|
46
|
+
env = process.env,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const matchedRule = findToolBlockRule(TOOL_BLOCK_RULES, {
|
|
49
|
+
toolName,
|
|
50
|
+
workspaceId: String(workspaceId || env?.WORKSPACE_ID || '').trim(),
|
|
51
|
+
agentId: env?.AGENT_ID,
|
|
52
|
+
});
|
|
53
|
+
return matchedRule?.message ?? '';
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
export function createVideoNarrationPlannerServer({
|
|
39
57
|
env = process.env,
|
|
40
58
|
fetchFn = globalThis.fetch,
|
|
@@ -52,6 +70,10 @@ export function createVideoNarrationPlannerServer({
|
|
|
52
70
|
},
|
|
53
71
|
async (args) => {
|
|
54
72
|
try {
|
|
73
|
+
const blockedMessage = resolvePlannerToolBlockMessage('plan_video', { env });
|
|
74
|
+
if (blockedMessage) {
|
|
75
|
+
return toErrorContent(new Error(blockedMessage));
|
|
76
|
+
}
|
|
55
77
|
const payload = planVideo(args ?? {});
|
|
56
78
|
return toTextContent(payload);
|
|
57
79
|
} catch (error) {
|
|
@@ -72,6 +94,13 @@ export function createVideoNarrationPlannerServer({
|
|
|
72
94
|
},
|
|
73
95
|
async (args) => {
|
|
74
96
|
try {
|
|
97
|
+
const blockedMessage = resolvePlannerToolBlockMessage('detail_sections', {
|
|
98
|
+
workspaceId: args?.workspace_id,
|
|
99
|
+
env,
|
|
100
|
+
});
|
|
101
|
+
if (blockedMessage) {
|
|
102
|
+
return toErrorContent(new Error(blockedMessage));
|
|
103
|
+
}
|
|
75
104
|
const payload = await detailSections(args ?? {}, { env, fetchFn });
|
|
76
105
|
return toTextContent(payload);
|
|
77
106
|
} catch (error) {
|
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
{ "name": "plan_video", "classification": "cacheable" },
|
|
9
9
|
{ "name": "detail_sections", "classification": "mandatory" }
|
|
10
10
|
],
|
|
11
|
+
"tool_block_rules": [
|
|
12
|
+
{
|
|
13
|
+
"workspace_id": "ae63cc9e-feff-4d7e-a62e-a7a7c5fd69d9",
|
|
14
|
+
"agent_id": "91a45fd7-ce5f-4da6-9b27-e34bf7b7c0e2",
|
|
15
|
+
"tools": ["plan_video"],
|
|
16
|
+
"message": "plan_video blocked for editor_in_chief in CvMax. In this workspace, @short_video_scripter owns video planning."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"workspace_id": "ae63cc9e-feff-4d7e-a62e-a7a7c5fd69d9",
|
|
20
|
+
"agent_id": "91a45fd7-ce5f-4da6-9b27-e34bf7b7c0e2",
|
|
21
|
+
"tools": ["detail_sections"],
|
|
22
|
+
"message": "detail_sections blocked for editor_in_chief in CvMax. In this workspace, @short_video_scripter owns video planning."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
11
25
|
"smoke_test": {
|
|
12
26
|
"tool": "plan_video",
|
|
13
27
|
"arguments": {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"default_outro_video_id": "outro-default-zh",
|
|
3
|
+
"sentence_char_range": {
|
|
4
|
+
"min": 100,
|
|
5
|
+
"max": 150
|
|
6
|
+
},
|
|
7
|
+
"platform_profiles": {
|
|
8
|
+
"xiaohongshu": {
|
|
9
|
+
"id": "xiaohongshu",
|
|
10
|
+
"aliases": ["xhs", "redbook", "xiaohongshu", "小红书"],
|
|
11
|
+
"duration_range_s": [30, 45],
|
|
12
|
+
"default_total_duration_s": 36,
|
|
13
|
+
"hook_duration_s": 6,
|
|
14
|
+
"cta_duration_s": 6,
|
|
15
|
+
"min_highlight_duration_s": 8,
|
|
16
|
+
"tone": "种草型、亲切、讲重点",
|
|
17
|
+
"voice_preset": "warm_female_zh_01",
|
|
18
|
+
"speed": 1.06
|
|
19
|
+
},
|
|
20
|
+
"douyin": {
|
|
21
|
+
"id": "douyin",
|
|
22
|
+
"aliases": ["douyin", "抖音", "tiktok_cn"],
|
|
23
|
+
"duration_range_s": [25, 40],
|
|
24
|
+
"default_total_duration_s": 32,
|
|
25
|
+
"hook_duration_s": 5,
|
|
26
|
+
"cta_duration_s": 5,
|
|
27
|
+
"min_highlight_duration_s": 7,
|
|
28
|
+
"tone": "快节奏、结论先行、行动明确",
|
|
29
|
+
"voice_preset": "news_male_zh_01",
|
|
30
|
+
"speed": 1.14
|
|
31
|
+
},
|
|
32
|
+
"wechat_video": {
|
|
33
|
+
"id": "wechat_video",
|
|
34
|
+
"aliases": ["wechat_video", "wechat-video", "video_channel", "视频号", "wechat", "公众号视频号"],
|
|
35
|
+
"duration_range_s": [35, 60],
|
|
36
|
+
"default_total_duration_s": 46,
|
|
37
|
+
"hook_duration_s": 7,
|
|
38
|
+
"cta_duration_s": 7,
|
|
39
|
+
"min_highlight_duration_s": 9,
|
|
40
|
+
"tone": "稳健讲解、信息完整、可信表达",
|
|
41
|
+
"voice_preset": "warm_male_zh_01",
|
|
42
|
+
"speed": 0.98
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"sentence_fillers": [
|
|
46
|
+
"你可以把这段信息理解成筛选效率的核心杠杆,它决定了你后续要不要投入更多时间。",
|
|
47
|
+
"我会只保留对决策真正有帮助的线索,避免你被页面里的装饰信息分散注意力。",
|
|
48
|
+
"看完这一段后,你应该能立刻得到一个可执行判断,而不是停留在模糊印象。",
|
|
49
|
+
"如果这部分与你的目标不匹配,建议立刻止损切换,不要把精力耗在低价值信息上。",
|
|
50
|
+
"把它和你自己的标准逐条对照,会比完整通读页面更快得到可靠结论。"
|
|
51
|
+
],
|
|
52
|
+
"narration_modes": [
|
|
53
|
+
"job_intel_broadcast",
|
|
54
|
+
"job_alert",
|
|
55
|
+
"info_summary",
|
|
56
|
+
"refuse_auto_broadcast"
|
|
57
|
+
],
|
|
58
|
+
"recruitment_keyword_terms": [
|
|
59
|
+
"招聘",
|
|
60
|
+
"校招",
|
|
61
|
+
"实习",
|
|
62
|
+
"职位",
|
|
63
|
+
"岗位",
|
|
64
|
+
"career",
|
|
65
|
+
"jobs",
|
|
66
|
+
"intern",
|
|
67
|
+
"campus"
|
|
68
|
+
],
|
|
69
|
+
"recruitment_type_labels": {
|
|
70
|
+
"campus": "校园招聘",
|
|
71
|
+
"intern": "实习招聘",
|
|
72
|
+
"experienced": "社会招聘",
|
|
73
|
+
"job_posting_unknown": "岗位招聘信息"
|
|
74
|
+
},
|
|
75
|
+
"slot_labels": {
|
|
76
|
+
"company": "公司",
|
|
77
|
+
"published_at": "发布时间",
|
|
78
|
+
"recruitment_type": "招聘类型",
|
|
79
|
+
"cohort": "届别",
|
|
80
|
+
"job_directions": "岗位方向",
|
|
81
|
+
"locations": "工作城市",
|
|
82
|
+
"target_or_requirements": "对象与要求",
|
|
83
|
+
"process": "招聘流程",
|
|
84
|
+
"entry_or_cta": "投递入口"
|
|
85
|
+
},
|
|
86
|
+
"mode_severity": {
|
|
87
|
+
"job_intel_broadcast": 4,
|
|
88
|
+
"job_alert": 3,
|
|
89
|
+
"info_summary": 2,
|
|
90
|
+
"refuse_auto_broadcast": 1
|
|
91
|
+
},
|
|
92
|
+
"mode_min_confidence": {
|
|
93
|
+
"strong": 0.6,
|
|
94
|
+
"support": 0.52,
|
|
95
|
+
"weakSignal": 0.42,
|
|
96
|
+
"floor": 0.36
|
|
97
|
+
},
|
|
98
|
+
"recruitment_fail_closed_groups": [
|
|
99
|
+
{
|
|
100
|
+
"id": "company+published_at+recruitment_type",
|
|
101
|
+
"keys": ["company", "published_at", "recruitment_type"]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "company+cohort+job_directions",
|
|
105
|
+
"keys": ["company", "cohort", "job_directions"]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "company+recruitment_type+locations",
|
|
109
|
+
"keys": ["company", "recruitment_type", "locations"]
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|