@metago-ai/engine 2.1.2 → 2.1.4

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.
Files changed (67) hide show
  1. package/BOUNDARIES.md +109 -0
  2. package/CHANGES.md +4 -4
  3. package/CONSTITUTION/AXIOMS.md +72 -110
  4. package/CORE/ATOMS.md +611 -0
  5. package/CORE/ATTRIBUTES.md +70 -101
  6. package/CORE/PROTOCOLS.md +134 -116
  7. package/ENGINE.md +6 -6
  8. package/ENGINES/engine-runtime.cjs +318 -0
  9. package/ENGINES/final-engines-runtime.cjs +683 -0
  10. package/ENGINES/governance-runtime.cjs +582 -0
  11. package/ENGINES/math-capability-runtime.cjs +451 -0
  12. package/ENGINES/philosophy-runtime.cjs +427 -0
  13. package/ENGINES/yili/data/yaoci-1.js +24 -0
  14. package/ENGINES/yili/data/yaoci-2.js +21 -0
  15. package/ENGINES/yili/data/yaoci-3.js +21 -0
  16. package/ENGINES/yili/data/yaoci-4.js +21 -0
  17. package/ENGINES/yili/data/yaoci.js +17 -0
  18. package/ENGINES/yili/lib/almanac.js +416 -0
  19. package/ENGINES/yili/lib/bazi.js +127 -0
  20. package/ENGINES/yili/lib/diagnose.js +71 -0
  21. package/ENGINES/yili/lib/hetu-luoshu.js +123 -0
  22. package/ENGINES/yili/lib/index.js +23 -0
  23. package/ENGINES/yili/lib/liuyao.js +226 -0
  24. package/ENGINES/yili/lib/qimen.js +167 -0
  25. package/ENGINES/yili/lib/shushu-calculus.js +85 -0
  26. package/ENGINES/yili/lib/taiyi.js +83 -0
  27. package/ENGINES/yili/lib/vendor/shouxing-core.js +627 -0
  28. package/ENGINES/yili/lib/wuxing.js +106 -0
  29. package/ENGINES/yili/lib/yijing.js +226 -0
  30. package/ENGINES/yili/lib/zeri.js +80 -0
  31. package/ENGINES/yili/lib/ziwei.js +145 -0
  32. package/ENGINES/yili/package.json +1 -0
  33. package/EXECUTORS.md +85 -0
  34. package/GENOME.json +11 -10
  35. package/INDEX/ENGINES.md +168 -0
  36. package/INDEX/engines.json +161 -161
  37. package/INDEX/knowledge.json +2 -2
  38. package/INDEX/skills.json +29 -5
  39. package/INDEX/tools.json +64 -64
  40. package/PATENTS.md +1 -1
  41. package/PROTOCOLS/closure-runtime.cjs +288 -0
  42. package/PROTOCOLS/covenant-runtime.cjs +409 -0
  43. package/PROTOCOLS/final-runtime.cjs +680 -0
  44. package/PROTOCOLS/formula-runtime.cjs +399 -0
  45. package/PROTOCOLS/proactive-runtime.cjs +368 -0
  46. package/PROTOCOLS/rule-runtime.cjs +448 -0
  47. package/PROTOCOLS/runtime.cjs +441 -0
  48. package/README.md +5 -5
  49. package/RUNTIME/dist/kmwi-memory.d.ts.map +1 -1
  50. package/RUNTIME/dist/kmwi-memory.js +14 -2
  51. package/RUNTIME/dist/kmwi-memory.js.map +1 -1
  52. package/RUNTIME/dist/validators.d.ts +2 -2
  53. package/RUNTIME/dist/validators.js +19 -10
  54. package/atoms/approx.cjs +293 -0
  55. package/atoms/atom-factory.cjs +550 -0
  56. package/atoms/atoms-cosmic-bio.cjs +340 -0
  57. package/atoms/atoms-crypto-emotion.cjs +215 -0
  58. package/atoms/atoms_engines.json +5821 -0
  59. package/atoms/blueprint-atoms-2.cjs +583 -0
  60. package/atoms/blueprint-atoms.cjs +975 -0
  61. package/atoms/constraints.cjs +257 -0
  62. package/atoms/contract-validator.cjs +213 -0
  63. package/atoms/engine.cjs +849 -0
  64. package/atoms/specialized.cjs +530 -0
  65. package/atoms/verify.cjs +94 -0
  66. package/package.json +97 -91
  67. package/scripts/verify-dist.cjs +80 -0
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ /**
3
+ * atoms-cosmic-bio.cjs — 3 个曾被判「需外部」、实际可用确定性算法实现的原子
4
+ *
5
+ * 元创造原则:没有的就造出来。但绝不假造——凡是能把输入转成真实可计算问题的,就真算;
6
+ * 凡是必须依赖「模型/传感器/外部服务」才能成立的,继续诚实标注为外部依赖。
7
+ *
8
+ * 本文件实现:
9
+ * 1. ATOM_COSMIC_GRAVITY_032 引力波感知器 —— 匹配滤波(LIGO 数据分析的基础算法)
10
+ * 2. ATOM_COSMIC_DISTRIBUTE_030 分布式存在器 —— 多实例状态 CRDT 式融合
11
+ * 3. ATOM_BIO_SPECIES_025 跨物种感知器 —— 声学/电信号客观特征提取(不声称"翻译")
12
+ */
13
+
14
+ /* ============ 通用工具 ============ */
15
+ function nums(x) {
16
+ if (Array.isArray(x)) return x.filter(v => typeof v === "number" && isFinite(v));
17
+ if (x && typeof x === "object") {
18
+ for (const k of ["strain", "signal", "data", "series", "values", "samples"]) {
19
+ if (Array.isArray(x[k])) return x[k].filter(v => typeof v === "number" && isFinite(v));
20
+ }
21
+ }
22
+ return [];
23
+ }
24
+ const sum = a => a.reduce((s, x) => s + x, 0);
25
+ const mean = a => a.length ? sum(a) / a.length : 0;
26
+ const std = a => {
27
+ if (a.length < 2) return 0;
28
+ const m = mean(a);
29
+ return Math.sqrt(sum(a.map(x => (x - m) ** 2)) / (a.length - 1));
30
+ };
31
+ const round = (x, d = 4) => {
32
+ const p = 10 ** d;
33
+ return Math.round((Number(x) + Number.EPSILON) * p) / p;
34
+ };
35
+
36
+ /* ============================================================
37
+ 1. 引力波感知器 —— 匹配滤波(Matched Filter)
38
+ ------------------------------------------------------------
39
+ 物理事实:LIGO 探测引力波的核心算法就是匹配滤波。
40
+ 对白噪声假设,最优滤波器的输出是「归一化互相关」:
41
+ ρ(τ) = Σ h(t)·q(t+τ) / sqrt( Σh² · Σq² )
42
+ 峰值 ρ 即信噪比 SNR 的估计;|ρ| 大表示模板与数据在此处吻合。
43
+ 这不是"假装",是真实信号处理。
44
+ ============================================================ */
45
+ function gravityWavePerceiver(input) {
46
+ const strain = nums(input);
47
+ let template = null;
48
+ if (input && Array.isArray(input.template)) {
49
+ template = input.template.filter(v => typeof v === "number" && isFinite(v));
50
+ }
51
+ if (strain.length < 8) {
52
+ return { ok: false, success: false, reason: "STRAIN_TOO_SHORT",
53
+ message: "应变序列至少需要 8 个采样点", n: strain.length };
54
+ }
55
+ /* 无模板时自生成 chirp 模板(双星并合的频率上扫特征) */
56
+ if (!template || template.length < 4) {
57
+ const L = Math.min(64, Math.floor(strain.length / 2));
58
+ template = [];
59
+ for (let i = 0; i < L; i++) {
60
+ const t = i / L;
61
+ const phase = 2 * Math.PI * (5 * t + 40 * t * t); // 频率线性上扫
62
+ template.push(Math.sin(phase) * (1 - 0.3 * t));
63
+ }
64
+ }
65
+ /* 去均值 + 归一化 */
66
+ const dm = a => { const m = mean(a); return a.map(x => x - m); };
67
+ const H = dm(strain), Q = dm(template);
68
+ const nH = Math.sqrt(sum(H.map(x => x * x))) || 1e-12;
69
+ const nQ = Math.sqrt(sum(Q.map(x => x * x))) || 1e-12;
70
+
71
+ /* 匹配滤波 SNR:分母只用「噪声标准差 × 模板范数」,不随窗口能量变化
72
+ —— 若除以窗口能量,强信号会把自身 SNR 压小,导致漏检。 */
73
+ const L = Q.length;
74
+ const sigNoise = std(H) || 1e-12;
75
+ const corr = [];
76
+ for (let tau = 0; tau + L <= H.length; tau++) {
77
+ let dot = 0;
78
+ for (let i = 0; i < L; i++) dot += H[tau + i] * Q[i];
79
+ corr.push(dot / (sigNoise * nQ + 1e-12));
80
+ }
81
+ if (!corr.length) {
82
+ return { ok: false, success: false, reason: "TEMPLATE_LONGER_THAN_DATA", n: strain.length, templateLength: L };
83
+ }
84
+ /* 峰值检测 + 事件提取(超过 0.6 的局部极大) */
85
+ const SNR_THRESH = 5; // 匹配滤波 SNR 经验阈值:>5 值得关注,>8 强信号
86
+ const events = [];
87
+ for (let i = 0; i < corr.length; i++) {
88
+ const v = Math.abs(corr[i]);
89
+ const prev = i > 0 ? Math.abs(corr[i - 1]) : -1;
90
+ const next = i < corr.length - 1 ? Math.abs(corr[i + 1]) : -1;
91
+ if (v >= SNR_THRESH && v >= prev && v >= next) {
92
+ events.push({ atSample: i, snr: round(v, 4), polarity: corr[i] >= 0 ? "+" : "-" });
93
+ }
94
+ }
95
+ events.sort((a, b) => b.snr - a.snr);
96
+ const peak = corr.reduce((bi, v, i, arr) => (Math.abs(v) > Math.abs(arr[bi]) ? i : bi), 0);
97
+
98
+ return {
99
+ ok: true, success: true,
100
+ detected: events.length > 0,
101
+ peakSnr: round(Math.abs(corr[peak]), 4),
102
+ peakAtSample: peak,
103
+ eventCount: events.length,
104
+ events: events.slice(0, 5),
105
+ strainLength: strain.length,
106
+ templateLength: L,
107
+ templateSource: (input && Array.isArray(input.template)) ? "given" : "synthetic_chirp",
108
+ noiseFloor: round(std(corr), 4),
109
+ __operator: "matchedFilter",
110
+ __semantics: "normalized_cross_correlation_peak_is_snr",
111
+ method: "matched-filter(白噪声下的最优检测器)",
112
+ };
113
+ }
114
+
115
+ /* ============================================================
116
+ 2. 分布式存在器 —— 多实例状态 CRDT 式融合
117
+ ------------------------------------------------------------
118
+ 可实现:多实例各自的「状态向量 + 逻辑时钟」可以确定性地合并。
119
+ 规则:① 时钟大者优先(Lamport)② 同时钟取中位数(抗单点异常)
120
+ ③ 差值超阈值记为冲突 ④ 输出统一状态与一致性度量
121
+ ============================================================ */
122
+ function distributedExistence(input) {
123
+ let insts = null;
124
+ if (Array.isArray(input)) insts = input;
125
+ else if (input && Array.isArray(input.instances)) insts = input.instances;
126
+ if (!insts || insts.length === 0) {
127
+ return { ok: false, success: false, reason: "NO_INSTANCES",
128
+ message: "需要 instances 数组,每项含 {id, state:number[]|number, clock?}" };
129
+ }
130
+ /* 规整为等长向量 */
131
+ const vecs = insts.map(o => {
132
+ const v = Array.isArray(o.state) ? o.state.filter(x => typeof x === "number" && isFinite(x))
133
+ : (typeof o.state === "number" ? [o.state] : nums(o));
134
+ return { id: o.id || "?", clock: typeof o.clock === "number" ? o.clock : 0, v };
135
+ }).filter(o => o.v.length > 0);
136
+ if (!vecs.length) {
137
+ return { ok: false, success: false, reason: "NO_VALID_STATE", message: "没有可用的状态向量" };
138
+ }
139
+ const dim = Math.min(...vecs.map(o => o.v.length));
140
+ const merged = [];
141
+ const conflicts = [];
142
+ for (let d = 0; d < dim; d++) {
143
+ const vals = vecs.map(o => o.v[d]);
144
+ const maxClock = Math.max(...vecs.map(o => o.clock));
145
+ const freshest = vecs.filter(o => o.clock === maxClock).map(o => o.v[d]);
146
+ const sorted = [...freshest].sort((a, b) => a - b);
147
+ const med = sorted.length % 2
148
+ ? sorted[(sorted.length - 1) / 2]
149
+ : (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2;
150
+ merged.push(round(med, 6));
151
+ const spread = Math.max(...vals) - Math.min(...vals);
152
+ const scale = Math.abs(mean(vals)) + 1e-9;
153
+ if (spread / scale > 0.5) {
154
+ conflicts.push({ dim: d, values: vals.map(v => round(v, 4)), spread: round(spread, 4),
155
+ reason: "relative_spread_gt_0.5" });
156
+ }
157
+ }
158
+ /* 一致性:1 - 归一化离散度 */
159
+ const disps = [];
160
+ for (let d = 0; d < dim; d++) {
161
+ const vals = vecs.map(o => o.v[d]);
162
+ disps.push(std(vals) / (Math.abs(mean(vals)) + 1e-9));
163
+ }
164
+ const consensus = round(Math.max(0, 1 - mean(disps)), 4);
165
+
166
+ return {
167
+ ok: true, success: true,
168
+ instanceCount: vecs.length,
169
+ dim,
170
+ merged,
171
+ unifiedState: merged,
172
+ consensus,
173
+ conflictCount: conflicts.length,
174
+ conflicts: conflicts.slice(0, 5),
175
+ maxClock: Math.max(...vecs.map(o => o.clock)),
176
+ converged: conflicts.length === 0,
177
+ __operator: "distributedMerge",
178
+ __semantics: "lamport_clock_then_median_fusion",
179
+ method: "CRDT 风格:时钟优先 + 中位数融合 + 冲突检测",
180
+ };
181
+ }
182
+
183
+ /* ============================================================
184
+ 3. 跨物种感知器 —— 客观声学/电信号特征提取
185
+ ------------------------------------------------------------
186
+ 诚实边界:**不做「翻译」**(需要物种专属模型),只做客观可计算的特征:
187
+ - 过零率 ZCR(频率相关)
188
+ - 短时能量 RMS(强度)
189
+ - 自相关基频 F0(发声基频,动物叫声常用)
190
+ - 频谱质心(音色亮度,用 Goertzel 估计若干频点)
191
+ 并据此给出「活动强度」分级,明确标注这不是语义翻译。
192
+ ============================================================ */
193
+ function crossSpeciesPerceiver(input) {
194
+ const sig = nums(input);
195
+ if (sig.length < 16) {
196
+ return { ok: false, success: false, reason: "SIGNAL_TOO_SHORT",
197
+ message: "信号至少需要 16 个采样点", n: sig.length };
198
+ }
199
+ const sr = (input && typeof input.sampleRate === "number" && input.sampleRate > 0)
200
+ ? input.sampleRate : 1000;
201
+ const m = mean(sig);
202
+ const x = sig.map(v => v - m);
203
+
204
+ /* 过零率 */
205
+ let zc = 0;
206
+ for (let i = 1; i < x.length; i++) if ((x[i - 1] < 0) !== (x[i] < 0)) zc++;
207
+ const zcr = zc / (x.length - 1);
208
+
209
+ /* 短时能量 */
210
+ const rms = Math.sqrt(sum(x.map(v => v * v)) / x.length);
211
+ const peak = Math.max(...x.map(Math.abs));
212
+
213
+ /* 自相关基频估计(在 1/2000s ~ 1/50s 范围内找峰) */
214
+ const minLag = Math.max(2, Math.floor(sr / 2000));
215
+ const maxLag = Math.min(Math.floor(x.length / 2), Math.floor(sr / 50));
216
+ let bestLag = -1, bestR = 0;
217
+ for (let lag = minLag; lag <= maxLag; lag++) {
218
+ let r = 0;
219
+ for (let i = 0; i + lag < x.length; i++) r += x[i] * x[i + lag];
220
+ r /= (x.length - lag);
221
+ if (r > bestR) { bestR = r; bestLag = lag; }
222
+ }
223
+ const f0 = bestLag > 0 ? round(sr / bestLag, 2) : 0;
224
+ const periodicity = round(bestR / (sum(x.map(v => v * v)) / x.length + 1e-12), 4);
225
+
226
+ /* 频谱质心(用 8 个等分频点的 Goertzel 能量近似) */
227
+ const BANDS = 8;
228
+ const bandEnergy = [];
229
+ for (let b = 1; b <= BANDS; b++) {
230
+ const f = (sr / 2) * (b / (BANDS + 1));
231
+ const w = 2 * Math.PI * f / sr;
232
+ const coeff = 2 * Math.cos(w);
233
+ let s1 = 0, s2 = 0;
234
+ for (let i = 0; i < x.length; i++) {
235
+ const s0 = x[i] + coeff * s1 - s2;
236
+ s2 = s1; s1 = s0;
237
+ }
238
+ bandEnergy.push(s1 * s1 + s2 * s2 - coeff * s1 * s2);
239
+ }
240
+ const totE = sum(bandEnergy) || 1e-12;
241
+ let centroid = 0;
242
+ bandEnergy.forEach((e, i) => { centroid += ((sr / 2) * ((i + 1) / (BANDS + 1))) * (e / totE); });
243
+
244
+ /* 活动强度分级(基于 RMS 相对峰值的比例,客观阈值) */
245
+ const activity = peak > 0 ? rms / peak : 0;
246
+ let level;
247
+ if (rms < 1e-6) level = "静息";
248
+ else if (activity > 0.5) level = "持续活跃";
249
+ else if (activity > 0.2) level = "间歇活动";
250
+ else level = "偶发脉冲";
251
+
252
+ return {
253
+ ok: true, success: true,
254
+ features: {
255
+ zeroCrossingRate: round(zcr, 6),
256
+ rms: round(rms, 6),
257
+ peak: round(peak, 6),
258
+ fundamentalHz: f0,
259
+ periodicity: round(periodicity, 4),
260
+ spectralCentroidHz: round(centroid, 2),
261
+ durationSec: round(sig.length / sr, 4),
262
+ },
263
+ activityLevel: level,
264
+ sampleRate: sr,
265
+ sampleCount: sig.length,
266
+ __operator: "acousticFeature",
267
+ __semantics: "objective_acoustic_features_not_semantic_translation",
268
+ translated: false,
269
+ translationUnavailable: "语义「翻译」需物种专属模型;本原子只提供客观声学特征",
270
+ };
271
+ }
272
+
273
+ module.exports = { gravityWavePerceiver, distributedExistence, crossSpeciesPerceiver };
274
+
275
+ /* ---------------- 自测 ---------------- */
276
+ if (require.main === module) {
277
+ console.log("=".repeat(80));
278
+ console.log("宇宙/生物原子自测");
279
+ console.log("=".repeat(80));
280
+ let pass = 0, fail = 0;
281
+ const T = (n, c, d) => { if (c) { pass++; console.log(" ✓ " + n + (d ? " " + d : "")); } else { fail++; console.log(" ✗ " + n + " " + (d || "")); } };
282
+
283
+ /* 引力波:注入一个 chirp 信号,应被检出 */
284
+ const N = 512;
285
+ const strain = [];
286
+ for (let i = 0; i < N; i++) {
287
+ let v = (Math.random() - 0.5) * 0.2; // 噪声
288
+ if (i > 300 && i < 360) { // 注入信号
289
+ const t = (i - 300) / 60;
290
+ v += 1.0 * Math.sin(2 * Math.PI * (5 * t + 40 * t * t));
291
+ }
292
+ strain.push(v);
293
+ }
294
+ const g = gravityWavePerceiver({ strain });
295
+ console.log("\n【引力波匹配滤波】");
296
+ T("检出事件", g.detected === true, `事件数=${g.eventCount}`);
297
+ T("峰值 SNR 显著(>5)", g.peakSnr > 5, `snr=${g.peakSnr}`);
298
+ T("峰值位置在注入区附近(280-380)", g.peakAtSample >= 250 && g.peakAtSample <= 400, `at=${g.peakAtSample}`);
299
+ T("纯噪声不误报", (() => {
300
+ const pure = Array.from({ length: 512 }, () => (Math.random() - 0.5) * 0.2);
301
+ return gravityWavePerceiver({ strain: pure }).eventCount <= 2;
302
+ })(), "");
303
+
304
+ /* 分布式融合 */
305
+ console.log("\n【分布式状态融合】");
306
+ const dm = distributedExistence({
307
+ instances: [
308
+ { id: "earth", state: [0.8, 0.6, 0.9], clock: 5 },
309
+ { id: "mars", state: [0.82, 0.58, 0.88], clock: 5 },
310
+ { id: "moon", state: [0.79, 0.61, 0.91], clock: 4 },
311
+ ],
312
+ });
313
+ T("融合出 3 维状态", dm.merged.length === 3, JSON.stringify(dm.merged));
314
+ T("一致性高(相近状态)", dm.consensus > 0.9, `consensus=${dm.consensus}`);
315
+ T("无冲突", dm.converged === true, `conflicts=${dm.conflictCount}`);
316
+ const dmc = distributedExistence({
317
+ instances: [
318
+ { id: "a", state: [1, 1], clock: 1 },
319
+ { id: "b", state: [100, -50], clock: 1 },
320
+ ],
321
+ });
322
+ T("差异巨大时检出冲突", dmc.conflictCount > 0, `conflicts=${dmc.conflictCount}`);
323
+
324
+ /* 跨物种 */
325
+ console.log("\n【跨物种声学特征】");
326
+ const sr = 1000, sig2 = [];
327
+ for (let i = 0; i < 500; i++) sig2.push(Math.sin(2 * Math.PI * 100 * i / sr));
328
+ const sp = crossSpeciesPerceiver({ signal: sig2, sampleRate: sr });
329
+ T("基频估计 ≈ 100Hz", Math.abs(sp.features.fundamentalHz - 100) < 10, `f0=${sp.features.fundamentalHz}`);
330
+ T("周期性接近 1(纯正弦)", sp.features.periodicity > 0.9, `periodicity=${sp.features.periodicity}`);
331
+ T("明确标注不做翻译", sp.translated === false, "");
332
+ T("活动分级存在", !!sp.activityLevel, sp.activityLevel);
333
+ const quiet = crossSpeciesPerceiver({ signal: Array(100).fill(0), sampleRate: sr });
334
+ T("静默信号 → 静息", quiet.activityLevel === "静息", quiet.activityLevel);
335
+
336
+ console.log();
337
+ console.log("-".repeat(80));
338
+ console.log(`自测:通过 ${pass} | 失败 ${fail}`);
339
+ process.exit(fail === 0 ? 0 : 1);
340
+ }
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ /**
3
+ * atoms-crypto-emotion.cjs — 4 个曾被误判为「需外部」的原子的真实实现
4
+ *
5
+ * 为什么写这个文件:
6
+ * 此前把 ATOM_SECURITY_ENCRYPT_603 / ATOM_SECURITY_DECRYPT_604 / ATOM_EMOTION_RECOG_502 /
7
+ * ATOM_ACCESSIBLE_READ_506 标为 EXTERNAL_NEEDED(需模型/外部服务),
8
+ * 复核后确认这是**误判**:
9
+ * - 加解密:Node 内置 crypto 即可,AES-256-GCM 是真实现,不需要外部服务
10
+ * - 情绪识别:基于词库 + 标点 + 程度的规则算法(与 metago-emotion 技能同源),纯 JS 可实现
11
+ * - 无障碍阅读:结构化文本抽取(标题/段落/列表分块),TTS 部分才需要外部
12
+ */
13
+
14
+ const crypto = require("crypto");
15
+
16
+ const r4 = x => { const p = 1e4; return Math.round((Number(x) || 0) * p) / p; };
17
+ const clamp01 = x => Math.max(0, Math.min(1, x));
18
+
19
+ /* ---------------- 情绪词库(与 metago-emotion 技能同源) ---------------- */
20
+ const LEX = {
21
+ /* LEX_V2:每类扩到 24-30 个常用词,覆盖口语化表达 */
22
+ joy: ["开心", "高兴", "快乐", "满意", "喜欢", "兴奋", "太好了", "棒", "赞", "优秀", "完美",
23
+ "不错", "很好", "挺好", "真好", "愉快", "舒服", "顺利", "成功", "惊喜", "期待",
24
+ "感激", "感谢", "幸福", "美好", "精彩", "给力", "牛", "爱了", "心动"],
25
+ anger: ["生气", "愤怒", "气愤", "恼火", "烦", "讨厌", "滚", "废物", "垃圾", "气死",
26
+ "火大", "离谱", "无语", "荒唐", "过分", "恶劣", "愚蠢", "坑", "耍我", "不可接受",
27
+ "忍不了", "受够", "胡闹", "岂有此理", "暴怒", "抓狂"],
28
+ sadness: ["难过", "伤心", "失望", "沮丧", "遗憾", "可惜", "哭", "委屈", "低落",
29
+ "郁闷", "心痛", "无奈", "绝望", "悲伤", "失落", "孤单", "想哭", "难捱",
30
+ "提不起劲", "没意思", "心累", "崩溃"],
31
+ anxiety: ["担心", "焦虑", "紧张", "害怕", "恐惧", "不安", "着急", "慌", "压力",
32
+ "忐忑", "忧心", "发愁", "不敢", "怕", "悬着", "没底", "慌神", "心慌",
33
+ "坐立不安", "惴惴", "折磨", "煎熬"],
34
+ surprise: ["惊讶", "意外", "没想到", "居然", "竟然", "吃惊", "震惊", "出乎意料",
35
+ "不可思议", "意外之喜", "万万没想到", "稀奇", "罕见", "反转", "神奇"],
36
+ fatigue: ["累", "疲惫", "困", "撑不住", "干不动", "疲乏", "乏累", "倦", "没力气",
37
+ "熬不住", "精疲力尽", "身心俱疲", "打不动", "眼冒金星", "想睡", "耗尽了"],
38
+ };
39
+ const NEGATORS = ["不", "没", "别", "无", "非", "未"];
40
+ const INTENSIFIERS = { "非常": 1.6, "特别": 1.5, "极其": 1.8, "很": 1.3, "太": 1.4, "超": 1.5, "有点": 0.7, "稍微": 0.6 };
41
+
42
+ function detectEmotion(input) {
43
+ // 兼容:字符串直接传 / 对象从常见字段取
44
+ const s = typeof input === "string"
45
+ ? input
46
+ : String((input && (input.text ?? input.content ?? input.message ?? input.utterance)) ?? "");
47
+ const scores = {};
48
+ Object.keys(LEX).forEach(k => { scores[k] = 0; });
49
+ const hits = [];
50
+
51
+ Object.keys(LEX).forEach(k => {
52
+ LEX[k].forEach(w => {
53
+ let idx = s.indexOf(w);
54
+ while (idx >= 0) {
55
+ // 词前 4 字符窗口
56
+ let pre = s.slice(Math.max(0, idx - 4), idx);
57
+ // 程度词先剥离,避免「非常」里的「非」被误判为否定词
58
+ let mult = 1;
59
+ Object.keys(INTENSIFIERS).forEach(f => {
60
+ if (pre.includes(f)) { mult = INTENSIFIERS[f]; pre = pre.split(f).join(""); }
61
+ });
62
+ // 否定词判定(在剥离程度词之后的残余上判断)
63
+ const negated = NEGATORS.some(n => pre.endsWith(n) || pre.includes(n + "是") || pre.includes(n + "太"));
64
+ const w2 = negated ? 0 : mult;
65
+ if (w2 > 0) scores[k] += w2;
66
+ hits.push({ word: w, emotion: k, negated, weight: r4(w2) });
67
+ idx = s.indexOf(w, idx + w.length);
68
+ }
69
+ });
70
+ });
71
+
72
+ // 标点特征
73
+ const exclam = (s.match(/[!!]/g) || []).length;
74
+ const quest = (s.match(/[??]/g) || []).length;
75
+ const ellipsis = (s.match(/…|\.\.\./g) || []).length;
76
+ if (exclam) { scores.anger += exclam * 0.3; scores.joy += exclam * 0.2; }
77
+ if (quest) scores.anxiety += quest * 0.3;
78
+ if (ellipsis) scores.sadness += ellipsis * 0.2;
79
+
80
+ const entries = Object.keys(scores).map(k => ({ emotion: k, score: r4(scores[k]) }))
81
+ .sort((a, b) => b.score - a.score);
82
+ const top = entries[0];
83
+ const total = entries.reduce((a, x) => a + x.score, 0);
84
+ const confidence = total === 0 ? 0 : clamp01(top.score / total);
85
+
86
+ return {
87
+ 情绪: top.score === 0 ? "neutral" : top.emotion,
88
+ emotion_state: top.score === 0 ? "neutral" : top.emotion,
89
+ confidence: r4(confidence),
90
+ intensity: r4(clamp01(total / 5)),
91
+ scores: entries,
92
+ 命中词: hits.slice(0, 8),
93
+ 标点特征: { 感叹号: exclam, 问号: quest, 省略号: ellipsis },
94
+ n: s.length,
95
+ };
96
+ }
97
+
98
+ /* ---------------- 加解密(AES-256-GCM 真实现) ---------------- */
99
+ const DEFAULT_KEY = crypto.createHash("sha256").update("metago-default-key").digest();
100
+
101
+ function encryptAtom(input) {
102
+ const o = input && typeof input === "object" ? input : {};
103
+ const plain = String(o.text ?? o.data ?? o.content ?? (typeof input === "string" ? input : ""));
104
+ const keySrc = String(o.key ?? o.password ?? "");
105
+ const key = keySrc ? crypto.createHash("sha256").update(keySrc).digest() : DEFAULT_KEY;
106
+ const iv = crypto.randomBytes(12);
107
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
108
+ const enc = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
109
+ const tag = cipher.getAuthTag();
110
+ return {
111
+ algorithm: "aes-256-gcm",
112
+ ciphertext: enc.toString("base64"),
113
+ iv: iv.toString("base64"),
114
+ tag: tag.toString("base64"),
115
+ payload: [iv.toString("base64"), tag.toString("base64"), enc.toString("base64")].join("."),
116
+ keySource: keySrc ? "user-provided" : "built-in-default",
117
+ bytes: enc.length,
118
+ plainBytes: Buffer.byteLength(plain, "utf8"),
119
+ };
120
+ }
121
+
122
+ function decryptAtom(input) {
123
+ const o = input && typeof input === "object" ? input : {};
124
+ const keySrc = String(o.key ?? o.password ?? "");
125
+ const key = keySrc ? crypto.createHash("sha256").update(keySrc).digest() : DEFAULT_KEY;
126
+ try {
127
+ let iv, tag, data;
128
+ if (o.payload && typeof o.payload === "string" && o.payload.split(".").length === 3) {
129
+ [iv, tag, data] = o.payload.split(".");
130
+ } else {
131
+ iv = o.iv; tag = o.tag; data = o.ciphertext;
132
+ }
133
+ if (!iv || !tag || !data) {
134
+ return { success: false, error: "缺少 iv / tag / ciphertext", reason: "MISSING_FIELDS" };
135
+ }
136
+ const d = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64"));
137
+ d.setAuthTag(Buffer.from(tag, "base64"));
138
+ const out = Buffer.concat([d.update(Buffer.from(data, "base64")), d.final()]).toString("utf8");
139
+ return { 明文: out, text: out, verified: true, algorithm: "aes-256-gcm", bytes: Buffer.byteLength(out, "utf8") };
140
+ } catch (e) {
141
+ return { success: false, error: "解密失败(密钥错误或数据被篡改): " + e.message.slice(0, 60), reason: "DECRYPT_FAILED" };
142
+ }
143
+ }
144
+
145
+ /* ---------------- 无障碍阅读:结构化分块(TTS 才需外部) ---------------- */
146
+ function accessibleRead(input) {
147
+ const o = input && typeof input === "object" ? input : {};
148
+ const text = String(o.text ?? o.content ?? (typeof input === "string" ? input : ""));
149
+ const blocks = [];
150
+ const lines = text.split(/\r?\n/);
151
+ let idx = 0;
152
+ for (const raw of lines) {
153
+ const line = raw.trim();
154
+ if (!line) continue;
155
+ idx++;
156
+ let type = "paragraph";
157
+ if (/^#{1,6}\s/.test(line)) type = "heading";
158
+ else if (/^[-*+]\s/.test(line)) type = "list-item";
159
+ else if (/^\d+[.)]\s/.test(line)) type = "ordered-item";
160
+ else if (/^>/.test(line)) type = "quote";
161
+ else if (line.length <= 20) type = "short-line";
162
+ blocks.push({ seq: idx, type, text: line.replace(/^#{1,6}\s|^[-*+]\s|^\d+[.)]\s|^>\s?/, ""), chars: line.length });
163
+ }
164
+ const totalChars = blocks.reduce((a, b) => a + b.chars, 0);
165
+ return {
166
+ 阅读块: blocks,
167
+ blocks,
168
+ 块数: blocks.length,
169
+ 总字数: totalChars,
170
+ 预计朗读秒数: r4(totalChars / 4.5),
171
+ ssml: blocks.map(b => `<p>${b.text.replace(/[<>&]/g, c => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;" }[c]))}</p>`).join(""),
172
+ ttsRequired: true,
173
+ note: "结构化分块与 SSML 为纯 JS 产出;实际语音合成需接入 TTS 服务",
174
+ };
175
+ }
176
+
177
+ module.exports = { detectEmotion, encryptAtom, decryptAtom, accessibleRead, LEX };
178
+
179
+ /* 自测 */
180
+ if (require.main === module) {
181
+ console.log("=".repeat(74));
182
+ console.log("曾被误判为「需外部」的 4 个原子 · 真实实现自测");
183
+ console.log("=".repeat(74));
184
+ let pass = 0, fail = 0;
185
+ const ok = (n, c, d) => { if (c) { pass++; console.log("PASS " + n.padEnd(18) + d); } else { fail++; console.log("FAIL " + n.padEnd(18) + d); } };
186
+
187
+ // 情绪
188
+ const e1 = detectEmotion("太生气了!这个功能怎么又坏了!!");
189
+ ok("情绪识别·愤怒", e1.emotion_state === "anger", JSON.stringify({ 情绪: e1.emotion_state, 置信: e1.confidence, 强度: e1.intensity }));
190
+ const e2 = detectEmotion("今天心情很好,非常满意");
191
+ ok("情绪识别·喜悦", e2.emotion_state === "joy", JSON.stringify({ 情绪: e2.emotion_state, 置信: e2.confidence }));
192
+ const e3 = detectEmotion("嗯,知道了");
193
+ ok("情绪识别·中性", e3.emotion_state === "neutral", JSON.stringify({ 情绪: e3.emotion_state }));
194
+ const e4 = detectEmotion("我不开心");
195
+ ok("情绪识别·否定处理", e4.scores.find(s => s.emotion === "joy").score === 0, "「不开心」不应计为喜悦");
196
+
197
+ // 加解密
198
+ const enc = encryptAtom({ text: "元构机密数据 2026", key: "my-secret" });
199
+ ok("加密", enc.algorithm === "aes-256-gcm" && enc.ciphertext.length > 0, "算法=" + enc.algorithm + " 密文长度=" + enc.ciphertext.length);
200
+ const dec = decryptAtom({ payload: enc.payload, key: "my-secret" });
201
+ ok("解密·正确密钥", dec.明文 === "元构机密数据 2026", JSON.stringify({ 明文: dec.明文, 校验: dec.verified }));
202
+ const dec2 = decryptAtom({ payload: enc.payload, key: "wrong-key" });
203
+ ok("解密·错误密钥应失败", dec2.success === false, dec2.error.slice(0, 40));
204
+ const enc2 = encryptAtom({ text: "same text", key: "k" });
205
+ ok("加密·随机 IV", enc2.payload !== encryptAtom({ text: "same text", key: "k" }).payload, "两次加密结果不同(IV 随机)");
206
+
207
+ // 无障碍阅读
208
+ const ar = accessibleRead({ text: "# 标题\n这是一段正文内容。\n- 列表项一\n- 列表项二\n> 引用内容" });
209
+ ok("无障碍阅读", ar.块数 === 5 && ar.阅读块[0].type === "heading", JSON.stringify({ 块数: ar.块数, 类型: ar.阅读块.map(b => b.type) }));
210
+
211
+ console.log();
212
+ console.log(`合计 ${pass + fail} 项 | 通过 ${pass} | 失败 ${fail}`);
213
+ console.log(fail === 0 ? "总判定:PASS" : "总判定:FAIL");
214
+ process.exit(fail === 0 ? 0 : 1);
215
+ }