ppxans-harness 2.6.0 → 3.2.1

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 (189) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +195 -195
  3. package/bin/ppx-channels.js +2 -2
  4. package/bin/ppx-serve.js +5 -5
  5. package/bin/ppx-setup.js +124 -0
  6. package/bin/ppx-web.js +140 -0
  7. package/bin/ppx.js +2 -2
  8. package/config/identity.md +6 -6
  9. package/config/ishiki.md +16 -16
  10. package/config/ppx.json +6 -124
  11. package/config/ppx.json.example +143 -0
  12. package/package.json +16 -6
  13. package/skills/.usage.json +6 -0
  14. package/skills/agent-professional-training/SKILL.md +94 -0
  15. package/skills/agent-professional-training/trajectories/2026-09-11-dpo-pairs.jsonl +2 -0
  16. package/skills/agent-professional-training/trajectories/2026-09-11-round2-trajectory.json +15 -0
  17. package/skills/agent-professional-training/trajectories/2026-09-13-first-run-trajectory.json +17 -0
  18. package/skills/brainstorm/SKILL.md +24 -0
  19. package/skills/cupid-lover-comms/SKILL.md +37 -0
  20. package/skills/debug/SKILL.md +26 -0
  21. package/skills/plan/SKILL.md +25 -0
  22. package/skills/ponytail/SKILL.md +25 -0
  23. package/skills/ppx-memory/SKILL.md +91 -0
  24. package/skills/ppx-memory/scripts/cli.js +192 -0
  25. package/skills/ppx-memory/scripts/experience.js +133 -0
  26. package/skills/ppx-memory/scripts/fact-store.js +842 -0
  27. package/skills/ppx-memory/scripts/l0.js +52 -0
  28. package/skills/ppx-memory/scripts/l2.js +146 -0
  29. package/skills/ppx-memory/scripts/l3.js +112 -0
  30. package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
  31. package/skills/ppx-memory/scripts/pii.js +42 -0
  32. package/skills/ppx-memory/scripts/schema.js +80 -0
  33. package/skills/ppx-memory/scripts/session.js +398 -0
  34. package/skills/ppx-memory/scripts/similarity.js +43 -0
  35. package/skills/ppx-memory/scripts/store.js +116 -0
  36. package/skills/ppx-memory/scripts/wal.js +38 -0
  37. package/skills/ppx-selfheal/SKILL.md +24 -0
  38. package/skills/ppx-selfheal/scripts/cli.js +80 -0
  39. package/skills/ppx-selfheal/scripts/healer.js +184 -0
  40. package/skills/ppx-selfheal/scripts/logger.js +17 -0
  41. package/skills/ppx-selfheal/scripts/store.js +116 -0
  42. package/skills/prompt-depth-kit/SKILL.md +28 -0
  43. package/skills/session-naming/SKILL.md +36 -0
  44. package/skills/verify/SKILL.md +25 -0
  45. package/src/agent/index.js +1352 -717
  46. package/src/agent/prompts.js +46 -2
  47. package/src/aml-server.js +197 -151
  48. package/src/ans/eviction.js +123 -143
  49. package/src/ans/guard.js +159 -120
  50. package/src/ans/lifecycle.js +96 -93
  51. package/src/ans/proactive.js +112 -129
  52. package/src/ans/reward.js +95 -111
  53. package/src/ans/values.js +15 -15
  54. package/src/audit/audit-chain.js +43 -7
  55. package/src/audit/verifier.js +157 -120
  56. package/src/bus/circuit-breaker.js +9 -1
  57. package/src/bus/runtime-bus.js +107 -93
  58. package/src/channels/base.js +57 -34
  59. package/src/channels/feishu.js +118 -126
  60. package/src/channels/http.js +1090 -651
  61. package/src/channels/index.js +111 -110
  62. package/src/channels/log.js +29 -29
  63. package/src/channels/wechat-crypto.js +73 -74
  64. package/src/channels/wechat.js +191 -197
  65. package/src/channels/workspace.js +94 -0
  66. package/src/channels-cli.js +126 -124
  67. package/src/cli.js +129 -120
  68. package/src/commands/index.js +142 -0
  69. package/src/config/channels.js +137 -170
  70. package/src/config/index.js +277 -224
  71. package/src/config/placeholder.js +31 -0
  72. package/src/config/providers.js +148 -182
  73. package/src/config/settings.js +156 -182
  74. package/src/core/policy.js +69 -18
  75. package/src/core/trace.js +8 -6
  76. package/src/edit/editblock.js +266 -0
  77. package/src/edit/snapshot.js +67 -0
  78. package/src/evidence/index.js +153 -0
  79. package/src/evolve/playbook.js +9 -10
  80. package/src/hooks/index.js +113 -0
  81. package/src/llm/client.js +4 -3
  82. package/src/llm/dsml.js +74 -74
  83. package/src/llm/embedder.js +41 -35
  84. package/src/llm/fence.js +52 -52
  85. package/src/llm/index.js +4 -4
  86. package/src/llm/local-embedder.js +94 -0
  87. package/src/llm/presets.js +113 -0
  88. package/src/llm/pricing.js +93 -0
  89. package/src/llm/retry.js +73 -73
  90. package/src/llm/router.js +92 -89
  91. package/src/mcp/admin.js +44 -10
  92. package/src/mcp/client.js +487 -477
  93. package/src/mcp/http.js +23 -37
  94. package/src/mcp/index.js +116 -116
  95. package/src/mcp/server.js +23 -18
  96. package/src/mcp/tasks.js +9 -14
  97. package/src/memory/asset-hub.js +6 -11
  98. package/src/memory/canvas.js +2 -5
  99. package/src/memory/compaction.js +28 -28
  100. package/src/memory/experience.js +133 -122
  101. package/src/memory/fact-store.js +914 -698
  102. package/src/memory/failure-episode.js +20 -11
  103. package/src/memory/fork.js +17 -8
  104. package/src/memory/index.js +8 -6
  105. package/src/memory/l0.js +53 -52
  106. package/src/memory/l2.js +145 -130
  107. package/src/memory/l3.js +111 -111
  108. package/src/memory/legion-board.js +71 -0
  109. package/src/memory/memory-ticker.js +239 -240
  110. package/src/memory/session.js +398 -397
  111. package/src/memory/sqlite-store.js +581 -0
  112. package/src/mode/blackboard.js +49 -49
  113. package/src/mode/graph.js +42 -41
  114. package/src/mode/index.js +64 -64
  115. package/src/mode/legion.js +54 -51
  116. package/src/mode/plan-exec.js +50 -50
  117. package/src/mode/router.js +28 -40
  118. package/src/orchestrator/agent-worker.js +69 -69
  119. package/src/orchestrator/dag.js +90 -83
  120. package/src/orchestrator/experts.js +76 -0
  121. package/src/orchestrator/index.js +1 -1
  122. package/src/orchestrator/legion.js +179 -187
  123. package/src/orchestrator/supervisor.js +6 -8
  124. package/src/permissions/index.js +378 -0
  125. package/src/persona/index.js +28 -29
  126. package/src/plugin/builtin.js +313 -215
  127. package/src/plugin/context.js +80 -79
  128. package/src/plugin/index.js +62 -62
  129. package/src/plugin/v3.js +73 -0
  130. package/src/protocol/index.js +148 -0
  131. package/src/repomap/index.js +309 -0
  132. package/src/review/index.js +393 -0
  133. package/src/seam/registry.js +3 -0
  134. package/src/seam/shell.js +55 -55
  135. package/src/security/injection.js +79 -0
  136. package/src/selfheal/evolve.js +67 -67
  137. package/src/selfheal/healer.js +184 -167
  138. package/src/selfheal/run.js +9 -9
  139. package/src/server.js +63 -60
  140. package/src/services/diagnose.js +180 -0
  141. package/src/services/learning-service.js +9 -0
  142. package/src/services/memory-health.js +34 -6
  143. package/src/services/memory-service.js +42 -9
  144. package/src/services/triage.js +138 -0
  145. package/src/session/parts.js +76 -0
  146. package/src/session/projection.js +73 -0
  147. package/src/session/rollout.js +54 -0
  148. package/src/session/turn.js +137 -0
  149. package/src/skills/lint.js +72 -0
  150. package/src/skills/loader.js +231 -150
  151. package/src/skills/search.js +58 -0
  152. package/src/skills/verify.js +95 -100
  153. package/src/tools/advanced.js +388 -352
  154. package/src/tools/builtin.js +384 -297
  155. package/src/tools/catalog.js +283 -159
  156. package/src/tools/command-guard.js +112 -112
  157. package/src/tools/custom.js +47 -47
  158. package/src/tools/delegate.js +383 -297
  159. package/src/tools/document.js +254 -253
  160. package/src/tools/git.js +151 -0
  161. package/src/tools/governance.js +47 -20
  162. package/src/tools/index.js +16 -11
  163. package/src/tools/methods.js +178 -178
  164. package/src/tools/ocr.js +59 -59
  165. package/src/tools/sandbox-worker.js +40 -0
  166. package/src/tools/sandbox.js +92 -0
  167. package/src/tools/seam.js +162 -125
  168. package/src/tools/selfmod.js +196 -176
  169. package/src/tools/v3.js +225 -0
  170. package/src/tools/vad.js +176 -0
  171. package/src/tools/voice.js +238 -0
  172. package/src/utils/async.js +14 -0
  173. package/src/utils/config-file.js +53 -0
  174. package/src/utils/crashguard.js +88 -0
  175. package/src/utils/http.js +53 -0
  176. package/src/utils/id.js +8 -0
  177. package/src/utils/json-state.js +33 -0
  178. package/src/utils/logger.js +17 -17
  179. package/src/utils/ndjson.js +25 -0
  180. package/src/utils/pii.js +42 -42
  181. package/src/utils/rate-limit.js +50 -0
  182. package/src/utils/schema.js +80 -0
  183. package/src/utils/similarity.js +43 -0
  184. package/src/utils/store.js +170 -108
  185. package/src/utils/text.js +15 -15
  186. package/src/utils/trace.js +153 -153
  187. package/src/utils/wal.js +39 -0
  188. package/src/utils/winutf8.js +16 -15
  189. package/src/wiki/index.js +170 -0
package/src/memory/l3.js CHANGED
@@ -1,112 +1,112 @@
1
- // src/memory/l3.js - L3 核心画像 (腾讯风格 persona generation)
2
- // 从记忆提炼: 用户画像 (user.persona.md) + agent 人格 (agent.persona.md)
3
- // 零依赖: 高频词统计 + 主题聚合, 输出结构化画像
4
- import fs from "node:fs";
5
- import path from "node:path";
6
- import { ensureDir, readText, writeText, logicalDay } from "../utils/store.js";
7
-
8
- const STOP = new Set(["这个","那个","我们","你们","他们","什么","怎么","可以","一个","就是","知道","没有","如果","因为","所以","但是","然后","现在","今天","昨天","明天","已经","还有","所有","这样","那样","自己","的东西","的事情","一下","一点","一些","这些","那些","东西","事情","问题","觉得","应该","需要","开始","继续","大家","真的","只是","可能","不是","都是","一直","非常","其实","最后","主要","联系","关系","喜欢","讨厌","不要","想要","认为"]);
9
-
10
- export class PersonaStore {
11
- constructor(dataDir, { userName = "兄弟" } = {}) {
12
- this.dir = path.join(dataDir, "memory", "l3");
13
- ensureDir(this.dir);
14
- this.userFile = path.join(this.dir, "user.persona.md");
15
- this.agentFile = path.join(this.dir, "agent.persona.md");
16
- this.userName = userName;
17
- }
18
-
19
- // 从一批事实提炼用户画像
20
- buildUserPersona(facts, { force = false } = {}) {
21
- if (!force && this._exists(this.userFile)) return this._read(this.userFile);
22
- // 聚焦用户相关的记忆 (来源: 对话 / LLM提炼 / 主动记 / 手动 / 用户分享)
23
- // 注: 事实的来源标记在 source 字段 (type 恒为 general), 之前按 type 过滤永远为空 -> 修正为按 source
24
- const USER_SOURCES = ["conversation", "extract", "agent-self", "manual", "user-shared"];
25
- const userFacts = facts.filter((f) => USER_SOURCES.includes(f.source) || USER_SOURCES.includes(f.type));
26
- const interests = this._topTopics(userFacts);
27
- // 记忆概要: 内容去重后取最近 10 条 (防 LLM 提炼变体/重复记忆稀释画像)
28
- const uniq = this._uniqByContent(userFacts).slice(-10);
29
- const md = `# ${this.userName} 的用户画像
30
-
31
- > 由皮皮虾 L3 画像引擎生成 | 更新: ${logicalDay()}
32
-
33
- ## 关注主题
34
- ${interests.length ? interests.map(([w, n]) => `- ${w} (出现${n}次)`).join("\n") : "- 暂无足够数据"}
35
-
36
- ## 记忆概要
37
- ${uniq.map((f) => `- ${f.content}`).join("\n") || "- 暂无"}
38
-
39
- ## 画像版本
40
- - 生成时间: ${logicalDay()}
41
- - 数据来源: 对话记忆 + 用户主动分享
42
- `;
43
- writeText(this.userFile, md);
44
- return md;
45
- }
46
-
47
- // 提炼 agent 自身人格 (从经验/工具使用学习)
48
- buildAgentPersona(lessons, { force = false } = {}) {
49
- if (!force && this._exists(this.agentFile)) return this._read(this.agentFile);
50
- // 学到的经验: 内容去重后取最近 10 条 (防重复经验污染自我画像)
51
- const uniq = this._uniqByContent(lessons, (l) => l.lesson).slice(-10);
52
- const md = `# 皮皮虾 自我画像
53
-
54
- > 从经验库自动学习 | 更新: ${logicalDay()}
55
-
56
- ## 学到的经验
57
- ${uniq.map((l) => `- ${l.lesson}`).join("\n") || "- 暂无"}
58
-
59
- ## 能力画像
60
- - 工具: 文件操作 / 命令执行 / 搜索 / HTTP / 定时任务
61
- - 记忆: 四层架构 (L0对话→L1原子→L2场景→L3画像)
62
- - 自愈: 崩溃恢复 / 数据修复
63
- - 军团: 多进程并行协作
64
- `;
65
- writeText(this.agentFile, md);
66
- return md;
67
- }
68
-
69
- // 内容去重: 按归一化内容 (去空白折叠) 过滤, 保留首次出现的条目
70
- _uniqByContent(items, pick = (x) => x.content) {
71
- const seen = new Set();
72
- const out = [];
73
- for (const it of items || []) {
74
- const key = String(pick(it) || "").trim().replace(/\s+/g, " ");
75
- if (!key || seen.has(key)) continue;
76
- seen.add(key);
77
- out.push(it);
78
- }
79
- return out;
80
- }
81
-
82
- // 读取已生成的画像 (供 agent._context 注入; 未生成返回 "")
83
- userPersona() { return this._read(this.userFile); }
84
- agentPersona() { return this._read(this.agentFile); }
85
-
86
- // 可观测: L3 画像更新时间 (无文件返回 null), 供 agent.stats() 聚合
87
- stats() {
88
- return {
89
- user_updated: this._mtime(this.userFile),
90
- agent_updated: this._mtime(this.agentFile),
91
- };
92
- }
93
-
94
- _mtime(f) {
95
- try { return new Date(fs.statSync(f).mtime).toISOString().slice(0, 10); } catch { return null; }
96
- }
97
-
98
- _topTopics(facts) {
99
- const freq = new Map();
100
- for (const f of facts) {
101
- const words = String(f.content).match(/[\u4e00-\u9fa5]{2,4}/g) || [];
102
- for (const w of words) {
103
- if (STOP.has(w)) continue;
104
- freq.set(w, (freq.get(w) || 0) + 1);
105
- }
106
- }
107
- return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
108
- }
109
-
110
- _exists(f) { try { return fs.existsSync(f); } catch { return false; } }
111
- _read(f) { return readText(f, ""); }
1
+ // src/memory/l3.js - L3 核心画像 (腾讯风格 persona generation)
2
+ // 从记忆提炼: 用户画像 (user.persona.md) + agent 人格 (agent.persona.md)
3
+ // 零依赖: 高频词统计 + 主题聚合, 输出结构化画像
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { ensureDir, readText, writeText, logicalDay } from "../utils/store.js";
7
+
8
+ const STOP = new Set(["这个","那个","我们","你们","他们","什么","怎么","可以","一个","就是","知道","没有","如果","因为","所以","但是","然后","现在","今天","昨天","明天","已经","还有","所有","这样","那样","自己","的东西","的事情","一下","一点","一些","这些","那些","东西","事情","问题","觉得","应该","需要","开始","继续","大家","真的","只是","可能","不是","都是","一直","非常","其实","最后","主要","联系","关系","喜欢","讨厌","不要","想要","认为"]);
9
+
10
+ export class PersonaStore {
11
+ constructor(dataDir, { userName = "兄弟" } = {}) {
12
+ this.dir = path.join(dataDir, "memory", "l3");
13
+ ensureDir(this.dir);
14
+ this.userFile = path.join(this.dir, "user.persona.md");
15
+ this.agentFile = path.join(this.dir, "agent.persona.md");
16
+ this.userName = userName;
17
+ }
18
+
19
+ // 从一批事实提炼用户画像
20
+ buildUserPersona(facts, { force = false } = {}) {
21
+ if (!force && this._exists(this.userFile)) return this._read(this.userFile);
22
+ // 聚焦用户相关的记忆 (来源: 对话 / LLM提炼 / 主动记 / 手动 / 用户分享)
23
+ // 注: 事实的来源标记在 source 字段 (type 恒为 general), 之前按 type 过滤永远为空 -> 修正为按 source
24
+ const USER_SOURCES = ["conversation", "extract", "agent-self", "manual", "user-shared"];
25
+ const userFacts = facts.filter((f) => USER_SOURCES.includes(f.source) || USER_SOURCES.includes(f.type));
26
+ const interests = this._topTopics(userFacts);
27
+ // 记忆概要: 内容去重后取最近 10 条 (防 LLM 提炼变体/重复记忆稀释画像)
28
+ const uniq = this._uniqByContent(userFacts).slice(-10);
29
+ const md = `# ${this.userName} 的用户画像
30
+
31
+ > 由皮皮虾 L3 画像引擎生成 | 更新: ${logicalDay()}
32
+
33
+ ## 关注主题
34
+ ${interests.length ? interests.map(([w, n]) => `- ${w} (出现${n}次)`).join("\n") : "- 暂无足够数据"}
35
+
36
+ ## 记忆概要
37
+ ${uniq.map((f) => `- ${f.content}`).join("\n") || "- 暂无"}
38
+
39
+ ## 画像版本
40
+ - 生成时间: ${logicalDay()}
41
+ - 数据来源: 对话记忆 + 用户主动分享
42
+ `;
43
+ writeText(this.userFile, md);
44
+ return md;
45
+ }
46
+
47
+ // 提炼 agent 自身人格 (从经验/工具使用学习)
48
+ buildAgentPersona(lessons, { force = false } = {}) {
49
+ if (!force && this._exists(this.agentFile)) return this._read(this.agentFile);
50
+ // 学到的经验: 内容去重后取最近 10 条 (防重复经验污染自我画像)
51
+ const uniq = this._uniqByContent(lessons, (l) => l.lesson).slice(-10);
52
+ const md = `# 皮皮虾 自我画像
53
+
54
+ > 从经验库自动学习 | 更新: ${logicalDay()}
55
+
56
+ ## 学到的经验
57
+ ${uniq.map((l) => `- ${l.lesson}`).join("\n") || "- 暂无"}
58
+
59
+ ## 能力画像
60
+ - 工具: 文件操作 / 命令执行 / 搜索 / HTTP / 定时任务
61
+ - 记忆: 四层架构 (L0对话→L1原子→L2场景→L3画像)
62
+ - 自愈: 崩溃恢复 / 数据修复
63
+ - 军团: 多进程并行协作
64
+ `;
65
+ writeText(this.agentFile, md);
66
+ return md;
67
+ }
68
+
69
+ // 内容去重: 按归一化内容 (去空白折叠) 过滤, 保留首次出现的条目
70
+ _uniqByContent(items, pick = (x) => x.content) {
71
+ const seen = new Set();
72
+ const out = [];
73
+ for (const it of items || []) {
74
+ const key = String(pick(it) || "").trim().replace(/\s+/g, " ");
75
+ if (!key || seen.has(key)) continue;
76
+ seen.add(key);
77
+ out.push(it);
78
+ }
79
+ return out;
80
+ }
81
+
82
+ // 读取已生成的画像 (供 agent._context 注入; 未生成返回 "")
83
+ userPersona() { return this._read(this.userFile); }
84
+ agentPersona() { return this._read(this.agentFile); }
85
+
86
+ // 可观测: L3 画像更新时间 (无文件返回 null), 供 agent.stats() 聚合
87
+ stats() {
88
+ return {
89
+ user_updated: this._mtime(this.userFile),
90
+ agent_updated: this._mtime(this.agentFile),
91
+ };
92
+ }
93
+
94
+ _mtime(f) {
95
+ try { return new Date(fs.statSync(f).mtime).toISOString().slice(0, 10); } catch { return null; }
96
+ }
97
+
98
+ _topTopics(facts) {
99
+ const freq = new Map();
100
+ for (const f of facts) {
101
+ const words = String(f.content).match(/[\u4e00-\u9fa5]{2,4}/g) || [];
102
+ for (const w of words) {
103
+ if (STOP.has(w)) continue;
104
+ freq.set(w, (freq.get(w) || 0) + 1);
105
+ }
106
+ }
107
+ return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
108
+ }
109
+
110
+ _exists(f) { try { return fs.existsSync(f); } catch { return false; } }
111
+ _read(f) { return readText(f, ""); }
112
112
  }
@@ -0,0 +1,71 @@
1
+ // src/memory/legion-board.js - 军团共享记忆板 (2026-10-02)
2
+ // 跨 agent 实时共享知识: 主 agent 与所有 worker 子 agent 通过同一全局文件互通。
3
+ // 与 FactStore (内存快照, 私有) 不同: 每次读/写都走盘 + 文件锁, 保证多进程实时一致性。
4
+ // 并发模式对齐 Experience: 锁内「重读 → 改 → 写」, 防陈旧覆盖。
5
+ import path from "node:path";
6
+ import { ensureDir, readJson, writeJson, nowISO, withFileLock } from "../utils/store.js";
7
+
8
+ export class LegionBoard {
9
+ constructor(globalDataDir, opts = {}) {
10
+ this.dir = path.join(globalDataDir, "legion");
11
+ ensureDir(this.dir);
12
+ this.file = path.join(this.dir, "board.json");
13
+ this.opts = {
14
+ maxEntries: 500, // 容量上限: 超限裁剪最旧 (FIFO, 记忆板是临时协作区不是档案库)
15
+ ttlDays: 7, // 条目存活期: 0/负 = 永不过期
16
+ ...opts,
17
+ };
18
+ }
19
+
20
+ // 发布一条共享知识 (topic 可选作频道, tags 可选作检索辅助)
21
+ publish({ from, topic, content, tags = [] }) {
22
+ if (!content || !String(content).trim()) throw new Error("board_publish: content 不能为空");
23
+ return withFileLock(this.file, () => {
24
+ const entries = readJson(this.file, []);
25
+ const entry = {
26
+ id: `bd_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
27
+ from: String(from || "unknown").slice(0, 64),
28
+ topic: String(topic || "general").slice(0, 64),
29
+ content: String(content).slice(0, 2000),
30
+ tags: (Array.isArray(tags) ? tags : []).map((t) => String(t).slice(0, 32)).slice(0, 8),
31
+ ts: nowISO(),
32
+ };
33
+ entries.push(entry);
34
+ // 写入时统一裁剪: TTL 过滤 + 容量 FIFO
35
+ const cutoff = this.opts.ttlDays > 0 ? Date.now() - this.opts.ttlDays * 86400000 : 0;
36
+ const kept = entries
37
+ .filter((x) => !cutoff || new Date(x.ts).getTime() >= cutoff)
38
+ .slice(-this.opts.maxEntries);
39
+ writeJson(this.file, kept);
40
+ return entry;
41
+ });
42
+ }
43
+
44
+ // 查询: 读时重读磁盘 → 实时可见其他 agent 刚发布的内容 (跨进程)
45
+ query({ q, topic, from, limit = 10 } = {}) {
46
+ let entries = readJson(this.file, []);
47
+ if (topic) entries = entries.filter((x) => x.topic === topic);
48
+ if (from) entries = entries.filter((x) => x.from === from);
49
+ if (q) {
50
+ const kw = String(q).toLowerCase();
51
+ entries = entries.filter((x) =>
52
+ x.content.toLowerCase().includes(kw) ||
53
+ x.topic.toLowerCase().includes(kw) ||
54
+ (x.tags || []).some((t) => t.toLowerCase().includes(kw))
55
+ );
56
+ }
57
+ // 新的在前
58
+ entries.sort((a, b) => (a.ts < b.ts ? 1 : -1));
59
+ return entries.slice(0, Math.max(1, Math.min(50, Number(limit) || 10)));
60
+ }
61
+
62
+ // 概况: 当前板上条数 + 参与过的 agent (可观测性)
63
+ stats() {
64
+ const entries = readJson(this.file, []);
65
+ return {
66
+ count: entries.length,
67
+ agents: [...new Set(entries.map((x) => x.from))],
68
+ topics: [...new Set(entries.map((x) => x.topic))],
69
+ };
70
+ }
71
+ }