@lightcone-ai/daemon 0.14.17 → 0.14.19

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,66 +1,105 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
2
3
  import { z } from 'zod';
3
4
  import { startFixtureServer } from '../../official-common/server.js';
4
5
 
5
- const FIXTURE_META = Object.freeze({
6
- mode: 'fixture',
7
- as_of: '2026-04-29',
8
- capability: 'keyword-research',
9
- disclaimer: 'Keyword stats are deterministic fixtures for planning rehearsal, not live platform telemetry.',
10
- });
6
+ const KEYWORD_FIXTURES = loadKeywordFixtures();
7
+ const FIXTURE_META = KEYWORD_FIXTURES.fixtureMeta;
8
+ const PLATFORM_ALIAS = KEYWORD_FIXTURES.platformAliases;
9
+ const PLATFORM_HINTS = KEYWORD_FIXTURES.platformHints;
10
+ const INTENT_BRANCHES = KEYWORD_FIXTURES.intentBranches;
11
11
 
12
- const PLATFORM_ALIAS = Object.freeze({
13
- xiaohongshu: 'xhs',
14
- redbook: 'xhs',
15
- douyin: 'douyin',
16
- tiktok_cn: 'douyin',
17
- wechat: 'wechat-mp',
18
- wechat_mp: 'wechat-mp',
19
- wechatmp: 'wechat-mp',
20
- gzh: 'wechat-mp',
21
- '\u516c\u4f17\u53f7': 'wechat-mp',
22
- all: 'all',
23
- });
12
+ function isPlainObject(value) {
13
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
14
+ }
24
15
 
25
- const PLATFORM_HINTS = Object.freeze({
26
- all: ['\u8d8b\u52bf', '\u6848\u4f8b', '\u6a21\u677f'],
27
- xhs: ['\u5c0f\u7ea2\u4e66\u7b14\u8bb0', '\u6807\u9898\u5199\u6cd5', '\u5c01\u9762\u7b56\u7565'],
28
- douyin: ['\u77ed\u89c6\u9891\u811a\u672c', '\u5b8c\u64ad\u7387', '\u9996\u5c4f\u5f00\u573a'],
29
- 'wechat-mp': ['\u516c\u4f17\u53f7\u957f\u6587', '\u8f6c\u5316\u6f0f\u6597', '\u79c1\u57df\u627f\u63a5'],
30
- });
16
+ function deepFreeze(value) {
17
+ if (!value || typeof value !== 'object') return value;
18
+ if (Object.isFrozen(value)) return value;
19
+ if (Array.isArray(value)) {
20
+ value.forEach(item => deepFreeze(item));
21
+ return Object.freeze(value);
22
+ }
23
+ for (const key of Object.keys(value)) {
24
+ deepFreeze(value[key]);
25
+ }
26
+ return Object.freeze(value);
27
+ }
31
28
 
32
- const INTENT_BRANCHES = Object.freeze([
33
- {
34
- id: 'pain_point',
35
- suffix: '\u75db\u70b9',
36
- weight: 1.0,
37
- child_templates: ['{seed} \u5e38\u89c1\u95ee\u9898', '{seed} \u4e3a\u4ec0\u4e48\u6ca1\u6548\u679c', '{seed} \u8bef\u533a'],
38
- },
39
- {
40
- id: 'tutorial',
41
- suffix: '\u6559\u7a0b',
42
- weight: 0.94,
43
- child_templates: ['{seed} \u5165\u95e8', '{seed} \u64cd\u4f5c\u6b65\u9aa4', '{seed} \u6267\u884c\u6e05\u5355'],
44
- },
45
- {
46
- id: 'template',
47
- suffix: '\u6a21\u677f',
48
- weight: 0.9,
49
- child_templates: ['{seed} \u6587\u6848\u6a21\u677f', '{seed} \u9009\u9898\u6a21\u677f', '{seed} \u8868\u683c\u6a21\u677f'],
50
- },
51
- {
52
- id: 'case_study',
53
- suffix: '\u6848\u4f8b',
54
- weight: 0.88,
55
- child_templates: ['{seed} \u6210\u529f\u6848\u4f8b', '{seed} \u5931\u8d25\u590d\u76d8', '{seed} A/B \u6d4b\u8bd5'],
56
- },
57
- {
58
- id: 'comparison',
59
- suffix: '\u5bf9\u6bd4',
60
- weight: 0.84,
61
- child_templates: ['{seed} \u5de5\u5177\u5bf9\u6bd4', '{seed} \u65b9\u6cd5\u5bf9\u6bd4', '{seed} \u54ea\u4e2a\u66f4\u597d'],
62
- },
63
- ]);
29
+ function toToken(value) {
30
+ return String(value ?? '').trim();
31
+ }
32
+
33
+ function normalizeAliasMap(rawAliases) {
34
+ if (!isPlainObject(rawAliases)) return { all: 'all' };
35
+ const aliases = {};
36
+ for (const [key, value] of Object.entries(rawAliases)) {
37
+ const alias = toToken(key).toLowerCase();
38
+ const target = toToken(value).toLowerCase();
39
+ if (!alias || !target) continue;
40
+ aliases[alias] = target;
41
+ }
42
+ if (!Object.hasOwn(aliases, 'all')) aliases.all = 'all';
43
+ return aliases;
44
+ }
45
+
46
+ function normalizePlatformHints(rawHints) {
47
+ if (!isPlainObject(rawHints)) return { all: [] };
48
+ const hints = {};
49
+ for (const [key, value] of Object.entries(rawHints)) {
50
+ const platform = toToken(key).toLowerCase();
51
+ if (!platform || !Array.isArray(value)) continue;
52
+ hints[platform] = value.map(item => toToken(item)).filter(Boolean);
53
+ }
54
+ if (!Array.isArray(hints.all)) hints.all = [];
55
+ return hints;
56
+ }
57
+
58
+ function normalizeIntentBranches(rawBranches) {
59
+ if (!Array.isArray(rawBranches)) return [];
60
+ return rawBranches
61
+ .map((branch) => {
62
+ if (!isPlainObject(branch)) return null;
63
+ const id = toToken(branch.id);
64
+ const suffix = toToken(branch.suffix);
65
+ const weight = Number(branch.weight);
66
+ const childTemplates = Array.isArray(branch.child_templates)
67
+ ? branch.child_templates.map(item => toToken(item)).filter(Boolean)
68
+ : [];
69
+ if (!id || !suffix || !Number.isFinite(weight) || childTemplates.length === 0) return null;
70
+ return {
71
+ id,
72
+ suffix,
73
+ weight,
74
+ child_templates: childTemplates,
75
+ };
76
+ })
77
+ .filter(Boolean);
78
+ }
79
+
80
+ function loadKeywordFixtures() {
81
+ let parsed = null;
82
+ try {
83
+ parsed = JSON.parse(
84
+ readFileSync(new URL('./keyword-fixtures.json', import.meta.url), 'utf8')
85
+ );
86
+ } catch (error) {
87
+ throw new Error(`keyword_fixture_load_failed:${error.message}`);
88
+ }
89
+ if (!isPlainObject(parsed)) {
90
+ throw new Error('keyword_fixture_invalid:root_object_required');
91
+ }
92
+ const intentBranches = normalizeIntentBranches(parsed.intent_branches);
93
+ if (intentBranches.length === 0) {
94
+ throw new Error('keyword_fixture_invalid:intent_branches_required');
95
+ }
96
+ return deepFreeze({
97
+ fixtureMeta: isPlainObject(parsed.fixture_meta) ? parsed.fixture_meta : {},
98
+ platformAliases: normalizeAliasMap(parsed.platform_aliases),
99
+ platformHints: normalizePlatformHints(parsed.platform_hints),
100
+ intentBranches,
101
+ });
102
+ }
64
103
 
65
104
  function normalizePlatform(value) {
66
105
  const raw = String(value ?? '').trim().toLowerCase();
@@ -0,0 +1,58 @@
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
+ }
@@ -7,7 +7,22 @@ import {
7
7
  analyzePage,
8
8
  analyzePageFromHtmlFixture,
9
9
  validatePageUnderstanding,
10
- } from '../../../../src/video/understanding/index.js';
10
+ } from '../../../src/_vendor/video/understanding/index.js';
11
+ import {
12
+ findToolBlockRule,
13
+ loadToolBlockRulesFromManifest,
14
+ } from '../../official-common/tool-access-policy.js';
15
+
16
+ const TOOL_BLOCK_RULES = loadToolBlockRulesFromManifest(new URL('./manifest.json', import.meta.url));
17
+
18
+ function resolveAnalyzePageBlockMessage(env = process.env) {
19
+ const matchedRule = findToolBlockRule(TOOL_BLOCK_RULES, {
20
+ toolName: 'analyze_page',
21
+ workspaceId: env?.WORKSPACE_ID,
22
+ agentId: env?.AGENT_ID,
23
+ });
24
+ return matchedRule?.message ?? null;
25
+ }
11
26
 
12
27
  function toText(payload) {
13
28
  return {
@@ -49,6 +64,10 @@ server.tool(
49
64
  },
50
65
  async ({ url, persona = '', options = {} }) => {
51
66
  try {
67
+ const blockedMessage = resolveAnalyzePageBlockMessage();
68
+ if (blockedMessage) {
69
+ return toError(blockedMessage);
70
+ }
52
71
  let payload;
53
72
  if (options?.fixture_mode) {
54
73
  payload = await analyzePageFromHtmlFixture({
@@ -7,6 +7,14 @@
7
7
  "tool_declarations": [
8
8
  { "name": "analyze_page", "classification": "cacheable" }
9
9
  ],
10
+ "tool_block_rules": [
11
+ {
12
+ "workspace_id": "ae63cc9e-feff-4d7e-a62e-a7a7c5fd69d9",
13
+ "agent_id": "91a45fd7-ce5f-4da6-9b27-e34bf7b7c0e2",
14
+ "tools": ["analyze_page"],
15
+ "message": "analyze_page blocked for editor_in_chief in CvMax. In this workspace, @short_video_scripter owns video production intake and page analysis."
16
+ }
17
+ ],
10
18
  "smoke_test": {
11
19
  "tool": "analyze_page",
12
20
  "arguments": {
@@ -1,175 +1,129 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
2
3
  import { z } from 'zod';
3
4
  import { startFixtureServer } from '../../official-common/server.js';
4
5
 
5
- const FIXTURE_META = Object.freeze({
6
- mode: 'fixture',
7
- as_of: '2026-04-29',
8
- capability: 'platform-policy-db',
9
- disclaimer: 'Policy fixtures are static snapshots; always verify against latest platform notices.',
10
- });
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;
11
10
 
12
- const PLATFORM_POLICY_DATA = Object.freeze({
13
- xhs: Object.freeze({
14
- platform: 'xhs',
15
- display_name: '小红书',
16
- policy_version: 'xhs-2026.04',
17
- updated_at: '2026-04-22',
18
- ai_label_required: true,
19
- ad_disclosure_required: true,
20
- policy_window_days: 7,
21
- required_labels: {
22
- ad: '赞助',
23
- ai: 'AI 生成内容标识',
24
- },
25
- sensitive_terms: Object.freeze([
26
- {
27
- id: 'xhs-external-redirect-wechat',
28
- term: '微信',
29
- severity: 'blocker',
30
- reason: '站外导流词,易触发限流或违规。',
31
- action: 'remove_redirect_hint',
32
- },
33
- {
34
- id: 'xhs-external-redirect-qr',
35
- term: '二维码',
36
- severity: 'blocker',
37
- reason: '站外导流相关词,发布前需清理。',
38
- action: 'remove_redirect_hint',
39
- },
40
- {
41
- id: 'xhs-fake-luxury',
42
- term: '高仿',
43
- severity: 'blocker',
44
- reason: '涉嫌仿冒交易,政策高风险。',
45
- action: 'reject_publish',
46
- },
47
- {
48
- id: 'xhs-medical-guarantee',
49
- term: '疗效保证',
50
- severity: 'warn',
51
- reason: '医疗功效承诺需附免责声明并谨慎表述。',
52
- action: 'add_disclaimer',
53
- },
54
- {
55
- id: 'xhs-finance-guarantee',
56
- term: '稳赚不赔',
57
- severity: 'blocker',
58
- reason: '金融收益承诺违规风险高。',
59
- action: 'remove_claim',
60
- },
61
- ]),
62
- }),
63
- douyin: Object.freeze({
64
- platform: 'douyin',
65
- display_name: '抖音',
66
- policy_version: 'douyin-2026.04',
67
- updated_at: '2026-04-20',
68
- ai_label_required: true,
69
- ad_disclosure_required: true,
70
- policy_window_days: 7,
71
- required_labels: {
72
- ad: '广告',
73
- ai: 'AI 生成声明',
74
- },
75
- sensitive_terms: Object.freeze([
76
- {
77
- id: 'douyin-fake-news',
78
- term: '内幕消息',
79
- severity: 'blocker',
80
- reason: '未经证实信息易触发平台处罚。',
81
- action: 'remove_unverified_claim',
82
- },
83
- {
84
- id: 'douyin-disaster-clickbait',
85
- term: '震惊全国',
86
- severity: 'warn',
87
- reason: '标题党表达可能降低推荐稳定性。',
88
- action: 'tone_down_title',
89
- },
90
- {
91
- id: 'douyin-finance-guarantee',
92
- term: '保本保收益',
93
- severity: 'blocker',
94
- reason: '金融收益保障承诺违规。',
95
- action: 'remove_claim',
96
- },
97
- {
98
- id: 'douyin-medical-cure',
99
- term: '包治百病',
100
- severity: 'blocker',
101
- reason: '医疗夸大宣传高风险。',
102
- action: 'remove_claim',
103
- },
104
- {
105
- id: 'douyin-traffic-manipulation',
106
- term: '互赞互粉',
107
- severity: 'warn',
108
- reason: '可被识别为异常增长操作。',
109
- action: 'remove_growth_hack',
110
- },
111
- ]),
112
- }),
113
- 'wechat-mp': Object.freeze({
114
- platform: 'wechat-mp',
115
- display_name: '公众号',
116
- policy_version: 'wechat-mp-2026.04',
117
- updated_at: '2026-04-19',
118
- ai_label_required: false,
119
- ad_disclosure_required: true,
120
- policy_window_days: 7,
121
- required_labels: {
122
- ad: '广告/合作声明',
123
- ai: '建议标注 AI 生成来源',
124
- },
125
- sensitive_terms: Object.freeze([
126
- {
127
- id: 'wechatmp-clickbait-share',
128
- term: '不转不是中国人',
129
- severity: 'blocker',
130
- reason: '诱导分享属于明确违规。',
131
- action: 'remove_manipulative_phrase',
132
- },
133
- {
134
- id: 'wechatmp-political-rumor',
135
- term: '内部通知',
136
- severity: 'warn',
137
- reason: '疑似无来源权威口径,需补证据。',
138
- action: 'add_citation_or_remove',
139
- },
140
- {
141
- id: 'wechatmp-medical-guarantee',
142
- term: '根治',
143
- severity: 'warn',
144
- reason: '医疗强承诺需免责声明并谨慎表达。',
145
- action: 'add_disclaimer',
146
- },
147
- {
148
- id: 'wechatmp-finance-guarantee',
149
- term: '稳赚',
150
- severity: 'blocker',
151
- reason: '金融收益承诺违规。',
152
- action: 'remove_claim',
153
- },
154
- {
155
- id: 'wechatmp-fake-scarcity',
156
- term: '最后一天',
157
- severity: 'warn',
158
- reason: '虚假稀缺文案易触发风控。',
159
- action: 'verify_or_tone_down',
160
- },
161
- ]),
162
- }),
163
- });
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
+ }
164
122
 
165
123
  function normalizePlatform(value) {
166
124
  const raw = String(value ?? '').trim().toLowerCase();
167
125
  if (!raw) return '';
168
- if (raw === 'xiaohongshu' || raw === 'redbook') return 'xhs';
169
- if (raw === 'wechat' || raw === 'wechatmp' || raw === 'wechat_mp' || raw === 'gzh' || raw === '公众号') {
170
- return 'wechat-mp';
171
- }
172
- if (raw === 'tiktok_cn') return 'douyin';
126
+ if (Object.hasOwn(PLATFORM_ALIASES, raw)) return PLATFORM_ALIASES[raw];
173
127
  return raw;
174
128
  }
175
129