@lightcone-ai/daemon 0.15.52 → 0.15.54

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.
@@ -1,270 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from 'node:fs';
2
+ // Thin-proxy. Real implementation runs on lightcone server (D17 anti-leak).
3
+ // Source of truth: src/mcp-services/platform-policy-db.js
3
4
  import { z } from 'zod';
4
- import { startFixtureServer } from '../../official-common/server.js';
5
+ import { startThinProxy } from '../../_thin-proxy/forward.js';
5
6
 
6
- const POLICY_FIXTURES = loadPolicyFixtures();
7
- const FIXTURE_META = POLICY_FIXTURES.fixtureMeta;
8
- const PLATFORM_ALIASES = POLICY_FIXTURES.platformAliases;
9
- const PLATFORM_POLICY_DATA = POLICY_FIXTURES.platformPolicyData;
10
-
11
- function isPlainObject(value) {
12
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
13
- }
14
-
15
- function deepFreeze(value) {
16
- if (!value || typeof value !== 'object') return value;
17
- if (Object.isFrozen(value)) return value;
18
- if (Array.isArray(value)) {
19
- value.forEach(item => deepFreeze(item));
20
- return Object.freeze(value);
21
- }
22
- for (const key of Object.keys(value)) {
23
- deepFreeze(value[key]);
24
- }
25
- return Object.freeze(value);
26
- }
27
-
28
- function toToken(value) {
29
- return String(value ?? '').trim();
30
- }
31
-
32
- function normalizeAliasMap(rawAliases) {
33
- if (!isPlainObject(rawAliases)) return {};
34
- const aliases = {};
35
- for (const [key, value] of Object.entries(rawAliases)) {
36
- const alias = toToken(key).toLowerCase();
37
- const target = toToken(value).toLowerCase();
38
- if (!alias || !target) continue;
39
- aliases[alias] = target;
40
- }
41
- return aliases;
42
- }
43
-
44
- function normalizeSensitiveTerms(rawTerms) {
45
- if (!Array.isArray(rawTerms)) return [];
46
- return rawTerms
47
- .map((item) => {
48
- if (!isPlainObject(item)) return null;
49
- const id = toToken(item.id);
50
- const term = toToken(item.term);
51
- const severity = toToken(item.severity).toLowerCase();
52
- if (!id || !term || !severity) return null;
53
- return {
54
- id,
55
- term,
56
- severity,
57
- reason: toToken(item.reason),
58
- action: toToken(item.action),
59
- };
60
- })
61
- .filter(Boolean);
62
- }
63
-
64
- function normalizeRequiredLabels(raw) {
65
- if (!isPlainObject(raw)) return {};
66
- const output = {};
67
- for (const [key, value] of Object.entries(raw)) {
68
- const normalizedKey = toToken(key);
69
- const normalizedValue = toToken(value);
70
- if (!normalizedKey || !normalizedValue) continue;
71
- output[normalizedKey] = normalizedValue;
72
- }
73
- return output;
74
- }
75
-
76
- function normalizePlatformPolicyData(rawData) {
77
- if (!isPlainObject(rawData)) return {};
78
- const normalized = {};
79
- for (const [platformKey, rawEntry] of Object.entries(rawData)) {
80
- if (!isPlainObject(rawEntry)) continue;
81
- const platform = toToken(rawEntry.platform || platformKey).toLowerCase();
82
- if (!platform) continue;
83
- normalized[platform] = {
84
- platform,
85
- display_name: toToken(rawEntry.display_name),
86
- policy_version: toToken(rawEntry.policy_version),
87
- updated_at: toToken(rawEntry.updated_at),
88
- ai_label_required: rawEntry.ai_label_required === true,
89
- ad_disclosure_required: rawEntry.ad_disclosure_required === true,
90
- policy_window_days: Number.isFinite(Number(rawEntry.policy_window_days))
91
- ? Math.max(1, Math.trunc(Number(rawEntry.policy_window_days)))
92
- : 7,
93
- required_labels: normalizeRequiredLabels(rawEntry.required_labels),
94
- sensitive_terms: normalizeSensitiveTerms(rawEntry.sensitive_terms),
95
- };
96
- }
97
- return normalized;
98
- }
99
-
100
- function loadPolicyFixtures() {
101
- let parsed = null;
102
- try {
103
- parsed = JSON.parse(
104
- readFileSync(new URL('./policy-fixtures.json', import.meta.url), 'utf8')
105
- );
106
- } catch (error) {
107
- throw new Error(`platform_policy_fixture_load_failed:${error.message}`);
108
- }
109
- if (!isPlainObject(parsed)) {
110
- throw new Error('platform_policy_fixture_invalid:root_object_required');
111
- }
112
- const platformPolicyData = normalizePlatformPolicyData(parsed.platform_policy_data);
113
- if (Object.keys(platformPolicyData).length === 0) {
114
- throw new Error('platform_policy_fixture_invalid:platform_policy_data_required');
115
- }
116
- return deepFreeze({
117
- fixtureMeta: isPlainObject(parsed.fixture_meta) ? parsed.fixture_meta : {},
118
- platformAliases: normalizeAliasMap(parsed.platform_aliases),
119
- platformPolicyData,
120
- });
121
- }
122
-
123
- function normalizePlatform(value) {
124
- const raw = String(value ?? '').trim().toLowerCase();
125
- if (!raw) return '';
126
- if (Object.hasOwn(PLATFORM_ALIASES, raw)) return PLATFORM_ALIASES[raw];
127
- return raw;
128
- }
129
-
130
- function findSnippet(text, term) {
131
- const source = String(text ?? '');
132
- const target = String(term ?? '');
133
- if (!source || !target) return '';
134
-
135
- const index = source.toLowerCase().indexOf(target.toLowerCase());
136
- if (index < 0) return '';
137
-
138
- const start = Math.max(0, index - 10);
139
- const end = Math.min(source.length, index + target.length + 10);
140
- return source.slice(start, end);
141
- }
142
-
143
- function listPlatformsSummary() {
144
- return Object.values(PLATFORM_POLICY_DATA).map((entry) => ({
145
- platform: entry.platform,
146
- display_name: entry.display_name,
147
- policy_version: entry.policy_version,
148
- updated_at: entry.updated_at,
149
- ai_label_required: entry.ai_label_required,
150
- ad_disclosure_required: entry.ad_disclosure_required,
151
- sensitive_term_count: entry.sensitive_terms.length,
152
- }));
153
- }
154
-
155
- await startFixtureServer({
7
+ await startThinProxy({
156
8
  serverId: 'platform-policy-db',
157
9
  serverName: 'official-platform-policy-db',
158
- toolName: 'platform_policy_db',
159
- toolDescription: 'Return policy fixtures and sensitive-term scan results for publishing precheck.',
160
- inputSchema: {
161
- operation: z.enum(['list_platforms', 'get_rules', 'scan_text']).optional()
162
- .describe('Operation: list platforms, get platform rules, or scan draft text.'),
163
- platform: z.string().optional()
164
- .describe('Target platform id or alias, e.g. xhs, douyin, wechat-mp.'),
165
- text: z.string().optional()
166
- .describe('Draft text for sensitive-term scan when operation=scan_text.'),
167
- include_terms: z.boolean().optional()
168
- .describe('Whether full term list is included in get_rules result (default true).'),
169
- max_matches: z.number().int().min(1).max(100).optional()
170
- .describe('Max returned violations for scan_text (default 20).'),
171
- policy_version: z.string().optional()
172
- .describe('Optional expected policy version; mismatch is returned as warning.'),
173
- },
174
- handler: ({
175
- operation = 'get_rules',
176
- platform,
177
- text,
178
- include_terms = true,
179
- max_matches = 20,
180
- policy_version,
181
- }) => {
182
- if (operation === 'list_platforms') {
183
- return {
184
- ...FIXTURE_META,
185
- operation,
186
- count: Object.keys(PLATFORM_POLICY_DATA).length,
187
- platforms: listPlatformsSummary(),
188
- };
189
- }
190
-
191
- const normalizedPlatform = normalizePlatform(platform);
192
- if (!normalizedPlatform || !Object.hasOwn(PLATFORM_POLICY_DATA, normalizedPlatform)) {
193
- throw new Error(`unsupported_platform:${platform}`);
194
- }
195
-
196
- const policy = PLATFORM_POLICY_DATA[normalizedPlatform];
197
- const versionMismatch = policy_version && policy_version !== policy.policy_version;
198
-
199
- if (operation === 'get_rules') {
200
- return {
201
- ...FIXTURE_META,
202
- operation,
203
- platform: policy.platform,
204
- display_name: policy.display_name,
205
- policy_version: policy.policy_version,
206
- updated_at: policy.updated_at,
207
- ai_label_required: policy.ai_label_required,
208
- ad_disclosure_required: policy.ad_disclosure_required,
209
- policy_window_days: policy.policy_window_days,
210
- required_labels: policy.required_labels,
211
- sensitive_term_count: policy.sensitive_terms.length,
212
- sensitive_terms: include_terms ? policy.sensitive_terms : undefined,
213
- warnings: versionMismatch ? [{
214
- code: 'policy_version_mismatch',
215
- expected: policy.policy_version,
216
- received: policy_version,
217
- }] : [],
218
- };
219
- }
220
-
221
- const draftText = String(text ?? '').trim();
222
- if (!draftText) {
223
- throw new Error('scan_text_requires_text');
224
- }
225
-
226
- const limitedMaxMatches = Math.max(1, Math.min(100, Math.trunc(Number(max_matches) || 20)));
227
- const lowerText = draftText.toLowerCase();
228
-
229
- const violations = [];
230
- for (const rule of policy.sensitive_terms) {
231
- if (!lowerText.includes(rule.term.toLowerCase())) continue;
232
- violations.push({
233
- rule_id: rule.id,
234
- term: rule.term,
235
- severity: rule.severity,
236
- reason: rule.reason,
237
- action: rule.action,
238
- snippet: findSnippet(draftText, rule.term),
239
- });
240
- if (violations.length >= limitedMaxMatches) break;
241
- }
242
-
243
- const severityStats = violations.reduce((acc, item) => {
244
- acc[item.severity] = (acc[item.severity] ?? 0) + 1;
245
- return acc;
246
- }, {});
247
- const blockerCount = severityStats.blocker ?? 0;
248
-
249
- return {
250
- ...FIXTURE_META,
251
- operation,
252
- platform: policy.platform,
253
- policy_version: policy.policy_version,
254
- scanned_length: draftText.length,
255
- match_count: violations.length,
256
- blocker_count: blockerCount,
257
- has_blocker: blockerCount > 0,
258
- severity_stats: severityStats,
259
- violations,
260
- warnings: versionMismatch ? [{
261
- code: 'policy_version_mismatch',
262
- expected: policy.policy_version,
263
- received: policy_version,
264
- }] : [],
265
- required_labels: policy.required_labels,
266
- ai_label_required: policy.ai_label_required,
267
- ad_disclosure_required: policy.ad_disclosure_required,
268
- };
269
- },
10
+ tools: [
11
+ {
12
+ name: 'platform_policy_db',
13
+ description: 'Return policy fixtures and sensitive-term scan results for publishing precheck.',
14
+ inputSchema: {
15
+ operation: z.enum(['list_platforms', 'get_rules', 'scan_text']).optional()
16
+ .describe('Tool operation: list_platforms / get_rules / scan_text (default: get_rules)'),
17
+ platform: z.string().optional().describe('Target platform code (xhs / douyin / kuaishou / bilibili / wechat-mp / ...)'),
18
+ text: z.string().optional().describe('Draft text to scan (required when operation=scan_text)'),
19
+ include_terms: z.boolean().optional().describe('Whether to return the full sensitive-term list (default: true)'),
20
+ max_matches: z.number().optional().describe('Maximum violations to return for scan_text (default: 20, max: 100)'),
21
+ policy_version: z.string().optional().describe('Optional expected policy version; mismatch surfaces a warning'),
22
+ },
23
+ },
24
+ ],
270
25
  });
@@ -1,137 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import { pathToFileURL } from 'url';
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
+ // Thin-proxy. Real impl on lightcone server.
3
+ // Source of truth: src/mcp-services/video-narration-planner/
5
4
  import { z } from 'zod';
5
+ import { startThinProxy } from '../../_thin-proxy/forward.js';
6
6
 
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));
14
-
15
- function isExecutedDirectly(metaUrl) {
16
- const entry = process.argv[1];
17
- if (!entry) return false;
18
- try {
19
- return pathToFileURL(entry).href === metaUrl;
20
- } catch {
21
- return false;
22
- }
23
- }
24
-
25
- function toTextContent(payload) {
26
- return {
27
- content: [{
28
- type: 'text',
29
- text: JSON.stringify(payload, null, 2),
30
- }],
31
- };
32
- }
33
-
34
- function toErrorContent(error) {
35
- return {
36
- isError: true,
37
- content: [{
38
- type: 'text',
39
- text: `Error: ${error.message}`,
40
- }],
41
- };
42
- }
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
-
56
- export function createVideoNarrationPlannerServer({
57
- env = process.env,
58
- fetchFn = globalThis.fetch,
59
- } = {}) {
60
- const server = new McpServer({ name: 'official-video-narration-planner', version: '0.1.0' });
61
-
62
- server.tool(
63
- 'plan_video',
64
- 'Stage 2: plan narrative arc + phase plan for URL narration video. Enforces highlights<=3 and phases<=5.',
7
+ await startThinProxy({
8
+ serverId: 'video-narration-planner',
9
+ serverName: 'official-video-narration-planner',
10
+ tools: [
65
11
  {
66
- understanding: z.record(z.any()).describe('Stage 1 output page_understanding object.'),
67
- persona: z.string().optional().describe('Target audience/persona description.'),
68
- target_platform: z.string().describe('Target platform: 小红书/xiaohongshu, 抖音/douyin, 视频号/wechat_video.'),
69
- total_duration_s: z.number().int().min(20).max(90).optional().describe('Optional explicit target duration in seconds.'),
12
+ name: 'plan_video',
13
+ description: 'Stage 2: plan narrative arc + phase plan for URL narration video. Enforces highlights<=3 and phases<=5.',
14
+ inputSchema: {
15
+ understanding: z.record(z.any()).describe('Stage 1 output page_understanding object.'),
16
+ persona: z.string().optional(),
17
+ target_platform: z.string(),
18
+ total_duration_s: z.number().int().min(20).max(90).optional(),
19
+ },
70
20
  },
71
- async (args) => {
72
- try {
73
- const blockedMessage = resolvePlannerToolBlockMessage('plan_video', { env });
74
- if (blockedMessage) {
75
- return toErrorContent(new Error(blockedMessage));
76
- }
77
- const payload = planVideo(args ?? {});
78
- return toTextContent(payload);
79
- } catch (error) {
80
- return toErrorContent(error);
81
- }
82
- }
83
- );
84
-
85
- server.tool(
86
- 'detail_sections',
87
- 'Stage 3: take agent-written sentences, call TTS voiceover for each phase, and fill duration/dwell. You MUST write the sentences yourself before calling this tool.',
88
21
  {
89
- strategy: z.record(z.any()).describe('Output of plan_video (video strategy with phase_plan).'),
90
- sentences: z.array(z.object({
91
- phase_id: z.string().describe('Phase ID from plan_video phase_plan (e.g. hook, highlight_1, cta).'),
92
- text: z.string().describe('Narration sentence you wrote for this phase.'),
93
- })).describe(
94
- 'Narration sentences YOU write, one per phase in the plan. '
95
- + 'Use semantic_slots, persona, and platform tone from plan_video output. '
96
- + 'Sound like a peer sharing useful info — not a news anchor. '
97
- + 'Each sentence: 8–60 chars, spoken Chinese, ends with punctuation. '
98
- + 'Do NOT use "信息来源为", "发布时间为", "凭截图判断" or similar metadata/warning phrasing.'
99
- ),
100
- workspace_id: z.string().optional().describe('Workspace id for TTS generation. Defaults to WORKSPACE_ID env if provided.'),
101
- credential_id: z.string().optional().describe('Optional explicit tts_provider credential id.'),
102
- format: z.enum(['mp3', 'wav', 'flac']).optional().describe('Desired audio format for generated voiceover.'),
103
- strict_tts: z.boolean().optional().describe('When true, fail if any TTS call fails; when false, fallback to estimated durations.'),
22
+ name: 'detail_sections',
23
+ description: 'Stage 3: take agent-written sentences, call TTS voiceover for each phase, and fill duration/dwell.',
24
+ inputSchema: {
25
+ strategy: z.record(z.any()),
26
+ sentences: z.array(z.object({
27
+ phase_id: z.string(),
28
+ text: z.string(),
29
+ })),
30
+ workspace_id: z.string().optional(),
31
+ credential_id: z.string().optional(),
32
+ format: z.enum(['mp3', 'wav', 'flac']).optional(),
33
+ strict_tts: z.boolean().optional(),
34
+ },
104
35
  },
105
- async (args) => {
106
- try {
107
- const blockedMessage = resolvePlannerToolBlockMessage('detail_sections', {
108
- workspaceId: args?.workspace_id,
109
- env,
110
- });
111
- if (blockedMessage) {
112
- return toErrorContent(new Error(blockedMessage));
113
- }
114
- const payload = await detailSections(args ?? {}, { env, fetchFn });
115
- return toTextContent(payload);
116
- } catch (error) {
117
- return toErrorContent(error);
118
- }
119
- }
120
- );
121
-
122
- return { server };
123
- }
124
-
125
- export async function startVideoNarrationPlannerServer(options = {}) {
126
- const { server } = createVideoNarrationPlannerServer(options);
127
- const transport = new StdioServerTransport();
128
- await server.connect(transport);
129
- console.error('[video-narration-planner] MCP server started');
130
- }
131
-
132
- if (isExecutedDirectly(import.meta.url)) {
133
- startVideoNarrationPlannerServer().catch((error) => {
134
- console.error(`[video-narration-planner] failed to start: ${error.message}`);
135
- process.exitCode = 1;
136
- });
137
- }
36
+ ],
37
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.15.52",
3
+ "version": "0.15.54",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,58 +0,0 @@
1
- {
2
- "fixture_meta": {
3
- "mode": "fixture",
4
- "as_of": "2026-04-29",
5
- "capability": "keyword-research",
6
- "disclaimer": "Keyword stats are deterministic fixtures for planning rehearsal, not live platform telemetry."
7
- },
8
- "platform_aliases": {
9
- "xiaohongshu": "xhs",
10
- "redbook": "xhs",
11
- "douyin": "douyin",
12
- "tiktok_cn": "douyin",
13
- "wechat": "wechat-mp",
14
- "wechat_mp": "wechat-mp",
15
- "wechatmp": "wechat-mp",
16
- "gzh": "wechat-mp",
17
- "公众号": "wechat-mp",
18
- "all": "all"
19
- },
20
- "platform_hints": {
21
- "all": ["趋势", "案例", "模板"],
22
- "xhs": ["小红书笔记", "标题写法", "封面策略"],
23
- "douyin": ["短视频脚本", "完播率", "首屏开场"],
24
- "wechat-mp": ["公众号长文", "转化漏斗", "私域承接"]
25
- },
26
- "intent_branches": [
27
- {
28
- "id": "pain_point",
29
- "suffix": "痛点",
30
- "weight": 1,
31
- "child_templates": ["{seed} 常见问题", "{seed} 为什么没效果", "{seed} 误区"]
32
- },
33
- {
34
- "id": "tutorial",
35
- "suffix": "教程",
36
- "weight": 0.94,
37
- "child_templates": ["{seed} 入门", "{seed} 操作步骤", "{seed} 执行清单"]
38
- },
39
- {
40
- "id": "template",
41
- "suffix": "模板",
42
- "weight": 0.9,
43
- "child_templates": ["{seed} 文案模板", "{seed} 选题模板", "{seed} 表格模板"]
44
- },
45
- {
46
- "id": "case_study",
47
- "suffix": "案例",
48
- "weight": 0.88,
49
- "child_templates": ["{seed} 成功案例", "{seed} 失败复盘", "{seed} A/B 测试"]
50
- },
51
- {
52
- "id": "comparison",
53
- "suffix": "对比",
54
- "weight": 0.84,
55
- "child_templates": ["{seed} 工具对比", "{seed} 方法对比", "{seed} 哪个更好"]
56
- }
57
- ]
58
- }